uniweb 0.7.1 → 0.7.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/README.md +64 -17
- package/package.json +4 -3
- package/src/commands/add.js +563 -0
- package/src/commands/build.js +49 -6
- package/src/commands/doctor.js +181 -2
- package/src/index.js +273 -131
- package/src/templates/index.js +0 -94
- package/src/templates/processor.js +10 -87
- package/src/templates/resolver.js +3 -3
- package/src/templates/validator.js +59 -17
- package/src/utils/config.js +229 -0
- package/src/utils/scaffold.js +175 -0
- package/templates/{single/foundation → foundation}/package.json.hbs +2 -2
- package/templates/foundation/src/foundation.js.hbs +7 -0
- package/templates/foundation/src/sections/.gitkeep +0 -0
- package/templates/{multi/sites/main → site}/package.json.hbs +2 -2
- package/templates/site/site.yml.hbs +10 -0
- package/templates/site/theme.yml +1 -0
- package/templates/{_shared → workspace}/package.json.hbs +3 -9
- package/templates/workspace/pnpm-workspace.yaml.hbs +4 -0
- package/templates/_shared/pnpm-workspace.yaml +0 -5
- package/templates/multi/README.md.hbs +0 -85
- package/templates/multi/foundations/default/package.json.hbs +0 -38
- package/templates/multi/foundations/default/src/foundation.js +0 -41
- package/templates/multi/package.json.hbs +0 -26
- package/templates/multi/sites/main/pages/home/1-welcome.md.hbs +0 -14
- package/templates/multi/sites/main/site.yml.hbs +0 -12
- package/templates/multi/sites/main/vite.config.js +0 -7
- package/templates/multi/template/.vscode/settings.json +0 -6
- package/templates/multi/template.json +0 -5
- package/templates/single/foundation/src/sections/Section/index.jsx +0 -121
- package/templates/single/foundation/src/sections/Section/meta.js +0 -61
- package/templates/single/foundation/src/styles.css +0 -5
- package/templates/single/foundation/vite.config.js +0 -3
- package/templates/single/site/index.html.hbs +0 -13
- package/templates/single/site/main.js +0 -7
- package/templates/single/site/package.json.hbs +0 -27
- package/templates/single/site/pages/about/1-about.md.hbs +0 -13
- package/templates/single/site/pages/about/page.yml +0 -2
- package/templates/single/site/pages/home/page.yml +0 -2
- package/templates/single/site/public/favicon.svg +0 -7
- package/templates/single/site/site.yml.hbs +0 -10
- package/templates/single/template.json +0 -10
- /package/{templates/single → starter}/foundation/src/foundation.js +0 -0
- /package/{templates/multi/foundations/default → starter/foundation}/src/sections/Section/index.jsx +0 -0
- /package/{templates/multi/foundations/default → starter/foundation}/src/sections/Section/meta.js +0 -0
- /package/{templates/multi/sites/main → starter/site}/pages/about/1-about.md.hbs +0 -0
- /package/{templates/multi/sites/main → starter/site}/pages/about/page.yml +0 -0
- /package/{templates/single → starter}/site/pages/home/1-welcome.md.hbs +0 -0
- /package/{templates/multi/sites/main → starter/site}/pages/home/page.yml +0 -0
- /package/templates/{multi/foundations/default → foundation}/src/styles.css +0 -0
- /package/templates/{multi/foundations/default → foundation}/vite.config.js +0 -0
- /package/templates/{multi/sites/main → site}/index.html.hbs +0 -0
- /package/templates/{multi/sites/main → site}/main.js +0 -0
- /package/templates/{multi/sites/main → site}/public/favicon.svg +0 -0
- /package/templates/{single/site → site}/vite.config.js +0 -0
- /package/templates/{_shared → workspace}/AGENTS.md.hbs +0 -0
- /package/templates/{single → workspace}/README.md.hbs +0 -0
- /package/templates/{single/template/.vscode → workspace/_vscode}/settings.json +0 -0
package/src/index.js
CHANGED
|
@@ -8,30 +8,26 @@
|
|
|
8
8
|
* Usage:
|
|
9
9
|
* npx uniweb create [project-name]
|
|
10
10
|
* npx uniweb create --template marketing
|
|
11
|
+
* npx uniweb add foundation [name]
|
|
11
12
|
* npx uniweb build
|
|
12
13
|
* npx uniweb docs # Generate COMPONENTS.md from schema
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
|
-
import { existsSync
|
|
16
|
+
import { existsSync } from 'node:fs'
|
|
16
17
|
import { execSync } from 'node:child_process'
|
|
17
|
-
import { resolve, join,
|
|
18
|
-
import { fileURLToPath } from 'node:url'
|
|
18
|
+
import { resolve, join, relative } from 'node:path'
|
|
19
19
|
import prompts from 'prompts'
|
|
20
20
|
import { build } from './commands/build.js'
|
|
21
21
|
import { docs } from './commands/docs.js'
|
|
22
22
|
import { doctor } from './commands/doctor.js'
|
|
23
23
|
import { i18n } from './commands/i18n.js'
|
|
24
|
-
import {
|
|
24
|
+
import { add } from './commands/add.js'
|
|
25
25
|
import {
|
|
26
26
|
resolveTemplate,
|
|
27
|
-
applyExternalTemplate,
|
|
28
27
|
parseTemplateId,
|
|
29
|
-
listAvailableTemplates,
|
|
30
|
-
BUILTIN_TEMPLATES,
|
|
31
28
|
} from './templates/index.js'
|
|
32
|
-
import {
|
|
33
|
-
|
|
34
|
-
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
29
|
+
import { validateTemplate } from './templates/validator.js'
|
|
30
|
+
import { scaffoldWorkspace, scaffoldFoundation, scaffoldSite, applyContent, applyStarter } from './utils/scaffold.js'
|
|
35
31
|
|
|
36
32
|
// Colors for terminal output
|
|
37
33
|
const colors = {
|
|
@@ -60,79 +56,213 @@ function title(message) {
|
|
|
60
56
|
console.log(`\n${colors.cyan}${colors.bright}${message}${colors.reset}\n`)
|
|
61
57
|
}
|
|
62
58
|
|
|
63
|
-
// Built-in template definitions (metadata for display)
|
|
64
|
-
const templates = {
|
|
65
|
-
single: {
|
|
66
|
-
name: 'Single Project',
|
|
67
|
-
description: 'One site + one foundation in site/ and foundation/ (recommended)',
|
|
68
|
-
},
|
|
69
|
-
multi: {
|
|
70
|
-
name: 'Multi-Site Workspace',
|
|
71
|
-
description: 'Multiple sites and foundations in sites/* and foundations/*',
|
|
72
|
-
},
|
|
73
|
-
}
|
|
74
|
-
|
|
75
59
|
/**
|
|
76
|
-
*
|
|
60
|
+
* Create a project using the new package template flow (default)
|
|
77
61
|
*/
|
|
78
|
-
function
|
|
79
|
-
|
|
62
|
+
async function createFromPackageTemplates(projectDir, projectName, options = {}) {
|
|
63
|
+
const { onProgress, onWarning } = options
|
|
64
|
+
|
|
65
|
+
onProgress?.('Setting up workspace...')
|
|
66
|
+
|
|
67
|
+
// 1. Scaffold workspace
|
|
68
|
+
await scaffoldWorkspace(projectDir, {
|
|
69
|
+
projectName,
|
|
70
|
+
workspaceGlobs: ['foundation', 'site'],
|
|
71
|
+
scripts: {
|
|
72
|
+
dev: 'pnpm --filter site dev',
|
|
73
|
+
build: 'uniweb build',
|
|
74
|
+
preview: 'pnpm --filter site preview',
|
|
75
|
+
},
|
|
76
|
+
}, { onProgress, onWarning })
|
|
77
|
+
|
|
78
|
+
// 2. Scaffold foundation
|
|
79
|
+
onProgress?.('Creating foundation...')
|
|
80
|
+
await scaffoldFoundation(join(projectDir, 'foundation'), {
|
|
81
|
+
name: 'foundation',
|
|
82
|
+
projectName,
|
|
83
|
+
isExtension: false,
|
|
84
|
+
}, { onProgress, onWarning })
|
|
85
|
+
|
|
86
|
+
// 3. Scaffold site
|
|
87
|
+
onProgress?.('Creating site...')
|
|
88
|
+
await scaffoldSite(join(projectDir, 'site'), {
|
|
89
|
+
name: 'site',
|
|
90
|
+
projectName,
|
|
91
|
+
foundationName: 'foundation',
|
|
92
|
+
foundationPath: 'file:../foundation',
|
|
93
|
+
}, { onProgress, onWarning })
|
|
94
|
+
|
|
95
|
+
// 4. Apply starter content
|
|
96
|
+
onProgress?.('Adding starter content...')
|
|
97
|
+
await applyStarter(projectDir, { projectName }, { onProgress, onWarning })
|
|
98
|
+
|
|
99
|
+
success(`Created project: ${projectName}`)
|
|
80
100
|
}
|
|
81
101
|
|
|
82
102
|
/**
|
|
83
|
-
*
|
|
103
|
+
* Create a blank workspace (no packages, grow with `add`)
|
|
84
104
|
*/
|
|
85
|
-
function
|
|
86
|
-
|
|
105
|
+
async function createBlankWorkspace(projectDir, projectName, options = {}) {
|
|
106
|
+
const { onProgress, onWarning } = options
|
|
107
|
+
|
|
108
|
+
onProgress?.('Setting up blank workspace...')
|
|
109
|
+
|
|
110
|
+
await scaffoldWorkspace(projectDir, {
|
|
111
|
+
projectName,
|
|
112
|
+
workspaceGlobs: [],
|
|
113
|
+
scripts: {
|
|
114
|
+
build: 'uniweb build',
|
|
115
|
+
},
|
|
116
|
+
}, { onProgress, onWarning })
|
|
117
|
+
|
|
118
|
+
success(`Created blank workspace: ${projectName}`)
|
|
87
119
|
}
|
|
88
120
|
|
|
89
121
|
/**
|
|
90
|
-
*
|
|
122
|
+
* Create a project from a format 2 content template
|
|
123
|
+
*
|
|
124
|
+
* Scaffolds workspace structure from package templates, then overlays
|
|
125
|
+
* content (sections, pages, theme) from the content template.
|
|
91
126
|
*/
|
|
92
|
-
async function
|
|
93
|
-
const {
|
|
127
|
+
async function createFromContentTemplate(projectDir, projectName, metadata, templateRootPath, options = {}) {
|
|
128
|
+
const { onProgress, onWarning } = options
|
|
129
|
+
|
|
130
|
+
// Determine packages to create
|
|
131
|
+
const packages = metadata.packages || [
|
|
132
|
+
{ type: 'foundation', name: 'foundation' },
|
|
133
|
+
{ type: 'site', name: 'site', foundation: 'foundation' },
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
// Compute placement for each package
|
|
137
|
+
const placed = computePlacement(packages)
|
|
138
|
+
|
|
139
|
+
// Compute workspace globs and scripts from placement
|
|
140
|
+
const workspaceGlobs = placed.map(p => p.relativePath)
|
|
141
|
+
const sites = placed.filter(p => p.type === 'site')
|
|
142
|
+
const scripts = {
|
|
143
|
+
build: 'uniweb build',
|
|
144
|
+
}
|
|
145
|
+
if (sites.length === 1) {
|
|
146
|
+
scripts.dev = `pnpm --filter ${sites[0].name} dev`
|
|
147
|
+
scripts.preview = `pnpm --filter ${sites[0].name} preview`
|
|
148
|
+
} else {
|
|
149
|
+
for (const s of sites) {
|
|
150
|
+
scripts[`dev:${s.name}`] = `pnpm --filter ${s.name} dev`
|
|
151
|
+
scripts[`preview:${s.name}`] = `pnpm --filter ${s.name} preview`
|
|
152
|
+
}
|
|
153
|
+
// First site gets unqualified aliases
|
|
154
|
+
if (sites.length > 0) {
|
|
155
|
+
scripts.dev = `pnpm --filter ${sites[0].name} dev`
|
|
156
|
+
scripts.preview = `pnpm --filter ${sites[0].name} preview`
|
|
157
|
+
}
|
|
158
|
+
}
|
|
94
159
|
|
|
95
|
-
|
|
160
|
+
// 1. Scaffold workspace
|
|
161
|
+
onProgress?.('Setting up workspace...')
|
|
162
|
+
await scaffoldWorkspace(projectDir, {
|
|
163
|
+
projectName,
|
|
164
|
+
workspaceGlobs,
|
|
165
|
+
scripts,
|
|
166
|
+
}, { onProgress, onWarning })
|
|
167
|
+
|
|
168
|
+
// 2. Scaffold and apply content for each package
|
|
169
|
+
for (const pkg of placed) {
|
|
170
|
+
const fullPath = join(projectDir, pkg.relativePath)
|
|
171
|
+
|
|
172
|
+
if (pkg.type === 'foundation' || pkg.type === 'extension') {
|
|
173
|
+
onProgress?.(`Creating ${pkg.type}: ${pkg.name}...`)
|
|
174
|
+
await scaffoldFoundation(fullPath, {
|
|
175
|
+
name: pkg.name,
|
|
176
|
+
projectName,
|
|
177
|
+
isExtension: pkg.type === 'extension',
|
|
178
|
+
}, { onProgress, onWarning })
|
|
179
|
+
} else if (pkg.type === 'site') {
|
|
180
|
+
// Find the foundation this site wires to
|
|
181
|
+
const foundationName = pkg.foundation || 'foundation'
|
|
182
|
+
const foundationPkg = placed.find(p =>
|
|
183
|
+
(p.type === 'foundation') && (p.name === foundationName)
|
|
184
|
+
)
|
|
185
|
+
const foundationPath = foundationPkg
|
|
186
|
+
? computeFoundationFilePath(pkg.relativePath, foundationPkg.relativePath)
|
|
187
|
+
: 'file:../foundation'
|
|
188
|
+
|
|
189
|
+
onProgress?.(`Creating site: ${pkg.name}...`)
|
|
190
|
+
await scaffoldSite(fullPath, {
|
|
191
|
+
name: pkg.name,
|
|
192
|
+
projectName,
|
|
193
|
+
foundationName,
|
|
194
|
+
foundationPath,
|
|
195
|
+
foundationRef: foundationName !== 'foundation' ? foundationName : undefined,
|
|
196
|
+
}, { onProgress, onWarning })
|
|
197
|
+
}
|
|
96
198
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
199
|
+
// Apply content from the matching content directory
|
|
200
|
+
const contentDir = findContentDirFor(metadata.contentDirs, pkg)
|
|
201
|
+
if (contentDir) {
|
|
202
|
+
onProgress?.(`Applying ${metadata.name} content to ${pkg.name}...`)
|
|
203
|
+
await applyContent(contentDir.dir, fullPath, { projectName }, { onProgress, onWarning })
|
|
204
|
+
}
|
|
102
205
|
}
|
|
103
206
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
207
|
+
success(`Created project: ${projectName}`)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Compute placement (relative paths) for packages
|
|
212
|
+
*
|
|
213
|
+
* Rules:
|
|
214
|
+
* - 1 foundation named "foundation" → foundation/
|
|
215
|
+
* - Multiple foundations → foundations/{name}/
|
|
216
|
+
* - Extensions → extensions/{name}/
|
|
217
|
+
* - 1 site named "site" → site/
|
|
218
|
+
* - Multiple sites → sites/{name}/
|
|
219
|
+
*/
|
|
220
|
+
function computePlacement(packages) {
|
|
221
|
+
const foundations = packages.filter(p => p.type === 'foundation')
|
|
222
|
+
const extensions = packages.filter(p => p.type === 'extension')
|
|
223
|
+
const sites = packages.filter(p => p.type === 'site')
|
|
224
|
+
|
|
225
|
+
const placed = []
|
|
226
|
+
|
|
227
|
+
for (const f of foundations) {
|
|
228
|
+
if (foundations.length === 1 && f.name === 'foundation') {
|
|
229
|
+
placed.push({ ...f, relativePath: 'foundation' })
|
|
230
|
+
} else {
|
|
231
|
+
placed.push({ ...f, relativePath: `foundations/${f.name}` })
|
|
113
232
|
}
|
|
114
233
|
}
|
|
115
234
|
|
|
116
|
-
|
|
117
|
-
|
|
235
|
+
for (const e of extensions) {
|
|
236
|
+
placed.push({ ...e, relativePath: `extensions/${e.name}` })
|
|
237
|
+
}
|
|
118
238
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
239
|
+
for (const s of sites) {
|
|
240
|
+
if (sites.length === 1 && s.name === 'site') {
|
|
241
|
+
placed.push({ ...s, relativePath: 'site' })
|
|
242
|
+
} else {
|
|
243
|
+
placed.push({ ...s, relativePath: `sites/${s.name}` })
|
|
244
|
+
}
|
|
125
245
|
}
|
|
126
246
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
variant,
|
|
130
|
-
basePath,
|
|
131
|
-
onProgress,
|
|
132
|
-
onWarning,
|
|
133
|
-
})
|
|
247
|
+
return placed
|
|
248
|
+
}
|
|
134
249
|
|
|
135
|
-
|
|
250
|
+
/**
|
|
251
|
+
* Find the content directory that matches a placed package
|
|
252
|
+
*/
|
|
253
|
+
function findContentDirFor(contentDirs, pkg) {
|
|
254
|
+
if (!contentDirs) return null
|
|
255
|
+
// Match by name first, then by type
|
|
256
|
+
return contentDirs.find(d => d.name === pkg.name) ||
|
|
257
|
+
contentDirs.find(d => d.type === pkg.type && d.name === pkg.type)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Compute relative file: path from site to foundation
|
|
262
|
+
*/
|
|
263
|
+
function computeFoundationFilePath(sitePath, foundationPath) {
|
|
264
|
+
const rel = relative(sitePath, foundationPath)
|
|
265
|
+
return `file:${rel}`
|
|
136
266
|
}
|
|
137
267
|
|
|
138
268
|
async function main() {
|
|
@@ -169,6 +299,12 @@ async function main() {
|
|
|
169
299
|
return
|
|
170
300
|
}
|
|
171
301
|
|
|
302
|
+
// Handle add command
|
|
303
|
+
if (command === 'add') {
|
|
304
|
+
await add(args.slice(1))
|
|
305
|
+
return
|
|
306
|
+
}
|
|
307
|
+
|
|
172
308
|
// Handle create command
|
|
173
309
|
if (command !== 'create') {
|
|
174
310
|
error(`Unknown command: ${command}`)
|
|
@@ -180,7 +316,7 @@ async function main() {
|
|
|
180
316
|
|
|
181
317
|
// Parse arguments
|
|
182
318
|
let projectName = args[1]
|
|
183
|
-
let templateType =
|
|
319
|
+
let templateType = null // null = use new package template flow
|
|
184
320
|
|
|
185
321
|
// Check for --template flag
|
|
186
322
|
const templateIndex = args.indexOf('--template')
|
|
@@ -195,13 +331,6 @@ async function main() {
|
|
|
195
331
|
}
|
|
196
332
|
}
|
|
197
333
|
|
|
198
|
-
// Check for --variant flag
|
|
199
|
-
let variant = null
|
|
200
|
-
const variantIndex = args.indexOf('--variant')
|
|
201
|
-
if (variantIndex !== -1 && args[variantIndex + 1]) {
|
|
202
|
-
variant = args[variantIndex + 1]
|
|
203
|
-
}
|
|
204
|
-
|
|
205
334
|
// Check for --name flag (used for project display name)
|
|
206
335
|
let displayName = null
|
|
207
336
|
const nameIndex = args.indexOf('--name')
|
|
@@ -212,6 +341,11 @@ async function main() {
|
|
|
212
341
|
// Check for --no-git flag
|
|
213
342
|
const noGit = args.includes('--no-git')
|
|
214
343
|
|
|
344
|
+
// Skip positional name if it starts with -- (it's a flag, not a name)
|
|
345
|
+
if (projectName && projectName.startsWith('--')) {
|
|
346
|
+
projectName = null
|
|
347
|
+
}
|
|
348
|
+
|
|
215
349
|
// Interactive prompts
|
|
216
350
|
const response = await prompts([
|
|
217
351
|
{
|
|
@@ -227,23 +361,6 @@ async function main() {
|
|
|
227
361
|
return true
|
|
228
362
|
},
|
|
229
363
|
},
|
|
230
|
-
{
|
|
231
|
-
type: templateType ? null : 'select',
|
|
232
|
-
name: 'template',
|
|
233
|
-
message: 'What would you like to create?',
|
|
234
|
-
choices: [
|
|
235
|
-
{
|
|
236
|
-
title: templates.single.name,
|
|
237
|
-
description: templates.single.description,
|
|
238
|
-
value: 'single',
|
|
239
|
-
},
|
|
240
|
-
{
|
|
241
|
-
title: templates.multi.name,
|
|
242
|
-
description: templates.multi.description,
|
|
243
|
-
value: 'multi',
|
|
244
|
-
},
|
|
245
|
-
],
|
|
246
|
-
},
|
|
247
364
|
], {
|
|
248
365
|
onCancel: () => {
|
|
249
366
|
log('\nScaffolding cancelled.')
|
|
@@ -252,13 +369,14 @@ async function main() {
|
|
|
252
369
|
})
|
|
253
370
|
|
|
254
371
|
projectName = projectName || response.projectName
|
|
255
|
-
templateType = templateType || response.template
|
|
256
372
|
|
|
257
|
-
if (!projectName
|
|
258
|
-
error('Missing project name
|
|
373
|
+
if (!projectName) {
|
|
374
|
+
error('Missing project name')
|
|
259
375
|
process.exit(1)
|
|
260
376
|
}
|
|
261
377
|
|
|
378
|
+
const effectiveName = displayName || projectName
|
|
379
|
+
|
|
262
380
|
// Create project directory
|
|
263
381
|
const projectDir = resolve(process.cwd(), projectName)
|
|
264
382
|
|
|
@@ -267,46 +385,53 @@ async function main() {
|
|
|
267
385
|
process.exit(1)
|
|
268
386
|
}
|
|
269
387
|
|
|
270
|
-
//
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
if (parsed.type === 'builtin') {
|
|
274
|
-
const templateMeta = templates[templateType]
|
|
275
|
-
log(`\nCreating ${templateMeta ? templateMeta.name.toLowerCase() : templateType}...`)
|
|
388
|
+
// Template routing logic
|
|
389
|
+
const progressCb = (msg) => log(` ${colors.dim}${msg}${colors.reset}`)
|
|
390
|
+
const warningCb = (msg) => log(` ${colors.yellow}Warning: ${msg}${colors.reset}`)
|
|
276
391
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
onProgress:
|
|
282
|
-
onWarning:
|
|
392
|
+
if (templateType === 'blank') {
|
|
393
|
+
// Blank workspace
|
|
394
|
+
log('\nCreating blank workspace...')
|
|
395
|
+
await createBlankWorkspace(projectDir, effectiveName, {
|
|
396
|
+
onProgress: progressCb,
|
|
397
|
+
onWarning: warningCb,
|
|
398
|
+
})
|
|
399
|
+
} else if (!templateType) {
|
|
400
|
+
// Default flow (package templates + starter)
|
|
401
|
+
log('\nCreating project...')
|
|
402
|
+
await createFromPackageTemplates(projectDir, effectiveName, {
|
|
403
|
+
onProgress: progressCb,
|
|
404
|
+
onWarning: warningCb,
|
|
283
405
|
})
|
|
284
406
|
} else {
|
|
285
|
-
// External
|
|
407
|
+
// External: official/npm/github/local
|
|
286
408
|
log(`\nResolving template: ${templateType}...`)
|
|
287
409
|
|
|
288
410
|
try {
|
|
289
411
|
const resolved = await resolveTemplate(templateType, {
|
|
290
|
-
onProgress:
|
|
412
|
+
onProgress: progressCb,
|
|
291
413
|
})
|
|
292
414
|
|
|
293
415
|
log(`\nCreating project from ${resolved.name || resolved.package || `${resolved.owner}/${resolved.repo}`}...`)
|
|
294
416
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
417
|
+
// Validate and apply as format 2 content template
|
|
418
|
+
const metadata = await validateTemplate(resolved.path, {})
|
|
419
|
+
|
|
420
|
+
try {
|
|
421
|
+
await createFromContentTemplate(projectDir, effectiveName, metadata, resolved.path, {
|
|
422
|
+
onProgress: progressCb,
|
|
423
|
+
onWarning: warningCb,
|
|
424
|
+
})
|
|
425
|
+
} finally {
|
|
426
|
+
if (resolved.cleanup) await resolved.cleanup()
|
|
427
|
+
}
|
|
303
428
|
} catch (err) {
|
|
304
429
|
error(`Failed to apply template: ${err.message}`)
|
|
305
430
|
log('')
|
|
306
431
|
log(`${colors.yellow}Troubleshooting:${colors.reset}`)
|
|
307
432
|
log(` • Check your network connection`)
|
|
308
433
|
log(` • Official templates require GitHub access (may be blocked by corporate networks)`)
|
|
309
|
-
log(` • Try the
|
|
434
|
+
log(` • Try the default template instead: ${colors.cyan}uniweb create ${projectName}${colors.reset}`)
|
|
310
435
|
process.exit(1)
|
|
311
436
|
}
|
|
312
437
|
}
|
|
@@ -332,10 +457,19 @@ async function main() {
|
|
|
332
457
|
// Success message
|
|
333
458
|
title('Project created successfully!')
|
|
334
459
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
460
|
+
if (templateType === 'blank') {
|
|
461
|
+
log(`Next steps:\n`)
|
|
462
|
+
log(` ${colors.cyan}cd ${projectName}${colors.reset}`)
|
|
463
|
+
log(` ${colors.cyan}uniweb add foundation${colors.reset}`)
|
|
464
|
+
log(` ${colors.cyan}uniweb add site${colors.reset}`)
|
|
465
|
+
log(` ${colors.cyan}pnpm install${colors.reset}`)
|
|
466
|
+
log(` ${colors.cyan}pnpm dev${colors.reset}`)
|
|
467
|
+
} else {
|
|
468
|
+
log(`Next steps:\n`)
|
|
469
|
+
log(` ${colors.cyan}cd ${projectName}${colors.reset}`)
|
|
470
|
+
log(` ${colors.cyan}pnpm install${colors.reset}`)
|
|
471
|
+
log(` ${colors.cyan}pnpm dev${colors.reset}`)
|
|
472
|
+
}
|
|
339
473
|
log('')
|
|
340
474
|
}
|
|
341
475
|
|
|
@@ -348,16 +482,21 @@ ${colors.bright}Usage:${colors.reset}
|
|
|
348
482
|
|
|
349
483
|
${colors.bright}Commands:${colors.reset}
|
|
350
484
|
create [name] Create a new project
|
|
485
|
+
add <type> [name] Add a foundation, site, or extension to a project
|
|
351
486
|
build Build the current project
|
|
352
487
|
docs Generate component documentation
|
|
353
488
|
doctor Diagnose project configuration issues
|
|
354
489
|
i18n <cmd> Internationalization (extract, sync, status)
|
|
355
490
|
|
|
356
491
|
${colors.bright}Create Options:${colors.reset}
|
|
357
|
-
--template <type> Project template
|
|
358
|
-
--variant <name> Template variant (e.g., tailwind3 for legacy)
|
|
492
|
+
--template <type> Project template (default: creates foundation + site + starter)
|
|
359
493
|
--name <name> Project display name
|
|
360
|
-
--no-git
|
|
494
|
+
--no-git Skip git repository initialization
|
|
495
|
+
|
|
496
|
+
${colors.bright}Add Subcommands:${colors.reset}
|
|
497
|
+
add foundation [name] Add a foundation (--from, --path, --project)
|
|
498
|
+
add site [name] Add a site (--from, --foundation, --path, --project)
|
|
499
|
+
add extension <name> Add an extension (--from, --site, --path)
|
|
361
500
|
|
|
362
501
|
${colors.bright}Build Options:${colors.reset}
|
|
363
502
|
--target <type> Build target (foundation, site) - auto-detected if not specified
|
|
@@ -386,8 +525,7 @@ ${colors.bright}i18n Commands:${colors.reset}
|
|
|
386
525
|
status Show translation coverage per locale
|
|
387
526
|
|
|
388
527
|
${colors.bright}Template Types:${colors.reset}
|
|
389
|
-
|
|
390
|
-
multi Multiple sites and foundations
|
|
528
|
+
blank Empty workspace (grow with 'add')
|
|
391
529
|
marketing Official marketing template
|
|
392
530
|
./path/to/template Local directory
|
|
393
531
|
@scope/template-name npm package
|
|
@@ -395,17 +533,21 @@ ${colors.bright}Template Types:${colors.reset}
|
|
|
395
533
|
https://github.com/user/repo GitHub URL
|
|
396
534
|
|
|
397
535
|
${colors.bright}Examples:${colors.reset}
|
|
398
|
-
npx uniweb create my-project
|
|
399
|
-
npx uniweb create my-project --template
|
|
400
|
-
npx uniweb create my-project --template marketing
|
|
401
|
-
npx uniweb create my-project --template
|
|
402
|
-
|
|
403
|
-
|
|
536
|
+
npx uniweb create my-project # Default (foundation + site + starter)
|
|
537
|
+
npx uniweb create my-project --template blank # Blank workspace
|
|
538
|
+
npx uniweb create my-project --template marketing # Official template
|
|
539
|
+
npx uniweb create my-project --template ./my-template # Local template
|
|
540
|
+
|
|
541
|
+
cd my-project
|
|
542
|
+
npx uniweb add foundation marketing # Add foundations/marketing/
|
|
543
|
+
npx uniweb add foundation marketing --from marketing # Scaffold + marketing sections
|
|
544
|
+
npx uniweb add site blog --foundation marketing # Add sites/blog/ wired to marketing
|
|
545
|
+
npx uniweb add site blog --from docs --foundation blog # Scaffold + docs pages
|
|
546
|
+
npx uniweb add extension effects --site site # Add extensions/effects/
|
|
547
|
+
|
|
404
548
|
npx uniweb build
|
|
405
549
|
npx uniweb build --target foundation
|
|
406
|
-
npx uniweb
|
|
407
|
-
npx uniweb build --no-prerender # Skip prerendering even if enabled in config
|
|
408
|
-
cd foundation && npx uniweb docs # Generate COMPONENTS.md
|
|
550
|
+
cd foundation && npx uniweb docs # Generate COMPONENTS.md
|
|
409
551
|
`)
|
|
410
552
|
}
|
|
411
553
|
|
package/src/templates/index.js
CHANGED
|
@@ -12,12 +12,6 @@ import { fetchNpmTemplate } from './fetchers/npm.js'
|
|
|
12
12
|
import { fetchGitHubTemplate } from './fetchers/github.js'
|
|
13
13
|
import { fetchOfficialTemplate, listOfficialTemplates } from './fetchers/release.js'
|
|
14
14
|
import { validateTemplate } from './validator.js'
|
|
15
|
-
import {
|
|
16
|
-
copyTemplateDirectory,
|
|
17
|
-
registerVersions,
|
|
18
|
-
getMissingVersions,
|
|
19
|
-
clearMissingVersions
|
|
20
|
-
} from './processor.js'
|
|
21
15
|
|
|
22
16
|
/**
|
|
23
17
|
* Resolve a template identifier and return the template path
|
|
@@ -163,100 +157,12 @@ async function resolveLocalTemplate(templatePath, options = {}) {
|
|
|
163
157
|
}
|
|
164
158
|
}
|
|
165
159
|
|
|
166
|
-
/**
|
|
167
|
-
* Apply a template to a target directory
|
|
168
|
-
*
|
|
169
|
-
* @param {string} templatePath - Path to the template root (contains template.json)
|
|
170
|
-
* @param {string} targetPath - Destination directory for the scaffolded project
|
|
171
|
-
* @param {Object} data - Template variables
|
|
172
|
-
* @param {Object} options - Apply options
|
|
173
|
-
* @param {string} options.variant - Template variant to use
|
|
174
|
-
* @param {string} options.uniwebVersion - Current Uniweb version for compatibility check
|
|
175
|
-
* @param {Function} options.onWarning - Warning callback
|
|
176
|
-
* @param {Function} options.onProgress - Progress callback
|
|
177
|
-
* @returns {Promise<Object>} Template metadata
|
|
178
|
-
*/
|
|
179
|
-
export async function applyTemplate(templatePath, targetPath, data = {}, options = {}) {
|
|
180
|
-
const { uniwebVersion, variant, onWarning, onProgress } = options
|
|
181
|
-
|
|
182
|
-
// Validate the template
|
|
183
|
-
const metadata = await validateTemplate(templatePath, { uniwebVersion })
|
|
184
|
-
|
|
185
|
-
// Register versions for the {{version}} helper
|
|
186
|
-
if (data.versions) {
|
|
187
|
-
registerVersions(data.versions)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Apply default variables
|
|
191
|
-
const templateData = {
|
|
192
|
-
year: new Date().getFullYear(),
|
|
193
|
-
...data
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Copy template files
|
|
197
|
-
await copyTemplateDirectory(
|
|
198
|
-
metadata.templateDir,
|
|
199
|
-
targetPath,
|
|
200
|
-
templateData,
|
|
201
|
-
{ variant, onWarning, onProgress }
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
// Check for missing versions and warn
|
|
205
|
-
const missingVersions = getMissingVersions()
|
|
206
|
-
if (missingVersions.length > 0 && onWarning) {
|
|
207
|
-
onWarning(`Missing version data for packages: ${missingVersions.join(', ')}. Using fallback version.`)
|
|
208
|
-
}
|
|
209
|
-
clearMissingVersions()
|
|
210
|
-
|
|
211
|
-
return metadata
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Apply an external template to a target directory
|
|
216
|
-
*
|
|
217
|
-
* @param {Object} resolved - Resolved template from resolveTemplate()
|
|
218
|
-
* @param {string} targetPath - Target directory
|
|
219
|
-
* @param {Object} data - Template variables
|
|
220
|
-
* @param {Object} options - Apply options
|
|
221
|
-
*/
|
|
222
|
-
export async function applyExternalTemplate(resolved, targetPath, data, options = {}) {
|
|
223
|
-
const { variant, onProgress, onWarning } = options
|
|
224
|
-
|
|
225
|
-
try {
|
|
226
|
-
const metadata = await applyTemplate(
|
|
227
|
-
resolved.path,
|
|
228
|
-
targetPath,
|
|
229
|
-
data,
|
|
230
|
-
{ variant, onProgress, onWarning }
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
return metadata
|
|
234
|
-
} finally {
|
|
235
|
-
// Clean up temp directory if there is one
|
|
236
|
-
if (resolved.cleanup) {
|
|
237
|
-
await resolved.cleanup()
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
160
|
/**
|
|
243
161
|
* List all available templates
|
|
244
162
|
*/
|
|
245
163
|
export async function listAvailableTemplates() {
|
|
246
164
|
const templates = []
|
|
247
165
|
|
|
248
|
-
// Built-in templates
|
|
249
|
-
for (const name of BUILTIN_TEMPLATES) {
|
|
250
|
-
templates.push({
|
|
251
|
-
type: 'builtin',
|
|
252
|
-
id: name,
|
|
253
|
-
name: name.charAt(0).toUpperCase() + name.slice(1),
|
|
254
|
-
description: name === 'single'
|
|
255
|
-
? 'One site + one foundation'
|
|
256
|
-
: 'Multiple sites and foundations',
|
|
257
|
-
})
|
|
258
|
-
}
|
|
259
|
-
|
|
260
166
|
// Official templates from GitHub releases
|
|
261
167
|
try {
|
|
262
168
|
const official = await listOfficialTemplates()
|