uniweb 0.13.16 → 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 +16 -14
- 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/publish.js
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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',
|
|
74
|
-
|
|
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 = (
|
|
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) =>
|
|
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
|
|
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 =
|
|
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
|
|
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)
|
|
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:
|
|
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(
|
|
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)
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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(
|
|
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 = {
|
|
242
|
+
resolved = {
|
|
243
|
+
targetName: 'production',
|
|
244
|
+
host: 'uniweb',
|
|
245
|
+
config: {},
|
|
246
|
+
autoSave: 'lastDeploy',
|
|
247
|
+
fromFile: false
|
|
248
|
+
}
|
|
209
249
|
}
|
|
210
|
-
const autoSave = noSave ? 'off' :
|
|
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(
|
|
216
|
-
|
|
255
|
+
say.dim(
|
|
256
|
+
`Runtime : ${runtimeVersion || '(unresolved — needs 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({
|
|
220
|
-
|
|
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({
|
|
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`, {
|
|
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, {
|
|
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(
|
|
279
|
-
|
|
280
|
-
|
|
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(
|
|
410
|
+
say.dim(
|
|
411
|
+
`Media : ${Object.keys(map).length}/${mediaRefs.length} ref(s) → serve URL`
|
|
412
|
+
)
|
|
294
413
|
} catch (err) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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, {
|
|
433
|
+
dataBundle = await uploadDataBundle(client, ball, {
|
|
434
|
+
siteUuid: site.uuid,
|
|
435
|
+
onProgress: (m) => say.dim(` ${m}`)
|
|
436
|
+
})
|
|
312
437
|
} catch (err) {
|
|
313
|
-
|
|
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(
|
|
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')
|
|
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({
|
|
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
|
|
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({
|
|
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(
|
|
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, {
|
|
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(
|
|
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 {
|
|
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 =
|
|
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
|
|
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: {
|
|
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(
|
|
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 }
|