uniweb 0.1.6 → 0.2.0

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 (3) hide show
  1. package/README.md +291 -59
  2. package/package.json +3 -3
  3. package/src/index.js +298 -112
package/README.md CHANGED
@@ -1,24 +1,116 @@
1
1
  # uniweb
2
2
 
3
- CLI for the Uniweb Component Web Platform.
3
+ Create a well-structured Vite + React site with content/code separation, file-based routing, localization, and structured content—out of the box. The architecture scales to dynamic content management and collaborative visual editing when you need it.
4
4
 
5
5
  ## Quick Start
6
6
 
7
- Create a new Uniweb project with a starter template:
8
-
9
- ```bash
10
- npx uniweb@latest create my-project --template workspace
11
- ```
12
-
13
- This scaffolds a Vite project with everything wired—routing, state, and build pipeline ready. You get a site and a Foundation in a monorepo, pre-configured to work together.
7
+ Create a new Uniweb project:
14
8
 
15
9
  ```bash
10
+ npx uniweb@latest create my-project
16
11
  cd my-project
17
12
  pnpm install
18
13
  pnpm dev
19
14
  ```
20
15
 
21
- No heavy framework to learn. Foundations are React component libraries built with Vite and styled with Tailwind. Sites are Vite apps that load content from markdown files. The CLI handles the wiring.
16
+ You get a workspace with two packages:
17
+
18
+ - **`site/`** — Content, pages, entry point
19
+ - **`foundation/`** — Your React components
20
+
21
+ Content authors work in markdown. Component authors work in React. Neither can break the other's work, and component updates flow to every site using them.
22
+
23
+ ## What You Get
24
+
25
+ ```
26
+ my-project/
27
+ ├── site/ # Content + configuration
28
+ │ ├── pages/ # File-based routing
29
+ │ │ └── home/
30
+ │ │ ├── page.yml # Page metadata
31
+ │ │ └── 1-hero.md # Section content
32
+ │ ├── locales/ # i18n (mirrors pages/)
33
+ │ │ └── es/
34
+ │ └── public/ # Static assets
35
+
36
+ └── foundation/ # Your components
37
+ └── src/
38
+ └── components/
39
+ └── Hero/
40
+ └── index.jsx
41
+ ```
42
+
43
+ **Pages are folders.** Create `pages/about/` with a markdown file inside → visit `/about`. That's the whole routing model.
44
+
45
+ ### Content as Markdown
46
+
47
+ ```markdown
48
+ ---
49
+ component: Hero
50
+ theme: dark
51
+ ---
52
+
53
+ # Welcome
54
+
55
+ Build something great.
56
+
57
+ [Get Started](#)
58
+ ```
59
+
60
+ Frontmatter specifies the component and configuration. The body contains the actual content—headings, paragraphs, links, images—which gets semantically parsed into structured data your component receives.
61
+
62
+ ### Beyond Markdown
63
+
64
+ For content that doesn't fit markdown patterns—products, team members, events—use JSON blocks with schema tags:
65
+
66
+ ````markdown
67
+ ```json #team-member
68
+ {
69
+ "name": "Sarah Chen",
70
+ "role": "Lead Architect"
71
+ }
72
+ ```
73
+ ````
74
+
75
+ Components receive validated, localized data. Natural content stays in markdown; structured data goes in JSON blocks.
76
+
77
+ ### Components as React
78
+
79
+ ```jsx
80
+ export function Hero({ content, params }) {
81
+ const { title } = content.main.header;
82
+ const { paragraphs, links } = content.main.body;
83
+ const { theme = 'light' } = params;
84
+
85
+ return (
86
+ <section className={`py-20 text-center ${theme === 'dark' ? 'bg-gray-900 text-white' : ''}`}>
87
+ <h1 className="text-4xl font-bold">{title}</h1>
88
+ <p className="text-xl text-gray-600">{paragraphs[0]}</p>
89
+ {links[0] && (
90
+ <a href={links[0].url} className="mt-8 px-6 py-3 bg-blue-600 text-white rounded inline-block">
91
+ {links[0].text}
92
+ </a>
93
+ )}
94
+ </section>
95
+ );
96
+ }
97
+ ```
98
+
99
+ Standard React. Standard Tailwind. The `{ content, params }` interface is only for *exposed* components—the ones content creators select in markdown frontmatter. Internal components (the majority of your codebase) use regular React props.
100
+
101
+ No framework to learn. Foundations are purpose-built component systems designed for a specific domain (marketing, documentation, learning, etc.). Sites are Vite apps that load content from markdown files. The CLI handles the wiring.
102
+
103
+ ## The Bigger Picture
104
+
105
+ The structure you start with scales without rewrites:
106
+
107
+ 1. **Single project** — One site, one component library. Most projects stay here.
108
+ 2. **Multi-site** — One foundation powers multiple sites. Release it once, updates propagate automatically.
109
+ 3. **Full platform** — [uniweb.app](https://uniweb.app) adds visual editing, live content management, and team collaboration. Your foundation plugs in and its components become native to the editor.
110
+
111
+ Start with local markdown files deployed anywhere. Connect to [uniweb.app](https://uniweb.app) when you're ready for dynamic content and team collaboration.
112
+
113
+ ---
22
114
 
23
115
  ## Installation
24
116
 
@@ -42,9 +134,9 @@ uniweb create [project-name] [options]
42
134
 
43
135
  **Options:**
44
136
 
45
- | Option | Description |
46
- | ------------------- | ------------------------------------------------------ |
47
- | `--template <type>` | Project template: `workspace`, `site`, or `foundation` |
137
+ | Option | Description |
138
+ | ------------------- | ------------------------------------- |
139
+ | `--template <type>` | Project template: `single` or `multi` |
48
140
 
49
141
  **Examples:**
50
142
 
@@ -52,17 +144,14 @@ uniweb create [project-name] [options]
52
144
  # Interactive prompts
53
145
  uniweb create
54
146
 
55
- # Create with specific name
147
+ # Create with specific name (defaults to single template)
56
148
  uniweb create my-project
57
149
 
58
- # Full workspace (site + foundation)
59
- uniweb create my-workspace --template workspace
60
-
61
- # Standalone foundation
62
- uniweb create my-foundation --template foundation
150
+ # Single project with site + foundation
151
+ uniweb create my-project --template single
63
152
 
64
- # Standalone site
65
- uniweb create my-site --template site
153
+ # Multi-site/foundation monorepo
154
+ uniweb create my-workspace --template multi
66
155
  ```
67
156
 
68
157
  ### `build`
@@ -75,9 +164,10 @@ uniweb build [options]
75
164
 
76
165
  **Options:**
77
166
 
78
- | Option | Description |
79
- | ----------------- | --------------------------------------------------------------------- |
80
- | `--target <type>` | Build target: `foundation` or `site` (auto-detected if not specified) |
167
+ | Option | Description |
168
+ | ------------------- | --------------------------------------------------------------------- |
169
+ | `--target <type>` | Build target: `foundation` or `site` (auto-detected if not specified) |
170
+ | `--platform <name>` | Deployment platform (e.g., `vercel`) for platform-specific output |
81
171
 
82
172
  **Examples:**
83
173
 
@@ -90,56 +180,112 @@ uniweb build --target foundation
90
180
 
91
181
  # Explicitly build as site
92
182
  uniweb build --target site
183
+
184
+ # Build for Vercel deployment
185
+ uniweb build --platform vercel
93
186
  ```
94
187
 
95
188
  ## Project Templates
96
189
 
97
- ### Workspace
190
+ ### Single (Default)
98
191
 
99
- A monorepo with both a site and foundation for co-development.
192
+ A minimal workspace with a site and foundation as sibling packages. This is the recommended starting point.
100
193
 
101
194
  ```
102
- my-workspace/
103
- ├── package.json
104
- ├── pnpm-workspace.yaml
105
- └── packages/
106
- ├── site/ # Website using the foundation
107
- └── foundation/ # Component library
195
+ my-project/
196
+ ├── package.json # Workspace root (includes workspaces field for npm)
197
+ ├── pnpm-workspace.yaml # Pre-configured for evolution (see below)
198
+
199
+ ├── site/ # Site package (content + entry)
200
+ │ ├── package.json
201
+ │ ├── vite.config.js
202
+ │ ├── index.html
203
+ │ ├── site.yml
204
+ │ ├── src/
205
+ │ │ └── main.jsx # Thin entry point
206
+ │ ├── pages/ # Content structure
207
+ │ │ └── home/
208
+ │ │ ├── page.yml
209
+ │ │ └── 1-hero.md
210
+ │ ├── locales/ # i18n (mirrors pages/)
211
+ │ │ └── es/
212
+ │ │ └── home/
213
+ │ └── public/ # Static assets
214
+
215
+ └── foundation/ # Foundation package (components)
216
+ ├── package.json
217
+ ├── vite.config.js
218
+ ├── src/
219
+ │ ├── index.js # Component registry
220
+ │ ├── styles.css # Tailwind
221
+ │ ├── meta.js # Foundation metadata
222
+ │ └── components/
223
+ │ └── Hero/
224
+ │ ├── index.jsx
225
+ │ └── meta.js
226
+ └── ...
108
227
  ```
109
228
 
110
- ### Site
229
+ **Key characteristics:**
111
230
 
112
- A standalone site using an existing foundation.
231
+ - **Convention-compliant** Each package has its own `src/`
232
+ - **Clear dep boundaries** — Component libs → `foundation/package.json`, runtime → `site/package.json`
233
+ - **Zero extraction** — `foundation/` is already a complete, publishable package
234
+ - **Scales naturally** — Rename to `sites/marketing/` and `foundations/marketing/` when needed
235
+
236
+ ### Multi
237
+
238
+ A monorepo for multi-site or multi-foundation development.
113
239
 
114
240
  ```
115
- my-site/
116
- ├── package.json
117
- ├── vite.config.js
118
- ├── site.yml
119
- ├── src/
120
- └── main.jsx
121
- └── pages/
122
- └── home/
123
- ├── page.yml
124
- └── 1-hero.md
241
+ my-workspace/
242
+ ├── package.json # Workspace root (includes workspaces field for npm)
243
+ ├── pnpm-workspace.yaml # Same config as site template
244
+
245
+ ├── sites/
246
+ ├── marketing/ # Main marketing site
247
+ │ │ ├── package.json
248
+ │ │ ├── site.yml
249
+ │ │ ├── src/
250
+ │ │ ├── pages/
251
+ │ │ └── ...
252
+ │ └── docs/ # Documentation site
253
+
254
+ └── foundations/
255
+ ├── marketing/ # Marketing foundation
256
+ │ ├── package.json
257
+ │ ├── src/components/
258
+ │ └── ...
259
+ └── documentation/ # Documentation foundation
125
260
  ```
126
261
 
127
- ### Foundation
262
+ Use this when you need:
128
263
 
129
- A standalone component library.
264
+ - Multiple sites sharing foundations
265
+ - Multiple foundations for different purposes
266
+ - A testing site for foundation development
130
267
 
131
- ```
132
- my-foundation/
133
- ├── package.json
134
- ├── vite.config.js
135
- ├── tailwind.config.js
136
- └── src/
137
- ├── meta.js # Foundation metadata
138
- ├── styles.css
139
- └── components/
140
- └── Hero/
141
- ├── index.jsx
142
- └── meta.js
268
+ ## Dependency Management
269
+
270
+ Each package manages its own dependencies:
271
+
272
+ **`site/package.json`:**
273
+
274
+ - `@uniweb/runtime`
275
+ - `@my-project/foundation` (workspace link)
276
+ - Vite, Tailwind (dev)
277
+
278
+ **`foundation/package.json`:**
279
+
280
+ - Component libraries (carousel, icons, etc.)
281
+ - React as peer dependency
282
+
283
+ ```bash
284
+ # Add component dependency
285
+ cd foundation && pnpm add embla-carousel
286
+
287
+ # Site references foundation via workspace
288
+ # No path gymnastics needed
143
289
  ```
144
290
 
145
291
  ## Foundation Build Process
@@ -165,10 +311,96 @@ dist/
165
311
  └── [preset].webp
166
312
  ```
167
313
 
314
+ ## Workspace Configuration
315
+
316
+ Both templates use the same unified workspace configuration:
317
+
318
+ ```yaml
319
+ # pnpm-workspace.yaml
320
+ packages:
321
+ - "site"
322
+ - "foundation"
323
+ - "sites/*"
324
+ - "foundations/*"
325
+ ```
326
+
327
+ ```json
328
+ // package.json (workspaces field for npm compatibility)
329
+ {
330
+ "workspaces": ["site", "foundation", "sites/*", "foundations/*"]
331
+ }
332
+ ```
333
+
334
+ This means **no config changes when evolving your project**.
335
+
336
+ ## Evolving Your Project
337
+
338
+ The workspace is pre-configured for growth. Choose your path:
339
+
340
+ **Add alongside existing structure:**
341
+
342
+ ```bash
343
+ # Keep site/ and foundation/, add more in sites/ and foundations/
344
+ mkdir -p sites/docs
345
+ mkdir -p foundations/documentation
346
+ ```
347
+
348
+ **Or migrate to multi-site structure:**
349
+
350
+ ```bash
351
+ # Move and rename by purpose
352
+ mv site sites/marketing
353
+ mv foundation foundations/marketing
354
+
355
+ # Update package names and workspace dependencies in package.json files
356
+ ```
357
+
358
+ Both patterns work simultaneously—evolve gradually as needed.
359
+
360
+ ## Releasing a Foundation
361
+
362
+ Publish your foundation to [uniweb.app](https://uniweb.app) to make it available for your sites:
363
+
364
+ ```bash
365
+ uniweb login # First time only
366
+ uniweb build
367
+ uniweb publish # Publishes the foundation in the current directory
368
+ ```
369
+
370
+ Each release creates a new version. Sites link to foundations at runtime and control their own update strategy—automatic, minor-only, patch-only, or pinned to a specific version. You own your foundations and license them to sites—yours or your clients'.
371
+
372
+ You can also publish to npm:
373
+
374
+ ```bash
375
+ npm publish
376
+ ```
377
+
378
+ ## FAQ
379
+
380
+ **How is this different from MDX?**
381
+
382
+ MDX blends markdown and JSX—content authors write code. Uniweb keeps them separate: content stays in markdown, components stay in React. Content authors can't break components, and component updates don't require content changes.
383
+
384
+ **How is this different from Astro?**
385
+
386
+ Astro is a static site generator. Uniweb is a content architecture that works with any deployment (static, SSR, or platform-managed). The Foundation model means components are reusable across sites and ready for visual editing.
387
+
388
+ **Do I need uniweb.app?**
389
+
390
+ No. Local markdown files work great for developer-managed sites. The platform adds dynamic content, visual editing, and team collaboration when you need it.
391
+
392
+ **Is this SEO-friendly?**
393
+
394
+ Yes. Content is pre-embedded in the initial HTML—no fetch waterfalls, no layout shifts, instant interaction. Meta tags are generated per page for proper social sharing previews. SSG and SSR are also supported.
395
+
396
+ **What about dynamic routes?**
397
+
398
+ Pages can define data sources that auto-generate subroutes. A `/blog` page can have an index (listing) and a `[slug]` template that renders each post. No manual folders for every entry.
399
+
168
400
  ## Related Packages
169
401
 
170
- - [`@uniweb/build`](https://github.com/uniweb/build) - Foundation build tooling
171
- - [`@uniweb/runtime`](https://github.com/uniweb/runtime) - Runtime loader for sites
402
+ - [`@uniweb/build`](https://github.com/uniweb/build) Foundation build tooling
403
+ - [`@uniweb/runtime`](https://github.com/uniweb/runtime) Runtime loader for sites
172
404
 
173
405
  ## License
174
406
 
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "uniweb",
3
- "version": "0.1.6",
4
- "description": "CLI for the Uniweb Component Web Platform",
3
+ "version": "0.2.0",
4
+ "description": "Create structured Vite + React sites with content/code separation",
5
5
  "type": "module",
6
6
  "bin": {
7
- "uniweb": "./src/index.js"
7
+ "uniweb": "src/index.js"
8
8
  },
9
9
  "files": [
10
10
  "src"
package/src/index.js CHANGED
@@ -7,9 +7,8 @@
7
7
  *
8
8
  * Usage:
9
9
  * npx uniweb create [project-name]
10
- * npx uniweb create --template site
11
- * npx uniweb create --template foundation
12
- * npx uniweb create --template workspace
10
+ * npx uniweb create --template single # site/ + foundation/ (default)
11
+ * npx uniweb create --template multi # sites/* + foundations/*
13
12
  * npx uniweb build
14
13
  * npx uniweb build --target foundation
15
14
  */
@@ -51,17 +50,13 @@ function title(message) {
51
50
 
52
51
  // Template definitions
53
52
  const templates = {
54
- workspace: {
55
- name: 'Site + Foundation Workspace',
56
- description: 'Monorepo with a site and foundation for co-development',
53
+ single: {
54
+ name: 'Single Project',
55
+ description: 'One site + one foundation in site/ and foundation/ (recommended)',
57
56
  },
58
- site: {
59
- name: 'Site Only',
60
- description: 'A site that uses an existing foundation',
61
- },
62
- foundation: {
63
- name: 'Foundation Only',
64
- description: 'A standalone foundation package',
57
+ multi: {
58
+ name: 'Multi-Site Project',
59
+ description: 'Multiple sites and foundations in sites/* and foundations/*',
65
60
  },
66
61
  }
67
62
 
@@ -126,19 +121,14 @@ async function main() {
126
121
  message: 'What would you like to create?',
127
122
  choices: [
128
123
  {
129
- title: templates.workspace.name,
130
- description: templates.workspace.description,
131
- value: 'workspace',
132
- },
133
- {
134
- title: templates.site.name,
135
- description: templates.site.description,
136
- value: 'site',
124
+ title: templates.single.name,
125
+ description: templates.single.description,
126
+ value: 'single',
137
127
  },
138
128
  {
139
- title: templates.foundation.name,
140
- description: templates.foundation.description,
141
- value: 'foundation',
129
+ title: templates.multi.name,
130
+ description: templates.multi.description,
131
+ value: 'multi',
142
132
  },
143
133
  ],
144
134
  },
@@ -169,14 +159,11 @@ async function main() {
169
159
 
170
160
  // Generate project based on template
171
161
  switch (templateType) {
172
- case 'workspace':
173
- await createWorkspace(projectDir, projectName)
174
- break
175
- case 'site':
176
- await createSite(projectDir, projectName)
162
+ case 'single':
163
+ await createSingleProject(projectDir, projectName)
177
164
  break
178
- case 'foundation':
179
- await createFoundation(projectDir, projectName)
165
+ case 'multi':
166
+ await createMultiProject(projectDir, projectName)
180
167
  break
181
168
  }
182
169
 
@@ -186,15 +173,7 @@ async function main() {
186
173
  log(`Next steps:\n`)
187
174
  log(` ${colors.cyan}cd ${projectName}${colors.reset}`)
188
175
  log(` ${colors.cyan}pnpm install${colors.reset}`)
189
-
190
- if (templateType === 'workspace') {
191
- log(` ${colors.cyan}pnpm dev${colors.reset}`)
192
- } else if (templateType === 'site') {
193
- log(` ${colors.cyan}pnpm dev${colors.reset}`)
194
- } else {
195
- log(` ${colors.cyan}pnpm build${colors.reset}`)
196
- }
197
-
176
+ log(` ${colors.cyan}pnpm dev${colors.reset}`)
198
177
  log('')
199
178
  }
200
179
 
@@ -210,50 +189,62 @@ ${colors.bright}Commands:${colors.reset}
210
189
  build Build the current project
211
190
 
212
191
  ${colors.bright}Create Options:${colors.reset}
213
- --template <type> Project template (workspace, site, foundation)
192
+ --template <type> Project template (single, multi)
214
193
 
215
194
  ${colors.bright}Build Options:${colors.reset}
216
195
  --target <type> Build target (foundation, site) - auto-detected if not specified
196
+ --platform <name> Deployment platform (e.g., vercel) for platform-specific output
217
197
 
218
198
  ${colors.bright}Examples:${colors.reset}
219
199
  npx uniweb create my-project
220
- npx uniweb create my-site --template site
221
- npx uniweb create my-foundation --template foundation
200
+ npx uniweb create my-project --template single
201
+ npx uniweb create my-workspace --template multi
222
202
  npx uniweb build
223
203
  npx uniweb build --target foundation
224
204
 
225
205
  ${colors.bright}Templates:${colors.reset}
226
- workspace Site + Foundation monorepo for co-development
227
- site Standalone site using an existing foundation
228
- foundation Standalone foundation package
206
+ single One site + one foundation in site/ and foundation/ (default)
207
+ multi Multiple sites and foundations in sites/* and foundations/*
229
208
  `)
230
209
  }
231
210
 
232
211
  // Template generators
233
- async function createWorkspace(projectDir, projectName) {
212
+
213
+ /**
214
+ * Creates the common project base: package.json, pnpm-workspace.yaml, .gitignore
215
+ * Both single and multi templates share the same workspace configuration.
216
+ */
217
+ function createProjectBase(projectDir, projectName, defaultFilter) {
234
218
  mkdirSync(projectDir, { recursive: true })
235
219
 
236
- // Root package.json
220
+ // Root package.json (workspaces field for npm compatibility)
237
221
  writeJSON(join(projectDir, 'package.json'), {
238
222
  name: projectName,
239
223
  version: '0.1.0',
240
224
  private: true,
241
225
  type: 'module',
242
- workspaces: ['packages/*'],
243
226
  scripts: {
244
- dev: 'pnpm --filter site dev',
245
- 'dev:runtime': 'VITE_FOUNDATION_MODE=runtime pnpm --filter site dev',
246
- 'build:foundation': 'pnpm --filter foundation build',
247
- build: 'pnpm build:foundation && pnpm --filter site build',
227
+ dev: `pnpm --filter ${defaultFilter} dev`,
228
+ 'dev:runtime': `VITE_FOUNDATION_MODE=runtime pnpm --filter ${defaultFilter} dev`,
229
+ build: 'pnpm -r build',
248
230
  },
231
+ workspaces: [
232
+ 'site',
233
+ 'foundation',
234
+ 'sites/*',
235
+ 'foundations/*',
236
+ ],
249
237
  pnpm: {
250
238
  onlyBuiltDependencies: ['esbuild', 'sharp'],
251
239
  },
252
240
  })
253
241
 
254
- // pnpm-workspace.yaml
242
+ // pnpm-workspace.yaml (all patterns for seamless evolution)
255
243
  writeFile(join(projectDir, 'pnpm-workspace.yaml'), `packages:
256
- - 'packages/*'
244
+ - 'site'
245
+ - 'foundation'
246
+ - 'sites/*'
247
+ - 'foundations/*'
257
248
  `)
258
249
 
259
250
  // .gitignore
@@ -262,11 +253,19 @@ dist
262
253
  .DS_Store
263
254
  *.local
264
255
  `)
256
+ }
257
+
258
+ /**
259
+ * Creates a single project with site/ and foundation/ as sibling packages.
260
+ * This is the default template for new projects.
261
+ */
262
+ async function createSingleProject(projectDir, projectName) {
263
+ createProjectBase(projectDir, projectName, 'site')
265
264
 
266
265
  // README.md
267
266
  writeFile(join(projectDir, 'README.md'), `# ${projectName}
268
267
 
269
- A Uniweb workspace with a site and foundation for co-development.
268
+ Structured Vite + React, ready to go.
270
269
 
271
270
  ## Quick Start
272
271
 
@@ -281,16 +280,21 @@ Open [http://localhost:3000](http://localhost:3000) to see your site.
281
280
 
282
281
  \`\`\`
283
282
  ${projectName}/
284
- ├── packages/
285
- │ ├── site/ # Your website
286
- │ │ ├── pages/ # Content pages (markdown + YAML)
287
- │ │ ├── src/ # Site entry point
288
- │ │ └── vite.config.js
289
- └── foundation/ # Your component library
290
- └── src/
291
- ├── components/ # React components
292
- ├── meta.js # Foundation metadata
293
- │ └── styles.css # Tailwind styles
283
+ ├── site/ # Content + configuration
284
+ │ ├── pages/ # File-based routing
285
+ │ │ └── home/
286
+ │ │ ├── page.yml # Page metadata
287
+ │ │ └── 1-hero.md # Section content
288
+ ├── locales/ # i18n (mirrors pages/)
289
+ ├── src/ # Site entry point
290
+ └── public/ # Static assets
291
+
292
+ ├── foundation/ # Your components
293
+ │ └── src/
294
+ │ ├── components/ # React components
295
+ │ ├── meta.js # Foundation metadata
296
+ │ └── styles.css # Tailwind styles
297
+
294
298
  ├── package.json
295
299
  └── pnpm-workspace.yaml
296
300
  \`\`\`
@@ -303,8 +307,8 @@ ${projectName}/
303
307
  pnpm dev
304
308
  \`\`\`
305
309
 
306
- Edit components in \`packages/foundation/src/components/\` and see changes instantly via HMR.
307
- Edit content in \`packages/site/pages/\` to add or modify pages.
310
+ Edit components in \`foundation/src/components/\` and see changes instantly via HMR.
311
+ Edit content in \`site/pages/\` to add or modify pages.
308
312
 
309
313
  ### Runtime Loading Mode
310
314
 
@@ -318,24 +322,23 @@ Use this to debug issues that only appear in production.
318
322
  ## Building for Production
319
323
 
320
324
  \`\`\`bash
321
- # Build both foundation and site
322
325
  pnpm build
323
326
  \`\`\`
324
327
 
325
- Output:
326
- - \`packages/foundation/dist/\` — Bundled foundation (JS + CSS + schema)
327
- - \`packages/site/dist/\` — Production-ready site
328
+ **Output:**
329
+ - \`foundation/dist/\` — Bundled components, CSS, and schema.json
330
+ - \`site/dist/\` — Production-ready static site
328
331
 
329
332
  ## Adding Components
330
333
 
331
- 1. Create a new folder in \`packages/foundation/src/components/YourComponent/\`
334
+ 1. Create a new folder in \`foundation/src/components/YourComponent/\`
332
335
  2. Add \`index.jsx\` with your React component
333
336
  3. Add \`meta.js\` describing the component's content slots and options
334
- 4. Export from \`packages/foundation/src/index.js\`
337
+ 4. Export from \`foundation/src/index.js\`
335
338
 
336
339
  ## Adding Pages
337
340
 
338
- 1. Create a folder in \`packages/site/pages/your-page/\`
341
+ 1. Create a folder in \`site/pages/your-page/\`
339
342
  2. Add \`page.yml\` with page metadata
340
343
  3. Add markdown files (\`1-section.md\`, \`2-section.md\`, etc.) for each section
341
344
 
@@ -344,24 +347,48 @@ Each markdown file specifies which component to use:
344
347
  \`\`\`markdown
345
348
  ---
346
349
  component: Hero
347
- title: Your Title
348
- subtitle: Your subtitle
349
350
  ---
350
351
 
351
- Optional body content here.
352
+ # Your Title
353
+
354
+ Your subtitle or description here.
355
+
356
+ [Get Started](#)
352
357
  \`\`\`
353
358
 
354
- ## Configuration
359
+ ## Scaling Up
355
360
 
356
- Each site has a \`site.yml\` that configures which foundation it uses:
361
+ The workspace is pre-configured for growth—no config changes needed.
357
362
 
358
- \`\`\`yaml
359
- name: site
360
- defaultLanguage: en
361
- foundation: foundation # References packages/foundation
363
+ **Add a second site** (keep existing structure):
364
+
365
+ \`\`\`bash
366
+ mkdir -p sites/docs
367
+ # Create your docs site in sites/docs/
362
368
  \`\`\`
363
369
 
364
- To add multiple sites or foundations, create new packages and update each site's \`site.yml\`.
370
+ **Or migrate to multi-site structure**:
371
+
372
+ \`\`\`bash
373
+ # Move and rename by purpose
374
+ mv site sites/marketing
375
+ mv foundation foundations/marketing
376
+
377
+ # Update package names in package.json files
378
+ # Update dependencies to reference new names
379
+ \`\`\`
380
+
381
+ Both patterns work simultaneously—evolve gradually as needed.
382
+
383
+ ## Publishing Your Foundation
384
+
385
+ Your \`foundation/\` is already a complete package:
386
+
387
+ \`\`\`bash
388
+ cd foundation
389
+ npx uniweb build
390
+ npm publish
391
+ \`\`\`
365
392
 
366
393
  ## What is Uniweb?
367
394
 
@@ -372,22 +399,145 @@ Sites provide content that flows through Foundations.
372
399
  Learn more:
373
400
  - [Uniweb on GitHub](https://github.com/uniweb)
374
401
  - [CLI Documentation](https://github.com/uniweb/cli)
375
- - [uniweb.app](https://uniweb.app) — Full publishing platform
402
+ - [uniweb.app](https://uniweb.app) — Visual editing platform
376
403
 
377
404
  `)
378
405
 
379
406
  // Create site package
380
- await createSite(join(projectDir, 'packages/site'), 'site', true)
407
+ await createSite(join(projectDir, 'site'), 'site', true)
381
408
 
382
409
  // Create foundation package
383
- await createFoundation(join(projectDir, 'packages/foundation'), 'foundation', true)
410
+ await createFoundation(join(projectDir, 'foundation'), 'foundation', true)
384
411
 
385
412
  // Update site to reference workspace foundation
386
- const sitePackageJson = join(projectDir, 'packages/site/package.json')
413
+ const sitePackageJson = join(projectDir, 'site/package.json')
387
414
  const sitePkg = JSON.parse(readFile(sitePackageJson))
388
415
  sitePkg.dependencies['foundation'] = 'workspace:*'
389
416
  writeJSON(sitePackageJson, sitePkg)
390
417
 
418
+ success(`Created project: ${projectName}`)
419
+ }
420
+
421
+ /**
422
+ * Creates a multi-site/foundation workspace with sites/ and foundations/ directories.
423
+ * Used for larger projects with multiple sites sharing foundations.
424
+ */
425
+ async function createMultiProject(projectDir, projectName) {
426
+ createProjectBase(projectDir, projectName, 'marketing')
427
+
428
+ // README.md
429
+ writeFile(join(projectDir, 'README.md'), `# ${projectName}
430
+
431
+ A Uniweb workspace for multiple sites and foundations.
432
+
433
+ ## Quick Start
434
+
435
+ \`\`\`bash
436
+ pnpm install
437
+ pnpm dev
438
+ \`\`\`
439
+
440
+ Open [http://localhost:3000](http://localhost:3000) to see the marketing site.
441
+
442
+ ## Project Structure
443
+
444
+ \`\`\`
445
+ ${projectName}/
446
+ ├── sites/
447
+ │ └── marketing/ # Main marketing site
448
+ │ ├── pages/ # Content pages
449
+ │ ├── locales/ # i18n
450
+ │ └── src/
451
+
452
+ ├── foundations/
453
+ │ └── marketing/ # Marketing foundation
454
+ │ └── src/
455
+ │ ├── components/ # React components
456
+ │ └── styles.css # Tailwind styles
457
+
458
+ ├── package.json
459
+ └── pnpm-workspace.yaml
460
+ \`\`\`
461
+
462
+ ## Development
463
+
464
+ \`\`\`bash
465
+ # Run the marketing site (default)
466
+ pnpm dev
467
+
468
+ # Run a specific site
469
+ pnpm --filter docs dev
470
+
471
+ # Runtime loading mode (tests production behavior)
472
+ pnpm dev:runtime
473
+ \`\`\`
474
+
475
+ ## Adding More Sites
476
+
477
+ Create a new site in \`sites/\`:
478
+
479
+ \`\`\`bash
480
+ mkdir -p sites/docs
481
+ # Copy structure from sites/marketing or create manually
482
+ \`\`\`
483
+
484
+ Update \`sites/docs/site.yml\` to specify which foundation:
485
+
486
+ \`\`\`yaml
487
+ name: docs
488
+ defaultLanguage: en
489
+ foundation: documentation # Or marketing, or any foundation
490
+ \`\`\`
491
+
492
+ ## Adding More Foundations
493
+
494
+ Create a new foundation in \`foundations/\`:
495
+
496
+ \`\`\`bash
497
+ mkdir -p foundations/documentation
498
+ # Build components for documentation use case
499
+ \`\`\`
500
+
501
+ Name foundations by purpose: marketing, documentation, learning, etc.
502
+
503
+ ## Building for Production
504
+
505
+ \`\`\`bash
506
+ # Build everything
507
+ pnpm build
508
+
509
+ # Build specific packages
510
+ pnpm --filter marketing build
511
+ pnpm --filter foundations/marketing build
512
+ \`\`\`
513
+
514
+ ## Learn More
515
+
516
+ - [Uniweb on GitHub](https://github.com/uniweb)
517
+ - [uniweb.app](https://uniweb.app) — Visual editing platform
518
+
519
+ `)
520
+
521
+ // Create first site in sites/marketing
522
+ await createSite(join(projectDir, 'sites/marketing'), 'marketing', true)
523
+
524
+ // Create first foundation in foundations/marketing
525
+ await createFoundation(join(projectDir, 'foundations/marketing'), 'marketing', true)
526
+
527
+ // Update site to reference workspace foundation
528
+ const sitePackageJson = join(projectDir, 'sites/marketing/package.json')
529
+ const sitePkg = JSON.parse(readFile(sitePackageJson))
530
+ sitePkg.dependencies['marketing'] = 'workspace:*'
531
+ writeJSON(sitePackageJson, sitePkg)
532
+
533
+ // Update site.yml to reference the marketing foundation
534
+ writeFile(join(projectDir, 'sites/marketing/site.yml'), `name: marketing
535
+ defaultLanguage: en
536
+
537
+ # Foundation to use for this site
538
+ foundation: marketing
539
+ `)
540
+
391
541
  success(`Created workspace: ${projectName}`)
392
542
  }
393
543
 
@@ -569,13 +719,13 @@ order: 1
569
719
  // pages/home/1-hero.md
570
720
  writeFile(join(projectDir, 'pages/home/1-hero.md'), `---
571
721
  component: Hero
572
- title: Welcome to ${projectName}
573
- subtitle: Built with Uniweb and Vite
574
- ctaText: Get Started
575
- ctaUrl: "#"
576
722
  ---
577
723
 
578
- Your content goes here.
724
+ # Welcome to ${projectName}
725
+
726
+ Built with Uniweb and Vite.
727
+
728
+ [Get Started](#)
579
729
  `)
580
730
 
581
731
  // README.md (only for standalone site, not workspace)
@@ -623,19 +773,22 @@ order: 2
623
773
  \`\`\`markdown
624
774
  ---
625
775
  component: Hero
626
- title: Section Title
627
- subtitle: Section subtitle
776
+ theme: dark
628
777
  ---
629
778
 
630
- Optional markdown content here.
779
+ # Section Title
780
+
781
+ Section description here.
782
+
783
+ [Call to Action](#)
631
784
  \`\`\`
632
785
 
633
786
  ## How It Works
634
787
 
635
788
  - Each folder in \`pages/\` becomes a route (\`/home\`, \`/about\`, etc.)
636
789
  - Section files are numbered to control order (\`1-*.md\`, \`2-*.md\`)
637
- - The \`component\` field specifies which Foundation component renders the section
638
- - Other frontmatter fields become the component's content
790
+ - Frontmatter specifies the component and configuration parameters
791
+ - Content in the markdown body is semantically parsed into structured data
639
792
 
640
793
  ## Configuration
641
794
 
@@ -847,22 +1000,36 @@ export { default } from './index.js'
847
1000
  // src/components/Hero/index.jsx
848
1001
  writeFile(join(projectDir, 'src/components/Hero/index.jsx'), `import React from 'react'
849
1002
 
850
- export function Hero({ content }) {
851
- const { title, subtitle, ctaText, ctaUrl } = content
1003
+ export function Hero({ content, params }) {
1004
+ // Extract from semantic parser structure
1005
+ const { title } = content.main?.header || {}
1006
+ const { paragraphs = [], links = [] } = content.main?.body || {}
1007
+ const { theme = 'light' } = params || {}
1008
+
1009
+ const isDark = theme === 'dark'
1010
+ const cta = links[0]
852
1011
 
853
1012
  return (
854
- <section className="py-20 px-6 bg-gradient-to-br from-primary to-blue-700 text-white">
1013
+ <section className={\`py-20 px-6 \${isDark ? 'bg-gradient-to-br from-primary to-blue-700 text-white' : 'bg-gray-50'}\`}>
855
1014
  <div className="max-w-4xl mx-auto text-center">
856
- <h1 className="text-4xl md:text-5xl font-bold mb-6">{title}</h1>
857
- {subtitle && (
858
- <p className="text-lg text-blue-100 mb-8">{subtitle}</p>
1015
+ {title && (
1016
+ <h1 className="text-4xl md:text-5xl font-bold mb-6">{title}</h1>
1017
+ )}
1018
+ {paragraphs[0] && (
1019
+ <p className={\`text-lg mb-8 \${isDark ? 'text-blue-100' : 'text-gray-600'}\`}>
1020
+ {paragraphs[0]}
1021
+ </p>
859
1022
  )}
860
- {ctaText && ctaUrl && (
1023
+ {cta && (
861
1024
  <a
862
- href={ctaUrl}
863
- className="inline-block px-6 py-3 bg-white text-primary font-semibold rounded-lg hover:bg-blue-50 transition-colors"
1025
+ href={cta.url}
1026
+ className={\`inline-block px-6 py-3 font-semibold rounded-lg transition-colors \${
1027
+ isDark
1028
+ ? 'bg-white text-primary hover:bg-blue-50'
1029
+ : 'bg-primary text-white hover:bg-blue-700'
1030
+ }\`}
864
1031
  >
865
- {ctaText}
1032
+ {cta.text}
866
1033
  </a>
867
1034
  )}
868
1035
  </div>
@@ -876,26 +1043,45 @@ export default Hero
876
1043
  // src/components/Hero/meta.js
877
1044
  writeFile(join(projectDir, 'src/components/Hero/meta.js'), `/**
878
1045
  * Hero Component Metadata
1046
+ *
1047
+ * Content comes from the markdown body (parsed semantically):
1048
+ * - H1 → title (content.main.header.title)
1049
+ * - Paragraphs → description (content.main.body.paragraphs)
1050
+ * - Links → CTA buttons (content.main.body.links)
879
1051
  */
880
1052
  export default {
881
1053
  title: 'Hero Banner',
882
- description: 'A prominent header section with headline, subtitle, and call-to-action',
1054
+ description: 'A prominent header section with headline, description, and call-to-action',
883
1055
  category: 'Headers',
884
1056
 
1057
+ // Content structure (informational - describes what the semantic parser provides)
885
1058
  elements: {
886
1059
  title: {
887
1060
  label: 'Headline',
1061
+ description: 'From H1 in markdown',
888
1062
  required: true,
889
1063
  },
890
- subtitle: {
891
- label: 'Subtitle',
1064
+ paragraphs: {
1065
+ label: 'Description',
1066
+ description: 'From paragraphs in markdown',
892
1067
  },
893
1068
  links: {
894
1069
  label: 'Call to Action',
1070
+ description: 'From links in markdown',
895
1071
  },
896
1072
  },
897
1073
 
1074
+ // Configuration parameters (set in frontmatter)
898
1075
  properties: {
1076
+ theme: {
1077
+ type: 'select',
1078
+ label: 'Theme',
1079
+ options: [
1080
+ { value: 'light', label: 'Light' },
1081
+ { value: 'dark', label: 'Dark' },
1082
+ ],
1083
+ default: 'light',
1084
+ },
899
1085
  alignment: {
900
1086
  type: 'select',
901
1087
  label: 'Text Alignment',