uniweb 0.6.18 → 0.6.20

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
@@ -85,6 +85,8 @@ my-project/
85
85
 
86
86
  **Pages are folders.** Create `pages/about/` with markdown files inside → visit `/about`. That's the whole routing model.
87
87
 
88
+ **Batteries included:** File-based routing, pre-rendering, localization, dynamic routes, media processing, search indexing, and more. See [Building with Uniweb](https://github.com/uniweb/docs/blob/main/development/building-with-uniweb.md) for the full list.
89
+
88
90
  ### Content as Markdown
89
91
 
90
92
  ```markdown
@@ -163,7 +165,7 @@ After creating your project:
163
165
 
164
166
  3. **Learn the configuration** — Run `uniweb docs site` or `uniweb docs page` for quick reference on configuration options.
165
167
 
166
- 4. **Create a section type** — Add a file to `foundation/src/sections/` (e.g., `Banner.jsx`) and rebuild. Bare files at the root are discovered automatically — no `meta.js` needed. Add `meta.js` when you want to declare params or presets. See the [Component Metadata Guide](./docs/component-metadata.md) for the full schema.
168
+ 4. **Create a section type** — Add a file to `foundation/src/sections/` (e.g., `Banner.jsx`) and rebuild. Bare files at the root are discovered automatically — no `meta.js` needed. Add `meta.js` when you want to declare params or presets. See the [Component Metadata Reference](https://github.com/uniweb/docs/blob/main/reference/component-metadata.md) for the full schema.
167
169
 
168
170
  The `meta.js` file defines what content and parameters a component accepts. The runtime uses this metadata to apply defaults and guarantee content structure—no defensive null checks needed in your component code.
169
171
 
@@ -234,24 +236,27 @@ Start with local files deployed anywhere. The same foundation works across all t
234
236
 
235
237
  ---
236
238
 
237
- ## Guides and Documentation
238
-
239
- [Developer Guides](./guides/developers/) — Building foundations and components, converting designs, component patterns, theming contexts
239
+ ## Documentation
240
240
 
241
- [Content Author Guides](./guides/authors/) — Writing pages in markdown, writing dynamic content, site setup, theming, recipes
241
+ Full documentation is available at **[github.com/uniweb/docs](https://github.com/uniweb/docs)**:
242
242
 
243
- [Reference Docs](./docs/) Full reference for all configuration, commands, and framework
243
+ | Section | Topics |
244
+ | ------- | ------ |
245
+ | [Getting Started](https://github.com/uniweb/docs/tree/main/getting-started) | Introduction, quickstart, templates |
246
+ | [Authoring](https://github.com/uniweb/docs/tree/main/authoring) | Writing content, site setup, theming, collections, translations |
247
+ | [Development](https://github.com/uniweb/docs/tree/main/development) | Building foundations, component patterns, data fetching, layouts |
248
+ | [Reference](https://github.com/uniweb/docs/tree/main/reference) | Configuration files, kit API, CLI commands, deployment |
244
249
 
245
250
  ### Quick Reference
246
251
 
247
252
  | Topic | Guide |
248
253
  | ------------------ | ------------------------------------------------------------------- |
249
- | Content Structure | [How markdown becomes component props](./docs/content-structure.md) |
250
- | Component Metadata | [The meta.js schema](./docs/component-metadata.md) |
251
- | Site Configuration | [site.yml reference](./docs/site-configuration.md) |
252
- | CLI Commands | [create, build, docs, i18n](./docs/cli-commands.md) |
253
- | Templates | [Built-in, official, and external templates](./docs/templates.md) |
254
- | Deployment | [Vercel, Netlify, Cloudflare, and more](./docs/deployment.md) |
254
+ | Content Structure | [How markdown becomes component props](https://github.com/uniweb/docs/blob/main/reference/content-structure.md) |
255
+ | Component Metadata | [The meta.js schema](https://github.com/uniweb/docs/blob/main/reference/component-metadata.md) |
256
+ | Site Configuration | [site.yml reference](https://github.com/uniweb/docs/blob/main/reference/site-configuration.md) |
257
+ | CLI Commands | [create, build, docs, i18n](https://github.com/uniweb/docs/blob/main/reference/cli-commands.md) |
258
+ | Templates | [Built-in, official, and external templates](https://github.com/uniweb/docs/blob/main/getting-started/templates.md) |
259
+ | Deployment | [Vercel, Netlify, Cloudflare, and more](https://github.com/uniweb/docs/blob/main/reference/deployment.md) |
255
260
 
256
261
  ---
257
262
 
@@ -349,6 +354,10 @@ Yes. Content is pre-embedded in the initial HTML—no fetch waterfalls, no layou
349
354
 
350
355
  Pages can define data sources that auto-generate subroutes. A `/blog` page can have an index and a `[slug]` template that renders each post.
351
356
 
357
+ **Is Uniweb good for documentation sites?**
358
+
359
+ Yes — documentation is a natural fit. Content stays in markdown (easy to version, review, and contribute to), while the foundation handles navigation, search, and rendering. [Uniweb's own docs](https://github.com/uniweb/docs) use this pattern: pure markdown in a public repo, rendered by a separate foundation.
360
+
352
361
  ## Related Packages
353
362
 
354
363
  - [`@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.6.18",
3
+ "version": "0.6.20",
4
4
  "description": "Create structured Vite + React sites with content/code separation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -42,7 +42,7 @@
42
42
  "tar": "^7.0.0",
43
43
  "@uniweb/core": "0.4.7",
44
44
  "@uniweb/kit": "0.5.10",
45
- "@uniweb/runtime": "0.5.21",
46
- "@uniweb/build": "0.6.15"
45
+ "@uniweb/build": "0.6.17",
46
+ "@uniweb/runtime": "0.5.21"
47
47
  }
48
48
  }
@@ -159,6 +159,10 @@ site/pages/
159
159
 
160
160
  Decimals insert between: `2.5-testimonials.md` goes between `2-` and `3-`.
161
161
 
162
+ **Ignored files/folders:**
163
+ - `README.md` — repo documentation, not site content
164
+ - `_*.md` or `_*/` — drafts and private content (e.g., `_drafts/`, `_old-hero.md`)
165
+
162
166
  **page.yml:**
163
167
  ```yaml
164
168
  title: About Us
@@ -369,3 +373,23 @@ Use with: `bg-primary`, `text-primary`, `bg-primary/10`
369
373
  **Component not appearing** — Verify `meta.js` exists and doesn't have `hidden: true`. Rebuild: `cd foundation && pnpm build`.
370
374
 
371
375
  **Styles not applying** — Verify `@source` in `styles.css` includes your component paths. Check custom colors match `@theme` definitions.
376
+
377
+ ## Further Documentation
378
+
379
+ Full Uniweb documentation is available at **https://github.com/uniweb/docs** — raw markdown files you can fetch directly.
380
+
381
+ | Section | Path | Topics |
382
+ |---------|------|--------|
383
+ | **Getting Started** | `getting-started/` | What is Uniweb, quickstart guide, templates overview |
384
+ | **Authoring** | `authoring/` | Writing content, site setup, collections, theming, linking, search, recipes, translations |
385
+ | **Development** | `development/` | Building foundations, component patterns, data fetching, custom layouts, i18n, converting existing designs |
386
+ | **Reference** | `reference/` | site.yml, page.yml, content structure, meta.js, kit hooks/components, theming tokens, CLI commands, deployment |
387
+
388
+ **Quick access pattern:** `https://raw.githubusercontent.com/uniweb/docs/main/{section}/{page}.md`
389
+
390
+ Examples:
391
+ - Content structure details: `reference/content-structure.md`
392
+ - Component metadata (meta.js): `reference/component-metadata.md`
393
+ - Kit hooks and components: `reference/kit-reference.md`
394
+ - Theming tokens: `reference/site-theming.md`
395
+ - Data fetching patterns: `reference/data-fetching.md`
@@ -4,11 +4,12 @@
4
4
  * Commands for managing site content internationalization.
5
5
  *
6
6
  * Usage:
7
- * uniweb i18n extract Extract translatable strings to manifest
8
- * uniweb i18n init [locales] Generate starter locale files from manifest
9
- * uniweb i18n sync Sync manifest with content changes
10
- * uniweb i18n status Show translation coverage per locale
11
- * uniweb i18n --target <path> Specify site directory explicitly
7
+ * uniweb i18n extract Extract/update translatable strings (default)
8
+ * uniweb i18n generate [locales] Generate starter locale files from manifest
9
+ * uniweb i18n status Show translation coverage per locale
10
+ * uniweb i18n --target <path> Specify site directory explicitly
11
+ *
12
+ * Aliases: sync → extract, init → generate
12
13
  *
13
14
  * When run from workspace root, auto-detects sites. If multiple exist,
14
15
  * prompts for selection.
@@ -81,9 +82,9 @@ export async function i18n(args) {
81
82
  // Parse --target option
82
83
  const { target, remainingArgs } = parseTargetOption(args)
83
84
 
84
- // Default to 'sync' if no subcommand (or if first arg is an option)
85
+ // Default to 'extract' if no subcommand (or if first arg is an option)
85
86
  const firstArg = remainingArgs[0]
86
- const effectiveSubcommand = !firstArg || firstArg.startsWith('-') ? 'sync' : firstArg
87
+ const effectiveSubcommand = !firstArg || firstArg.startsWith('-') ? 'extract' : firstArg
87
88
  const effectiveArgs = !firstArg || firstArg.startsWith('-') ? remainingArgs : remainingArgs.slice(1)
88
89
 
89
90
  // Find site root
@@ -98,14 +99,13 @@ export async function i18n(args) {
98
99
 
99
100
  switch (effectiveSubcommand) {
100
101
  case 'extract':
102
+ case 'sync':
101
103
  await runExtract(siteRoot, config, effectiveArgs)
102
104
  break
105
+ case 'generate':
103
106
  case 'init':
104
107
  await runInit(siteRoot, config, effectiveArgs)
105
108
  break
106
- case 'sync':
107
- await runSync(siteRoot, config, effectiveArgs)
108
- break
109
109
  case 'status':
110
110
  await runStatus(siteRoot, config, effectiveArgs)
111
111
  break
@@ -228,13 +228,14 @@ async function loadSiteConfig(siteRoot) {
228
228
  */
229
229
  async function runExtract(siteRoot, config, args) {
230
230
  const verbose = args.includes('--verbose') || args.includes('-v')
231
+ const dryRun = args.includes('--dry-run')
231
232
  const collectionsOnly = args.includes('--collections-only') || args.includes('--collections')
232
233
  const noCollections = args.includes('--no-collections')
233
234
  // --with-collections is now a no-op (collections are included by default)
234
235
 
235
236
  // Extract page content (unless --collections-only)
236
237
  if (!collectionsOnly) {
237
- log(`\n${colors.cyan}Extracting translatable content...${colors.reset}\n`)
238
+ log(`\n${colors.cyan}Extracting translatable content${dryRun ? ' (dry run)' : ''}...${colors.reset}\n`)
238
239
 
239
240
  // Check if site has been built
240
241
  const siteContentPath = join(siteRoot, 'dist', 'site-content.json')
@@ -247,22 +248,32 @@ async function runExtract(siteRoot, config, args) {
247
248
  // Dynamic import to avoid loading at CLI startup
248
249
  const { extractManifest, formatSyncReport } = await import('@uniweb/build/i18n')
249
250
 
251
+ // Check if this is a first-time extract (no previous manifest)
252
+ const manifestPath = join(siteRoot, config.localesDir, 'manifest.json')
253
+ const isUpdate = existsSync(manifestPath)
254
+
250
255
  const { manifest, report } = await extractManifest(siteRoot, {
251
256
  localesDir: config.localesDir,
252
257
  siteContentPath,
253
258
  verbose,
259
+ dryRun,
254
260
  })
255
261
 
256
262
  // Show results
257
263
  const unitCount = Object.keys(manifest.units).length
258
264
  success(`Extracted ${unitCount} translatable strings`)
259
265
 
260
- if (report) {
266
+ // Show sync report for updates (skip on first extract — everything would be "added")
267
+ if (report && isUpdate) {
261
268
  log('')
262
269
  log(formatSyncReport(report))
263
270
  }
264
271
 
265
- log(`\nManifest written to: ${colors.dim}${config.localesDir}/manifest.json${colors.reset}`)
272
+ if (dryRun) {
273
+ log(`\n${colors.dim}Dry run — no files were modified.${colors.reset}`)
274
+ } else {
275
+ log(`\nManifest written to: ${colors.dim}${config.localesDir}/manifest.json${colors.reset}`)
276
+ }
266
277
 
267
278
  if (config.locales.length === 0) {
268
279
  log(`\n${colors.dim}No translation files found in ${config.localesDir}/.`)
@@ -277,7 +288,7 @@ async function runExtract(siteRoot, config, args) {
277
288
 
278
289
  // Extract collection content (by default, skip with --no-collections)
279
290
  if (!noCollections) {
280
- log(`\n${colors.cyan}Extracting collection content...${colors.reset}\n`)
291
+ log(`\n${colors.cyan}Extracting collection content${dryRun ? ' (dry run)' : ''}...${colors.reset}\n`)
281
292
 
282
293
  // Check if collections exist
283
294
  const dataDir = join(siteRoot, 'public', 'data')
@@ -293,20 +304,28 @@ async function runExtract(siteRoot, config, args) {
293
304
  try {
294
305
  const { extractCollectionManifest, formatSyncReport } = await import('@uniweb/build/i18n')
295
306
 
307
+ const collectionsManifestPath = join(siteRoot, config.localesDir, 'collections', 'manifest.json')
308
+ const isUpdate = existsSync(collectionsManifestPath)
309
+
296
310
  const { manifest, report } = await extractCollectionManifest(siteRoot, {
297
311
  localesDir: config.localesDir,
312
+ dryRun,
298
313
  })
299
314
 
300
315
  const unitCount = Object.keys(manifest.units).length
301
316
  if (unitCount > 0) {
302
317
  success(`Extracted ${unitCount} translatable strings from collections`)
303
318
 
304
- if (report) {
319
+ if (report && isUpdate) {
305
320
  log('')
306
321
  log(formatSyncReport(report))
307
322
  }
308
323
 
309
- log(`\nManifest written to: ${colors.dim}${config.localesDir}/collections/manifest.json${colors.reset}`)
324
+ if (dryRun) {
325
+ log(`\n${colors.dim}Dry run — no files were modified.${colors.reset}`)
326
+ } else {
327
+ log(`\nManifest written to: ${colors.dim}${config.localesDir}/collections/manifest.json${colors.reset}`)
328
+ }
310
329
  } else {
311
330
  log(`${colors.dim}No translatable content found in collections.${colors.reset}`)
312
331
  }
@@ -319,13 +338,13 @@ async function runExtract(siteRoot, config, args) {
319
338
  }
320
339
 
321
340
  /**
322
- * Init command - generate starter translation files from manifest
341
+ * Generate command - generate starter translation files from manifest
323
342
  *
324
343
  * Usage:
325
- * uniweb i18n init es fr Initialize specific locales
326
- * uniweb i18n init Initialize all configured locales
327
- * uniweb i18n init --empty Use empty strings instead of source text
328
- * uniweb i18n init --force Overwrite existing files entirely
344
+ * uniweb i18n generate es fr Generate specific locales
345
+ * uniweb i18n generate Generate all configured locales
346
+ * uniweb i18n generate --empty Use empty strings instead of source text
347
+ * uniweb i18n generate --force Overwrite existing files entirely
329
348
  */
330
349
  async function runInit(siteRoot, config, args) {
331
350
  const useEmpty = args.includes('--empty')
@@ -360,7 +379,7 @@ async function runInit(siteRoot, config, args) {
360
379
 
361
380
  if (!targetLocales || targetLocales.length === 0) {
362
381
  error('No target locales specified.')
363
- log(`${colors.dim}Specify locales as arguments (e.g., "uniweb i18n init es fr")`)
382
+ log(`${colors.dim}Specify locales as arguments (e.g., "uniweb i18n generate es fr")`)
364
383
  log(`or configure them in site.yml under i18n.locales.${colors.reset}`)
365
384
  process.exit(1)
366
385
  }
@@ -425,55 +444,6 @@ async function runInit(siteRoot, config, args) {
425
444
  log(` 3. Run 'uniweb i18n status' to check coverage${colors.reset}`)
426
445
  }
427
446
 
428
- /**
429
- * Sync command - detect changes and update manifest
430
- */
431
- async function runSync(siteRoot, config, args) {
432
- const verbose = args.includes('--verbose') || args.includes('-v')
433
- const dryRun = args.includes('--dry-run')
434
-
435
- log(`\n${colors.cyan}Syncing i18n manifest...${colors.reset}\n`)
436
-
437
- // Check if site has been built
438
- const siteContentPath = join(siteRoot, 'dist', 'site-content.json')
439
- if (!existsSync(siteContentPath)) {
440
- error('Site content not found. Run "uniweb build" first.')
441
- process.exit(1)
442
- }
443
-
444
- // Check if manifest exists
445
- const manifestPath = join(siteRoot, config.localesDir, 'manifest.json')
446
- if (!existsSync(manifestPath)) {
447
- warn('No existing manifest found. Running extract instead.')
448
- return runExtract(siteRoot, config, args)
449
- }
450
-
451
- try {
452
- const { extractManifest, formatSyncReport } = await import('@uniweb/build/i18n')
453
-
454
- if (dryRun) {
455
- log(`${colors.dim}(dry run - no files will be modified)${colors.reset}\n`)
456
- }
457
-
458
- const { manifest, report } = await extractManifest(siteRoot, {
459
- localesDir: config.localesDir,
460
- siteContentPath,
461
- verbose,
462
- dryRun,
463
- })
464
-
465
- log(formatSyncReport(report))
466
-
467
- if (!dryRun) {
468
- success('\nManifest updated')
469
- }
470
- } catch (err) {
471
- error(`Sync failed: ${err.message}`)
472
- if (verbose) console.error(err)
473
- process.exit(1)
474
- }
475
- }
476
-
477
447
  /**
478
448
  * Status command - show translation coverage
479
449
  */
@@ -1458,10 +1428,8 @@ ${colors.bright}Usage:${colors.reset}
1458
1428
 
1459
1429
  ${colors.bright}Commands:${colors.reset}
1460
1430
  ${colors.dim}# Hash-based (granular) translation${colors.reset}
1461
- (default) Same as sync - extract/update strings (runs if no command given)
1462
- extract Extract translatable strings to locales/manifest.json
1463
- init Generate starter locale files from manifest keys
1464
- sync Update manifest with content changes (detects moved/changed content)
1431
+ extract Extract/update translatable strings (default if no command given)
1432
+ generate Generate starter locale files from manifest keys
1465
1433
  status Show translation coverage per locale
1466
1434
  audit Find stale translations (no longer in manifest) and missing ones
1467
1435
 
@@ -1475,9 +1443,9 @@ ${colors.bright}Commands:${colors.reset}
1475
1443
  ${colors.bright}Options:${colors.reset}
1476
1444
  -t, --target <path> Site directory (auto-detected if not specified)
1477
1445
  --verbose Show detailed output
1478
- --dry-run (sync/prune) Show changes without writing files
1479
- --empty (init) Use empty strings instead of source text
1480
- --force (init) Overwrite existing locale files entirely
1446
+ --dry-run (extract/prune) Preview changes without writing files
1447
+ --empty (generate) Use empty strings instead of source text
1448
+ --force (generate) Overwrite existing locale files entirely
1481
1449
  --clean (audit) Remove stale entries from locale files
1482
1450
  --missing (status) List all missing strings instead of summary
1483
1451
  --freeform (status/prune) Include free-form translation status
@@ -1500,7 +1468,7 @@ ${colors.bright}Configuration:${colors.reset}
1500
1468
  ${colors.bright}Workflow:${colors.reset}
1501
1469
  1. Build your site: uniweb build
1502
1470
  2. Extract strings: uniweb i18n extract
1503
- 3. Initialize locale files: uniweb i18n init es fr
1471
+ 3. Generate locale files: uniweb i18n generate es fr
1504
1472
  4. Translate locale files: Edit locales/es.json, locales/fr.json, etc.
1505
1473
  5. Build with translations: uniweb build (generates locale-specific output)
1506
1474
 
@@ -1519,18 +1487,18 @@ ${colors.bright}File Structure:${colors.reset}
1519
1487
 
1520
1488
  ${colors.bright}Examples:${colors.reset}
1521
1489
  ${colors.dim}# Hash-based workflow${colors.reset}
1522
- uniweb i18n extract # Extract all translatable strings
1523
- uniweb i18n extract --verbose # Show extracted strings
1490
+ uniweb i18n extract # Extract all translatable strings
1491
+ uniweb i18n extract --dry-run # Preview without writing
1492
+ uniweb i18n extract --verbose # Show extracted strings
1524
1493
  uniweb i18n extract --no-collections # Pages only (skip collections)
1525
- uniweb i18n init es fr # Create starter files for Spanish and French
1526
- uniweb i18n init --empty # Create files with empty values (for translators)
1527
- uniweb i18n init --force # Overwrite existing locale files
1528
- uniweb i18n sync # Update manifest after content changes
1529
- uniweb i18n status # Show coverage for all locales
1530
- uniweb i18n status es # Show coverage for Spanish only
1494
+ uniweb i18n generate es fr # Create starter files for Spanish and French
1495
+ uniweb i18n generate --empty # Create files with empty values (for translators)
1496
+ uniweb i18n generate --force # Overwrite existing locale files
1497
+ uniweb i18n status # Show coverage for all locales
1498
+ uniweb i18n status es # Show coverage for Spanish only
1531
1499
  uniweb i18n status es --missing --json # Export missing for AI translation
1532
- uniweb i18n audit # Find stale and missing translations
1533
- uniweb i18n audit --clean # Remove stale entries
1500
+ uniweb i18n audit # Find stale and missing translations
1501
+ uniweb i18n audit --clean # Remove stale entries
1534
1502
 
1535
1503
  ${colors.dim}# Free-form workflow (complete section replacement)${colors.reset}
1536
1504
  uniweb i18n init-freeform es pages/about hero
@@ -1542,6 +1510,10 @@ ${colors.bright}Examples:${colors.reset}
1542
1510
  uniweb i18n prune --freeform --dry-run # Preview orphan cleanup
1543
1511
  uniweb i18n --target site # Specify site directory explicitly
1544
1512
 
1513
+ ${colors.bright}Aliases:${colors.reset}
1514
+ sync → extract (backward-compatible)
1515
+ init → generate (backward-compatible)
1516
+
1545
1517
  ${colors.bright}Notes:${colors.reset}
1546
1518
  Run from a site directory to operate on that site.
1547
1519
  Run from workspace root to auto-detect sites (prompts if multiple).