uniweb 0.81.1 → 0.81.3
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 +5 -5
- package/partials/agents.md +2 -0
- package/src/backend/client.js +8 -2
- package/src/backend/payment-handoff.js +151 -13
- package/src/commands/publish.js +25 -8
- package/src/commands/pull.js +5 -1
- package/src/framework-index.json +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uniweb",
|
|
3
|
-
"version": "0.81.
|
|
3
|
+
"version": "0.81.3",
|
|
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/runtime": "^0.28.10",
|
|
45
44
|
"@uniweb/content-writer": "^0.3.4",
|
|
46
45
|
"@uniweb/schemas": "^0.10.2",
|
|
47
|
-
"@uniweb/
|
|
46
|
+
"@uniweb/runtime": "^0.28.10",
|
|
48
47
|
"@uniweb/kit": "^0.19.15",
|
|
48
|
+
"@uniweb/core": "^0.36.0",
|
|
49
49
|
"@uniweb/semantic-parser": "^1.5.1"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@uniweb/content-reader": "^1.2.8",
|
|
53
52
|
"@uniweb/semantic-parser": "^1.5.1",
|
|
54
|
-
"@uniweb/
|
|
53
|
+
"@uniweb/content-reader": "^1.2.8",
|
|
54
|
+
"@uniweb/build": "^0.75.1"
|
|
55
55
|
},
|
|
56
56
|
"peerDependenciesMeta": {
|
|
57
57
|
"@uniweb/build": {
|
package/partials/agents.md
CHANGED
|
@@ -2761,6 +2761,8 @@ locales/freeform/es/records/article/x.md # records work too
|
|
|
2761
2761
|
|
|
2762
2762
|
These are **body only — no frontmatter**; params and config still come from the source section. `uniweb i18n init-freeform es pages/about hero` creates one pre-filled and records a source hash, so `uniweb i18n status --freeform` can tell you when the original moved on (`update-hash` to acknowledge). `move`, `rename`, and `prune --freeform` keep them aligned when pages get reorganized.
|
|
2763
2763
|
|
|
2764
|
+
**A tagged data block translates by the shape its key declares** in the component's `meta.js` `data:` — a data schema or an inline field map: its text fields, never an enum, a URL or a `translatable: false` field. A key declared `{}` leaves its blocks to a heuristic that offers any string whose field name and value don't look structural, so a `style: display-black` naming one of your component's looks gets translated too. Declare the shape of every block a translator will see — `{ name: 'string', style: { type: 'string', enum: [...] } }`.
|
|
2765
|
+
|
|
2764
2766
|
**Records translate in the same `extract` run**, into their own manifest at `locales/records/manifest.json` — keyed by the RECORD, so a record translated once is found by every query that returns it. A record whose query has a data schema translates exactly the fields the schema says are text — never an enum, a URL, a date, a number, a file, a reference or a `translatable: false` field — which are the fields a push carries in each language; mark a string that is a code or a key `translatable: false` and no translator is asked for it. An excerpt the build derives is re-derived from each language's body.
|
|
2765
2767
|
|
|
2766
2768
|
**Component side.** Nothing to do: `content.title` arrives in the active language. The one thing a foundation builds is a switcher.
|
package/src/backend/client.js
CHANGED
|
@@ -748,8 +748,14 @@ export class BackendClient {
|
|
|
748
748
|
* @param {string[]} [opts.languages]
|
|
749
749
|
* @returns {Promise<Response>}
|
|
750
750
|
*/
|
|
751
|
-
async publishSite(uuid, { languages } = {}) {
|
|
752
|
-
|
|
751
|
+
async publishSite(uuid, { languages, confirm, unattended = false } = {}) {
|
|
752
|
+
// `confirm`: the token of a confirm the backend offered on a refused publish, sent back as it
|
|
753
|
+
// came, to settle a change to the site's plan and publish in one call. `unattended`: no one
|
|
754
|
+
// answered it — `--yes` did, which the backend settles for a reduction and nothing else.
|
|
755
|
+
const query = confirm
|
|
756
|
+
? `?confirm=${encodeURIComponent(confirm)}${unattended ? '&unattended=true' : ''}`
|
|
757
|
+
: ''
|
|
758
|
+
return this.request(`/dev/site/publish/${encodeURIComponent(uuid)}${query}`, {
|
|
753
759
|
method: 'POST',
|
|
754
760
|
...(languages ? { body: JSON.stringify({ languages }) } : {})
|
|
755
761
|
})
|
|
@@ -91,8 +91,8 @@ const OPENABLE = /^https?:\/\//i
|
|
|
91
91
|
* @param {string} [o.contentType] - the response's content-type header
|
|
92
92
|
* @param {string} [o.body] - the raw response body
|
|
93
93
|
* @returns {{ kind: 'not-payment' }
|
|
94
|
-
* | { kind: 'settle', url: string, handle: string|null, reason: string, message: string|null }
|
|
95
|
-
* | { kind: 'stop', reason: string|null, message: string|null }}
|
|
94
|
+
* | { kind: 'settle', url: string, handle: string|null, reason: string, message: string|null, confirm: { prompt: string, token: string, reduction: boolean }|null }
|
|
95
|
+
* | { kind: 'stop', reason: string|null, message: string|null, confirm: { prompt: string, token: string, reduction: boolean }|null }}
|
|
96
96
|
*/
|
|
97
97
|
export function readPaymentRefusal({ status, contentType = '', body = '' } = {}) {
|
|
98
98
|
if (status !== 402) return { kind: 'not-payment' }
|
|
@@ -134,7 +134,17 @@ export function readPaymentRefusal({ status, contentType = '', body = '' } = {})
|
|
|
134
134
|
// problem body carries the number, so the two are not distinguishable by their
|
|
135
135
|
// fields. Reading a URL out of a body that never promised this envelope is how
|
|
136
136
|
// a `settle` gets synthesised from something that is not a refusal at all.
|
|
137
|
-
if (!isProblem) return { kind: 'stop', reason, message }
|
|
137
|
+
if (!isProblem) return { kind: 'stop', reason, message, confirm: null }
|
|
138
|
+
|
|
139
|
+
// ⭐ A CHANGE THE TERMINAL MAY CONFIRM (since 2026-10-03): `{ prompt, token, reduction }`
|
|
140
|
+
// beside the door, when everything the publish owes is a change to a plan the site already has.
|
|
141
|
+
// `prompt` is the backend's sentence, shown verbatim; `token` goes back as it came. Read only
|
|
142
|
+
// whole — a confirm missing either half is not one.
|
|
143
|
+
const confirm =
|
|
144
|
+
problem.confirm && typeof problem.confirm.prompt === 'string' && problem.confirm.prompt &&
|
|
145
|
+
typeof problem.confirm.token === 'string' && problem.confirm.token
|
|
146
|
+
? { prompt: problem.confirm.prompt, token: problem.confirm.token, reduction: problem.confirm.reduction === true }
|
|
147
|
+
: null
|
|
138
148
|
|
|
139
149
|
// The door, if one was handed over. `remedy_url` is the current spelling — ONE
|
|
140
150
|
// key for every reason, which is what lets a reason we have never heard of still
|
|
@@ -160,7 +170,7 @@ export function readPaymentRefusal({ status, contentType = '', body = '' } = {})
|
|
|
160
170
|
// the platform's URL opener; a `file:` or a `javascript:` URL is not a place a
|
|
161
171
|
// person goes. Refusing them costs a legitimate backend nothing.
|
|
162
172
|
const url = candidate && OPENABLE.test(candidate) ? candidate : null
|
|
163
|
-
if (!url) return { kind: 'stop', reason, message }
|
|
173
|
+
if (!url) return { kind: 'stop', reason, message, confirm }
|
|
164
174
|
|
|
165
175
|
return {
|
|
166
176
|
kind: 'settle',
|
|
@@ -170,7 +180,8 @@ export function readPaymentRefusal({ status, contentType = '', body = '' } = {})
|
|
|
170
180
|
? legacy.handle
|
|
171
181
|
: null,
|
|
172
182
|
reason,
|
|
173
|
-
message
|
|
183
|
+
message,
|
|
184
|
+
confirm
|
|
174
185
|
}
|
|
175
186
|
}
|
|
176
187
|
|
|
@@ -187,9 +198,11 @@ export function readPaymentRefusal({ status, contentType = '', body = '' } = {})
|
|
|
187
198
|
* @param {string[]} o.args - argv slice (for --non-interactive detection)
|
|
188
199
|
* @param {object} o.say - { ok, info, warn, err, dim } reporters
|
|
189
200
|
* @param {(url: string) => Promise<boolean>} [o.open] - injected for tests
|
|
201
|
+
* @param {boolean} [o.interactive] - whether someone is at the terminal, when the caller has decided
|
|
202
|
+
* it already (`settleRefusal`); else read from `args` and stdin
|
|
190
203
|
* @returns {Promise<{ opened: boolean }>}
|
|
191
204
|
*/
|
|
192
|
-
export async function reportPaymentRefusal({ verdict, args = [], say, open }) {
|
|
205
|
+
export async function reportPaymentRefusal({ verdict, args = [], say, open, interactive }) {
|
|
193
206
|
// The backend's own sentence is the HEADLINE when there is one. A generic
|
|
194
207
|
// lead would be wrong as often as right — "payment is required" does not
|
|
195
208
|
// describe a declined card — and `detail` is written for this reader.
|
|
@@ -201,24 +214,149 @@ export async function reportPaymentRefusal({ verdict, args = [], say, open }) {
|
|
|
201
214
|
return { opened: false }
|
|
202
215
|
}
|
|
203
216
|
|
|
204
|
-
|
|
205
|
-
|
|
217
|
+
if (interactive === undefined) {
|
|
218
|
+
const { isNonInteractive } = await import('../utils/interactive.js')
|
|
219
|
+
interactive = !isNonInteractive(args)
|
|
220
|
+
}
|
|
221
|
+
if (!interactive) {
|
|
206
222
|
say.dim(`Finish this in a browser, then re-run \`uniweb publish\`:`)
|
|
207
223
|
say.dim(` ${verdict.url}`)
|
|
208
224
|
return { opened: false }
|
|
209
225
|
}
|
|
210
226
|
|
|
227
|
+
return openDoor({ url: verdict.url, say, open })
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Open the backend's door, verbatim, and say how to come back. */
|
|
231
|
+
async function openDoor({ url, say, open }) {
|
|
211
232
|
const openBrowser = open || (await import('../utils/registry-auth.js')).openBrowser
|
|
212
|
-
// ⛔ Reason-agnostic wording. The backend's own `detail`
|
|
213
|
-
//
|
|
214
|
-
//
|
|
233
|
+
// ⛔ Reason-agnostic wording. The backend's own `detail` carries the specifics;
|
|
234
|
+
// a lead sentence naming payment would be wrong the moment a refusal is a quota
|
|
235
|
+
// or an unverified domain — and the reason set is open by design.
|
|
215
236
|
say.info('Opening your browser to finish this…')
|
|
216
|
-
say.dim(` ${
|
|
237
|
+
say.dim(` ${url}`)
|
|
217
238
|
// VERBATIM. Nothing is appended — see the header.
|
|
218
|
-
const opened = await openBrowser(
|
|
239
|
+
const opened = await openBrowser(url)
|
|
219
240
|
if (!opened) {
|
|
220
241
|
say.warn('Could not open a browser automatically — open the URL above.')
|
|
221
242
|
}
|
|
222
243
|
say.dim('Once that is done, re-run `uniweb publish`.')
|
|
223
244
|
return { opened }
|
|
224
245
|
}
|
|
246
|
+
|
|
247
|
+
const CONFIRM = 'Confirm and publish'
|
|
248
|
+
const CONTINUE = 'Continue and publish'
|
|
249
|
+
const OPEN = 'Open it in the app'
|
|
250
|
+
const CANCEL = 'Cancel'
|
|
251
|
+
|
|
252
|
+
// A reduction offered again and again, with no one to ask, is answered this many times and no more.
|
|
253
|
+
const MAX_UNATTENDED = 3
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* ⭐ A REFUSAL THE TERMINAL MAY SETTLE (since 2026-10-03). When everything a publish owes is a change
|
|
257
|
+
* to a plan the site already has, the `402` carries `confirm: { prompt, token, reduction }` beside its
|
|
258
|
+
* door, and the backend's sentence is asked at an interactive terminal: confirm, open the app, or
|
|
259
|
+
* cancel. Confirming publishes again with the token (`?confirm=`):
|
|
260
|
+
*
|
|
261
|
+
* - `200` — settled and published: the response is returned, for the caller to finish with.
|
|
262
|
+
* - `402` with a fresh `confirm` — the change or its price moved and nothing was applied: asked again,
|
|
263
|
+
* with the new sentence.
|
|
264
|
+
* - `402` without one — what is owed can no longer be confirmed here (it now asks for a first plan,
|
|
265
|
+
* or the plan's card can no longer be named): its sentence, and its door. ⛔ Not a declined card —
|
|
266
|
+
* a confirmed change is invoiced as it applies, and a decline later shows in the app's billing.
|
|
267
|
+
* *(This said "the card could not settle it" until the day it was written, 2026-10-03.)*
|
|
268
|
+
*
|
|
269
|
+
* ⭐ **A CHARGE AND A REDUCTION ARE ASKED DIFFERENTLY** (`reduction`, a field the backend keeps
|
|
270
|
+
* stable). A charge is confirmed only by someone at the terminal: Enter cancels, no flag answers it,
|
|
271
|
+
* and with `--yes` — which promises never to block on a prompt — it is not asked either, but reported
|
|
272
|
+
* with the way to confirm it. A reduction — the offer only lowers what the site pays — asks lightly: Enter continues, and
|
|
273
|
+
* `--yes` answers it without asking, with or without a terminal, telling the backend it was answered
|
|
274
|
+
* unattended (`&unattended=true`), which settles a reduction and nothing else. *[Diego, 2026-10-03:
|
|
275
|
+
* "allow it for reductions only, keep charges interactive".]*
|
|
276
|
+
*
|
|
277
|
+
* With no one at the terminal and nothing to answer it, there is no prompt: the refusal is reported as
|
|
278
|
+
* before, its door printed, and the publish fails. A refusal with no `confirm` behaves as it always has.
|
|
279
|
+
*
|
|
280
|
+
* @param {object} o
|
|
281
|
+
* @param {ReturnType<typeof readPaymentRefusal>} o.verdict - the first refusal
|
|
282
|
+
* @param {string[]} o.args - argv slice (for `--yes` and --non-interactive detection)
|
|
283
|
+
* @param {object} o.say - { ok, info, warn, err, dim } reporters
|
|
284
|
+
* @param {(token: string, options?: { unattended?: boolean }) => Promise<Response>} o.republish - the
|
|
285
|
+
* same publish, with `?confirm=<token>` (and `&unattended=true` when no one answered)
|
|
286
|
+
* @param {boolean} [o.interactive] - whether someone is at the terminal; read from `args` and stdin
|
|
287
|
+
* @param {boolean} [o.yes] - whether `--yes` answers a reduction; read from `args`
|
|
288
|
+
* @param {(question: string, options: string[], fallback: string) => Promise<string>} [o.ask]
|
|
289
|
+
* @param {(url: string) => Promise<boolean>} [o.open]
|
|
290
|
+
* @returns {Promise<{ exitCode: number } | { response: Response, body?: string }>} `response` when
|
|
291
|
+
* the publish went through, or failed for a reason that is not payment (with its body, read)
|
|
292
|
+
*/
|
|
293
|
+
export async function settleRefusal({ verdict, args = [], say, republish, interactive, yes, ask, open }) {
|
|
294
|
+
if (interactive === undefined) {
|
|
295
|
+
const { isNonInteractive } = await import('../utils/interactive.js')
|
|
296
|
+
interactive = !isNonInteractive(args)
|
|
297
|
+
}
|
|
298
|
+
if (yes === undefined) yes = args.includes('--yes')
|
|
299
|
+
let current = verdict
|
|
300
|
+
let unattended = 0
|
|
301
|
+
for (;;) {
|
|
302
|
+
const offer = current.confirm
|
|
303
|
+
const reduction = offer?.reduction === true
|
|
304
|
+
let answered
|
|
305
|
+
|
|
306
|
+
if (reduction && yes && unattended < MAX_UNATTENDED) {
|
|
307
|
+
// `--yes` answers a change that only lowers the bill — said, so the log shows what was accepted.
|
|
308
|
+
unattended++
|
|
309
|
+
say.info(offer.prompt)
|
|
310
|
+
say.dim('Continuing: `--yes` answers a change that only lowers what the site pays.')
|
|
311
|
+
answered = { unattended: true }
|
|
312
|
+
} else if (!offer || !interactive || (yes && !reduction)) {
|
|
313
|
+
await reportPaymentRefusal({ verdict: current, args, say, open, interactive: interactive && !yes })
|
|
314
|
+
if (offer && !reduction) {
|
|
315
|
+
say.dim('This change charges the site\'s card, so only you can confirm it: run `uniweb publish` at a terminal, without `--yes`.')
|
|
316
|
+
} else if (reduction) {
|
|
317
|
+
say.dim('This change only lowers what the site pays: `uniweb publish --yes` continues it without a prompt.')
|
|
318
|
+
}
|
|
319
|
+
return { exitCode: 1 }
|
|
320
|
+
} else {
|
|
321
|
+
const go = reduction ? CONTINUE : CONFIRM
|
|
322
|
+
const choices = [go, ...(current.kind === 'settle' ? [OPEN] : []), CANCEL]
|
|
323
|
+
// Enter continues a reduction and cancels a charge.
|
|
324
|
+
const fallback = reduction ? CONTINUE : CANCEL
|
|
325
|
+
const pick = ask
|
|
326
|
+
? await ask(offer.prompt, choices, fallback)
|
|
327
|
+
: await (await import('../utils/interactive.js')).pickOne(offer.prompt, choices, fallback)
|
|
328
|
+
|
|
329
|
+
if (pick === OPEN) {
|
|
330
|
+
await openDoor({ url: current.url, say, open })
|
|
331
|
+
return { exitCode: 1 }
|
|
332
|
+
}
|
|
333
|
+
if (pick !== go) {
|
|
334
|
+
say.dim('Cancelled — nothing changed, and nothing was made live. The site is synced as a draft.')
|
|
335
|
+
return { exitCode: 1 }
|
|
336
|
+
}
|
|
337
|
+
answered = {}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
let response
|
|
341
|
+
try {
|
|
342
|
+
response = await republish(offer.token, answered)
|
|
343
|
+
} catch (err) {
|
|
344
|
+
say.err(String(err?.message || err))
|
|
345
|
+
return { exitCode: 1 }
|
|
346
|
+
}
|
|
347
|
+
if (response.ok) return { response }
|
|
348
|
+
|
|
349
|
+
const body = await response.text().catch(() => '')
|
|
350
|
+
const next = readPaymentRefusal({
|
|
351
|
+
status: response.status,
|
|
352
|
+
contentType: response.headers?.get?.('content-type') || '',
|
|
353
|
+
body
|
|
354
|
+
})
|
|
355
|
+
if (next.kind === 'not-payment') return { response, body }
|
|
356
|
+
if (next.confirm) {
|
|
357
|
+
// Nothing was applied: the change or its price moved since the offer. Ask again.
|
|
358
|
+
say.warn('The change or its price moved since that offer, so nothing was applied.')
|
|
359
|
+
}
|
|
360
|
+
current = next
|
|
361
|
+
}
|
|
362
|
+
}
|
package/src/commands/publish.js
CHANGED
|
@@ -112,7 +112,7 @@ import {
|
|
|
112
112
|
} from '../backend/foundation-bring-along.js'
|
|
113
113
|
import {
|
|
114
114
|
readPaymentRefusal,
|
|
115
|
-
|
|
115
|
+
settleRefusal
|
|
116
116
|
} from '../backend/payment-handoff.js'
|
|
117
117
|
import { reportSchemalessQueries } from '../utils/schemaless-report.js'
|
|
118
118
|
import {
|
|
@@ -1050,31 +1050,43 @@ export async function publish(args = []) {
|
|
|
1050
1050
|
say.dim('Is that the backend you meant? Switch with: uniweb login --backend <url>')
|
|
1051
1051
|
return { exitCode: 1 }
|
|
1052
1052
|
}
|
|
1053
|
+
// The body of a publish that did not go through — read once, and kept for the report below.
|
|
1054
|
+
let failedBody = ''
|
|
1053
1055
|
if (!pubRes.ok) {
|
|
1054
|
-
|
|
1056
|
+
failedBody = await pubRes.text().catch(() => '')
|
|
1055
1057
|
|
|
1056
1058
|
// A 402 is the backend's payment gate — the ONLY gate, evaluated here on
|
|
1057
1059
|
// every publish against whatever posture that deployment runs. It is a
|
|
1058
1060
|
// refusal, not a fault: the content is already synced as a draft, so the
|
|
1059
1061
|
// recovery is to settle and re-run. Give it the backend's own sentence
|
|
1060
|
-
// rather than the raw envelope
|
|
1062
|
+
// rather than the raw envelope — and when it offers a change the terminal
|
|
1063
|
+
// may confirm, ask, and publish again with the answer.
|
|
1061
1064
|
const refusal = readPaymentRefusal({
|
|
1062
1065
|
status: pubRes.status,
|
|
1063
1066
|
contentType: pubRes.headers?.get?.('content-type') || '',
|
|
1064
|
-
body
|
|
1067
|
+
body: failedBody
|
|
1065
1068
|
})
|
|
1066
1069
|
if (refusal.kind !== 'not-payment') {
|
|
1067
|
-
await
|
|
1068
|
-
|
|
1070
|
+
const settled = await settleRefusal({
|
|
1071
|
+
verdict: refusal,
|
|
1072
|
+
args,
|
|
1073
|
+
say,
|
|
1074
|
+
republish: (token, { unattended } = {}) =>
|
|
1075
|
+
client.publishSite(siteUuid, { ...(languages ? { languages } : {}), confirm: token, unattended })
|
|
1076
|
+
})
|
|
1077
|
+
if ('exitCode' in settled) return { exitCode: settled.exitCode }
|
|
1078
|
+
pubRes = settled.response
|
|
1079
|
+
failedBody = settled.body ?? ''
|
|
1069
1080
|
}
|
|
1070
|
-
|
|
1081
|
+
}
|
|
1082
|
+
if (!pubRes.ok) {
|
|
1071
1083
|
say.err(`Publish rejected: HTTP ${pubRes.status} ${pubRes.statusText}`)
|
|
1072
1084
|
if (pubRes.status === 401 || pubRes.status === 403) {
|
|
1073
1085
|
say.dim(
|
|
1074
1086
|
"Credentials weren't accepted — run `uniweb login` again."
|
|
1075
1087
|
)
|
|
1076
1088
|
}
|
|
1077
|
-
if (
|
|
1089
|
+
if (failedBody) say.dim(failedBody.slice(0, 800))
|
|
1078
1090
|
return { exitCode: 1 }
|
|
1079
1091
|
}
|
|
1080
1092
|
let result
|
|
@@ -1163,6 +1175,11 @@ export async function publish(args = []) {
|
|
|
1163
1175
|
say.ok(
|
|
1164
1176
|
`Published ${c.bold}${siteUuid}${c.reset}${result.status ? ` (${result.status})` : ''}`
|
|
1165
1177
|
)
|
|
1178
|
+
// What the request changed, in the backend's own sentences — a confirmed change, or a reduction
|
|
1179
|
+
// (`applied`, its §4A). Absent when nothing changed.
|
|
1180
|
+
for (const line of Array.isArray(result.applied) ? result.applied : []) {
|
|
1181
|
+
if (typeof line === 'string' && line) say.info(line)
|
|
1182
|
+
}
|
|
1166
1183
|
|
|
1167
1184
|
// ⛔ SAY IT WHEN A LANGUAGE ASKED FOR DID NOT GO OUT.
|
|
1168
1185
|
//
|
package/src/commands/pull.js
CHANGED
|
@@ -73,6 +73,7 @@ import { downloadMissingAssets, downloadFileRecords } from '../backend/asset-dow
|
|
|
73
73
|
import {
|
|
74
74
|
readBackendState,
|
|
75
75
|
siteContentDocumentToProject,
|
|
76
|
+
dataBlockModels,
|
|
76
77
|
recordsToProject,
|
|
77
78
|
siteSelfScope,
|
|
78
79
|
readZip,
|
|
@@ -988,7 +989,10 @@ export async function pull(args = [], deps = {}) {
|
|
|
988
989
|
prune,
|
|
989
990
|
keepAuthoredFoundation,
|
|
990
991
|
// So a query's `@acme/member` comes back as the author's `@/member`.
|
|
991
|
-
scope: await scopeFor(siteDoc)
|
|
992
|
+
scope: await scopeFor(siteDoc),
|
|
993
|
+
// A translated data block is read back by what its section's type declares it is, as the
|
|
994
|
+
// push translated it — after the registered foundation above, whose section types say it.
|
|
995
|
+
dataModel: await dataBlockModels(siteDir)
|
|
992
996
|
})
|
|
993
997
|
wrote.push(...report.pages, ...report.sections, ...report.layout)
|
|
994
998
|
removed.push(
|
package/src/framework-index.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-10-
|
|
3
|
+
"generatedAt": "2026-10-03T13:45:26.166Z",
|
|
4
4
|
"packages": {
|
|
5
5
|
"@uniweb/api": {
|
|
6
6
|
"version": "0.6.9",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
]
|
|
11
11
|
},
|
|
12
12
|
"@uniweb/build": {
|
|
13
|
-
"version": "0.75.
|
|
13
|
+
"version": "0.75.1",
|
|
14
14
|
"path": "framework/build",
|
|
15
15
|
"deps": [
|
|
16
16
|
"@uniweb/content-reader",
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
"deps": []
|
|
118
118
|
},
|
|
119
119
|
"@uniweb/templates": {
|
|
120
|
-
"version": "0.17.
|
|
120
|
+
"version": "0.17.12",
|
|
121
121
|
"path": "framework/templates",
|
|
122
122
|
"deps": []
|
|
123
123
|
},
|