@uniweb/build 0.19.0 → 0.20.1

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/build",
3
- "version": "0.19.0",
3
+ "version": "0.20.1",
4
4
  "description": "Build tooling for the Uniweb Component Web Platform",
5
5
  "type": "module",
6
6
  "exports": {
@@ -59,16 +59,16 @@
59
59
  "js-yaml": "^4.1.0",
60
60
  "sharp": "^0.35.3",
61
61
  "yaml": "^2.5.0",
62
- "@uniweb/content-writer": "^0.3.3",
63
- "@uniweb/projections": "^0.2.8",
62
+ "@uniweb/schemas": "^0.2.10",
64
63
  "@uniweb/theming": "^0.1.15",
65
- "@uniweb/schemas": "^0.2.10"
64
+ "@uniweb/content-writer": "^0.3.3",
65
+ "@uniweb/projections": "^0.2.8"
66
66
  },
67
67
  "optionalDependencies": {
68
- "@uniweb/content-reader": "^1.2.2",
69
68
  "@uniweb/runtime": "^0.11.7",
70
69
  "@uniweb/schemas": "^0.2.10",
71
- "@uniweb/semantic-parser": "^1.2.2"
70
+ "@uniweb/semantic-parser": "^1.2.2",
71
+ "@uniweb/content-reader": "^1.2.2"
72
72
  },
73
73
  "peerDependencies": {
74
74
  "vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
package/src/prerender.js CHANGED
@@ -148,6 +148,41 @@ async function executeAllFetches(siteContent, siteDir, onProgress, localeInfo) {
148
148
  * @param {function} onProgress - Progress callback
149
149
  * @returns {Array} Expanded pages array with dynamic pages replaced by concrete instances
150
150
  */
151
+ /**
152
+ * Where a content-less container's redirect stub should point, in THIS locale.
153
+ *
154
+ * A folder with no body of its own gets a `<meta http-equiv="refresh">` stub so
155
+ * the redirect works without JS. `page.getNavigableRoute()` answers in CANONICAL
156
+ * routes — right, because the hierarchy is canonical and "does this folder have
157
+ * a page of its own?" is not a language question. But the stub is per-locale, so
158
+ * the destination has to be localized exactly the way `outputRoute` is: translate
159
+ * the slug, then re-apply the locale prefix.
160
+ *
161
+ * Emitting the canonical route raw sent a reader who asked for `/fr/<container>`
162
+ * to the DEFAULT-locale page — a language switch they never asked for, from a
163
+ * URL that was correct. The runtime's copy of this redirect (PageRenderer.jsx)
164
+ * had the identical bug and was fixed first; this is its twin, and the two must
165
+ * agree or a container behaves differently on a cold load than on an in-app
166
+ * navigation.
167
+ *
168
+ * The DECISION to redirect stays canonical (`target !== page.route`) — only the
169
+ * destination is localized. Comparing a localized destination against a
170
+ * canonical route would make a folder with an index child redirect to itself.
171
+ *
172
+ * @param {string} target - Canonical route from getNavigableRoute()
173
+ * @param {Object} ctx
174
+ * @param {Object} ctx.website - Website instance (for translateRoute / basePath)
175
+ * @param {string} ctx.locale - Locale being rendered
176
+ * @param {boolean} ctx.isDefault - Is this the site's default locale?
177
+ * @param {string} ctx.routePrefix - Locale prefix for this pass ('' or '/fr')
178
+ * @returns {string} Absolute path for the stub's refresh + canonical link
179
+ */
180
+ export function localizeRedirectTarget(target, { website, locale, isDefault, routePrefix = '' }) {
181
+ const localized = isDefault ? target : routePrefix + website.translateRoute(target, locale)
182
+ const withSlash = localized.startsWith('/') ? localized : '/' + localized
183
+ return (website.basePath || '') + withSlash
184
+ }
185
+
151
186
  export function expandDynamicPages(pages, pageFetchedData, onProgress) {
152
187
  const expandedPages = []
153
188
 
@@ -704,8 +739,12 @@ export async function prerenderSite(siteDir, options = {}) {
704
739
  if (!page.hasContent()) {
705
740
  const target = page.getNavigableRoute()
706
741
  if (target && target !== page.route) {
707
- const base = website.basePath || ''
708
- const targetPath = base + (target.startsWith('/') ? target : '/' + target)
742
+ const targetPath = localizeRedirectTarget(target, {
743
+ website,
744
+ locale,
745
+ isDefault,
746
+ routePrefix
747
+ })
709
748
  onProgress(` Auto-redirect ${outputRoute} → ${targetPath}`)
710
749
  const redirectHtml = `<!DOCTYPE html><html><head><meta charset="utf-8"><meta http-equiv="refresh" content="0;url=${targetPath}"><link rel="canonical" href="${targetPath}"><title>Redirecting...</title></head><body><p>Redirecting to <a href="${targetPath}">${targetPath}</a></p></body></html>`
711
750
  const outputPath = getOutputPath(distDir, outputRoute)
@@ -255,28 +255,6 @@ export function readSiteConfig(siteRoot) {
255
255
  }
256
256
  }
257
257
 
258
- /**
259
- * Read and parse intelligence.yml configuration (AI knowledge page settings).
260
- *
261
- * Returns the non-secret fields. The `apiKey` field (if present) uses the
262
- * `env:VAR_NAME` syntax for CLI/local dev — the actual key is never stored
263
- * in artifacts or published content.
264
- *
265
- * @param {string} siteRoot - Path to site directory
266
- * @returns {Object|null} Parsed intelligence config, or null if no file exists
267
- */
268
- export function readIntelligenceConfig(siteRoot) {
269
- const configPath = resolve(siteRoot, 'intelligence.yml')
270
- if (!existsSync(configPath)) return null
271
-
272
- try {
273
- return yaml.load(readFileSync(configPath, 'utf8')) || {}
274
- } catch (err) {
275
- console.warn('[site-config] Failed to read intelligence.yml:', err.message)
276
- return null
277
- }
278
- }
279
-
280
258
  /**
281
259
  * Create a complete Vite configuration for a Uniweb site
282
260
  *
@@ -2345,10 +2345,6 @@ export async function collectSiteContent(sitePath, options = {}) {
2345
2345
  // Convert versionedScopes Map to plain object for JSON serialization
2346
2346
  const versionedScopesObj = Object.fromEntries(versionedScopes)
2347
2347
 
2348
- // Read intelligence.yml if it exists (AI knowledge page settings)
2349
- const intelligenceConfig = await readYamlFile(join(sitePath, 'intelligence.yml'))
2350
- const hasIntelligence = intelligenceConfig && Object.keys(intelligenceConfig).length > 0
2351
-
2352
2348
  // Compile per-page `slug:` maps into config.i18n.routeTranslations
2353
2349
  // (canonical route → per-locale display route). Producer-only: the runtime
2354
2350
  // (@uniweb/core website.js) and the sitemap generator already consume this
@@ -2410,7 +2406,12 @@ export async function collectSiteContent(sitePath, options = {}) {
2410
2406
  ? { languages: publishable }
2411
2407
  : {}),
2412
2408
  fetch: parseFetchConfig(siteConfig.fetch),
2413
- ...(hasIntelligence && { intelligence: intelligenceConfig }),
2409
+ // NOTE: `intelligence.yml` was read here and emitted as `config.intelligence`.
2410
+ // Removed 2026-08-12 — the assistant surface is `site.yml::assistant`, which
2411
+ // needs no line at all on this lane (`config` spreads all of site.yml) and one
2412
+ // line on the sync lane, so it reaches BOTH. The separate file could only ever
2413
+ // reach this one, which is what made an authored persona vanish on hosted
2414
+ // sites. See kb/framework/architecture/assistant-config.md.
2414
2415
  ...(routeTranslations
2415
2416
  ? { i18n: { ...(siteConfig.i18n || {}), routeTranslations } }
2416
2417
  : {}),