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.
Files changed (59) hide show
  1. package/README.md +64 -17
  2. package/package.json +4 -3
  3. package/src/commands/add.js +563 -0
  4. package/src/commands/build.js +49 -6
  5. package/src/commands/doctor.js +181 -2
  6. package/src/index.js +273 -131
  7. package/src/templates/index.js +0 -94
  8. package/src/templates/processor.js +10 -87
  9. package/src/templates/resolver.js +3 -3
  10. package/src/templates/validator.js +59 -17
  11. package/src/utils/config.js +229 -0
  12. package/src/utils/scaffold.js +175 -0
  13. package/templates/{single/foundation → foundation}/package.json.hbs +2 -2
  14. package/templates/foundation/src/foundation.js.hbs +7 -0
  15. package/templates/foundation/src/sections/.gitkeep +0 -0
  16. package/templates/{multi/sites/main → site}/package.json.hbs +2 -2
  17. package/templates/site/site.yml.hbs +10 -0
  18. package/templates/site/theme.yml +1 -0
  19. package/templates/{_shared → workspace}/package.json.hbs +3 -9
  20. package/templates/workspace/pnpm-workspace.yaml.hbs +4 -0
  21. package/templates/_shared/pnpm-workspace.yaml +0 -5
  22. package/templates/multi/README.md.hbs +0 -85
  23. package/templates/multi/foundations/default/package.json.hbs +0 -38
  24. package/templates/multi/foundations/default/src/foundation.js +0 -41
  25. package/templates/multi/package.json.hbs +0 -26
  26. package/templates/multi/sites/main/pages/home/1-welcome.md.hbs +0 -14
  27. package/templates/multi/sites/main/site.yml.hbs +0 -12
  28. package/templates/multi/sites/main/vite.config.js +0 -7
  29. package/templates/multi/template/.vscode/settings.json +0 -6
  30. package/templates/multi/template.json +0 -5
  31. package/templates/single/foundation/src/sections/Section/index.jsx +0 -121
  32. package/templates/single/foundation/src/sections/Section/meta.js +0 -61
  33. package/templates/single/foundation/src/styles.css +0 -5
  34. package/templates/single/foundation/vite.config.js +0 -3
  35. package/templates/single/site/index.html.hbs +0 -13
  36. package/templates/single/site/main.js +0 -7
  37. package/templates/single/site/package.json.hbs +0 -27
  38. package/templates/single/site/pages/about/1-about.md.hbs +0 -13
  39. package/templates/single/site/pages/about/page.yml +0 -2
  40. package/templates/single/site/pages/home/page.yml +0 -2
  41. package/templates/single/site/public/favicon.svg +0 -7
  42. package/templates/single/site/site.yml.hbs +0 -10
  43. package/templates/single/template.json +0 -10
  44. /package/{templates/single → starter}/foundation/src/foundation.js +0 -0
  45. /package/{templates/multi/foundations/default → starter/foundation}/src/sections/Section/index.jsx +0 -0
  46. /package/{templates/multi/foundations/default → starter/foundation}/src/sections/Section/meta.js +0 -0
  47. /package/{templates/multi/sites/main → starter/site}/pages/about/1-about.md.hbs +0 -0
  48. /package/{templates/multi/sites/main → starter/site}/pages/about/page.yml +0 -0
  49. /package/{templates/single → starter}/site/pages/home/1-welcome.md.hbs +0 -0
  50. /package/{templates/multi/sites/main → starter/site}/pages/home/page.yml +0 -0
  51. /package/templates/{multi/foundations/default → foundation}/src/styles.css +0 -0
  52. /package/templates/{multi/foundations/default → foundation}/vite.config.js +0 -0
  53. /package/templates/{multi/sites/main → site}/index.html.hbs +0 -0
  54. /package/templates/{multi/sites/main → site}/main.js +0 -0
  55. /package/templates/{multi/sites/main → site}/public/favicon.svg +0 -0
  56. /package/templates/{single/site → site}/vite.config.js +0 -0
  57. /package/templates/{_shared → workspace}/AGENTS.md.hbs +0 -0
  58. /package/templates/{single → workspace}/README.md.hbs +0 -0
  59. /package/templates/{single/template/.vscode → workspace/_vscode}/settings.json +0 -0
@@ -34,7 +34,52 @@ function isSite(dir) {
34
34
  * Check if a directory is a foundation
35
35
  */
36
36
  function isFoundation(dir) {
37
- return existsSync(join(dir, 'src', 'components'))
37
+ // Primary: has foundation.js config
38
+ if (existsSync(join(dir, 'src', 'foundation.js'))) return true
39
+ // Fallback: has src/sections/
40
+ if (existsSync(join(dir, 'src', 'sections'))) return true
41
+ // Legacy fallback: has src/components/
42
+ if (existsSync(join(dir, 'src', 'components'))) return true
43
+ return false
44
+ }
45
+
46
+ /**
47
+ * Load foundation.js config from a directory
48
+ * Returns the default export, or null if not found/loadable
49
+ */
50
+ function loadFoundationJs(dir) {
51
+ const filePath = join(dir, 'src', 'foundation.js')
52
+ if (!existsSync(filePath)) return null
53
+ try {
54
+ const content = readFileSync(filePath, 'utf8')
55
+ // Simple extraction: check for extension: true
56
+ return { extension: /extension\s*:\s*true/.test(content) }
57
+ } catch {
58
+ return null
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Load built schema.json from a directory
64
+ */
65
+ function loadSchemaJson(dir) {
66
+ const schemaPath = join(dir, 'dist', 'schema.json')
67
+ if (!existsSync(schemaPath)) return null
68
+ try {
69
+ return JSON.parse(readFileSync(schemaPath, 'utf8'))
70
+ } catch {
71
+ return null
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Check if a foundation is an extension (via schema.json or foundation.js)
77
+ */
78
+ function isExtensionPackage(dir) {
79
+ const schema = loadSchemaJson(dir)
80
+ if (schema?._self?.role === 'extension') return true
81
+ const config = loadFoundationJs(dir)
82
+ return config?.extension === true
38
83
  }
39
84
 
40
85
  /**
@@ -85,7 +130,8 @@ export async function doctor(args = []) {
85
130
  }
86
131
  } else if (isFoundation(projectDir)) {
87
132
  workspaceDir = dirname(projectDir)
88
- if (basename(workspaceDir) === 'foundations') {
133
+ const parentName = basename(workspaceDir)
134
+ if (parentName === 'foundations' || parentName === 'extensions') {
89
135
  workspaceDir = dirname(workspaceDir)
90
136
  }
91
137
  }
@@ -140,6 +186,31 @@ export async function doctor(args = []) {
140
186
  }
141
187
  }
142
188
 
189
+ // Discover extensions
190
+ const extensions = []
191
+
192
+ const extensionsDir = join(workspaceDir, 'extensions')
193
+ if (existsSync(extensionsDir)) {
194
+ try {
195
+ const entries = readdirSync(extensionsDir, { withFileTypes: true })
196
+ for (const entry of entries) {
197
+ if (entry.isDirectory()) {
198
+ const extensionPath = join(extensionsDir, entry.name)
199
+ if (isFoundation(extensionPath)) {
200
+ const pkg = loadPackageJson(extensionPath)
201
+ extensions.push({
202
+ path: extensionPath,
203
+ name: pkg?.name || entry.name,
204
+ folderName: entry.name
205
+ })
206
+ }
207
+ }
208
+ }
209
+ } catch {
210
+ // Ignore errors
211
+ }
212
+ }
213
+
143
214
  if (foundations.length === 0) {
144
215
  warn('No foundations found')
145
216
  } else {
@@ -150,6 +221,15 @@ export async function doctor(args = []) {
150
221
  }
151
222
  }
152
223
 
224
+ if (extensions.length > 0) {
225
+ log('')
226
+ success(`Found ${extensions.length} extension(s):`)
227
+ for (const e of extensions) {
228
+ const nameMismatch = e.name !== e.folderName ? ` ${colors.dim}(folder: ${e.folderName}/)${colors.reset}` : ''
229
+ log(` • ${e.name}${nameMismatch}`)
230
+ }
231
+ }
232
+
153
233
  // Find all sites
154
234
  const sites = []
155
235
 
@@ -305,6 +385,105 @@ export async function doctor(args = []) {
305
385
  }
306
386
  }
307
387
 
388
+ // Check extensions
389
+ for (const ext of extensions) {
390
+ log('')
391
+ info(`Checking extension: ${ext.name}`)
392
+
393
+ // Check if it declares extension: true
394
+ if (!isExtensionPackage(ext.path)) {
395
+ issues.push({
396
+ type: 'warn',
397
+ message: `Extension "${ext.name}" in extensions/ doesn't declare extension: true in foundation.js`
398
+ })
399
+ warn(`Missing extension identity`)
400
+ log(` ${colors.dim}Add to src/foundation.js:${colors.reset}`)
401
+ log(` export default { extension: true }`)
402
+ } else {
403
+ success(`Extension identity: extension: true`)
404
+ }
405
+
406
+ // Check for vars or layouts
407
+ const config = loadFoundationJs(ext.path)
408
+ const schema = loadSchemaJson(ext.path)
409
+
410
+ if (schema?._self?.vars && Object.keys(schema._self.vars).length > 0) {
411
+ issues.push({
412
+ type: 'warn',
413
+ message: `Extension "${ext.name}" declares theme variables (vars). Extensions don't define theme variables.`
414
+ })
415
+ warn(`Extension declares vars — these won't take effect`)
416
+ }
417
+
418
+ if (schema?._layouts && Object.keys(schema._layouts).length > 0) {
419
+ issues.push({
420
+ type: 'warn',
421
+ message: `Extension "${ext.name}" provides layouts. Extensions don't provide layouts.`
422
+ })
423
+ warn(`Extension provides layouts — these won't take effect`)
424
+ }
425
+
426
+ // Check if built
427
+ const extensionDist = join(ext.path, 'dist', 'foundation.js')
428
+ if (!existsSync(extensionDist)) {
429
+ issues.push({
430
+ type: 'warn',
431
+ message: `Extension not built: ${ext.name}`
432
+ })
433
+ warn(`Extension not built yet`)
434
+ log(` ${colors.dim}Run: pnpm --filter ${ext.name} build${colors.reset}`)
435
+ } else {
436
+ success(`Extension built: dist/foundation.js exists`)
437
+ }
438
+ }
439
+
440
+ // Check if any foundation with extension: true is wired as a primary foundation
441
+ for (const f of foundations) {
442
+ if (isExtensionPackage(f.path)) {
443
+ const sitesUsingAsPrimary = sites.filter(s => {
444
+ const siteYml = loadSiteYml(s.path)
445
+ return siteYml?.foundation === f.name
446
+ })
447
+ for (const site of sitesUsingAsPrimary) {
448
+ issues.push({
449
+ type: 'warn',
450
+ site: site.name,
451
+ message: `Foundation "${f.name}" declares extension: true but is wired as the primary foundation. It should be in extensions: instead.`
452
+ })
453
+ warn(`"${f.name}" is an extension but used as primary foundation in site "${site.name}"`)
454
+ log(` ${colors.dim}Move it to extensions: in site.yml instead of foundation:${colors.reset}`)
455
+ }
456
+ }
457
+ }
458
+
459
+ // Validate extension URLs in site.yml
460
+ for (const site of sites) {
461
+ const siteYml = loadSiteYml(site.path)
462
+ if (!siteYml?.extensions || !Array.isArray(siteYml.extensions)) continue
463
+
464
+ for (const extUrl of siteYml.extensions) {
465
+ if (typeof extUrl !== 'string') continue
466
+
467
+ // Skip remote URLs — can't validate those
468
+ if (extUrl.startsWith('http://') || extUrl.startsWith('https://')) continue
469
+
470
+ // Local extension URL: check if it maps to a known extension
471
+ // URLs like /effects/foundation.js or /extensions/effects/foundation.js
472
+ const urlParts = extUrl.replace(/^\//, '').split('/')
473
+ const extName = urlParts.length >= 2 ? urlParts[urlParts.length - 2] : urlParts[0]
474
+
475
+ // Check if a matching extension exists and is built
476
+ const matchingExt = extensions.find(e => e.folderName === extName || e.name === extName)
477
+ if (matchingExt && !existsSync(join(matchingExt.path, 'dist', 'foundation.js'))) {
478
+ issues.push({
479
+ type: 'warn',
480
+ site: site.name,
481
+ message: `Extension "${extName}" referenced in site.yml is not built`
482
+ })
483
+ }
484
+ }
485
+ }
486
+
308
487
  // Summary
309
488
  log('')
310
489
  log('─'.repeat(50))