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 +14 -0
- package/package.json +3 -3
- package/src/commands/build.js +164 -14
- package/src/index.js +6 -10
- package/src/templates/resolver.js +1 -1
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.
|
|
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/
|
|
43
|
-
"@uniweb/
|
|
42
|
+
"@uniweb/kit": "0.2.1",
|
|
43
|
+
"@uniweb/runtime": "0.3.0"
|
|
44
44
|
}
|
|
45
45
|
}
|
package/src/commands/build.js
CHANGED
|
@@ -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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
65
|
+
// Primary detection: config files
|
|
66
|
+
if (existsSync(join(projectDir, 'src', 'foundation.js'))) {
|
|
67
|
+
return 'foundation'
|
|
68
|
+
}
|
|
61
69
|
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 === '
|
|
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'
|
|
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']
|