uniweb 1.19.2 → 1.20.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uniweb",
3
- "version": "1.19.2",
3
+ "version": "1.20.0",
4
4
  "description": "Create structured Vite + React sites with content/code separation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -37,18 +37,18 @@
37
37
  "node": ">=22.12"
38
38
  },
39
39
  "dependencies": {
40
- "handlebars": "^4.7.8",
41
- "js-yaml": "^4.1.0",
40
+ "handlebars": "^4.7.10",
41
+ "js-yaml": "^4.3.2",
42
42
  "prompts": "^2.4.2",
43
- "tar": "^7.0.0",
44
- "@uniweb/core": "~1.4.6",
45
- "@uniweb/content-writer": "~1.3.0",
43
+ "tar": "^7.5.21",
44
+ "@uniweb/content-writer": "~1.3.1",
46
45
  "@uniweb/schemas": "~1.6.5",
46
+ "@uniweb/core": "~1.4.6",
47
47
  "@uniweb/semantic-parser": "~1.13.0"
48
48
  },
49
49
  "peerDependencies": {
50
- "@uniweb/build": "~1.9.1",
51
- "@uniweb/content-reader": "~1.5.6",
50
+ "@uniweb/build": "~1.10.0",
51
+ "@uniweb/content-reader": "~1.5.7",
52
52
  "@uniweb/semantic-parser": "~1.13.0"
53
53
  },
54
54
  "peerDependenciesMeta": {
@@ -801,6 +801,8 @@ The point is the pair. An agent fetches the index, *reads* what each page is abo
801
801
 
802
802
  **Large sites also get per-branch indexes.** A branch with at least 5 pages gets its own — `/docs/llms.txt` lists just the docs, titled by that folder. The site index still lists everything; a branch index is an extra entry point, not a replacement, so an agent that starts at `/llms.txt` still reaches any page in two hops.
803
803
 
804
+ **Each language gets its own.** A site in several languages has `/fr/llms.txt` beside `/llms.txt`, listing the French pages, record pages included, and linking each at its French address (`/fr/a-propos.md`). Its search index is per language the same way (`/fr/search-index.json`).
805
+
804
806
  ```yaml
805
807
  # site.yml — all optional; these are the defaults
806
808
  agents:
@@ -12,8 +12,9 @@
12
12
  * --bundle (internal; called by `uniweb deploy --host` / `uniweb export`)
13
13
  * Full vite + post-vite pipeline. Produces a static-host JS bundle
14
14
  * (`dist/index.html`, `dist/entry.js`, `_importmap/*`, `_pages/*` for
15
- * split mode, sitemap/robots/search-index, prerendered HTML when
16
- * configured). The foundation is statically inlined when site.yml names a
15
+ * split mode, sitemap/robots, each language's search index, llms.txt and
16
+ * page markdown, prerendered HTML when configured). The foundation is
17
+ * statically inlined when site.yml names a
17
18
  * workspace-local one (the self-contained case), and loaded by URL when
18
19
  * it names a URL, or under VITE_FOUNDATION_MODE=runtime. A catalog ref
19
20
  * (`@org/name@version`) is refused (`detectFoundationType`).
@@ -461,19 +462,38 @@ async function loadI18nConfig(projectDir, siteConfig = null) {
461
462
  }
462
463
 
463
464
  /**
464
- * Build localized content for all configured locales
465
+ * Build every configured language but the default: its records, its content, and — on the bundle
466
+ * lane — its search index, llms.txt and page markdown, in that order (`buildLocalizedSite`).
467
+ *
468
+ * ⛔ Until 2026-10-25 each lane ran content, then records, here in two copies, and a language's
469
+ * search index was written in the content step, before its records were translated.
465
470
  */
466
- async function buildLocalizedContent(projectDir, i18nConfig) {
467
- const { buildLocalizedContent } = await import('@uniweb/build/i18n')
468
-
469
- const outputs = await buildLocalizedContent(projectDir, {
470
- localesDir: i18nConfig.localesDir,
471
+ async function buildLocalizedSite(projectDir, i18nConfig, { projections = false } = {}) {
472
+ const { buildLocalizedSite } = await import('@uniweb/build/i18n')
473
+ return buildLocalizedSite(projectDir, {
471
474
  locales: i18nConfig.locales,
475
+ localesDir: i18nConfig.localesDir,
472
476
  outputDir: join(projectDir, 'dist'),
473
- fallbackToSource: true
477
+ projections
474
478
  })
479
+ }
475
480
 
476
- return outputs
481
+ /** What `buildLocalizedSite` wrote, per language. */
482
+ function reportLocalizedSite(built) {
483
+ const locales = Object.keys(built.content)
484
+ success(`Generated ${locales.length} locale(s)`)
485
+ for (const locale of locales) {
486
+ log(` ${colors.dim}dist/${locale}/site-content.json${colors.reset}`)
487
+ const files = built.projections[locale] || []
488
+ for (const file of files) {
489
+ if (file.kind === 'search') log(` ${colors.dim}dist/${file.fileName} (${file.count} entries)${colors.reset}`)
490
+ if (file.kind === 'index') log(` ${colors.dim}dist/${file.fileName}${colors.reset}`)
491
+ }
492
+ const markdown = files.filter((file) => file.kind === 'markdown').length
493
+ if (markdown) log(` ${colors.dim}dist/${locale}/ — ${markdown} page markdown projections${colors.reset}`)
494
+ }
495
+ const translated = Object.values(built.records).filter((outputs) => Object.keys(outputs).length > 0).length
496
+ if (translated > 0) success(`Translated records for ${translated} locale(s)`)
477
497
  }
478
498
 
479
499
  /**
@@ -586,16 +606,16 @@ function resolveFoundationDir(projectDir, siteConfig) {
586
606
  *
587
607
  * Does NOT emit HTML, JS, CSS, source maps, _importmap chunks, processed media
588
608
  * (dist/assets/), or static-host extras (sitemap, robots, search-index,
589
- * _pages/*) — `publish` reads none of them. ⛔ *It processed media into
590
- * dist/assets/ — WebP copies, video posters, PDF thumbnails — until 2026-10-19.*
591
- * ⚠️ Except each extra language's `dist/<lang>/search-index.json`: the
592
- * `buildLocalizedContent` step below writes one on this lane too, and nothing
593
- * reads it (measured 2026-10-25).
609
+ * llms.txt, page markdown, _pages/*), in any language — `publish` reads none
610
+ * of them. ⛔ *It processed media into dist/assets/ — WebP copies, video
611
+ * posters, PDF thumbnails — until 2026-10-19, and until 2026-10-25 wrote the
612
+ * default language's llms.txt and page markdown, and each other language's
613
+ * search index.*
594
614
  *
595
- * The `buildLocalizedContent` step is the same call bundle mode makes
596
- * post-vite, so multi-locale sites get identical per-locale outputs in
597
- * either mode. Record translation (`buildLocalizedRecords`) also runs here,
598
- * writing each locale's query files to dist/<lang>/data/.
615
+ * The `buildLocalizedSite` step is the call bundle mode makes post-vite,
616
+ * without the projections, so multi-locale sites get identical per-locale
617
+ * outputs in either mode: each language's query files (dist/<lang>/data/),
618
+ * then its content (dist/<lang>/site-content.json).
599
619
  *
600
620
  * History: when `deploy` uploaded `dist/site-content.json` to a backend, it
601
621
  * read the bundle pipeline's copy, which prerender rewrites into a lightweight
@@ -634,49 +654,16 @@ async function buildSiteLink(projectDir, options = {}) {
634
654
  })
635
655
  success(`Wrote ${join('dist', 'site-content.json')}`)
636
656
 
637
- // Per-locale variants — same call bundle mode makes post-vite. Both
638
- // modes produce identical `dist/<lang>/site-content.json` outputs, and
639
- // `publish` reads which languages were built from them.
657
+ // Per-locale variants — the call bundle mode makes post-vite, without the
658
+ // projections: a host derives its own. Both modes produce identical
659
+ // `dist/<lang>/site-content.json` outputs, and `publish` reads which
660
+ // languages were built from them.
640
661
  const i18nConfig = await loadI18nConfig(projectDir, siteConfig)
641
662
  if (i18nConfig && i18nConfig.locales.length > 0) {
642
663
  log('')
643
664
  info(`Building localized content for: ${i18nConfig.locales.join(', ')}`)
644
665
  try {
645
- const outputs = await buildLocalizedContent(projectDir, i18nConfig)
646
- success(`Generated ${Object.keys(outputs).length} locale(s)`)
647
- for (const [locale] of Object.entries(outputs)) {
648
- log(` ${colors.dim}dist/${locale}/site-content.json${colors.reset}`)
649
- }
650
-
651
- // Collection translations — optional; don't fail the build if
652
- // missing. Bundle mode does the same.
653
- try {
654
- const { buildLocalizedRecords } = await import('@uniweb/build/i18n')
655
- const recordOutputs = await buildLocalizedRecords(projectDir, {
656
- locales: i18nConfig.locales,
657
- outputDir: distDir,
658
- recordLocalesDir: join(
659
- projectDir,
660
- i18nConfig.localesDir,
661
- 'records'
662
- ),
663
- // Free-form record translations live under it — the default was `locales/`
664
- // whatever the site said.
665
- localesDir: join(projectDir, i18nConfig.localesDir)
666
- })
667
- const recordCount = Object.values(recordOutputs).reduce(
668
- (sum, localeOutputs) => sum + Object.keys(localeOutputs).length,
669
- 0
670
- )
671
- if (recordCount > 0) {
672
- success(
673
- `Translated records for ${Object.keys(recordOutputs).length} locale(s)`
674
- )
675
- }
676
- } catch (err) {
677
- if (process.env.UNIWEB_DEBUG)
678
- console.error('Record translation:', err.message)
679
- }
666
+ reportLocalizedSite(await buildLocalizedSite(projectDir, i18nConfig))
680
667
  } catch (err) {
681
668
  error(`i18n build failed: ${err.message}`)
682
669
  if (process.env.UNIWEB_DEBUG) console.error(err.stack)
@@ -722,52 +709,14 @@ async function buildSite(projectDir, options = {}) {
722
709
  info(`Building localized content for: ${i18nConfig.locales.join(', ')}`)
723
710
 
724
711
  try {
725
- // Generate locale-specific site-content.json
726
- const outputs = await buildLocalizedContent(projectDir, i18nConfig)
712
+ // Each language's records, then its content, then its search index, llms.txt and page
713
+ // markdown, made from both.
714
+ const built = await buildLocalizedSite(projectDir, i18nConfig, { projections: true })
727
715
 
728
716
  // Generate locale-specific index.html files
729
717
  await generateLocalizedHtml(projectDir, i18nConfig)
730
718
 
731
- success(`Generated ${Object.keys(outputs).length} locale(s)`)
732
-
733
- for (const [locale, path] of Object.entries(outputs)) {
734
- log(` ${colors.dim}dist/${locale}/site-content.json${colors.reset}`)
735
- }
736
-
737
- // Translate collections if they exist
738
- try {
739
- const { buildLocalizedRecords } = await import('@uniweb/build/i18n')
740
-
741
- const recordOutputs = await buildLocalizedRecords(projectDir, {
742
- locales: i18nConfig.locales,
743
- outputDir: join(projectDir, 'dist'),
744
- recordLocalesDir: join(
745
- projectDir,
746
- i18nConfig.localesDir,
747
- 'records'
748
- ),
749
- // Free-form record translations live under it — the default was `locales/`
750
- // whatever the site said.
751
- localesDir: join(projectDir, i18nConfig.localesDir)
752
- })
753
-
754
- // Count collections translated
755
- const recordCount = Object.values(recordOutputs).reduce(
756
- (sum, localeOutputs) => sum + Object.keys(localeOutputs).length,
757
- 0
758
- )
759
-
760
- if (recordCount > 0) {
761
- success(
762
- `Translated records for ${Object.keys(recordOutputs).length} locale(s)`
763
- )
764
- }
765
- } catch (err) {
766
- // Collection translation is optional, don't fail build
767
- if (process.env.UNIWEB_DEBUG) {
768
- console.error('Record translation:', err.message)
769
- }
770
- }
719
+ reportLocalizedSite(built)
771
720
  } catch (err) {
772
721
  error(`i18n build failed: ${err.message}`)
773
722
  if (process.env.UNIWEB_DEBUG) {
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-10-25T19:16:49.028Z",
3
+ "generatedAt": "2026-10-25T20:26:22.771Z",
4
4
  "packages": {
5
5
  "@uniweb/api": {
6
6
  "version": "1.6.4"
7
7
  },
8
8
  "@uniweb/build": {
9
- "version": "1.9.1"
9
+ "version": "1.10.0"
10
10
  },
11
11
  "@uniweb/content-reader": {
12
- "version": "1.5.6"
12
+ "version": "1.5.7"
13
13
  },
14
14
  "@uniweb/content-writer": {
15
- "version": "1.3.0"
15
+ "version": "1.3.1"
16
16
  },
17
17
  "@uniweb/core": {
18
18
  "version": "1.4.6"
@@ -51,7 +51,7 @@
51
51
  "version": "1.13.0"
52
52
  },
53
53
  "@uniweb/snapshot": {
54
- "version": "1.1.0"
54
+ "version": "1.1.1"
55
55
  },
56
56
  "@uniweb/templates": {
57
57
  "version": "1.1.5"
@@ -60,7 +60,7 @@
60
60
  "version": "1.2.1"
61
61
  },
62
62
  "@uniweb/unipress": {
63
- "version": "1.1.15"
63
+ "version": "1.1.16"
64
64
  }
65
65
  },
66
66
  "templates": {