@uniweb/build 1.10.6 → 1.10.8
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 +7 -7
- package/src/foundation/config.js +11 -0
- package/src/i18n/index.js +34 -2
- package/src/uwx/backfill.js +9 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.8",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -57,14 +57,14 @@
|
|
|
57
57
|
"sharp": "^0.35.5",
|
|
58
58
|
"yaml": "^2.5.0",
|
|
59
59
|
"@uniweb/content-reader": "~1.5.7",
|
|
60
|
+
"@uniweb/content-writer": "~1.3.1",
|
|
60
61
|
"@uniweb/schemas": "~1.6.5",
|
|
61
62
|
"@uniweb/semantic-parser": "~1.13.0",
|
|
62
|
-
"@uniweb/
|
|
63
|
-
"@uniweb/
|
|
64
|
-
"@uniweb/content-writer": "~1.3.1"
|
|
63
|
+
"@uniweb/projections": "~1.2.3",
|
|
64
|
+
"@uniweb/theming": "~1.2.1"
|
|
65
65
|
},
|
|
66
66
|
"optionalDependencies": {
|
|
67
|
-
"@uniweb/runtime": "~1.2.
|
|
67
|
+
"@uniweb/runtime": "~1.2.5"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
70
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
|
@@ -73,8 +73,8 @@
|
|
|
73
73
|
"@tailwindcss/vite": "^4.0.0",
|
|
74
74
|
"@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
|
|
75
75
|
"vite-plugin-svgr": "^4.0.0",
|
|
76
|
-
"@uniweb/core": "~1.4.
|
|
77
|
-
"@uniweb/runtime": "~1.2.
|
|
76
|
+
"@uniweb/core": "~1.4.9",
|
|
77
|
+
"@uniweb/runtime": "~1.2.5"
|
|
78
78
|
},
|
|
79
79
|
"peerDependenciesMeta": {
|
|
80
80
|
"vite": {
|
package/src/foundation/config.js
CHANGED
|
@@ -55,6 +55,7 @@ export { nodeEnvDefine }
|
|
|
55
55
|
* @param {boolean} [options.tailwind] - Include Tailwind CSS v4 plugin (default: true)
|
|
56
56
|
* @param {boolean} [options.sourcemap] - Generate sourcemaps (default: true)
|
|
57
57
|
* @param {Object} [options.build] - Additional build options to merge
|
|
58
|
+
* @param {Object} [options.experimental] - Vite experimental options to merge
|
|
58
59
|
* @returns {Promise<Object>} Vite configuration
|
|
59
60
|
*/
|
|
60
61
|
export async function defineFoundationConfig(options = {}) {
|
|
@@ -76,6 +77,7 @@ export async function defineFoundationConfig(options = {}) {
|
|
|
76
77
|
tailwind = true,
|
|
77
78
|
sourcemap = true,
|
|
78
79
|
build: buildOverrides = {},
|
|
80
|
+
experimental: experimentalOverrides = {},
|
|
79
81
|
...restOptions
|
|
80
82
|
} = options
|
|
81
83
|
|
|
@@ -126,6 +128,15 @@ export async function defineFoundationConfig(options = {}) {
|
|
|
126
128
|
return {
|
|
127
129
|
plugins,
|
|
128
130
|
|
|
131
|
+
// ⭐ A FOUNDATION NAMES ITS OWN FILES FROM WHERE ITS CODE IS. A host serves it from an
|
|
132
|
+
// address of its own, never the page's, so a file its code references — a worker; library
|
|
133
|
+
// mode inlines every other asset — is found beside the code, not at the page's root.
|
|
134
|
+
// ⛔ Until 2026-10-27 that URL was `/assets/<file>`, which a published site does not have.
|
|
135
|
+
experimental: {
|
|
136
|
+
renderBuiltUrl: (filename, { hostType }) => (hostType === 'js' ? { relative: true } : undefined),
|
|
137
|
+
...experimentalOverrides
|
|
138
|
+
},
|
|
139
|
+
|
|
129
140
|
build: {
|
|
130
141
|
lib: {
|
|
131
142
|
entry: resolve(foundationRoot, entry),
|
package/src/i18n/index.js
CHANGED
|
@@ -454,6 +454,14 @@ export async function buildLocalizedContent(siteRoot, options = {}) {
|
|
|
454
454
|
await writeFile(siteContentPath, JSON.stringify(siteContent, null, 2))
|
|
455
455
|
}
|
|
456
456
|
|
|
457
|
+
// The paths the site's source holds a section at, drafts included — read once, and only
|
|
458
|
+
// when a translation translates nothing this build publishes (`warnAboutFreeformIssues`).
|
|
459
|
+
let sourcePaths
|
|
460
|
+
const inSource = async (path) => {
|
|
461
|
+
sourcePaths ??= freeformSourceIndex(await collectSourceQuietly(siteRoot)).validPaths
|
|
462
|
+
return sourcePaths.has(path)
|
|
463
|
+
}
|
|
464
|
+
|
|
457
465
|
for (const locale of locales) {
|
|
458
466
|
// ⛔ THE DEFAULT LOCALE IS THE ROOT TREE, NEVER A SUBDIRECTORY. Emitting
|
|
459
467
|
// `dist/<default>/` mints a byte-identical duplicate of every page at a second
|
|
@@ -496,7 +504,7 @@ export async function buildLocalizedContent(siteRoot, options = {}) {
|
|
|
496
504
|
})
|
|
497
505
|
|
|
498
506
|
// Check for stale/orphaned free-form translations and warn
|
|
499
|
-
await warnAboutFreeformIssues(locale, freeformDir, siteContent)
|
|
507
|
+
await warnAboutFreeformIssues(locale, freeformDir, siteContent, inSource)
|
|
500
508
|
} else {
|
|
501
509
|
// Use sync merge (original behavior)
|
|
502
510
|
translated = mergeTranslations(siteContent, translations, {
|
|
@@ -534,8 +542,10 @@ export async function buildLocalizedContent(siteRoot, options = {}) {
|
|
|
534
542
|
* @param {string} locale - Locale code
|
|
535
543
|
* @param {string} freeformDir - Path to locale's freeform directory
|
|
536
544
|
* @param {Object} siteContent - Site content for building source hashes
|
|
545
|
+
* @param {(path: string) => Promise<boolean>} inSource - whether the site's source holds a
|
|
546
|
+
* section at a translation's path
|
|
537
547
|
*/
|
|
538
|
-
async function warnAboutFreeformIssues(locale, freeformDir, siteContent) {
|
|
548
|
+
async function warnAboutFreeformIssues(locale, freeformDir, siteContent, inSource) {
|
|
539
549
|
try {
|
|
540
550
|
// Every path the renderer reads a section's translation from, for every section it
|
|
541
551
|
// translates, with the hash of each source — the index the CLI's free-form commands
|
|
@@ -550,9 +560,14 @@ async function warnAboutFreeformIssues(locale, freeformDir, siteContent) {
|
|
|
550
560
|
|
|
551
561
|
// Check for orphaned translations — only those this content can judge: never a
|
|
552
562
|
// record's (`records/…`), which is read from records the site content does not hold.
|
|
563
|
+
// ⭐ AND ONLY ONE THE SITE'S SOURCE DOES NOT HOLD EITHER. A published build carries no
|
|
564
|
+
// drafts, so a translation of a `hidden:` section or page translates nothing here while
|
|
565
|
+
// its section is still in the site — the source says, as `uniweb i18n status --freeform`
|
|
566
|
+
// judges. ⛔ Until 2026-10-27 each such translation was reported orphaned at every build.
|
|
553
567
|
const orphaned = await getOrphanedTranslations(freeformDir, validPaths)
|
|
554
568
|
for (const item of orphaned) {
|
|
555
569
|
if (!canJudge(item.path)) continue
|
|
570
|
+
if (await inSource(item.path)) continue
|
|
556
571
|
console.warn(`[i18n] Free-form translation orphaned: ${locale}/${item.path}`)
|
|
557
572
|
}
|
|
558
573
|
|
|
@@ -574,6 +589,23 @@ async function warnAboutFreeformIssues(locale, freeformDir, siteContent) {
|
|
|
574
589
|
}
|
|
575
590
|
}
|
|
576
591
|
|
|
592
|
+
/**
|
|
593
|
+
* The site's content as its source says it, drafts included — what `uniweb i18n` judges a
|
|
594
|
+
* free-form translation by. This build collected the site and reported what it found, so
|
|
595
|
+
* this second reading says nothing.
|
|
596
|
+
*/
|
|
597
|
+
async function collectSourceQuietly(siteRoot) {
|
|
598
|
+
const { collectSiteContent } = await import('../site/content-collector.js')
|
|
599
|
+
const { log, warn } = console
|
|
600
|
+
console.log = console.warn = () => {}
|
|
601
|
+
try {
|
|
602
|
+
return await collectSiteContent(siteRoot)
|
|
603
|
+
} finally {
|
|
604
|
+
console.log = log
|
|
605
|
+
console.warn = warn
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
577
609
|
/**
|
|
578
610
|
* Format translation status for console output
|
|
579
611
|
* @param {Object} status - Status from getTranslationStatus
|
package/src/uwx/backfill.js
CHANGED
|
@@ -225,11 +225,16 @@ export function backfillBibFile(filePath, uuidBySlug) {
|
|
|
225
225
|
|
|
226
226
|
// Unwrap a localized wire value `{ <locale>: v }` back to the authored bare value
|
|
227
227
|
// (the source locale, else the first present). Non-localized values pass through.
|
|
228
|
+
// ⛔ One with no locale at all, `{}`, is no value: undefined, and the field is not written.
|
|
229
|
+
// Until 2026-10-26 it passed through as the empty map, so a record's emptied caption was
|
|
230
|
+
// written `caption: {}` — an object where its schema says string, which `uniweb validate`
|
|
231
|
+
// refuses, and a publish stopped on fifteen of them.
|
|
228
232
|
export function unwrapLocalized(value, sourceLocale) {
|
|
229
233
|
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
230
234
|
if (Object.prototype.hasOwnProperty.call(value, sourceLocale)) return value[sourceLocale]
|
|
231
235
|
const keys = Object.keys(value)
|
|
232
236
|
if (keys.length) return value[keys[0]]
|
|
237
|
+
return undefined
|
|
233
238
|
}
|
|
234
239
|
return value
|
|
235
240
|
}
|
|
@@ -238,7 +243,8 @@ export function unwrapLocalized(value, sourceLocale) {
|
|
|
238
243
|
// field element-wise. An array unwraps each element; a bare localized map unwraps to
|
|
239
244
|
// one value. The container shape is preserved (mirrors how the producer built it).
|
|
240
245
|
export function unwrapLocalizedList(value, sourceLocale) {
|
|
241
|
-
|
|
246
|
+
// An element with no locale at all is no element (`unwrapLocalized`).
|
|
247
|
+
if (Array.isArray(value)) return value.map((v) => unwrapLocalized(v, sourceLocale)).filter((v) => v !== undefined)
|
|
242
248
|
return unwrapLocalized(value, sourceLocale)
|
|
243
249
|
}
|
|
244
250
|
|
|
@@ -347,7 +353,8 @@ function decodeRecord(fields, value, dec, sectionName) {
|
|
|
347
353
|
const isBody = Boolean(dec.bodyAt) && sectionName === dec.bodyAt.section && key === dec.bodyAt.key
|
|
348
354
|
const decoded = decodeLeaf(raw, field, dec, isBody)
|
|
349
355
|
if (isBody) dec.body = typeof decoded === 'string' ? decoded : String(decoded ?? '')
|
|
350
|
-
|
|
356
|
+
// A translatable value with no text in any language is no value: the field is not written.
|
|
357
|
+
else if (decoded !== undefined) out[key] = decoded
|
|
351
358
|
}
|
|
352
359
|
return out
|
|
353
360
|
}
|