uniweb 0.12.55 → 0.12.57

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": "0.12.55",
3
+ "version": "0.12.57",
4
4
  "description": "Create structured Vite + React sites with content/code separation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,14 +41,14 @@
41
41
  "js-yaml": "^4.1.0",
42
42
  "prompts": "^2.4.2",
43
43
  "tar": "^7.0.0",
44
- "@uniweb/core": "0.7.22",
45
- "@uniweb/kit": "0.9.29",
46
- "@uniweb/runtime": "0.8.28"
44
+ "@uniweb/core": "0.7.23",
45
+ "@uniweb/kit": "0.9.30",
46
+ "@uniweb/runtime": "0.8.29"
47
47
  },
48
48
  "peerDependencies": {
49
- "@uniweb/content-reader": "1.1.13",
50
- "@uniweb/semantic-parser": "1.1.18",
51
- "@uniweb/build": "0.14.35"
49
+ "@uniweb/build": "0.14.37",
50
+ "@uniweb/content-reader": "1.1.14",
51
+ "@uniweb/semantic-parser": "1.1.18"
52
52
  },
53
53
  "peerDependenciesMeta": {
54
54
  "@uniweb/build": {
@@ -845,6 +845,8 @@ seo:
845
845
 
846
846
  **Localized URLs:** On a multilingual site (`languages:` in site.yml), a page's `slug: { <lang>: <segment> }` gives it a native URL segment per language (`/about` → `/fr/a-propos`); the folder name stays the canonical route. Nested folders compose automatically, and localized URLs flow through navigation, the language switcher, and the sitemap.
847
847
 
848
+ **Draft languages:** `publishLanguages: [en, fr]` in site.yml lists which declared languages a published build ships — unlisted ones stay dev-previewable drafts. Absent field = publish all declared; the default language must be listed.
849
+
848
850
  **Content-less containers:** Folders with `page.yml` but no markdown are structural groups. They appear in `getPageHierarchy()` with `hasContent: false` and their own title/label. When visited directly, the runtime auto-redirects to the first descendant with content. This supports hierarchical navigation (courses → modules → lessons) at any depth.
849
851
 
850
852
  ### Lists as Navigation Menus
@@ -1181,7 +1183,7 @@ function Article({ content, block }) {
1181
1183
 
1182
1184
  Components can ignore keys in `content.data` they don't need — the same way unused `params` are ignored.
1183
1185
 
1184
- **Declaring data schemas.** `meta.js` declares the schema for each `content.data` key with a single `data:` field — there is no separate `schemas:` key. Each entry's value is one of: a **named ref** (`'@/article'` resolves to this foundation's `foundation/schemas/article.{js,json,yml}`; `'@std/person'` is a shared standard), an **inline field map** (`{ field: { type, default } }`), or an **inline rich-form** (`{ fields: [...] }`, an editor form). Refs use Uniweb namespacing, resolved on disk at build time, never fetched: `@/name` (this foundation's own `foundation/schemas/`), `@std/name` (the shared standard schemas, shipped in the `@uniweb/schemas` package), and `@org/name` (an org's own schemas, from its `@org/schemas` package — define a schema once and share it across foundations). The schema is a hint: it supplies field defaults and drives the editor, not delivery (which is default-on). For an explicit opt-out (rare), set `data: false`.
1186
+ **Declaring data schemas.** `meta.js` declares the schema for each `content.data` key with a single `data:` field — there is no separate `schemas:` key. Each entry's value is one of: a **named ref** (`'@/article'` resolves to this foundation's `foundation/schemas/article.{js,json,yml,yaml}`; `'@std/person'` is a shared standard), an **inline field map** (`{ field: { type, default } }`), or an **inline rich-form** (`{ fields: [...] }`, an editor form). Refs use Uniweb namespacing, resolved on disk at build time, never fetched: `@/name` (this foundation's own `foundation/schemas/`), `@std/name` (the shared standard schemas, shipped in the `@uniweb/schemas` package), and `@org/name` (an org's own schemas, from its `@org/schemas` package — define a schema once and share it across foundations). The schema is a hint: it supplies field defaults and drives the editor, not delivery (which is default-on). For an explicit opt-out (rare), set `data: false`.
1185
1187
 
1186
1188
  ```js
1187
1189
  // meta.js
@@ -1201,7 +1203,7 @@ export default {
1201
1203
  export default { '@acme': '../shared/acme-schemas', '@brand': process.env.BRAND_SCHEMAS }
1202
1204
  ```
1203
1205
 
1204
- Plain JS, so paths can be relative, absolute, or read from an env var. A routed scope wins over the package convention; `@/` and `@uniweb` are never routable; an empty value (e.g. an unset env var) falls back to the package convention.
1206
+ Plain JS, so paths can be relative, absolute, or read from an env var. A routed scope wins over the package convention; `@/` and `@uniweb` are never routable; an empty value (e.g. an unset env var) falls back to the package convention. A routed scope has no package fallback for a *missing* schema (it errors rather than silently load a different definition). To override a single schema — keeping the rest of the scope routed to the shared folder — add a per-schema key pointing at a file: `'@acme/person': './schemas/acme-person.yml'` (most-specific wins: file › directory › package).
1205
1207
 
1206
1208
  **Validate your data.** `uniweb validate` checks your file-based data against these declared schemas — missing required fields, type/enum/format mismatches, nested fields — before you ship. Warns by default, `--strict` for a non-zero CI exit. It's distinct from `uniweb doctor` (which checks project structure): `validate` checks your *data* against the schemas you *declared*. Remote (`url:`), `ref`/`options`, and rich `sections`-form inputs are reported deferred — validate those against live data.
1207
1209
 
@@ -60,6 +60,7 @@ import {
60
60
  classifyPackage,
61
61
  isExtensionPackage,
62
62
  } from '@uniweb/build'
63
+ import { resolveDefaultLocale, normalizeLanguageList } from '@uniweb/core/locale-config'
63
64
  import { readSiteConfig } from '@uniweb/build/site'
64
65
  import { readWorkspaceConfig, resolveGlob } from '../utils/config.js'
65
66
 
@@ -373,12 +374,26 @@ async function loadI18nConfig(projectDir, siteConfig = null) {
373
374
 
374
375
  // Resolve locales (undefined/'*' → all available, array → specific)
375
376
  const { resolveLocales } = await import('@uniweb/build/i18n')
376
- const locales = await resolveLocales(config.languages, localesPath)
377
+ let locales = await resolveLocales(config.languages, localesPath)
378
+
379
+ // Publish filter: build outputs are visitor artifacts. With an explicit
380
+ // `publishLanguages:`, only listed locales get dist/{locale}/ content,
381
+ // per-locale HTML, hreflang, and translated collections. Draft locales
382
+ // stay previewable in dev, which doesn't run this path. Intersecting the
383
+ // DISK-resolved list here also covers wildcard (`languages: '*'`) sites.
384
+ if (config.publishLanguages != null) {
385
+ const publishSet = new Set(normalizeLanguageList(config.publishLanguages))
386
+ const dropped = locales.filter((l) => !publishSet.has(l))
387
+ locales = locales.filter((l) => publishSet.has(l))
388
+ if (dropped.length > 0) {
389
+ info(`Skipping non-publishable locale(s): ${dropped.join(', ')} (not in publishLanguages)`)
390
+ }
391
+ }
377
392
 
378
393
  if (locales.length === 0) return null
379
394
 
380
395
  return {
381
- defaultLocale: config.defaultLanguage || 'en',
396
+ defaultLocale: resolveDefaultLocale(config),
382
397
  locales,
383
398
  localesDir,
384
399
  }
@@ -19,6 +19,7 @@ import { resolve, join, dirname, basename, relative } from 'path'
19
19
  import { existsSync } from 'fs'
20
20
  import { readFile, writeFile, mkdir, readdir, unlink, rename } from 'fs/promises'
21
21
  import yaml from 'js-yaml'
22
+ import { resolveDefaultLocale } from '@uniweb/core/locale-config'
22
23
  import {
23
24
  isWorkspaceRoot,
24
25
  findSites,
@@ -216,7 +217,7 @@ async function loadSiteConfig(siteRoot) {
216
217
  const locales = await resolveLocales(config.languages, localesPath)
217
218
 
218
219
  return {
219
- defaultLocale: config.defaultLanguage || 'en',
220
+ defaultLocale: resolveDefaultLocale(config),
220
221
  locales,
221
222
  localesDir,
222
223
  ...config.i18n,
@@ -48,6 +48,7 @@ import {
48
48
  rewriteBallAssets,
49
49
  } from '@uniweb/build/site'
50
50
  import { emitSyncPackages } from '@uniweb/build/uwx'
51
+ import { resolveDefaultLocale } from '@uniweb/core/locale-config'
51
52
 
52
53
  import { BackendClient } from '../backend/client.js'
53
54
  import { resolveSiteDir, resolveSiteBackend } from './deploy.js'
@@ -117,7 +118,9 @@ function languagesFromContent(siteContent) {
117
118
 
118
119
  // Languages from site.yml — used only for the dry-run summary (no build yet).
119
120
  function languagesFromSiteYml(siteYml) {
120
- const def = siteYml.defaultLanguage || siteYml.lang || 'en'
121
+ // Legacy `lang:` still honored between defaultLanguage and the shared
122
+ // `defaultLanguage || languages[0] || 'en'` rule.
123
+ const def = siteYml.defaultLanguage || siteYml.lang || resolveDefaultLocale(siteYml)
121
124
  const locales = siteYml.i18n?.locales || siteYml.languages
122
125
  if (!Array.isArray(locales) || locales.length === 0) return null
123
126
  const norm = locales.map((l) => (typeof l === 'string' ? l : l?.value || l?.code)).filter(Boolean)
@@ -454,6 +454,17 @@ async function runRegister(args = []) {
454
454
  return { exitCode: 1 }
455
455
  }
456
456
  }
457
+ // Registering ≠ delivering the file. A registered schema is a content type
458
+ // authors use in the app; a FOUNDATION build still resolves '@scope/name' from
459
+ // disk (a package or a routed folder). This is the moment that belief forms —
460
+ // you just registered it — so correct it here, not in a doc nobody's reading.
461
+ if (!jsonMode && defined.length > 0 && scope) {
462
+ log('')
463
+ info('Registered schemas are content types authors can use in the Uniweb app.')
464
+ log(` ${colors.dim}Delivery is separate: a foundation that binds ${scope}/<name> resolves it from${colors.reset}`)
465
+ log(` ${colors.dim}disk — an ${scope}/schemas package, or a folder routed in schemas.config.js.${colors.reset}`)
466
+ }
467
+
457
468
  if (jsonMode) {
458
469
  // Join my authoritative submitted names with the backend's minted ids. Each
459
470
  // response entry is `{ registered: { name, version, payload_model_uuid, … },
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-07-22T10:07:24.554Z",
3
+ "generatedAt": "2026-07-23T17:37:49.259Z",
4
4
  "packages": {
5
5
  "@uniweb/build": {
6
- "version": "0.14.35",
6
+ "version": "0.14.37",
7
7
  "path": "framework/build",
8
8
  "deps": [
9
9
  "@uniweb/content-reader",
@@ -15,17 +15,17 @@
15
15
  ]
16
16
  },
17
17
  "@uniweb/content-reader": {
18
- "version": "1.1.13",
18
+ "version": "1.1.14",
19
19
  "path": "framework/content-reader",
20
20
  "deps": []
21
21
  },
22
22
  "@uniweb/content-writer": {
23
- "version": "0.2.6",
23
+ "version": "0.2.7",
24
24
  "path": "framework/content-writer",
25
25
  "deps": []
26
26
  },
27
27
  "@uniweb/core": {
28
- "version": "0.7.22",
28
+ "version": "0.7.23",
29
29
  "path": "framework/core",
30
30
  "deps": [
31
31
  "@uniweb/semantic-parser",
@@ -43,7 +43,7 @@
43
43
  "deps": []
44
44
  },
45
45
  "@uniweb/kit": {
46
- "version": "0.9.29",
46
+ "version": "0.9.30",
47
47
  "path": "framework/kit",
48
48
  "deps": [
49
49
  "@uniweb/core",
@@ -61,7 +61,7 @@
61
61
  "deps": []
62
62
  },
63
63
  "@uniweb/runtime": {
64
- "version": "0.8.28",
64
+ "version": "0.8.29",
65
65
  "path": "framework/runtime",
66
66
  "deps": [
67
67
  "@uniweb/core",
@@ -99,7 +99,7 @@
99
99
  "deps": []
100
100
  },
101
101
  "@uniweb/unipress": {
102
- "version": "0.4.43",
102
+ "version": "0.4.44",
103
103
  "path": "framework/unipress",
104
104
  "deps": [
105
105
  "@uniweb/build",