@uniweb/build 0.27.2 → 0.28.0
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/uwx/folder.js +77 -1
- package/src/uwx/index.js +4 -1
- package/src/uwx/sync-package.js +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -59,17 +59,17 @@
|
|
|
59
59
|
"js-yaml": "^4.1.0",
|
|
60
60
|
"sharp": "^0.35.3",
|
|
61
61
|
"yaml": "^2.5.0",
|
|
62
|
-
"@uniweb/projections": "^0.4.
|
|
63
|
-
"@uniweb/
|
|
62
|
+
"@uniweb/projections": "^0.4.1",
|
|
63
|
+
"@uniweb/theming": "^0.1.15",
|
|
64
64
|
"@uniweb/content-writer": "^0.3.4",
|
|
65
|
-
"@uniweb/
|
|
66
|
-
"@uniweb/
|
|
65
|
+
"@uniweb/semantic-parser": "^1.3.1",
|
|
66
|
+
"@uniweb/schemas": "^0.2.11"
|
|
67
67
|
},
|
|
68
68
|
"optionalDependencies": {
|
|
69
69
|
"@uniweb/semantic-parser": "^1.3.1",
|
|
70
|
-
"@uniweb/
|
|
70
|
+
"@uniweb/content-reader": "^1.2.4",
|
|
71
71
|
"@uniweb/runtime": "^0.13.0",
|
|
72
|
-
"@uniweb/
|
|
72
|
+
"@uniweb/schemas": "^0.2.11"
|
|
73
73
|
},
|
|
74
74
|
"peerDependencies": {
|
|
75
75
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
package/src/uwx/folder.js
CHANGED
|
@@ -96,6 +96,76 @@ function virtualContents(folders, groups) {
|
|
|
96
96
|
return folders.map(buildNode).filter(Boolean)
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Walk a folder document's `contents` tree, visiting every item with the
|
|
102
|
+
* slash-joined `path_segment` chain that addresses it.
|
|
103
|
+
*
|
|
104
|
+
* ⛔ IT MUST RECURSE INTO `$children`. `contents` is SELF-NESTING: a walk of the
|
|
105
|
+
* top level sees the branches and misses every record under them — which is 6 of
|
|
106
|
+
* the 7 entries in a two-collection site. *(Named by the backend lane, 2026-08-27,
|
|
107
|
+
* before it could be got wrong.)*
|
|
108
|
+
*/
|
|
109
|
+
function walkFolderItems(contents, cb, prefix = '') {
|
|
110
|
+
for (const item of contents || []) {
|
|
111
|
+
if (!item || typeof item !== 'object') continue
|
|
112
|
+
const seg = typeof item.path_segment === 'string' ? item.path_segment : null
|
|
113
|
+
const path = seg ? (prefix ? `${prefix}/${seg}` : seg) : prefix
|
|
114
|
+
if (seg) cb(path, item)
|
|
115
|
+
walkFolderItems(item.$children, cb, path)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Harvest per-item identity from the folder document the backend returns.
|
|
121
|
+
*
|
|
122
|
+
* ⭐ THE KEY IS THE `path_segment` CHAIN, and it is the right one because the
|
|
123
|
+
* backend's own model declares `path_segment` SIBLING-UNIQUE — so the chain is
|
|
124
|
+
* unique within the folder, stable across pushes, and derivable identically on
|
|
125
|
+
* both sides without either lane holding the other's ids.
|
|
126
|
+
*
|
|
127
|
+
* @param {object} doc - a stored `@uniweb/folder` document (`{ contents: [...] }`)
|
|
128
|
+
* @returns {Record<string,string>} path → `$uuid`
|
|
129
|
+
*/
|
|
130
|
+
export function collectFolderItemUuids(doc) {
|
|
131
|
+
const out = {}
|
|
132
|
+
walkFolderItems(doc?.contents, (path, item) => {
|
|
133
|
+
if (typeof item.$uuid === 'string' && item.$uuid) out[path] = item.$uuid
|
|
134
|
+
})
|
|
135
|
+
return out
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Stamp known `$uuid`s onto a folder document about to be sent, so the backend
|
|
140
|
+
* matches its stored rows instead of reading every item as new.
|
|
141
|
+
*
|
|
142
|
+
* ⛔ WHY THIS EXISTS. `contents` is a `multi` section: an item without a `$uuid`
|
|
143
|
+
* is a new row, so re-sending the folder without identity would replace every
|
|
144
|
+
* placement. The backend refuses that outright (`identity_required`) — correctly
|
|
145
|
+
* — and the refusal is what a `publish` after a `push` used to hit, because
|
|
146
|
+
* send-only-changed skips the unchanged RECORDS and re-sends the FOLDER alone.
|
|
147
|
+
*
|
|
148
|
+
* ⚠️ The folder ENTITY still carries no `$uuid` — that stays the backend's, keyed
|
|
149
|
+
* from the site-content uuid. This is about its ITEMS, and the two were conflated
|
|
150
|
+
* by a comment in this file that was true of the entity and false of its contents.
|
|
151
|
+
*
|
|
152
|
+
* @returns {{ stamped: number, unknown: number }}
|
|
153
|
+
*/
|
|
154
|
+
export function stampFolderItemUuids(doc, pathToUuid = {}) {
|
|
155
|
+
let stamped = 0
|
|
156
|
+
let unknown = 0
|
|
157
|
+
walkFolderItems(doc?.contents, (path, item) => {
|
|
158
|
+
const uuid = pathToUuid[path]
|
|
159
|
+
if (uuid) {
|
|
160
|
+
item.$uuid = uuid
|
|
161
|
+
stamped++
|
|
162
|
+
} else {
|
|
163
|
+
unknown++
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
return { stamped, unknown }
|
|
167
|
+
}
|
|
168
|
+
|
|
99
169
|
/**
|
|
100
170
|
* Build the `@uniweb/folder` entity descriptor, or null when there are no records.
|
|
101
171
|
*
|
|
@@ -106,9 +176,12 @@ function virtualContents(folders, groups) {
|
|
|
106
176
|
* @param {object[]} params.recordEntities - the collection-record entities (full
|
|
107
177
|
* set, BEFORE send-only-changed filtering), each `{ id, uuid, slug, collection? }`
|
|
108
178
|
* @param {Array|null} [params.folders] - `collections.yml::folders` virtual org
|
|
179
|
+
* @param {Record<string,string>} [params.itemUuids] - path → `$uuid`, harvested
|
|
180
|
+
* from the folder document a previous push returned. Absent on a first
|
|
181
|
+
* push, where every item is genuinely new.
|
|
109
182
|
* @returns {{ id, uuid, model, file, document, collection: '@folder' }|null}
|
|
110
183
|
*/
|
|
111
|
-
export function buildFolderEntity({ recordEntities, folders = null }) {
|
|
184
|
+
export function buildFolderEntity({ recordEntities, folders = null, itemUuids = null }) {
|
|
112
185
|
if (!Array.isArray(recordEntities) || recordEntities.length === 0) return null
|
|
113
186
|
const groups = groupByCollection(recordEntities)
|
|
114
187
|
const contents = folders ? virtualContents(folders, groups) : defaultContents(groups)
|
|
@@ -118,6 +191,9 @@ export function buildFolderEntity({ recordEntities, folders = null }) {
|
|
|
118
191
|
$model: FOLDER_MODEL_NAME,
|
|
119
192
|
contents,
|
|
120
193
|
}
|
|
194
|
+
// Re-arm placement identity. Without it a second send reads as "every item is
|
|
195
|
+
// new" and the backend refuses rather than replacing them all.
|
|
196
|
+
if (itemUuids && Object.keys(itemUuids).length) stampFolderItemUuids(document, itemUuids)
|
|
121
197
|
|
|
122
198
|
return {
|
|
123
199
|
id: FOLDER_ENTITY_KEY,
|
package/src/uwx/index.js
CHANGED
|
@@ -55,7 +55,10 @@ export {
|
|
|
55
55
|
COLLECTIONS_YML_RELPATH,
|
|
56
56
|
} from './collections-config.js'
|
|
57
57
|
export { upsertYamlScalar } from './yaml-upsert.js'
|
|
58
|
-
export { buildFolderEntity
|
|
58
|
+
export { buildFolderEntity,
|
|
59
|
+
collectFolderItemUuids,
|
|
60
|
+
stampFolderItemUuids
|
|
61
|
+
} from './folder.js'
|
|
59
62
|
export {
|
|
60
63
|
collectionsToProject,
|
|
61
64
|
declarationsToCollectionsYml,
|
package/src/uwx/sync-package.js
CHANGED
|
@@ -277,6 +277,12 @@ export async function emitSyncPackages(siteRoot, opts = {}) {
|
|
|
277
277
|
const folder = buildFolderEntity({
|
|
278
278
|
recordEntities: col.entities,
|
|
279
279
|
folders: col.colConfig?.folders ?? null,
|
|
280
|
+
// Placement identity from the folder document a previous push returned.
|
|
281
|
+
// Absent on a first push — every item is genuinely new then. Its ABSENCE on a
|
|
282
|
+
// later push is what made `publish` after `push` fail: send-only-changed skips
|
|
283
|
+
// the unchanged records and re-sends the folder ALONE, so the folder is exactly
|
|
284
|
+
// the payload whose item identity has to survive.
|
|
285
|
+
...(opts.folderItemUuids ? { itemUuids: opts.folderItemUuids } : {}),
|
|
280
286
|
})
|
|
281
287
|
|
|
282
288
|
const siteDoc = includeSite ? await siteProjectToDocument(siteRoot, { sourceLocale }) : null
|