uniweb 0.87.4 → 0.88.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +4 -4
- package/partials/agents.md +3 -1
- package/src/commands/add.js +2 -5
- package/src/commands/template.js +35 -46
- package/src/framework-index.json +23 -98
- package/src/index.js +50 -50
- package/src/templates/fetchers/release.js +16 -62
- package/src/templates/index.js +15 -32
- package/src/templates/resolver.js +46 -73
- package/src/templates/validator.js +15 -187
- package/src/utils/scaffold.js +3 -20
- package/src/utils/update-check.js +73 -38
- package/src/versions.js +4 -1
|
@@ -57,36 +57,22 @@ function writeState(state) {
|
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
59
|
* Print update notification to stderr (doesn't interfere with piped output).
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* `create`, where the user is about to scaffold files from CLI-bundled
|
|
63
|
-
* templates and a stale CLI means stale starter content).
|
|
60
|
+
* A trailing notice for a finished command; `create` and `clone` do not get
|
|
61
|
+
* one, they stop first (refuseOutdatedProjectStart).
|
|
64
62
|
*/
|
|
65
|
-
function printNotification(current, latest
|
|
63
|
+
function printNotification(current, latest) {
|
|
66
64
|
const yellow = '\x1b[33m'
|
|
67
65
|
const cyan = '\x1b[36m'
|
|
68
66
|
const dim = '\x1b[2m'
|
|
69
67
|
const reset = '\x1b[0m'
|
|
70
68
|
const updateCmd = globalCliUpdateCmd(detectGlobalCliPm())
|
|
71
69
|
console.error('')
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
)
|
|
79
|
-
console.error(
|
|
80
|
-
`${dim}Or run a one-shot fresh:${reset} npx uniweb@latest <command>`
|
|
81
|
-
)
|
|
82
|
-
} else {
|
|
83
|
-
console.error(
|
|
84
|
-
`${yellow}Update available:${reset} ${dim}${current}${reset} → ${cyan}${latest}${reset}`
|
|
85
|
-
)
|
|
86
|
-
console.error(
|
|
87
|
-
`${dim}Run${reset} ${updateCmd} ${dim}to update the CLI${reset}`
|
|
88
|
-
)
|
|
89
|
-
}
|
|
70
|
+
console.error(
|
|
71
|
+
`${yellow}Update available:${reset} ${dim}${current}${reset} → ${cyan}${latest}${reset}`
|
|
72
|
+
)
|
|
73
|
+
console.error(
|
|
74
|
+
`${dim}Run${reset} ${updateCmd} ${dim}to update the CLI${reset}`
|
|
75
|
+
)
|
|
90
76
|
}
|
|
91
77
|
|
|
92
78
|
/**
|
|
@@ -94,28 +80,78 @@ function printNotification(current, latest, tone = 'soft') {
|
|
|
94
80
|
* version is known. No network fetch — only reads what `startUpdateCheck`
|
|
95
81
|
* has previously cached. Returns true if a notification was printed.
|
|
96
82
|
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* - `--version` / `-v` (tone='soft'): brief trailing notice — the user
|
|
101
|
-
* was already asking about version, mention staleness while we're
|
|
102
|
-
* here. Goes to stderr so scripts capturing stdout aren't affected.
|
|
83
|
+
* For `--version` / `-v` off a terminal: the user was already asking about
|
|
84
|
+
* the version, so staleness is mentioned while we're here. Goes to stderr so
|
|
85
|
+
* scripts capturing stdout aren't affected.
|
|
103
86
|
*
|
|
104
87
|
* @param {string} currentVersion
|
|
105
|
-
* @param {'eager'|'soft'} [tone='eager']
|
|
106
88
|
* @returns {boolean} true if a notification was printed
|
|
107
89
|
*/
|
|
108
|
-
export function maybeNotifyFromCache(currentVersion
|
|
90
|
+
export function maybeNotifyFromCache(currentVersion) {
|
|
109
91
|
const state = readState()
|
|
110
92
|
if (!state.latestVersion) return false
|
|
111
93
|
if (compareSemver(state.latestVersion, currentVersion) <= 0) return false
|
|
112
|
-
printNotification(currentVersion, state.latestVersion
|
|
94
|
+
printNotification(currentVersion, state.latestVersion)
|
|
113
95
|
return true
|
|
114
96
|
}
|
|
115
97
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Whether a release newer than `current` is published. Unknown (`latest` null:
|
|
100
|
+
* offline, or a slow registry) is not behind.
|
|
101
|
+
*
|
|
102
|
+
* @param {string} current
|
|
103
|
+
* @param {string|null} latest
|
|
104
|
+
* @returns {boolean}
|
|
105
|
+
*/
|
|
106
|
+
export function isBehind(current, latest) {
|
|
107
|
+
return Boolean(latest) && compareSemver(latest, current) > 0
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Arguments as a shell would need them typed again. */
|
|
111
|
+
export function shellJoin(args) {
|
|
112
|
+
return args
|
|
113
|
+
.map((a) => (/^[\w@%+=:,./-]+$/.test(a) ? a : `'${String(a).replace(/'/g, `'\\''`)}'`))
|
|
114
|
+
.join(' ')
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Stop `create` or `clone` when a global CLI has been replaced by a newer
|
|
119
|
+
* release — before anything is written.
|
|
120
|
+
*
|
|
121
|
+
* ⭐ A PROJECT STARTS ON THE CLI THAT CREATES IT: its version table pins the
|
|
122
|
+
* `@uniweb/*` packages, and its official templates come from the release it was
|
|
123
|
+
* published with. Inside a project a global install only launches the project's
|
|
124
|
+
* own CLI; outside one, for `create` and `clone`, it does the work itself — so
|
|
125
|
+
* an outdated one starts the project outdated, and says nothing. `npx uniweb`
|
|
126
|
+
* with no version runs a global install too, which is how a 0.12 CLI went on
|
|
127
|
+
* offering the ten templates it knew while sixteen shipped (2026-10-08).
|
|
128
|
+
*
|
|
129
|
+
* Any newer release stops it, not only a newer minor: the version table moves
|
|
130
|
+
* whenever any package does — a patch of the CLI can pin a new breaking line of
|
|
131
|
+
* one — and the fix is one command either way. Unknown proceeds: this stops a
|
|
132
|
+
* known mistake, it does not require the network.
|
|
133
|
+
*
|
|
134
|
+
* @param {string} current - this CLI's version
|
|
135
|
+
* @param {string[]} argv - the command and its arguments, repeated in the fix
|
|
136
|
+
* @returns {Promise<boolean>} true when it refused, the reason printed
|
|
137
|
+
*/
|
|
138
|
+
export async function refuseOutdatedProjectStart(current, argv) {
|
|
139
|
+
const latest = await getLatestVersion({ maxAgeMs: 60 * 60 * 1000 })
|
|
140
|
+
if (!isBehind(current, latest)) return false
|
|
141
|
+
const red = '\x1b[31m'
|
|
142
|
+
const cyan = '\x1b[36m'
|
|
143
|
+
const dim = '\x1b[2m'
|
|
144
|
+
const reset = '\x1b[0m'
|
|
145
|
+
console.error(`${red}✗${reset} This uniweb is ${current}; the latest release is ${latest}.`)
|
|
146
|
+
console.error(
|
|
147
|
+
` ${dim}A project starts on the packages and templates of the CLI that creates it.${reset}`
|
|
148
|
+
)
|
|
149
|
+
console.error(` Run the latest: ${cyan}npx uniweb@latest ${shellJoin(argv)}${reset}`)
|
|
150
|
+
console.error(
|
|
151
|
+
` Or update this one: ${cyan}${globalCliUpdateCmd(detectGlobalCliPm())}${reset}`
|
|
152
|
+
)
|
|
153
|
+
return true
|
|
154
|
+
}
|
|
119
155
|
|
|
120
156
|
/**
|
|
121
157
|
* Resolve the latest published CLI version WITHOUT printing anything.
|
|
@@ -188,12 +224,11 @@ export async function getLatestVersion({
|
|
|
188
224
|
* @param {object} [opts]
|
|
189
225
|
* @param {number} [opts.timeoutMs=1500] Network timeout. Slow / offline
|
|
190
226
|
* calls return silently — never block the verb for long.
|
|
191
|
-
* @param {'eager'|'soft'} [opts.tone='soft'] Notification copy.
|
|
192
227
|
* @returns {Promise<boolean>} true if a notice was printed.
|
|
193
228
|
*/
|
|
194
229
|
export async function fetchAndNotifyIfNewer(
|
|
195
230
|
currentVersion,
|
|
196
|
-
{ timeoutMs = 1500
|
|
231
|
+
{ timeoutMs = 1500 } = {}
|
|
197
232
|
) {
|
|
198
233
|
const controller = new AbortController()
|
|
199
234
|
const timer = setTimeout(() => controller.abort(), timeoutMs)
|
|
@@ -216,7 +251,7 @@ export async function fetchAndNotifyIfNewer(
|
|
|
216
251
|
// Refresh the cache so other code paths see this fresh result.
|
|
217
252
|
writeState({ lastCheck: Date.now(), latestVersion: latest })
|
|
218
253
|
if (compareSemver(latest, currentVersion) <= 0) return false
|
|
219
|
-
printNotification(currentVersion, latest
|
|
254
|
+
printNotification(currentVersion, latest)
|
|
220
255
|
return true
|
|
221
256
|
}
|
|
222
257
|
|
package/src/versions.js
CHANGED
|
@@ -240,8 +240,11 @@ function readWorkspaceVersion(packageName) {
|
|
|
240
240
|
* Returns null if the file doesn't exist, is unparseable, or has a
|
|
241
241
|
* schema version we don't understand. Callers treat null as "no
|
|
242
242
|
* snapshot available" and fall back to their next source.
|
|
243
|
+
*
|
|
244
|
+
* Also the official templates' one source (`templates/resolver.js`): their
|
|
245
|
+
* roster, and the `@uniweb/templates` release their content downloads from.
|
|
243
246
|
*/
|
|
244
|
-
function loadFrameworkIndex() {
|
|
247
|
+
export function loadFrameworkIndex() {
|
|
245
248
|
const indexPath = join(__dirname, 'framework-index.json')
|
|
246
249
|
try {
|
|
247
250
|
const raw = readFileSync(indexPath, 'utf8')
|