uniweb 0.79.0 → 0.79.2
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/partials/agents.md +16 -4
- package/src/backend/asset-download.js +49 -0
- package/src/backend/site-media.js +13 -5
- package/src/backend/site-sync.js +31 -3
- package/src/commands/publish.js +2 -1
- package/src/commands/pull.js +20 -1
- package/src/commands/push.js +2 -1
- package/src/framework-index.json +3 -3
- package/templates/foundation/package.json.hbs +10 -1
- package/templates/site/_gitignore +5 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uniweb",
|
|
3
|
-
"version": "0.79.
|
|
3
|
+
"version": "0.79.2",
|
|
4
4
|
"description": "Create structured Vite + React sites with content/code separation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,17 +41,17 @@
|
|
|
41
41
|
"js-yaml": "^4.1.0",
|
|
42
42
|
"prompts": "^2.4.2",
|
|
43
43
|
"tar": "^7.0.0",
|
|
44
|
+
"@uniweb/core": "^0.35.0",
|
|
45
|
+
"@uniweb/schemas": "^0.9.3",
|
|
46
|
+
"@uniweb/kit": "^0.19.11",
|
|
44
47
|
"@uniweb/runtime": "^0.28.3",
|
|
45
|
-
"@uniweb/schemas": "^0.9.1",
|
|
46
48
|
"@uniweb/content-writer": "^0.3.4",
|
|
47
|
-
"@uniweb/kit": "^0.19.11",
|
|
48
|
-
"@uniweb/core": "^0.35.0",
|
|
49
49
|
"@uniweb/semantic-parser": "^1.4.1"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
+
"@uniweb/build": "^0.73.2",
|
|
52
53
|
"@uniweb/content-reader": "^1.2.7",
|
|
53
|
-
"@uniweb/semantic-parser": "^1.4.1"
|
|
54
|
-
"@uniweb/build": "^0.73.0"
|
|
54
|
+
"@uniweb/semantic-parser": "^1.4.1"
|
|
55
55
|
},
|
|
56
56
|
"peerDependenciesMeta": {
|
|
57
57
|
"@uniweb/build": {
|
package/partials/agents.md
CHANGED
|
@@ -866,6 +866,10 @@ site/
|
|
|
866
866
|
|
|
867
867
|
Keep those folders flat: `records/article/design-tips.md` works; `records/article/2025/design-tips.md` is read as the `2025` schema of an `article` org, which is not what you meant. Organise in `records/folder.yml` instead. A file whose name starts with `_` is not a record — somewhere to keep work in progress.
|
|
868
868
|
|
|
869
|
+
**Links are records too — `@uniweb/link`.** A file in `records/uniweb/link/` holding `url:` and nothing else is a link record (one per file; no body, no other fields). Its display text and tags go on its `records/folder.yml` entry (`- path: uniweb/link/launch-video.yml` with `label:` / `tags:`), like any record's. Select links with a query `schema: '@uniweb/link'` and declare them in `meta.js` as `data: { videos: '@uniweb/link' }`; each arrives as `{ url, $name, $label, $tags }`. A link's `url` is absolute (`https://…`), and `uniweb push` refuses a relative one and a `draft: true` link.
|
|
870
|
+
|
|
871
|
+
**Files are records too — `@uniweb/file`.** The file itself is the record: any file dropped into `records/uniweb/file/` (a PDF, a spreadsheet, any type) is a record named by its file name without the extension; nothing in it is read. Its label and tags go on its `records/folder.yml` entry (`- path: uniweb/file/brochure.pdf`). Query with `schema: '@uniweb/file'`, declare with `data: { downloads: '@uniweb/file' }`; each arrives as `{ file: { url, name, mime, size }, $name, $label, $tags }` — link to `file.url`. `uniweb push` uploads the file with the site's media; a pull fetches it back. Two files of one name with different extensions clash — rename one. On a site that goes to a backend, a component must declare the type (`@uniweb/link` or `@uniweb/file`) for a query over it to be accepted.
|
|
872
|
+
|
|
869
873
|
**Four formats, one shape.** All of these produce the same records at runtime:
|
|
870
874
|
|
|
871
875
|
| Format | Best for | Notes |
|
|
@@ -907,7 +911,7 @@ The body — the value of the schema's content field, `body.content`.
|
|
|
907
911
|
- article/2025-*.md
|
|
908
912
|
```
|
|
909
913
|
|
|
910
|
-
A path under a folder is relative to `records/`, naming one file or matching many. A record's entry can instead be `{ path, tags, label }` — what the folder says about the record: a component receives them beside it as `$tags` (in the order written) and `$label`, and a query reads them like any key (`where: { $tags: featured }`); such an entry may sit at the top level, where a bare path is refused. A folder takes `tags:` too. A query reads one folder with `scope: archive` — the folder and everything inside it. A record sits in one folder; if you want a computed subset, that is a query, not a second placement. Structure is for querying, not for navigation — and `folder.yml` never lists a record at the top level (the build refuses it: every file in `records/` is a record already).
|
|
914
|
+
A path under a folder is relative to `records/`, naming one file or matching many. A record's entry can instead be `{ path, tags, label }` — what the folder says about the record: a component receives them beside it as `$tags` (in the order written) and `$label`, and a query reads them like any key (`where: { $tags: featured }`); such an entry may sit at the top level, where a bare path is refused. A `label` — a record entry's or a folder's — is one text in the site's language, or one per language (`label: { en: Launch video, fr: Vidéo de lancement }`); `$label` is the page's language, else its base language, else the site's. A folder takes `tags:` too. A query reads one folder with `scope: archive` — the folder and everything inside it. A record sits in one folder; if you want a computed subset, that is a query, not a second placement. Structure is for querying, not for navigation — and `folder.yml` never lists a record at the top level (the build refuses it: every file in `records/` is a record already).
|
|
911
915
|
|
|
912
916
|
⚠️ **With a backend, `records/` is what `uniweb push` sends** — every record in it. A site with no `records/` at all leaves the backend's records alone; an *empty* `records/` removes them, and the CLI asks before it does. Records pushed to a backend are served once the site is published.
|
|
913
917
|
|
|
@@ -1037,9 +1041,17 @@ function MyComponent({ content, params, block }) {
|
|
|
1037
1041
|
}
|
|
1038
1042
|
```
|
|
1039
1043
|
|
|
1040
|
-
Frontmatter becomes `params
|
|
1044
|
+
**Frontmatter becomes `params` — except the names framework keeps for itself.** Every section can be given these, whatever its type, and none of them reaches your component as a param. So **never declare one in `meta.js` `params:`** — a component that does never receives what an author writes.
|
|
1045
|
+
|
|
1046
|
+
| in a section's frontmatter | what it is | a component reads it from |
|
|
1047
|
+
|---|---|---|
|
|
1048
|
+
| `type`, `id`, `hidden` | the section itself — its section type, its id, and a draft (`hidden: true`: left out of a published build, kept by `uniweb dev`) | — |
|
|
1049
|
+
| `query`, `fetch` | the section's data | `content.data`, under the keys its `meta.js` `data:` declares |
|
|
1050
|
+
| `theme`, `background`, `grid`, `vars` | the section's settings, which framework applies — the color context and the background around your component, the section's theme and your component's CSS variables in the page stylesheet, the child layout | `block`, when the component needs one for its own logic: `useColorContext(block)` for the context (a light vs. dark logo, say), `<SectionBackground block={block} />` to draw the background itself, `<ChildGrid from={block} />` for the grid |
|
|
1051
|
+
| `props` | params written as one map | `params` — its keys are merged in, one by one |
|
|
1052
|
+
| `data`, `preset`, `input` | nothing: `data:` is refused (it was `query:`'s old name); `preset:` and `input:` are ignored with a warning | — |
|
|
1041
1053
|
|
|
1042
|
-
|
|
1054
|
+
The build warns when `params:` declares any of them. A preset may still set `theme` or `background` — a preset's params are written into the section's frontmatter when an author starts a section from it, so `dark: { label: 'Dark', params: { theme: 'dark' } }` is a dark section. Every other key is a param, declared or not; components ignore the ones they don't use.
|
|
1043
1055
|
|
|
1044
1056
|
### Rendering content with Kit
|
|
1045
1057
|
|
|
@@ -2411,7 +2423,7 @@ For cases the factory doesn't cover, write handlers directly using `Loom`, `inst
|
|
|
2411
2423
|
|
|
2412
2424
|
> **`instantiateContent` resolves `{placeholders}` in text nodes only** — not in link `href`s or other node/mark attributes. So `[{email}](mailto:{email})` fills the visible label but leaves the `mailto:` URL literal. For dynamic URLs, emit the value as plain text and let the component linkify it, or build the href in the handler yourself.
|
|
2413
2425
|
|
|
2414
|
-
|
|
2426
|
+
**`source` and `where` are ordinary params** that the Loom handler reads — they reach both `block.properties` (for handler access) and `params` (visible to components). Components can ignore them. List them in `meta.js` params with descriptions so the editor and schema recognize them. (Unlike `theme` and `background`, which are the section's own settings and never reach `params` — see the table under *Frontmatter becomes `params`*.)
|
|
2415
2427
|
<!-- /template:loom -->
|
|
2416
2428
|
|
|
2417
2429
|
---
|
|
@@ -171,3 +171,52 @@ export async function downloadMissingAssets({
|
|
|
171
171
|
updateBackendMap(siteDir, origin, 'assets', learned, carryServed)
|
|
172
172
|
return out
|
|
173
173
|
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Fetch the files a pull's file records name (`@uniweb/file`) to where each lands in
|
|
177
|
+
* `records/uniweb/file/` — the list `recordsToProject` returns as `fileDownloads` — and record each
|
|
178
|
+
* one's asset under its key (`sync.json` `assets`), as a push records the upload, so the next pull
|
|
179
|
+
* knows the bytes on disk are that asset and fetches nothing.
|
|
180
|
+
*
|
|
181
|
+
* @param {object} opts
|
|
182
|
+
* @param {Array<{ url: string, path: string, ref: string, assetId?: string, assetExt?: string }>} opts.downloads
|
|
183
|
+
* @param {string} opts.siteDir
|
|
184
|
+
* @param {string} opts.origin - backend origin, to resolve an origin-relative URL
|
|
185
|
+
* @param {typeof fetch} [opts.fetchImpl]
|
|
186
|
+
* @param {(m: string) => void} [opts.onProgress]
|
|
187
|
+
* @param {(m: string) => void} [opts.warn]
|
|
188
|
+
* @returns {Promise<{ downloaded: string[], failed: string[] }>} paths
|
|
189
|
+
*/
|
|
190
|
+
export async function downloadFileRecords({
|
|
191
|
+
downloads = [],
|
|
192
|
+
siteDir,
|
|
193
|
+
origin,
|
|
194
|
+
fetchImpl,
|
|
195
|
+
onProgress = () => {},
|
|
196
|
+
warn = () => {}
|
|
197
|
+
}) {
|
|
198
|
+
const doFetch = fetchImpl || ((u) => globalThis.fetch(u))
|
|
199
|
+
const out = { downloaded: [], failed: [] }
|
|
200
|
+
const learned = {}
|
|
201
|
+
for (const d of downloads) {
|
|
202
|
+
try {
|
|
203
|
+
const res = await doFetch(new URL(d.url, origin).href)
|
|
204
|
+
if (!res.ok) {
|
|
205
|
+
warn(`file ${d.ref}: HTTP ${res.status} — not fetched`)
|
|
206
|
+
out.failed.push(d.path)
|
|
207
|
+
continue
|
|
208
|
+
}
|
|
209
|
+
const bytes = Buffer.from(await res.arrayBuffer())
|
|
210
|
+
mkdirSync(dirname(d.path), { recursive: true })
|
|
211
|
+
writeFileSync(d.path, bytes)
|
|
212
|
+
onProgress(`↓ ${d.ref}`)
|
|
213
|
+
out.downloaded.push(d.path)
|
|
214
|
+
if (d.assetId) learned[d.ref] = { id: d.assetId, ext: d.assetExt || '' }
|
|
215
|
+
} catch (err) {
|
|
216
|
+
warn(`file ${d.ref}: ${err.message} — not fetched`)
|
|
217
|
+
out.failed.push(d.path)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (Object.keys(learned).length) updateBackendMap(siteDir, origin, 'assets', learned, carryServed)
|
|
221
|
+
return out
|
|
222
|
+
}
|
|
@@ -24,7 +24,8 @@ import { humanBytes } from '../utils/bytes.js'
|
|
|
24
24
|
* @param {object} client - BackendClient (uploadSiteAssets). No `discover` — this
|
|
25
25
|
* lane stopped consulting the capability doc when `assetBase` was removed.
|
|
26
26
|
* @param {string} siteDir - the site root (site-root refs resolve under public/)
|
|
27
|
-
* @param {string
|
|
27
|
+
* @param {Array<string|{ ref: string, path: string, contentType?: string }>} refs - site-root local
|
|
28
|
+
* asset refs (`/images/x.png`), or files already located (a file record's)
|
|
28
29
|
* @param {{ siteUuid?: string|null, onProgress?: (m: string) => void, warn?: (m: string) => void }} [opts]
|
|
29
30
|
* `siteUuid` is the owner the uploaded bytes are charged to. Callers create the
|
|
30
31
|
* site before uploading precisely so this is set — an unowned upload is charged
|
|
@@ -47,8 +48,15 @@ export async function uploadSiteMedia(
|
|
|
47
48
|
|
|
48
49
|
const files = []
|
|
49
50
|
const missing = []
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
// ⭐ A ref is a site-root media path (`/images/x.png`), resolved under the site — or a file the
|
|
52
|
+
// caller already located, `{ ref, path, contentType }`: a file record's file and its preview, which
|
|
53
|
+
// live in the records directory and declare their own type (`@uniweb/file`, 2026-09-28).
|
|
54
|
+
const keys = []
|
|
55
|
+
for (const item of refs) {
|
|
56
|
+
const ref = typeof item === 'string' ? item : item?.ref
|
|
57
|
+
if (typeof ref !== 'string' || !ref) continue
|
|
58
|
+
keys.push(ref)
|
|
59
|
+
const resolved = typeof item === 'string' ? resolveAssetPath(ref, siteDir, siteDir).resolved : item.path
|
|
52
60
|
if (!resolved || !existsSync(resolved)) {
|
|
53
61
|
warn?.(`local-media: ${ref} not found under the site (skipped)`)
|
|
54
62
|
missing.push(ref)
|
|
@@ -57,7 +65,7 @@ export async function uploadSiteMedia(
|
|
|
57
65
|
const bytes = readFileSync(resolved)
|
|
58
66
|
files.push({
|
|
59
67
|
path: ref.replace(/^\/+/, ''), // bookkeeping key into the plan (must be unique)
|
|
60
|
-
content_type: contentTypeFor(basename(resolved)),
|
|
68
|
+
content_type: (typeof item === 'object' && item.contentType) || contentTypeFor(basename(resolved)),
|
|
61
69
|
size: bytes.length,
|
|
62
70
|
sha256: createHash('sha256').update(bytes).digest('hex'),
|
|
63
71
|
localUrl: ref, // the rewrite key — the original content ref
|
|
@@ -77,7 +85,7 @@ export async function uploadSiteMedia(
|
|
|
77
85
|
// dedup skip, so a re-push of unchanged media still records identity without
|
|
78
86
|
// moving bytes — which is what makes the committed map cheap to keep accurate.
|
|
79
87
|
const ids = {}
|
|
80
|
-
for (const ref of
|
|
88
|
+
for (const ref of keys) {
|
|
81
89
|
const entry = result.assetsByLocalUrl[ref]
|
|
82
90
|
if (!entry) continue
|
|
83
91
|
// `served` — a fingerprint of the address, never the address. It is how a pull
|
package/src/backend/site-sync.js
CHANGED
|
@@ -49,7 +49,10 @@ import {
|
|
|
49
49
|
matchStoredRecords,
|
|
50
50
|
parseCatalogRef,
|
|
51
51
|
readRegisteredFoundation,
|
|
52
|
-
writeRegisteredFoundation
|
|
52
|
+
writeRegisteredFoundation,
|
|
53
|
+
backfillLinkUuids,
|
|
54
|
+
LINK_MODEL,
|
|
55
|
+
bankedFileUuids
|
|
53
56
|
} from '@uniweb/build/uwx'
|
|
54
57
|
|
|
55
58
|
// First entity `$`-document out of a `.uwx` we produced or the backend served.
|
|
@@ -1419,7 +1422,9 @@ export async function recoverUnbankedIdentity({ client, siteDir, pkg, note }) {
|
|
|
1419
1422
|
}
|
|
1420
1423
|
// The records the package sends, and the folder placing them. The emit reads the `records`
|
|
1421
1424
|
// map from sync.json itself, so it needs no option — only the build again.
|
|
1422
|
-
|
|
1425
|
+
// Link records are matched as records are — by Model and name (`@uniweb/link`, `link-records.js`).
|
|
1426
|
+
const links = (pkg?.records?.links || []).map((l) => ({ ownId: l.ownId, model: LINK_MODEL, slug: l.slug }))
|
|
1427
|
+
if (await recoverRecordsIdentity({ client, siteDir, index: [...(pkg?.records?.index || []), ...links], note })) {
|
|
1423
1428
|
again = {
|
|
1424
1429
|
...again,
|
|
1425
1430
|
folderItemUuids: readFolderItemUuids(siteDir, client.origin),
|
|
@@ -1754,7 +1759,7 @@ export async function pushSyncPackages({
|
|
|
1754
1759
|
// Two unrelated conflicts share HTTP 409, so branch on the machine-readable
|
|
1755
1760
|
// `reason` — never on `detail`, which is prose the backend may reword.
|
|
1756
1761
|
let problem = null
|
|
1757
|
-
if ((res.status === 409 || res.status === 400) && body) {
|
|
1762
|
+
if ((res.status === 409 || res.status === 400 || res.status === 422) && body) {
|
|
1758
1763
|
try {
|
|
1759
1764
|
problem = JSON.parse(body)
|
|
1760
1765
|
} catch {
|
|
@@ -1934,6 +1939,15 @@ export async function pushSyncPackages({
|
|
|
1934
1939
|
` deleted there → uniweb forget --backend ${client.origin}, then push again: it creates a NEW site`
|
|
1935
1940
|
)
|
|
1936
1941
|
note('Deleting this folder removes only your local copy, either way.')
|
|
1942
|
+
} else if (problem?.reason === 'template_records_not_copyable' && Array.isArray(problem.records)) {
|
|
1943
|
+
// ⭐ A push asking to designate the site a template is refused whole when a record in its
|
|
1944
|
+
// folder cannot be copied into the sites made from it — each named, with a `detail` written
|
|
1945
|
+
// to be shown. Until 2026-09-28 this printed the raw problem document, cut at 800 characters.
|
|
1946
|
+
note(problem.title || 'These records cannot be copied into a template:')
|
|
1947
|
+
for (const r of problem.records) {
|
|
1948
|
+
note(` ${r?.name ?? '(unnamed)'} — ${r?.detail || r?.reason || 'no reason given'}`)
|
|
1949
|
+
}
|
|
1950
|
+
return null
|
|
1937
1951
|
} else if (res.status === 409 && (problem?.title || problem?.detail)) {
|
|
1938
1952
|
// ⛔ A 409 names its own cause — a reference the store refuses, a rule of the record's type,
|
|
1939
1953
|
// an append-only section — so it is shown as the backend put it, not explained by us. Until
|
|
@@ -2240,6 +2254,20 @@ export async function pushSyncPackages({
|
|
|
2240
2254
|
const placements = collectFolderItemUuids(folderDoc)
|
|
2241
2255
|
if (Object.keys(placements).length)
|
|
2242
2256
|
writeFolderItemUuids(siteDir, client.origin, placements)
|
|
2257
|
+
// ⭐ A LINK RECORD'S IDENTITY is its folder entry's `$uuid`, banked as a record's is: into
|
|
2258
|
+
// its file the first time, then mapped per backend (`link-records.js`, 2026-09-28).
|
|
2259
|
+
if (records.links?.length) {
|
|
2260
|
+
const lb = backfillLinkUuids({ links: records.links, folderDoc })
|
|
2261
|
+
if (Object.keys(lb.mapped).length) updateBackendMap(siteDir, client.origin, 'records', lb.mapped)
|
|
2262
|
+
for (const w of lb.warnings) note(`! ${w}`)
|
|
2263
|
+
if (lb.updated.length) wrote.push(`wrote ${lb.updated.length} link file(s)`)
|
|
2264
|
+
}
|
|
2265
|
+
// ⭐ …and a FILE RECORD's: a file cannot carry it, so it is banked per backend by the file's
|
|
2266
|
+
// path (`sync.json` `files`; `file-records.js`, 2026-09-28).
|
|
2267
|
+
if (records.files?.length) {
|
|
2268
|
+
const fb = bankedFileUuids({ files: records.files, folderDoc })
|
|
2269
|
+
if (Object.keys(fb).length) updateBackendMap(siteDir, client.origin, 'files', fb)
|
|
2270
|
+
}
|
|
2243
2271
|
}
|
|
2244
2272
|
finalizedTotal += finalized.length
|
|
2245
2273
|
}
|
package/src/commands/publish.js
CHANGED
|
@@ -656,7 +656,8 @@ export async function publish(args = []) {
|
|
|
656
656
|
// the ref→serveUrl map; rewrite the entity content AND the ball with it.
|
|
657
657
|
let assetRewrite = null
|
|
658
658
|
let assetIds = null
|
|
659
|
-
|
|
659
|
+
// …and each file record's file (`@uniweb/file` — `{ ref, path, contentType }`, already located).
|
|
660
|
+
const mediaRefs = [...new Set([...localAssets, ...ballAssets]), ...(probe.localFiles || [])]
|
|
660
661
|
if (mediaRefs.length) {
|
|
661
662
|
say.info('Uploading media…')
|
|
662
663
|
try {
|
package/src/commands/pull.js
CHANGED
|
@@ -69,7 +69,7 @@ import { createHash } from 'node:crypto'
|
|
|
69
69
|
import { createInterface } from 'node:readline/promises'
|
|
70
70
|
import { join, dirname, relative, resolve } from 'node:path'
|
|
71
71
|
import yaml from 'js-yaml'
|
|
72
|
-
import { downloadMissingAssets } from '../backend/asset-download.js'
|
|
72
|
+
import { downloadMissingAssets, downloadFileRecords } from '../backend/asset-download.js'
|
|
73
73
|
import {
|
|
74
74
|
readBackendState,
|
|
75
75
|
siteContentDocumentToProject,
|
|
@@ -1068,6 +1068,25 @@ export async function pull(args = [], deps = {}) {
|
|
|
1068
1068
|
removed.push(resolve(siteDir, report.recordsFile))
|
|
1069
1069
|
}
|
|
1070
1070
|
records += report.placed.length + report.updated.length
|
|
1071
|
+
// ⭐ A FILE RECORD'S BYTES (`@uniweb/file`, 2026-09-28): fetched to where `folder.yml` now
|
|
1072
|
+
// places each one, as media are fetched. A file that did not arrive is a record not taken —
|
|
1073
|
+
// reported, and the pull fails below as it does for any, so the next one fetches it again.
|
|
1074
|
+
if (report.fileDownloads?.length) {
|
|
1075
|
+
const got = await downloadFileRecords({
|
|
1076
|
+
downloads: report.fileDownloads,
|
|
1077
|
+
siteDir,
|
|
1078
|
+
origin: client.origin,
|
|
1079
|
+
onProgress: (m) => note(` ${m}`),
|
|
1080
|
+
warn: (m) => note(`! ${m}`)
|
|
1081
|
+
})
|
|
1082
|
+
records += got.downloaded.length
|
|
1083
|
+
wrote.push(...got.downloaded)
|
|
1084
|
+
if (got.failed.length) {
|
|
1085
|
+
report.skipped.push(
|
|
1086
|
+
...got.failed.map((path) => ({ slug: relative(siteDir, path), reason: 'its file could not be fetched' }))
|
|
1087
|
+
)
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1071
1090
|
// ⛔ A PULL THAT DID NOT PLACE A RECORD HAS NOT TAKEN THE BACKEND'S RECORDS.
|
|
1072
1091
|
// Until 2026-09-23 each skip was a dim note, the pull said "✓ Pulled … 0
|
|
1073
1092
|
// record(s)" and exited 0, and it banked the lane's ETag and tokens. So the next
|
package/src/commands/push.js
CHANGED
|
@@ -409,7 +409,8 @@ export async function push(args = [], deps = {}) {
|
|
|
409
409
|
...(foundationDir ? { foundationDir } : {}),
|
|
410
410
|
resolveModel: makeModelResolver({ client, offline: false, siteDir })
|
|
411
411
|
})
|
|
412
|
-
|
|
412
|
+
// Site-root media, and each file record's file (`@uniweb/file` — `{ ref, path, contentType }`).
|
|
413
|
+
mediaRefs = [...(probe.localAssets || []), ...(probe.localFiles || [])]
|
|
413
414
|
refusals = probe.refusals || []
|
|
414
415
|
} catch (err) {
|
|
415
416
|
error(`Could not scan the site for local media: ${err.message}`)
|
package/src/framework-index.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-09-
|
|
3
|
+
"generatedAt": "2026-09-28T23:00:27.056Z",
|
|
4
4
|
"packages": {
|
|
5
5
|
"@uniweb/api": {
|
|
6
6
|
"version": "0.6.8",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
]
|
|
11
11
|
},
|
|
12
12
|
"@uniweb/build": {
|
|
13
|
-
"version": "0.73.
|
|
13
|
+
"version": "0.73.2",
|
|
14
14
|
"path": "framework/build",
|
|
15
15
|
"deps": [
|
|
16
16
|
"@uniweb/content-reader",
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
"deps": []
|
|
98
98
|
},
|
|
99
99
|
"@uniweb/schemas": {
|
|
100
|
-
"version": "0.9.
|
|
100
|
+
"version": "0.9.3",
|
|
101
101
|
"path": "framework/schemas",
|
|
102
102
|
"deps": []
|
|
103
103
|
},
|
|
@@ -14,7 +14,16 @@
|
|
|
14
14
|
"#components/*": "./components/*",
|
|
15
15
|
"#utils/*": "./utils/*"
|
|
16
16
|
},
|
|
17
|
-
"files": [
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"sections",
|
|
20
|
+
"components",
|
|
21
|
+
"layouts",
|
|
22
|
+
"utils",
|
|
23
|
+
"main.js",
|
|
24
|
+
"styles.css",
|
|
25
|
+
"_entry.generated.js"
|
|
26
|
+
],
|
|
18
27
|
"scripts": {
|
|
19
28
|
"dev": "vite",
|
|
20
29
|
"build": "uniweb build",
|
|
@@ -4,7 +4,9 @@ dist
|
|
|
4
4
|
.DS_Store
|
|
5
5
|
*.local
|
|
6
6
|
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
7
|
+
# Generated from records/ on every build — compiled queries in public/data/, and
|
|
8
|
+
# each record's files, published in public/records/. Both land in the source tree
|
|
9
|
+
# rather than dist/, so without this they look permanent, get committed, and then
|
|
10
|
+
# look like something you may edit. They aren't: records live in records/.
|
|
10
11
|
public/data/
|
|
12
|
+
public/records/
|