uniweb 0.25.3 → 0.25.4
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.25.
|
|
3
|
+
"version": "0.25.4",
|
|
4
4
|
"description": "Create structured Vite + React sites with content/code separation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -42,12 +42,12 @@
|
|
|
42
42
|
"prompts": "^2.4.2",
|
|
43
43
|
"tar": "^7.0.0",
|
|
44
44
|
"@uniweb/core": "^0.10.1",
|
|
45
|
-
"@uniweb/
|
|
45
|
+
"@uniweb/semantic-parser": "^1.2.3",
|
|
46
46
|
"@uniweb/runtime": "^0.12.2",
|
|
47
|
-
"@uniweb/
|
|
47
|
+
"@uniweb/kit": "^0.12.3"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"@uniweb/build": "^0.24.
|
|
50
|
+
"@uniweb/build": "^0.24.4",
|
|
51
51
|
"@uniweb/content-reader": "^1.2.3",
|
|
52
52
|
"@uniweb/semantic-parser": "^1.2.3"
|
|
53
53
|
},
|
package/src/backend/site-sync.js
CHANGED
|
@@ -24,7 +24,8 @@ import {
|
|
|
24
24
|
diffSiteUnits,
|
|
25
25
|
describeSiteDiff,
|
|
26
26
|
computeUnitHashes,
|
|
27
|
-
collectUnitUuids
|
|
27
|
+
collectUnitUuids,
|
|
28
|
+
readAssetMap
|
|
28
29
|
} from '@uniweb/build/uwx'
|
|
29
30
|
|
|
30
31
|
// First entity `$`-document out of a `.uwx` we produced or the backend served.
|
|
@@ -181,11 +182,11 @@ function readSyncCacheFile(siteDir) {
|
|
|
181
182
|
return {} // missing / unreadable → treat everything as changed
|
|
182
183
|
}
|
|
183
184
|
}
|
|
184
|
-
// The cache holds
|
|
185
|
-
//
|
|
186
|
-
// so every writer must preserve the ones it
|
|
187
|
-
// than
|
|
188
|
-
// whichever map got clobbered.
|
|
185
|
+
// The cache holds several maps written on DIFFERENT events — content hashes and the
|
|
186
|
+
// injections that produced them on a successful push, base versions on push AND
|
|
187
|
+
// pull, unit hashes on push and pull — so every writer must preserve the ones it
|
|
188
|
+
// isn't touching. One merge point rather than a hand-rolled preserve per writer,
|
|
189
|
+
// because getting that wrong silently disarms whichever map got clobbered.
|
|
189
190
|
function updateSyncCache(siteDir, patch) {
|
|
190
191
|
const p = syncCachePath(siteDir)
|
|
191
192
|
mkdirSync(dirname(p), { recursive: true })
|
|
@@ -203,9 +204,10 @@ const readMap = (siteDir, key) => {
|
|
|
203
204
|
* Drop every cache map that describes a BACKEND site, when this clone is bound to
|
|
204
205
|
* none. Returns the names dropped (empty when there was nothing to do).
|
|
205
206
|
*
|
|
206
|
-
* `.uniweb/sync-cache.json` holds
|
|
207
|
-
* content hashes,
|
|
208
|
-
* `pages/about/about.md`) is the same
|
|
207
|
+
* `.uniweb/sync-cache.json` holds five maps keyed by *unit path* — item uuids,
|
|
208
|
+
* content hashes, the injections those hashes were taken over, entity base versions,
|
|
209
|
+
* unit bases — and a unit path (`site.yml`, `pages/about/about.md`) is the same
|
|
210
|
+
* string for every site. So the cache does not
|
|
209
211
|
* self-invalidate when `site.yml::$uuid` goes away: it keeps describing the site
|
|
210
212
|
* this folder used to be.
|
|
211
213
|
*
|
|
@@ -228,21 +230,29 @@ const readMap = (siteDir, key) => {
|
|
|
228
230
|
* the clone look bound before the check runs.
|
|
229
231
|
*/
|
|
230
232
|
/**
|
|
231
|
-
* Drop the
|
|
233
|
+
* Drop the five remote-derived maps unconditionally, stamping the site they now
|
|
232
234
|
* describe (or clearing the stamp when there is none). Shared by the pre-flight
|
|
233
235
|
* guard and the `item_uuid_conflict` recovery — the guard decides WHETHER, this
|
|
234
236
|
* decides WHAT, and they must not drift.
|
|
235
237
|
*/
|
|
236
238
|
export function clearRemoteSyncState(siteDir, siteUuid = null) {
|
|
237
239
|
const prior = readSyncCacheFile(siteDir)
|
|
238
|
-
const dropped = [
|
|
239
|
-
|
|
240
|
-
|
|
240
|
+
const dropped = [
|
|
241
|
+
'itemUuids',
|
|
242
|
+
'hashes',
|
|
243
|
+
'baseVersions',
|
|
244
|
+
'unitBases',
|
|
245
|
+
'applied'
|
|
246
|
+
].filter((k) => prior[k] && Object.keys(prior[k]).length)
|
|
241
247
|
updateSyncCache(siteDir, {
|
|
242
248
|
itemUuids: {},
|
|
243
249
|
hashes: {},
|
|
244
250
|
baseVersions: {},
|
|
245
251
|
unitBases: {},
|
|
252
|
+
// Remote-derived like the rest, and doubly so: it holds the OLD site's asset
|
|
253
|
+
// serve URLs. Surviving the drop, it would rewrite the new site's media to
|
|
254
|
+
// bytes owned by the site this folder used to be.
|
|
255
|
+
applied: {},
|
|
246
256
|
siteUuid: siteUuid || null
|
|
247
257
|
})
|
|
248
258
|
return dropped
|
|
@@ -258,7 +268,13 @@ export function clearRemoteSyncStateIfUnbound(siteDir) {
|
|
|
258
268
|
}
|
|
259
269
|
|
|
260
270
|
const prior = readSyncCacheFile(siteDir)
|
|
261
|
-
const REMOTE_MAPS = [
|
|
271
|
+
const REMOTE_MAPS = [
|
|
272
|
+
'itemUuids',
|
|
273
|
+
'hashes',
|
|
274
|
+
'baseVersions',
|
|
275
|
+
'unitBases',
|
|
276
|
+
'applied'
|
|
277
|
+
]
|
|
262
278
|
const populated = REMOTE_MAPS.filter(
|
|
263
279
|
(k) => prior[k] && Object.keys(prior[k]).length
|
|
264
280
|
)
|
|
@@ -302,8 +318,53 @@ export function clearRemoteSyncStateIfUnbound(siteDir) {
|
|
|
302
318
|
export function readSyncCache(siteDir) {
|
|
303
319
|
return readMap(siteDir, 'hashes')
|
|
304
320
|
}
|
|
305
|
-
|
|
306
|
-
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* The hash-affecting injections the emit that produced those hashes applied, in the
|
|
324
|
+
* exact shape `emitSyncPackages` takes back as opts.
|
|
325
|
+
*
|
|
326
|
+
* ⛔ **Only the ones nothing else records.** `assetIds` is deliberately NOT banked
|
|
327
|
+
* here even though the emit applies it: `assets.json` is COMMITTED project state
|
|
328
|
+
* holding exactly that map (local ref → `{id, ext}`), written by the same push, and
|
|
329
|
+
* a gitignored second copy would be a second thing to disagree — the reason that
|
|
330
|
+
* file itself refuses to hold a serve URL. It also has the worse lifetime of the
|
|
331
|
+
* two: `clearRemoteSyncState` wipes this cache, and the committed map correctly
|
|
332
|
+
* survives. ⇒ Bank what a reader cannot re-derive; re-derive the rest.
|
|
333
|
+
*
|
|
334
|
+
* ⛔ Read this whenever you re-emit to COMPARE against `hashes`. A push hashes the
|
|
335
|
+
* DELIVERED document — local `/images/x.png` rewritten to the backend serve URL it
|
|
336
|
+
* just uploaded to, `info.foundation` replaced by the version-pinned ref — and banks
|
|
337
|
+
* that. An offline re-emit produces the AUTHORED document, which is a different
|
|
338
|
+
* document, so it matches nothing and every entity reads as changed forever. That is
|
|
339
|
+
* not hypothetical: it is what `uniweb status` did on any site with one local image
|
|
340
|
+
* (backend-framework-787e, 2026-08-19) — `push` said "1 entity unchanged" and
|
|
341
|
+
* `status --json` said `changed: 1`, from the same cache, seconds apart.
|
|
342
|
+
*
|
|
343
|
+
* ⭐ What is left is what only a backend round-trip produces — an asset **serve URL**
|
|
344
|
+
* and a released foundation version — and `status` is offline by design (measured at
|
|
345
|
+
* zero HTTP requests, a property the cross-client flows rely on). ⚠️ Note the serve
|
|
346
|
+
* URL is REPLAYED, never composed: we re-use the string the host handed us, which is
|
|
347
|
+
* a different act from reconstructing one, and the distinction is the same one that
|
|
348
|
+
* keeps `assets.json` id-only. An asset genuinely new to the site has no recorded
|
|
349
|
+
* mapping and still reads as changed, which is correct.
|
|
350
|
+
*/
|
|
351
|
+
export function readAppliedInjections(siteDir) {
|
|
352
|
+
return readMap(siteDir, 'applied')
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Bank the content hashes and the injections that produced them. ⛔ ONE call, both
|
|
357
|
+
* maps: they describe the same document, so writing either alone leaves the cache
|
|
358
|
+
* self-inconsistent — and the failure is silent, since a hash never says which
|
|
359
|
+
* document it is of. `applied` is written even when empty, so it can never be a
|
|
360
|
+
* leftover from an earlier push describing hashes it no longer matches.
|
|
361
|
+
*
|
|
362
|
+
* `assetIds` is dropped rather than stored — see readAppliedInjections: it has a
|
|
363
|
+
* committed source of truth in `assets.json`, and the reader re-derives it there.
|
|
364
|
+
*/
|
|
365
|
+
export function writeSyncCache(siteDir, hashes, applied) {
|
|
366
|
+
const { assetIds: _inAssetsJson, ...bankable } = applied || {}
|
|
367
|
+
updateSyncCache(siteDir, { hashes, applied: bankable })
|
|
307
368
|
}
|
|
308
369
|
|
|
309
370
|
/**
|
|
@@ -835,10 +896,24 @@ export function writeUnitBases(siteDir, patch) {
|
|
|
835
896
|
*/
|
|
836
897
|
export async function probeUnpushed(siteDir, { sendAll = false } = {}) {
|
|
837
898
|
const priorHashes = readSyncCache(siteDir)
|
|
899
|
+
// Re-emit the document the last push HASHED, not the one the author wrote — see
|
|
900
|
+
// readAppliedInjections. Two sources, on purpose:
|
|
901
|
+
// · BANKED — the serve URLs and pinned refs only a round-trip produces. Empty
|
|
902
|
+
// for a cache written before this was banked (and for a never-pushed site),
|
|
903
|
+
// which is the pre-fix behaviour and self-heals on the next push. It can never
|
|
904
|
+
// point at the wrong document: it is written with the hashes it belongs to.
|
|
905
|
+
// · RE-DERIVED — asset identity, from the COMMITTED `assets.json` the same push
|
|
906
|
+
// wrote. Reading the live file rather than a snapshot is what makes a moved
|
|
907
|
+
// map (a teammate's push, a pull) read as changed instead of matching a copy
|
|
908
|
+
// of itself.
|
|
909
|
+
const applied = readAppliedInjections(siteDir)
|
|
910
|
+
const assetIds = readAssetMap(siteDir)
|
|
838
911
|
const pkg = await emitSyncPackages(siteDir, {
|
|
839
912
|
resolveModel: makeModelResolver({ client: null, offline: true }),
|
|
840
913
|
priorHashes,
|
|
841
|
-
sendAll
|
|
914
|
+
sendAll,
|
|
915
|
+
...applied,
|
|
916
|
+
...(Object.keys(assetIds).length ? { assetIds } : {})
|
|
842
917
|
})
|
|
843
918
|
const changed =
|
|
844
919
|
(pkg.siteContent?.entityCount || 0) + (pkg.collections?.entityCount || 0)
|
|
@@ -867,7 +942,7 @@ export async function pushSyncPackages({
|
|
|
867
942
|
asOrg,
|
|
868
943
|
report
|
|
869
944
|
}) {
|
|
870
|
-
const { siteContent, collections, siteContentUuid, hashes } = pkg
|
|
945
|
+
const { siteContent, collections, siteContentUuid, hashes, applied } = pkg
|
|
871
946
|
const { info, note, error } = report
|
|
872
947
|
const dim = report.dim || ((s) => s)
|
|
873
948
|
|
|
@@ -1200,7 +1275,7 @@ export async function pushSyncPackages({
|
|
|
1200
1275
|
// then bank the post-write tokens so the NEXT push carries a current base.
|
|
1201
1276
|
// Entities absent from finalized[] (skipped, or not editable) keep their cached
|
|
1202
1277
|
// value — absence is not invalidation.
|
|
1203
|
-
writeSyncCache(siteDir, hashes)
|
|
1278
|
+
writeSyncCache(siteDir, hashes, applied)
|
|
1204
1279
|
mergeHarvested()
|
|
1205
1280
|
// Re-base the page attribution: our emitted document and the backend's post-write
|
|
1206
1281
|
// copy of it are the two sides' new agreed state. Only when the site-content lane
|
package/src/framework-index.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-08-
|
|
3
|
+
"generatedAt": "2026-08-19T03:59:24.190Z",
|
|
4
4
|
"packages": {
|
|
5
5
|
"@uniweb/build": {
|
|
6
|
-
"version": "0.24.
|
|
6
|
+
"version": "0.24.4",
|
|
7
7
|
"path": "framework/build",
|
|
8
8
|
"deps": [
|
|
9
9
|
"@uniweb/content-reader",
|
|
@@ -39,7 +39,15 @@
|
|
|
39
39
|
* an upload landed.
|
|
40
40
|
*
|
|
41
41
|
* Contract ratified 2026-08-18; see `kb/framework/build/data-ball-retirement.md`.
|
|
42
|
-
*
|
|
42
|
+
* **Wired into `publish.js` and shipped in `uniweb` 0.25.3.** Both arms of the
|
|
43
|
+
* endpoint exist (presigned landed 2026-08-18, per Diego).
|
|
44
|
+
*
|
|
45
|
+
* ⚠️ **The presigned arm has never been exercised against a real backend from
|
|
46
|
+
* here.** The branch is one line and unit-tested both ways, but a stub is not a
|
|
47
|
+
* presigning deployment — three claims diverged that way in a single day while
|
|
48
|
+
* this lane was being built. A local `uniwebd` answers `direct`, so proving it
|
|
49
|
+
* needs a presigning deployment and one real publish. **Believed correct, not
|
|
50
|
+
* demonstrated.**
|
|
43
51
|
*/
|
|
44
52
|
|
|
45
53
|
import { createHash } from 'node:crypto'
|