uniweb 0.3.4 → 0.3.6
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 +5 -5
- package/src/commands/build.js +43 -2
- package/src/commands/doctor.js +331 -0
- package/src/index.js +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uniweb",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "Create structured Vite + React sites with content/code separation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"js-yaml": "^4.1.0",
|
|
38
38
|
"prompts": "^2.4.2",
|
|
39
39
|
"tar": "^7.0.0",
|
|
40
|
-
"@uniweb/build": "0.2.
|
|
41
|
-
"@uniweb/core": "0.2.
|
|
42
|
-
"@uniweb/runtime": "0.3.
|
|
43
|
-
"@uniweb/kit": "0.2.
|
|
40
|
+
"@uniweb/build": "0.2.3",
|
|
41
|
+
"@uniweb/core": "0.2.3",
|
|
42
|
+
"@uniweb/runtime": "0.3.1",
|
|
43
|
+
"@uniweb/kit": "0.2.2"
|
|
44
44
|
}
|
|
45
45
|
}
|
package/src/commands/build.js
CHANGED
|
@@ -311,12 +311,39 @@ async function generateLocalizedHtml(projectDir, i18nConfig) {
|
|
|
311
311
|
/**
|
|
312
312
|
* Resolve foundation directory based on site config and project structure
|
|
313
313
|
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
314
|
+
* Priority:
|
|
315
|
+
* 1. Read site's package.json to find foundation dependency path (most reliable)
|
|
316
|
+
* 2. Check foundations/{name} for multi-site projects
|
|
317
|
+
* 3. Check ../foundation for single-site projects
|
|
318
|
+
* 4. Check ../{name} as fallback
|
|
316
319
|
*/
|
|
317
320
|
function resolveFoundationDir(projectDir, siteConfig) {
|
|
318
321
|
const foundationName = siteConfig?.foundation
|
|
319
322
|
|
|
323
|
+
// First, try to resolve from site's package.json dependencies
|
|
324
|
+
// This is the most reliable method as it matches how Vite resolves imports
|
|
325
|
+
if (foundationName) {
|
|
326
|
+
const pkgPath = join(projectDir, 'package.json')
|
|
327
|
+
if (existsSync(pkgPath)) {
|
|
328
|
+
try {
|
|
329
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
330
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies }
|
|
331
|
+
const depValue = deps[foundationName]
|
|
332
|
+
|
|
333
|
+
// Check for file: protocol (local dependency)
|
|
334
|
+
if (depValue && depValue.startsWith('file:')) {
|
|
335
|
+
const relativePath = depValue.slice(5) // Remove 'file:' prefix
|
|
336
|
+
const resolvedPath = join(projectDir, relativePath)
|
|
337
|
+
if (existsSync(resolvedPath)) {
|
|
338
|
+
return resolvedPath
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
} catch {
|
|
342
|
+
// Ignore JSON parse errors, fall through to other methods
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
320
347
|
// Check if we're in a multi-site structure (site is under sites/)
|
|
321
348
|
const parentDir = join(projectDir, '..')
|
|
322
349
|
const grandParentDir = join(projectDir, '..', '..')
|
|
@@ -416,6 +443,20 @@ async function buildSite(projectDir, options = {}) {
|
|
|
416
443
|
}
|
|
417
444
|
} catch (err) {
|
|
418
445
|
error(`Pre-rendering failed: ${err.message}`)
|
|
446
|
+
|
|
447
|
+
// Provide helpful guidance for common errors
|
|
448
|
+
if (err.message.includes('Foundation not found')) {
|
|
449
|
+
log('')
|
|
450
|
+
log(`${colors.yellow}This usually means:${colors.reset}`)
|
|
451
|
+
log(` 1. The foundation hasn't been built yet (run foundation build first)`)
|
|
452
|
+
log(` 2. The foundation name in site.yml doesn't match your setup`)
|
|
453
|
+
log('')
|
|
454
|
+
log(`${colors.dim}Check that:${colors.reset}`)
|
|
455
|
+
log(` • site.yml 'foundation:' matches the package name in your foundation's package.json`)
|
|
456
|
+
log(` • site's package.json has a dependency pointing to the correct foundation path`)
|
|
457
|
+
log(` • The foundation's dist/foundation.js exists (build the foundation first)`)
|
|
458
|
+
}
|
|
459
|
+
|
|
419
460
|
if (process.env.DEBUG) {
|
|
420
461
|
console.error(err.stack)
|
|
421
462
|
}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* uniweb doctor - Diagnose project configuration issues
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs'
|
|
6
|
+
import { join, resolve, basename, dirname, relative } from 'node:path'
|
|
7
|
+
import yaml from 'js-yaml'
|
|
8
|
+
|
|
9
|
+
// ANSI colors
|
|
10
|
+
const colors = {
|
|
11
|
+
reset: '\x1b[0m',
|
|
12
|
+
bright: '\x1b[1m',
|
|
13
|
+
dim: '\x1b[2m',
|
|
14
|
+
red: '\x1b[31m',
|
|
15
|
+
green: '\x1b[32m',
|
|
16
|
+
yellow: '\x1b[33m',
|
|
17
|
+
blue: '\x1b[36m'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const success = (msg) => console.log(`${colors.green}✓${colors.reset} ${msg}`)
|
|
21
|
+
const warn = (msg) => console.log(`${colors.yellow}⚠${colors.reset} ${msg}`)
|
|
22
|
+
const error = (msg) => console.log(`${colors.red}✗${colors.reset} ${msg}`)
|
|
23
|
+
const info = (msg) => console.log(`${colors.blue}→${colors.reset} ${msg}`)
|
|
24
|
+
const log = console.log
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Check if a directory is a site
|
|
28
|
+
*/
|
|
29
|
+
function isSite(dir) {
|
|
30
|
+
return existsSync(join(dir, 'site.yml')) || existsSync(join(dir, 'site.yaml'))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Check if a directory is a foundation
|
|
35
|
+
*/
|
|
36
|
+
function isFoundation(dir) {
|
|
37
|
+
return existsSync(join(dir, 'src', 'components'))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Load package.json from a directory
|
|
42
|
+
*/
|
|
43
|
+
function loadPackageJson(dir) {
|
|
44
|
+
const pkgPath = join(dir, 'package.json')
|
|
45
|
+
if (!existsSync(pkgPath)) return null
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
48
|
+
} catch {
|
|
49
|
+
return null
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Load site.yml from a directory
|
|
55
|
+
*/
|
|
56
|
+
function loadSiteYml(dir) {
|
|
57
|
+
const ymlPath = join(dir, 'site.yml')
|
|
58
|
+
const yamlPath = join(dir, 'site.yaml')
|
|
59
|
+
const configPath = existsSync(ymlPath) ? ymlPath : existsSync(yamlPath) ? yamlPath : null
|
|
60
|
+
if (!configPath) return null
|
|
61
|
+
try {
|
|
62
|
+
return yaml.load(readFileSync(configPath, 'utf8'))
|
|
63
|
+
} catch {
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Main doctor command
|
|
70
|
+
*/
|
|
71
|
+
export async function doctor(args = []) {
|
|
72
|
+
log('')
|
|
73
|
+
log(`${colors.blue}${colors.bright}Uniweb Doctor${colors.reset}`)
|
|
74
|
+
log(`${colors.dim}Checking project configuration...${colors.reset}`)
|
|
75
|
+
|
|
76
|
+
const projectDir = resolve(process.cwd())
|
|
77
|
+
|
|
78
|
+
// Detect project type and find workspace root
|
|
79
|
+
let workspaceDir = projectDir
|
|
80
|
+
|
|
81
|
+
if (isSite(projectDir)) {
|
|
82
|
+
workspaceDir = dirname(projectDir)
|
|
83
|
+
if (basename(workspaceDir) === 'sites') {
|
|
84
|
+
workspaceDir = dirname(workspaceDir)
|
|
85
|
+
}
|
|
86
|
+
} else if (isFoundation(projectDir)) {
|
|
87
|
+
workspaceDir = dirname(projectDir)
|
|
88
|
+
if (basename(workspaceDir) === 'foundations') {
|
|
89
|
+
workspaceDir = dirname(workspaceDir)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check workspace structure
|
|
94
|
+
const hasWorkspaceConfig = existsSync(join(workspaceDir, 'pnpm-workspace.yaml')) ||
|
|
95
|
+
existsSync(join(workspaceDir, 'package.json'))
|
|
96
|
+
|
|
97
|
+
if (!hasWorkspaceConfig) {
|
|
98
|
+
error('Not in a Uniweb workspace')
|
|
99
|
+
log(`${colors.dim}Run this command from your project root or a site/foundation directory.${colors.reset}`)
|
|
100
|
+
process.exit(1)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
log('')
|
|
104
|
+
info(`Workspace: ${workspaceDir}`)
|
|
105
|
+
|
|
106
|
+
// Find all foundations
|
|
107
|
+
const foundations = []
|
|
108
|
+
|
|
109
|
+
// Check single-foundation layout
|
|
110
|
+
const foundationDir = join(workspaceDir, 'foundation')
|
|
111
|
+
if (isFoundation(foundationDir)) {
|
|
112
|
+
const pkg = loadPackageJson(foundationDir)
|
|
113
|
+
foundations.push({
|
|
114
|
+
path: foundationDir,
|
|
115
|
+
name: pkg?.name || 'foundation',
|
|
116
|
+
folderName: 'foundation'
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Check multi-foundation layout
|
|
121
|
+
const foundationsDir = join(workspaceDir, 'foundations')
|
|
122
|
+
if (existsSync(foundationsDir)) {
|
|
123
|
+
try {
|
|
124
|
+
const entries = readdirSync(foundationsDir, { withFileTypes: true })
|
|
125
|
+
for (const entry of entries) {
|
|
126
|
+
if (entry.isDirectory()) {
|
|
127
|
+
const foundationPath = join(foundationsDir, entry.name)
|
|
128
|
+
if (isFoundation(foundationPath)) {
|
|
129
|
+
const pkg = loadPackageJson(foundationPath)
|
|
130
|
+
foundations.push({
|
|
131
|
+
path: foundationPath,
|
|
132
|
+
name: pkg?.name || entry.name,
|
|
133
|
+
folderName: entry.name
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} catch {
|
|
139
|
+
// Ignore errors
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (foundations.length === 0) {
|
|
144
|
+
warn('No foundations found')
|
|
145
|
+
} else {
|
|
146
|
+
success(`Found ${foundations.length} foundation(s):`)
|
|
147
|
+
for (const f of foundations) {
|
|
148
|
+
const nameMismatch = f.name !== f.folderName ? ` ${colors.dim}(folder: ${f.folderName}/)${colors.reset}` : ''
|
|
149
|
+
log(` • ${f.name}${nameMismatch}`)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Find all sites
|
|
154
|
+
const sites = []
|
|
155
|
+
|
|
156
|
+
// Check single-site layout
|
|
157
|
+
const siteDir = join(workspaceDir, 'site')
|
|
158
|
+
if (isSite(siteDir)) {
|
|
159
|
+
sites.push({ path: siteDir, name: 'site' })
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Check multi-site layout
|
|
163
|
+
const sitesDir = join(workspaceDir, 'sites')
|
|
164
|
+
if (existsSync(sitesDir)) {
|
|
165
|
+
try {
|
|
166
|
+
const entries = readdirSync(sitesDir, { withFileTypes: true })
|
|
167
|
+
for (const entry of entries) {
|
|
168
|
+
if (entry.isDirectory()) {
|
|
169
|
+
const sitePath = join(sitesDir, entry.name)
|
|
170
|
+
if (isSite(sitePath)) {
|
|
171
|
+
sites.push({ path: sitePath, name: entry.name })
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
} catch {
|
|
176
|
+
// Ignore errors
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (sites.length === 0) {
|
|
181
|
+
warn('No sites found')
|
|
182
|
+
} else {
|
|
183
|
+
log('')
|
|
184
|
+
success(`Found ${sites.length} site(s):`)
|
|
185
|
+
for (const s of sites) {
|
|
186
|
+
log(` • ${s.name}`)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Check each site
|
|
191
|
+
const issues = []
|
|
192
|
+
|
|
193
|
+
for (const site of sites) {
|
|
194
|
+
const siteName = site.name
|
|
195
|
+
const sitePath = site.path
|
|
196
|
+
const siteYml = loadSiteYml(sitePath)
|
|
197
|
+
const sitePkg = loadPackageJson(sitePath)
|
|
198
|
+
|
|
199
|
+
log('')
|
|
200
|
+
info(`Checking site: ${siteName}`)
|
|
201
|
+
|
|
202
|
+
if (!siteYml) {
|
|
203
|
+
issues.push({ type: 'error', site: siteName, message: 'Missing site.yml' })
|
|
204
|
+
error('Missing site.yml')
|
|
205
|
+
continue
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!sitePkg) {
|
|
209
|
+
issues.push({ type: 'error', site: siteName, message: 'Missing package.json' })
|
|
210
|
+
error('Missing package.json')
|
|
211
|
+
continue
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const foundationName = siteYml.foundation
|
|
215
|
+
if (!foundationName) {
|
|
216
|
+
warn('No foundation specified in site.yml (using runtime loading?)')
|
|
217
|
+
continue
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Check if foundation name matches a known foundation
|
|
221
|
+
const matchingFoundation = foundations.find(f => f.name === foundationName)
|
|
222
|
+
|
|
223
|
+
if (!matchingFoundation) {
|
|
224
|
+
// Check if it might match a folder name instead
|
|
225
|
+
const folderMatch = foundations.find(f => f.folderName === foundationName)
|
|
226
|
+
if (folderMatch) {
|
|
227
|
+
issues.push({
|
|
228
|
+
type: 'error',
|
|
229
|
+
site: siteName,
|
|
230
|
+
message: `Foundation mismatch: site.yml uses folder name "${foundationName}" instead of package name "${folderMatch.name}"`
|
|
231
|
+
})
|
|
232
|
+
error(`Foundation mismatch:`)
|
|
233
|
+
log(` site.yml says: ${colors.yellow}foundation: ${foundationName}${colors.reset}`)
|
|
234
|
+
log(` This matches the folder name, but the package name is: ${colors.green}${folderMatch.name}${colors.reset}`)
|
|
235
|
+
log('')
|
|
236
|
+
log(` ${colors.dim}To fix, update site.yml:${colors.reset}`)
|
|
237
|
+
log(` foundation: ${folderMatch.name}`)
|
|
238
|
+
continue
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
issues.push({
|
|
242
|
+
type: 'error',
|
|
243
|
+
site: siteName,
|
|
244
|
+
message: `Foundation "${foundationName}" not found`
|
|
245
|
+
})
|
|
246
|
+
error(`Foundation "${foundationName}" not found`)
|
|
247
|
+
log('')
|
|
248
|
+
log(` ${colors.dim}Available foundations:${colors.reset}`)
|
|
249
|
+
for (const f of foundations) {
|
|
250
|
+
log(` • ${f.name} (${f.folderName}/)`)
|
|
251
|
+
}
|
|
252
|
+
continue
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
success(`Foundation reference: ${foundationName}`)
|
|
256
|
+
|
|
257
|
+
// Check package.json dependency
|
|
258
|
+
const deps = { ...sitePkg.dependencies, ...sitePkg.devDependencies }
|
|
259
|
+
const depValue = deps[foundationName]
|
|
260
|
+
|
|
261
|
+
if (!depValue) {
|
|
262
|
+
issues.push({
|
|
263
|
+
type: 'error',
|
|
264
|
+
site: siteName,
|
|
265
|
+
message: `Missing dependency "${foundationName}" in package.json`
|
|
266
|
+
})
|
|
267
|
+
error(`Missing dependency "${foundationName}" in package.json`)
|
|
268
|
+
log('')
|
|
269
|
+
log(` ${colors.dim}Add to site's package.json dependencies:${colors.reset}`)
|
|
270
|
+
log(` "${foundationName}": "file:${relative(sitePath, matchingFoundation.path)}"`)
|
|
271
|
+
continue
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (depValue.startsWith('file:')) {
|
|
275
|
+
const depPath = join(sitePath, depValue.slice(5))
|
|
276
|
+
if (!existsSync(depPath)) {
|
|
277
|
+
issues.push({
|
|
278
|
+
type: 'error',
|
|
279
|
+
site: siteName,
|
|
280
|
+
message: `Dependency path doesn't exist: ${depValue}`
|
|
281
|
+
})
|
|
282
|
+
error(`Dependency path doesn't exist: ${depValue}`)
|
|
283
|
+
log('')
|
|
284
|
+
log(` ${colors.dim}Update site's package.json:${colors.reset}`)
|
|
285
|
+
log(` "${foundationName}": "file:${relative(sitePath, matchingFoundation.path)}"`)
|
|
286
|
+
continue
|
|
287
|
+
}
|
|
288
|
+
success(`Dependency path: ${depValue}`)
|
|
289
|
+
} else {
|
|
290
|
+
success(`Dependency: ${depValue} (npm package)`)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Check if foundation is built
|
|
294
|
+
const foundationDist = join(matchingFoundation.path, 'dist', 'foundation.js')
|
|
295
|
+
if (!existsSync(foundationDist)) {
|
|
296
|
+
issues.push({
|
|
297
|
+
type: 'warn',
|
|
298
|
+
site: siteName,
|
|
299
|
+
message: `Foundation not built: ${matchingFoundation.name}`
|
|
300
|
+
})
|
|
301
|
+
warn(`Foundation not built yet`)
|
|
302
|
+
log(` ${colors.dim}Run: pnpm --filter ${matchingFoundation.name} build${colors.reset}`)
|
|
303
|
+
} else {
|
|
304
|
+
success(`Foundation built: dist/foundation.js exists`)
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Summary
|
|
309
|
+
log('')
|
|
310
|
+
log('─'.repeat(50))
|
|
311
|
+
|
|
312
|
+
const errors = issues.filter(i => i.type === 'error')
|
|
313
|
+
const warnings = issues.filter(i => i.type === 'warn')
|
|
314
|
+
|
|
315
|
+
if (errors.length === 0 && warnings.length === 0) {
|
|
316
|
+
log('')
|
|
317
|
+
log(`${colors.green}${colors.bright}All checks passed!${colors.reset}`)
|
|
318
|
+
log('')
|
|
319
|
+
} else {
|
|
320
|
+
log('')
|
|
321
|
+
if (errors.length > 0) {
|
|
322
|
+
log(`${colors.red}${errors.length} error(s)${colors.reset}`)
|
|
323
|
+
}
|
|
324
|
+
if (warnings.length > 0) {
|
|
325
|
+
log(`${colors.yellow}${warnings.length} warning(s)${colors.reset}`)
|
|
326
|
+
}
|
|
327
|
+
log('')
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return { issues, errors: errors.length, warnings: warnings.length }
|
|
331
|
+
}
|
package/src/index.js
CHANGED
|
@@ -18,6 +18,7 @@ import { fileURLToPath } from 'node:url'
|
|
|
18
18
|
import prompts from 'prompts'
|
|
19
19
|
import { build } from './commands/build.js'
|
|
20
20
|
import { docs } from './commands/docs.js'
|
|
21
|
+
import { doctor } from './commands/doctor.js'
|
|
21
22
|
import { i18n } from './commands/i18n.js'
|
|
22
23
|
import { getVersionsForTemplates, getVersion } from './versions.js'
|
|
23
24
|
import {
|
|
@@ -161,6 +162,12 @@ async function main() {
|
|
|
161
162
|
return
|
|
162
163
|
}
|
|
163
164
|
|
|
165
|
+
// Handle doctor command
|
|
166
|
+
if (command === 'doctor') {
|
|
167
|
+
await doctor(args.slice(1))
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
164
171
|
// Handle create command
|
|
165
172
|
if (command !== 'create') {
|
|
166
173
|
error(`Unknown command: ${command}`)
|
|
@@ -321,6 +328,7 @@ ${colors.bright}Commands:${colors.reset}
|
|
|
321
328
|
create [name] Create a new project
|
|
322
329
|
build Build the current project
|
|
323
330
|
docs Generate component documentation
|
|
331
|
+
doctor Diagnose project configuration issues
|
|
324
332
|
i18n <cmd> Internationalization (extract, sync, status)
|
|
325
333
|
|
|
326
334
|
${colors.bright}Create Options:${colors.reset}
|