uniweb 0.3.3 → 0.3.5
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 +80 -1
- package/package.json +3 -3
- package/src/commands/build.js +43 -2
- package/src/commands/doctor.js +331 -0
- package/src/index.js +8 -0
- package/templates/multi/sites/main/site.yml.hbs +3 -0
- package/templates/single/site/site.yml.hbs +3 -0
package/README.md
CHANGED
|
@@ -106,7 +106,16 @@ role: Lead Architect
|
|
|
106
106
|
```
|
|
107
107
|
````
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
Access the parsed data via `content.data`:
|
|
110
|
+
|
|
111
|
+
```jsx
|
|
112
|
+
function TeamCard({ content }) {
|
|
113
|
+
const member = content.data['team-member']
|
|
114
|
+
return <div>{member.name} — {member.role}</div>
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Natural content stays in markdown; structured data goes in tagged blocks (YAML or JSON).
|
|
110
119
|
|
|
111
120
|
### Components as React
|
|
112
121
|
|
|
@@ -648,6 +657,76 @@ Yes. Content is pre-embedded in the initial HTML—no fetch waterfalls, no layou
|
|
|
648
657
|
|
|
649
658
|
Pages can define data sources that auto-generate subroutes. A `/blog` page can have an index and a `[slug]` template that renders each post.
|
|
650
659
|
|
|
660
|
+
## Common Gotchas
|
|
661
|
+
|
|
662
|
+
### Homepage Configuration
|
|
663
|
+
|
|
664
|
+
Set your homepage with `index:` in `site.yml`:
|
|
665
|
+
|
|
666
|
+
```yaml
|
|
667
|
+
# site.yml
|
|
668
|
+
index: home # The page folder that becomes /
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
The `index:` option tells the build which page folder becomes the root route (`/`). The page still exists in `pages/home/`, but visitors access it at `/`.
|
|
672
|
+
|
|
673
|
+
Don't confuse this with `pages:` (which explicitly lists pages and hides any not listed).
|
|
674
|
+
|
|
675
|
+
### Content Shapes
|
|
676
|
+
|
|
677
|
+
Lists and items in markdown become **objects**, not strings. A bullet list:
|
|
678
|
+
|
|
679
|
+
```markdown
|
|
680
|
+
- First item
|
|
681
|
+
- Second item
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
Becomes:
|
|
685
|
+
|
|
686
|
+
```js
|
|
687
|
+
content.items = [
|
|
688
|
+
{ title: "First item", paragraphs: [], links: [], imgs: [] },
|
|
689
|
+
{ title: "Second item", paragraphs: [], links: [], imgs: [] }
|
|
690
|
+
]
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
Each item has the same structure as a content block. See [Content Structure](./docs/content-structure.md) for the full shape.
|
|
694
|
+
|
|
695
|
+
### Tagged Data Blocks
|
|
696
|
+
|
|
697
|
+
Tagged code blocks like:
|
|
698
|
+
|
|
699
|
+
````markdown
|
|
700
|
+
```yaml:team-member
|
|
701
|
+
name: Sarah Chen
|
|
702
|
+
role: Lead Architect
|
|
703
|
+
```
|
|
704
|
+
````
|
|
705
|
+
|
|
706
|
+
Are accessible via `content.data`:
|
|
707
|
+
|
|
708
|
+
```jsx
|
|
709
|
+
function TeamMember({ content }) {
|
|
710
|
+
const member = content.data['team-member']
|
|
711
|
+
return <div>{member.name} - {member.role}</div>
|
|
712
|
+
}
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
The tag name (after the colon) becomes the key in `content.data`.
|
|
716
|
+
|
|
717
|
+
### Root vs Package Commands
|
|
718
|
+
|
|
719
|
+
In a Uniweb workspace, commands run differently at different levels:
|
|
720
|
+
|
|
721
|
+
| Location | Command | What it does |
|
|
722
|
+
|----------|---------|--------------|
|
|
723
|
+
| Project root | `pnpm build` | Builds all packages (foundation + site) |
|
|
724
|
+
| Project root | `pnpm dev` | Starts dev server for site |
|
|
725
|
+
| `foundation/` | `uniweb build` | Builds just the foundation |
|
|
726
|
+
| `site/` | `uniweb build` | Builds just the site |
|
|
727
|
+
|
|
728
|
+
For day-to-day development, run `pnpm dev` from the project root. The workspace scripts handle the rest.
|
|
729
|
+
|
|
651
730
|
## Related Packages
|
|
652
731
|
|
|
653
732
|
- [`@uniweb/build`](https://github.com/uniweb/build) — Foundation build tooling
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uniweb",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "Create structured Vite + React sites with content/code separation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -37,8 +37,8 @@
|
|
|
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.
|
|
40
|
+
"@uniweb/build": "0.2.2",
|
|
41
|
+
"@uniweb/core": "0.2.2",
|
|
42
42
|
"@uniweb/kit": "0.2.1",
|
|
43
43
|
"@uniweb/runtime": "0.3.0"
|
|
44
44
|
}
|
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}
|