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
@@ -46,7 +46,7 @@ import {
46
46
  recordLastDeploy,
47
47
  assembleDataBall,
48
48
  collectBallAssets,
49
- rewriteBallAssets,
49
+ rewriteBallAssets
50
50
  } from '@uniweb/build/site'
51
51
  import { emitSyncPackages } from '@uniweb/build/uwx'
52
52
  import { resolveDefaultLocale } from '@uniweb/core/locale-config'
@@ -62,30 +62,41 @@ import {
62
62
  readBaseVersions,
63
63
  readItemBaseVersions,
64
64
  ensureItemUuids,
65
- pushSyncPackages,
65
+ ensureSiteExists,
66
+ clearRemoteSyncStateIfUnbound,
67
+ pushSyncPackages
66
68
  } from '../backend/site-sync.js'
67
69
  import { uploadDataBundle } from '../backend/data-bundle.js'
68
- import { uploadSiteMedia, isStorageRefusal } from '../backend/site-media.js'
70
+ import { uploadSiteMedia, describeAssetRefusal } from '../backend/site-media.js'
69
71
  import { bringFoundationAlong } from '../backend/foundation-bring-along.js'
70
72
  import { settlePaymentIfNeeded } from '../backend/payment-handoff.js'
71
73
 
72
74
  const c = {
73
- reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
74
- cyan: '\x1b[36m', green: '\x1b[32m', yellow: '\x1b[33m', red: '\x1b[31m',
75
+ reset: '\x1b[0m',
76
+ bold: '\x1b[1m',
77
+ dim: '\x1b[2m',
78
+ cyan: '\x1b[36m',
79
+ green: '\x1b[32m',
80
+ yellow: '\x1b[33m',
81
+ red: '\x1b[31m'
75
82
  }
76
83
  const say = {
77
84
  ok: (m) => console.log(`${c.green}✓${c.reset} ${m}`),
78
85
  info: (m) => console.log(`${c.cyan}→${c.reset} ${m}`),
79
86
  warn: (m) => console.log(`${c.yellow}⚠${c.reset} ${m}`),
80
87
  err: (m) => console.error(`${c.red}✗${c.reset} ${m}`),
81
- dim: (m) => console.log(` ${c.dim}${m}${c.reset}`),
88
+ dim: (m) => console.log(` ${c.dim}${m}${c.reset}`)
82
89
  }
83
90
 
84
91
  // Minimal yes/no prompt. Returns `defaultYes` on an empty answer.
85
92
  async function confirm(question, defaultYes = false) {
86
93
  const rl = createInterface({ input: process.stdin, output: process.stdout })
87
94
  try {
88
- const a = (await rl.question(`${question} ${defaultYes ? '[Y/n]' : '[y/N]'} `)).trim().toLowerCase()
95
+ const a = (
96
+ await rl.question(`${question} ${defaultYes ? '[Y/n]' : '[y/N]'} `)
97
+ )
98
+ .trim()
99
+ .toLowerCase()
89
100
  if (!a) return defaultYes
90
101
  return a === 'y' || a === 'yes'
91
102
  } finally {
@@ -97,7 +108,9 @@ async function confirm(question, defaultYes = false) {
97
108
  // sort). Null when the list is empty.
98
109
  function pickHighestRuntime(installed) {
99
110
  if (!Array.isArray(installed) || installed.length === 0) return null
100
- return [...installed].sort((a, b) => String(b).localeCompare(String(a), undefined, { numeric: true }))[0]
111
+ return [...installed].sort((a, b) =>
112
+ String(b).localeCompare(String(a), undefined, { numeric: true })
113
+ )[0]
101
114
  }
102
115
 
103
116
  // Origin-relative serve path → clickable absolute URL (self-serve default).
@@ -122,17 +135,22 @@ function readSiteYml(path) {
122
135
  function languagesFromContent(siteContent) {
123
136
  const langs = siteContent?.config?.languages
124
137
  if (!Array.isArray(langs) || langs.length === 0) return ['en']
125
- return langs.map((l) => (typeof l === 'string' ? l : l?.value || l?.code)).filter(Boolean)
138
+ return langs
139
+ .map((l) => (typeof l === 'string' ? l : l?.value || l?.code))
140
+ .filter(Boolean)
126
141
  }
127
142
 
128
143
  // Languages from site.yml — used only for the dry-run summary (no build yet).
129
144
  function languagesFromSiteYml(siteYml) {
130
145
  // Legacy `lang:` still honored between defaultLanguage and the shared
131
146
  // `defaultLanguage || languages[0] || 'en'` rule.
132
- const def = siteYml.defaultLanguage || siteYml.lang || resolveDefaultLocale(siteYml)
147
+ const def =
148
+ siteYml.defaultLanguage || siteYml.lang || resolveDefaultLocale(siteYml)
133
149
  const locales = siteYml.i18n?.locales || siteYml.languages
134
150
  if (!Array.isArray(locales) || locales.length === 0) return null
135
- const norm = locales.map((l) => (typeof l === 'string' ? l : l?.value || l?.code)).filter(Boolean)
151
+ const norm = locales
152
+ .map((l) => (typeof l === 'string' ? l : l?.value || l?.code))
153
+ .filter(Boolean)
136
154
  return [def, ...norm.filter((l) => l !== def)]
137
155
  }
138
156
 
@@ -141,7 +159,8 @@ async function persistLastDeploy(siteDir, opts) {
141
159
  if (opts.autoSave === 'off') return
142
160
  try {
143
161
  const result = await recordLastDeploy(siteDir, opts)
144
- if (result?.created) say.dim(`Wrote deploy.yml (target: ${opts.targetName})`)
162
+ if (result?.created)
163
+ say.dim(`Wrote deploy.yml (target: ${opts.targetName})`)
145
164
  } catch (err) {
146
165
  // The publish itself succeeded — never fail the whole command on a
147
166
  // memo-write error. Surface it so the user can fix the file.
@@ -162,34 +181,49 @@ export async function publish(args = []) {
162
181
  const siteBackend = await resolveSiteBackend(siteDir)
163
182
 
164
183
  const client = new BackendClient({
165
- originFlag: readFlagValue(args, '--backend') || readFlagValue(args, '--registry'),
184
+ originFlag:
185
+ readFlagValue(args, '--backend') || readFlagValue(args, '--registry'),
166
186
  siteBackend,
167
187
  token: readFlagValue(args, '--token') || undefined,
168
188
  args,
169
- command: 'Publishing',
189
+ command: 'Publishing'
170
190
  })
171
191
 
172
192
  // Capability handshake (cached). Publish ends in a go-live, so the publish
173
193
  // lane must be offered.
174
194
  const config = await client.discover()
175
195
  if (config?.delivery && config.delivery.publish === false) {
176
- say.err(`Backend at ${client.origin} does not offer the publish lane (delivery.publish=false).`)
196
+ say.err(
197
+ `Backend at ${client.origin} does not offer the publish lane (delivery.publish=false).`
198
+ )
177
199
  return { exitCode: 1 }
178
200
  }
179
201
 
180
202
  // Runtime: an explicit site.yml::runtime pin wins; else the highest installed;
181
203
  // else fail closed (better than serving a site with no runtime). A dry-run is
182
204
  // a pure preview, so it only WARNS — it stays useful with no backend reachable.
183
- const installed = Array.isArray(config?.runtime?.installed) ? config.runtime.installed : []
184
- if (siteYml.runtime && installed.length && !installed.includes(siteYml.runtime)) {
185
- say.err(`Runtime ${siteYml.runtime} (from site.yml) is not installed on the backend.`)
186
- say.dim(`Installed: ${installed.join(', ') || '(none)'} — pin one of these in site.yml (\`runtime:\`), or have it installed on the backend.`)
205
+ const installed = Array.isArray(config?.runtime?.installed)
206
+ ? config.runtime.installed
207
+ : []
208
+ if (
209
+ siteYml.runtime &&
210
+ installed.length &&
211
+ !installed.includes(siteYml.runtime)
212
+ ) {
213
+ say.err(
214
+ `Runtime ${siteYml.runtime} (from site.yml) is not installed on the backend.`
215
+ )
216
+ say.dim(
217
+ `Installed: ${installed.join(', ') || '(none)'} — pin one of these in site.yml (\`runtime:\`), or have it installed on the backend.`
218
+ )
187
219
  if (!dryRun) return { exitCode: 1 }
188
220
  }
189
221
  const runtimeVersion = siteYml.runtime || pickHighestRuntime(installed)
190
222
  if (!runtimeVersion && !dryRun) {
191
223
  say.err('Could not resolve a runtime version.')
192
- say.dim('Pin one with `runtime:` in site.yml, or install one on the backend so /dev/config reports it.')
224
+ say.dim(
225
+ 'Pin one with `runtime:` in site.yml, or install one on the backend so /dev/config reports it.'
226
+ )
193
227
  return { exitCode: 1 }
194
228
  }
195
229
 
@@ -205,19 +239,44 @@ export async function publish(args = []) {
205
239
  resolved = resolveTarget(deployYml, null)
206
240
  } catch {
207
241
  // Malformed/ambiguous deploy.yml — don't block the publish on the memo.
208
- resolved = { targetName: 'production', host: 'uniweb', config: {}, autoSave: 'lastDeploy', fromFile: false }
242
+ resolved = {
243
+ targetName: 'production',
244
+ host: 'uniweb',
245
+ config: {},
246
+ autoSave: 'lastDeploy',
247
+ fromFile: false
248
+ }
209
249
  }
210
- const autoSave = noSave ? 'off' : (resolved.autoSave || 'lastDeploy')
250
+ const autoSave = noSave ? 'off' : resolved.autoSave || 'lastDeploy'
211
251
 
212
252
  if (dryRun) {
213
253
  say.info('Dry run — would bring the foundation along, sync, and go live:')
214
254
  say.dim(`Backend : ${client.origin}`)
215
- say.dim(`Runtime : ${runtimeVersion || '(unresolved — needs a backend or a site.yml runtime: pin)'}${runtimeVersion && !siteYml.runtime ? ' (highest installed)' : ''}`)
216
- say.dim(`site_uuid : ${siteYml.$uuid || '(nonethe first push mints it)'}`)
255
+ say.dim(
256
+ `Runtime : ${runtimeVersion || '(unresolvedneeds a backend or a site.yml runtime: pin)'}${runtimeVersion && !siteYml.runtime ? ' (highest installed)' : ''}`
257
+ )
258
+ say.dim(
259
+ `site_uuid : ${siteYml.$uuid || '(none — the site is created before anything uploads)'}`
260
+ )
217
261
  const langs = languagesFromSiteYml(siteYml)
218
262
  if (langs) say.dim(`Languages : ${langs.join(', ')}`)
219
- await bringFoundationAlong({ client, siteDir, siteYml, args, say, confirm, cliBin: process.argv[1], dryRun: true })
220
- await settlePaymentIfNeeded({ client, uuid: siteYml.$uuid || null, args, say, dryRun: true })
263
+ await bringFoundationAlong({
264
+ client,
265
+ siteDir,
266
+ siteYml,
267
+ args,
268
+ say,
269
+ confirm,
270
+ cliBin: process.argv[1],
271
+ dryRun: true
272
+ })
273
+ await settlePaymentIfNeeded({
274
+ client,
275
+ uuid: siteYml.$uuid || null,
276
+ args,
277
+ say,
278
+ dryRun: true
279
+ })
221
280
  return { exitCode: 0 }
222
281
  }
223
282
 
@@ -225,7 +284,15 @@ export async function publish(args = []) {
225
284
  // changed (or isn't registered). Never ship a site pointing at stale code.
226
285
  let fnd
227
286
  try {
228
- fnd = await bringFoundationAlong({ client, siteDir, siteYml, args, say, confirm, cliBin: process.argv[1] })
287
+ fnd = await bringFoundationAlong({
288
+ client,
289
+ siteDir,
290
+ siteYml,
291
+ args,
292
+ say,
293
+ confirm,
294
+ cliBin: process.argv[1]
295
+ })
229
296
  } catch (err) {
230
297
  say.err(`Foundation release failed: ${err.message}`)
231
298
  say.dim('Fix the foundation, then re-run `uniweb publish`.')
@@ -238,7 +305,11 @@ export async function publish(args = []) {
238
305
  // the inner build can't resolve to a different installed version.
239
306
  say.info('Building site…')
240
307
  console.log('')
241
- execSync(`node ${JSON.stringify(process.argv[1])} build --link`, { cwd: siteDir, stdio: 'inherit', env: process.env })
308
+ execSync(`node ${JSON.stringify(process.argv[1])} build --link`, {
309
+ cwd: siteDir,
310
+ stdio: 'inherit',
311
+ env: process.env
312
+ })
242
313
  console.log('')
243
314
 
244
315
  const distDir = join(siteDir, 'dist')
@@ -255,7 +326,10 @@ export async function publish(args = []) {
255
326
  // — collections with no data schema, delivered statically via the ball).
256
327
  let probe
257
328
  try {
258
- probe = await emitSyncPackages(siteDir, { ...(foundationDir ? { foundationDir } : {}), resolveModel })
329
+ probe = await emitSyncPackages(siteDir, {
330
+ ...(foundationDir ? { foundationDir } : {}),
331
+ resolveModel
332
+ })
259
333
  } catch (err) {
260
334
  say.err(`Could not build the sync package: ${err.message}`)
261
335
  return { exitCode: 1 }
@@ -263,6 +337,43 @@ export async function publish(args = []) {
263
337
  const schemalessNames = (probe.schemaless || []).map((col) => col.name)
264
338
  const localAssets = probe.localAssets || []
265
339
 
340
+ // 3a. A clone with no `$uuid` is bound to no backend site, so every cached map
341
+ // that describes one is stale — including after the documented "clear
342
+ // `$uuid` to re-publish as a new site" recovery. Must run BEFORE the create,
343
+ // which mints a uuid and would make the clone look bound.
344
+ const droppedState = clearRemoteSyncStateIfUnbound(siteDir)
345
+ if (droppedState.length) {
346
+ say.dim(
347
+ `Cleared stale sync state from a previous site (${droppedState.join(', ')}).`
348
+ )
349
+ }
350
+
351
+ // 3b. Make sure the SITE EXISTS before a single byte is uploaded.
352
+ //
353
+ // Uploaded bytes are metered against an owning entity and reclaimed by
354
+ // deleting it. Uploading before the site exists therefore produces bytes
355
+ // that are charged and can never be freed — there is nothing to delete —
356
+ // so a repeatedly-failing first publish would burn quota with no recovery
357
+ // short of support. Creating the site first makes the artifact of a failed
358
+ // publish an EMPTY SITE instead: it costs nothing to keep and the owner can
359
+ // clear it. A no-op once `$uuid` is set, so only a first publish pays.
360
+ //
361
+ // This ordering is load-bearing, not incidental — the asset plan requires an
362
+ // owner. A test asserts the create precedes the upload.
363
+ const site = await ensureSiteExists({
364
+ client,
365
+ siteDir,
366
+ name: siteYml.name,
367
+ foundation: fnd.ref || siteYml.foundation,
368
+ asOrg,
369
+ note: (m) => say.dim(m)
370
+ })
371
+ if (!site.uuid) {
372
+ say.err(`Could not create the site on the backend: ${site.reason}`)
373
+ say.dim('Nothing was uploaded and nothing was charged.')
374
+ return { exitCode: 1 }
375
+ }
376
+
266
377
  // 4. Assemble the static-data ball (schema-less data + search index) BEFORE
267
378
  // uploading, since its records can carry local media too.
268
379
  let ball = await assembleDataBall(distDir, schemalessNames)
@@ -275,10 +386,16 @@ export async function publish(args = []) {
275
386
  if (mediaRefs.length) {
276
387
  say.info('Uploading media…')
277
388
  try {
278
- const { map, failed } = await uploadSiteMedia(client, siteDir, mediaRefs, {
279
- onProgress: (m) => say.dim(` ${m}`),
280
- warn: (m) => say.dim(`! ${m}`),
281
- })
389
+ const { map, failed } = await uploadSiteMedia(
390
+ client,
391
+ siteDir,
392
+ mediaRefs,
393
+ {
394
+ siteUuid: site.uuid,
395
+ onProgress: (m) => say.dim(` ${m}`),
396
+ warn: (m) => say.dim(`! ${m}`)
397
+ }
398
+ )
282
399
  // A ref whose bytes did not land must NOT be published: the content would go
283
400
  // out still pointing at the local path, so the site ships a broken image and
284
401
  // the only trace is a warning nobody reads. A missing FILE is different —
@@ -290,15 +407,20 @@ export async function publish(args = []) {
290
407
  }
291
408
  if (Object.keys(map).length) assetRewrite = map
292
409
  if (ballAssets.length) ball = rewriteBallAssets(ball, map)
293
- say.dim(`Media : ${Object.keys(map).length}/${mediaRefs.length} ref(s) → serve URL`)
410
+ say.dim(
411
+ `Media : ${Object.keys(map).length}/${mediaRefs.length} ref(s) → serve URL`
412
+ )
294
413
  } catch (err) {
295
- if (isStorageRefusal(err)) {
296
- say.err('Storage quota reached the media in this publish does not fit.')
297
- say.dim(' Remove or shrink assets, or raise the plan limit, then re-run.')
298
- say.dim(` ${err.message}`)
299
- return { exitCode: 1 }
414
+ // A typed plan refusal gets its own account (quota, per-file cap, plan caps);
415
+ // anything else falls through to the raw message. Nothing has been pushed at
416
+ // this point, so either way the site is untouched.
417
+ const refusal = describeAssetRefusal(err)
418
+ if (refusal) {
419
+ say.err(refusal.headline)
420
+ for (const line of refusal.notes) say.dim(line)
421
+ } else {
422
+ say.err(`Media upload failed: ${err.message}`)
300
423
  }
301
- say.err(`Media upload failed: ${err.message}`)
302
424
  return { exitCode: 1 }
303
425
  }
304
426
  }
@@ -308,12 +430,24 @@ export async function publish(args = []) {
308
430
  if (ball) {
309
431
  say.info('Uploading data bundle…')
310
432
  try {
311
- dataBundle = await uploadDataBundle(client, ball, { onProgress: (m) => say.dim(` ${m}`) })
433
+ dataBundle = await uploadDataBundle(client, ball, {
434
+ siteUuid: site.uuid,
435
+ onProgress: (m) => say.dim(` ${m}`)
436
+ })
312
437
  } catch (err) {
313
- say.err(`Data bundle upload failed: ${err.message}`)
438
+ // The ball rides the same asset lane, so it hits the same typed refusals.
439
+ const refusal = describeAssetRefusal(err)
440
+ if (refusal) {
441
+ say.err(refusal.headline)
442
+ for (const line of refusal.notes) say.dim(line)
443
+ } else {
444
+ say.err(`Data bundle upload failed: ${err.message}`)
445
+ }
314
446
  return { exitCode: 1 }
315
447
  }
316
- say.dim(`Data bundle : ${Object.keys(ball.data).length} data + ${Object.keys(ball.search).length} search file(s)`)
448
+ say.dim(
449
+ `Data bundle : ${Object.keys(ball.data).length} data + ${Object.keys(ball.search).length} search file(s)`
450
+ )
317
451
  }
318
452
 
319
453
  // 5. Push the site (content + folder) over the send-only-changed cache —
@@ -323,10 +457,16 @@ export async function publish(args = []) {
323
457
  // publish rides the same gated push as `uniweb push`: if an app author has
324
458
  // edited since this clone last synced, the push is refused rather than
325
459
  // overwriting them, and nothing goes live. `--force` drops the precondition.
326
- const baseVersions = args.includes('--force') ? null : readBaseVersions(siteDir)
460
+ const baseVersions = args.includes('--force')
461
+ ? null
462
+ : readBaseVersions(siteDir)
327
463
  // Per-item identity, recovered from the backend when this clone has never seen it.
328
464
  // Without it the backend re-mints every page and section row (see readItemUuids).
329
- const itemUuids = await ensureItemUuids({ client, siteDir, note: (m) => say.dim(m) })
465
+ const itemUuids = await ensureItemUuids({
466
+ client,
467
+ siteDir,
468
+ note: (m) => say.dim(m)
469
+ })
330
470
  // Stamp deploy-derived info on the site-content entity: the data-bundle URL,
331
471
  // and the PINNED foundation ref (`@scope/name@version`) from the bring-along.
332
472
  // Delivery is version-pinned end-to-end (the gateway serves a foundation only
@@ -336,7 +476,7 @@ export async function publish(args = []) {
336
476
  // is null → the site.yml ref is forwarded verbatim (already pinned).
337
477
  const injectInfo = {
338
478
  ...(dataBundle ? { data_bundle: dataBundle } : {}),
339
- ...(fnd.ref ? { foundation: fnd.ref } : {}),
479
+ ...(fnd.ref ? { foundation: fnd.ref } : {})
340
480
  }
341
481
  let pkg
342
482
  try {
@@ -345,9 +485,11 @@ export async function publish(args = []) {
345
485
  resolveModel,
346
486
  priorHashes,
347
487
  itemUuids,
348
- ...(baseVersions ? { baseVersions, itemBaseVersions: readItemBaseVersions(siteDir) } : {}),
488
+ ...(baseVersions
489
+ ? { baseVersions, itemBaseVersions: readItemBaseVersions(siteDir) }
490
+ : {}),
349
491
  ...(Object.keys(injectInfo).length ? { injectInfo } : {}),
350
- ...(assetRewrite ? { assetRewrite } : {}),
492
+ ...(assetRewrite ? { assetRewrite } : {})
351
493
  })
352
494
  } catch (err) {
353
495
  say.err(`Could not build the sync package: ${err.message}`)
@@ -358,9 +500,15 @@ export async function publish(args = []) {
358
500
  info: (m) => say.info(m),
359
501
  note: (m) => say.dim(m),
360
502
  error: (m) => say.err(m),
361
- dim: (s) => `${c.dim}${s}${c.reset}`,
503
+ dim: (s) => `${c.dim}${s}${c.reset}`
362
504
  }
363
- const pushResult = await pushSyncPackages({ client, siteDir, pkg, asOrg, report })
505
+ const pushResult = await pushSyncPackages({
506
+ client,
507
+ siteDir,
508
+ pkg,
509
+ asOrg,
510
+ report
511
+ })
364
512
  if (pushResult.exitCode !== 0) return { exitCode: pushResult.exitCode }
365
513
  const siteUuid = pushResult.boundSiteUuid
366
514
  if (!siteUuid) {
@@ -374,7 +522,9 @@ export async function publish(args = []) {
374
522
  // decline leaves a recoverable state (re-run after paying).
375
523
  const pay = await settlePaymentIfNeeded({ client, uuid: siteUuid, args, say })
376
524
  if (!pay.proceed) {
377
- say.info('Site synced as a draft but not made live. Re-run `uniweb publish` once payment is complete.')
525
+ say.info(
526
+ 'Site synced as a draft but not made live. Re-run `uniweb publish` once payment is complete.'
527
+ )
378
528
  return { exitCode: 0 }
379
529
  }
380
530
 
@@ -384,7 +534,10 @@ export async function publish(args = []) {
384
534
  say.info(`Publishing to ${c.dim}${client.origin}${c.reset} …`)
385
535
  let pubRes
386
536
  try {
387
- pubRes = await client.publishSite(siteUuid, { runtimeVersion, ...(languages ? { languages } : {}) })
537
+ pubRes = await client.publishSite(siteUuid, {
538
+ runtimeVersion,
539
+ ...(languages ? { languages } : {})
540
+ })
388
541
  } catch (err) {
389
542
  say.err(`Could not reach the backend at ${client.origin}: ${err.message}`)
390
543
  say.dim('Set the origin with --backend <url> or UNIWEB_REGISTER_URL.')
@@ -393,14 +546,20 @@ export async function publish(args = []) {
393
546
  if (!pubRes.ok) {
394
547
  say.err(`Publish rejected: HTTP ${pubRes.status} ${pubRes.statusText}`)
395
548
  if (pubRes.status === 401 || pubRes.status === 403) {
396
- say.dim("Credentials weren't accepted — run `uniweb login` (or pass --token <bearer>).")
549
+ say.dim(
550
+ "Credentials weren't accepted — run `uniweb login` (or pass --token <bearer>)."
551
+ )
397
552
  }
398
553
  const body = await pubRes.text().catch(() => '')
399
554
  if (body) say.dim(body.slice(0, 800))
400
555
  return { exitCode: 1 }
401
556
  }
402
557
  let result
403
- try { result = await pubRes.json() } catch { result = {} }
558
+ try {
559
+ result = await pubRes.json()
560
+ } catch {
561
+ result = {}
562
+ }
404
563
  const serveUrl = absolutizeServeUrl(client.origin, result.url)
405
564
 
406
565
  // 8. Persist deploy.yml memory — a record of what went live (and so a re-run
@@ -410,14 +569,19 @@ export async function publish(args = []) {
410
569
  // Record the ref that actually went live: the pinned `@scope/name@version`
411
570
  // from the bring-along when present, else the site.yml ref verbatim.
412
571
  const gitAt = headProvenance(siteDir)
413
- const siteYmlRef = typeof siteYml.foundation === 'string' ? siteYml.foundation : siteYml.foundation?.ref || null
572
+ const siteYmlRef =
573
+ typeof siteYml.foundation === 'string'
574
+ ? siteYml.foundation
575
+ : siteYml.foundation?.ref || null
414
576
  const recordedRef = fnd.ref || siteYmlRef
415
577
  await persistLastDeploy(siteDir, {
416
578
  targetName: resolved.targetName,
417
579
  // First publish scaffolds deploy.yml with the backend recorded on the
418
580
  // target, binding the site to where it went live (uniweb.app, or a B2B
419
581
  // backend). resolveSiteBackend reads it back on later publishes.
420
- targetConfig: resolved.fromFile ? null : { host: 'uniweb', backend: client.origin },
582
+ targetConfig: resolved.fromFile
583
+ ? null
584
+ : { host: 'uniweb', backend: client.origin },
421
585
  autoSave,
422
586
  lastDeploy: {
423
587
  at: new Date().toISOString(),
@@ -430,14 +594,19 @@ export async function publish(args = []) {
430
594
  backend: client.origin,
431
595
  siteUuid,
432
596
  url: serveUrl,
433
- foundation: { ...(recordedRef ? { ref: recordedRef } : {}), released: fnd.released },
597
+ foundation: {
598
+ ...(recordedRef ? { ref: recordedRef } : {}),
599
+ released: fnd.released
600
+ },
434
601
  runtime: runtimeVersion,
435
- locales: Array.isArray(result.locales) ? result.locales : languages,
436
- },
602
+ locales: Array.isArray(result.locales) ? result.locales : languages
603
+ }
437
604
  })
438
605
 
439
606
  console.log('')
440
- say.ok(`Published ${c.bold}${siteUuid}${c.reset}${result.status ? ` (${result.status})` : ''}`)
607
+ say.ok(
608
+ `Published ${c.bold}${siteUuid}${c.reset}${result.status ? ` (${result.status})` : ''}`
609
+ )
441
610
  if (serveUrl) console.log(` ${c.cyan}${serveUrl}${c.reset}`)
442
611
  if (result.deploy_uuid) say.dim(`deploy: ${result.deploy_uuid}`)
443
612
  return { exitCode: 0 }