uniweb 0.12.0 → 0.12.1
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 +6 -6
- package/src/commands/add.js +2 -4
- package/src/framework-index.json +7 -7
- package/src/index.js +5 -1
- package/src/templates/validator.js +52 -4
- package/src/utils/scaffold.js +59 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uniweb",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "Create structured Vite + React sites with content/code separation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,14 +41,14 @@
|
|
|
41
41
|
"js-yaml": "^4.1.0",
|
|
42
42
|
"prompts": "^2.4.2",
|
|
43
43
|
"tar": "^7.0.0",
|
|
44
|
-
"@uniweb/core": "0.7.
|
|
45
|
-
"@uniweb/kit": "0.9.
|
|
46
|
-
"@uniweb/runtime": "0.8.
|
|
44
|
+
"@uniweb/core": "0.7.9",
|
|
45
|
+
"@uniweb/kit": "0.9.9",
|
|
46
|
+
"@uniweb/runtime": "0.8.10"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"@uniweb/build": "0.13.
|
|
49
|
+
"@uniweb/build": "0.13.1",
|
|
50
50
|
"@uniweb/content-reader": "1.1.9",
|
|
51
|
-
"@uniweb/semantic-parser": "1.1.
|
|
51
|
+
"@uniweb/semantic-parser": "1.1.16"
|
|
52
52
|
},
|
|
53
53
|
"peerDependenciesMeta": {
|
|
54
54
|
"@uniweb/build": {
|
package/src/commands/add.js
CHANGED
|
@@ -730,12 +730,9 @@ async function applyFromTemplate(templateId, packageType, targetDir, projectName
|
|
|
730
730
|
const metadata = await validateTemplate(resolved.path, {})
|
|
731
731
|
|
|
732
732
|
// Look in contentDirs for matching package type
|
|
733
|
-
let contentDir = null
|
|
734
733
|
const match = metadata.contentDirs.find(d => d.type === packageType) ||
|
|
735
734
|
metadata.contentDirs.find(d => d.name === packageType)
|
|
736
|
-
|
|
737
|
-
contentDir = match.dir
|
|
738
|
-
}
|
|
735
|
+
const contentDir = match ? match.dir : null
|
|
739
736
|
|
|
740
737
|
if (contentDir) {
|
|
741
738
|
info(`Applying ${metadata.name} content...`)
|
|
@@ -744,6 +741,7 @@ async function applyFromTemplate(templateId, packageType, targetDir, projectName
|
|
|
744
741
|
versions: getVersionsForTemplates(),
|
|
745
742
|
}, {
|
|
746
743
|
onProgress: (msg) => info(` ${msg}`),
|
|
744
|
+
renames: match.renames,
|
|
747
745
|
})
|
|
748
746
|
|
|
749
747
|
// Merge template dependencies
|
package/src/framework-index.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-04-29T13:
|
|
3
|
+
"generatedAt": "2026-04-29T13:42:04.684Z",
|
|
4
4
|
"packages": {
|
|
5
5
|
"@uniweb/build": {
|
|
6
|
-
"version": "0.13.
|
|
6
|
+
"version": "0.13.1",
|
|
7
7
|
"path": "framework/build",
|
|
8
8
|
"deps": [
|
|
9
9
|
"@uniweb/content-reader",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"deps": []
|
|
25
25
|
},
|
|
26
26
|
"@uniweb/core": {
|
|
27
|
-
"version": "0.7.
|
|
27
|
+
"version": "0.7.9",
|
|
28
28
|
"path": "framework/core",
|
|
29
29
|
"deps": [
|
|
30
30
|
"@uniweb/semantic-parser",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"deps": []
|
|
43
43
|
},
|
|
44
44
|
"@uniweb/kit": {
|
|
45
|
-
"version": "0.9.
|
|
45
|
+
"version": "0.9.9",
|
|
46
46
|
"path": "framework/kit",
|
|
47
47
|
"deps": [
|
|
48
48
|
"@uniweb/core"
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"deps": []
|
|
60
60
|
},
|
|
61
61
|
"@uniweb/runtime": {
|
|
62
|
-
"version": "0.8.
|
|
62
|
+
"version": "0.8.10",
|
|
63
63
|
"path": "framework/runtime",
|
|
64
64
|
"deps": [
|
|
65
65
|
"@uniweb/core",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"deps": []
|
|
78
78
|
},
|
|
79
79
|
"@uniweb/semantic-parser": {
|
|
80
|
-
"version": "1.1.
|
|
80
|
+
"version": "1.1.16",
|
|
81
81
|
"path": "framework/semantic-parser",
|
|
82
82
|
"deps": []
|
|
83
83
|
},
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"deps": []
|
|
93
93
|
},
|
|
94
94
|
"@uniweb/unipress": {
|
|
95
|
-
"version": "0.
|
|
95
|
+
"version": "0.4.0",
|
|
96
96
|
"path": "framework/unipress",
|
|
97
97
|
"deps": [
|
|
98
98
|
"@uniweb/build",
|
package/src/index.js
CHANGED
|
@@ -347,7 +347,11 @@ async function createFromContentTemplate(projectDir, projectName, metadata, temp
|
|
|
347
347
|
const contentDir = findContentDirFor(metadata.contentDirs, pkg)
|
|
348
348
|
if (contentDir) {
|
|
349
349
|
onProgress?.(`Applying ${metadata.name} content to ${pkg.name}...`)
|
|
350
|
-
await applyContent(contentDir.dir, fullPath, { projectName }, {
|
|
350
|
+
await applyContent(contentDir.dir, fullPath, { projectName }, {
|
|
351
|
+
onProgress,
|
|
352
|
+
onWarning,
|
|
353
|
+
renames: contentDir.renames,
|
|
354
|
+
})
|
|
351
355
|
}
|
|
352
356
|
|
|
353
357
|
// Merge template dependencies into package.json
|
|
@@ -175,7 +175,7 @@ export async function validateTemplate(templateRoot, options = {}) {
|
|
|
175
175
|
*
|
|
176
176
|
* @param {string} templateRoot - Root of the template (contains template.json)
|
|
177
177
|
* @param {Object} metadata - Parsed template.json
|
|
178
|
-
* @returns {Array<Object>} Content directories: [{ type, name, dir, foundation? }]
|
|
178
|
+
* @returns {Array<Object>} Content directories: [{ type, name, dir, foundation?, renames? }]
|
|
179
179
|
*/
|
|
180
180
|
export function resolveContentDirs(templateRoot, metadata) {
|
|
181
181
|
const dirs = []
|
|
@@ -185,19 +185,25 @@ export function resolveContentDirs(templateRoot, metadata) {
|
|
|
185
185
|
for (const pkg of metadata.packages) {
|
|
186
186
|
const dir = path.join(templateRoot, pkg.name)
|
|
187
187
|
if (existsSync(dir)) {
|
|
188
|
-
|
|
188
|
+
const entry = {
|
|
189
189
|
type: pkg.type,
|
|
190
190
|
name: pkg.name,
|
|
191
191
|
dir,
|
|
192
192
|
...(pkg.foundation ? { foundation: pkg.foundation } : {}),
|
|
193
|
-
}
|
|
193
|
+
}
|
|
194
|
+
if (entry.type === 'foundation' || entry.type === 'extension') {
|
|
195
|
+
applyLegacyFoundationLayout(entry)
|
|
196
|
+
}
|
|
197
|
+
dirs.push(entry)
|
|
194
198
|
}
|
|
195
199
|
}
|
|
196
200
|
} else {
|
|
197
201
|
// Standard template: look for foundation/ and site/
|
|
198
202
|
const foundationDir = path.join(templateRoot, 'foundation')
|
|
199
203
|
if (existsSync(foundationDir)) {
|
|
200
|
-
|
|
204
|
+
const entry = { type: 'foundation', name: 'foundation', dir: foundationDir }
|
|
205
|
+
applyLegacyFoundationLayout(entry)
|
|
206
|
+
dirs.push(entry)
|
|
201
207
|
}
|
|
202
208
|
|
|
203
209
|
const siteDir = path.join(templateRoot, 'site')
|
|
@@ -209,6 +215,48 @@ export function resolveContentDirs(templateRoot, metadata) {
|
|
|
209
215
|
return dirs
|
|
210
216
|
}
|
|
211
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Detect and unwrap the legacy foundation layout.
|
|
220
|
+
*
|
|
221
|
+
* Old templates (published in the `uniweb/templates` releases up to
|
|
222
|
+
* v0.7.x) shipped foundation content nested one level deeper, with the
|
|
223
|
+
* package source under `foundation/src/` and the user-authored
|
|
224
|
+
* declarations file named `foundation.js`:
|
|
225
|
+
*
|
|
226
|
+
* <template>/foundation/src/foundation.js
|
|
227
|
+
* <template>/foundation/src/sections/...
|
|
228
|
+
* <template>/foundation/src/components/...
|
|
229
|
+
*
|
|
230
|
+
* The current layout is flat — the foundation package root contains
|
|
231
|
+
* the source directly, and the declarations file is named `main.js`:
|
|
232
|
+
*
|
|
233
|
+
* <template>/foundation/main.js
|
|
234
|
+
* <template>/foundation/sections/...
|
|
235
|
+
*
|
|
236
|
+
* The CLI scaffolds the new flat shape into the project's `src/`
|
|
237
|
+
* directory, so an unmodified copy of an old-format template would
|
|
238
|
+
* land at `src/src/foundation.js` (extra `src/` layer + old name).
|
|
239
|
+
*
|
|
240
|
+
* This helper detects the legacy marker (`<dir>/src/foundation.js`),
|
|
241
|
+
* mutates the contentDir entry to point at the inner `src/` directory,
|
|
242
|
+
* and records a top-level rename so `foundation.js` is written as
|
|
243
|
+
* `main.js`. Once `uniweb/templates` is republished with the flat
|
|
244
|
+
* layout, this branch becomes a no-op.
|
|
245
|
+
*/
|
|
246
|
+
function applyLegacyFoundationLayout(entry) {
|
|
247
|
+
const innerSrc = path.join(entry.dir, 'src')
|
|
248
|
+
if (!existsSync(innerSrc)) return
|
|
249
|
+
|
|
250
|
+
const legacyMain = path.join(innerSrc, 'foundation.js')
|
|
251
|
+
const newMain = path.join(innerSrc, 'main.js')
|
|
252
|
+
if (!existsSync(legacyMain) && !existsSync(newMain)) return
|
|
253
|
+
|
|
254
|
+
entry.dir = innerSrc
|
|
255
|
+
if (existsSync(legacyMain)) {
|
|
256
|
+
entry.renames = { ...(entry.renames || {}), 'foundation.js': 'main.js' }
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
212
260
|
/**
|
|
213
261
|
* Get list of available templates in a templates directory
|
|
214
262
|
*
|
package/src/utils/scaffold.js
CHANGED
|
@@ -106,6 +106,11 @@ export async function scaffoldSite(targetDir, context, options = {}) {
|
|
|
106
106
|
* @param {string} targetDir - Target directory to overlay onto
|
|
107
107
|
* @param {Object} context - Handlebars context for .hbs files
|
|
108
108
|
* @param {Object} [options] - Processing options
|
|
109
|
+
* @param {Object} [options.renames] - Top-level filename remapping
|
|
110
|
+
* (e.g. `{ 'foundation.js': 'main.js' }`). Applied only at depth 0
|
|
111
|
+
* so renames don't accidentally rewrite same-named files in nested
|
|
112
|
+
* directories. Used to migrate legacy `foundation/foundation.js`
|
|
113
|
+
* templates onto the new flat `src/main.js` layout.
|
|
109
114
|
*/
|
|
110
115
|
export async function applyContent(contentDir, targetDir, context, options = {}) {
|
|
111
116
|
if (!existsSync(contentDir)) return
|
|
@@ -127,29 +132,41 @@ export async function applyContent(contentDir, targetDir, context, options = {})
|
|
|
127
132
|
'site.yml': ['name', 'foundation'],
|
|
128
133
|
}
|
|
129
134
|
|
|
130
|
-
await copyContentRecursive(contentDir, targetDir, context, STRUCTURAL_FILES, MERGE_FILES, options)
|
|
135
|
+
await copyContentRecursive(contentDir, targetDir, context, STRUCTURAL_FILES, MERGE_FILES, options, 0)
|
|
131
136
|
}
|
|
132
137
|
|
|
133
138
|
/**
|
|
134
|
-
* Recursively copy content files, skipping structural files
|
|
139
|
+
* Recursively copy content files, skipping structural files.
|
|
140
|
+
*
|
|
141
|
+
* `depth` is tracked so the `renames` map (passed via options) only
|
|
142
|
+
* applies at depth 0 — the top of the content directory. Without this
|
|
143
|
+
* guard, a rename like `foundation.js → main.js` would also rewrite a
|
|
144
|
+
* nested `sections/foo/foundation.js` if one existed, which is not the
|
|
145
|
+
* intent.
|
|
135
146
|
*/
|
|
136
|
-
async function copyContentRecursive(sourceDir, targetDir, context, structuralFiles, mergeFiles, options) {
|
|
147
|
+
async function copyContentRecursive(sourceDir, targetDir, context, structuralFiles, mergeFiles, options, depth = 0) {
|
|
137
148
|
await fs.mkdir(targetDir, { recursive: true })
|
|
138
149
|
|
|
139
150
|
const entries = readdirSync(sourceDir, { withFileTypes: true })
|
|
151
|
+
const renames = (depth === 0 && options.renames) || null
|
|
140
152
|
|
|
141
153
|
for (const entry of entries) {
|
|
142
154
|
const sourcePath = join(sourceDir, entry.name)
|
|
143
155
|
|
|
144
156
|
if (entry.isDirectory()) {
|
|
145
157
|
const targetSubDir = join(targetDir, entry.name)
|
|
146
|
-
await copyContentRecursive(sourcePath, targetSubDir, context, structuralFiles, mergeFiles, options)
|
|
158
|
+
await copyContentRecursive(sourcePath, targetSubDir, context, structuralFiles, mergeFiles, options, depth + 1)
|
|
147
159
|
} else {
|
|
148
160
|
// Determine the output filename (strip .hbs extension)
|
|
149
|
-
|
|
161
|
+
let outputName = entry.name.endsWith('.hbs')
|
|
150
162
|
? entry.name.slice(0, -4)
|
|
151
163
|
: entry.name
|
|
152
164
|
|
|
165
|
+
// Apply top-level rename (e.g. legacy `foundation.js` → `main.js`)
|
|
166
|
+
if (renames && Object.prototype.hasOwnProperty.call(renames, outputName)) {
|
|
167
|
+
outputName = renames[outputName]
|
|
168
|
+
}
|
|
169
|
+
|
|
153
170
|
// Skip structural files
|
|
154
171
|
if (structuralFiles.has(outputName)) continue
|
|
155
172
|
|
|
@@ -190,8 +207,17 @@ async function copyContentRecursive(sourceDir, targetDir, context, structuralFil
|
|
|
190
207
|
for (const key of preserveKeys) {
|
|
191
208
|
if (existing[key] === undefined) continue
|
|
192
209
|
const baseLine = matchTopLevelLine(existingContent, key)
|
|
193
|
-
if (baseLine)
|
|
210
|
+
if (!baseLine) continue
|
|
211
|
+
// If the new content carries the key, replace its line with
|
|
212
|
+
// the scaffolded value (preserving the user's project/foundation
|
|
213
|
+
// choice). Otherwise insert the line — older content templates
|
|
214
|
+
// (notably `docs/site/site.yml.hbs`) omit `foundation:` entirely,
|
|
215
|
+
// and dropping it leaves the site without a foundation ref so
|
|
216
|
+
// the entry's `import '#foundation/styles'` fails at build time.
|
|
217
|
+
if (matchTopLevelLine(merged, key)) {
|
|
194
218
|
merged = replaceTopLevelLine(merged, key, baseLine)
|
|
219
|
+
} else {
|
|
220
|
+
merged = insertTopLevelLine(merged, baseLine)
|
|
195
221
|
}
|
|
196
222
|
}
|
|
197
223
|
|
|
@@ -213,14 +239,20 @@ async function copyContentRecursive(sourceDir, targetDir, context, structuralFil
|
|
|
213
239
|
/**
|
|
214
240
|
* Apply the built-in starter content
|
|
215
241
|
*
|
|
242
|
+
* The starter ships its foundation content under `cli/starter/foundation/`
|
|
243
|
+
* (a description of the *kind* of content), and is applied into whatever
|
|
244
|
+
* folder the workspace uses for the foundation package — which is `src/`
|
|
245
|
+
* in the current single-foundation layout, not `foundation/`. Callers
|
|
246
|
+
* can override via options when scaffolding multi-foundation workspaces.
|
|
247
|
+
*
|
|
216
248
|
* @param {string} projectDir - Root project directory
|
|
217
249
|
* @param {Object} context - Template context
|
|
218
250
|
* @param {Object} [options] - Processing options
|
|
219
|
-
* @param {string} [options.foundationDir] - Foundation directory name (default: '
|
|
251
|
+
* @param {string} [options.foundationDir] - Foundation directory name (default: 'src')
|
|
220
252
|
* @param {string} [options.siteDir] - Site directory name (default: 'site')
|
|
221
253
|
*/
|
|
222
254
|
export async function applyStarter(projectDir, context, options = {}) {
|
|
223
|
-
const foundationDir = options.foundationDir || '
|
|
255
|
+
const foundationDir = options.foundationDir || 'src'
|
|
224
256
|
const siteDir = options.siteDir || 'site'
|
|
225
257
|
|
|
226
258
|
// Apply foundation starter content
|
|
@@ -266,6 +298,25 @@ function replaceTopLevelLine(content, key, replacement) {
|
|
|
266
298
|
)
|
|
267
299
|
}
|
|
268
300
|
|
|
301
|
+
/**
|
|
302
|
+
* Insert a top-level YAML line into a content string.
|
|
303
|
+
*
|
|
304
|
+
* Used by the merge path when the content template omits a key that
|
|
305
|
+
* the scaffolded base file declared (e.g. an older `site.yml.hbs` with
|
|
306
|
+
* no `foundation:` line). Inserts immediately after the `name:` line
|
|
307
|
+
* if one exists — that's the conventional position for site
|
|
308
|
+
* configuration — and otherwise prepends to the file. The original
|
|
309
|
+
* trailing newline (or lack thereof) of the file is preserved.
|
|
310
|
+
*/
|
|
311
|
+
function insertTopLevelLine(content, line) {
|
|
312
|
+
const nameMatch = content.match(/^name:.*$/m)
|
|
313
|
+
if (nameMatch) {
|
|
314
|
+
const idx = nameMatch.index + nameMatch[0].length
|
|
315
|
+
return content.slice(0, idx) + '\n' + line + content.slice(idx)
|
|
316
|
+
}
|
|
317
|
+
return line + '\n' + content
|
|
318
|
+
}
|
|
319
|
+
|
|
269
320
|
/**
|
|
270
321
|
* Resolve a dependency version string from a template.json entry.
|
|
271
322
|
*
|