uniweb 0.25.5 → 0.26.0

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.25.5",
3
+ "version": "0.26.0",
4
4
  "description": "Create structured Vite + React sites with content/code separation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -47,9 +47,9 @@
47
47
  "@uniweb/semantic-parser": "^1.2.3"
48
48
  },
49
49
  "peerDependencies": {
50
- "@uniweb/build": "^0.24.5",
51
50
  "@uniweb/content-reader": "^1.2.3",
52
- "@uniweb/semantic-parser": "^1.2.3"
51
+ "@uniweb/semantic-parser": "^1.2.3",
52
+ "@uniweb/build": "^0.24.5"
53
53
  },
54
54
  "peerDependenciesMeta": {
55
55
  "@uniweb/build": {
@@ -338,23 +338,58 @@ async function bringLocalCodeAlong({
338
338
  // Case 3 (§4): the code was edited but the version wasn't bumped. The
339
339
  // registered version is immutable, so we never silently ship the old code —
340
340
  // the deliberate release gate is a version bump (§3.1).
341
+ // ⭐ THREE OUTCOMES, NOT TWO — `--yes` and "no TTY" are not the same answer.
342
+ //
343
+ // They were one condition until 2026-08-19, and conflating them meant the only
344
+ // party that never got asked was the one that most needed asking. A `--yes` is a
345
+ // decision someone made in advance; an absent TTY is the ABSENCE of a decision,
346
+ // and absence is not consent.
347
+ //
348
+ // ⚖️ Which flips the safe default by caller, for a reason particular to this
349
+ // situation: proceeding here ships a site bound to code that is NOT the code in
350
+ // the working tree. A human is at a terminal, reads the warning, and decides. An
351
+ // agent re-runs commands for free and reports "pushed" on exit 0 — so for it the
352
+ // cheap outcome is one more cycle and the expensive one is a silent wrong success
353
+ // it will not look at again. Refusing costs an agent a retry; proceeding costs it
354
+ // a false completion report and a live site nobody notices is stale.
355
+
356
+ // 1. Explicit consent — proceed, but at WARN. `dim` is what we print for things
357
+ // nobody needs to read, and "your changes are not live" is not that.
358
+ if (skipPrompts) {
359
+ say.warn(
360
+ `Local ${label} differs from the registered ${reg.latest_version} and the version wasn't bumped — ` +
361
+ `shipping against the registered code. Your local changes will NOT be live.`
362
+ )
363
+ return { released: false, proceed: true, ref: pinnedRef() }
364
+ }
365
+
366
+ // 2. Nobody to ask. Refuse, and name both real options as runnable commands so
367
+ // the caller's next step needs no interpretation. `refused` marks this as a
368
+ // hard stop rather than a human's "no", which callers map to different exits.
369
+ if (isNonInteractive(args)) {
370
+ say.err(
371
+ `Local ${kind} ${label} differs from the registered ${reg.latest_version}, and the version wasn't bumped.`
372
+ )
373
+ say.dim(`Nothing was sent. Either release the change, or ship without it:`)
374
+ say.dim(` • bump the ${kind}'s version in package.json, then re-run \`uniweb ${verb}\``)
375
+ say.dim(` • \`uniweb ${verb} --yes\` — sends content bound to the registered ${reg.latest_version}`)
376
+ return { released: false, proceed: false, refused: true, ref: null }
377
+ }
378
+
379
+ // 3. A human is here. State it, then ask.
341
380
  say.warn(
342
381
  `Your local ${label} differs from the registered version ${reg.latest_version}, but the version wasn't bumped.`
343
382
  )
344
383
  say.dim(
345
- `A registered version is immutable. Bump the ${kind}'s version to release the change, then re-run \`uniweb ${verb}\`.`
384
+ `A registered version is immutable bump the ${kind}'s version to release the change.`
346
385
  )
347
- if (skipPrompts || isNonInteractive(args)) {
348
- say.dim(`Proceeding with the already-registered ${reg.latest_version}.`)
349
- return { released: false, proceed: true, ref: pinnedRef() }
350
- }
351
386
  const proceed = await confirm(
352
387
  `Continue with the already-registered ${reg.latest_version} anyway?`,
353
388
  false
354
389
  )
355
390
  if (!proceed) {
356
391
  say.info(
357
- `Aborted — bump the ${kind} version, then re-run \`uniweb ${verb}\`.`
392
+ `Aborted — nothing was sent. Bump the ${kind} version, then re-run \`uniweb ${verb}\`.`
358
393
  )
359
394
  return { released: false, proceed: false, ref: null }
360
395
  }
@@ -428,7 +428,11 @@ export async function publish(args = []) {
428
428
  return { exitCode: 1 }
429
429
  }
430
430
  if (!ext.proceed) return { exitCode: 1 }
431
- if (!fnd.proceed) return { exitCode: 0 }
431
+ // A human who answered "no" chose this, so it is not a failure — exit 0. A
432
+ // REFUSAL is different: nobody was asked, nothing shipped, and the caller is
433
+ // usually an agent that reads the exit code and reports done. Exit 0 there would
434
+ // be the silent-wrong-success this whole branch exists to prevent.
435
+ if (!fnd.proceed) return { exitCode: fnd.refused ? 1 : 0 }
432
436
 
433
437
  // 2. Build the site data (link mode): dist/site-content.json (+ per-locale),
434
438
  // dist/data/*, dist/_search/*, dist/assets/*. Spawn the SAME CLI binary so