uniweb 0.12.28 → 0.12.29

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.12.28",
3
+ "version": "0.12.29",
4
4
  "description": "Create structured Vite + React sites with content/code separation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,12 +41,12 @@
41
41
  "js-yaml": "^4.1.0",
42
42
  "prompts": "^2.4.2",
43
43
  "tar": "^7.0.0",
44
- "@uniweb/core": "0.7.12",
45
- "@uniweb/kit": "0.9.16",
46
- "@uniweb/runtime": "0.8.17"
44
+ "@uniweb/core": "0.7.13",
45
+ "@uniweb/kit": "0.9.17",
46
+ "@uniweb/runtime": "0.8.18"
47
47
  },
48
48
  "peerDependencies": {
49
- "@uniweb/build": "0.14.12",
49
+ "@uniweb/build": "0.14.13",
50
50
  "@uniweb/content-reader": "1.1.12",
51
51
  "@uniweb/semantic-parser": "1.1.17"
52
52
  },
@@ -27,10 +27,13 @@
27
27
  * uniweb register --schema-only Skip the code delivery (schemas land, no dist upload)
28
28
  * uniweb register --dry-run Print the .uwx + the code file plan; submit nothing
29
29
  * uniweb register -o foundation.uwx Write the .uwx to a file; submit nothing
30
- * uniweb register --registry <url> Override the submit endpoint
30
+ * uniweb register --json Porcelain: ONE compact JSON line on stdout
31
+ * ({ok,scope,origin,entities:[{name,uuid,version,unchanged}]}),
32
+ * all human output to stderr — for scripted callers
33
+ * uniweb register --registry <url> Override the submit endpoint (alias: --backend)
31
34
  * uniweb register --token <bearer> Submit with this bearer; skips `uniweb login`
32
35
  *
33
- * Endpoint resolution: --registry <url> > UNIWEB_REGISTER_URL > the local default.
36
+ * Endpoint resolution: --backend / --registry <url> > UNIWEB_REGISTER_URL > the local default.
34
37
  * Auth (submit only): --token <bearer> > UNIWEB_TOKEN > `uniweb login` session.
35
38
  */
36
39
 
@@ -70,10 +73,18 @@ const colors = {
70
73
  reset: '\x1b[0m', bright: '\x1b[1m', dim: '\x1b[2m',
71
74
  red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[36m',
72
75
  }
73
- const log = console.log
76
+ // Porcelain (`--json`) mode: stdout carries ONLY the final compact JSON line, so
77
+ // all human/colored output diverts to stderr. `emitJson` writes to the REAL
78
+ // stdout (bypassing the redirect). `jsonMode`/`jsonEmitted`/`lastError` are reset
79
+ // per run by the exported `register` wrapper.
80
+ let jsonMode = false
81
+ let jsonEmitted = false
82
+ let lastError = null
83
+ const log = (...a) => (jsonMode ? console.error(...a) : console.log(...a))
74
84
  const success = (m) => log(`${colors.green}✓${colors.reset} ${m}`)
75
- const error = (m) => console.error(`${colors.red}✗${colors.reset} ${m}`)
85
+ const error = (m) => { lastError = String(m); console.error(`${colors.red}✗${colors.reset} ${m}`) }
76
86
  const info = (m) => log(`${colors.blue}→${colors.reset} ${m}`)
87
+ const emitJson = (obj) => { jsonEmitted = true; process.stdout.write(JSON.stringify(obj) + '\n') }
77
88
 
78
89
  function flagValue(args, name) {
79
90
  const eq = args.find((a) => a.startsWith(`${name}=`))
@@ -195,11 +206,27 @@ export function foundationNeedsBuild(targetDir) {
195
206
  }
196
207
 
197
208
  export async function register(args = []) {
209
+ jsonMode = args.includes('--json')
210
+ jsonEmitted = false
211
+ lastError = null
212
+ const result = await runRegister(args)
213
+ // Guarantee a porcelain line on stdout for every --json exit: the success path
214
+ // emits its own; here we cover the error / early-return paths so a scripted
215
+ // caller can always JSON.parse(stdout).
216
+ if (jsonMode && !jsonEmitted) {
217
+ emitJson(result?.exitCode === 0 ? { ok: true, entities: [] } : { ok: false, error: lastError || `register failed (exit ${result?.exitCode ?? 1})` })
218
+ }
219
+ return result
220
+ }
221
+
222
+ async function runRegister(args = []) {
198
223
  const dryRun = args.includes('--dry-run')
199
224
  const output = flagValue(args, '-o') || flagValue(args, '--output')
200
225
  const scopeFlag = flagValue(args, '--scope')
201
226
  const tokenFlag = flagValue(args, '--token')
202
- const client = new BackendClient({ originFlag: flagValue(args, '--registry'), token: tokenFlag, args, command: 'Registering' })
227
+ // Origin: --backend and --registry are aliases (matches deploy/publish + the
228
+ // origin-selection convention); either overrides UNIWEB_REGISTER_URL / default.
229
+ const client = new BackendClient({ originFlag: flagValue(args, '--backend') || flagValue(args, '--registry'), token: tokenFlag, args, command: 'Registering' })
203
230
 
204
231
  // Target: a schemas-only package (standalone data-schema register) or a
205
232
  // foundation (foundation + the schemas it renders). A schemas package is only
@@ -340,12 +367,16 @@ export async function register(args = []) {
340
367
  res = await client.register(json)
341
368
  } catch (err) {
342
369
  error(`Could not reach the registry at ${client.origin}: ${err.message}`)
343
- log(` ${colors.dim}Set the endpoint with --registry <url> or UNIWEB_REGISTER_URL.${colors.reset}`)
370
+ log(` ${colors.dim}Set the endpoint with --backend/--registry <url> or UNIWEB_REGISTER_URL.${colors.reset}`)
344
371
  return { exitCode: 2 }
345
372
  }
373
+ // Read the response body once: the --json success path needs it for the minted
374
+ // entity ids; the error path shows it.
375
+ const rawBody = await res.text().catch(() => '')
376
+ let parsedBody = null
377
+ try { parsedBody = rawBody ? JSON.parse(rawBody) : null } catch { parsedBody = null }
346
378
  let alreadyRegistered = false
347
379
  if (!res.ok) {
348
- const body = await res.text().catch(() => '')
349
380
  // Resume path: a registered version is immutable, so re-running after a
350
381
  // partial code delivery hits the duplicate rejection here — a STRUCTURED
351
382
  // 409 (problem+json, title "Conflict") — and proceeds to phase 2 (the
@@ -361,7 +392,7 @@ export async function register(args = []) {
361
392
  log(` ${colors.dim}The registry didn't accept your credentials — it may use different ones than \`uniweb login\`.${colors.reset}`)
362
393
  log(` ${colors.dim}Supply a registry bearer with --token <bearer> (or UNIWEB_TOKEN); an existing one may be wrong or expired.${colors.reset}`)
363
394
  }
364
- if (body) log(` ${colors.dim}${body.slice(0, 500)}${colors.reset}`)
395
+ if (rawBody) log(` ${colors.dim}${rawBody.slice(0, 500)}${colors.reset}`)
365
396
  return { exitCode: 1 }
366
397
  }
367
398
  }
@@ -415,5 +446,30 @@ export async function register(args = []) {
415
446
  return { exitCode: 1 }
416
447
  }
417
448
  }
449
+ if (jsonMode) {
450
+ // Join my authoritative submitted names with the backend's minted ids. Each
451
+ // response entry is `{ registered: { name, version, payload_model_uuid, … },
452
+ // unchanged }` (symmetric on the new-version + unchanged branches); a flat
453
+ // shape is tolerated as a fallback. Names from doc.entities are the spine, so
454
+ // the porcelain always reports WHICH names landed even if a field is absent.
455
+ const minted = {}
456
+ const addMint = (e) => {
457
+ if (!e || typeof e !== 'object') return
458
+ const reg = e.registered ?? e
459
+ if (reg.name) minted[reg.name] = { uuid: reg.payload_model_uuid ?? null, version: reg.version ?? null, unchanged: e.unchanged === true }
460
+ }
461
+ if (parsedBody && typeof parsedBody === 'object') {
462
+ if (Array.isArray(parsedBody.data_schemas)) parsedBody.data_schemas.forEach(addMint)
463
+ if (parsedBody.foundation_schema) addMint(parsedBody.foundation_schema)
464
+ }
465
+ const names = [
466
+ ...doc.entities.filter((e) => e.model === '@uniweb/data-schema').map((e) => e.name),
467
+ // The foundation-schema entity carries its `@scope/name` under `info`, not a
468
+ // top-level `name` (that's the foundation-schema shape).
469
+ ...doc.entities.filter((e) => e.model === '@uniweb/foundation-schema').map((e) => e.info?.name ?? e.name),
470
+ ].filter(Boolean)
471
+ const entities = names.map((name) => ({ name, ...(minted[name] || { uuid: null, version: null, unchanged: false }) }))
472
+ emitJson({ ok: true, scope: scope || null, origin: client.origin, entities })
473
+ }
418
474
  return { exitCode: 0 }
419
475
  }
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-06-15T14:11:09.239Z",
3
+ "generatedAt": "2026-06-16T22:32:52.549Z",
4
4
  "packages": {
5
5
  "@uniweb/build": {
6
- "version": "0.14.12",
6
+ "version": "0.14.13",
7
7
  "path": "framework/build",
8
8
  "deps": [
9
9
  "@uniweb/content-reader",
@@ -25,7 +25,7 @@
25
25
  "deps": []
26
26
  },
27
27
  "@uniweb/core": {
28
- "version": "0.7.12",
28
+ "version": "0.7.13",
29
29
  "path": "framework/core",
30
30
  "deps": [
31
31
  "@uniweb/semantic-parser",
@@ -43,7 +43,7 @@
43
43
  "deps": []
44
44
  },
45
45
  "@uniweb/kit": {
46
- "version": "0.9.16",
46
+ "version": "0.9.17",
47
47
  "path": "framework/kit",
48
48
  "deps": [
49
49
  "@uniweb/core",
@@ -61,7 +61,7 @@
61
61
  "deps": []
62
62
  },
63
63
  "@uniweb/runtime": {
64
- "version": "0.8.17",
64
+ "version": "0.8.18",
65
65
  "path": "framework/runtime",
66
66
  "deps": [
67
67
  "@uniweb/core",
@@ -89,17 +89,17 @@
89
89
  "deps": []
90
90
  },
91
91
  "@uniweb/templates": {
92
- "version": "0.7.40",
92
+ "version": "0.7.41",
93
93
  "path": "framework/templates",
94
94
  "deps": []
95
95
  },
96
96
  "@uniweb/theming": {
97
- "version": "0.1.3",
97
+ "version": "0.1.4",
98
98
  "path": "framework/theming",
99
99
  "deps": []
100
100
  },
101
101
  "@uniweb/unipress": {
102
- "version": "0.4.16",
102
+ "version": "0.4.17",
103
103
  "path": "framework/unipress",
104
104
  "deps": [
105
105
  "@uniweb/build",
package/src/index.js CHANGED
@@ -1105,7 +1105,7 @@ ${colors.bright}Options:${colors.reset}
1105
1105
  --dry-run Resolve site.yml + foundation/runtime; print summary; no writes
1106
1106
  --no-auto-publish Don't auto-publish workspace-local foundation as part of deploy
1107
1107
  --no-save Skip the auto-save of lastDeploy in deploy.yml
1108
- --local Internal: target the unicloud mock (see workspace root CLAUDE.md)
1108
+ --backend <url> Override the default backend origin (\$UNIWEB_REGISTER_URL or built-in)
1109
1109
  --non-interactive Fail with usage info instead of prompting
1110
1110
 
1111
1111
  ${colors.bright}Auth:${colors.reset}
package/src/versions.js CHANGED
@@ -40,6 +40,18 @@ const __dirname = dirname(fileURLToPath(import.meta.url))
40
40
  // Cache for resolved versions
41
41
  let resolvedVersions = null
42
42
 
43
+ /**
44
+ * The React major the framework standardizes on.
45
+ *
46
+ * React 19 changed the element marker symbol, so mixing React 18 and 19
47
+ * across the foundation ↔ runtime SSR boundary breaks rendering. The whole
48
+ * toolchain therefore pins a single major. This is the one place that value
49
+ * lives: scaffolded `package.json` files reference it through the
50
+ * `{{version "react"}}` / `{{version "react-dom"}}` template helpers instead
51
+ * of hardcoding a range.
52
+ */
53
+ export const REACT_VERSION = '^19.0.0'
54
+
43
55
  /**
44
56
  * Get the CLI's own package.json
45
57
  */
@@ -285,6 +297,10 @@ export function getVersionsForTemplates() {
285
297
  // Full package names
286
298
  ...versions,
287
299
 
300
+ // React pinned to the framework's official major (single source: REACT_VERSION above).
301
+ react: REACT_VERSION,
302
+ 'react-dom': REACT_VERSION,
303
+
288
304
  // Simplified names for templates (e.g., {{versions.build}})
289
305
  build: versions['@uniweb/build'],
290
306
  runtime: versions['@uniweb/runtime'],
@@ -22,8 +22,8 @@
22
22
  "preview": "vite preview"
23
23
  },
24
24
  "peerDependencies": {
25
- "react": "^18.0.0 || ^19.0.0",
26
- "react-dom": "^18.0.0 || ^19.0.0"
25
+ "react": "{{version "react"}}",
26
+ "react-dom": "{{version "react-dom"}}"
27
27
  },
28
28
  "dependencies": {
29
29
  "@uniweb/core": "{{version "@uniweb/core"}}",
@@ -33,8 +33,8 @@
33
33
  "@tailwindcss/vite": "^4.0.0",
34
34
  "@uniweb/build": "{{version "@uniweb/build"}}",
35
35
  "@vitejs/plugin-react": "^5.0.0",
36
- "react": "^19.0.0",
37
- "react-dom": "^19.0.0",
36
+ "react": "{{version "react"}}",
37
+ "react-dom": "{{version "react-dom"}}",
38
38
  "tailwindcss": "^4.0.0",
39
39
  "vite": "^7.0.0",
40
40
  "vite-plugin-svgr": "^4.2.0"
@@ -17,8 +17,8 @@
17
17
  "@tailwindcss/vite": "^4.0.0",
18
18
  "@uniweb/build": "{{version "@uniweb/build"}}",
19
19
  "@vitejs/plugin-react": "^5.0.0",
20
- "react": "^19.0.0",
21
- "react-dom": "^19.0.0",
20
+ "react": "{{version "react"}}",
21
+ "react-dom": "{{version "react-dom"}}",
22
22
  "react-router-dom": "^7.0.0",
23
23
  "tailwindcss": "^4.0.0",
24
24
  "vite": "^7.0.0",
@@ -16,8 +16,5 @@
16
16
  "devDependencies": {
17
17
  "@types/node": "^22.0.0",
18
18
  "uniweb": "{{version "uniweb"}}"
19
- },
20
- "pnpm": {
21
- "onlyBuiltDependencies": ["esbuild", "sharp"]
22
19
  }
23
20
  }
@@ -2,3 +2,9 @@ packages:
2
2
  {{#each workspaceGlobs}}
3
3
  - "{{this}}"
4
4
  {{/each}}
5
+
6
+ # Permit install scripts for these native deps. pnpm reads this from
7
+ # pnpm-workspace.yaml; the package.json "pnpm" field is ignored by pnpm 11+.
8
+ onlyBuiltDependencies:
9
+ - esbuild
10
+ - sharp