uniweb 0.26.0 → 0.26.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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uniweb",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.2",
|
|
4
4
|
"description": "Create structured Vite + React sites with content/code separation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -42,14 +42,14 @@
|
|
|
42
42
|
"prompts": "^2.4.2",
|
|
43
43
|
"tar": "^7.0.0",
|
|
44
44
|
"@uniweb/core": "^0.10.1",
|
|
45
|
+
"@uniweb/semantic-parser": "^1.2.3",
|
|
45
46
|
"@uniweb/kit": "^0.12.3",
|
|
46
|
-
"@uniweb/runtime": "^0.12.
|
|
47
|
-
"@uniweb/semantic-parser": "^1.2.3"
|
|
47
|
+
"@uniweb/runtime": "^0.12.3"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
+
"@uniweb/build": "^0.24.5",
|
|
50
51
|
"@uniweb/content-reader": "^1.2.3",
|
|
51
|
-
"@uniweb/semantic-parser": "^1.2.3"
|
|
52
|
-
"@uniweb/build": "^0.24.5"
|
|
52
|
+
"@uniweb/semantic-parser": "^1.2.3"
|
|
53
53
|
},
|
|
54
54
|
"peerDependenciesMeta": {
|
|
55
55
|
"@uniweb/build": {
|
|
@@ -241,6 +241,21 @@ async function bringLocalCodeAlong({
|
|
|
241
241
|
args.includes('--force') ||
|
|
242
242
|
args.includes('--no-verify')
|
|
243
243
|
|
|
244
|
+
// `--no-release`: ship the site against the code that is ALREADY released, and do
|
|
245
|
+
// not release the local changes.
|
|
246
|
+
//
|
|
247
|
+
// The intent is ordinary and had no name until 2026-08-19 — a developer edits
|
|
248
|
+
// content and a component in one sitting, and wants the copy fix live without
|
|
249
|
+
// shipping a half-finished component. Until now the way to get it was `--yes`,
|
|
250
|
+
// which means "do not ask me": the behaviour was reachable only as a side effect of
|
|
251
|
+
// a confirmation-skipper, which is discovery by accident.
|
|
252
|
+
//
|
|
253
|
+
// ⛔ It skips the RELEASE, never the REGISTRATION REQUIREMENT. A site referencing a
|
|
254
|
+
// foundation no deployment can resolve cannot be opened in the app at all, so where
|
|
255
|
+
// this flag cannot be honoured it REFUSES rather than doing the opposite of what it
|
|
256
|
+
// says (see the `!reg` branch).
|
|
257
|
+
const noRelease = args.includes('--no-release')
|
|
258
|
+
|
|
244
259
|
// The pinned ref to stamp on the pushed site — read at RETURN time (after any
|
|
245
260
|
// release), so it reflects the released version + the scope register derived.
|
|
246
261
|
// null when no scoped ref can be formed (then the site.yml ref is forwarded).
|
|
@@ -250,6 +265,15 @@ async function bringLocalCodeAlong({
|
|
|
250
265
|
return s && v ? `${s}@${v}` : null
|
|
251
266
|
}
|
|
252
267
|
|
|
268
|
+
// The ref for a run that releases NOTHING — the version the catalog actually holds,
|
|
269
|
+
// which is not always the local one. ⚠️ `pinnedRef()` reads the LOCAL package.json,
|
|
270
|
+
// so on a bumped-but-unreleased foundation it names a version nobody can serve. Any
|
|
271
|
+
// branch that skips a release must bind to this instead.
|
|
272
|
+
const registeredRef = (reg) =>
|
|
273
|
+
local.scopedName && reg?.latest_version
|
|
274
|
+
? `${local.scopedName}@${reg.latest_version}`
|
|
275
|
+
: null
|
|
276
|
+
|
|
253
277
|
// Dry-run reports the intent WITHOUT touching the network — it must not force
|
|
254
278
|
// a login (the digest read is auth-gated). The real run does the compare.
|
|
255
279
|
if (dryRun) {
|
|
@@ -277,6 +301,18 @@ async function bringLocalCodeAlong({
|
|
|
277
301
|
: null
|
|
278
302
|
|
|
279
303
|
if (!reg) {
|
|
304
|
+
// ⛔ Nothing to bind to. Releasing anyway would be the opposite of what was asked,
|
|
305
|
+
// and shipping anyway would leave a site the app cannot open — so stop and say so.
|
|
306
|
+
if (noRelease) {
|
|
307
|
+
say.err(
|
|
308
|
+
`--no-release, but ${label} has never been released — there is no registered version to bind to.`
|
|
309
|
+
)
|
|
310
|
+
say.dim(
|
|
311
|
+
`A site referencing an unreleased ${kind} cannot be opened in the app, so this cannot be skipped.`
|
|
312
|
+
)
|
|
313
|
+
say.dim(`Drop \`--no-release\` to release it now.`)
|
|
314
|
+
return { released: false, proceed: false, refused: true, ref: null }
|
|
315
|
+
}
|
|
280
316
|
say.info(`Releasing the ${kind} ${label} (not yet registered)…`)
|
|
281
317
|
return {
|
|
282
318
|
released: releaseFoundation(local, args, cliBin, say),
|
|
@@ -299,6 +335,15 @@ async function bringLocalCodeAlong({
|
|
|
299
335
|
|
|
300
336
|
// A different version locally → a new version to release.
|
|
301
337
|
if (local.version && local.version !== reg.latest_version) {
|
|
338
|
+
if (noRelease) {
|
|
339
|
+
// ⚠️ Bind to the REGISTERED version, not the local one. `pinnedRef()` would
|
|
340
|
+
// return the bumped-but-unreleased `local.version` here — a ref no deployment
|
|
341
|
+
// can serve, which is the very failure this flag must not create.
|
|
342
|
+
say.info(
|
|
343
|
+
`Keeping the released ${kind} ${reg.latest_version} — local ${local.version} not released (\`--no-release\`).`
|
|
344
|
+
)
|
|
345
|
+
return { released: false, proceed: true, ref: registeredRef(reg) }
|
|
346
|
+
}
|
|
302
347
|
say.info(
|
|
303
348
|
`Releasing the ${kind} ${label} (new version; registered latest is ${reg.latest_version})…`
|
|
304
349
|
)
|
|
@@ -313,6 +358,12 @@ async function bringLocalCodeAlong({
|
|
|
313
358
|
if (!reg.digest) {
|
|
314
359
|
// Degrade: the backend doesn't return the stored digest yet, so we can't
|
|
315
360
|
// be sure the registered version matches local. Offer to re-deliver.
|
|
361
|
+
if (noRelease) {
|
|
362
|
+
say.info(
|
|
363
|
+
`Keeping the released ${kind} ${reg.latest_version} — nothing released (\`--no-release\`).`
|
|
364
|
+
)
|
|
365
|
+
return { released: false, proceed: true, ref: registeredRef(reg) }
|
|
366
|
+
}
|
|
316
367
|
say.warn(
|
|
317
368
|
`Can't verify the registered ${label} matches your local copy (backend returned no digest).`
|
|
318
369
|
)
|
|
@@ -338,6 +389,14 @@ async function bringLocalCodeAlong({
|
|
|
338
389
|
// Case 3 (§4): the code was edited but the version wasn't bumped. The
|
|
339
390
|
// registered version is immutable, so we never silently ship the old code —
|
|
340
391
|
// the deliberate release gate is a version bump (§3.1).
|
|
392
|
+
// Asked for explicitly: the case this flag exists to name.
|
|
393
|
+
if (noRelease) {
|
|
394
|
+
say.info(
|
|
395
|
+
`Keeping the released ${kind} ${reg.latest_version} — your local changes are not released (\`--no-release\`).`
|
|
396
|
+
)
|
|
397
|
+
return { released: false, proceed: true, ref: registeredRef(reg) }
|
|
398
|
+
}
|
|
399
|
+
|
|
341
400
|
// ⭐ THREE OUTCOMES, NOT TWO — `--yes` and "no TTY" are not the same answer.
|
|
342
401
|
//
|
|
343
402
|
// They were one condition until 2026-08-19, and conflating them meant the only
|
|
@@ -372,7 +431,12 @@ async function bringLocalCodeAlong({
|
|
|
372
431
|
)
|
|
373
432
|
say.dim(`Nothing was sent. Either release the change, or ship without it:`)
|
|
374
433
|
say.dim(` • bump the ${kind}'s version in package.json, then re-run \`uniweb ${verb}\``)
|
|
375
|
-
|
|
434
|
+
// Teach the flag that NAMES this, not `--yes`. Both work, but `--yes` means "do
|
|
435
|
+
// not ask me" and only does this as a side effect — pointing a stuck user at it
|
|
436
|
+
// teaches a blunt instrument for a precise job.
|
|
437
|
+
say.dim(
|
|
438
|
+
` • \`uniweb ${verb} --no-release\` — sends content bound to the released ${reg.latest_version}`
|
|
439
|
+
)
|
|
376
440
|
return { released: false, proceed: false, refused: true, ref: null }
|
|
377
441
|
}
|
|
378
442
|
|
package/src/commands/push.js
CHANGED
|
@@ -241,6 +241,33 @@ export async function push(args = [], deps = {}) {
|
|
|
241
241
|
// `index` — the per-entity source-file map for back-fill, correlated by submission
|
|
242
242
|
// position. Non-local Models are fetched from the registry on demand. `priorHashes`
|
|
243
243
|
// (the .uniweb push-cache) drives "send only changed" across both lanes; --all bypasses.
|
|
244
|
+
// ⛔ DROP STALE REMOTE STATE FIRST — this must precede the hash read below, and it
|
|
245
|
+
// did not until 2026-08-19.
|
|
246
|
+
//
|
|
247
|
+
// The guard itself is right and its docblock names this exact outcome: on a clone
|
|
248
|
+
// whose `$uuid` was cleared to re-publish as a new site — WHICH IS OUR OWN
|
|
249
|
+
// DOCUMENTED RECOVERY — send-only-changed would "skip every entity whose content
|
|
250
|
+
// had not changed since the OLD site's last push, so the NEW site would come up
|
|
251
|
+
// missing exactly the content that did not change." It was defeated twice over:
|
|
252
|
+
//
|
|
253
|
+
// · it ran AFTER `readSyncCache`, so the emit still diffed against the old
|
|
254
|
+
// site's hashes even though the file on disk had just been emptied;
|
|
255
|
+
// · it sat inside `if (mediaRefs.length)`, so a site with no local images never
|
|
256
|
+
// reached it at all.
|
|
257
|
+
//
|
|
258
|
+
// Measured 2026-08-19 against a live uniwebd: clearing `$uuid` and pushing created
|
|
259
|
+
// the new site and pulled back **0 pages, 0 sections**. Successful exit, empty site,
|
|
260
|
+
// nothing to indicate it — the failure the guard was written to prevent.
|
|
261
|
+
//
|
|
262
|
+
// Still skipped for an offline emit: `-o` / `--dry-run` must not mutate project state.
|
|
263
|
+
if (!output && !dryRun) {
|
|
264
|
+
const dropped = clearRemoteSyncStateIfUnbound(siteDir)
|
|
265
|
+
if (dropped.length) {
|
|
266
|
+
note(
|
|
267
|
+
`Cleared stale sync state from a previous site (${dropped.join(', ')}).`
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
244
271
|
const priorHashes = readSyncCache(siteDir)
|
|
245
272
|
// Per-item identity, without which the backend reads every record as new and
|
|
246
273
|
// recreates every page and section row.
|
|
@@ -324,13 +351,23 @@ export async function push(args = [], deps = {}) {
|
|
|
324
351
|
// The site has to exist before its bytes do — an upload with no owning
|
|
325
352
|
// entity is charged and cannot be freed, because freeing means deleting the
|
|
326
353
|
// owner. A no-op once `$uuid` is set, so only a never-synced site pays.
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
354
|
+
// ⛔ The PINNED ref, not site.yml's. The create is a THIRD writer of this
|
|
355
|
+
// value — the emit below stamps it via `injectInfo`, publish passes it here,
|
|
356
|
+
// and push did neither until 2026-08-19: it sent the authored alias (`src`),
|
|
357
|
+
// which names a foundation no deployment can resolve. A site created that way
|
|
358
|
+
// keeps the bad ref, and the backend's create guard now refuses it outright
|
|
359
|
+
// (channel backend-framework-787e, their measurement).
|
|
360
|
+
//
|
|
361
|
+
// ⭐ Same shape as the send-only-changed defect fixed this morning, and missed
|
|
362
|
+
// for the same reason: the rule was applied at the writers under discussion and
|
|
363
|
+
// a third caller of the same value was never enumerated.
|
|
364
|
+
const site = await ensureSiteExists({
|
|
365
|
+
client,
|
|
366
|
+
siteDir,
|
|
367
|
+
asOrg,
|
|
368
|
+
note,
|
|
369
|
+
...(fnd.ref ? { foundation: fnd.ref } : {})
|
|
370
|
+
})
|
|
334
371
|
if (!site.uuid) {
|
|
335
372
|
error(`Could not create the site on the backend: ${site.reason}`)
|
|
336
373
|
note('Nothing was uploaded and nothing was charged.')
|
package/src/framework-index.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-08-
|
|
3
|
+
"generatedAt": "2026-08-19T12:16:02.893Z",
|
|
4
4
|
"packages": {
|
|
5
5
|
"@uniweb/build": {
|
|
6
6
|
"version": "0.24.5",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
]
|
|
77
77
|
},
|
|
78
78
|
"@uniweb/runtime": {
|
|
79
|
-
"version": "0.12.
|
|
79
|
+
"version": "0.12.3",
|
|
80
80
|
"path": "framework/runtime",
|
|
81
81
|
"deps": [
|
|
82
82
|
"@uniweb/core",
|
package/src/index.js
CHANGED
|
@@ -1316,6 +1316,11 @@ FOUNDATION on its own use \`uniweb register\` (alias \`uniweb release\`).
|
|
|
1316
1316
|
${colors.bright}Options:${colors.reset}
|
|
1317
1317
|
--dry-run Resolve everything; release/sync/POST nothing
|
|
1318
1318
|
--yes Skip confirmations (CI); never block on a prompt
|
|
1319
|
+
--no-release Ship the content against the code already released; release
|
|
1320
|
+
nothing. For editing content and a component in one sitting
|
|
1321
|
+
when only the content should go live. Refused if the
|
|
1322
|
+
foundation has never been released — there is nothing to
|
|
1323
|
+
bind to, and the app cannot open such a site.
|
|
1319
1324
|
--no-save Skip the deploy.yml lastDeploy auto-save
|
|
1320
1325
|
--no-validate Skip the content-conformance check (it only warns)
|
|
1321
1326
|
--org @org Publish under @org (membership-gated; alias: --as-org). Read
|
|
@@ -1753,6 +1758,7 @@ ${colors.bright}Global Options:${colors.reset}
|
|
|
1753
1758
|
${colors.bright}Publish Options:${colors.reset}
|
|
1754
1759
|
--dry-run Resolve everything; release/sync/POST nothing
|
|
1755
1760
|
--yes Skip confirmations (CI); never block on a prompt
|
|
1761
|
+
--no-release Ship content against the already-released code; release nothing
|
|
1756
1762
|
--org @org Publish under @org (first publish only; then remembered)
|
|
1757
1763
|
--personal Own the new site personally, deliberately (first publish only)
|
|
1758
1764
|
--no-save Skip the deploy.yml lastDeploy auto-save
|
package/src/utils/flag-guard.js
CHANGED
|
@@ -63,14 +63,16 @@ const VERBS = {
|
|
|
63
63
|
// one of the three flags that skip its prompts. Found by
|
|
64
64
|
// flag-guard-coverage.test.js the moment push gained the import, which is
|
|
65
65
|
// exactly the hand-enumeration failure that test exists to catch.
|
|
66
|
-
'--no-verify',
|
|
66
|
+
'--no-verify',
|
|
67
|
+
// ship content against the already-released code, releasing nothing
|
|
68
|
+
'--no-release', ...VIA_DEPLOY
|
|
67
69
|
],
|
|
68
70
|
publish: [
|
|
69
71
|
'--as-org', '--org', '--backend', '--dry-run', '--force', '--foundation',
|
|
70
72
|
'--personal', '--registry', '--token',
|
|
71
73
|
// read in utils/conformance.js, backend/site-sync.js, and
|
|
72
74
|
// backend/foundation-bring-along.js — none appear in publish.js
|
|
73
|
-
'--no-validate', '--yes', '--no-verify', ...VIA_DEPLOY
|
|
75
|
+
'--no-validate', '--yes', '--no-verify', '--no-release', ...VIA_DEPLOY
|
|
74
76
|
],
|
|
75
77
|
pull: [
|
|
76
78
|
'--backend', '--content-only', '--dry-run', '--force', '--merge',
|
|
@@ -90,7 +92,9 @@ const VERBS = {
|
|
|
90
92
|
status: [
|
|
91
93
|
'--backend', '--json', '--registry', '--remote', '--token', '--dry-run',
|
|
92
94
|
'--force', '--no-verify', '--no-validate', '--yes', '--org', '--as-org',
|
|
93
|
-
|
|
95
|
+
// inert here, reachable through the bring-along module status imports for
|
|
96
|
+
// `resolveLocalFoundation` — listed per the over-approximation note above
|
|
97
|
+
'--no-release', ...VIA_DEPLOY
|
|
94
98
|
],
|
|
95
99
|
/**
|
|
96
100
|
* `refresh` = `git pull`, then a DELEGATED `pull --merge`.
|