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
@@ -45,13 +45,24 @@ import { spawn } from 'node:child_process'
45
45
  import prompts from 'prompts'
46
46
 
47
47
  import { findWorkspaceRoot } from '../utils/workspace.js'
48
- import { readAgentsVersion, generateAgentsContent } from '../utils/agents-stamp.js'
48
+ import {
49
+ readAgentsVersion,
50
+ generateAgentsContent
51
+ } from '../utils/agents-stamp.js'
49
52
  import { getCliVersion } from '../versions.js'
50
53
  import { isNonInteractive } from '../utils/interactive.js'
51
- import { detectWorkspacePm, installCmd, detectGlobalCliPm, globalCliUpdateCmd } from '../utils/pm.js'
54
+ import {
55
+ detectWorkspacePm,
56
+ installCmd,
57
+ detectGlobalCliPm,
58
+ globalCliUpdateCmd
59
+ } from '../utils/pm.js'
52
60
  import { writeJsonPreservingStyle } from '../utils/json-file.js'
53
61
  import { surveyWorkspaceDeps, compareSemver } from '../utils/dep-survey.js'
54
- import { checkWorkspaceInstall, readDeclaredFoundation } from '../utils/install-integrity.js'
62
+ import {
63
+ checkWorkspaceInstall,
64
+ readDeclaredFoundation
65
+ } from '../utils/install-integrity.js'
55
66
  import { getLatestVersion } from '../utils/update-check.js'
56
67
 
57
68
  const colors = {
@@ -61,7 +72,7 @@ const colors = {
61
72
  red: '\x1b[31m',
62
73
  green: '\x1b[32m',
63
74
  yellow: '\x1b[33m',
64
- cyan: '\x1b[36m',
75
+ cyan: '\x1b[36m'
65
76
  }
66
77
 
67
78
  const success = (msg) => console.log(`${colors.green}✓${colors.reset} ${msg}`)
@@ -78,8 +89,10 @@ const log = console.log
78
89
  function isGlobalInstall() {
79
90
  const scriptPath = process.argv[1]
80
91
  if (!scriptPath) return false
81
- return !scriptPath.split('/').includes('node_modules') &&
82
- !scriptPath.split('\\').includes('node_modules')
92
+ return (
93
+ !scriptPath.split('/').includes('node_modules') &&
94
+ !scriptPath.split('\\').includes('node_modules')
95
+ )
83
96
  }
84
97
 
85
98
  /**
@@ -106,7 +119,9 @@ function findUniwebWorkspace(cwd) {
106
119
  if (!existsSync(pkgPath)) return null
107
120
  try {
108
121
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
109
- const hasUniwebDep = !!(pkg.devDependencies?.uniweb || pkg.dependencies?.uniweb)
122
+ const hasUniwebDep = !!(
123
+ pkg.devDependencies?.uniweb || pkg.dependencies?.uniweb
124
+ )
110
125
  return hasUniwebDep ? workspaceDir : null
111
126
  } catch {
112
127
  return null
@@ -137,18 +152,34 @@ function findUniwebWorkspace(cwd) {
137
152
  *
138
153
  * @returns {boolean} true if a notice was printed.
139
154
  */
140
- export function printStaleCliNotice({ cliVersion, latest, isNpx, isGlobal, globalPm }) {
155
+ export function printStaleCliNotice({
156
+ cliVersion,
157
+ latest,
158
+ isNpx,
159
+ isGlobal,
160
+ globalPm
161
+ }) {
141
162
  if (isNpx) return false
142
163
  if (!latest || compareSemver(latest, cliVersion) <= 0) return false
143
164
 
144
165
  log('')
145
- log(`${colors.yellow}⚠${colors.reset} ${colors.bright}A newer uniweb is available:${colors.reset} ${colors.dim}v${cliVersion}${colors.reset} → ${colors.cyan}v${latest}${colors.reset}`)
166
+ log(
167
+ `${colors.yellow}⚠${colors.reset} ${colors.bright}A newer uniweb is available:${colors.reset} ${colors.dim}v${cliVersion}${colors.reset} → ${colors.cyan}v${latest}${colors.reset}`
168
+ )
146
169
  if (isGlobal) {
147
- log(`${colors.dim}This run aligned the project to v${cliVersion}'s matrix. To update the CLI:${colors.reset} ${colors.cyan}${globalCliUpdateCmd(globalPm)}${colors.reset}`)
148
- log(`${colors.dim}Or align to the latest release without a global install:${colors.reset} ${colors.cyan}npx uniweb@latest update${colors.reset}`)
170
+ log(
171
+ `${colors.dim}This run aligned the project to v${cliVersion}'s matrix. To update the CLI:${colors.reset} ${colors.cyan}${globalCliUpdateCmd(globalPm)}${colors.reset}`
172
+ )
173
+ log(
174
+ `${colors.dim}Or align to the latest release without a global install:${colors.reset} ${colors.cyan}npx uniweb@latest update${colors.reset}`
175
+ )
149
176
  } else {
150
- log(`${colors.dim}This run aligned the project to v${cliVersion}'s matrix — the version your project pins.${colors.reset}`)
151
- log(`${colors.dim}To move to v${latest}:${colors.reset} ${colors.cyan}npx uniweb@latest update${colors.reset} ${colors.dim}(updates the pin too).${colors.reset}`)
177
+ log(
178
+ `${colors.dim}This run aligned the project to v${cliVersion}'s matrix the version your project pins.${colors.reset}`
179
+ )
180
+ log(
181
+ `${colors.dim}To move to v${latest}:${colors.reset} ${colors.cyan}npx uniweb@latest update${colors.reset} ${colors.dim}(updates the pin too).${colors.reset}`
182
+ )
152
183
  }
153
184
  log('')
154
185
  return true
@@ -159,7 +190,7 @@ function runCommand(cmd, cwd) {
159
190
  return new Promise((resolve) => {
160
191
  const [bin, ...rest] = cmd.split(' ')
161
192
  const child = spawn(bin, rest, { stdio: 'inherit', cwd })
162
- child.on('close', code => resolve(code ?? 0))
193
+ child.on('close', (code) => resolve(code ?? 0))
163
194
  child.on('error', () => resolve(1))
164
195
  })
165
196
  }
@@ -179,19 +210,28 @@ function runCommand(cmd, cwd) {
179
210
  * regression here is invisible — the command still works, it just stops
180
211
  * being readable.
181
212
  */
182
- export function printSurvey(report, cliVersion, agentsVersion, { verbose = false } = {}) {
213
+ export function printSurvey(
214
+ report,
215
+ cliVersion,
216
+ agentsVersion,
217
+ { verbose = false } = {}
218
+ ) {
183
219
  log('')
184
220
  log(`${colors.bright}uniweb CLI:${colors.reset} v${cliVersion}`)
185
- log(`${colors.bright}AGENTS.md stamp:${colors.reset} ${agentsVersion ? 'v' + agentsVersion : colors.dim + '(none)' + colors.reset}`)
221
+ log(
222
+ `${colors.bright}AGENTS.md stamp:${colors.reset} ${agentsVersion ? 'v' + agentsVersion : colors.dim + '(none)' + colors.reset}`
223
+ )
186
224
  log('')
187
225
 
188
226
  if (report.rows.length === 0) {
189
- log(`${colors.dim}No @uniweb/* deps found in workspace package.json files.${colors.reset}`)
227
+ log(
228
+ `${colors.dim}No @uniweb/* deps found in workspace package.json files.${colors.reset}`
229
+ )
190
230
  log('')
191
231
  return
192
232
  }
193
233
 
194
- const needsAttention = report.rows.filter(r => r.status !== 'aligned')
234
+ const needsAttention = report.rows.filter((r) => r.status !== 'aligned')
195
235
  const shown = verbose ? report.rows : needsAttention
196
236
  const alignedCount = report.rows.length - needsAttention.length
197
237
 
@@ -208,7 +248,7 @@ export function printSurvey(report, cliVersion, agentsVersion, { verbose = false
208
248
  log(`${colors.bright}Workspace deps (declared):${colors.reset}`)
209
249
  for (const [dir, dirRows] of Object.entries(byDir)) {
210
250
  log(` ${colors.dim}${dir}/${colors.reset}`)
211
- const maxName = Math.max(...dirRows.map(r => r.name.length))
251
+ const maxName = Math.max(...dirRows.map((r) => r.name.length))
212
252
  for (const row of dirRows) {
213
253
  const padding = ' '.repeat(maxName - row.name.length)
214
254
  let icon, statusText
@@ -222,11 +262,15 @@ export function printSurvey(report, cliVersion, agentsVersion, { verbose = false
222
262
  icon = `${colors.cyan}↑${colors.reset}`
223
263
  statusText = `${colors.cyan}ahead of CLI${colors.reset}`
224
264
  }
225
- log(` ${icon} ${row.name}${padding} ${row.current.padEnd(10)} → ${row.target.padEnd(10)} ${statusText}`)
265
+ log(
266
+ ` ${icon} ${row.name}${padding} ${row.current.padEnd(10)} → ${row.target.padEnd(10)} ${statusText}`
267
+ )
226
268
  }
227
269
  }
228
270
  if (!verbose && alignedCount > 0) {
229
- log(` ${colors.dim}(${alignedCount} other${alignedCount === 1 ? '' : 's'} already aligned — ${colors.reset}${colors.cyan}--verbose${colors.reset}${colors.dim} to list)${colors.reset}`)
271
+ log(
272
+ ` ${colors.dim}(${alignedCount} other${alignedCount === 1 ? '' : 's'} already aligned — ${colors.reset}${colors.cyan}--verbose${colors.reset}${colors.dim} to list)${colors.reset}`
273
+ )
230
274
  }
231
275
  log('')
232
276
  }
@@ -239,7 +283,7 @@ export function printSurvey(report, cliVersion, agentsVersion, { verbose = false
239
283
  * reflow the whole file). Returns the list of paths that actually changed.
240
284
  */
241
285
  function applyDepUpdates(workspaceDir, surveyRows, dryRun) {
242
- const behind = surveyRows.filter(r => r.status === 'behind')
286
+ const behind = surveyRows.filter((r) => r.status === 'behind')
243
287
  const byDir = {}
244
288
  for (const row of behind) {
245
289
  const dir = row.relDir === '(root)' ? '' : row.relDir
@@ -254,12 +298,20 @@ function applyDepUpdates(workspaceDir, surveyRows, dryRun) {
254
298
  if (!existsSync(pkgPath)) continue
255
299
  const original = readFileSync(pkgPath, 'utf8')
256
300
  let pkg
257
- try { pkg = JSON.parse(original) } catch { continue }
301
+ try {
302
+ pkg = JSON.parse(original)
303
+ } catch {
304
+ continue
305
+ }
258
306
 
259
307
  let changed = false
260
308
  for (const row of dirRows) {
261
309
  const section = pkg[row.section]
262
- if (section && section[row.name] !== undefined && section[row.name] !== row.target) {
310
+ if (
311
+ section &&
312
+ section[row.name] !== undefined &&
313
+ section[row.name] !== row.target
314
+ ) {
263
315
  section[row.name] = row.target
264
316
  changed = true
265
317
  }
@@ -295,8 +347,12 @@ export async function update(args = []) {
295
347
  const cliVersion = getCliVersion()
296
348
 
297
349
  if ((agentsOnly || depsOnly) && !inProject) {
298
- error(`${agentsOnly ? '--agents-only' : '--deps-only'} requires a Uniweb project (no \`uniweb\` dep in the workspace root).`)
299
- log(`${colors.dim}Run this command from inside a project created by${colors.reset} ${colors.cyan}uniweb create${colors.reset}${colors.dim}.${colors.reset}`)
350
+ error(
351
+ `${agentsOnly ? '--agents-only' : '--deps-only'} requires a Uniweb project (no \`uniweb\` dep in the workspace root).`
352
+ )
353
+ log(
354
+ `${colors.dim}Run this command from inside a project created by${colors.reset} ${colors.cyan}uniweb create${colors.reset}${colors.dim}.${colors.reset}`
355
+ )
300
356
  process.exit(1)
301
357
  }
302
358
 
@@ -328,45 +384,64 @@ export async function update(args = []) {
328
384
  // same convention `--version` follows in index.js. Skipped entirely when
329
385
  // there is nothing to reconcile — the notice is never reached from those
330
386
  // paths, and looking it up anyway would spend a network call on nothing.
331
- const latestCli = (isNpx || !inProject)
332
- ? null
333
- : await getLatestVersion({ allowNetwork: !!process.stdout.isTTY })
387
+ const latestCli =
388
+ isNpx || !inProject
389
+ ? null
390
+ : await getLatestVersion({ allowNetwork: !!process.stdout.isTTY })
334
391
 
335
392
  if (isNpx) {
336
- log(`${colors.dim}Running${colors.reset} ${colors.cyan}uniweb@${cliVersion}${colors.reset} ${colors.dim}via npx — aligning this project to v${cliVersion}'s matrix.${colors.reset}`)
337
- log(`${colors.dim}(To install the CLI:${colors.reset} ${colors.cyan}npm i -g uniweb${colors.reset}${colors.dim}.)${colors.reset}`)
393
+ log(
394
+ `${colors.dim}Running${colors.reset} ${colors.cyan}uniweb@${cliVersion}${colors.reset} ${colors.dim}via npx — aligning this project to v${cliVersion}'s matrix.${colors.reset}`
395
+ )
396
+ log(
397
+ `${colors.dim}(To install the CLI:${colors.reset} ${colors.cyan}npm i -g uniweb${colors.reset}${colors.dim}.)${colors.reset}`
398
+ )
338
399
  log('')
339
400
  } else if (!isGlobal) {
340
401
  // Project-local copy (lives in this project's node_modules).
341
- log(`${colors.dim}Running the project-local CLI (v${cliVersion}) — pinned by your project's${colors.reset} ${colors.cyan}package.json${colors.reset}${colors.dim}.${colors.reset}`)
402
+ log(
403
+ `${colors.dim}Running the project-local CLI (v${cliVersion}) — pinned by your project's${colors.reset} ${colors.cyan}package.json${colors.reset}${colors.dim}.${colors.reset}`
404
+ )
342
405
  log('')
343
406
  }
344
407
 
345
408
  if (!inProject) {
346
- log(`${colors.dim}Not inside a Uniweb project — nothing to reconcile.${colors.reset}`)
347
- log(`${colors.dim}Run this from a project created by${colors.reset} ${colors.cyan}uniweb create${colors.reset}${colors.dim}.${colors.reset}`)
409
+ log(
410
+ `${colors.dim}Not inside a Uniweb project nothing to reconcile.${colors.reset}`
411
+ )
412
+ log(
413
+ `${colors.dim}Run this from a project created by${colors.reset} ${colors.cyan}uniweb create${colors.reset}${colors.dim}.${colors.reset}`
414
+ )
348
415
  log('')
349
416
  return
350
417
  }
351
418
 
352
419
  // ── Step 1: Deps alignment ───────────────────────────────────────
353
- let depsEdited = false // package.json files were rewritten
354
- let installRan = false // `<pm> install` ran and succeeded
420
+ let depsEdited = false // package.json files were rewritten
421
+ let installRan = false // `<pm> install` ran and succeeded
355
422
  let editedPaths = []
356
423
 
357
424
  if (!skipDeps && survey) {
358
425
  if (!survey.anyDrift) {
359
426
  // The count carries the work the collapsed table no longer shows.
360
- success(`Workspace deps are aligned with the CLI (${survey.rows.length} checked).`)
427
+ success(
428
+ `Workspace deps are aligned with the CLI (${survey.rows.length} checked).`
429
+ )
361
430
  if (survey.anyAhead) {
362
- log(`${colors.dim}(Some deps are ahead of the CLI's bundled matrix — left untouched.)${colors.reset}`)
431
+ log(
432
+ `${colors.dim}(Some deps are ahead of the CLI's bundled matrix — left untouched.)${colors.reset}`
433
+ )
363
434
  }
364
435
  if (!existsSync(join(workspaceDir, 'node_modules'))) {
365
- warn(`No ${colors.bright}node_modules${colors.reset} in the workspace — run ${colors.cyan}${installCmd(installPm || 'pnpm')}${colors.reset} to install.`)
436
+ warn(
437
+ `No ${colors.bright}node_modules${colors.reset} in the workspace — run ${colors.cyan}${installCmd(installPm || 'pnpm')}${colors.reset} to install.`
438
+ )
366
439
  }
367
440
  log('')
368
441
  } else {
369
- log(`${colors.yellow}⚠${colors.reset} Some workspace deps lag the CLI's bundled matrix.`)
442
+ log(
443
+ `${colors.yellow}⚠${colors.reset} Some workspace deps lag the CLI's bundled matrix.`
444
+ )
370
445
  log('')
371
446
 
372
447
  // Decide whether to write the package.json edits.
@@ -377,8 +452,12 @@ export async function update(args = []) {
377
452
  proceed = true
378
453
  } else if (nonInteractive) {
379
454
  proceed = false
380
- info(`${colors.dim}Non-interactive — printing the alignment plan; not editing files.${colors.reset}`)
381
- log(`${colors.dim}To apply, re-run with${colors.reset} ${colors.cyan}--yes${colors.reset}${colors.dim}, or align manually:${colors.reset}`)
455
+ info(
456
+ `${colors.dim}Non-interactive printing the alignment plan; not editing files.${colors.reset}`
457
+ )
458
+ log(
459
+ `${colors.dim}To apply, re-run with${colors.reset} ${colors.cyan}--yes${colors.reset}${colors.dim}, or align manually:${colors.reset}`
460
+ )
382
461
  log(` ${colors.cyan}pnpm update "@uniweb/*" uniweb -r${colors.reset}`)
383
462
  log('')
384
463
  } else {
@@ -386,7 +465,7 @@ export async function update(args = []) {
386
465
  type: 'confirm',
387
466
  name: 'go',
388
467
  message: `Edit workspace package.json files to align with v${cliVersion}?`,
389
- initial: true,
468
+ initial: true
390
469
  })
391
470
  proceed = !!go
392
471
  }
@@ -395,11 +474,18 @@ export async function update(args = []) {
395
474
  const wouldEdit = applyDepUpdates(workspaceDir, survey.rows, true)
396
475
  if (wouldEdit.length > 0) {
397
476
  info('Dry-run: would update package.json in:')
398
- for (const path of wouldEdit) log(` ${colors.dim}- ${relativize(path, workspaceDir)}${colors.reset}`)
477
+ for (const path of wouldEdit)
478
+ log(
479
+ ` ${colors.dim}- ${relativize(path, workspaceDir)}${colors.reset}`
480
+ )
399
481
  if (installPm) {
400
- log(`${colors.dim}Then would run:${colors.reset} ${colors.cyan}${installCmd(installPm)}${colors.reset}`)
482
+ log(
483
+ `${colors.dim}Then would run:${colors.reset} ${colors.cyan}${installCmd(installPm)}${colors.reset}`
484
+ )
401
485
  } else {
402
- log(`${colors.dim}Then would prompt for an install command (no lockfile detected).${colors.reset}`)
486
+ log(
487
+ `${colors.dim}Then would prompt for an install command (no lockfile detected).${colors.reset}`
488
+ )
403
489
  }
404
490
  log('')
405
491
  }
@@ -410,26 +496,32 @@ export async function update(args = []) {
410
496
  info('No package.json files needed changes.')
411
497
  log('')
412
498
  } else {
413
- for (const path of editedPaths) success(`Updated ${relativize(path, workspaceDir)}`)
499
+ for (const path of editedPaths)
500
+ success(`Updated ${relativize(path, workspaceDir)}`)
414
501
  log('')
415
502
 
416
503
  // Resolve the workspace PM (lockfile-driven). If absent, ask.
417
504
  if (!installPm) {
418
505
  if (nonInteractive) {
419
- warn('No lockfile in workspace root — cannot pick an install command for you.')
420
- log(`${colors.dim}Run one of:${colors.reset} ${colors.cyan}pnpm install${colors.reset} ${colors.dim}/${colors.reset} ${colors.cyan}yarn install${colors.reset} ${colors.dim}/${colors.reset} ${colors.cyan}npm install${colors.reset}`)
506
+ warn(
507
+ 'No lockfile in workspace root cannot pick an install command for you.'
508
+ )
509
+ log(
510
+ `${colors.dim}Run one of:${colors.reset} ${colors.cyan}pnpm install${colors.reset} ${colors.dim}/${colors.reset} ${colors.cyan}yarn install${colors.reset} ${colors.dim}/${colors.reset} ${colors.cyan}npm install${colors.reset}`
511
+ )
421
512
  log('')
422
513
  } else {
423
514
  const { picked } = await prompts({
424
515
  type: 'select',
425
516
  name: 'picked',
426
- message: 'No lockfile found. Which package manager does this workspace use?',
517
+ message:
518
+ 'No lockfile found. Which package manager does this workspace use?',
427
519
  choices: [
428
520
  { title: 'pnpm', value: 'pnpm' },
429
521
  { title: 'yarn', value: 'yarn' },
430
522
  { title: 'npm', value: 'npm' },
431
- { title: 'skip — I\'ll install manually', value: null },
432
- ],
523
+ { title: "skip — I'll install manually", value: null }
524
+ ]
433
525
  })
434
526
  installPm = picked || null
435
527
  }
@@ -442,14 +534,16 @@ export async function update(args = []) {
442
534
  runInstall = true
443
535
  } else if (nonInteractive) {
444
536
  runInstall = false
445
- info(`${colors.dim}Non-interactive — run the install yourself:${colors.reset} ${colors.cyan}${cmd}${colors.reset}`)
537
+ info(
538
+ `${colors.dim}Non-interactive — run the install yourself:${colors.reset} ${colors.cyan}${cmd}${colors.reset}`
539
+ )
446
540
  log('')
447
541
  } else {
448
542
  const { go } = await prompts({
449
543
  type: 'confirm',
450
544
  name: 'go',
451
545
  message: `Run \`${cmd}\` now?`,
452
- initial: true,
546
+ initial: true
453
547
  })
454
548
  runInstall = !!go
455
549
  }
@@ -461,15 +555,25 @@ export async function update(args = []) {
461
555
  success('Install complete.')
462
556
  log('')
463
557
  } else {
464
- error(`Install failed (exit ${code}). package.json edits are intact.`)
465
- const editedRel = editedPaths.map(p => relativize(p, workspaceDir)).join(' ')
466
- log(`${colors.dim}To revert:${colors.reset} ${colors.cyan}git checkout -- ${editedRel}${colors.reset}`)
467
- log(`${colors.dim}To retry: ${colors.reset} ${colors.cyan}${cmd}${colors.reset}`)
558
+ error(
559
+ `Install failed (exit ${code}). package.json edits are intact.`
560
+ )
561
+ const editedRel = editedPaths
562
+ .map((p) => relativize(p, workspaceDir))
563
+ .join(' ')
564
+ log(
565
+ `${colors.dim}To revert:${colors.reset} ${colors.cyan}git checkout -- ${editedRel}${colors.reset}`
566
+ )
567
+ log(
568
+ `${colors.dim}To retry: ${colors.reset} ${colors.cyan}${cmd}${colors.reset}`
569
+ )
468
570
  log('')
469
571
  process.exit(code)
470
572
  }
471
573
  } else {
472
- log(`${colors.dim}Skipped install. Edits saved; run${colors.reset} ${colors.cyan}${cmd}${colors.reset} ${colors.dim}to apply them.${colors.reset}`)
574
+ log(
575
+ `${colors.dim}Skipped install. Edits saved; run${colors.reset} ${colors.cyan}${cmd}${colors.reset} ${colors.dim}to apply them.${colors.reset}`
576
+ )
473
577
  log('')
474
578
  }
475
579
  }
@@ -482,12 +586,20 @@ export async function update(args = []) {
482
586
  }
483
587
 
484
588
  // ── Step 2: AGENTS.md ────────────────────────────────────────────
485
- let agentsResult = null // 'created' | 'updated' | 'current' | 'skipped'
589
+ let agentsResult = null // 'created' | 'updated' | 'current' | 'skipped'
486
590
 
487
591
  if (!skipAgents) {
488
592
  agentsResult = await refreshAgents({
489
- workspaceDir, cliVersion, allowMismatch, dryRun,
490
- hasYes, nonInteractive, agentsOnly, depsEdited, installRan, installPm,
593
+ workspaceDir,
594
+ cliVersion,
595
+ allowMismatch,
596
+ dryRun,
597
+ hasYes,
598
+ nonInteractive,
599
+ agentsOnly,
600
+ depsEdited,
601
+ installRan,
602
+ installPm
491
603
  })
492
604
  }
493
605
 
@@ -503,14 +615,30 @@ export async function update(args = []) {
503
615
  }
504
616
 
505
617
  // ── Closing summary ──────────────────────────────────────────────
506
- if (!dryRun && (depsEdited || agentsResult === 'created' || agentsResult === 'updated')) {
507
- printSummary({ editedPaths, depsEdited, installRan, installPm, agentsResult, cliVersion })
618
+ if (
619
+ !dryRun &&
620
+ (depsEdited || agentsResult === 'created' || agentsResult === 'updated')
621
+ ) {
622
+ printSummary({
623
+ editedPaths,
624
+ depsEdited,
625
+ installRan,
626
+ installPm,
627
+ agentsResult,
628
+ cliVersion
629
+ })
508
630
  }
509
631
 
510
632
  // Last, deliberately — see printStaleCliNotice. Everything above reports
511
633
  // against THIS CLI's matrix; if the CLI itself is behind, that is the note
512
634
  // the reader should leave with.
513
- printStaleCliNotice({ cliVersion, latest: latestCli, isNpx, isGlobal, globalPm })
635
+ printStaleCliNotice({
636
+ cliVersion,
637
+ latest: latestCli,
638
+ isNpx,
639
+ isGlobal,
640
+ globalPm
641
+ })
514
642
  }
515
643
 
516
644
  /**
@@ -521,16 +649,17 @@ async function reportInstallIntegrity(workspaceDir) {
521
649
  // Imported here, not at the top: discover.js reaches @uniweb/build, an
522
650
  // optional peer that must stay off the CLI's startup path so `npx uniweb
523
651
  // create` works in an empty directory. update.js is loaded at startup.
524
- const { discoverSites, discoverFoundations } = await import('../utils/discover.js')
652
+ const { discoverSites, discoverFoundations } =
653
+ await import('../utils/discover.js')
525
654
 
526
655
  const sites = (await discoverSites(workspaceDir)).map((s) => ({
527
656
  name: s.name,
528
657
  path: join(workspaceDir, s.path),
529
- foundation: readDeclaredFoundation(join(workspaceDir, s.path)),
658
+ foundation: readDeclaredFoundation(join(workspaceDir, s.path))
530
659
  }))
531
660
  const foundations = (await discoverFoundations(workspaceDir)).map((f) => ({
532
661
  name: f.name,
533
- path: join(workspaceDir, f.path),
662
+ path: join(workspaceDir, f.path)
534
663
  }))
535
664
 
536
665
  const findings = checkWorkspaceInstall(sites, foundations, workspaceDir)
@@ -541,7 +670,9 @@ async function reportInstallIntegrity(workspaceDir) {
541
670
  for (const finding of findings) {
542
671
  log(` ${finding.message}`)
543
672
  }
544
- log(` ${colors.dim}${findings[0].remedy}. \`uniweb doctor\` explains in full.${colors.reset}`)
673
+ log(
674
+ ` ${colors.dim}${findings[0].remedy}. \`uniweb doctor\` explains in full.${colors.reset}`
675
+ )
545
676
  } catch {
546
677
  // A post-flight check must never turn a successful update into a failure.
547
678
  }
@@ -557,8 +688,16 @@ async function reportInstallIntegrity(workspaceDir) {
557
688
  */
558
689
  async function refreshAgents(ctx) {
559
690
  const {
560
- workspaceDir, cliVersion, allowMismatch, dryRun,
561
- hasYes, nonInteractive, agentsOnly, depsEdited, installRan, installPm,
691
+ workspaceDir,
692
+ cliVersion,
693
+ allowMismatch,
694
+ dryRun,
695
+ hasYes,
696
+ nonInteractive,
697
+ agentsOnly,
698
+ depsEdited,
699
+ installRan,
700
+ installPm
562
701
  } = ctx
563
702
 
564
703
  // Deps were rewritten but not installed → node_modules is now behind
@@ -566,8 +705,12 @@ async function refreshAgents(ctx) {
566
705
  // installed code doesn't have. No override — run the install first.
567
706
  if (depsEdited && !installRan) {
568
707
  const cmd = installCmd(installPm || 'pnpm')
569
- warn('AGENTS.md refresh skipped: package.json was updated but not installed.')
570
- log(`${colors.dim}Your${colors.reset} ${colors.bright}node_modules${colors.reset} ${colors.dim}is behind your${colors.reset} ${colors.bright}package.json${colors.reset}${colors.dim}. Run${colors.reset} ${colors.cyan}${cmd}${colors.reset}${colors.dim}, then re-run${colors.reset} ${colors.cyan}uniweb update${colors.reset}${colors.dim}.${colors.reset}`)
708
+ warn(
709
+ 'AGENTS.md refresh skipped: package.json was updated but not installed.'
710
+ )
711
+ log(
712
+ `${colors.dim}Your${colors.reset} ${colors.bright}node_modules${colors.reset} ${colors.dim}is behind your${colors.reset} ${colors.bright}package.json${colors.reset}${colors.dim}. Run${colors.reset} ${colors.cyan}${cmd}${colors.reset}${colors.dim}, then re-run${colors.reset} ${colors.cyan}uniweb update${colors.reset}${colors.dim}.${colors.reset}`
713
+ )
571
714
  log('')
572
715
  return 'skipped'
573
716
  }
@@ -577,8 +720,12 @@ async function refreshAgents(ctx) {
577
720
  const finalSurvey = await surveyWorkspaceDeps(workspaceDir)
578
721
  if (finalSurvey.anyDrift && !allowMismatch) {
579
722
  warn('AGENTS.md refresh skipped: workspace deps still lag the CLI.')
580
- log(`${colors.dim}AGENTS.md from v${cliVersion} would document features not in your installed packages.${colors.reset}`)
581
- log(`${colors.dim}Re-run without ${colors.reset}${colors.cyan}--no-deps${colors.reset}${colors.dim}, or pass ${colors.reset}${colors.cyan}--allow-mismatch${colors.reset}${colors.dim} to override.${colors.reset}`)
723
+ log(
724
+ `${colors.dim}AGENTS.md from v${cliVersion} would document features not in your installed packages.${colors.reset}`
725
+ )
726
+ log(
727
+ `${colors.dim}Re-run without ${colors.reset}${colors.cyan}--no-deps${colors.reset}${colors.dim}, or pass ${colors.reset}${colors.cyan}--allow-mismatch${colors.reset}${colors.dim} to override.${colors.reset}`
728
+ )
582
729
  log('')
583
730
  if (agentsOnly) process.exit(1)
584
731
  return 'skipped'
@@ -592,7 +739,9 @@ async function refreshAgents(ctx) {
592
739
  }
593
740
 
594
741
  if (dryRun) {
595
- info(`Dry-run: would ${currentAgentsVersion ? `update AGENTS.md (v${currentAgentsVersion} → v${cliVersion})` : `create AGENTS.md (v${cliVersion})`}.`)
742
+ info(
743
+ `Dry-run: would ${currentAgentsVersion ? `update AGENTS.md (v${currentAgentsVersion} → v${cliVersion})` : `create AGENTS.md (v${cliVersion})`}.`
744
+ )
596
745
  return 'skipped'
597
746
  }
598
747
 
@@ -600,7 +749,12 @@ async function refreshAgents(ctx) {
600
749
  const action = currentAgentsVersion
601
750
  ? `Update AGENTS.md (v${currentAgentsVersion} → v${cliVersion})?`
602
751
  : `Create AGENTS.md (v${cliVersion})?`
603
- const { yes } = await prompts({ type: 'confirm', name: 'yes', message: action, initial: true })
752
+ const { yes } = await prompts({
753
+ type: 'confirm',
754
+ name: 'yes',
755
+ message: action,
756
+ initial: true
757
+ })
604
758
  if (!yes) {
605
759
  log(`${colors.dim}Skipped AGENTS.md.${colors.reset}`)
606
760
  return 'skipped'
@@ -623,20 +777,38 @@ async function refreshAgents(ctx) {
623
777
  * installed packages changed, and stay quiet otherwise (this runs on every
624
778
  * `uniweb update`, including no-op ones).
625
779
  */
626
- export function printSummary({ editedPaths, depsEdited, installRan, installPm, agentsResult, cliVersion }) {
780
+ export function printSummary({
781
+ editedPaths,
782
+ depsEdited,
783
+ installRan,
784
+ installPm,
785
+ agentsResult,
786
+ cliVersion
787
+ }) {
627
788
  log(`${colors.bright}Summary${colors.reset}`)
628
789
  if (depsEdited) {
629
- log(` ${colors.green}✓${colors.reset} package.json updated in ${editedPaths.length} file${editedPaths.length === 1 ? '' : 's'}`)
790
+ log(
791
+ ` ${colors.green}✓${colors.reset} package.json updated in ${editedPaths.length} file${editedPaths.length === 1 ? '' : 's'}`
792
+ )
630
793
  if (installRan) {
631
- log(` ${colors.green}✓${colors.reset} ${installCmd(installPm || 'pnpm')} completed`)
794
+ log(
795
+ ` ${colors.green}✓${colors.reset} ${installCmd(installPm || 'pnpm')} completed`
796
+ )
632
797
  } else {
633
- log(` ${colors.yellow}⚠${colors.reset} install NOT run — run ${colors.cyan}${installCmd(installPm || 'pnpm')}${colors.reset} to apply`)
798
+ log(
799
+ ` ${colors.yellow}⚠${colors.reset} install NOT run — run ${colors.cyan}${installCmd(installPm || 'pnpm')}${colors.reset} to apply`
800
+ )
634
801
  }
635
802
  }
636
- if (agentsResult === 'created') log(` ${colors.green}✓${colors.reset} AGENTS.md created (v${cliVersion})`)
637
- else if (agentsResult === 'updated') log(` ${colors.green}✓${colors.reset} AGENTS.md updated (v${cliVersion})`)
638
- else if (agentsResult === 'skipped' && depsEdited) log(` ${colors.dim}·${colors.reset} AGENTS.md not refreshed (see above)`)
639
- log(`${colors.dim}Review changes with${colors.reset} ${colors.cyan}git diff${colors.reset}${colors.dim}, then commit.${colors.reset}`)
803
+ if (agentsResult === 'created')
804
+ log(` ${colors.green}✓${colors.reset} AGENTS.md created (v${cliVersion})`)
805
+ else if (agentsResult === 'updated')
806
+ log(` ${colors.green}✓${colors.reset} AGENTS.md updated (v${cliVersion})`)
807
+ else if (agentsResult === 'skipped' && depsEdited)
808
+ log(` ${colors.dim}·${colors.reset} AGENTS.md not refreshed (see above)`)
809
+ log(
810
+ `${colors.dim}Review changes with${colors.reset} ${colors.cyan}git diff${colors.reset}${colors.dim}, then commit.${colors.reset}`
811
+ )
640
812
 
641
813
  // Only when the installed packages actually changed under a process that may
642
814
  // still be running. This failure is nastier than it sounds: hot reload picks
@@ -646,8 +818,12 @@ export function printSummary({ editedPaths, depsEdited, installRan, installPm, a
646
818
  // thinks to look is the server that has been running fine all along.
647
819
  if (depsEdited && installRan) {
648
820
  log('')
649
- log(`${colors.yellow}↻${colors.reset} ${colors.bright}Restart any running dev server.${colors.reset}`)
650
- log(`${colors.dim}Hot reload picks up your source, not swapped dependencies — a running${colors.reset} ${colors.cyan}uniweb dev${colors.reset} ${colors.dim}keeps using the framework version it started with.${colors.reset}`)
821
+ log(
822
+ `${colors.yellow}↻${colors.reset} ${colors.bright}Restart any running dev server.${colors.reset}`
823
+ )
824
+ log(
825
+ `${colors.dim}Hot reload picks up your source, not swapped dependencies — a running${colors.reset} ${colors.cyan}uniweb dev${colors.reset} ${colors.dim}keeps using the framework version it started with.${colors.reset}`
826
+ )
651
827
  }
652
828
 
653
829
  log('')