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
@@ -58,8 +58,14 @@ import { isNonInteractive, getCliPrefix } from '../utils/interactive.js'
58
58
  import { extractFoundationRef } from '../utils/site-content-refs.js'
59
59
 
60
60
  const colors = {
61
- reset: '\x1b[0m', bright: '\x1b[1m', dim: '\x1b[2m',
62
- red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[36m', cyan: '\x1b[36m',
61
+ reset: '\x1b[0m',
62
+ bright: '\x1b[1m',
63
+ dim: '\x1b[2m',
64
+ red: '\x1b[31m',
65
+ green: '\x1b[32m',
66
+ yellow: '\x1b[33m',
67
+ blue: '\x1b[36m',
68
+ cyan: '\x1b[36m'
63
69
  }
64
70
  const log = console.log
65
71
  const success = (m) => log(`${colors.green}✓${colors.reset} ${m}`)
@@ -71,12 +77,16 @@ function flagValue(args, name) {
71
77
  const eq = args.find((a) => a.startsWith(`${name}=`))
72
78
  if (eq) return eq.slice(name.length + 1)
73
79
  const i = args.indexOf(name)
74
- if (i !== -1 && args[i + 1] && !args[i + 1].startsWith('-')) return args[i + 1]
80
+ if (i !== -1 && args[i + 1] && !args[i + 1].startsWith('-'))
81
+ return args[i + 1]
75
82
  return null
76
83
  }
77
84
 
78
85
  function slugify(s) {
79
- return String(s || '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '')
86
+ return String(s || '')
87
+ .toLowerCase()
88
+ .replace(/[^a-z0-9]+/g, '-')
89
+ .replace(/^-+|-+$/g, '')
80
90
  }
81
91
 
82
92
  // Tolerant single-entity document extraction (mirrors pull.js; duplicated rather
@@ -110,7 +120,7 @@ export function extractCloneSeeds(document) {
110
120
  const info = document?.info || {}
111
121
  return {
112
122
  foundationRef: extractFoundationRef(info, document),
113
- name: unwrapScalar(info.name) ?? unwrapScalar(document?.name) ?? null,
123
+ name: unwrapScalar(info.name) ?? unwrapScalar(document?.name) ?? null
114
124
  }
115
125
  }
116
126
 
@@ -154,27 +164,35 @@ export async function clone(args = [], deps = {}) {
154
164
 
155
165
  if (!siteUuid) {
156
166
  error('Missing site uuid.')
157
- log(`\nUsage: ${getCliPrefix()} clone <site-uuid> [name|.] [--path <dir>] [--project <name>] [--no-collections]`)
158
- log(`${colors.dim}Sites are private run \`uniweb login\` first.${colors.reset}`)
167
+ log(
168
+ `\nUsage: ${getCliPrefix()} clone <site-uuid> [name|.] [--path <dir>] [--project <name>] [--no-collections]`
169
+ )
170
+ log(
171
+ `${colors.dim}Sites are private — run \`uniweb login\` first.${colors.reset}`
172
+ )
159
173
  return { exitCode: 2 }
160
174
  }
161
175
 
162
- const noCollections = args.includes('--no-collections') || args.includes('--content-only')
176
+ const noCollections =
177
+ args.includes('--no-collections') || args.includes('--content-only')
163
178
  const pathFlag = flagValue(args, '--path')
164
179
  const projectFlag = flagValue(args, '--project')
165
180
  const tokenFlag = flagValue(args, '--token')
166
- const explicitBackend = flagValue(args, '--backend') || flagValue(args, '--registry')
181
+ const explicitBackend =
182
+ flagValue(args, '--backend') || flagValue(args, '--registry')
167
183
  const client = new BackendClient({
168
184
  originFlag: explicitBackend,
169
185
  token: tokenFlag,
170
186
  getToken: deps.getToken,
171
187
  fetchImpl: deps.fetch,
172
188
  args,
173
- command: 'Cloning',
189
+ command: 'Cloning'
174
190
  })
175
191
 
176
192
  // 1. GET the site-content document (no @uniweb/build needed for a read).
177
- info(`Reading site ${colors.bright}${siteUuid}${colors.reset} from ${colors.dim}${client.origin}${colors.reset} …`)
193
+ info(
194
+ `Reading site ${colors.bright}${siteUuid}${colors.reset} from ${colors.dim}${client.origin}${colors.reset} …`
195
+ )
178
196
  let payload
179
197
  try {
180
198
  const res = await client.pullSiteContent(siteUuid)
@@ -184,7 +202,8 @@ export async function clone(args = [], deps = {}) {
184
202
  }
185
203
  if (!res.ok) {
186
204
  error(`Could not read the site: HTTP ${res.status} ${res.statusText}`)
187
- if (res.status === 401 || res.status === 403) note('Run `uniweb login` first (or pass --token <bearer>).')
205
+ if (res.status === 401 || res.status === 403)
206
+ note('Run `uniweb login` first (or pass --token <bearer>).')
188
207
  return { exitCode: 1 }
189
208
  }
190
209
  payload = await res.json()
@@ -200,7 +219,9 @@ export async function clone(args = [], deps = {}) {
200
219
  }
201
220
  const { foundationRef, name: siteDisplayName } = extractCloneSeeds(document)
202
221
  if (!foundationRef) {
203
- note('! The pulled site declares no foundation ref — set `foundation:` in site.yml after clone.')
222
+ note(
223
+ '! The pulled site declares no foundation ref — set `foundation:` in site.yml after clone.'
224
+ )
204
225
  }
205
226
 
206
227
  // 2. Resolve placement (one verb, context-aware).
@@ -224,7 +245,12 @@ export async function clone(args = [], deps = {}) {
224
245
  } else if (existingRoot) {
225
246
  isNewWorkspace = false
226
247
  projectDir = existingRoot
227
- placement = resolvePlacement(existingRoot, target, { path: pathFlag, project: projectFlag }, SITE_KIND)
248
+ placement = resolvePlacement(
249
+ existingRoot,
250
+ target,
251
+ { path: pathFlag, project: projectFlag },
252
+ SITE_KIND
253
+ )
228
254
  siteDir = join(existingRoot, placement.relativePath)
229
255
  sitePkgName = placement.packageName
230
256
  workspaceName = sitePkgName
@@ -232,11 +258,15 @@ export async function clone(args = [], deps = {}) {
232
258
  isNewWorkspace = true
233
259
  workspaceName = target || slugify(siteDisplayName) || null
234
260
  if (!workspaceName) {
235
- error('Could not derive a project name from the site. Pass one: `uniweb clone <uuid> <name>`.')
261
+ error(
262
+ 'Could not derive a project name from the site. Pass one: `uniweb clone <uuid> <name>`.'
263
+ )
236
264
  return { exitCode: 2 }
237
265
  }
238
266
  if (!/^[a-z0-9-]+$/.test(workspaceName)) {
239
- error(`Invalid project name "${workspaceName}" — use lowercase letters, numbers, and hyphens.`)
267
+ error(
268
+ `Invalid project name "${workspaceName}" — use lowercase letters, numbers, and hyphens.`
269
+ )
240
270
  return { exitCode: 2 }
241
271
  }
242
272
  projectDir = resolve(cwd, workspaceName)
@@ -256,18 +286,28 @@ export async function clone(args = [], deps = {}) {
256
286
 
257
287
  // 3. Scaffold the harness (ref-only site: foundationRef, no foundationPath).
258
288
  const onProgress = (m) => note(m)
259
- const siteContext = { name: sitePkgName, projectName: workspaceName, ...(foundationRef ? { foundationRef } : {}) }
289
+ const siteContext = {
290
+ name: sitePkgName,
291
+ projectName: workspaceName,
292
+ ...(foundationRef ? { foundationRef } : {})
293
+ }
260
294
 
261
295
  if (isNewWorkspace) {
262
296
  info(`Scaffolding ${colors.bright}${workspaceName}${colors.reset} …`)
263
297
  await scaffoldWorkspace(
264
298
  projectDir,
265
- { projectName: workspaceName, workspaceGlobs: ['site'], scripts: { dev: 'uniweb dev', build: 'uniweb build' } },
266
- { onProgress },
299
+ {
300
+ projectName: workspaceName,
301
+ workspaceGlobs: ['site'],
302
+ scripts: { dev: 'uniweb dev', build: 'uniweb build' }
303
+ },
304
+ { onProgress }
267
305
  )
268
306
  await scaffoldSite(siteDir, siteContext, { onProgress })
269
307
  } else {
270
- info(`Adding site ${colors.bright}${sitePkgName}${colors.reset} to the workspace at ${colors.dim}${placement.relativePath}/${colors.reset} …`)
308
+ info(
309
+ `Adding site ${colors.bright}${sitePkgName}${colors.reset} to the workspace at ${colors.dim}${placement.relativePath}/${colors.reset} …`
310
+ )
271
311
  await scaffoldSite(siteDir, siteContext, { onProgress })
272
312
  await addWorkspaceGlob(existingRoot, placement.relativePath)
273
313
  }
@@ -276,7 +316,9 @@ export async function clone(args = [], deps = {}) {
276
316
  // same uuid (the backend resolves the site's @uniweb/folder from it), so there is no
277
317
  // separate folder uuid to seed.
278
318
  seedYamlUuid(join(siteDir, 'site.yml'), siteUuid)
279
- success(`Scaffolded the site harness${foundationRef ? ` (foundation: ${foundationRef})` : ''}.`)
319
+ success(
320
+ `Scaffolded the site harness${foundationRef ? ` (foundation: ${foundationRef})` : ''}.`
321
+ )
280
322
 
281
323
  // 5. Install, then delegate the projection to the project-local `uniweb pull`.
282
324
  const pm = detectWorkspacePm(projectDir) || 'pnpm'
@@ -289,7 +331,9 @@ export async function clone(args = [], deps = {}) {
289
331
  info(`Installing dependencies (${installCmd(pm)}) …`)
290
332
  const r = spawnSync(pm, ['install'], { cwd: projectDir, stdio: 'inherit' })
291
333
  if (r.status !== 0) {
292
- error(`Install failed. Once it succeeds, run \`uniweb pull\` from ${siteDir} to fetch the content.`)
334
+ error(
335
+ `Install failed. Once it succeeds, run \`uniweb pull\` from ${siteDir} to fetch the content.`
336
+ )
293
337
  return { exitCode: 1 }
294
338
  }
295
339
  }
@@ -305,17 +349,26 @@ export async function clone(args = [], deps = {}) {
305
349
  await deps.runPull(siteDir, pm, pullExtra)
306
350
  } else {
307
351
  info('Pulling content …')
308
- const r = spawnSync(pm, pullExecArgv(pm, pullExtra), { cwd: siteDir, stdio: 'inherit' })
352
+ const r = spawnSync(pm, pullExecArgv(pm, pullExtra), {
353
+ cwd: siteDir,
354
+ stdio: 'inherit'
355
+ })
309
356
  if (r.status !== 0) {
310
- error(`Content pull failed. Fix the issue, then run \`uniweb pull\` from ${siteDir}.`)
357
+ error(
358
+ `Content pull failed. Fix the issue, then run \`uniweb pull\` from ${siteDir}.`
359
+ )
311
360
  return { exitCode: 1 }
312
361
  }
313
362
  }
314
363
 
315
364
  log('')
316
- success(`Cloned site into ${colors.bright}${isNewWorkspace && !inPlace ? workspaceName : siteDir}${colors.reset}`)
365
+ success(
366
+ `Cloned site into ${colors.bright}${isNewWorkspace && !inPlace ? workspaceName : siteDir}${colors.reset}`
367
+ )
317
368
  if (isNewWorkspace && !inPlace) {
318
- log(`\nNext: ${colors.cyan}cd ${workspaceName} && uniweb dev${colors.reset}`)
369
+ log(
370
+ `\nNext: ${colors.cyan}cd ${workspaceName} && uniweb dev${colors.reset}`
371
+ )
319
372
  } else {
320
373
  log(`\nNext: ${colors.cyan}uniweb dev${colors.reset}`)
321
374
  }
@@ -28,14 +28,14 @@ const c = {
28
28
  cyan: '\x1b[36m',
29
29
  green: '\x1b[32m',
30
30
  red: '\x1b[31m',
31
- yellow: '\x1b[33m',
31
+ yellow: '\x1b[33m'
32
32
  }
33
33
  const say = {
34
34
  ok: (m) => console.log(`${c.green}✓${c.reset} ${m}`),
35
35
  info: (m) => console.log(`${c.cyan}→${c.reset} ${m}`),
36
36
  err: (m) => console.error(`${c.red}✗${c.reset} ${m}`),
37
37
  warn: (m) => console.log(`${c.yellow}!${c.reset} ${m}`),
38
- dim: (m) => console.log(` ${c.dim}${m}${c.reset}`),
38
+ dim: (m) => console.log(` ${c.dim}${m}${c.reset}`)
39
39
  }
40
40
 
41
41
  function usage() {
@@ -103,16 +103,18 @@ async function contentExport(args) {
103
103
  // export), so a fresh project exports a uuid-less document.
104
104
  buf = await uwx.emitSiteSyncPackage(dir, {
105
105
  sidecar: useSidecar, // read <dir>/.uniweb/uwx-ids.json if present
106
- sourceLocale,
106
+ sourceLocale
107
107
  })
108
108
  defaultName = basename(dir)
109
109
  } else {
110
110
  const schema = JSON.parse(readFileSync(schemaPath, 'utf8'))
111
- say.info(`Packaging foundation schema → @uniweb/foundation-schema (.uwx)…`)
111
+ say.info(
112
+ `Packaging foundation schema → @uniweb/foundation-schema (.uwx)…`
113
+ )
112
114
  buf = uwx.emitFoundationSchemaPackage(schema, {
113
115
  sidecar: useSidecar ? join(dir, uwx.SIDECAR_RELPATH) : undefined,
114
116
  foundationDir: dir,
115
- sourceLocale,
117
+ sourceLocale
116
118
  })
117
119
  defaultName = (schema?._self?.name || basename(dir)).replace(
118
120
  /[^a-z0-9._-]+/gi,
@@ -157,9 +159,11 @@ async function contentExport(args) {
157
159
  const pg = countPages(entity.pages)
158
160
  if (pg.p) counts.pages = pg.p
159
161
  if (pg.s) counts.page_sections = pg.s
160
- if (entity.layout_sections?.length) counts.layout_sections = entity.layout_sections.length
162
+ if (entity.layout_sections?.length)
163
+ counts.layout_sections = entity.layout_sections.length
161
164
  if (entity.extensions?.length) counts.extensions = entity.extensions.length
162
- if (entity.collections?.length) counts.collections = entity.collections.length
165
+ if (entity.collections?.length)
166
+ counts.collections = entity.collections.length
163
167
  }
164
168
 
165
169
  console.log('')
@@ -49,7 +49,11 @@ import { existsSync, readFileSync } from 'node:fs'
49
49
  import { resolve, join } from 'node:path'
50
50
  import { execSync } from 'node:child_process'
51
51
 
52
- import { loadDeployYml, resolveTarget, recordLastDeploy } from '@uniweb/build/site'
52
+ import {
53
+ loadDeployYml,
54
+ resolveTarget,
55
+ recordLastDeploy
56
+ } from '@uniweb/build/site'
53
57
  import { promptForDestination } from '../utils/destination-prompt.js'
54
58
  import { readFlagValue } from '../utils/args.js'
55
59
  import { parseBoolEnv } from '../utils/env.js'
@@ -59,7 +63,7 @@ import {
59
63
  findWorkspaceRoot,
60
64
  findSites,
61
65
  classifyPackage,
62
- promptSelect,
66
+ promptSelect
63
67
  } from '../utils/workspace.js'
64
68
  import { isNonInteractive, getCliPrefix } from '../utils/interactive.js'
65
69
 
@@ -70,14 +74,14 @@ const c = {
70
74
  cyan: '\x1b[36m',
71
75
  green: '\x1b[32m',
72
76
  yellow: '\x1b[33m',
73
- red: '\x1b[31m',
77
+ red: '\x1b[31m'
74
78
  }
75
79
  const say = {
76
80
  ok: (m) => console.log(`${c.green}✓${c.reset} ${m}`),
77
81
  info: (m) => console.log(`${c.cyan}→${c.reset} ${m}`),
78
82
  warn: (m) => console.log(`${c.yellow}⚠${c.reset} ${m}`),
79
83
  err: (m) => console.error(`${c.red}✗${c.reset} ${m}`),
80
- dim: (m) => console.log(` ${c.dim}${m}${c.reset}`),
84
+ dim: (m) => console.log(` ${c.dim}${m}${c.reset}`)
81
85
  }
82
86
 
83
87
  // ─── Main ───────────────────────────────────────────────────
@@ -126,7 +130,7 @@ export async function deploy(args = []) {
126
130
  const wantsWizard = hostFromFlag === null
127
131
  const knownHost = wantsWizard
128
132
  ? null
129
- : (hostFromFlag || (resolved.fromFile ? resolved.host : null))
133
+ : hostFromFlag || (resolved.fromFile ? resolved.host : null)
130
134
 
131
135
  if (knownHost === 'uniweb') {
132
136
  plan = { kind: 'uniweb' }
@@ -146,14 +150,23 @@ export async function deploy(args = []) {
146
150
  }
147
151
  say.err('`uniweb deploy` needs a destination.')
148
152
  console.log('')
149
- say.dim('`uniweb publish` Uniweb Cloud (sync + dynamic hosting; brings the foundation along)')
150
- say.dim('`uniweb deploy --host=…` A third-party host run `uniweb deploy --help` for the list')
153
+ say.dim(
154
+ '`uniweb publish` Uniweb Cloud (sync + dynamic hosting; brings the foundation along)'
155
+ )
156
+ say.dim(
157
+ '`uniweb deploy --host=…` A third-party host — run `uniweb deploy --help` for the list'
158
+ )
151
159
  say.dim('`uniweb add ci --host=…` Set up CI so every push deploys')
152
- say.dim('`uniweb export` Self-contained dist/ artifact you upload anywhere')
160
+ say.dim(
161
+ '`uniweb export` Self-contained dist/ artifact you upload anywhere'
162
+ )
153
163
  process.exit(1)
154
164
  }
155
165
  try {
156
- plan = await promptForDestination({ args, preselect: resolved.fromFile ? resolved.host : null })
166
+ plan = await promptForDestination({
167
+ args,
168
+ preselect: resolved.fromFile ? resolved.host : null
169
+ })
157
170
  } catch (err) {
158
171
  say.err(err.message)
159
172
  process.exit(1)
@@ -181,7 +194,9 @@ export async function deploy(args = []) {
181
194
  // passing a flag it would ignore — a dry run that actually ran a full
182
195
  // build would be a lie.
183
196
  if (dryRun) {
184
- say.info('Dry run — would run `uniweb export` to build a self-contained dist/.')
197
+ say.info(
198
+ 'Dry run — would run `uniweb export` to build a self-contained dist/.'
199
+ )
185
200
  return
186
201
  }
187
202
  say.info('Building a self-contained artifact → running `uniweb export`.')
@@ -208,7 +223,7 @@ export async function deploy(args = []) {
208
223
  await deployStaticHost(siteDir, plan.host, resolved, {
209
224
  dryRun,
210
225
  autoSave,
211
- hostOverridden,
226
+ hostOverridden
212
227
  })
213
228
  }
214
229
 
@@ -239,7 +254,7 @@ async function delegateToAddCi(siteDir, host, dryRun) {
239
254
  try {
240
255
  execSync(`node ${JSON.stringify(process.argv[1])} ${cmd}`, {
241
256
  cwd: workspaceRoot,
242
- stdio: 'inherit',
257
+ stdio: 'inherit'
243
258
  })
244
259
  } catch {
245
260
  // add ci already printed the reason and set the exit code.
@@ -254,10 +269,15 @@ async function delegateToAddCi(siteDir, host, dryRun) {
254
269
  // `uniweb build` (bundle mode + prerender) first, then hands dist/ to the
255
270
  // adapter's deploy hook for upload + invalidation.
256
271
 
257
- async function deployStaticHost(siteDir, hostName, resolved, { dryRun, autoSave, hostOverridden }) {
272
+ async function deployStaticHost(
273
+ siteDir,
274
+ hostName,
275
+ resolved,
276
+ { dryRun, autoSave, hostOverridden }
277
+ ) {
258
278
  let getAdapter
259
279
  try {
260
- ({ getAdapter } = await import('@uniweb/build/hosts'))
280
+ ;({ getAdapter } = await import('@uniweb/build/hosts'))
261
281
  } catch (err) {
262
282
  say.err('Failed to load host adapter registry from @uniweb/build/hosts.')
263
283
  say.dim(err.message)
@@ -269,14 +289,20 @@ async function deployStaticHost(siteDir, hostName, resolved, { dryRun, autoSave,
269
289
  adapter = getAdapter(hostName)
270
290
  } catch (err) {
271
291
  say.err(err.message)
272
- say.dim('Set the host in deploy.yml or pass --host=<name>. See `uniweb deploy --help`.')
292
+ say.dim(
293
+ 'Set the host in deploy.yml or pass --host=<name>. See `uniweb deploy --help`.'
294
+ )
273
295
  process.exit(1)
274
296
  }
275
297
 
276
298
  if (typeof adapter.deploy !== 'function') {
277
299
  say.err(`Host adapter '${hostName}' does not implement a deploy step.`)
278
- say.dim(`Build with \`uniweb build --host=${hostName}\` and upload \`dist/\` manually,`)
279
- say.dim(`or use a host whose adapter ships a deploy hook (e.g., s3-cloudfront).`)
300
+ say.dim(
301
+ `Build with \`uniweb build --host=${hostName}\` and upload \`dist/\` manually,`
302
+ )
303
+ say.dim(
304
+ `or use a host whose adapter ships a deploy hook (e.g., s3-cloudfront).`
305
+ )
280
306
  process.exit(1)
281
307
  }
282
308
 
@@ -284,11 +310,16 @@ async function deployStaticHost(siteDir, hostName, resolved, { dryRun, autoSave,
284
310
  const distDir = join(siteDir, 'dist')
285
311
 
286
312
  if (dryRun) {
287
- say.info(`Dry run — would deploy via host adapter: ${c.bold}${adapter.name}${c.reset}`)
313
+ say.info(
314
+ `Dry run — would deploy via host adapter: ${c.bold}${adapter.name}${c.reset}`
315
+ )
288
316
  say.dim(`Site dir : ${siteDir}`)
289
- say.dim(`dist/ : ${existsSync(distDir) ? 'exists (would not rebuild)' : 'missing (would build)'}`)
317
+ say.dim(
318
+ `dist/ : ${existsSync(distDir) ? 'exists (would not rebuild)' : 'missing (would build)'}`
319
+ )
290
320
  say.dim(`Target : ${resolved.targetName}`)
291
- if (adapter.display?.pushWith) say.dim(`Uploads by: ${adapter.display.pushWith}`)
321
+ if (adapter.display?.pushWith)
322
+ say.dim(`Uploads by: ${adapter.display.pushWith}`)
292
323
  // Echo the resolved target's config as-is. Each adapter reads
293
324
  // different keys (bucket/distributionId for s3, projectName for
294
325
  // Cloudflare, siteId for Netlify), so listing a fixed set would be
@@ -297,10 +328,14 @@ async function deployStaticHost(siteDir, hostName, resolved, { dryRun, autoSave,
297
328
  if (configKeys.length) {
298
329
  say.dim('Config :')
299
330
  for (const key of configKeys.sort()) {
300
- say.dim(` ${key}: ${typeof deployConfig[key] === 'object' ? JSON.stringify(deployConfig[key]) : deployConfig[key]}`)
331
+ say.dim(
332
+ ` ${key}: ${typeof deployConfig[key] === 'object' ? JSON.stringify(deployConfig[key]) : deployConfig[key]}`
333
+ )
301
334
  }
302
335
  } else {
303
- say.dim('Config : (none in deploy.yml — the adapter will say what it needs)')
336
+ say.dim(
337
+ 'Config : (none in deploy.yml — the adapter will say what it needs)'
338
+ )
304
339
  }
305
340
  return
306
341
  }
@@ -342,7 +377,7 @@ async function deployStaticHost(siteDir, hostName, resolved, { dryRun, autoSave,
342
377
  distDir,
343
378
  deployConfig,
344
379
  env: process.env,
345
- log: (m) => console.log(m),
380
+ log: (m) => console.log(m)
346
381
  })
347
382
  } catch (err) {
348
383
  if (err && err.name === 'DeployError') {
@@ -358,10 +393,15 @@ async function deployStaticHost(siteDir, hostName, resolved, { dryRun, autoSave,
358
393
 
359
394
  // Record a fresh lastDeploy.<target> entry. Skipped on --no-save and
360
395
  // on ad-hoc --host overrides — see autoSave gating in deploy().
361
- const gitStamp = { at: new Date().toISOString(), git: headProvenance(siteDir) }
396
+ const gitStamp = {
397
+ at: new Date().toISOString(),
398
+ git: headProvenance(siteDir)
399
+ }
362
400
  await persistLastDeploy(siteDir, {
363
401
  targetName: resolved.targetName,
364
- targetConfig: resolved.fromFile ? null : { host: hostName, ...deployConfig },
402
+ targetConfig: resolved.fromFile
403
+ ? null
404
+ : { host: hostName, ...deployConfig },
365
405
  autoSave,
366
406
  lastDeploy: {
367
407
  at: gitStamp.at,
@@ -374,11 +414,13 @@ async function deployStaticHost(siteDir, hostName, resolved, { dryRun, autoSave,
374
414
  ...(deployResult?.url ? { url: deployResult.url } : {}),
375
415
  // Same provenance the Uniweb path records: which commit this artifact came
376
416
  // from, and whether the tree was clean when it was built.
377
- ...(gitStamp.git ? { git: gitStamp.git } : {}),
378
- },
417
+ ...(gitStamp.git ? { git: gitStamp.git } : {})
418
+ }
379
419
  })
380
420
  if (hostOverridden && !dryRun) {
381
- say.dim('--host override active — did not write to deploy.yml. Edit deploy.yml to make this permanent.')
421
+ say.dim(
422
+ '--host override active — did not write to deploy.yml. Edit deploy.yml to make this permanent.'
423
+ )
382
424
  }
383
425
  }
384
426
 
@@ -425,7 +467,9 @@ export async function resolveSiteDir(args, verb = 'deploy') {
425
467
  }
426
468
  const choice = await promptSelect('Which site?', sites)
427
469
  if (!choice) {
428
- console.log(`\n${verb.charAt(0).toUpperCase() + verb.slice(1)} cancelled.`)
470
+ console.log(
471
+ `\n${verb.charAt(0).toUpperCase() + verb.slice(1)} cancelled.`
472
+ )
429
473
  process.exit(0)
430
474
  }
431
475
  return resolve(workspaceRoot, choice)
@@ -434,9 +478,13 @@ export async function resolveSiteDir(args, verb = 'deploy') {
434
478
 
435
479
  say.err('No site found in this workspace.')
436
480
  if (verb === 'export') {
437
- say.dim('`export` produces a self-contained dist/ artifact for third-party hosting.')
481
+ say.dim(
482
+ '`export` produces a self-contained dist/ artifact for third-party hosting.'
483
+ )
438
484
  } else if (verb === 'deploy') {
439
- say.dim('`deploy` ships a built site to a third-party host (use `uniweb publish` for Uniweb hosting).')
485
+ say.dim(
486
+ '`deploy` ships a built site to a third-party host (use `uniweb publish` for Uniweb hosting).'
487
+ )
440
488
  } else {
441
489
  say.dim(`\`${verb}\` operates on a site.`)
442
490
  }
@@ -455,7 +503,7 @@ export async function resolveSiteBackend(siteDir) {
455
503
  try {
456
504
  const deployYml = await loadDeployYml(siteDir)
457
505
  const resolved = resolveTarget(deployYml, null)
458
- return resolved.host === 'uniweb' ? (resolved.config?.backend || null) : null
506
+ return resolved.host === 'uniweb' ? resolved.config?.backend || null : null
459
507
  } catch {
460
508
  return null
461
509
  }
@@ -40,7 +40,10 @@ import { readWorkspaceConfig } from '../utils/config.js'
40
40
  import { discoverSites } from '../utils/discover.js'
41
41
  import { findWorkspaceRoot } from '../utils/workspace.js'
42
42
  import { readFlagValue } from '../utils/args.js'
43
- import { checkSiteInstall, readDeclaredFoundation } from '../utils/install-integrity.js'
43
+ import {
44
+ checkSiteInstall,
45
+ readDeclaredFoundation
46
+ } from '../utils/install-integrity.js'
44
47
  import { discoverFoundations } from '../utils/discover.js'
45
48
 
46
49
  const RED = '\x1b[31m'
@@ -62,8 +65,12 @@ export async function dev(args = []) {
62
65
  workspaceConfig = { packages: [] }
63
66
  }
64
67
  if (workspaceConfig.packages.length === 0) {
65
- console.error(`${RED}✗${RESET} Not in a Uniweb workspace (no pnpm-workspace.yaml or package.json::workspaces).`)
66
- console.error(` Run \`uniweb create <name>\` to scaffold a project, or cd into an existing one.`)
68
+ console.error(
69
+ `${RED}✗${RESET} Not in a Uniweb workspace (no pnpm-workspace.yaml or package.json::workspaces).`
70
+ )
71
+ console.error(
72
+ ` Run \`uniweb create <name>\` to scaffold a project, or cd into an existing one.`
73
+ )
67
74
  process.exit(1)
68
75
  }
69
76
 
@@ -76,24 +83,29 @@ export async function dev(args = []) {
76
83
 
77
84
  // Pick the site
78
85
  const siteFlag = readFlagValue(args, '--site')
79
- const positional = args.find(a => !a.startsWith('-'))
80
- const requested = (typeof siteFlag === 'string' ? siteFlag : null) || positional || null
86
+ const positional = args.find((a) => !a.startsWith('-'))
87
+ const requested =
88
+ (typeof siteFlag === 'string' ? siteFlag : null) || positional || null
81
89
 
82
90
  let site
83
91
  if (requested) {
84
- site = sites.find(s => s.name === requested) || sites.find(s => s.path === requested)
92
+ site =
93
+ sites.find((s) => s.name === requested) ||
94
+ sites.find((s) => s.path === requested)
85
95
  if (!site) {
86
96
  console.error(`${RED}✗${RESET} Site "${requested}" not found.`)
87
- console.error(` Available: ${sites.map(s => s.name).join(', ')}`)
97
+ console.error(` Available: ${sites.map((s) => s.name).join(', ')}`)
88
98
  process.exit(1)
89
99
  }
90
100
  } else if (sites.length === 1) {
91
101
  site = sites[0]
92
102
  } else {
93
103
  site = sites[0]
94
- console.error(`${YELLOW}⚠${RESET} Multiple sites found; using ${CYAN}${site.name}${RESET}.`)
104
+ console.error(
105
+ `${YELLOW}⚠${RESET} Multiple sites found; using ${CYAN}${site.name}${RESET}.`
106
+ )
95
107
  console.error(` Pick a different one with \`uniweb dev --site <name>\`.`)
96
- console.error(` Available: ${sites.map(s => s.name).join(', ')}`)
108
+ console.error(` Available: ${sites.map((s) => s.name).join(', ')}`)
97
109
  console.error('')
98
110
  }
99
111
 
@@ -109,12 +121,14 @@ export async function dev(args = []) {
109
121
  const [bin, ...rest] = command.split(' ')
110
122
  const sitePath = join(rootDir, site.path)
111
123
 
112
- console.error(`${DIM}→ ${command}${RESET} ${DIM}(site: ${site.name}, dir: ${sitePath})${RESET}`)
124
+ console.error(
125
+ `${DIM}→ ${command}${RESET} ${DIM}(site: ${site.name}, dir: ${sitePath})${RESET}`
126
+ )
113
127
  console.error('')
114
128
 
115
129
  const child = spawn(bin, rest, { cwd: rootDir, stdio: 'inherit' })
116
- child.on('close', code => process.exit(code ?? 0))
117
- child.on('error', err => {
130
+ child.on('close', (code) => process.exit(code ?? 0))
131
+ child.on('error', (err) => {
118
132
  console.error(`${RED}✗${RESET} Failed to start dev server: ${err.message}`)
119
133
  process.exit(1)
120
134
  })
@@ -133,18 +147,26 @@ async function warnOnStaleInstall(rootDir, site) {
133
147
 
134
148
  const foundations = await discoverFoundations(rootDir)
135
149
  const foundation = foundations
136
- .map(f => ({ ...f, path: join(rootDir, f.path) }))
137
- .find(f => f.name === foundationName)
150
+ .map((f) => ({ ...f, path: join(rootDir, f.path) }))
151
+ .find((f) => f.name === foundationName)
138
152
  if (!foundation) return
139
153
 
140
- const findings = checkSiteInstall({ name: site.name, path: sitePath }, foundation, rootDir)
154
+ const findings = checkSiteInstall(
155
+ { name: site.name, path: sitePath },
156
+ foundation,
157
+ rootDir
158
+ )
141
159
  if (!findings.length) return
142
160
 
143
- console.error(`${YELLOW}⚠${RESET} Dev will not build against your workspace foundation:`)
161
+ console.error(
162
+ `${YELLOW}⚠${RESET} Dev will not build against your workspace foundation:`
163
+ )
144
164
  for (const finding of findings) {
145
165
  console.error(` ${finding.message}`)
146
166
  }
147
- console.error(` ${findings[0].remedy}. \`uniweb doctor\` explains in full.`)
167
+ console.error(
168
+ ` ${findings[0].remedy}. \`uniweb doctor\` explains in full.`
169
+ )
148
170
  console.error('')
149
171
  } catch {
150
172
  // A diagnostic must never be the reason dev fails to start.