uniweb 0.13.17 → 0.14.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/README.md +1 -1
- package/package.json +7 -7
- package/partials/agents.md +51 -0
- package/src/backend/client.js +173 -40
- package/src/backend/data-bundle.js +15 -3
- package/src/backend/foundation-bring-along.js +76 -21
- package/src/backend/payment-handoff.js +28 -9
- package/src/backend/site-media.js +147 -19
- package/src/backend/site-sync.js +362 -40
- package/src/commands/add.js +575 -273
- package/src/commands/build.js +160 -57
- package/src/commands/clone.js +79 -26
- package/src/commands/content.js +11 -7
- package/src/commands/deploy.js +80 -32
- package/src/commands/dev.js +39 -17
- package/src/commands/docs.js +44 -16
- package/src/commands/doctor.js +338 -82
- package/src/commands/export.js +15 -7
- package/src/commands/handoff.js +6 -2
- package/src/commands/i18n.js +248 -99
- package/src/commands/inspect.js +48 -19
- package/src/commands/invite.js +9 -3
- package/src/commands/org.js +19 -5
- package/src/commands/publish.js +229 -60
- package/src/commands/pull.js +157 -48
- package/src/commands/push.js +144 -42
- package/src/commands/refresh.js +37 -10
- package/src/commands/register.js +235 -64
- package/src/commands/rename.js +126 -46
- package/src/commands/runtime.js +74 -24
- package/src/commands/status.js +48 -15
- package/src/commands/sync.js +7 -2
- package/src/commands/template.js +12 -4
- package/src/commands/update.js +263 -87
- package/src/commands/validate.js +115 -32
- package/src/framework-index.json +15 -13
- package/src/index.js +298 -145
- package/src/templates/fetchers/github.js +7 -7
- package/src/templates/fetchers/npm.js +3 -3
- package/src/templates/fetchers/release.js +21 -18
- package/src/templates/index.js +34 -12
- package/src/templates/processor.js +44 -12
- package/src/templates/resolver.js +42 -15
- package/src/templates/validator.js +14 -7
- package/src/utils/asset-upload.js +90 -22
- package/src/utils/code-upload.js +62 -37
- package/src/utils/config.js +31 -10
- package/src/utils/dep-survey.js +33 -7
- package/src/utils/destination-prompt.js +27 -17
- package/src/utils/git.js +66 -22
- package/src/utils/host-prompt.js +4 -3
- package/src/utils/install-integrity.js +8 -4
- package/src/utils/interactive.js +3 -3
- package/src/utils/json-file.js +6 -2
- package/src/utils/names.js +15 -6
- package/src/utils/placement.js +16 -8
- package/src/utils/registry-auth.js +185 -57
- package/src/utils/registry-orgs.js +142 -53
- package/src/utils/runtime-upload.js +52 -14
- package/src/utils/scaffold.js +55 -14
- package/src/utils/site-content-refs.js +3 -1
- package/src/utils/update-check.js +43 -17
- package/src/utils/workspace.js +13 -7
- package/src/versions.js +18 -7
- package/templates/site/_gitignore +5 -0
package/src/commands/register.js
CHANGED
|
@@ -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 {
|
|
64
|
+
import {
|
|
65
|
+
classifyPackage,
|
|
66
|
+
isSchemasPackage,
|
|
67
|
+
collectStandaloneSchemas
|
|
68
|
+
} from '@uniweb/build'
|
|
65
69
|
import { readRegistryAuth } from '../utils/registry-auth.js'
|
|
66
|
-
import {
|
|
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 {
|
|
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',
|
|
75
|
-
|
|
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) => {
|
|
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) => {
|
|
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('-'))
|
|
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(
|
|
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(
|
|
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
|
|
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)
|
|
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) {
|
|
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(
|
|
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 =
|
|
190
|
-
|
|
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 =
|
|
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 (
|
|
201
|
-
|
|
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(
|
|
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({
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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', {
|
|
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({
|
|
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(
|
|
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(
|
|
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
|
|
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({
|
|
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
|
|
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(
|
|
428
|
+
info(
|
|
429
|
+
`${colors.bright}${readPkgName(targetDir) || 'schemas'}${colors.reset} ${colors.dim}(schemas-only — no foundation)${colors.reset}`
|
|
430
|
+
)
|
|
335
431
|
} else {
|
|
336
|
-
info(
|
|
432
|
+
info(
|
|
433
|
+
`${colors.bright}${schema._self.name}@${schema._self.version}${colors.reset}`
|
|
434
|
+
)
|
|
337
435
|
}
|
|
338
|
-
log(
|
|
339
|
-
|
|
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(
|
|
457
|
+
info(
|
|
458
|
+
`Would then deliver ${distFiles.length} code file(s) (meta/ excluded):`
|
|
459
|
+
)
|
|
357
460
|
for (const f of distFiles) {
|
|
358
|
-
log(
|
|
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(
|
|
367
|
-
|
|
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(
|
|
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 {
|
|
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(
|
|
511
|
+
info(
|
|
512
|
+
`${colors.dim}Schema for this version is already registered — resuming code delivery.${colors.reset}`
|
|
513
|
+
)
|
|
397
514
|
} else {
|
|
398
|
-
error(
|
|
515
|
+
error(
|
|
516
|
+
`Registry rejected the submission: HTTP ${res.status} ${res.statusText}`
|
|
517
|
+
)
|
|
399
518
|
if (res.status === 401 || res.status === 403) {
|
|
400
|
-
log(
|
|
401
|
-
|
|
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(
|
|
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(
|
|
562
|
+
log(
|
|
563
|
+
` ${colors.red}${f.path}${colors.reset} ${colors.dim}HTTP ${f.status} ${f.detail}${colors.reset}`
|
|
564
|
+
)
|
|
438
565
|
}
|
|
439
|
-
log(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
464
|
-
|
|
465
|
-
|
|
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)
|
|
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))
|
|
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
|
|
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
|
|
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) => ({
|
|
491
|
-
|
|
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
|
}
|