@uniweb/build 0.24.3 → 0.24.5
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 +6 -6
- package/src/uwx/site-project.js +20 -3
- package/src/uwx/sync-package.js +41 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.5",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -60,16 +60,16 @@
|
|
|
60
60
|
"sharp": "^0.35.3",
|
|
61
61
|
"yaml": "^2.5.0",
|
|
62
62
|
"@uniweb/schemas": "^0.2.10",
|
|
63
|
-
"@uniweb/semantic-parser": "^1.2.3",
|
|
64
|
-
"@uniweb/theming": "^0.1.15",
|
|
65
63
|
"@uniweb/content-writer": "^0.3.3",
|
|
66
|
-
"@uniweb/projections": "^0.3.3"
|
|
64
|
+
"@uniweb/projections": "^0.3.3",
|
|
65
|
+
"@uniweb/semantic-parser": "^1.2.3",
|
|
66
|
+
"@uniweb/theming": "^0.1.15"
|
|
67
67
|
},
|
|
68
68
|
"optionalDependencies": {
|
|
69
|
+
"@uniweb/content-reader": "^1.2.3",
|
|
69
70
|
"@uniweb/schemas": "^0.2.10",
|
|
70
|
-
"@uniweb/semantic-parser": "^1.2.3",
|
|
71
71
|
"@uniweb/runtime": "^0.12.2",
|
|
72
|
-
"@uniweb/
|
|
72
|
+
"@uniweb/semantic-parser": "^1.2.3"
|
|
73
73
|
},
|
|
74
74
|
"peerDependencies": {
|
|
75
75
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
package/src/uwx/site-project.js
CHANGED
|
@@ -90,6 +90,22 @@ function placeByUuid(ctx, uuid, targetAbs) {
|
|
|
90
90
|
// most are identity; only `default_language` differs (the file key is camelCase
|
|
91
91
|
// `defaultLanguage`).
|
|
92
92
|
const INFO_TO_SITE_YML = {
|
|
93
|
+
// ⛔ `foundation` is the one entry a caller may SUPPRESS (`keepAuthoredFoundation`),
|
|
94
|
+
// and it is the same principle as restoring authored asset paths below: a round
|
|
95
|
+
// trip must not mangle what the author wrote.
|
|
96
|
+
//
|
|
97
|
+
// The stored value is not always what they wrote. `publish` stamps the RELEASED,
|
|
98
|
+
// version-pinned ref over it, because delivery is version-pinned end to end. Project
|
|
99
|
+
// that straight back into a workspace project and `foundation: src` becomes
|
|
100
|
+
// `@org/x@1.2.3` — which `detectFoundationType` REFUSES for a build (a build is
|
|
101
|
+
// offline and does not guess where a foundation is served), so the project can no
|
|
102
|
+
// longer `build`, `dev` or `export`. Measured 2026-08-19.
|
|
103
|
+
//
|
|
104
|
+
// ⚖️ It is NOT wrong everywhere, which is why this is a caller's decision and not a
|
|
105
|
+
// deletion: a project from `uniweb clone` has no local foundation on disk, and there
|
|
106
|
+
// the pinned ref is exactly what site.yml should say. Only the caller knows which
|
|
107
|
+
// shape it is writing into — and it needs the build's own resolver to know, which is
|
|
108
|
+
// why that check is not made here (it would drag the vite chain into `uwx/`).
|
|
93
109
|
foundation: 'foundation',
|
|
94
110
|
languages: 'languages',
|
|
95
111
|
default_language: 'defaultLanguage',
|
|
@@ -152,7 +168,7 @@ const INFO_TO_SITE_YML = {
|
|
|
152
168
|
* @returns {{ siteConfig: string, theme?: string, headHtml?: string }} per-file
|
|
153
169
|
* write status ('updated' | 'unchanged')
|
|
154
170
|
*/
|
|
155
|
-
export function siteInfoToConfig({ document, siteRoot, sourceLocale = LOCALIZED_FIELD_ASSUMPTION.defaultSourceLocale, collector }) {
|
|
171
|
+
export function siteInfoToConfig({ document, siteRoot, sourceLocale = LOCALIZED_FIELD_ASSUMPTION.defaultSourceLocale, collector, keepAuthoredFoundation = false }) {
|
|
156
172
|
const info = document?.info || {}
|
|
157
173
|
|
|
158
174
|
const siteChanges = {}
|
|
@@ -173,6 +189,7 @@ export function siteInfoToConfig({ document, siteRoot, sourceLocale = LOCALIZED_
|
|
|
173
189
|
|
|
174
190
|
// Verbatim fields (includes `seo` — the site-level social/SEO block).
|
|
175
191
|
for (const [infoKey, ymlKey] of Object.entries(INFO_TO_SITE_YML)) {
|
|
192
|
+
if (infoKey === 'foundation' && keepAuthoredFoundation) continue
|
|
176
193
|
if (info[infoKey] !== undefined) siteChanges[ymlKey] = info[infoKey]
|
|
177
194
|
}
|
|
178
195
|
|
|
@@ -626,7 +643,7 @@ function projectLayout(layoutSections, layoutBaseDir, report, prune, ctx) {
|
|
|
626
643
|
* empty set.
|
|
627
644
|
* @returns {{ config: object, collections: object, locales: object, pages: string[], sections: string[], layout: string[], deleted: string[], renamed: object[] }}
|
|
628
645
|
*/
|
|
629
|
-
export function siteContentDocumentToProject({ document, siteRoot, sourceLocale = LOCALIZED_FIELD_ASSUMPTION.defaultSourceLocale, prune = false }) {
|
|
646
|
+
export function siteContentDocumentToProject({ document, siteRoot, sourceLocale = LOCALIZED_FIELD_ASSUMPTION.defaultSourceLocale, prune = false, keepAuthoredFoundation = false }) {
|
|
630
647
|
const report = { config: null, collections: null, locales: null, assets: null, pages: [], sections: [], layout: [], deleted: [], renamed: [] }
|
|
631
648
|
|
|
632
649
|
// Collects target-locale translations of localized scalars as they're projected;
|
|
@@ -640,7 +657,7 @@ export function siteContentDocumentToProject({ document, siteRoot, sourceLocale
|
|
|
640
657
|
// round trip that changed nothing.
|
|
641
658
|
report.assets = restoreAssetRefs(document, readAssetMap(siteRoot))
|
|
642
659
|
|
|
643
|
-
report.config = siteInfoToConfig({ document, siteRoot, sourceLocale, collector })
|
|
660
|
+
report.config = siteInfoToConfig({ document, siteRoot, sourceLocale, collector, keepAuthoredFoundation })
|
|
644
661
|
report.collections = declarationsToCollectionsYml({ document, siteRoot })
|
|
645
662
|
|
|
646
663
|
// The uuid identity index (gitignored `.uniweb/`): read the prior map to anchor
|
package/src/uwx/sync-package.js
CHANGED
|
@@ -237,11 +237,16 @@ function rewriteEntityAssets(node, map, ids) {
|
|
|
237
237
|
* siteContent: { buffer, entityCount, index, models }|null,
|
|
238
238
|
* collections: { buffer, entityCount, index, models }|null,
|
|
239
239
|
* hashes: Object<string,string>, warnings: string[], skipped: number,
|
|
240
|
-
* schemaless: Array<{name: string, model: string}>, localAssets: string[]
|
|
240
|
+
* schemaless: Array<{name: string, model: string}>, localAssets: string[],
|
|
241
|
+
* applied: object }>}
|
|
241
242
|
* `schemaless` lists collections that resolved no data schema (soft-skipped from
|
|
242
243
|
* the sync) — the composite deploy delivers these statically via the data ball.
|
|
243
244
|
* `localAssets` lists the site-root local media refs (`/images/x.png`) the deploy
|
|
244
245
|
* must upload + rewrite to serve URLs; co-located refs are warned and skipped.
|
|
246
|
+
* `applied` echoes the hash-affecting injections this emit actually used
|
|
247
|
+
* (`assetRewrite` / `assetIds` / `injectInfo` / `injectExtensions`), ready to be
|
|
248
|
+
* passed straight back as opts. A caller that banks the `hashes` must bank this
|
|
249
|
+
* beside them, or an offline re-emit cannot reproduce the document they describe.
|
|
245
250
|
* Each lane is null when it has nothing to push. The collections `index` keeps a
|
|
246
251
|
* leading `{ kind: 'folder' }` placeholder (submission position 0 → the folder
|
|
247
252
|
* entity) so record back-fill stays positionally aligned; the folder itself has no
|
|
@@ -275,8 +280,10 @@ export async function emitSyncPackages(siteRoot, opts = {}) {
|
|
|
275
280
|
// stamped here — NOT authored in site.yml, so they ride the wire but never project
|
|
276
281
|
// back on pull (the `info.assets` precedent). They are part of the hashed content,
|
|
277
282
|
// so a changed bundle URL correctly re-fires the site-content lane.
|
|
278
|
-
|
|
279
|
-
|
|
283
|
+
const injectInfo =
|
|
284
|
+
opts.injectInfo && typeof opts.injectInfo === 'object' ? opts.injectInfo : null
|
|
285
|
+
if (siteDoc && injectInfo) {
|
|
286
|
+
siteDoc.info = { ...siteDoc.info, ...injectInfo }
|
|
280
287
|
}
|
|
281
288
|
// Same idea one level out: `extensions` is a sibling of `info`, not a field in
|
|
282
289
|
// it, so a pinned extension ref cannot ride `injectInfo`. `publish` releases a
|
|
@@ -285,9 +292,13 @@ export async function emitSyncPackages(siteRoot, opts = {}) {
|
|
|
285
292
|
// for the same reason: delivery is version-pinned, so an unpinned local name on
|
|
286
293
|
// the wire points at code the host cannot serve. Keyed by `$id` (the authored
|
|
287
294
|
// declaration), so entries the publish didn't touch are left verbatim.
|
|
288
|
-
|
|
295
|
+
const injectExtensions =
|
|
296
|
+
opts.injectExtensions && typeof opts.injectExtensions === 'object'
|
|
297
|
+
? opts.injectExtensions
|
|
298
|
+
: null
|
|
299
|
+
if (siteDoc && injectExtensions && Array.isArray(siteDoc.extensions)) {
|
|
289
300
|
siteDoc.extensions = siteDoc.extensions.map((e) => {
|
|
290
|
-
const pinned =
|
|
301
|
+
const pinned = injectExtensions[e?.$id]
|
|
291
302
|
if (!pinned) return e
|
|
292
303
|
const { url: _dropped, ...rest } = e
|
|
293
304
|
return { ...rest, ref: pinned }
|
|
@@ -402,9 +413,33 @@ export async function emitSyncPackages(siteRoot, opts = {}) {
|
|
|
402
413
|
// the content lane by its presence/absence.
|
|
403
414
|
const siteContentUuid = siteDoc?.$uuid
|
|
404
415
|
|
|
416
|
+
// ⛔ EVERY INJECTION ABOVE IS PART OF THE HASHED DOCUMENT — so a reader that
|
|
417
|
+
// cannot reproduce them cannot compare against the hashes a push banked.
|
|
418
|
+
//
|
|
419
|
+
// `uniweb status` is exactly such a reader: it re-emits OFFLINE, and both an asset
|
|
420
|
+
// serve URL and a pinned foundation ref are things only a backend round-trip can
|
|
421
|
+
// produce. Left implicit, it reports every entity as changed FOREVER — measured
|
|
422
|
+
// 2026-08-19 on a site with one `/images/*.svg` reference, where push banked the
|
|
423
|
+
// rewritten hash, status re-hashed the authored one, and `changed` never dropped
|
|
424
|
+
// below 1 no matter how many times you pushed. `push.js` had already written the
|
|
425
|
+
// rule down for its own two emits ("or every entity reads as changed forever") and
|
|
426
|
+
// the third reader of the same cache never got it.
|
|
427
|
+
//
|
|
428
|
+
// ⭐ So the EMITTER reports what it applied, the pusher banks it beside the hashes,
|
|
429
|
+
// and the offline reader replays it. Reporting it here rather than having each
|
|
430
|
+
// caller remember to pass its own injections along is the whole point: what gets
|
|
431
|
+
// banked is then, by construction, what was hashed. A future injection is covered
|
|
432
|
+
// by adding one line here — not by finding every reader.
|
|
433
|
+
const applied = {
|
|
434
|
+
...(assetRewrite ? { assetRewrite } : {}),
|
|
435
|
+
...(assetIds ? { assetIds } : {}),
|
|
436
|
+
...(injectInfo ? { injectInfo } : {}),
|
|
437
|
+
...(injectExtensions ? { injectExtensions } : {})
|
|
438
|
+
}
|
|
439
|
+
|
|
405
440
|
return {
|
|
406
441
|
siteContent, collections, siteContentUuid, hashes, warnings, skipped,
|
|
407
|
-
schemaless: col.schemaless, localAssets,
|
|
442
|
+
schemaless: col.schemaless, localAssets, applied,
|
|
408
443
|
// { stamped, unknown } when identity was applied; null when the caller passed
|
|
409
444
|
// no map. `unknown > 0` with `stamped === 0` on a site that has been pushed
|
|
410
445
|
// before is the index-loss signature the backend refuses — the caller reports it.
|