uniweb 0.3.1 → 0.3.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 CHANGED
@@ -13,6 +13,20 @@ pnpm install
13
13
  pnpm dev
14
14
  ```
15
15
 
16
+ Open http://localhost:5173 to see your site. Edit files in `site/pages/` and `foundation/src/components/` to see changes instantly.
17
+
18
+ ### Development Commands
19
+
20
+ Run these from the **project root** (where `pnpm-workspace.yaml` is):
21
+
22
+ ```bash
23
+ pnpm dev # Start development server
24
+ pnpm build # Build foundation + site for production
25
+ pnpm preview # Preview the production build
26
+ ```
27
+
28
+ The `build` command generates static HTML in `site/dist/`. Open those files to verify your output before deploying.
29
+
16
30
  The `marketing` template includes real components (Hero, Features, Pricing, Testimonials, FAQ, and more) with sample content—a working site you can explore and modify.
17
31
 
18
32
  **Other templates:**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uniweb",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Create structured Vite + React sites with content/code separation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -39,7 +39,7 @@
39
39
  "tar": "^7.0.0",
40
40
  "@uniweb/build": "0.2.1",
41
41
  "@uniweb/core": "0.2.1",
42
- "@uniweb/runtime": "0.3.0",
43
- "@uniweb/kit": "0.2.1"
42
+ "@uniweb/kit": "0.2.1",
43
+ "@uniweb/runtime": "0.3.0"
44
44
  }
45
45
  }
@@ -10,7 +10,7 @@
10
10
  * uniweb build --prerender # Build site + pre-render to static HTML (SSG)
11
11
  */
12
12
 
13
- import { existsSync } from 'node:fs'
13
+ import { existsSync, readFileSync, readdirSync } from 'node:fs'
14
14
  import { resolve, join } from 'node:path'
15
15
  import { spawn } from 'node:child_process'
16
16
  import { writeFile, mkdir } from 'node:fs/promises'
@@ -53,22 +53,51 @@ function info(message) {
53
53
 
54
54
  /**
55
55
  * Detect project type based on directory contents
56
+ *
57
+ * Detection priority:
58
+ * 1. foundation.js → foundation
59
+ * 2. site.yml → site
60
+ * 3. src/components/ → foundation (fallback)
61
+ * 4. pages/ → site (fallback)
62
+ * 5. pnpm-workspace.yaml or package.json workspaces → workspace
56
63
  */
57
64
  function detectProjectType(projectDir) {
58
- const srcDir = join(projectDir, 'src')
59
- const componentsDir = join(srcDir, 'components')
60
- const pagesDir = join(projectDir, 'pages')
65
+ // Primary detection: config files
66
+ if (existsSync(join(projectDir, 'src', 'foundation.js'))) {
67
+ return 'foundation'
68
+ }
61
69
 
62
- // Foundation: has src/components/ with component folders containing meta.js
63
- if (existsSync(componentsDir)) {
70
+ if (existsSync(join(projectDir, 'site.yml'))) {
71
+ return 'site'
72
+ }
73
+
74
+ // Fallback detection: directory structure
75
+ if (existsSync(join(projectDir, 'src', 'components'))) {
64
76
  return 'foundation'
65
77
  }
66
78
 
67
- // Site: has pages/ directory
68
- if (existsSync(pagesDir)) {
79
+ if (existsSync(join(projectDir, 'pages'))) {
69
80
  return 'site'
70
81
  }
71
82
 
83
+ // Workspace: has pnpm-workspace.yaml or package.json with workspaces
84
+ if (existsSync(join(projectDir, 'pnpm-workspace.yaml'))) {
85
+ return 'workspace'
86
+ }
87
+
88
+ // Check package.json for workspaces field
89
+ const packageJsonPath = join(projectDir, 'package.json')
90
+ if (existsSync(packageJsonPath)) {
91
+ try {
92
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'))
93
+ if (packageJson.workspaces) {
94
+ return 'workspace'
95
+ }
96
+ } catch {
97
+ // Ignore parse errors
98
+ }
99
+ }
100
+
72
101
  return null
73
102
  }
74
103
 
@@ -395,6 +424,123 @@ async function buildSite(projectDir, options = {}) {
395
424
  }
396
425
  }
397
426
 
427
+ /**
428
+ * Check if a directory is a foundation
429
+ */
430
+ function isFoundation(dir) {
431
+ // Primary: has foundation.js config
432
+ if (existsSync(join(dir, 'src', 'foundation.js'))) return true
433
+ // Fallback: has src/components/
434
+ if (existsSync(join(dir, 'src', 'components'))) return true
435
+ return false
436
+ }
437
+
438
+ /**
439
+ * Check if a directory is a site
440
+ */
441
+ function isSite(dir) {
442
+ // Primary: has site.yml config
443
+ if (existsSync(join(dir, 'site.yml'))) return true
444
+ // Fallback: has pages/
445
+ if (existsSync(join(dir, 'pages'))) return true
446
+ return false
447
+ }
448
+
449
+ /**
450
+ * Discover workspace packages based on workspace config
451
+ */
452
+ function discoverWorkspacePackages(workspaceDir) {
453
+ const foundations = []
454
+ const sites = []
455
+
456
+ // Check standard locations
457
+ const standardFoundation = join(workspaceDir, 'foundation')
458
+ const standardSite = join(workspaceDir, 'site')
459
+
460
+ if (existsSync(standardFoundation) && isFoundation(standardFoundation)) {
461
+ foundations.push({ name: 'foundation', path: standardFoundation })
462
+ }
463
+
464
+ if (existsSync(standardSite) && isSite(standardSite)) {
465
+ sites.push({ name: 'site', path: standardSite })
466
+ }
467
+
468
+ // Check multi-site locations (foundations/*, sites/*)
469
+ const foundationsDir = join(workspaceDir, 'foundations')
470
+ const sitesDir = join(workspaceDir, 'sites')
471
+
472
+ if (existsSync(foundationsDir)) {
473
+ for (const name of readdirSync(foundationsDir)) {
474
+ const path = join(foundationsDir, name)
475
+ if (isFoundation(path)) {
476
+ foundations.push({ name, path })
477
+ }
478
+ }
479
+ }
480
+
481
+ if (existsSync(sitesDir)) {
482
+ for (const name of readdirSync(sitesDir)) {
483
+ const path = join(sitesDir, name)
484
+ if (isSite(path)) {
485
+ sites.push({ name, path })
486
+ }
487
+ }
488
+ }
489
+
490
+ return { foundations, sites }
491
+ }
492
+
493
+ /**
494
+ * Build all packages in a workspace
495
+ */
496
+ async function buildWorkspace(workspaceDir, options = {}) {
497
+ const { prerenderFlag, noPrerenderFlag } = options
498
+
499
+ log(`${colors.cyan}${colors.bright}Building workspace...${colors.reset}`)
500
+ log('')
501
+
502
+ const { foundations, sites } = discoverWorkspacePackages(workspaceDir)
503
+
504
+ if (foundations.length === 0 && sites.length === 0) {
505
+ error('No foundations or sites found in workspace')
506
+ log('')
507
+ log('Expected structure:')
508
+ log(' foundation/ or foundations/*/')
509
+ log(' site/ or sites/*/')
510
+ process.exit(1)
511
+ }
512
+
513
+ // Build foundations first (sites depend on them)
514
+ for (const foundation of foundations) {
515
+ log(`${colors.bright}[${foundation.name}]${colors.reset}`)
516
+ await buildFoundation(foundation.path)
517
+ log('')
518
+ }
519
+
520
+ // Build sites
521
+ for (const site of sites) {
522
+ log(`${colors.bright}[${site.name}]${colors.reset}`)
523
+
524
+ const siteConfig = readSiteConfig(site.path)
525
+ const configPrerender = siteConfig.build?.prerender === true
526
+
527
+ let prerender = configPrerender
528
+ if (prerenderFlag) prerender = true
529
+ if (noPrerenderFlag) prerender = false
530
+
531
+ // Resolve foundation directory for this site
532
+ const foundationDir = resolveFoundationDir(site.path, siteConfig)
533
+
534
+ await buildSite(site.path, { prerender, foundationDir, siteConfig })
535
+ log('')
536
+ }
537
+
538
+ // Summary
539
+ log(`${colors.green}${colors.bright}Workspace build complete!${colors.reset}`)
540
+ log('')
541
+ log(`Built ${foundations.length} foundation(s) and ${sites.length} site(s)`)
542
+ }
543
+
398
544
  /**
399
545
  * Main build command handler
400
546
  */
@@ -430,22 +576,26 @@ export async function build(args = []) {
430
576
 
431
577
  if (!targetType) {
432
578
  error('Could not detect project type')
433
- log('Use --target foundation or --target site')
579
+ log('Use --target foundation or --target site, or run from workspace root')
434
580
  process.exit(1)
435
581
  }
436
582
 
437
- info(`Detected project type: ${targetType}`)
583
+ if (targetType !== 'workspace') {
584
+ info(`Detected project type: ${targetType}`)
585
+ }
438
586
  }
439
587
 
440
- // Validate prerender flags are only used with site target
441
- if ((prerenderFlag || noPrerenderFlag) && targetType !== 'site') {
442
- error('--prerender/--no-prerender can only be used with site builds')
588
+ // Validate prerender flags are only used with site/workspace target
589
+ if ((prerenderFlag || noPrerenderFlag) && targetType === 'foundation') {
590
+ error('--prerender/--no-prerender can only be used with site or workspace builds')
443
591
  process.exit(1)
444
592
  }
445
593
 
446
594
  // Run appropriate build
447
595
  try {
448
- if (targetType === 'foundation') {
596
+ if (targetType === 'workspace') {
597
+ await buildWorkspace(projectDir, { prerenderFlag, noPrerenderFlag })
598
+ } else if (targetType === 'foundation') {
449
599
  await buildFoundation(projectDir)
450
600
  } else {
451
601
  // For sites, read config to determine prerender default
package/src/index.js CHANGED
@@ -68,10 +68,6 @@ const templates = {
68
68
  name: 'Multi-Site Workspace',
69
69
  description: 'Multiple sites and foundations in sites/* and foundations/*',
70
70
  },
71
- template: {
72
- name: 'Template Starter',
73
- description: 'Create a shareable Uniweb template for npm or GitHub',
74
- },
75
71
  }
76
72
 
77
73
  /**
@@ -235,11 +231,6 @@ async function main() {
235
231
  description: templates.multi.description,
236
232
  value: 'multi',
237
233
  },
238
- {
239
- title: templates.template.name,
240
- description: templates.template.description,
241
- value: 'template',
242
- },
243
234
  ],
244
235
  },
245
236
  ], {
@@ -300,6 +291,11 @@ async function main() {
300
291
  })
301
292
  } catch (err) {
302
293
  error(`Failed to apply template: ${err.message}`)
294
+ log('')
295
+ log(`${colors.yellow}Troubleshooting:${colors.reset}`)
296
+ log(` • Check your network connection`)
297
+ log(` • Official templates require GitHub access (may be blocked by corporate networks)`)
298
+ log(` • Try the built-in template instead: ${colors.cyan}uniweb create ${projectName}${colors.reset}`)
303
299
  process.exit(1)
304
300
  }
305
301
  }
@@ -339,6 +335,7 @@ ${colors.bright}Build Options:${colors.reset}
339
335
  --foundation-dir Path to foundation directory (for prerendering)
340
336
  --platform <name> Deployment platform (e.g., vercel) for platform-specific output
341
337
 
338
+ At workspace root, builds all foundations first, then all sites.
342
339
  Pre-rendering is enabled by default when build.prerender: true in site.yml
343
340
 
344
341
  ${colors.bright}Docs Subcommands:${colors.reset}
@@ -359,7 +356,6 @@ ${colors.bright}i18n Commands:${colors.reset}
359
356
  ${colors.bright}Template Types:${colors.reset}
360
357
  single One site + one foundation (default)
361
358
  multi Multiple sites and foundations
362
- template Starter for creating shareable templates
363
359
  marketing Official marketing template
364
360
  @scope/template-name npm package
365
361
  github:user/repo GitHub repository
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  // Built-in templates (file-based in cli/templates/)
6
- export const BUILTIN_TEMPLATES = ['single', 'multi', 'template']
6
+ export const BUILTIN_TEMPLATES = ['single', 'multi']
7
7
 
8
8
  // Official templates from @uniweb/templates package (downloaded from GitHub releases)
9
9
  export const OFFICIAL_TEMPLATES = ['marketing', 'academic', 'docs', 'international']