uniweb 0.13.17 → 0.14.1

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.
Files changed (65) hide show
  1. package/README.md +1 -1
  2. package/package.json +7 -7
  3. package/partials/agents.md +51 -0
  4. package/src/backend/client.js +173 -40
  5. package/src/backend/data-bundle.js +15 -3
  6. package/src/backend/foundation-bring-along.js +76 -21
  7. package/src/backend/payment-handoff.js +28 -9
  8. package/src/backend/site-media.js +147 -19
  9. package/src/backend/site-sync.js +362 -40
  10. package/src/commands/add.js +575 -273
  11. package/src/commands/build.js +160 -57
  12. package/src/commands/clone.js +79 -26
  13. package/src/commands/content.js +11 -7
  14. package/src/commands/deploy.js +80 -32
  15. package/src/commands/dev.js +39 -17
  16. package/src/commands/docs.js +44 -16
  17. package/src/commands/doctor.js +338 -82
  18. package/src/commands/export.js +15 -7
  19. package/src/commands/handoff.js +6 -2
  20. package/src/commands/i18n.js +248 -99
  21. package/src/commands/inspect.js +48 -19
  22. package/src/commands/invite.js +9 -3
  23. package/src/commands/org.js +19 -5
  24. package/src/commands/publish.js +229 -60
  25. package/src/commands/pull.js +157 -48
  26. package/src/commands/push.js +144 -42
  27. package/src/commands/refresh.js +37 -10
  28. package/src/commands/register.js +235 -64
  29. package/src/commands/rename.js +126 -46
  30. package/src/commands/runtime.js +74 -24
  31. package/src/commands/status.js +48 -15
  32. package/src/commands/sync.js +7 -2
  33. package/src/commands/template.js +12 -4
  34. package/src/commands/update.js +263 -87
  35. package/src/commands/validate.js +115 -32
  36. package/src/framework-index.json +15 -13
  37. package/src/index.js +298 -145
  38. package/src/templates/fetchers/github.js +7 -7
  39. package/src/templates/fetchers/npm.js +3 -3
  40. package/src/templates/fetchers/release.js +21 -18
  41. package/src/templates/index.js +34 -12
  42. package/src/templates/processor.js +44 -12
  43. package/src/templates/resolver.js +42 -15
  44. package/src/templates/validator.js +14 -7
  45. package/src/utils/asset-upload.js +90 -22
  46. package/src/utils/code-upload.js +62 -37
  47. package/src/utils/config.js +31 -10
  48. package/src/utils/dep-survey.js +33 -7
  49. package/src/utils/destination-prompt.js +27 -17
  50. package/src/utils/git.js +66 -22
  51. package/src/utils/host-prompt.js +4 -3
  52. package/src/utils/install-integrity.js +8 -4
  53. package/src/utils/interactive.js +3 -3
  54. package/src/utils/json-file.js +6 -2
  55. package/src/utils/names.js +15 -6
  56. package/src/utils/placement.js +16 -8
  57. package/src/utils/registry-auth.js +185 -57
  58. package/src/utils/registry-orgs.js +142 -53
  59. package/src/utils/runtime-upload.js +52 -14
  60. package/src/utils/scaffold.js +55 -14
  61. package/src/utils/site-content-refs.js +3 -1
  62. package/src/utils/update-check.js +43 -17
  63. package/src/utils/workspace.js +13 -7
  64. package/src/versions.js +18 -7
  65. package/templates/site/_gitignore +5 -0
@@ -61,18 +61,34 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs'
61
61
  import { execSync } from 'node:child_process'
62
62
  import { resolve, join } from 'node:path'
63
63
  import { buildRegistryPackage, buildSchemaOnlyPackage } from '@uniweb/build/uwx'
64
- import { classifyPackage, isSchemasPackage, collectStandaloneSchemas } from '@uniweb/build'
64
+ import {
65
+ classifyPackage,
66
+ isSchemasPackage,
67
+ collectStandaloneSchemas
68
+ } from '@uniweb/build'
65
69
  import { readRegistryAuth } from '../utils/registry-auth.js'
66
- import { collectDistFiles, computeFoundationDigest } from '../utils/code-upload.js'
70
+ import {
71
+ collectDistFiles,
72
+ computeFoundationDigest
73
+ } from '../utils/code-upload.js'
67
74
  import { deriveScope } from '../utils/registry-orgs.js'
68
75
  import { BackendClient } from '../backend/client.js'
69
76
  import { writeJsonPreservingStyleAsync } from '../utils/json-file.js'
70
- import { findWorkspaceRoot, findFoundations, promptSelect } from '../utils/workspace.js'
77
+ import {
78
+ findWorkspaceRoot,
79
+ findFoundations,
80
+ promptSelect
81
+ } from '../utils/workspace.js'
71
82
  import { isNonInteractive, getCliPrefix } from '../utils/interactive.js'
72
83
 
73
84
  const colors = {
74
- reset: '\x1b[0m', bright: '\x1b[1m', dim: '\x1b[2m',
75
- red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[36m',
85
+ reset: '\x1b[0m',
86
+ bright: '\x1b[1m',
87
+ dim: '\x1b[2m',
88
+ red: '\x1b[31m',
89
+ green: '\x1b[32m',
90
+ yellow: '\x1b[33m',
91
+ blue: '\x1b[36m'
76
92
  }
77
93
  // Porcelain (`--json`) mode: stdout carries ONLY the final compact JSON line, so
78
94
  // all human/colored output diverts to stderr. `emitJson` writes to the REAL
@@ -83,15 +99,22 @@ let jsonEmitted = false
83
99
  let lastError = null
84
100
  const log = (...a) => (jsonMode ? console.error(...a) : console.log(...a))
85
101
  const success = (m) => log(`${colors.green}✓${colors.reset} ${m}`)
86
- const error = (m) => { lastError = String(m); console.error(`${colors.red}✗${colors.reset} ${m}`) }
102
+ const error = (m) => {
103
+ lastError = String(m)
104
+ console.error(`${colors.red}✗${colors.reset} ${m}`)
105
+ }
87
106
  const info = (m) => log(`${colors.blue}→${colors.reset} ${m}`)
88
- const emitJson = (obj) => { jsonEmitted = true; process.stdout.write(JSON.stringify(obj) + '\n') }
107
+ const emitJson = (obj) => {
108
+ jsonEmitted = true
109
+ process.stdout.write(JSON.stringify(obj) + '\n')
110
+ }
89
111
 
90
112
  function flagValue(args, name) {
91
113
  const eq = args.find((a) => a.startsWith(`${name}=`))
92
114
  if (eq) return eq.slice(name.length + 1)
93
115
  const i = args.indexOf(name)
94
- if (i !== -1 && args[i + 1] && !args[i + 1].startsWith('-')) return args[i + 1]
116
+ if (i !== -1 && args[i + 1] && !args[i + 1].startsWith('-'))
117
+ return args[i + 1]
95
118
  return null
96
119
  }
97
120
 
@@ -99,7 +122,9 @@ function flagValue(args, name) {
99
122
  // package.json isn't reachable.
100
123
  function cliVersion() {
101
124
  try {
102
- return JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8')).version
125
+ return JSON.parse(
126
+ readFileSync(new URL('../../package.json', import.meta.url), 'utf8')
127
+ ).version
103
128
  } catch {
104
129
  return '0.0.0'
105
130
  }
@@ -109,7 +134,9 @@ function cliVersion() {
109
134
  // (`{ "uniweb": { "scope": "@acme" } }`) — the default when `--scope` is absent.
110
135
  function readPkgScope(foundationDir) {
111
136
  try {
112
- const pkg = JSON.parse(readFileSync(join(foundationDir, 'package.json'), 'utf8'))
137
+ const pkg = JSON.parse(
138
+ readFileSync(join(foundationDir, 'package.json'), 'utf8')
139
+ )
113
140
  return pkg?.uniweb?.scope || null
114
141
  } catch {
115
142
  return null
@@ -130,7 +157,9 @@ async function writePkgScope(foundationDir, scope) {
130
157
  // register has no foundation `_self` to name, so it labels by package name.
131
158
  function readPkgName(dir) {
132
159
  try {
133
- return JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8'))?.name || null
160
+ return (
161
+ JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8'))?.name || null
162
+ )
134
163
  } catch {
135
164
  return null
136
165
  }
@@ -152,16 +181,24 @@ async function resolveFoundationDir(args) {
152
181
  if (foundations.length > 1) {
153
182
  if (isNonInteractive(args)) {
154
183
  error('Multiple foundations found. Run register from the one you mean.')
155
- for (const f of foundations) log(` ${colors.cyan || ''}cd ${f} && ${getCliPrefix()} register${colors.reset}`)
184
+ for (const f of foundations)
185
+ log(
186
+ ` ${colors.cyan || ''}cd ${f} && ${getCliPrefix()} register${colors.reset}`
187
+ )
156
188
  process.exit(1)
157
189
  }
158
190
  const choice = await promptSelect('Which foundation?', foundations)
159
- if (!choice) { log('\nRegister cancelled.'); process.exit(0) }
191
+ if (!choice) {
192
+ log('\nRegister cancelled.')
193
+ process.exit(0)
194
+ }
160
195
  return resolve(workspaceRoot, choice)
161
196
  }
162
197
  }
163
198
 
164
- error('No foundation found. Run register from a foundation directory or a workspace that has one.')
199
+ error(
200
+ 'No foundation found. Run register from a foundation directory or a workspace that has one.'
201
+ )
165
202
  process.exit(1)
166
203
  }
167
204
 
@@ -186,19 +223,31 @@ export function foundationNeedsBuild(targetDir) {
186
223
  const distDir = join(targetDir, 'dist')
187
224
  const schemaPath = join(distDir, 'meta', 'schema.json')
188
225
  // @uniweb/build emits dist/entry.js; older builds emitted dist/foundation.js.
189
- const hasArtifact = existsSync(join(distDir, 'entry.js')) || existsSync(join(distDir, 'foundation.js'))
190
- if (!hasArtifact || !existsSync(schemaPath)) return { needs: true, reason: 'no dist/ found' }
226
+ const hasArtifact =
227
+ existsSync(join(distDir, 'entry.js')) ||
228
+ existsSync(join(distDir, 'foundation.js'))
229
+ if (!hasArtifact || !existsSync(schemaPath))
230
+ return { needs: true, reason: 'no dist/ found' }
191
231
  let pkgVersion = null
192
232
  try {
193
- pkgVersion = JSON.parse(readFileSync(join(targetDir, 'package.json'), 'utf8'))?.version || null
233
+ pkgVersion =
234
+ JSON.parse(readFileSync(join(targetDir, 'package.json'), 'utf8'))
235
+ ?.version || null
194
236
  } catch {
195
237
  // No readable package.json version — fall through; a present schema with no
196
238
  // version to compare is treated as fresh (the submit path validates names).
197
239
  }
198
240
  try {
199
241
  const peek = JSON.parse(readFileSync(schemaPath, 'utf8'))
200
- if (peek?._self?.version && pkgVersion && peek._self.version !== pkgVersion) {
201
- return { needs: true, reason: `package.json version (${pkgVersion}) differs from built schema (${peek._self.version})` }
242
+ if (
243
+ peek?._self?.version &&
244
+ pkgVersion &&
245
+ peek._self.version !== pkgVersion
246
+ ) {
247
+ return {
248
+ needs: true,
249
+ reason: `package.json version (${pkgVersion}) differs from built schema (${peek._self.version})`
250
+ }
202
251
  }
203
252
  } catch {
204
253
  return { needs: true, reason: 'dist/meta/schema.json could not be parsed' }
@@ -215,7 +264,15 @@ export async function register(args = []) {
215
264
  // emits its own; here we cover the error / early-return paths so a scripted
216
265
  // caller can always JSON.parse(stdout).
217
266
  if (jsonMode && !jsonEmitted) {
218
- emitJson(result?.exitCode === 0 ? { ok: true, entities: [] } : { ok: false, error: lastError || `register failed (exit ${result?.exitCode ?? 1})` })
267
+ emitJson(
268
+ result?.exitCode === 0
269
+ ? { ok: true, entities: [] }
270
+ : {
271
+ ok: false,
272
+ error:
273
+ lastError || `register failed (exit ${result?.exitCode ?? 1})`
274
+ }
275
+ )
219
276
  }
220
277
  return result
221
278
  }
@@ -227,20 +284,31 @@ async function runRegister(args = []) {
227
284
  const tokenFlag = flagValue(args, '--token')
228
285
  // Origin: --backend and --registry are aliases (matches deploy/publish + the
229
286
  // origin-selection convention); either overrides UNIWEB_REGISTER_URL / default.
230
- const client = new BackendClient({ originFlag: flagValue(args, '--backend') || flagValue(args, '--registry'), token: tokenFlag, args, command: 'Registering' })
287
+ const client = new BackendClient({
288
+ originFlag: flagValue(args, '--backend') || flagValue(args, '--registry'),
289
+ token: tokenFlag,
290
+ args,
291
+ command: 'Registering'
292
+ })
231
293
 
232
294
  // Target: a schemas-only package (standalone data-schema register) or a
233
295
  // foundation (foundation + the schemas it renders). A schemas package is only
234
296
  // detected when the cwd isn't a foundation, so the foundation path — including
235
297
  // its workspace-scan + prompt (resolveFoundationDir) — is unchanged.
236
298
  const standalone = isSchemasPackage(process.cwd())
237
- const targetDir = standalone ? process.cwd() : await resolveFoundationDir(args)
299
+ const targetDir = standalone
300
+ ? process.cwd()
301
+ : await resolveFoundationDir(args)
238
302
 
239
303
  // Scope: --scope flag, else package.json `uniweb.scope`, else (real submit
240
304
  // only) derived from login membership in the bootstrap below.
241
305
  const pkgScope = readPkgScope(targetDir)
242
306
  let scope = scopeFlag || pkgScope
243
- let scopeSource = scopeFlag ? '--scope' : pkgScope ? 'package.json uniweb.scope' : null
307
+ let scopeSource = scopeFlag
308
+ ? '--scope'
309
+ : pkgScope
310
+ ? 'package.json uniweb.scope'
311
+ : null
244
312
  const isPreview = !!output || dryRun
245
313
 
246
314
  // Each path supplies a different schema source: the standalone path discovers
@@ -256,7 +324,9 @@ async function runRegister(args = []) {
256
324
  }
257
325
  if (!schemas || Object.keys(schemas).length === 0) {
258
326
  error('No data schemas found in this package.')
259
- log(` ${colors.dim}Expected a package that exports schemas (getSchema / schemas), or a schemas/ directory of *.yml files.${colors.reset}`)
327
+ log(
328
+ ` ${colors.dim}Expected a package that exports schemas (getSchema / schemas), or a schemas/ directory of *.yml files.${colors.reset}`
329
+ )
260
330
  return { exitCode: 2 }
261
331
  }
262
332
  } else {
@@ -268,12 +338,17 @@ async function runRegister(args = []) {
268
338
  if (needs) {
269
339
  if (isPreview) {
270
340
  error(`No usable build (${reason}).`)
271
- log(` Build the foundation first: ${colors.bright}uniweb build${colors.reset}`)
341
+ log(
342
+ ` Build the foundation first: ${colors.bright}uniweb build${colors.reset}`
343
+ )
272
344
  return { exitCode: 2 }
273
345
  }
274
346
  info(`${reason} — building the foundation first …`)
275
347
  try {
276
- execSync('npx uniweb build --target foundation', { cwd: targetDir, stdio: 'inherit' })
348
+ execSync('npx uniweb build --target foundation', {
349
+ cwd: targetDir,
350
+ stdio: 'inherit'
351
+ })
277
352
  } catch (err) {
278
353
  error(`Build failed: ${err.message}`)
279
354
  return { exitCode: 2 }
@@ -298,15 +373,24 @@ async function runRegister(args = []) {
298
373
  if (!scope && !isPreview) {
299
374
  const token = await client.token()
300
375
  const sess = await readRegistryAuth()
301
- const derived = await deriveScope({ apiBase: client.origin, token, accountHandle: sess?.handle || null, args })
376
+ const derived = await deriveScope({
377
+ apiBase: client.origin,
378
+ token,
379
+ accountHandle: sess?.handle || null,
380
+ args
381
+ })
302
382
  if (!derived) return { exitCode: 0 }
303
383
  scope = `@${derived}`
304
384
  scopeSource = 'login'
305
385
  try {
306
386
  await writePkgScope(targetDir, scope)
307
- info(`Saved ${colors.bright}${scope}${colors.reset} as this ${standalone ? 'package' : 'foundation'}'s publish scope (package.json).`)
387
+ info(
388
+ `Saved ${colors.bright}${scope}${colors.reset} as this ${standalone ? 'package' : 'foundation'}'s publish scope (package.json).`
389
+ )
308
390
  } catch {
309
- log(` ${colors.dim}(Could not save the scope to package.json — pass --scope ${scope} next time.)${colors.reset}`)
391
+ log(
392
+ ` ${colors.dim}(Could not save the scope to package.json — pass --scope ${scope} next time.)${colors.reset}`
393
+ )
310
394
  }
311
395
  }
312
396
 
@@ -314,29 +398,46 @@ async function runRegister(args = []) {
314
398
  // register ships (shipping-model.md §4.1). Rides in the foundation-schema
315
399
  // entity's info.digest; the backend stores it opaque and returns it so
316
400
  // publish/status can detect "code changed since release" with no local state.
317
- const digest = standalone ? null : computeFoundationDigest(join(targetDir, 'dist'))
401
+ const digest = standalone
402
+ ? null
403
+ : computeFoundationDigest(join(targetDir, 'dist'))
318
404
 
319
405
  const exporter = { tool: 'uniweb', version: cliVersion(), instance: 'build' }
320
406
  let doc
321
407
  try {
322
408
  doc = standalone
323
409
  ? buildSchemaOnlyPackage({ schemas, scope, exporter })
324
- : buildRegistryPackage({ schema, foundationDir: targetDir, scope, exporter, digest })
410
+ : buildRegistryPackage({
411
+ schema,
412
+ foundationDir: targetDir,
413
+ scope,
414
+ exporter,
415
+ digest
416
+ })
325
417
  } catch (err) {
326
418
  error(`Could not assemble the .uwx: ${err.message}`)
327
419
  return { exitCode: 2 }
328
420
  }
329
421
  const json = JSON.stringify(doc, null, 2)
330
422
 
331
- const defined = doc.entities.filter((e) => e.model === '@uniweb/data-schema').map((e) => e.name)
423
+ const defined = doc.entities
424
+ .filter((e) => e.model === '@uniweb/data-schema')
425
+ .map((e) => e.name)
332
426
  log('')
333
427
  if (standalone) {
334
- info(`${colors.bright}${readPkgName(targetDir) || 'schemas'}${colors.reset} ${colors.dim}(schemas-only — no foundation)${colors.reset}`)
428
+ info(
429
+ `${colors.bright}${readPkgName(targetDir) || 'schemas'}${colors.reset} ${colors.dim}(schemas-only — no foundation)${colors.reset}`
430
+ )
335
431
  } else {
336
- info(`${colors.bright}${schema._self.name}@${schema._self.version}${colors.reset}`)
432
+ info(
433
+ `${colors.bright}${schema._self.name}@${schema._self.version}${colors.reset}`
434
+ )
337
435
  }
338
- log(` ${colors.dim}data schemas ${standalone ? 'registered' : 'defined'}: ${defined.length ? defined.join(', ') : '(none)'}${colors.reset}`)
339
- if (scope) log(` ${colors.dim}scope: ${scope} (${scopeSource})${colors.reset}`)
436
+ log(
437
+ ` ${colors.dim}data schemas ${standalone ? 'registered' : 'defined'}: ${defined.length ? defined.join(', ') : '(none)'}${colors.reset}`
438
+ )
439
+ if (scope)
440
+ log(` ${colors.dim}scope: ${scope} (${scopeSource})${colors.reset}`)
340
441
  if (digest) log(` ${colors.dim}digest: ${digest}${colors.reset}`)
341
442
 
342
443
  // Preview paths — no submit, no auth needed.
@@ -353,9 +454,13 @@ async function runRegister(args = []) {
353
454
  if (!standalone && !args.includes('--schema-only')) {
354
455
  const distFiles = collectDistFiles(join(targetDir, 'dist'))
355
456
  log('')
356
- info(`Would then deliver ${distFiles.length} code file(s) (meta/ excluded):`)
457
+ info(
458
+ `Would then deliver ${distFiles.length} code file(s) (meta/ excluded):`
459
+ )
357
460
  for (const f of distFiles) {
358
- log(` ${colors.dim}${f.path} ${f.size} bytes ${f.content_type}${colors.reset}`)
461
+ log(
462
+ ` ${colors.dim}${f.path} ${f.size} bytes ${f.content_type}${colors.reset}`
463
+ )
359
464
  }
360
465
  }
361
466
  return { exitCode: 0 }
@@ -363,8 +468,12 @@ async function runRegister(args = []) {
363
468
 
364
469
  // Submit requires a concrete scope — the registry rejects @/… fail-closed.
365
470
  if (!scope) {
366
- error('No publish scope — set "uniweb.scope" in package.json, or pass --scope @org.')
367
- log(` ${colors.dim}Without a scope, names stay @/… and the registry rejects them.${colors.reset}`)
471
+ error(
472
+ 'No publish scope set "uniweb.scope" in package.json, or pass --scope @org.'
473
+ )
474
+ log(
475
+ ` ${colors.dim}Without a scope, names stay @/… and the registry rejects them.${colors.reset}`
476
+ )
368
477
  return { exitCode: 2 }
369
478
  }
370
479
  // Submit — the client carries the bearer (--token › UNIWEB_TOKEN › stored
@@ -375,14 +484,20 @@ async function runRegister(args = []) {
375
484
  res = await client.register(json)
376
485
  } catch (err) {
377
486
  error(`Could not reach the registry at ${client.origin}: ${err.message}`)
378
- log(` ${colors.dim}Set the endpoint with --backend/--registry <url> or UNIWEB_REGISTER_URL.${colors.reset}`)
487
+ log(
488
+ ` ${colors.dim}Set the endpoint with --backend/--registry <url> or UNIWEB_REGISTER_URL.${colors.reset}`
489
+ )
379
490
  return { exitCode: 2 }
380
491
  }
381
492
  // Read the response body once: the --json success path needs it for the minted
382
493
  // entity ids; the error path shows it.
383
494
  const rawBody = await res.text().catch(() => '')
384
495
  let parsedBody = null
385
- try { parsedBody = rawBody ? JSON.parse(rawBody) : null } catch { parsedBody = null }
496
+ try {
497
+ parsedBody = rawBody ? JSON.parse(rawBody) : null
498
+ } catch {
499
+ parsedBody = null
500
+ }
386
501
  let alreadyRegistered = false
387
502
  if (!res.ok) {
388
503
  // Resume path: a registered version is immutable, so re-running after a
@@ -393,12 +508,20 @@ async function runRegister(args = []) {
393
508
  const isDuplicate = !standalone && res.status === 409
394
509
  if (isDuplicate) {
395
510
  alreadyRegistered = true
396
- info(`${colors.dim}Schema for this version is already registered — resuming code delivery.${colors.reset}`)
511
+ info(
512
+ `${colors.dim}Schema for this version is already registered — resuming code delivery.${colors.reset}`
513
+ )
397
514
  } else {
398
- error(`Registry rejected the submission: HTTP ${res.status} ${res.statusText}`)
515
+ error(
516
+ `Registry rejected the submission: HTTP ${res.status} ${res.statusText}`
517
+ )
399
518
  if (res.status === 401 || res.status === 403) {
400
- log(` ${colors.dim}The registry didn't accept your credentials — it may use different ones than \`uniweb login\`.${colors.reset}`)
401
- log(` ${colors.dim}Supply a registry bearer with --token <bearer> (or UNIWEB_TOKEN); an existing one may be wrong or expired.${colors.reset}`)
519
+ log(
520
+ ` ${colors.dim}The registry didn't accept your credentials it may use different ones than \`uniweb login\`.${colors.reset}`
521
+ )
522
+ log(
523
+ ` ${colors.dim}Supply a registry bearer with --token <bearer> (or UNIWEB_TOKEN); an existing one may be wrong or expired.${colors.reset}`
524
+ )
402
525
  }
403
526
  if (rawBody) log(` ${colors.dim}${rawBody.slice(0, 500)}${colors.reset}`)
404
527
  return { exitCode: 1 }
@@ -423,34 +546,57 @@ async function runRegister(args = []) {
423
546
  const bareName = schema._self.name
424
547
  const name = bareName.startsWith('@') ? bareName : `${scope}/${bareName}`
425
548
  const version = schema._self.version
426
- info(`Delivering code for ${colors.bright}${name}@${version}${colors.reset} …`)
549
+ info(
550
+ `Delivering code for ${colors.bright}${name}@${version}${colors.reset} …`
551
+ )
427
552
  try {
428
553
  const result = await client.uploadFoundationCode({
429
554
  name,
430
555
  version,
431
556
  distDir,
432
- onProgress: (m) => log(` ${colors.dim}${m}${colors.reset}`),
557
+ onProgress: (m) => log(` ${colors.dim}${m}${colors.reset}`)
433
558
  })
434
559
  if (result.failed.length) {
435
560
  error(`${result.failed.length} file(s) failed to upload:`)
436
561
  for (const f of result.failed) {
437
- log(` ${colors.red}${f.path}${colors.reset} ${colors.dim}HTTP ${f.status} ${f.detail}${colors.reset}`)
562
+ log(
563
+ ` ${colors.red}${f.path}${colors.reset} ${colors.dim}HTTP ${f.status} ${f.detail}${colors.reset}`
564
+ )
438
565
  }
439
- log(` ${colors.dim}Re-run \`uniweb register\` to resume — completed files are safe no-ops.${colors.reset}`)
566
+ log(
567
+ ` ${colors.dim}Re-run \`uniweb register\` to resume — completed files are safe no-ops.${colors.reset}`
568
+ )
440
569
  return { exitCode: 1 }
441
570
  }
442
- const where = result.serveBase || 'the registry gateway'
443
571
  if (result.verified === true) {
444
- success(`Code delivered (${result.uploaded.length} files)entry verified live at ${colors.dim}${where}${colors.reset}`)
572
+ // serveBase is guaranteed here it is what gated the check.
573
+ success(
574
+ `Code delivered (${result.uploaded.length} files) — entry verified live at ${colors.dim}${result.serveBase}${colors.reset}`
575
+ )
445
576
  } else if (result.verified === false) {
446
- error('Code uploaded but the entry verification fetch did not match — investigate before using this version.')
577
+ error(
578
+ 'Code uploaded but the entry verification fetch did not match — investigate before using this version.'
579
+ )
447
580
  return { exitCode: 1 }
448
581
  } else {
449
- success(`Code delivered (${result.uploaded.length} files, ${result.mode} mode)`)
582
+ success(
583
+ `Code delivered (${result.uploaded.length} files, ${result.mode} mode)`
584
+ )
585
+ // Say WHY nothing was checked, rather than skipping in silence. The CLI
586
+ // never reconstructs a serve URL (see utils/code-upload.js), so a plan
587
+ // without `serve_base` means the location is not ours to know — name it
588
+ // as a backend gap so it surfaces there instead of looking like a pass.
589
+ if (!result.serveBase) {
590
+ log(
591
+ ` ${colors.dim}Not verified: the backend reported no serve location for this version.${colors.reset}`
592
+ )
593
+ }
450
594
  }
451
595
  } catch (err) {
452
596
  error(`Code delivery failed: ${err.message}`)
453
- log(` ${colors.dim}The schema registration above succeeded; re-run \`uniweb register\` to deliver the code.${colors.reset}`)
597
+ log(
598
+ ` ${colors.dim}The schema registration above succeeded; re-run \`uniweb register\` to deliver the code.${colors.reset}`
599
+ )
454
600
  return { exitCode: 1 }
455
601
  }
456
602
  }
@@ -460,9 +606,15 @@ async function runRegister(args = []) {
460
606
  // you just registered it — so correct it here, not in a doc nobody's reading.
461
607
  if (!jsonMode && defined.length > 0 && scope) {
462
608
  log('')
463
- info('Registered schemas are content types authors can use in the Uniweb app.')
464
- log(` ${colors.dim}Delivery is separate: a foundation that binds ${scope}/<name> resolves it from${colors.reset}`)
465
- log(` ${colors.dim}disk — an ${scope}/schemas package, or a folder routed in schemas.config.js.${colors.reset}`)
609
+ info(
610
+ 'Registered schemas are content types authors can use in the Uniweb app.'
611
+ )
612
+ log(
613
+ ` ${colors.dim}Delivery is separate: a foundation that binds ${scope}/<name> resolves it from${colors.reset}`
614
+ )
615
+ log(
616
+ ` ${colors.dim}disk — an ${scope}/schemas package, or a folder routed in schemas.config.js.${colors.reset}`
617
+ )
466
618
  }
467
619
 
468
620
  if (jsonMode) {
@@ -475,20 +627,39 @@ async function runRegister(args = []) {
475
627
  const addMint = (e) => {
476
628
  if (!e || typeof e !== 'object') return
477
629
  const reg = e.registered ?? e
478
- if (reg.name) minted[reg.name] = { uuid: reg.payload_model_uuid ?? null, version: reg.version ?? null, unchanged: e.unchanged === true }
630
+ if (reg.name)
631
+ minted[reg.name] = {
632
+ uuid: reg.payload_model_uuid ?? null,
633
+ version: reg.version ?? null,
634
+ unchanged: e.unchanged === true
635
+ }
479
636
  }
480
637
  if (parsedBody && typeof parsedBody === 'object') {
481
- if (Array.isArray(parsedBody.data_schemas)) parsedBody.data_schemas.forEach(addMint)
638
+ if (Array.isArray(parsedBody.data_schemas))
639
+ parsedBody.data_schemas.forEach(addMint)
482
640
  if (parsedBody.foundation_schema) addMint(parsedBody.foundation_schema)
483
641
  }
484
642
  const names = [
485
- ...doc.entities.filter((e) => e.model === '@uniweb/data-schema').map((e) => e.name),
643
+ ...doc.entities
644
+ .filter((e) => e.model === '@uniweb/data-schema')
645
+ .map((e) => e.name),
486
646
  // The foundation-schema entity carries its `@scope/name` under `info`, not a
487
647
  // top-level `name` (that's the foundation-schema shape).
488
- ...doc.entities.filter((e) => e.model === '@uniweb/foundation-schema').map((e) => e.info?.name ?? e.name),
648
+ ...doc.entities
649
+ .filter((e) => e.model === '@uniweb/foundation-schema')
650
+ .map((e) => e.info?.name ?? e.name)
489
651
  ].filter(Boolean)
490
- const entities = names.map((name) => ({ name, ...(minted[name] || { uuid: null, version: null, unchanged: false }) }))
491
- emitJson({ ok: true, scope: scope || null, origin: client.origin, digest: digest || null, entities })
652
+ const entities = names.map((name) => ({
653
+ name,
654
+ ...(minted[name] || { uuid: null, version: null, unchanged: false })
655
+ }))
656
+ emitJson({
657
+ ok: true,
658
+ scope: scope || null,
659
+ origin: client.origin,
660
+ digest: digest || null,
661
+ entities
662
+ })
492
663
  }
493
664
  return { exitCode: 0 }
494
665
  }