uniweb 0.7.1 → 0.7.3
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 +6 -5
- package/src/commands/add.js +571 -0
- package/src/commands/build.js +49 -6
- package/src/commands/doctor.js +181 -2
- package/src/index.js +281 -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 +196 -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, mergeTemplateDependencies } from './utils/scaffold.js'
|
|
35
31
|
|
|
36
32
|
// Colors for terminal output
|
|
37
33
|
const colors = {
|
|
@@ -60,79 +56,221 @@ 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
|
-
|
|
102
|
-
|
|
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
|
+
}
|
|
103
205
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (onWarning) {
|
|
110
|
-
onWarning(`Base template '${templateConfig.base}' not found at ${basePath}`)
|
|
206
|
+
// Merge template dependencies into package.json
|
|
207
|
+
if (metadata.dependencies) {
|
|
208
|
+
const deps = metadata.dependencies[pkg.name] || metadata.dependencies[pkg.type]
|
|
209
|
+
if (deps) {
|
|
210
|
+
await mergeTemplateDependencies(join(fullPath, 'package.json'), deps)
|
|
111
211
|
}
|
|
112
|
-
basePath = null
|
|
113
212
|
}
|
|
114
213
|
}
|
|
115
214
|
|
|
116
|
-
|
|
117
|
-
|
|
215
|
+
success(`Created project: ${projectName}`)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Compute placement (relative paths) for packages
|
|
220
|
+
*
|
|
221
|
+
* Rules:
|
|
222
|
+
* - 1 foundation named "foundation" → foundation/
|
|
223
|
+
* - Multiple foundations → foundations/{name}/
|
|
224
|
+
* - Extensions → extensions/{name}/
|
|
225
|
+
* - 1 site named "site" → site/
|
|
226
|
+
* - Multiple sites → sites/{name}/
|
|
227
|
+
*/
|
|
228
|
+
function computePlacement(packages) {
|
|
229
|
+
const foundations = packages.filter(p => p.type === 'foundation')
|
|
230
|
+
const extensions = packages.filter(p => p.type === 'extension')
|
|
231
|
+
const sites = packages.filter(p => p.type === 'site')
|
|
232
|
+
|
|
233
|
+
const placed = []
|
|
234
|
+
|
|
235
|
+
for (const f of foundations) {
|
|
236
|
+
if (foundations.length === 1 && f.name === 'foundation') {
|
|
237
|
+
placed.push({ ...f, relativePath: 'foundation' })
|
|
238
|
+
} else {
|
|
239
|
+
placed.push({ ...f, relativePath: `foundations/${f.name}` })
|
|
240
|
+
}
|
|
241
|
+
}
|
|
118
242
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
projectName: projectName || 'my-project',
|
|
122
|
-
templateName: templateName,
|
|
123
|
-
templateTitle: projectName || 'My Project',
|
|
124
|
-
templateDescription: templateConfig.description || 'A Uniweb project',
|
|
243
|
+
for (const e of extensions) {
|
|
244
|
+
placed.push({ ...e, relativePath: `extensions/${e.name}` })
|
|
125
245
|
}
|
|
126
246
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
247
|
+
for (const s of sites) {
|
|
248
|
+
if (sites.length === 1 && s.name === 'site') {
|
|
249
|
+
placed.push({ ...s, relativePath: 'site' })
|
|
250
|
+
} else {
|
|
251
|
+
placed.push({ ...s, relativePath: `sites/${s.name}` })
|
|
252
|
+
}
|
|
253
|
+
}
|
|
134
254
|
|
|
135
|
-
|
|
255
|
+
return placed
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Find the content directory that matches a placed package
|
|
260
|
+
*/
|
|
261
|
+
function findContentDirFor(contentDirs, pkg) {
|
|
262
|
+
if (!contentDirs) return null
|
|
263
|
+
// Match by name first, then by type
|
|
264
|
+
return contentDirs.find(d => d.name === pkg.name) ||
|
|
265
|
+
contentDirs.find(d => d.type === pkg.type && d.name === pkg.type)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Compute relative file: path from site to foundation
|
|
270
|
+
*/
|
|
271
|
+
function computeFoundationFilePath(sitePath, foundationPath) {
|
|
272
|
+
const rel = relative(sitePath, foundationPath)
|
|
273
|
+
return `file:${rel}`
|
|
136
274
|
}
|
|
137
275
|
|
|
138
276
|
async function main() {
|
|
@@ -169,6 +307,12 @@ async function main() {
|
|
|
169
307
|
return
|
|
170
308
|
}
|
|
171
309
|
|
|
310
|
+
// Handle add command
|
|
311
|
+
if (command === 'add') {
|
|
312
|
+
await add(args.slice(1))
|
|
313
|
+
return
|
|
314
|
+
}
|
|
315
|
+
|
|
172
316
|
// Handle create command
|
|
173
317
|
if (command !== 'create') {
|
|
174
318
|
error(`Unknown command: ${command}`)
|
|
@@ -180,7 +324,7 @@ async function main() {
|
|
|
180
324
|
|
|
181
325
|
// Parse arguments
|
|
182
326
|
let projectName = args[1]
|
|
183
|
-
let templateType =
|
|
327
|
+
let templateType = null // null = use new package template flow
|
|
184
328
|
|
|
185
329
|
// Check for --template flag
|
|
186
330
|
const templateIndex = args.indexOf('--template')
|
|
@@ -195,13 +339,6 @@ async function main() {
|
|
|
195
339
|
}
|
|
196
340
|
}
|
|
197
341
|
|
|
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
342
|
// Check for --name flag (used for project display name)
|
|
206
343
|
let displayName = null
|
|
207
344
|
const nameIndex = args.indexOf('--name')
|
|
@@ -212,6 +349,11 @@ async function main() {
|
|
|
212
349
|
// Check for --no-git flag
|
|
213
350
|
const noGit = args.includes('--no-git')
|
|
214
351
|
|
|
352
|
+
// Skip positional name if it starts with -- (it's a flag, not a name)
|
|
353
|
+
if (projectName && projectName.startsWith('--')) {
|
|
354
|
+
projectName = null
|
|
355
|
+
}
|
|
356
|
+
|
|
215
357
|
// Interactive prompts
|
|
216
358
|
const response = await prompts([
|
|
217
359
|
{
|
|
@@ -227,23 +369,6 @@ async function main() {
|
|
|
227
369
|
return true
|
|
228
370
|
},
|
|
229
371
|
},
|
|
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
372
|
], {
|
|
248
373
|
onCancel: () => {
|
|
249
374
|
log('\nScaffolding cancelled.')
|
|
@@ -252,13 +377,14 @@ async function main() {
|
|
|
252
377
|
})
|
|
253
378
|
|
|
254
379
|
projectName = projectName || response.projectName
|
|
255
|
-
templateType = templateType || response.template
|
|
256
380
|
|
|
257
|
-
if (!projectName
|
|
258
|
-
error('Missing project name
|
|
381
|
+
if (!projectName) {
|
|
382
|
+
error('Missing project name')
|
|
259
383
|
process.exit(1)
|
|
260
384
|
}
|
|
261
385
|
|
|
386
|
+
const effectiveName = displayName || projectName
|
|
387
|
+
|
|
262
388
|
// Create project directory
|
|
263
389
|
const projectDir = resolve(process.cwd(), projectName)
|
|
264
390
|
|
|
@@ -267,46 +393,53 @@ async function main() {
|
|
|
267
393
|
process.exit(1)
|
|
268
394
|
}
|
|
269
395
|
|
|
270
|
-
//
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
if (parsed.type === 'builtin') {
|
|
274
|
-
const templateMeta = templates[templateType]
|
|
275
|
-
log(`\nCreating ${templateMeta ? templateMeta.name.toLowerCase() : templateType}...`)
|
|
396
|
+
// Template routing logic
|
|
397
|
+
const progressCb = (msg) => log(` ${colors.dim}${msg}${colors.reset}`)
|
|
398
|
+
const warningCb = (msg) => log(` ${colors.yellow}Warning: ${msg}${colors.reset}`)
|
|
276
399
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
onProgress:
|
|
282
|
-
onWarning:
|
|
400
|
+
if (templateType === 'blank') {
|
|
401
|
+
// Blank workspace
|
|
402
|
+
log('\nCreating blank workspace...')
|
|
403
|
+
await createBlankWorkspace(projectDir, effectiveName, {
|
|
404
|
+
onProgress: progressCb,
|
|
405
|
+
onWarning: warningCb,
|
|
406
|
+
})
|
|
407
|
+
} else if (!templateType) {
|
|
408
|
+
// Default flow (package templates + starter)
|
|
409
|
+
log('\nCreating project...')
|
|
410
|
+
await createFromPackageTemplates(projectDir, effectiveName, {
|
|
411
|
+
onProgress: progressCb,
|
|
412
|
+
onWarning: warningCb,
|
|
283
413
|
})
|
|
284
414
|
} else {
|
|
285
|
-
// External
|
|
415
|
+
// External: official/npm/github/local
|
|
286
416
|
log(`\nResolving template: ${templateType}...`)
|
|
287
417
|
|
|
288
418
|
try {
|
|
289
419
|
const resolved = await resolveTemplate(templateType, {
|
|
290
|
-
onProgress:
|
|
420
|
+
onProgress: progressCb,
|
|
291
421
|
})
|
|
292
422
|
|
|
293
423
|
log(`\nCreating project from ${resolved.name || resolved.package || `${resolved.owner}/${resolved.repo}`}...`)
|
|
294
424
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
425
|
+
// Validate and apply as format 2 content template
|
|
426
|
+
const metadata = await validateTemplate(resolved.path, {})
|
|
427
|
+
|
|
428
|
+
try {
|
|
429
|
+
await createFromContentTemplate(projectDir, effectiveName, metadata, resolved.path, {
|
|
430
|
+
onProgress: progressCb,
|
|
431
|
+
onWarning: warningCb,
|
|
432
|
+
})
|
|
433
|
+
} finally {
|
|
434
|
+
if (resolved.cleanup) await resolved.cleanup()
|
|
435
|
+
}
|
|
303
436
|
} catch (err) {
|
|
304
437
|
error(`Failed to apply template: ${err.message}`)
|
|
305
438
|
log('')
|
|
306
439
|
log(`${colors.yellow}Troubleshooting:${colors.reset}`)
|
|
307
440
|
log(` • Check your network connection`)
|
|
308
441
|
log(` • Official templates require GitHub access (may be blocked by corporate networks)`)
|
|
309
|
-
log(` • Try the
|
|
442
|
+
log(` • Try the default template instead: ${colors.cyan}uniweb create ${projectName}${colors.reset}`)
|
|
310
443
|
process.exit(1)
|
|
311
444
|
}
|
|
312
445
|
}
|
|
@@ -332,10 +465,19 @@ async function main() {
|
|
|
332
465
|
// Success message
|
|
333
466
|
title('Project created successfully!')
|
|
334
467
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
468
|
+
if (templateType === 'blank') {
|
|
469
|
+
log(`Next steps:\n`)
|
|
470
|
+
log(` ${colors.cyan}cd ${projectName}${colors.reset}`)
|
|
471
|
+
log(` ${colors.cyan}uniweb add foundation${colors.reset}`)
|
|
472
|
+
log(` ${colors.cyan}uniweb add site${colors.reset}`)
|
|
473
|
+
log(` ${colors.cyan}pnpm install${colors.reset}`)
|
|
474
|
+
log(` ${colors.cyan}pnpm dev${colors.reset}`)
|
|
475
|
+
} else {
|
|
476
|
+
log(`Next steps:\n`)
|
|
477
|
+
log(` ${colors.cyan}cd ${projectName}${colors.reset}`)
|
|
478
|
+
log(` ${colors.cyan}pnpm install${colors.reset}`)
|
|
479
|
+
log(` ${colors.cyan}pnpm dev${colors.reset}`)
|
|
480
|
+
}
|
|
339
481
|
log('')
|
|
340
482
|
}
|
|
341
483
|
|
|
@@ -348,16 +490,21 @@ ${colors.bright}Usage:${colors.reset}
|
|
|
348
490
|
|
|
349
491
|
${colors.bright}Commands:${colors.reset}
|
|
350
492
|
create [name] Create a new project
|
|
493
|
+
add <type> [name] Add a foundation, site, or extension to a project
|
|
351
494
|
build Build the current project
|
|
352
495
|
docs Generate component documentation
|
|
353
496
|
doctor Diagnose project configuration issues
|
|
354
497
|
i18n <cmd> Internationalization (extract, sync, status)
|
|
355
498
|
|
|
356
499
|
${colors.bright}Create Options:${colors.reset}
|
|
357
|
-
--template <type> Project template
|
|
358
|
-
--variant <name> Template variant (e.g., tailwind3 for legacy)
|
|
500
|
+
--template <type> Project template (default: creates foundation + site + starter)
|
|
359
501
|
--name <name> Project display name
|
|
360
|
-
--no-git
|
|
502
|
+
--no-git Skip git repository initialization
|
|
503
|
+
|
|
504
|
+
${colors.bright}Add Subcommands:${colors.reset}
|
|
505
|
+
add foundation [name] Add a foundation (--from, --path, --project)
|
|
506
|
+
add site [name] Add a site (--from, --foundation, --path, --project)
|
|
507
|
+
add extension <name> Add an extension (--from, --site, --path)
|
|
361
508
|
|
|
362
509
|
${colors.bright}Build Options:${colors.reset}
|
|
363
510
|
--target <type> Build target (foundation, site) - auto-detected if not specified
|
|
@@ -386,8 +533,7 @@ ${colors.bright}i18n Commands:${colors.reset}
|
|
|
386
533
|
status Show translation coverage per locale
|
|
387
534
|
|
|
388
535
|
${colors.bright}Template Types:${colors.reset}
|
|
389
|
-
|
|
390
|
-
multi Multiple sites and foundations
|
|
536
|
+
blank Empty workspace (grow with 'add')
|
|
391
537
|
marketing Official marketing template
|
|
392
538
|
./path/to/template Local directory
|
|
393
539
|
@scope/template-name npm package
|
|
@@ -395,17 +541,21 @@ ${colors.bright}Template Types:${colors.reset}
|
|
|
395
541
|
https://github.com/user/repo GitHub URL
|
|
396
542
|
|
|
397
543
|
${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
|
-
|
|
544
|
+
npx uniweb create my-project # Default (foundation + site + starter)
|
|
545
|
+
npx uniweb create my-project --template blank # Blank workspace
|
|
546
|
+
npx uniweb create my-project --template marketing # Official template
|
|
547
|
+
npx uniweb create my-project --template ./my-template # Local template
|
|
548
|
+
|
|
549
|
+
cd my-project
|
|
550
|
+
npx uniweb add foundation marketing # Add foundations/marketing/
|
|
551
|
+
npx uniweb add foundation marketing --from marketing # Scaffold + marketing sections
|
|
552
|
+
npx uniweb add site blog --foundation marketing # Add sites/blog/ wired to marketing
|
|
553
|
+
npx uniweb add site blog --from docs --foundation blog # Scaffold + docs pages
|
|
554
|
+
npx uniweb add extension effects --site site # Add extensions/effects/
|
|
555
|
+
|
|
404
556
|
npx uniweb build
|
|
405
557
|
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
|
|
558
|
+
cd foundation && npx uniweb docs # Generate COMPONENTS.md
|
|
409
559
|
`)
|
|
410
560
|
}
|
|
411
561
|
|
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()
|