@uniweb/build 0.16.10 → 0.16.11
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 +4 -4
- package/src/site/build-site-data.js +20 -56
- package/src/site/data-ball.js +37 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.11",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -60,12 +60,12 @@
|
|
|
60
60
|
"sharp": "^0.35.3",
|
|
61
61
|
"yaml": "^2.5.0",
|
|
62
62
|
"@uniweb/content-writer": "0.3.3",
|
|
63
|
-
"@uniweb/
|
|
64
|
-
"@uniweb/
|
|
63
|
+
"@uniweb/theming": "0.1.15",
|
|
64
|
+
"@uniweb/projections": "0.2.5"
|
|
65
65
|
},
|
|
66
66
|
"optionalDependencies": {
|
|
67
67
|
"@uniweb/content-reader": "1.2.2",
|
|
68
|
-
"@uniweb/runtime": "0.9.
|
|
68
|
+
"@uniweb/runtime": "0.9.5",
|
|
69
69
|
"@uniweb/semantic-parser": "1.2.1",
|
|
70
70
|
"@uniweb/schemas": "0.2.5"
|
|
71
71
|
},
|
|
@@ -27,10 +27,6 @@ import { processCollections, writeCollectionFiles } from './collection-processor
|
|
|
27
27
|
import { processAssets, rewriteSiteContentPaths } from './asset-processor.js'
|
|
28
28
|
import { processAdvancedAssets } from './advanced-processors.js'
|
|
29
29
|
import {
|
|
30
|
-
generateSearchIndex,
|
|
31
|
-
generateCollectionIndex,
|
|
32
|
-
mergeSearchIndexes,
|
|
33
|
-
getSearchIndexFilename,
|
|
34
30
|
renderSiteIndex,
|
|
35
31
|
renderPageMarkdown,
|
|
36
32
|
resolveAgentsConfig,
|
|
@@ -205,58 +201,26 @@ export async function buildSiteData({
|
|
|
205
201
|
const contentPath = join(resolvedDistDir, 'site-content.json')
|
|
206
202
|
await writeFile(contentPath, JSON.stringify(finalContent, null, 2))
|
|
207
203
|
|
|
208
|
-
// 5.
|
|
209
|
-
//
|
|
210
|
-
//
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
// Collection indexes — one per routed + search-configured collection
|
|
229
|
-
const collections = finalContent.config?.collections || {}
|
|
230
|
-
const collectionIndexes = []
|
|
231
|
-
for (const [collName, collConfig] of Object.entries(collections)) {
|
|
232
|
-
if (!collConfig.search?.enabled || !collConfig.route) continue
|
|
233
|
-
const cascadeFile = join(resolvedDistDir, DATA_DIR, `${collName}.json`)
|
|
234
|
-
if (!existsSync(cascadeFile)) continue
|
|
235
|
-
let collectionData
|
|
236
|
-
try {
|
|
237
|
-
collectionData = JSON.parse(await readFile(cascadeFile, 'utf8'))
|
|
238
|
-
} catch {
|
|
239
|
-
continue
|
|
240
|
-
}
|
|
241
|
-
const collIndex = generateCollectionIndex(collName, collConfig, collectionData, defaultLocale)
|
|
242
|
-
collectionIndexes.push(collIndex)
|
|
243
|
-
await writeFile(join(searchDir, `${collName}.json`), JSON.stringify(collIndex))
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// The single-file form, for the BROWSER lane.
|
|
247
|
-
//
|
|
248
|
-
// The split files above serve a server that loads only the parts a query
|
|
249
|
-
// needs. Kit's client-side `index` provider needs all of it, and asks for
|
|
250
|
-
// `search-index.json` — so without this, a site published through this lane
|
|
251
|
-
// 404s on its own search index and degrades to no results. Emitting both
|
|
252
|
-
// from the same entries is one extra serialization and means the client
|
|
253
|
-
// works identically on every lane, with no host configuration describing
|
|
254
|
-
// where the index lives.
|
|
255
|
-
await writeFile(
|
|
256
|
-
join(resolvedDistDir, getSearchIndexFilename(defaultLocale, defaultLocale)),
|
|
257
|
-
JSON.stringify(mergeSearchIndexes(pagesIndex, collectionIndexes))
|
|
258
|
-
)
|
|
259
|
-
}
|
|
204
|
+
// 5. (removed 2026-08-01) This lane used to emit a search index — the split
|
|
205
|
+
// `_search/{locale}/*.json` for a server, and `search-index.json` for the
|
|
206
|
+
// browser — gated by `features: [search]`.
|
|
207
|
+
//
|
|
208
|
+
// Both are gone because only ONE of the two publishers produced them. A
|
|
209
|
+
// CLI deploy did; a CMS publish did not, so a site's search existed or
|
|
210
|
+
// vanished depending on who published it last. That is the flicker rule,
|
|
211
|
+
// and the fix is not to make the app produce them too — it is that a host
|
|
212
|
+
// storing the content derives search from it, one input that exists
|
|
213
|
+
// identically on both lanes.
|
|
214
|
+
//
|
|
215
|
+
// The browser one was doubly dead: nothing ever uploaded it. `dist/` on
|
|
216
|
+
// this lane reaches a backend only through the data ball and the media
|
|
217
|
+
// refs, and the ball read `dist/data` and `dist/_search` — never the dist
|
|
218
|
+
// root. So it was serialized on every publish and dropped.
|
|
219
|
+
//
|
|
220
|
+
// ⚠️ The static index for hosts with NO backend is untouched and still
|
|
221
|
+
// emitted by the bundle lane (`site/plugin.js` → `search-index.json`).
|
|
222
|
+
// That is what GitHub Pages and every other static target serve, and the
|
|
223
|
+
// framework has more targets than one backend.
|
|
260
224
|
|
|
261
225
|
// 6. Agent projections — `llms.txt` and one `.md` per page.
|
|
262
226
|
//
|
package/src/site/data-ball.js
CHANGED
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
* collections partition by schema presence: a collection that resolves a data schema
|
|
4
4
|
* syncs as folder entities; a SCHEMA-LESS collection has no entity model, so its built
|
|
5
5
|
* `dist/data/<name>.json` (cascade + any `deferred:` per-record files) is delivered
|
|
6
|
-
* statically. This bundles that schema-less subset of `dist/data/**`
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* bytes the gateway serves.
|
|
6
|
+
* statically. This bundles that schema-less subset of `dist/data/**` into one JSON doc
|
|
7
|
+
* the deploy uploads as a single content-addressed asset; the backend unwraps it into
|
|
8
|
+
* the `/data/*` bytes the gateway serves.
|
|
10
9
|
*
|
|
11
|
-
* { data:
|
|
12
|
-
* search: { "<relpath-under-_search>": <json> } } // the whole (baked) index
|
|
10
|
+
* { data: { "<relpath-under-data>": <json> } } // schema-less collections only
|
|
13
11
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
12
|
+
* **A search index used to ride here too, and deliberately no longer does** (2026-08-01).
|
|
13
|
+
* Only a CLI deploy produced one — a CMS publish produced none — so a site's search
|
|
14
|
+
* existed or vanished depending on who published it, which is the flicker rule exactly.
|
|
15
|
+
* A host that wants search derives it from the content it already stores. See the note
|
|
16
|
+
* at the removal point below, and `collab/context/site-derived-artifacts.md`.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { existsSync } from 'node:fs'
|
|
@@ -64,8 +64,6 @@ export async function assembleDataBall(distDir, schemalessNames = []) {
|
|
|
64
64
|
for (const [relPath, value] of Object.entries(allData)) {
|
|
65
65
|
if (schemaless.has(collectionOf(relPath))) data[relPath] = value
|
|
66
66
|
}
|
|
67
|
-
const search = await readJsonTree(join(distDir, '_search'))
|
|
68
|
-
|
|
69
67
|
// Agent projections deliberately do NOT ride the ball.
|
|
70
68
|
//
|
|
71
69
|
// A backend that stores the site's content derives them itself at publish —
|
|
@@ -78,10 +76,36 @@ export async function assembleDataBall(distDir, schemalessNames = []) {
|
|
|
78
76
|
// (An opt-in `projections` bucket lived here while the delivery contract was
|
|
79
77
|
// open. It never shipped enabled, and the one-producer ruling closed the
|
|
80
78
|
// question — removed rather than left as a flag nobody may turn on.)
|
|
79
|
+
//
|
|
80
|
+
// THE SEARCH INDEX IS ONE OF THOSE PROJECTIONS, and it rode this ball anyway
|
|
81
|
+
// — `const search = readJsonTree(dist/_search)` sat four lines above this
|
|
82
|
+
// comment, doing the exact thing the comment forbids. Removed 2026-08-01.
|
|
83
|
+
//
|
|
84
|
+
// What made it wrong is not symmetry, it is the flicker rule: a CLI deploy
|
|
85
|
+
// produced the index and a CMS publish produced none, so a site's search
|
|
86
|
+
// oscillated with whoever published it. Deriving it from stored content — one
|
|
87
|
+
// input that exists identically on both lanes — makes that unexpressible,
|
|
88
|
+
// which is a stronger guarantee than any producer agreement.
|
|
89
|
+
//
|
|
90
|
+
// The static index for hosts with no backend is UNCHANGED and still emitted:
|
|
91
|
+
// `search-index.json`, bundle lane, what GitHub Pages and every other static
|
|
92
|
+
// target serve. The framework has more targets than one backend, and that is
|
|
93
|
+
// the artifact for the rest of them.
|
|
81
94
|
|
|
82
|
-
if (Object.keys(data).length === 0
|
|
95
|
+
if (Object.keys(data).length === 0) return null
|
|
83
96
|
|
|
84
|
-
|
|
97
|
+
// `search` ships as a deliberate empty map rather than being dropped, for one
|
|
98
|
+
// release, because the consumer described THIS as its safe path: "my relay is
|
|
99
|
+
// harmless while it receives an empty map", and it retires the field as
|
|
100
|
+
// cleanup once nothing sends content. An absent key is a different shape from
|
|
101
|
+
// an empty one to a strict deserializer, and a missing required field fails
|
|
102
|
+
// exactly as loudly as an unknown one — which is how the producer half of
|
|
103
|
+
// this contract broke pushes earlier the same day, in the other direction.
|
|
104
|
+
//
|
|
105
|
+
// So this is not indecision: emitting empty is the announced-and-agreed step,
|
|
106
|
+
// and dropping the key belongs to the consumer's cleanup, after which this
|
|
107
|
+
// line goes too.
|
|
108
|
+
return { data, search: {} }
|
|
85
109
|
}
|
|
86
110
|
|
|
87
111
|
// --- local media in the ball -------------------------------------------------
|