uniweb 0.12.14 → 0.12.16
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 +46 -19
- package/package.json +3 -3
- package/partials/agents.md +5 -0
- package/src/commands/add.js +224 -7
- package/src/commands/build.js +2 -2
- package/src/commands/doctor.js +155 -1
- package/src/commands/update.js +352 -54
- package/src/framework-index.json +3 -3
- package/src/index.js +25 -14
- package/src/utils/config.js +32 -32
- package/src/utils/pm.js +28 -2
package/src/commands/update.js
CHANGED
|
@@ -1,35 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* uniweb update —
|
|
3
|
-
*
|
|
2
|
+
* uniweb update — Reconcile a Uniweb workspace's state with the running
|
|
3
|
+
* CLI's expectations. Three convergence steps, in order:
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* 1. Self-update the global CLI install (npm / pnpm / yarn auto-detected).
|
|
6
|
+
* 2. Align workspace `@uniweb/*` + `uniweb` deps to the CLI's bundled
|
|
7
|
+
* version matrix (`getResolvedVersions`), then run `<pm> install`.
|
|
8
|
+
* 3. Refresh AGENTS.md from the CLI's bundled partial.
|
|
6
9
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* In non-interactive mode, prints the command and exits — never runs an
|
|
14
|
-
* unconfirmed self-update from a script.
|
|
15
|
-
*
|
|
16
|
-
* 2. **Refresh AGENTS.md** (only when the cwd resolves to a *Uniweb*
|
|
17
|
-
* project — checked via `package.json::devDependencies::uniweb` or
|
|
18
|
-
* `dependencies::uniweb` at the workspace root). The previous
|
|
19
|
-
* implementation walked up looking for ANY pnpm-workspace.yaml or
|
|
20
|
-
* `package.json::workspaces` root, which falsely identified unrelated
|
|
21
|
-
* monorepos as Uniweb projects and wrote AGENTS.md into them.
|
|
10
|
+
* Why steps 2 and 3 belong together: AGENTS.md is regenerated from the
|
|
11
|
+
* CLI's *current* partials and stamped with `cliVersion`. Refreshing it
|
|
12
|
+
* while declared deps in `package.json` lag the CLI silently produces a
|
|
13
|
+
* doc that documents features the installed code doesn't have. The
|
|
14
|
+
* verb's drift gate refuses that combination unless `--allow-mismatch`
|
|
15
|
+
* is explicit.
|
|
22
16
|
*
|
|
23
17
|
* Flags:
|
|
24
|
-
* --agents-only
|
|
25
|
-
* --
|
|
26
|
-
* --
|
|
27
|
-
* --
|
|
18
|
+
* --agents-only Skip self-update + deps; only refresh AGENTS.md.
|
|
19
|
+
* --deps-only Skip self-update + AGENTS.md; only align deps.
|
|
20
|
+
* --no-agents Skip the AGENTS.md step.
|
|
21
|
+
* --no-deps Skip the deps-alignment step.
|
|
22
|
+
* --dry-run Print survey + would-be writes; no mutations.
|
|
23
|
+
* --allow-mismatch Permit AGENTS.md refresh when declared deps lag.
|
|
24
|
+
* --yes Skip confirmation prompts (still respects gates).
|
|
25
|
+
* --non-interactive Auto-detected; never auto-installs from a script.
|
|
28
26
|
*
|
|
29
27
|
* Project-local case (CLI lives in node_modules, not global): self-update
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* AGENTS.md refresh path only.
|
|
28
|
+
* is a no-op (the version is pinned by package.json). Deps + AGENTS.md
|
|
29
|
+
* paths still run.
|
|
33
30
|
*/
|
|
34
31
|
|
|
35
32
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
@@ -37,10 +34,11 @@ import { join } from 'node:path'
|
|
|
37
34
|
import { spawn } from 'node:child_process'
|
|
38
35
|
import prompts from 'prompts'
|
|
39
36
|
|
|
40
|
-
import { findWorkspaceRoot } from '../utils/workspace.js'
|
|
37
|
+
import { findWorkspaceRoot, getWorkspacePackages } from '../utils/workspace.js'
|
|
41
38
|
import { readAgentsVersion, generateAgentsContent } from '../utils/agents-stamp.js'
|
|
42
|
-
import { getCliVersion } from '../versions.js'
|
|
39
|
+
import { getCliVersion, getResolvedVersions, updatePackageVersions } from '../versions.js'
|
|
43
40
|
import { isNonInteractive } from '../utils/interactive.js'
|
|
41
|
+
import { detectWorkspacePm, installCmd } from '../utils/pm.js'
|
|
44
42
|
|
|
45
43
|
const colors = {
|
|
46
44
|
reset: '\x1b[0m',
|
|
@@ -55,6 +53,7 @@ const colors = {
|
|
|
55
53
|
const success = (msg) => console.log(`${colors.green}✓${colors.reset} ${msg}`)
|
|
56
54
|
const warn = (msg) => console.log(`${colors.yellow}⚠${colors.reset} ${msg}`)
|
|
57
55
|
const error = (msg) => console.log(`${colors.red}✗${colors.reset} ${msg}`)
|
|
56
|
+
const info = (msg) => console.log(`${colors.cyan}ℹ${colors.reset} ${msg}`)
|
|
58
57
|
const log = console.log
|
|
59
58
|
|
|
60
59
|
/**
|
|
@@ -72,8 +71,8 @@ function isGlobalInstall() {
|
|
|
72
71
|
/**
|
|
73
72
|
* Find a *Uniweb* workspace root from cwd. Stricter than findWorkspaceRoot
|
|
74
73
|
* — also requires that the workspace's root package.json declares uniweb
|
|
75
|
-
* as a dep or devDep. Otherwise
|
|
76
|
-
*
|
|
74
|
+
* as a dep or devDep. Otherwise we'd write AGENTS.md and edit package.json
|
|
75
|
+
* files in unrelated monorepos.
|
|
77
76
|
*/
|
|
78
77
|
function findUniwebWorkspace(cwd) {
|
|
79
78
|
const workspaceDir = findWorkspaceRoot(cwd)
|
|
@@ -90,9 +89,9 @@ function findUniwebWorkspace(cwd) {
|
|
|
90
89
|
}
|
|
91
90
|
|
|
92
91
|
/**
|
|
93
|
-
* Detect the package manager that owns the global install.
|
|
94
|
-
* based
|
|
95
|
-
*
|
|
92
|
+
* Detect the package manager that owns the *global* CLI install.
|
|
93
|
+
* Path-based (different signal than detectWorkspacePm, which reads
|
|
94
|
+
* lockfiles in the workspace).
|
|
96
95
|
*
|
|
97
96
|
* @returns {'pnpm'|'yarn'|'npm'}
|
|
98
97
|
*/
|
|
@@ -127,11 +126,21 @@ async function fetchLatestVersion() {
|
|
|
127
126
|
}
|
|
128
127
|
|
|
129
128
|
/**
|
|
130
|
-
*
|
|
129
|
+
* Strip a leading semver range operator (^, ~, >=, <, etc.) so two specs
|
|
130
|
+
* can be compared by their underlying version. Range expressions like
|
|
131
|
+
* ">=0.5 <0.7" aren't fully parsed — we take the first version-shaped
|
|
132
|
+
* token. Sufficient for `@uniweb/*` deps which use `^x.y.z` consistently.
|
|
133
|
+
*/
|
|
134
|
+
function stripRange(spec) {
|
|
135
|
+
return (spec || '').replace(/^[\^~>=<\s]+/, '').trim().split(/\s+/)[0] || ''
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Compare two version specs (range prefix tolerated). Returns 1/-1/0.
|
|
131
140
|
*/
|
|
132
141
|
function compareSemver(a, b) {
|
|
133
|
-
const pa = a.split('.').map(Number)
|
|
134
|
-
const pb = b.split('.').map(Number)
|
|
142
|
+
const pa = stripRange(a).split('.').map(Number)
|
|
143
|
+
const pb = stripRange(b).split('.').map(Number)
|
|
135
144
|
for (let i = 0; i < 3; i++) {
|
|
136
145
|
if ((pa[i] || 0) > (pb[i] || 0)) return 1
|
|
137
146
|
if ((pa[i] || 0) < (pb[i] || 0)) return -1
|
|
@@ -142,28 +151,177 @@ function compareSemver(a, b) {
|
|
|
142
151
|
/**
|
|
143
152
|
* Run a shell command, inheriting stdio. Resolves with the exit code.
|
|
144
153
|
*/
|
|
145
|
-
function runCommand(cmd) {
|
|
154
|
+
function runCommand(cmd, cwd) {
|
|
146
155
|
return new Promise((resolve) => {
|
|
147
156
|
const [bin, ...rest] = cmd.split(' ')
|
|
148
|
-
const child = spawn(bin, rest, { stdio: 'inherit' })
|
|
157
|
+
const child = spawn(bin, rest, { stdio: 'inherit', cwd })
|
|
149
158
|
child.on('close', code => resolve(code ?? 0))
|
|
150
159
|
child.on('error', () => resolve(1))
|
|
151
160
|
})
|
|
152
161
|
}
|
|
153
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Survey workspace `@uniweb/*` and `uniweb` deps against the CLI's
|
|
165
|
+
* bundled version matrix. Returns a structured report with one row per
|
|
166
|
+
* (package directory, dep section, dep name).
|
|
167
|
+
*
|
|
168
|
+
* Comparison is on *declared* versions (package.json), not installed
|
|
169
|
+
* (node_modules) — that's what the user committed and what they'll
|
|
170
|
+
* `git diff` after `applyDepUpdates`.
|
|
171
|
+
*/
|
|
172
|
+
async function surveyVersions(workspaceDir) {
|
|
173
|
+
const targets = getResolvedVersions()
|
|
174
|
+
const packages = await getWorkspacePackages(workspaceDir)
|
|
175
|
+
const dirs = ['', ...packages]
|
|
176
|
+
const rows = []
|
|
177
|
+
let anyDrift = false
|
|
178
|
+
let anyAhead = false
|
|
179
|
+
|
|
180
|
+
for (const relDir of dirs) {
|
|
181
|
+
const pkgDir = relDir ? join(workspaceDir, relDir) : workspaceDir
|
|
182
|
+
const pkgPath = join(pkgDir, 'package.json')
|
|
183
|
+
if (!existsSync(pkgPath)) continue
|
|
184
|
+
let pkg
|
|
185
|
+
try { pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) } catch { continue }
|
|
186
|
+
|
|
187
|
+
for (const sectionName of ['dependencies', 'devDependencies', 'peerDependencies']) {
|
|
188
|
+
const section = pkg[sectionName]
|
|
189
|
+
if (!section) continue
|
|
190
|
+
for (const [name, current] of Object.entries(section)) {
|
|
191
|
+
if (!(name.startsWith('@uniweb/') || name === 'uniweb')) continue
|
|
192
|
+
const target = targets[name]
|
|
193
|
+
if (!target) continue
|
|
194
|
+
const cmp = compareSemver(target, current)
|
|
195
|
+
let status
|
|
196
|
+
if (cmp > 0) { status = 'behind'; anyDrift = true }
|
|
197
|
+
else if (cmp < 0) { status = 'ahead'; anyAhead = true }
|
|
198
|
+
else { status = 'aligned' }
|
|
199
|
+
rows.push({
|
|
200
|
+
relDir: relDir || '(root)',
|
|
201
|
+
section: sectionName,
|
|
202
|
+
name,
|
|
203
|
+
current,
|
|
204
|
+
target,
|
|
205
|
+
status,
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return { targets, rows, anyDrift, anyAhead }
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Print the survey report grouped by package directory.
|
|
216
|
+
*/
|
|
217
|
+
function printSurvey(report, cliVersion, agentsVersion) {
|
|
218
|
+
log('')
|
|
219
|
+
log(`${colors.bright}uniweb CLI:${colors.reset} v${cliVersion}`)
|
|
220
|
+
log(`${colors.bright}AGENTS.md stamp:${colors.reset} ${agentsVersion ? 'v' + agentsVersion : colors.dim + '(none)' + colors.reset}`)
|
|
221
|
+
log('')
|
|
222
|
+
|
|
223
|
+
if (report.rows.length === 0) {
|
|
224
|
+
log(`${colors.dim}No @uniweb/* deps found in workspace package.json files.${colors.reset}`)
|
|
225
|
+
log('')
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const byDir = {}
|
|
230
|
+
for (const row of report.rows) {
|
|
231
|
+
if (!byDir[row.relDir]) byDir[row.relDir] = []
|
|
232
|
+
byDir[row.relDir].push(row)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
log(`${colors.bright}Workspace deps (declared):${colors.reset}`)
|
|
236
|
+
for (const [dir, dirRows] of Object.entries(byDir)) {
|
|
237
|
+
log(` ${colors.dim}${dir}/${colors.reset}`)
|
|
238
|
+
const maxName = Math.max(...dirRows.map(r => r.name.length))
|
|
239
|
+
for (const row of dirRows) {
|
|
240
|
+
const padding = ' '.repeat(maxName - row.name.length)
|
|
241
|
+
let icon, statusText
|
|
242
|
+
if (row.status === 'aligned') {
|
|
243
|
+
icon = `${colors.green}✓${colors.reset}`
|
|
244
|
+
statusText = `${colors.dim}aligned${colors.reset}`
|
|
245
|
+
} else if (row.status === 'behind') {
|
|
246
|
+
icon = `${colors.yellow}✗${colors.reset}`
|
|
247
|
+
statusText = `${colors.yellow}behind${colors.reset}`
|
|
248
|
+
} else {
|
|
249
|
+
icon = `${colors.cyan}↑${colors.reset}`
|
|
250
|
+
statusText = `${colors.cyan}ahead of CLI${colors.reset}`
|
|
251
|
+
}
|
|
252
|
+
log(` ${icon} ${row.name}${padding} ${row.current.padEnd(10)} → ${row.target.padEnd(10)} ${statusText}`)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
log('')
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Apply the CLI's bundled matrix to every workspace package.json.
|
|
260
|
+
* `updatePackageVersions` only touches `@uniweb/*` + `uniweb` keys, so
|
|
261
|
+
* unrelated deps (`react`, `vite`, `file:../foundation`, etc.) are left
|
|
262
|
+
* untouched. Returns the list of paths that actually changed.
|
|
263
|
+
*/
|
|
264
|
+
async function applyDepUpdates(workspaceDir, dryRun) {
|
|
265
|
+
const packages = await getWorkspacePackages(workspaceDir)
|
|
266
|
+
const dirs = ['', ...packages]
|
|
267
|
+
const edited = []
|
|
268
|
+
|
|
269
|
+
for (const relDir of dirs) {
|
|
270
|
+
const pkgDir = relDir ? join(workspaceDir, relDir) : workspaceDir
|
|
271
|
+
const pkgPath = join(pkgDir, 'package.json')
|
|
272
|
+
if (!existsSync(pkgPath)) continue
|
|
273
|
+
const original = readFileSync(pkgPath, 'utf8')
|
|
274
|
+
let pkg
|
|
275
|
+
try { pkg = JSON.parse(original) } catch { continue }
|
|
276
|
+
|
|
277
|
+
const updated = updatePackageVersions(pkg)
|
|
278
|
+
const newContent = JSON.stringify(updated, null, 2) + (original.endsWith('\n') ? '\n' : '')
|
|
279
|
+
|
|
280
|
+
if (newContent !== original) {
|
|
281
|
+
edited.push(pkgPath)
|
|
282
|
+
if (!dryRun) writeFileSync(pkgPath, newContent)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return edited
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function relativize(path, root) {
|
|
290
|
+
return path.startsWith(root) ? path.slice(root.length + 1) : path
|
|
291
|
+
}
|
|
292
|
+
|
|
154
293
|
export async function update(args = []) {
|
|
155
294
|
const agentsOnly = args.includes('--agents-only')
|
|
156
|
-
const
|
|
157
|
-
const
|
|
295
|
+
const depsOnly = args.includes('--deps-only')
|
|
296
|
+
const skipAgents = args.includes('--no-agents') || depsOnly
|
|
297
|
+
const skipDeps = args.includes('--no-deps') || agentsOnly
|
|
298
|
+
const dryRun = args.includes('--dry-run')
|
|
299
|
+
const allowMismatch = args.includes('--allow-mismatch')
|
|
300
|
+
const nonInteractive = isNonInteractive(args)
|
|
301
|
+
const skipPrompt = args.includes('--yes') || nonInteractive || dryRun
|
|
158
302
|
const isGlobal = isGlobalInstall()
|
|
159
303
|
const workspaceDir = findUniwebWorkspace(process.cwd())
|
|
160
304
|
const inProject = !!workspaceDir
|
|
161
305
|
const cliVersion = getCliVersion()
|
|
162
306
|
|
|
163
|
-
|
|
164
|
-
|
|
307
|
+
if ((agentsOnly || depsOnly) && !inProject) {
|
|
308
|
+
error(`${agentsOnly ? '--agents-only' : '--deps-only'} requires a Uniweb project (no \`uniweb\` dep in the workspace root).`)
|
|
309
|
+
log(`${colors.dim}Run this command from inside a project created by${colors.reset} ${colors.cyan}uniweb create${colors.reset}${colors.dim}.${colors.reset}`)
|
|
310
|
+
process.exit(1)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// ── Survey first (always, when in a Uniweb project) ──────────────
|
|
314
|
+
let survey = null
|
|
315
|
+
let agentsVersion = null
|
|
316
|
+
if (inProject) {
|
|
317
|
+
survey = await surveyVersions(workspaceDir)
|
|
318
|
+
agentsVersion = readAgentsVersion(join(workspaceDir, 'AGENTS.md'))
|
|
319
|
+
printSurvey(survey, cliVersion, agentsVersion)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ── Step 1: Self-update path ─────────────────────────────────────
|
|
323
|
+
if (!agentsOnly && !depsOnly) {
|
|
165
324
|
if (!isGlobal) {
|
|
166
|
-
// Project-local: can't self-update meaningfully.
|
|
167
325
|
log(`${colors.dim}Running the project-local CLI (v${cliVersion}). This copy is pinned by your${colors.reset}`)
|
|
168
326
|
log(`${colors.dim}project's package.json. To update it, bump${colors.reset} ${colors.cyan}uniweb${colors.reset}${colors.dim} in${colors.reset} ${colors.cyan}package.json${colors.reset}${colors.dim} and re-install.${colors.reset}`)
|
|
169
327
|
log('')
|
|
@@ -184,7 +342,10 @@ export async function update(args = []) {
|
|
|
184
342
|
log(`${colors.dim}Will run:${colors.reset} ${colors.cyan}${cmd}${colors.reset}`)
|
|
185
343
|
log('')
|
|
186
344
|
|
|
187
|
-
if (
|
|
345
|
+
if (dryRun) {
|
|
346
|
+
info(`${colors.dim}--dry-run: would run \`${cmd}\`.${colors.reset}`)
|
|
347
|
+
log('')
|
|
348
|
+
} else if (skipPrompt) {
|
|
188
349
|
log(`${colors.dim}Non-interactive — skipping self-update. Run the command above to update.${colors.reset}`)
|
|
189
350
|
log('')
|
|
190
351
|
} else {
|
|
@@ -211,15 +372,148 @@ export async function update(args = []) {
|
|
|
211
372
|
}
|
|
212
373
|
}
|
|
213
374
|
|
|
214
|
-
//
|
|
375
|
+
// ── Step 2: Deps alignment ───────────────────────────────────────
|
|
376
|
+
if (!skipDeps && inProject && survey) {
|
|
377
|
+
if (!survey.anyDrift) {
|
|
378
|
+
success('Workspace deps are aligned with the CLI.')
|
|
379
|
+
if (survey.anyAhead) {
|
|
380
|
+
log(`${colors.dim}(Some deps are ahead of the CLI's bundled matrix — left untouched.)${colors.reset}`)
|
|
381
|
+
}
|
|
382
|
+
log('')
|
|
383
|
+
} else {
|
|
384
|
+
log(`${colors.yellow}⚠${colors.reset} Some workspace deps lag the CLI's bundled matrix.`)
|
|
385
|
+
log('')
|
|
386
|
+
|
|
387
|
+
let proceed
|
|
388
|
+
if (dryRun) {
|
|
389
|
+
proceed = false
|
|
390
|
+
} else if (skipPrompt) {
|
|
391
|
+
proceed = !nonInteractive || args.includes('--yes')
|
|
392
|
+
// In CI without --yes, refuse to mutate. The survey above is the report.
|
|
393
|
+
if (!proceed) {
|
|
394
|
+
info(`${colors.dim}Non-interactive — printing the alignment plan; not editing files.${colors.reset}`)
|
|
395
|
+
log(`${colors.dim}To apply, re-run with${colors.reset} ${colors.cyan}--yes${colors.reset}${colors.dim}, or align manually:${colors.reset}`)
|
|
396
|
+
log(` ${colors.cyan}pnpm update "@uniweb/*" uniweb -r${colors.reset}`)
|
|
397
|
+
log('')
|
|
398
|
+
}
|
|
399
|
+
} else {
|
|
400
|
+
const { go } = await prompts({
|
|
401
|
+
type: 'confirm',
|
|
402
|
+
name: 'go',
|
|
403
|
+
message: `Edit workspace package.json files to align with v${cliVersion}?`,
|
|
404
|
+
initial: true,
|
|
405
|
+
})
|
|
406
|
+
proceed = !!go
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (dryRun) {
|
|
410
|
+
const wouldEdit = await applyDepUpdates(workspaceDir, true)
|
|
411
|
+
if (wouldEdit.length > 0) {
|
|
412
|
+
info('Dry-run: would update package.json in:')
|
|
413
|
+
for (const path of wouldEdit) log(` ${colors.dim}- ${relativize(path, workspaceDir)}${colors.reset}`)
|
|
414
|
+
const pm = detectWorkspacePm(workspaceDir)
|
|
415
|
+
if (pm) {
|
|
416
|
+
log(`${colors.dim}Then would run:${colors.reset} ${colors.cyan}${installCmd(pm)}${colors.reset}`)
|
|
417
|
+
} else {
|
|
418
|
+
log(`${colors.dim}Then would prompt for an install command (no lockfile detected).${colors.reset}`)
|
|
419
|
+
}
|
|
420
|
+
log('')
|
|
421
|
+
}
|
|
422
|
+
} else if (proceed) {
|
|
423
|
+
const edited = await applyDepUpdates(workspaceDir, false)
|
|
424
|
+
if (edited.length === 0) {
|
|
425
|
+
info('No package.json files needed changes.')
|
|
426
|
+
log('')
|
|
427
|
+
} else {
|
|
428
|
+
for (const path of edited) {
|
|
429
|
+
success(`Updated ${relativize(path, workspaceDir)}`)
|
|
430
|
+
}
|
|
431
|
+
log('')
|
|
432
|
+
|
|
433
|
+
// Resolve the workspace PM (lockfile-driven). If absent, ask.
|
|
434
|
+
let pm = detectWorkspacePm(workspaceDir)
|
|
435
|
+
if (!pm) {
|
|
436
|
+
if (nonInteractive) {
|
|
437
|
+
warn('No lockfile in workspace root — cannot pick an install command for you.')
|
|
438
|
+
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}`)
|
|
439
|
+
log('')
|
|
440
|
+
} else {
|
|
441
|
+
const { picked } = await prompts({
|
|
442
|
+
type: 'select',
|
|
443
|
+
name: 'picked',
|
|
444
|
+
message: 'No lockfile found. Which package manager does this workspace use?',
|
|
445
|
+
choices: [
|
|
446
|
+
{ title: 'pnpm', value: 'pnpm' },
|
|
447
|
+
{ title: 'yarn', value: 'yarn' },
|
|
448
|
+
{ title: 'npm', value: 'npm' },
|
|
449
|
+
{ title: 'skip — I\'ll install manually', value: null },
|
|
450
|
+
],
|
|
451
|
+
})
|
|
452
|
+
pm = picked || null
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (pm) {
|
|
457
|
+
const cmd = installCmd(pm)
|
|
458
|
+
let runInstall
|
|
459
|
+
if (nonInteractive) {
|
|
460
|
+
runInstall = false
|
|
461
|
+
info(`${colors.dim}Non-interactive — printing install command:${colors.reset}`)
|
|
462
|
+
log(` ${colors.cyan}${cmd}${colors.reset}`)
|
|
463
|
+
log('')
|
|
464
|
+
} else if (skipPrompt) {
|
|
465
|
+
runInstall = true
|
|
466
|
+
} else {
|
|
467
|
+
const { go } = await prompts({
|
|
468
|
+
type: 'confirm',
|
|
469
|
+
name: 'go',
|
|
470
|
+
message: `Run \`${cmd}\` now?`,
|
|
471
|
+
initial: true,
|
|
472
|
+
})
|
|
473
|
+
runInstall = !!go
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (runInstall) {
|
|
477
|
+
const code = await runCommand(cmd, workspaceDir)
|
|
478
|
+
if (code === 0) {
|
|
479
|
+
success('Install complete.')
|
|
480
|
+
log('')
|
|
481
|
+
} else {
|
|
482
|
+
error(`Install failed (exit ${code}). package.json edits are intact.`)
|
|
483
|
+
const editedRel = edited.map(p => relativize(p, workspaceDir)).join(' ')
|
|
484
|
+
log(`${colors.dim}To revert:${colors.reset} ${colors.cyan}git checkout -- ${editedRel}${colors.reset}`)
|
|
485
|
+
log(`${colors.dim}To retry: ${colors.reset} ${colors.cyan}${cmd}${colors.reset}`)
|
|
486
|
+
log('')
|
|
487
|
+
process.exit(code)
|
|
488
|
+
}
|
|
489
|
+
} else {
|
|
490
|
+
log(`${colors.dim}Skipped install. Edits saved; run${colors.reset} ${colors.cyan}${cmd}${colors.reset} ${colors.dim}to apply.${colors.reset}`)
|
|
491
|
+
log('')
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
} else {
|
|
496
|
+
log(`${colors.dim}Skipped deps alignment.${colors.reset}`)
|
|
497
|
+
log('')
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// ── Step 3: AGENTS.md ────────────────────────────────────────────
|
|
215
503
|
if (skipAgents) return
|
|
216
504
|
if (!inProject) {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
505
|
+
// Self-update-only invocation outside a Uniweb project: quietly skip.
|
|
506
|
+
return
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Re-survey: deps may have just been edited, which clears the gate.
|
|
510
|
+
const finalSurvey = await surveyVersions(workspaceDir)
|
|
511
|
+
if (finalSurvey.anyDrift && !allowMismatch) {
|
|
512
|
+
warn('AGENTS.md refresh skipped: workspace deps still lag the CLI.')
|
|
513
|
+
log(`${colors.dim}AGENTS.md from v${cliVersion} would document features not in your installed packages.${colors.reset}`)
|
|
514
|
+
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}`)
|
|
515
|
+
log('')
|
|
516
|
+
if (agentsOnly) process.exit(1)
|
|
223
517
|
return
|
|
224
518
|
}
|
|
225
519
|
|
|
@@ -230,11 +524,15 @@ export async function update(args = []) {
|
|
|
230
524
|
return
|
|
231
525
|
}
|
|
232
526
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
527
|
+
if (dryRun) {
|
|
528
|
+
info(`Dry-run: would ${currentAgentsVersion ? `update AGENTS.md (v${currentAgentsVersion} → v${cliVersion})` : `create AGENTS.md (v${cliVersion})`}.`)
|
|
529
|
+
return
|
|
530
|
+
}
|
|
531
|
+
|
|
236
532
|
if (!skipPrompt && !agentsOnly) {
|
|
237
|
-
const action = currentAgentsVersion
|
|
533
|
+
const action = currentAgentsVersion
|
|
534
|
+
? `Update AGENTS.md (v${currentAgentsVersion} → v${cliVersion})?`
|
|
535
|
+
: `Create AGENTS.md (v${cliVersion})?`
|
|
238
536
|
const { yes } = await prompts({ type: 'confirm', name: 'yes', message: action, initial: true })
|
|
239
537
|
if (!yes) {
|
|
240
538
|
log(`${colors.dim}Skipped AGENTS.md.${colors.reset}`)
|
package/src/framework-index.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-05-
|
|
3
|
+
"generatedAt": "2026-05-06T17:01:18.376Z",
|
|
4
4
|
"packages": {
|
|
5
5
|
"@uniweb/build": {
|
|
6
|
-
"version": "0.14.
|
|
6
|
+
"version": "0.14.3",
|
|
7
7
|
"path": "framework/build",
|
|
8
8
|
"deps": [
|
|
9
9
|
"@uniweb/content-reader",
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"deps": []
|
|
93
93
|
},
|
|
94
94
|
"@uniweb/unipress": {
|
|
95
|
-
"version": "0.4.
|
|
95
|
+
"version": "0.4.8",
|
|
96
96
|
"path": "framework/unipress",
|
|
97
97
|
"deps": [
|
|
98
98
|
"@uniweb/build",
|
package/src/index.js
CHANGED
|
@@ -1185,28 +1185,39 @@ Prints the parsed content shape of a markdown file or folder — the
|
|
|
1185
1185
|
Useful for debugging "why isn't my section getting X?".
|
|
1186
1186
|
`,
|
|
1187
1187
|
update: `
|
|
1188
|
-
${colors.cyan}${colors.bright}uniweb update${colors.reset} ${colors.dim}—
|
|
1188
|
+
${colors.cyan}${colors.bright}uniweb update${colors.reset} ${colors.dim}— Reconcile workspace state with the running CLI${colors.reset}
|
|
1189
1189
|
|
|
1190
1190
|
${colors.bright}Usage:${colors.reset}
|
|
1191
|
-
uniweb update
|
|
1192
|
-
uniweb update --
|
|
1193
|
-
uniweb update --
|
|
1194
|
-
uniweb update --
|
|
1191
|
+
uniweb update Self-update + align deps + refresh AGENTS.md
|
|
1192
|
+
uniweb update --deps-only Only align workspace @uniweb/* deps
|
|
1193
|
+
uniweb update --agents-only Only refresh AGENTS.md
|
|
1194
|
+
uniweb update --no-deps Skip the deps-alignment step
|
|
1195
|
+
uniweb update --no-agents Skip the AGENTS.md step
|
|
1196
|
+
uniweb update --dry-run Print survey + would-be writes; no mutations
|
|
1197
|
+
uniweb update --allow-mismatch Refresh AGENTS.md even if declared deps lag
|
|
1198
|
+
uniweb update --yes Skip confirmation prompts
|
|
1195
1199
|
|
|
1196
1200
|
${colors.bright}What it does:${colors.reset}
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1201
|
+
Prints a version survey first (CLI version, AGENTS.md stamp, every
|
|
1202
|
+
@uniweb/* + uniweb dep declared in workspace package.json files,
|
|
1203
|
+
marked aligned / behind / ahead). Then three steps:
|
|
1204
|
+
|
|
1205
|
+
1. ${colors.bright}Self-update${colors.reset} the global install via npm / pnpm / yarn
|
|
1206
|
+
(auto-detected). TTY prompts; non-interactive prints the command.
|
|
1207
|
+
2. ${colors.bright}Align workspace deps${colors.reset} to the CLI's bundled version matrix —
|
|
1208
|
+
edits every workspace package.json (only @uniweb/* + uniweb keys),
|
|
1209
|
+
then offers to run the workspace's package manager (lockfile-detected:
|
|
1210
|
+
pnpm-lock.yaml → pnpm, yarn.lock → yarn, package-lock.json → npm).
|
|
1211
|
+
If the install fails, package.json edits are kept and a revert
|
|
1212
|
+
command is printed.
|
|
1213
|
+
3. ${colors.bright}Refresh AGENTS.md${colors.reset} from the CLI's bundled partial. Refuses to
|
|
1214
|
+
run while declared deps still lag the CLI (would document features
|
|
1215
|
+
not in your installed packages); pass --allow-mismatch to override.
|
|
1204
1216
|
|
|
1205
1217
|
${colors.bright}Project-local installs:${colors.reset}
|
|
1206
1218
|
When the running CLI is project-local (lives in node_modules), self-
|
|
1207
1219
|
update is a no-op — the version is pinned by your project's
|
|
1208
|
-
package.json. The
|
|
1209
|
-
AGENTS.md refresh path only.
|
|
1220
|
+
package.json. The deps + AGENTS.md steps still run.
|
|
1210
1221
|
`,
|
|
1211
1222
|
}
|
|
1212
1223
|
|