uniweb 0.70.0 → 0.70.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 +7 -7
- package/src/backend/client.js +38 -7
- package/src/backend/service-request.js +11 -0
- package/src/backend/site-preview.js +77 -0
- package/src/backend/site-sync.js +154 -19
- package/src/commands/clone.js +6 -0
- package/src/commands/dev.js +29 -1
- package/src/commands/publish.js +19 -4
- package/src/commands/pull.js +40 -4
- package/src/commands/push.js +22 -2
- package/src/framework-index.json +5 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uniweb",
|
|
3
|
-
"version": "0.70.
|
|
3
|
+
"version": "0.70.2",
|
|
4
4
|
"description": "Create structured Vite + React sites with content/code separation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -42,16 +42,16 @@
|
|
|
42
42
|
"prompts": "^2.4.2",
|
|
43
43
|
"tar": "^7.0.0",
|
|
44
44
|
"@uniweb/content-writer": "^0.3.4",
|
|
45
|
-
"@uniweb/
|
|
46
|
-
"@uniweb/runtime": "^0.27.5",
|
|
45
|
+
"@uniweb/runtime": "^0.27.6",
|
|
47
46
|
"@uniweb/kit": "^0.19.5",
|
|
48
|
-
"@uniweb/
|
|
49
|
-
"@uniweb/semantic-parser": "^1.4.1"
|
|
47
|
+
"@uniweb/core": "^0.30.1",
|
|
48
|
+
"@uniweb/semantic-parser": "^1.4.1",
|
|
49
|
+
"@uniweb/schemas": "^0.7.2"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@uniweb/build": "^0.64.0",
|
|
53
52
|
"@uniweb/content-reader": "^1.2.5",
|
|
54
|
-
"@uniweb/semantic-parser": "^1.4.1"
|
|
53
|
+
"@uniweb/semantic-parser": "^1.4.1",
|
|
54
|
+
"@uniweb/build": "^0.64.2"
|
|
55
55
|
},
|
|
56
56
|
"peerDependenciesMeta": {
|
|
57
57
|
"@uniweb/build": {
|
package/src/backend/client.js
CHANGED
|
@@ -74,10 +74,12 @@ export function resolveBackendOrigin() {
|
|
|
74
74
|
* The fallback capability doc for when `GET /dev/config` was not asked for (no
|
|
75
75
|
* credential in hand) or did not answer. Keeps the client non-breaking.
|
|
76
76
|
*
|
|
77
|
-
* ⭐ **It is EMPTY, and that is the accurate shape.** The CLI reads
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
77
|
+
* ⭐ **It is EMPTY, and that is the accurate shape.** The CLI reads only that document's
|
|
78
|
+
* `siteContent` key lists — `queryFields`, `pageFields`, `settingsFields` — and their absence
|
|
79
|
+
* is meaningful: unknown means a push sends no optional key, which every deployment accepts,
|
|
80
|
+
* rather than claiming a deployment takes one. Every other key that used to sit here had no
|
|
81
|
+
* reader. *(The leaf named here was `delivery.siteSubscriptionRequired` until 2026-09-26; it
|
|
82
|
+
* went 2026-08-30.)*
|
|
81
83
|
*
|
|
82
84
|
* ⛔ **Do not restore a key "for completeness".** A default for a field nothing reads is
|
|
83
85
|
* a reader waiting to happen, and it is how this file came to describe a client that
|
|
@@ -457,9 +459,12 @@ export class BackendClient {
|
|
|
457
459
|
* were dropped because a serve location is read from the response that carries it
|
|
458
460
|
* (`serve_base`, `serve_url`, `config.base`), never from a handshake; `auth.loginPath`
|
|
459
461
|
* is not read either — the login path is a hardcoded constant
|
|
460
|
-
* (`utils/registry-auth.js`). What is actually consumed is
|
|
461
|
-
* `
|
|
462
|
-
*
|
|
462
|
+
* (`utils/registry-auth.js`). What is actually consumed is the key lists under
|
|
463
|
+
* `siteContent` — `queryFields`, `pageFields`, `settingsFields`, the keys the deployment's
|
|
464
|
+
* Sections declare (`site-sync.js::deploymentFields`) — which is policy, never an address. Do not
|
|
465
|
+
* add a reader for the rest: each one would be a second place a backend's layout is
|
|
466
|
+
* pinned. (This named `delivery.siteSubscriptionRequired` until 2026-09-26; that leaf
|
|
467
|
+
* went 2026-08-30.)
|
|
463
468
|
*
|
|
464
469
|
* @returns {Promise<object>}
|
|
465
470
|
*/
|
|
@@ -577,6 +582,32 @@ export class BackendClient {
|
|
|
577
582
|
}
|
|
578
583
|
}
|
|
579
584
|
|
|
585
|
+
/**
|
|
586
|
+
* GET /dev/site/foundation-schema/{site}/{@scope}/{name}/{version} → the registered version of a
|
|
587
|
+
* foundation a site uses, as the backend holds it: its `schema` (section types with their
|
|
588
|
+
* `data:`), `info`, `data_schemas` (names), `module_url` and `css_url`. Read through the SITE,
|
|
589
|
+
* which is the licence: the caller must be able to read the site, and the site must use the
|
|
590
|
+
* foundation. Null on a 404 (not registered, or not this site's) and on any failure — a caller
|
|
591
|
+
* degrades, since what it is read for can be done without it, less well.
|
|
592
|
+
* @param {string} siteUuid - the site-content uuid
|
|
593
|
+
* @param {string} ref - the catalog ref, `@scope/name@version`
|
|
594
|
+
* @returns {Promise<object|null>}
|
|
595
|
+
*/
|
|
596
|
+
async readRegisteredFoundation(siteUuid, ref) {
|
|
597
|
+
const m = /^(@[^/]+)\/([^@/]+)@(.+)$/.exec(String(ref || ''))
|
|
598
|
+
if (!siteUuid || !m) return null
|
|
599
|
+
try {
|
|
600
|
+
const res = await this.request(
|
|
601
|
+
`/dev/site/foundation-schema/${encodeURIComponent(siteUuid)}/${m[1]}/${encodeURIComponent(m[2])}/${encodeURIComponent(m[3])}`
|
|
602
|
+
)
|
|
603
|
+
if (!res.ok) return null
|
|
604
|
+
const body = await res.json().catch(() => null)
|
|
605
|
+
return body && typeof body === 'object' && body.schema && typeof body.schema === 'object' ? body : null
|
|
606
|
+
} catch {
|
|
607
|
+
return null
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
580
611
|
// ── Orgs ──────────────────────────────────────────────────────────────────────
|
|
581
612
|
|
|
582
613
|
/** GET /dev/orgs → { account_handle, orgs[] }. */
|
|
@@ -76,6 +76,11 @@ export function fingerprintDeclaration(declared) {
|
|
|
76
76
|
.slice(0, 16)
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/** Absent, or an empty list — a stored request that asks for nothing. */
|
|
80
|
+
function isNothing(value) {
|
|
81
|
+
return value === undefined || value === null || (Array.isArray(value) && value.length === 0)
|
|
82
|
+
}
|
|
83
|
+
|
|
79
84
|
/** Deterministic JSON: object keys sorted at every depth, arrays left in order. */
|
|
80
85
|
function stableString(value) {
|
|
81
86
|
return JSON.stringify(value, (_k, v) =>
|
|
@@ -212,6 +217,12 @@ export function reconcile(localValue, remoteValue, baseFingerprint) {
|
|
|
212
217
|
const base = baseFingerprint || null
|
|
213
218
|
|
|
214
219
|
if (local === remote) return { action: 'none', local, remote }
|
|
220
|
+
// ⛔ A FILE THAT DECLARES NOTHING ASKS NOTHING, so it is never one side of a conflict. With
|
|
221
|
+
// nothing stored either — an empty list is the store's nothing — there is nothing to do; with
|
|
222
|
+
// something stored, the file is merely behind. Until 2026-09-26 this was a `conflict`, and the
|
|
223
|
+
// first publish of a site whose file is silent warned that its services "were changed
|
|
224
|
+
// elsewhere, and site.yml changed too", listing both as nothing.
|
|
225
|
+
if (local === null) return { action: isNothing(remoteValue) ? 'none' : 'adopt', local, remote }
|
|
215
226
|
if (!base) return { action: 'conflict', local, remote }
|
|
216
227
|
|
|
217
228
|
const localMoved = local !== base
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `uniweb dev` asks the backend before previewing a site whose foundation is a catalog ref —
|
|
3
|
+
* every clone.
|
|
4
|
+
*
|
|
5
|
+
* Such a site has no build of its foundation, and a build does not turn a ref into a URL: where a
|
|
6
|
+
* version is served is the backend's to say. So the dev server is handed the backend's answer, read
|
|
7
|
+
* for it here through the site — the route push and pull read the version's section types from
|
|
8
|
+
* (`client.readRegisteredFoundation`) — and passed on in `UNIWEB_PREVIEW` (`@uniweb/build/site`'s
|
|
9
|
+
* `preview.js` has the contract). The backend it names is also where the site's kept media URLs are
|
|
10
|
+
* fetched from.
|
|
11
|
+
*
|
|
12
|
+
* ⛔ Nothing here is kept. The answer lives as long as the dev server: `site.yml` keeps the ref, and
|
|
13
|
+
* no file a push or pull reads gets the URL — the round trip never sees it.
|
|
14
|
+
*
|
|
15
|
+
* ⚠️ It may not be known. Whether a backend tells the CLI where a version is served is the backend's
|
|
16
|
+
* to decide, and may change, so an answer without a location is an ordinary outcome: the preview
|
|
17
|
+
* says so and does not start. Nothing else reads it — push, pull and publish work without it.
|
|
18
|
+
*/
|
|
19
|
+
import { readBackendState, parseCatalogRef } from '@uniweb/build/uwx'
|
|
20
|
+
import { BackendClient } from './client.js'
|
|
21
|
+
import { resolveWorkspace } from './workspace.js'
|
|
22
|
+
import { readDeclaredFoundation } from '../utils/install-integrity.js'
|
|
23
|
+
import { syncedBackends } from '../utils/site-identity.js'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* What the dev server of `siteDir` is handed, when its foundation is a catalog ref.
|
|
27
|
+
*
|
|
28
|
+
* @param {object} p
|
|
29
|
+
* @param {string} p.siteDir
|
|
30
|
+
* @param {string[]} [p.args]
|
|
31
|
+
* @param {BackendClient} [p.client] - injected in tests
|
|
32
|
+
* @returns {Promise<null
|
|
33
|
+
* | { preview: { backend: string, foundation: { ref: string, url: string, cssUrl: string|null } } }
|
|
34
|
+
* | { refused: string[] }>}
|
|
35
|
+
* null when there is nothing to ask — the foundation is not a catalog ref
|
|
36
|
+
*/
|
|
37
|
+
export async function readSitePreview({ siteDir, args = [], client = null }) {
|
|
38
|
+
const ref = readDeclaredFoundation(siteDir)
|
|
39
|
+
if (!parseCatalogRef(ref)) return null
|
|
40
|
+
|
|
41
|
+
client = client || new BackendClient({ args, command: 'Previewing' })
|
|
42
|
+
const uuid = readBackendState(siteDir, client.origin).site?.uuid
|
|
43
|
+
if (!uuid) {
|
|
44
|
+
const known = syncedBackends(siteDir)
|
|
45
|
+
return {
|
|
46
|
+
refused: [
|
|
47
|
+
`This site's foundation is ${ref}, and the backend the site is on says where that version is served.`,
|
|
48
|
+
known.length
|
|
49
|
+
? `The site is on ${known.join(', ')} — not on ${client.origin}, the backend you are logged in to. To preview it: uniweb login --backend <url>`
|
|
50
|
+
: `The site is on no backend yet: \`uniweb push\` puts it on ${client.origin}.`
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const ws = await resolveWorkspace({ client, args })
|
|
56
|
+
if (ws.refused) return { refused: ['A preview reads the site in one workspace, and none is chosen.', ws.reason] }
|
|
57
|
+
client.setWorkspace(ws.workspace, { source: ws.source })
|
|
58
|
+
|
|
59
|
+
const reply = await client.readRegisteredFoundation(uuid, ref)
|
|
60
|
+
const url = typeof reply?.module_url === 'string' && reply.module_url ? reply.module_url : null
|
|
61
|
+
if (!url) {
|
|
62
|
+
return {
|
|
63
|
+
refused: [
|
|
64
|
+
reply
|
|
65
|
+
? `${client.origin} did not say where ${ref} is served, so this site cannot be previewed here.`
|
|
66
|
+
: `Could not read ${ref} through this site on ${client.origin}, so this site cannot be previewed here.`,
|
|
67
|
+
'Push, pull and publish do not need it.'
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
preview: {
|
|
73
|
+
backend: client.origin,
|
|
74
|
+
foundation: { ref, url, cssUrl: typeof reply.css_url === 'string' && reply.css_url ? reply.css_url : null }
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
package/src/backend/site-sync.js
CHANGED
|
@@ -46,7 +46,10 @@ import {
|
|
|
46
46
|
harvestRecordItems,
|
|
47
47
|
storedRecordItems,
|
|
48
48
|
reprintRecordItems,
|
|
49
|
-
matchStoredRecords
|
|
49
|
+
matchStoredRecords,
|
|
50
|
+
parseCatalogRef,
|
|
51
|
+
readRegisteredFoundation,
|
|
52
|
+
writeRegisteredFoundation
|
|
50
53
|
} from '@uniweb/build/uwx'
|
|
51
54
|
|
|
52
55
|
// First entity `$`-document out of a `.uwx` we produced or the backend served.
|
|
@@ -326,18 +329,46 @@ function changedSummary(finalized) {
|
|
|
326
329
|
// 404 → null (the emitter then says "register it first"). The bearer is acquired lazily
|
|
327
330
|
// by the client, so a fully-local sync never authenticates.
|
|
328
331
|
//
|
|
329
|
-
// `offline` (set for `-o` / `--dry-run`)
|
|
330
|
-
//
|
|
331
|
-
|
|
332
|
+
// `offline` (set for `-o` / `--dry-run`, and for `status`) never touches the backend — an
|
|
333
|
+
// offline emit must never authenticate. It answers with the Models a push or a pull KEPT for
|
|
334
|
+
// this backend (`keptModels`), or null. ⭐ With `siteDir`, an online read keeps each Model it
|
|
335
|
+
// reads, per backend — so a clone, whose foundation is not in the project to declare them, can
|
|
336
|
+
// still compare offline. ⛔ Until 2026-09-26 nothing was kept, and `uniweb status` in a clone
|
|
337
|
+
// could not resolve a Model ("… could not be resolved").
|
|
338
|
+
export function makeModelResolver({ client, offline = false, siteDir = null, backend = null }) {
|
|
339
|
+
const origin = backend ?? client?.origin ?? null
|
|
340
|
+
const kept = siteDir && origin ? keptModels(siteDir, origin) : {}
|
|
332
341
|
const cache = new Map()
|
|
333
342
|
return async (modelName) => {
|
|
334
343
|
if (cache.has(modelName)) return cache.get(modelName)
|
|
335
|
-
|
|
344
|
+
let decl
|
|
345
|
+
if (offline) {
|
|
346
|
+
decl = kept[modelName] ?? null
|
|
347
|
+
} else {
|
|
348
|
+
decl = await client.readDataSchema(modelName)
|
|
349
|
+
if (decl && siteDir && origin) keepModels(siteDir, origin, { [modelName]: decl })
|
|
350
|
+
}
|
|
336
351
|
cache.set(modelName, decl)
|
|
337
352
|
return decl
|
|
338
353
|
}
|
|
339
354
|
}
|
|
340
355
|
|
|
356
|
+
/**
|
|
357
|
+
* The Models a push or a pull read from this backend, by name — kept in `.uniweb/backend-cache.json`
|
|
358
|
+
* so an offline emit resolves what an online one did. Regenerable: losing them costs `status` its
|
|
359
|
+
* comparison until the next push or pull, never a wrong answer.
|
|
360
|
+
*/
|
|
361
|
+
export function keptModels(siteDir, backend) {
|
|
362
|
+
const models = readSyncCacheFile(siteDir, backend).models
|
|
363
|
+
return models && typeof models === 'object' ? models : {}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** Keep Models read from this backend, beside the ones already kept. */
|
|
367
|
+
export function keepModels(siteDir, backend, models) {
|
|
368
|
+
if (!models || !Object.keys(models).length) return
|
|
369
|
+
updateSyncCache(siteDir, backend, { models: { ...keptModels(siteDir, backend), ...models } })
|
|
370
|
+
}
|
|
371
|
+
|
|
341
372
|
// `.uniweb/backend-cache.json` — what a backend had and what we last sent it, so the
|
|
342
373
|
// next transfer can skip work. Gitignored, per clone, safe to delete.
|
|
343
374
|
//
|
|
@@ -653,6 +684,55 @@ export function readAppliedInjections(siteDir, backend) {
|
|
|
653
684
|
return readMap(siteDir, backend, 'applied')
|
|
654
685
|
}
|
|
655
686
|
|
|
687
|
+
/**
|
|
688
|
+
* ⭐ THE REGISTERED FOUNDATION A CLONE KEEPS. A site whose foundation is a catalog ref has no build of
|
|
689
|
+
* it in the project, and the section types' `data:` it declares are what type a query named for a
|
|
690
|
+
* data key — `team:` read as `@acme/member` (`typed_by_data_key`). Read once from the backend,
|
|
691
|
+
* through the site, and kept under `.uniweb/` (`@uniweb/build`'s `registered-foundation.js`), where
|
|
692
|
+
* the build, the push and the pull read it; a registered version never changes, so a kept copy is
|
|
693
|
+
* never read again.
|
|
694
|
+
*
|
|
695
|
+
* Null when the foundation is not a catalog ref, the site is not on this backend yet, or the read
|
|
696
|
+
* fails: nothing is then typed by it — a pull writes such a query's schema out, and a push sends
|
|
697
|
+
* what that says — so both go on.
|
|
698
|
+
*/
|
|
699
|
+
export async function ensureRegisteredFoundation({ client, siteDir, siteUuid, ref }) {
|
|
700
|
+
if (!parseCatalogRef(ref)) return null
|
|
701
|
+
const kept = readRegisteredFoundation(siteDir, ref)
|
|
702
|
+
if (kept) return kept
|
|
703
|
+
const reply = siteUuid ? await client.readRegisteredFoundation(siteUuid, ref) : null
|
|
704
|
+
if (!reply) return null
|
|
705
|
+
// ⛔ ONLY WHAT WE READ — the section types. The reply carries more than we asked for, a serve
|
|
706
|
+
// location among it, and a serve location is decided at publish and read from the response that
|
|
707
|
+
// carries it, never kept. Until 2026-09-26 the whole reply was kept.
|
|
708
|
+
const sectionTypes = { schema: reply.schema }
|
|
709
|
+
writeRegisteredFoundation(siteDir, ref, sectionTypes)
|
|
710
|
+
return sectionTypes
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// The Sections whose declared keys a push reads before it sends an optional one.
|
|
714
|
+
const DEPLOYMENT_FIELD_LISTS = ['queryFields', 'pageFields', 'settingsFields']
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* The keys this deployment's `queries`, page and `settings` Sections declare, as it says so itself —
|
|
718
|
+
* `GET /dev/config` → `siteContent.queryFields` / `pageFields` / `settingsFields` — so a push sends
|
|
719
|
+
* an optional key only to a deployment that takes it (`typed_by_data_key`; `og_title` and
|
|
720
|
+
* `og_description`): one that does not declare a key refuses a push carrying it. Offline — a dry
|
|
721
|
+
* run, `--output` — the lists the last push to this backend banked, so the package is the one that
|
|
722
|
+
* push would build.
|
|
723
|
+
*
|
|
724
|
+
* Only the lists in hand are returned, as emit options: with none, nothing optional is sent, which
|
|
725
|
+
* every deployment accepts.
|
|
726
|
+
*/
|
|
727
|
+
export async function deploymentFields({ client, siteDir, offline = false }) {
|
|
728
|
+
const source = offline
|
|
729
|
+
? readAppliedInjections(siteDir, client.origin)
|
|
730
|
+
: (await client.discover())?.siteContent
|
|
731
|
+
const out = {}
|
|
732
|
+
for (const name of DEPLOYMENT_FIELD_LISTS) if (Array.isArray(source?.[name])) out[name] = source[name]
|
|
733
|
+
return out
|
|
734
|
+
}
|
|
735
|
+
|
|
656
736
|
/**
|
|
657
737
|
* Bank the content hashes and the injections that produced them. ⛔ ONE call, both
|
|
658
738
|
* maps: they describe the same document, so writing either alone leaves the cache
|
|
@@ -1430,7 +1510,7 @@ export async function probeUnpushed(siteDir, { backend = null, sendAll = false }
|
|
|
1430
1510
|
*/
|
|
1431
1511
|
async function comparisonEmit(
|
|
1432
1512
|
siteDir,
|
|
1433
|
-
{ backend = null, priorHashes = {}, sendAll = false, declarations = null } = {}
|
|
1513
|
+
{ backend = null, priorHashes = {}, sendAll = false, declarations = null, fields = null } = {}
|
|
1434
1514
|
) {
|
|
1435
1515
|
const applied = readAppliedInjections(siteDir, backend)
|
|
1436
1516
|
// ⛔ Per backend: an asset id is minted by one and means nothing to another, so a
|
|
@@ -1446,10 +1526,13 @@ async function comparisonEmit(
|
|
|
1446
1526
|
// of its Models here, and nothing is banked.
|
|
1447
1527
|
resolveModel: declarations
|
|
1448
1528
|
? async (name) => declarations.get(name) ?? null
|
|
1449
|
-
: makeModelResolver({ client: null, offline: true }),
|
|
1529
|
+
: makeModelResolver({ client: null, offline: true, siteDir, backend }),
|
|
1450
1530
|
priorHashes,
|
|
1451
1531
|
sendAll,
|
|
1452
1532
|
...applied,
|
|
1533
|
+
// The deployment's own answer (`deploymentFields`), when the caller has just read it, over what
|
|
1534
|
+
// the last push banked.
|
|
1535
|
+
...(fields || {}),
|
|
1453
1536
|
...(Object.keys(queryUuids).length ? { queryUuids } : {}),
|
|
1454
1537
|
...(Object.keys(assetIds).length ? { assetIds } : {})
|
|
1455
1538
|
// ⛔ No `org`: a site's `@/x` refs resolve into its FOUNDATION's scope, which the
|
|
@@ -1484,9 +1567,12 @@ async function comparisonEmit(
|
|
|
1484
1567
|
* @param {Map<string,object>} [opts.declarations] - the Models the pull read, by name — so a
|
|
1485
1568
|
* site whose foundation is not in the project (a clone's) can be re-banked offline
|
|
1486
1569
|
*/
|
|
1487
|
-
export async function rebankSyncHashes(siteDir, backend = null, { recordDocs, declarations } = {}) {
|
|
1488
|
-
|
|
1489
|
-
|
|
1570
|
+
export async function rebankSyncHashes(siteDir, backend = null, { recordDocs, declarations, fields = null } = {}) {
|
|
1571
|
+
// ⛔ With the keys this deployment's Sections take, when the caller has them (`deploymentFields`):
|
|
1572
|
+
// they shape the document the next push sends (`typed_by_data_key`, `og_title`), so a hash banked
|
|
1573
|
+
// without them marks a clone's first push as changed. Until 2026-09-26 a pull banked none.
|
|
1574
|
+
const pkg = await comparisonEmit(siteDir, { backend, sendAll: true, declarations, fields })
|
|
1575
|
+
writeSyncCache(siteDir, backend, onlyWhatThePullTook(siteDir, backend, pkg, recordDocs), pkg.applied || {})
|
|
1490
1576
|
// ⭐ And the identity of the records' list items, from the documents the pull just took:
|
|
1491
1577
|
// the files it wrote hold the stored items in stored order, so the emit over them pairs
|
|
1492
1578
|
// with each document item for item.
|
|
@@ -1505,6 +1591,44 @@ export async function rebankSyncHashes(siteDir, backend = null, { recordDocs, de
|
|
|
1505
1591
|
return Object.keys(pkg.hashes || {}).length
|
|
1506
1592
|
}
|
|
1507
1593
|
|
|
1594
|
+
/**
|
|
1595
|
+
* The hashes a pull may bank: the tree's, except for a record the pull did not take.
|
|
1596
|
+
*
|
|
1597
|
+
* ⛔ ONLY WHAT CAME FROM THE BACKEND IS AGREED. A record on disk that the backend did not
|
|
1598
|
+
* hand back — written since the last push, or one a push had refused — is not in sync
|
|
1599
|
+
* because a pull ran. Until 2026-09-26 it was banked with the rest, so every later push
|
|
1600
|
+
* skipped it and `uniweb status` said "in sync": a record written before a `uniweb sync`
|
|
1601
|
+
* (which pulls, then pushes) never reached the backend. One this backend never accepted —
|
|
1602
|
+
* no identity from it — is always sent: a hash it has banked can only be that mistake's.
|
|
1603
|
+
* One it did accept keeps what the last push banked. The folder keeps its hash too while
|
|
1604
|
+
* it places such a record. A pull that took no records (`--no-records`, a 304) leaves
|
|
1605
|
+
* every record's hash as it was.
|
|
1606
|
+
*
|
|
1607
|
+
* @param {object[]|undefined} recordDocs - the record documents the pull took
|
|
1608
|
+
*/
|
|
1609
|
+
function onlyWhatThePullTook(siteDir, backend, pkg, recordDocs) {
|
|
1610
|
+
const hashes = { ...(pkg.hashes || {}) }
|
|
1611
|
+
const prior = readSyncCache(siteDir, backend)
|
|
1612
|
+
const keep = (key) => {
|
|
1613
|
+
if (Object.hasOwn(prior, key)) hashes[key] = prior[key]
|
|
1614
|
+
else delete hashes[key]
|
|
1615
|
+
}
|
|
1616
|
+
const taken = new Set((recordDocs || []).map((d) => d?.$uuid).filter(Boolean))
|
|
1617
|
+
const recordUuids = readBackendState(siteDir, backend).records || {}
|
|
1618
|
+
let notTaken = 0
|
|
1619
|
+
for (const entry of pkg.records?.index || []) {
|
|
1620
|
+
if (entry.kind === 'folder') continue // the folder itself — held below while it places one
|
|
1621
|
+
const theirs = entry.ownId ? recordUuids[entry.ownId] : null
|
|
1622
|
+
if (theirs && taken.has(theirs)) continue
|
|
1623
|
+
notTaken++
|
|
1624
|
+
const key = `${entry.model} ${entry.id}`
|
|
1625
|
+
if (theirs) keep(key)
|
|
1626
|
+
else delete hashes[key]
|
|
1627
|
+
}
|
|
1628
|
+
if (notTaken) for (const key of Object.keys(hashes)) if (key.startsWith('@uniweb/folder ')) keep(key)
|
|
1629
|
+
return hashes
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1508
1632
|
/**
|
|
1509
1633
|
* Re-bank the hashes of the records a push sent naming a record it created — `keys`,
|
|
1510
1634
|
* the emit's `namesNew` — as the next push will hash them, and fingerprint their list
|
|
@@ -1714,6 +1838,18 @@ export async function pushSyncPackages({
|
|
|
1714
1838
|
if (problem.detail) note(String(problem.detail))
|
|
1715
1839
|
return null
|
|
1716
1840
|
}
|
|
1841
|
+
// ⭐ A RECORD KEEPS ITS TYPE. A record file moved to another type's folder keeps its `$uuid`, and
|
|
1842
|
+
// the backend refuses it as that type (`type_changed`, naming the record and both types), before
|
|
1843
|
+
// writing anything. Storing it as the new type makes it a new record.
|
|
1844
|
+
if (problem?.reason === 'type_changed') {
|
|
1845
|
+
error(`${label} push refused — a stored record would change its type.`)
|
|
1846
|
+
if (problem.detail) note(String(problem.detail))
|
|
1847
|
+
note(
|
|
1848
|
+
'A record keeps its type. To store it as the new type, make it a new record: delete the ' +
|
|
1849
|
+
'`$uuid` line from its file, then push again.'
|
|
1850
|
+
)
|
|
1851
|
+
return null
|
|
1852
|
+
}
|
|
1717
1853
|
if (problem?.reason === 'stale_base') {
|
|
1718
1854
|
error(
|
|
1719
1855
|
`${label} push refused — the backend has newer content than your last pull.`
|
|
@@ -1798,15 +1934,14 @@ export async function pushSyncPackages({
|
|
|
1798
1934
|
` deleted there → uniweb forget --backend ${client.origin}, then push again: it creates a NEW site`
|
|
1799
1935
|
)
|
|
1800
1936
|
note('Deleting this folder removes only your local copy, either way.')
|
|
1801
|
-
} else if (res.status === 409) {
|
|
1802
|
-
//
|
|
1803
|
-
//
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
)
|
|
1937
|
+
} else if (res.status === 409 && (problem?.title || problem?.detail)) {
|
|
1938
|
+
// ⛔ A 409 names its own cause — a reference the store refuses, a rule of the record's type,
|
|
1939
|
+
// an append-only section — so it is shown as the backend put it, not explained by us. Until
|
|
1940
|
+
// 2026-09-26 every one was explained as "this site's record structure is already established
|
|
1941
|
+
// … push it as a new site", a rule the backend no longer has, and it misled over a refused
|
|
1942
|
+
// reference. (`stale_base` and `item_uuid_conflict` have their own explanations, above.)
|
|
1943
|
+
note([problem.title, problem.detail].filter(Boolean).join(' — '))
|
|
1944
|
+
return null
|
|
1810
1945
|
}
|
|
1811
1946
|
if (body) note(body.slice(0, 800))
|
|
1812
1947
|
return null
|
package/src/commands/clone.js
CHANGED
|
@@ -426,6 +426,12 @@ export async function clone(args = [], deps = {}) {
|
|
|
426
426
|
|
|
427
427
|
const pullExtra = []
|
|
428
428
|
if (noRecords) pullExtra.push('--no-records')
|
|
429
|
+
// The workspace this clone was told to work in: the pull is another process, and asks the same
|
|
430
|
+
// question. ⛔ Until 2026-09-26 it was not told, so it used the login's — or, with none chosen,
|
|
431
|
+
// refused: "none is chosen". A workspace from the login or the environment reaches it by itself.
|
|
432
|
+
const orgFlag = flagValue(args, '--org')
|
|
433
|
+
if (orgFlag) pullExtra.push('--org', orgFlag)
|
|
434
|
+
if (args.includes('--personal')) pullExtra.push('--personal')
|
|
429
435
|
|
|
430
436
|
if (deps.skipPull) {
|
|
431
437
|
note('Skipping pull (test mode).')
|
package/src/commands/dev.js
CHANGED
|
@@ -24,6 +24,14 @@
|
|
|
24
24
|
* site by default (mirrors the `pnpm dev` shortcut `uniweb create` writes).
|
|
25
25
|
* Use `--site` to pick a different one without editing the root scripts.
|
|
26
26
|
*
|
|
27
|
+
* A site whose foundation is a catalog ref — every clone — has no build of it, and
|
|
28
|
+
* only the backend the site is on can say where that version is served. So `dev`
|
|
29
|
+
* asks it, through the site, and hands the answer to the dev server it starts in its
|
|
30
|
+
* environment (`UNIWEB_PREVIEW`), with the backend it asked, which is also where the
|
|
31
|
+
* site's kept media URLs are fetched from. Nothing is written: `site.yml` keeps the
|
|
32
|
+
* ref and the round trip never sees the URL. A backend that does not say is an
|
|
33
|
+
* ordinary answer — `dev` says so and starts nothing (`backend/site-preview.js`).
|
|
34
|
+
*
|
|
27
35
|
* Implementation: shells out to the package manager that invoked the CLI
|
|
28
36
|
* (detected via npm_config_user_agent), running the workspace-filtered
|
|
29
37
|
* dev command (`pnpm --filter <name> dev` or `npm -w <name> run dev`).
|
|
@@ -121,12 +129,32 @@ export async function dev(args = []) {
|
|
|
121
129
|
const [bin, ...rest] = command.split(' ')
|
|
122
130
|
const sitePath = join(rootDir, site.path)
|
|
123
131
|
|
|
132
|
+
// ⭐ A SITE WHOSE FOUNDATION IS A CATALOG REF — a clone — is previewed with what the backend
|
|
133
|
+
// it is on says: where that version is served. Asked here, for this dev server alone, and
|
|
134
|
+
// handed to it in its environment; nothing is written (`backend/site-preview.js`). Any other
|
|
135
|
+
// site needs nothing from a backend, and this asks nothing.
|
|
136
|
+
const { readSitePreview } = await import('../backend/site-preview.js')
|
|
137
|
+
const asked = await readSitePreview({ siteDir: sitePath, args })
|
|
138
|
+
if (asked?.refused) {
|
|
139
|
+
console.error(`${RED}✗${RESET} ${asked.refused[0]}`)
|
|
140
|
+
for (const line of asked.refused.slice(1)) console.error(` ${line}`)
|
|
141
|
+
process.exit(1)
|
|
142
|
+
}
|
|
143
|
+
let env = process.env
|
|
144
|
+
if (asked?.preview) {
|
|
145
|
+
const { PREVIEW_ENV, encodePreview } = await import('@uniweb/build/site')
|
|
146
|
+
env = { ...process.env, [PREVIEW_ENV]: encodePreview(asked.preview) }
|
|
147
|
+
console.error(
|
|
148
|
+
`${DIM}→ Previewing with ${asked.preview.foundation.ref}, served by ${asked.preview.backend}${RESET}`
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
124
152
|
console.error(
|
|
125
153
|
`${DIM}→ ${command}${RESET} ${DIM}(site: ${site.name}, dir: ${sitePath})${RESET}`
|
|
126
154
|
)
|
|
127
155
|
console.error('')
|
|
128
156
|
|
|
129
|
-
const child = spawn(bin, rest, { cwd: rootDir, stdio: 'inherit' })
|
|
157
|
+
const child = spawn(bin, rest, { cwd: rootDir, stdio: 'inherit', env })
|
|
130
158
|
child.on('close', (code) => process.exit(code ?? 0))
|
|
131
159
|
child.on('error', (err) => {
|
|
132
160
|
console.error(`${RED}✗${RESET} Failed to start dev server: ${err.message}`)
|
package/src/commands/publish.js
CHANGED
|
@@ -87,6 +87,8 @@ import {
|
|
|
87
87
|
ensureItemUuids,
|
|
88
88
|
readFolderItemUuids,
|
|
89
89
|
readRecordItemUuids,
|
|
90
|
+
deploymentFields,
|
|
91
|
+
ensureRegisteredFoundation,
|
|
90
92
|
recoverUnbankedIdentity,
|
|
91
93
|
ensureSiteExists,
|
|
92
94
|
refuseUnsendableRecords,
|
|
@@ -339,9 +341,11 @@ export async function publish(args = []) {
|
|
|
339
341
|
// (2026-08-30). Restoring a reader for it would re-create a check that cannot fail
|
|
340
342
|
// while implying a capability that was never negotiable.
|
|
341
343
|
//
|
|
342
|
-
// Discovery is not consulted
|
|
343
|
-
// (`
|
|
344
|
-
//
|
|
344
|
+
// Discovery is not consulted for a gate: the CLI reads the key lists under `siteContent`
|
|
345
|
+
// (`queryFields`, `pageFields`, `settingsFields`), which say what the push below may SEND
|
|
346
|
+
// (`deploymentFields`), never whether to publish. (`delivery.siteSubscriptionRequired`
|
|
347
|
+
// was deleted 2026-08-30; this said it was still read, after the site create, until
|
|
348
|
+
// 2026-09-26.)
|
|
345
349
|
|
|
346
350
|
// ⛔ NOTHING about a runtime is sent from here. `site.yml::runtime` was a
|
|
347
351
|
// vestigial prop and is no longer read [Diego, 2026-08-22]; `?runtime=` is no
|
|
@@ -550,7 +554,7 @@ export async function publish(args = []) {
|
|
|
550
554
|
}
|
|
551
555
|
|
|
552
556
|
// Non-local @std/registry Model schemas resolve through the backend (same as push).
|
|
553
|
-
const resolveModel = makeModelResolver({ client, offline: false })
|
|
557
|
+
const resolveModel = makeModelResolver({ client, offline: false, siteDir })
|
|
554
558
|
|
|
555
559
|
// ⛔ AN EMPTY RECORDS DIRECTORY REMOVES. It is the one path where an ordinary act
|
|
556
560
|
// is destructive — a directory emptied by accident, or kept with only a
|
|
@@ -961,8 +965,19 @@ export async function publish(args = []) {
|
|
|
961
965
|
// edited since this clone last synced, the push is refused rather than
|
|
962
966
|
// overwriting them, and nothing goes live. `--force` drops the precondition.
|
|
963
967
|
const forced = args.includes('--force')
|
|
968
|
+
const fields = await deploymentFields({ client, siteDir })
|
|
969
|
+
// A foundation named by catalog ref — a clone's: its kept registered version types a query named
|
|
970
|
+
// for a data key (`ensureRegisteredFoundation`).
|
|
971
|
+
await ensureRegisteredFoundation({
|
|
972
|
+
client,
|
|
973
|
+
siteDir,
|
|
974
|
+
siteUuid: readBackendState(siteDir, client.origin).site?.uuid,
|
|
975
|
+
ref: siteYml?.foundation
|
|
976
|
+
})
|
|
964
977
|
const emitOptions = {
|
|
965
978
|
backend: client.origin,
|
|
979
|
+
// The keys this deployment's Sections take — see deploymentFields.
|
|
980
|
+
...fields,
|
|
966
981
|
...(declaration.declare ? {} : { declareServices: false }),
|
|
967
982
|
// Placement identity for the folder — see writeFolderItemUuids.
|
|
968
983
|
folderItemUuids: readFolderItemUuids(siteDir, client.origin),
|
package/src/commands/pull.js
CHANGED
|
@@ -60,7 +60,9 @@ import {
|
|
|
60
60
|
writeFileSync,
|
|
61
61
|
mkdirSync,
|
|
62
62
|
mkdtempSync,
|
|
63
|
-
rmSync
|
|
63
|
+
rmSync,
|
|
64
|
+
readdirSync,
|
|
65
|
+
statSync
|
|
64
66
|
} from 'node:fs'
|
|
65
67
|
import { tmpdir } from 'node:os'
|
|
66
68
|
import { createHash } from 'node:crypto'
|
|
@@ -87,6 +89,8 @@ import {
|
|
|
87
89
|
} from '../utils/pull-written.js'
|
|
88
90
|
import {
|
|
89
91
|
makeModelResolver,
|
|
92
|
+
ensureRegisteredFoundation,
|
|
93
|
+
deploymentFields,
|
|
90
94
|
rebankSyncHashes,
|
|
91
95
|
writeQueryUuids,
|
|
92
96
|
mergeBaseVersions,
|
|
@@ -427,6 +431,27 @@ function readManifestTokens(buf) {
|
|
|
427
431
|
* @param {object} [deps] - injectable seams for testing: `fetch` (default global
|
|
428
432
|
* fetch), `resolveSiteDir`, `getToken` (skip auth).
|
|
429
433
|
*/
|
|
434
|
+
// Every file under `roots` (site-relative paths, files or directories), site-relative.
|
|
435
|
+
function filesUnder(siteDir, roots) {
|
|
436
|
+
const out = []
|
|
437
|
+
const walk = (rel) => {
|
|
438
|
+
const abs = join(siteDir, rel)
|
|
439
|
+
let stat
|
|
440
|
+
try {
|
|
441
|
+
stat = statSync(abs)
|
|
442
|
+
} catch {
|
|
443
|
+
return
|
|
444
|
+
}
|
|
445
|
+
if (stat.isDirectory()) {
|
|
446
|
+
for (const name of readdirSync(abs)) walk(join(rel, name))
|
|
447
|
+
} else if (stat.isFile()) {
|
|
448
|
+
out.push(rel)
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
for (const root of roots) walk(root)
|
|
452
|
+
return out
|
|
453
|
+
}
|
|
454
|
+
|
|
430
455
|
/**
|
|
431
456
|
* Refuse a pull that would overwrite unsaved work. Returns a result object to
|
|
432
457
|
* return from `pull`, or null to proceed.
|
|
@@ -440,7 +465,12 @@ async function checkWorkingTree(siteDir, args) {
|
|
|
440
465
|
let dirty = uncommittedUnder(siteDir, roots)
|
|
441
466
|
|
|
442
467
|
if (dirty === null) {
|
|
443
|
-
// Not a git work tree. Nothing to fall back on if this goes wrong
|
|
468
|
+
// Not a git work tree. Nothing to fall back on if this goes wrong — unless nothing here is the
|
|
469
|
+
// author's: every file under pull's roots is one a pull, or the clone that made this site, wrote
|
|
470
|
+
// and nothing has changed since (`pull-written.js`). ⛔ Until 2026-09-26 a fresh clone's own pull,
|
|
471
|
+
// which runs with no terminal to ask, refused here, and `uniweb clone` could not finish.
|
|
472
|
+
const written = readWritten(siteDir)
|
|
473
|
+
if (filesUnder(siteDir, roots).every((f) => isPullOutput(siteDir, f, written))) return null
|
|
444
474
|
if (isNonInteractive(args)) {
|
|
445
475
|
error('Refusing to pull: this site is not in a git repository.')
|
|
446
476
|
note(
|
|
@@ -685,7 +715,7 @@ export async function pull(args = [], deps = {}) {
|
|
|
685
715
|
command: 'Pulling'
|
|
686
716
|
})
|
|
687
717
|
// One reader of Models for the whole pull, so a Model is read once whichever lane asks first.
|
|
688
|
-
const readModel = makeModelResolver({ client })
|
|
718
|
+
const readModel = makeModelResolver({ client, siteDir })
|
|
689
719
|
|
|
690
720
|
// ⭐ No scope check any more — and none is needed. This project's identity on
|
|
691
721
|
// `client.origin` is read from that origin's own section of sync.json, so a
|
|
@@ -957,6 +987,11 @@ export async function pull(args = [], deps = {}) {
|
|
|
957
987
|
// unreadable here
|
|
958
988
|
}
|
|
959
989
|
}
|
|
990
|
+
// A foundation named by catalog ref — a clone's: keep its registered version before the
|
|
991
|
+
// queries are written, since its section types are what type a query named for a data key.
|
|
992
|
+
if (!keepAuthoredFoundation) {
|
|
993
|
+
await ensureRegisteredFoundation({ client, siteDir, siteUuid: siteContentUuid, ref: siteDoc?.info?.foundation })
|
|
994
|
+
}
|
|
960
995
|
const report = siteContentDocumentToProject({
|
|
961
996
|
document: siteDoc,
|
|
962
997
|
siteRoot: siteDir,
|
|
@@ -1128,7 +1163,8 @@ export async function pull(args = [], deps = {}) {
|
|
|
1128
1163
|
}
|
|
1129
1164
|
await rebankSyncHashes(siteDir, client.origin, {
|
|
1130
1165
|
recordDocs: pulledRecordDocs,
|
|
1131
|
-
...(declarations.size ? { declarations } : {})
|
|
1166
|
+
...(declarations.size ? { declarations } : {}),
|
|
1167
|
+
fields: await deploymentFields({ client, siteDir })
|
|
1132
1168
|
})
|
|
1133
1169
|
} catch (err) {
|
|
1134
1170
|
note(`! could not re-bank the sync cache: ${err.message}`)
|
package/src/commands/push.js
CHANGED
|
@@ -83,6 +83,8 @@ import {
|
|
|
83
83
|
readFolderItemUuids,
|
|
84
84
|
readQueryUuids,
|
|
85
85
|
readRecordItemUuids,
|
|
86
|
+
deploymentFields,
|
|
87
|
+
ensureRegisteredFoundation,
|
|
86
88
|
recoverUnbankedIdentity,
|
|
87
89
|
ensureItemUuids,
|
|
88
90
|
refuseUnsendableRecords,
|
|
@@ -405,7 +407,7 @@ export async function push(args = [], deps = {}) {
|
|
|
405
407
|
// Placement identity for the folder — see writeFolderItemUuids.
|
|
406
408
|
folderItemUuids: readFolderItemUuids(siteDir, client.origin),
|
|
407
409
|
...(foundationDir ? { foundationDir } : {}),
|
|
408
|
-
resolveModel: makeModelResolver({ client, offline: false })
|
|
410
|
+
resolveModel: makeModelResolver({ client, offline: false, siteDir })
|
|
409
411
|
})
|
|
410
412
|
mediaRefs = probe.localAssets || []
|
|
411
413
|
refusals = probe.refusals || []
|
|
@@ -498,6 +500,21 @@ export async function push(args = [], deps = {}) {
|
|
|
498
500
|
output || dryRun
|
|
499
501
|
? readItemUuids(siteDir, client.origin)
|
|
500
502
|
: await ensureItemUuids({ client, siteDir, note })
|
|
503
|
+
const fields = await deploymentFields({
|
|
504
|
+
client,
|
|
505
|
+
siteDir,
|
|
506
|
+
offline: Boolean(output) || dryRun
|
|
507
|
+
})
|
|
508
|
+
// A foundation named by catalog ref — a clone's: its kept registered version types a query named
|
|
509
|
+
// for a data key (`ensureRegisteredFoundation`). Offline, only what is already kept.
|
|
510
|
+
if (!output && !dryRun) {
|
|
511
|
+
await ensureRegisteredFoundation({
|
|
512
|
+
client,
|
|
513
|
+
siteDir,
|
|
514
|
+
siteUuid: readBackendState(siteDir, client.origin).site?.uuid,
|
|
515
|
+
ref: siteYml?.foundation
|
|
516
|
+
})
|
|
517
|
+
}
|
|
501
518
|
const emitOptions = {
|
|
502
519
|
backend: client.origin,
|
|
503
520
|
// Placement identity for the folder — see writeFolderItemUuids.
|
|
@@ -505,12 +522,15 @@ export async function push(args = [], deps = {}) {
|
|
|
505
522
|
// Identity for the `queries` section — see readQueryUuids. Keyed by
|
|
506
523
|
// name, because a declaration has no file for a path-keyed map to hold.
|
|
507
524
|
queryUuids: readQueryUuids(siteDir, client.origin),
|
|
525
|
+
// The keys this deployment's Sections take — see deploymentFields.
|
|
526
|
+
...fields,
|
|
508
527
|
// Identity for the records' list items — see readRecordItemUuids.
|
|
509
528
|
recordItemUuids: readRecordItemUuids(siteDir, client.origin),
|
|
510
529
|
...(foundationDir ? { foundationDir } : {}),
|
|
511
530
|
resolveModel: makeModelResolver({
|
|
512
531
|
client,
|
|
513
|
-
offline: Boolean(output) || dryRun
|
|
532
|
+
offline: Boolean(output) || dryRun,
|
|
533
|
+
siteDir
|
|
514
534
|
}),
|
|
515
535
|
priorHashes,
|
|
516
536
|
sendAll,
|
package/src/framework-index.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-09-
|
|
3
|
+
"generatedAt": "2026-09-27T03:02:47.415Z",
|
|
4
4
|
"packages": {
|
|
5
5
|
"@uniweb/api": {
|
|
6
6
|
"version": "0.6.3",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
]
|
|
11
11
|
},
|
|
12
12
|
"@uniweb/build": {
|
|
13
|
-
"version": "0.64.
|
|
13
|
+
"version": "0.64.2",
|
|
14
14
|
"path": "framework/build",
|
|
15
15
|
"deps": [
|
|
16
16
|
"@uniweb/content-reader",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"deps": []
|
|
36
36
|
},
|
|
37
37
|
"@uniweb/core": {
|
|
38
|
-
"version": "0.30.
|
|
38
|
+
"version": "0.30.1",
|
|
39
39
|
"path": "framework/core",
|
|
40
40
|
"deps": [
|
|
41
41
|
"@uniweb/semantic-parser",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
]
|
|
84
84
|
},
|
|
85
85
|
"@uniweb/runtime": {
|
|
86
|
-
"version": "0.27.
|
|
86
|
+
"version": "0.27.6",
|
|
87
87
|
"path": "framework/runtime",
|
|
88
88
|
"deps": [
|
|
89
89
|
"@uniweb/core",
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
"deps": []
|
|
97
97
|
},
|
|
98
98
|
"@uniweb/schemas": {
|
|
99
|
-
"version": "0.7.
|
|
99
|
+
"version": "0.7.2",
|
|
100
100
|
"path": "framework/schemas",
|
|
101
101
|
"deps": []
|
|
102
102
|
},
|