update-versions 7.2.3 → 7.2.5
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/CHANGELOG.md +7 -0
- package/README.md +1 -1
- package/cli-update-notifier.js +793 -0
- package/cli.js +50 -87
- package/dependency-spec.js +32 -0
- package/package.json +11 -10
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## 7.2.5 (2026-09-06)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- **update-versions:** remove p-reduce from package inventory ([22fbf9a](https://github.com/codsen/codsen/commit/22fbf9afe447a3b33c8c6aaee47a28769b7f5f6c))
|
|
11
|
+
- **update-versions:** replace external update notifier ([aee67ab](https://github.com/codsen/codsen/commit/aee67abd0b123e73a89003af55a4ffea04f46881))
|
|
12
|
+
|
|
6
13
|
## 7.2.0 (2026-08-19)
|
|
7
14
|
|
|
8
15
|
### Bug Fixes
|
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ upd
|
|
|
35
35
|
|
|
36
36
|
## Documentation
|
|
37
37
|
|
|
38
|
-
Please [visit codsen.com](https://codsen.com/os/update-versions/) for a full description of the API.
|
|
38
|
+
Please [visit codsen.com](https://codsen.com/os/update-versions/) for a full description of the API. For **Changelog**, see [raw md on GitHub](https://github.com/codsen/codsen/blob/main/packages/update-versions/CHANGELOG.md) or [the website](https://codsen.com/os/update-versions/changelog).
|
|
39
39
|
|
|
40
40
|
## Contributing
|
|
41
41
|
|
|
@@ -0,0 +1,793 @@
|
|
|
1
|
+
// This is the canonical source. `lect` copies it verbatim into every CLI.
|
|
2
|
+
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import {
|
|
5
|
+
mkdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
renameSync,
|
|
8
|
+
statSync,
|
|
9
|
+
unlinkSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
} from "node:fs";
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
|
|
16
|
+
const CHECK_INTERVAL = 24 * 60 * 60 * 1000;
|
|
17
|
+
const FAILURE_BACKOFF = 60 * 60 * 1000;
|
|
18
|
+
const FETCH_TIMEOUT = 10 * 1000;
|
|
19
|
+
const LOCK_STALE_AFTER = FETCH_TIMEOUT + 20 * 1000;
|
|
20
|
+
const MAX_JSON_BYTES = 64 * 1024;
|
|
21
|
+
const MODULE_FILENAME = fileURLToPath(import.meta.url);
|
|
22
|
+
const NOTIFICATION_LEASE = 60 * 60 * 1000;
|
|
23
|
+
const STATE_SCHEMA_VERSION = 1;
|
|
24
|
+
const WORKER_FLAG = "--codsen-update-check-worker";
|
|
25
|
+
|
|
26
|
+
const SEMVER_PATTERN =
|
|
27
|
+
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
28
|
+
const PACKAGE_NAME_PART = /^[a-z0-9][a-z0-9._~-]*$/;
|
|
29
|
+
|
|
30
|
+
function parseSemver(version) {
|
|
31
|
+
if (typeof version !== "string") {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const match = SEMVER_PATTERN.exec(version);
|
|
35
|
+
if (!match) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const prerelease = match[4] ? match[4].split(".") : [];
|
|
39
|
+
if (
|
|
40
|
+
prerelease.some(
|
|
41
|
+
(identifier) => /^\d+$/.test(identifier) && /^0\d+/.test(identifier),
|
|
42
|
+
)
|
|
43
|
+
) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
core: [match[1], match[2], match[3]],
|
|
48
|
+
prerelease,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function compareNumericStrings(left, right) {
|
|
53
|
+
if (left.length !== right.length) {
|
|
54
|
+
return left.length < right.length ? -1 : 1;
|
|
55
|
+
}
|
|
56
|
+
if (left === right) {
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
return left < right ? -1 : 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function compareSemver(leftVersion, rightVersion) {
|
|
63
|
+
const left = parseSemver(leftVersion);
|
|
64
|
+
const right = parseSemver(rightVersion);
|
|
65
|
+
if (!left || !right) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
for (let index = 0; index < left.core.length; index += 1) {
|
|
69
|
+
const comparison = compareNumericStrings(
|
|
70
|
+
left.core[index],
|
|
71
|
+
right.core[index],
|
|
72
|
+
);
|
|
73
|
+
if (comparison) {
|
|
74
|
+
return comparison;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (!left.prerelease.length || !right.prerelease.length) {
|
|
78
|
+
if (left.prerelease.length === right.prerelease.length) {
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
return left.prerelease.length ? -1 : 1;
|
|
82
|
+
}
|
|
83
|
+
const length = Math.max(left.prerelease.length, right.prerelease.length);
|
|
84
|
+
for (let index = 0; index < length; index += 1) {
|
|
85
|
+
const leftIdentifier = left.prerelease[index];
|
|
86
|
+
const rightIdentifier = right.prerelease[index];
|
|
87
|
+
if (leftIdentifier === undefined || rightIdentifier === undefined) {
|
|
88
|
+
return leftIdentifier === undefined ? -1 : 1;
|
|
89
|
+
}
|
|
90
|
+
if (leftIdentifier === rightIdentifier) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const leftIsNumeric = /^\d+$/.test(leftIdentifier);
|
|
94
|
+
const rightIsNumeric = /^\d+$/.test(rightIdentifier);
|
|
95
|
+
if (leftIsNumeric && rightIsNumeric) {
|
|
96
|
+
return compareNumericStrings(leftIdentifier, rightIdentifier);
|
|
97
|
+
}
|
|
98
|
+
if (leftIsNumeric !== rightIsNumeric) {
|
|
99
|
+
return leftIsNumeric ? -1 : 1;
|
|
100
|
+
}
|
|
101
|
+
return leftIdentifier < rightIdentifier ? -1 : 1;
|
|
102
|
+
}
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isValidPackageName(packageName) {
|
|
107
|
+
if (
|
|
108
|
+
typeof packageName !== "string" ||
|
|
109
|
+
!packageName ||
|
|
110
|
+
packageName.length > 214
|
|
111
|
+
) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
if (!packageName.startsWith("@")) {
|
|
115
|
+
return PACKAGE_NAME_PART.test(packageName);
|
|
116
|
+
}
|
|
117
|
+
const parts = packageName.slice(1).split("/");
|
|
118
|
+
return (
|
|
119
|
+
parts.length === 2 && parts.every((part) => PACKAGE_NAME_PART.test(part))
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function validTimestamp(value) {
|
|
124
|
+
return Number.isSafeInteger(value) && value >= 0 ? value : 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function initialState(now, legacy = {}) {
|
|
128
|
+
const legacyCheck = validTimestamp(legacy.lastUpdateCheck);
|
|
129
|
+
return {
|
|
130
|
+
schemaVersion: STATE_SCHEMA_VERSION,
|
|
131
|
+
createdAt: legacyCheck || now,
|
|
132
|
+
lastAttempt: 0,
|
|
133
|
+
lastSuccess: 0,
|
|
134
|
+
latestVersion: parseSemver(legacy.latestVersion)
|
|
135
|
+
? legacy.latestVersion
|
|
136
|
+
: null,
|
|
137
|
+
lastNotification: null,
|
|
138
|
+
pendingNotification: null,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function normalizeState(value, now) {
|
|
143
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
const latestVersion = parseSemver(value.latestVersion)
|
|
147
|
+
? value.latestVersion
|
|
148
|
+
: null;
|
|
149
|
+
const notification = value.lastNotification;
|
|
150
|
+
const lastNotification =
|
|
151
|
+
notification &&
|
|
152
|
+
typeof notification === "object" &&
|
|
153
|
+
parseSemver(notification.current) &&
|
|
154
|
+
parseSemver(notification.latest)
|
|
155
|
+
? {
|
|
156
|
+
current: notification.current,
|
|
157
|
+
latest: notification.latest,
|
|
158
|
+
at: validTimestamp(notification.at),
|
|
159
|
+
}
|
|
160
|
+
: null;
|
|
161
|
+
const pending = value.pendingNotification;
|
|
162
|
+
const pendingNotification =
|
|
163
|
+
pending &&
|
|
164
|
+
typeof pending === "object" &&
|
|
165
|
+
parseSemver(pending.current) &&
|
|
166
|
+
parseSemver(pending.latest) &&
|
|
167
|
+
typeof pending.token === "string" &&
|
|
168
|
+
pending.token.length <= 256
|
|
169
|
+
? {
|
|
170
|
+
current: pending.current,
|
|
171
|
+
latest: pending.latest,
|
|
172
|
+
token: pending.token,
|
|
173
|
+
at: validTimestamp(pending.at),
|
|
174
|
+
}
|
|
175
|
+
: null;
|
|
176
|
+
return {
|
|
177
|
+
schemaVersion: STATE_SCHEMA_VERSION,
|
|
178
|
+
createdAt: validTimestamp(value.createdAt) || now,
|
|
179
|
+
lastAttempt: validTimestamp(value.lastAttempt),
|
|
180
|
+
lastSuccess: validTimestamp(value.lastSuccess),
|
|
181
|
+
latestVersion,
|
|
182
|
+
lastNotification,
|
|
183
|
+
pendingNotification,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function readSmallJson(filename) {
|
|
188
|
+
if (!filename) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
const stats = statSync(filename);
|
|
193
|
+
if (!stats.isFile() || stats.size > MAX_JSON_BYTES) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
return JSON.parse(readFileSync(filename, "utf8"));
|
|
197
|
+
} catch {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function readState(filename, now) {
|
|
203
|
+
return normalizeState(readSmallJson(filename), now);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function writeState(filename, state) {
|
|
207
|
+
let temporaryFilename;
|
|
208
|
+
try {
|
|
209
|
+
mkdirSync(path.dirname(filename), { mode: 0o700, recursive: true });
|
|
210
|
+
temporaryFilename = `${filename}.${process.pid}.${Date.now().toString(36)}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
211
|
+
writeFileSync(temporaryFilename, `${JSON.stringify(state, null, 2)}\n`, {
|
|
212
|
+
encoding: "utf8",
|
|
213
|
+
flag: "wx",
|
|
214
|
+
mode: 0o600,
|
|
215
|
+
});
|
|
216
|
+
renameSync(temporaryFilename, filename);
|
|
217
|
+
return true;
|
|
218
|
+
} catch {
|
|
219
|
+
if (temporaryFilename) {
|
|
220
|
+
try {
|
|
221
|
+
unlinkSync(temporaryFilename);
|
|
222
|
+
} catch {}
|
|
223
|
+
}
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function acquireLock(lockFile, now, staleAfter = LOCK_STALE_AFTER) {
|
|
229
|
+
try {
|
|
230
|
+
mkdirSync(path.dirname(lockFile), { mode: 0o700, recursive: true });
|
|
231
|
+
} catch {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
const token = `${process.pid}:${Date.now()}:${Math.random()}`;
|
|
235
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
236
|
+
try {
|
|
237
|
+
writeFileSync(lockFile, token, {
|
|
238
|
+
encoding: "utf8",
|
|
239
|
+
flag: "wx",
|
|
240
|
+
mode: 0o600,
|
|
241
|
+
});
|
|
242
|
+
return token;
|
|
243
|
+
} catch (error) {
|
|
244
|
+
if (error?.code !== "EEXIST") {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
try {
|
|
248
|
+
const observedToken = readFileSync(lockFile, "utf8");
|
|
249
|
+
const observedStats = statSync(lockFile);
|
|
250
|
+
const age = now - observedStats.mtimeMs;
|
|
251
|
+
if (!Number.isFinite(age) || age <= staleAfter) {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
const currentStats = statSync(lockFile);
|
|
255
|
+
if (
|
|
256
|
+
readFileSync(lockFile, "utf8") !== observedToken ||
|
|
257
|
+
currentStats.dev !== observedStats.dev ||
|
|
258
|
+
currentStats.ino !== observedStats.ino ||
|
|
259
|
+
currentStats.mtimeMs !== observedStats.mtimeMs
|
|
260
|
+
) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
if (readFileSync(lockFile, "utf8") !== observedToken) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
unlinkSync(lockFile);
|
|
267
|
+
} catch {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function releaseLock(lockFile, token) {
|
|
276
|
+
try {
|
|
277
|
+
if (readFileSync(lockFile, "utf8") === token) {
|
|
278
|
+
unlinkSync(lockFile);
|
|
279
|
+
}
|
|
280
|
+
} catch {}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function updateStateWithLock(
|
|
284
|
+
paths,
|
|
285
|
+
now,
|
|
286
|
+
update,
|
|
287
|
+
staleAfter = LOCK_STALE_AFTER,
|
|
288
|
+
) {
|
|
289
|
+
const lockToken = acquireLock(paths.lockFile, now, staleAfter);
|
|
290
|
+
if (!lockToken) {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
try {
|
|
294
|
+
const existingState = readState(paths.cacheFile, now);
|
|
295
|
+
const nextState = update(existingState || initialState(now), {
|
|
296
|
+
exists: Boolean(existingState),
|
|
297
|
+
});
|
|
298
|
+
return nextState && writeState(paths.cacheFile, nextState)
|
|
299
|
+
? nextState
|
|
300
|
+
: null;
|
|
301
|
+
} finally {
|
|
302
|
+
releaseLock(paths.lockFile, lockToken);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function resolveCacheRoot(env, home, platform) {
|
|
307
|
+
if (
|
|
308
|
+
typeof env.XDG_CACHE_HOME === "string" &&
|
|
309
|
+
path.isAbsolute(env.XDG_CACHE_HOME)
|
|
310
|
+
) {
|
|
311
|
+
return env.XDG_CACHE_HOME;
|
|
312
|
+
}
|
|
313
|
+
if (platform === "win32") {
|
|
314
|
+
if (
|
|
315
|
+
typeof env.LOCALAPPDATA === "string" &&
|
|
316
|
+
path.isAbsolute(env.LOCALAPPDATA)
|
|
317
|
+
) {
|
|
318
|
+
return env.LOCALAPPDATA;
|
|
319
|
+
}
|
|
320
|
+
return home && path.isAbsolute(home)
|
|
321
|
+
? path.join(home, "AppData", "Local")
|
|
322
|
+
: null;
|
|
323
|
+
}
|
|
324
|
+
if (!home || !path.isAbsolute(home)) {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
return platform === "darwin"
|
|
328
|
+
? path.join(home, "Library", "Caches")
|
|
329
|
+
: path.join(home, ".cache");
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function resolvePaths(packageName, runtime = {}) {
|
|
333
|
+
const env = runtime.env ?? process.env;
|
|
334
|
+
const home = runtime.home ?? homedir();
|
|
335
|
+
const platform = runtime.platform ?? process.platform;
|
|
336
|
+
const cacheRoot = runtime.cacheRoot ?? resolveCacheRoot(env, home, platform);
|
|
337
|
+
if (!cacheRoot || !path.isAbsolute(cacheRoot)) {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
const encodedName = Buffer.from(packageName).toString("base64url");
|
|
341
|
+
const directory = path.join(cacheRoot, "codsen", "update-notifier");
|
|
342
|
+
const cacheFile =
|
|
343
|
+
runtime.cacheFile ?? path.join(directory, `${encodedName}.json`);
|
|
344
|
+
const lockFile = runtime.lockFile ?? `${cacheFile}.lock`;
|
|
345
|
+
const configRoot =
|
|
346
|
+
env.XDG_CONFIG_HOME || (home && path.join(home, ".config"));
|
|
347
|
+
const legacyConfigFile =
|
|
348
|
+
"legacyConfigFile" in runtime
|
|
349
|
+
? runtime.legacyConfigFile
|
|
350
|
+
: configRoot &&
|
|
351
|
+
path.join(
|
|
352
|
+
configRoot,
|
|
353
|
+
"configstore",
|
|
354
|
+
`update-notifier-${packageName}.json`,
|
|
355
|
+
);
|
|
356
|
+
return { cacheFile, legacyConfigFile, lockFile };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function readLegacyConfig(filename) {
|
|
360
|
+
const value = readSmallJson(filename);
|
|
361
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
362
|
+
return {};
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
optOut: Boolean(value.optOut),
|
|
366
|
+
lastUpdateCheck: validTimestamp(value.lastUpdateCheck),
|
|
367
|
+
latestVersion: value.update?.latest,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function isCi(env) {
|
|
372
|
+
return (
|
|
373
|
+
env.CI !== "0" &&
|
|
374
|
+
env.CI !== "false" &&
|
|
375
|
+
("CI" in env ||
|
|
376
|
+
"CONTINUOUS_INTEGRATION" in env ||
|
|
377
|
+
Object.keys(env).some((key) => key.startsWith("CI_")))
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function isPackageManagerInvocation(env) {
|
|
382
|
+
const userAgent = env.npm_config_user_agent;
|
|
383
|
+
return (
|
|
384
|
+
(typeof env.npm_package_json === "string" &&
|
|
385
|
+
env.npm_package_json.endsWith("package.json")) ||
|
|
386
|
+
(typeof userAgent === "string" &&
|
|
387
|
+
["npm", "yarn", "pnpm", "bun"].some((name) => userAgent.startsWith(name)))
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function isDisabled({
|
|
392
|
+
argv,
|
|
393
|
+
enabled,
|
|
394
|
+
env,
|
|
395
|
+
packageName,
|
|
396
|
+
packageVersion,
|
|
397
|
+
stderr,
|
|
398
|
+
}) {
|
|
399
|
+
return (
|
|
400
|
+
enabled === false ||
|
|
401
|
+
!isValidPackageName(packageName) ||
|
|
402
|
+
!parseSemver(packageVersion) ||
|
|
403
|
+
stderr?.isTTY !== true ||
|
|
404
|
+
"NO_UPDATE_NOTIFIER" in env ||
|
|
405
|
+
env.NODE_ENV === "test" ||
|
|
406
|
+
argv.includes("--no-update-notifier") ||
|
|
407
|
+
isCi(env) ||
|
|
408
|
+
isPackageManagerInvocation(env)
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function isCheckDue(state, now, options = {}) {
|
|
413
|
+
const checkInterval = options.checkInterval ?? CHECK_INTERVAL;
|
|
414
|
+
const failureBackoff = options.failureBackoff ?? FAILURE_BACKOFF;
|
|
415
|
+
const successOrCreation = state.lastSuccess || state.createdAt;
|
|
416
|
+
return (
|
|
417
|
+
now - successOrCreation >= checkInterval &&
|
|
418
|
+
(!state.lastAttempt || now - state.lastAttempt >= failureBackoff)
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function isNotificationDue(state, currentVersion) {
|
|
423
|
+
const latestVersion = state.latestVersion;
|
|
424
|
+
if (compareSemver(latestVersion, currentVersion) !== 1) {
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
return !(
|
|
428
|
+
state.lastNotification?.current === currentVersion &&
|
|
429
|
+
state.lastNotification?.latest === latestVersion
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function formatCliUpdateNotification({
|
|
434
|
+
currentVersion,
|
|
435
|
+
latestVersion,
|
|
436
|
+
packageName,
|
|
437
|
+
}) {
|
|
438
|
+
return `\nUpdate available for ${packageName}: ${currentVersion} → ${latestVersion}\nhttps://www.npmjs.com/package/${encodeURIComponent(packageName)}\n\n`;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function hasActiveNotificationReservation(
|
|
442
|
+
state,
|
|
443
|
+
currentVersion,
|
|
444
|
+
latestVersion,
|
|
445
|
+
now,
|
|
446
|
+
lease,
|
|
447
|
+
) {
|
|
448
|
+
const pending = state.pendingNotification;
|
|
449
|
+
return (
|
|
450
|
+
pending?.current === currentVersion &&
|
|
451
|
+
pending.latest === latestVersion &&
|
|
452
|
+
now - pending.at < lease
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function registerNotification({
|
|
457
|
+
currentVersion,
|
|
458
|
+
now,
|
|
459
|
+
onExit,
|
|
460
|
+
packageName,
|
|
461
|
+
paths,
|
|
462
|
+
reservationToken,
|
|
463
|
+
staleAfter,
|
|
464
|
+
stderr,
|
|
465
|
+
}) {
|
|
466
|
+
const finish = (code) => {
|
|
467
|
+
const notificationTime = now();
|
|
468
|
+
updateStateWithLock(
|
|
469
|
+
paths,
|
|
470
|
+
notificationTime,
|
|
471
|
+
(state) => {
|
|
472
|
+
if (state.pendingNotification?.token !== reservationToken) {
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
if (code !== 0 || stderr?.isTTY !== true) {
|
|
476
|
+
return { ...state, pendingNotification: null };
|
|
477
|
+
}
|
|
478
|
+
const notificationLatest = state.latestVersion;
|
|
479
|
+
if (compareSemver(notificationLatest, currentVersion) !== 1) {
|
|
480
|
+
return { ...state, pendingNotification: null };
|
|
481
|
+
}
|
|
482
|
+
try {
|
|
483
|
+
stderr.write(
|
|
484
|
+
formatCliUpdateNotification({
|
|
485
|
+
currentVersion,
|
|
486
|
+
latestVersion: notificationLatest,
|
|
487
|
+
packageName,
|
|
488
|
+
}),
|
|
489
|
+
);
|
|
490
|
+
} catch {
|
|
491
|
+
return { ...state, pendingNotification: null };
|
|
492
|
+
}
|
|
493
|
+
return {
|
|
494
|
+
...state,
|
|
495
|
+
lastNotification: {
|
|
496
|
+
current: currentVersion,
|
|
497
|
+
latest: notificationLatest,
|
|
498
|
+
at: notificationTime,
|
|
499
|
+
},
|
|
500
|
+
pendingNotification: null,
|
|
501
|
+
};
|
|
502
|
+
},
|
|
503
|
+
staleAfter,
|
|
504
|
+
);
|
|
505
|
+
};
|
|
506
|
+
try {
|
|
507
|
+
onExit(finish);
|
|
508
|
+
return true;
|
|
509
|
+
} catch {
|
|
510
|
+
finish(1);
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function spawnWorker(packageName, attemptToken, spawnImpl = spawn) {
|
|
516
|
+
try {
|
|
517
|
+
const child = spawnImpl(
|
|
518
|
+
process.execPath,
|
|
519
|
+
[MODULE_FILENAME, WORKER_FLAG, packageName, String(attemptToken)],
|
|
520
|
+
{
|
|
521
|
+
detached: true,
|
|
522
|
+
stdio: "ignore",
|
|
523
|
+
windowsHide: true,
|
|
524
|
+
},
|
|
525
|
+
);
|
|
526
|
+
child.once?.("error", () => {});
|
|
527
|
+
child.unref?.();
|
|
528
|
+
return true;
|
|
529
|
+
} catch {
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function notifyOfCliUpdate({ enabled = true, pkg } = {}, runtime = {}) {
|
|
535
|
+
try {
|
|
536
|
+
const argv = runtime.argv ?? process.argv.slice(2);
|
|
537
|
+
const env = runtime.env ?? process.env;
|
|
538
|
+
const stderr = runtime.stderr ?? process.stderr;
|
|
539
|
+
const packageName = pkg?.name;
|
|
540
|
+
const packageVersion = pkg?.version;
|
|
541
|
+
if (
|
|
542
|
+
isDisabled({
|
|
543
|
+
argv,
|
|
544
|
+
enabled,
|
|
545
|
+
env,
|
|
546
|
+
packageName,
|
|
547
|
+
packageVersion,
|
|
548
|
+
stderr,
|
|
549
|
+
})
|
|
550
|
+
) {
|
|
551
|
+
return false;
|
|
552
|
+
}
|
|
553
|
+
const paths = resolvePaths(packageName, runtime);
|
|
554
|
+
if (!paths) {
|
|
555
|
+
return false;
|
|
556
|
+
}
|
|
557
|
+
const legacy = readLegacyConfig(paths.legacyConfigFile);
|
|
558
|
+
if (legacy.optOut) {
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
const now = runtime.now ?? Date.now;
|
|
562
|
+
const currentTime = now();
|
|
563
|
+
const staleAfter = runtime.lockStaleAfter ?? LOCK_STALE_AFTER;
|
|
564
|
+
let state = readState(paths.cacheFile, currentTime);
|
|
565
|
+
if (!state) {
|
|
566
|
+
state = updateStateWithLock(
|
|
567
|
+
paths,
|
|
568
|
+
currentTime,
|
|
569
|
+
(current, { exists }) =>
|
|
570
|
+
exists ? current : initialState(currentTime, legacy),
|
|
571
|
+
staleAfter,
|
|
572
|
+
);
|
|
573
|
+
if (!state) {
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
if (isNotificationDue(state, packageVersion)) {
|
|
578
|
+
const reservationToken = `${process.pid}:${currentTime}:${Math.random()}`;
|
|
579
|
+
const claimedState = updateStateWithLock(
|
|
580
|
+
paths,
|
|
581
|
+
currentTime,
|
|
582
|
+
(current) => {
|
|
583
|
+
const currentLatest = current.latestVersion;
|
|
584
|
+
return isNotificationDue(current, packageVersion) &&
|
|
585
|
+
!hasActiveNotificationReservation(
|
|
586
|
+
current,
|
|
587
|
+
packageVersion,
|
|
588
|
+
currentLatest,
|
|
589
|
+
currentTime,
|
|
590
|
+
runtime.notificationLease ?? NOTIFICATION_LEASE,
|
|
591
|
+
)
|
|
592
|
+
? {
|
|
593
|
+
...current,
|
|
594
|
+
pendingNotification: {
|
|
595
|
+
current: packageVersion,
|
|
596
|
+
latest: currentLatest,
|
|
597
|
+
token: reservationToken,
|
|
598
|
+
at: currentTime,
|
|
599
|
+
},
|
|
600
|
+
}
|
|
601
|
+
: null;
|
|
602
|
+
},
|
|
603
|
+
staleAfter,
|
|
604
|
+
);
|
|
605
|
+
if (!claimedState) {
|
|
606
|
+
return false;
|
|
607
|
+
}
|
|
608
|
+
return registerNotification({
|
|
609
|
+
currentVersion: packageVersion,
|
|
610
|
+
now,
|
|
611
|
+
onExit:
|
|
612
|
+
runtime.onExit ?? ((listener) => process.once("exit", listener)),
|
|
613
|
+
packageName,
|
|
614
|
+
paths,
|
|
615
|
+
reservationToken,
|
|
616
|
+
staleAfter,
|
|
617
|
+
stderr,
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
if (
|
|
621
|
+
!isCheckDue(state, currentTime, {
|
|
622
|
+
checkInterval: runtime.checkInterval,
|
|
623
|
+
failureBackoff: runtime.failureBackoff,
|
|
624
|
+
})
|
|
625
|
+
) {
|
|
626
|
+
return false;
|
|
627
|
+
}
|
|
628
|
+
const scheduledState = updateStateWithLock(
|
|
629
|
+
paths,
|
|
630
|
+
currentTime,
|
|
631
|
+
(current) =>
|
|
632
|
+
isCheckDue(current, currentTime, {
|
|
633
|
+
checkInterval: runtime.checkInterval,
|
|
634
|
+
failureBackoff: runtime.failureBackoff,
|
|
635
|
+
})
|
|
636
|
+
? { ...current, lastAttempt: currentTime }
|
|
637
|
+
: null,
|
|
638
|
+
staleAfter,
|
|
639
|
+
);
|
|
640
|
+
if (!scheduledState) {
|
|
641
|
+
return false;
|
|
642
|
+
}
|
|
643
|
+
const startWorker = runtime.spawnWorker ?? spawnWorker;
|
|
644
|
+
return Boolean(startWorker(packageName, currentTime, runtime.spawnImpl));
|
|
645
|
+
} catch {
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
async function readLimitedBody(response) {
|
|
651
|
+
const contentLength = Number(response.headers?.get?.("content-length"));
|
|
652
|
+
if (Number.isFinite(contentLength) && contentLength > MAX_JSON_BYTES) {
|
|
653
|
+
return null;
|
|
654
|
+
}
|
|
655
|
+
if (!response.body?.getReader) {
|
|
656
|
+
const text = await response.text();
|
|
657
|
+
return Buffer.byteLength(text) <= MAX_JSON_BYTES ? text : null;
|
|
658
|
+
}
|
|
659
|
+
const reader = response.body.getReader();
|
|
660
|
+
const chunks = [];
|
|
661
|
+
let total = 0;
|
|
662
|
+
try {
|
|
663
|
+
while (true) {
|
|
664
|
+
const { done, value } = await reader.read();
|
|
665
|
+
if (done) {
|
|
666
|
+
break;
|
|
667
|
+
}
|
|
668
|
+
const chunk = Buffer.from(value);
|
|
669
|
+
total += chunk.length;
|
|
670
|
+
if (total > MAX_JSON_BYTES) {
|
|
671
|
+
await reader.cancel();
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
chunks.push(chunk);
|
|
675
|
+
}
|
|
676
|
+
} finally {
|
|
677
|
+
reader.releaseLock();
|
|
678
|
+
}
|
|
679
|
+
return Buffer.concat(chunks, total).toString("utf8");
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
async function fetchLatestVersion(packageName, runtime = {}) {
|
|
683
|
+
const fetchImpl = runtime.fetchImpl ?? globalThis.fetch;
|
|
684
|
+
if (typeof fetchImpl !== "function") {
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
const timeoutMs = runtime.fetchTimeout ?? FETCH_TIMEOUT;
|
|
688
|
+
const controller = new AbortController();
|
|
689
|
+
let timeout;
|
|
690
|
+
const request = Promise.resolve()
|
|
691
|
+
.then(async () => {
|
|
692
|
+
const response = await fetchImpl(
|
|
693
|
+
`https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`,
|
|
694
|
+
{
|
|
695
|
+
headers: { accept: "application/json" },
|
|
696
|
+
redirect: "follow",
|
|
697
|
+
signal: controller.signal,
|
|
698
|
+
},
|
|
699
|
+
);
|
|
700
|
+
if (!response?.ok) {
|
|
701
|
+
return null;
|
|
702
|
+
}
|
|
703
|
+
const body = await readLimitedBody(response);
|
|
704
|
+
if (body === null) {
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
const version = JSON.parse(body).version;
|
|
708
|
+
return parseSemver(version) ? version : null;
|
|
709
|
+
})
|
|
710
|
+
.catch(() => null);
|
|
711
|
+
const timedOut = new Promise((resolve) => {
|
|
712
|
+
timeout = setTimeout(() => {
|
|
713
|
+
controller.abort();
|
|
714
|
+
resolve(null);
|
|
715
|
+
}, timeoutMs);
|
|
716
|
+
});
|
|
717
|
+
try {
|
|
718
|
+
return await Promise.race([request, timedOut]);
|
|
719
|
+
} finally {
|
|
720
|
+
clearTimeout(timeout);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
async function runUpdateCheck(packageName, runtime = {}) {
|
|
725
|
+
try {
|
|
726
|
+
if (!isValidPackageName(packageName)) {
|
|
727
|
+
return false;
|
|
728
|
+
}
|
|
729
|
+
const paths = resolvePaths(packageName, runtime);
|
|
730
|
+
if (!paths) {
|
|
731
|
+
return false;
|
|
732
|
+
}
|
|
733
|
+
const latestVersion = await fetchLatestVersion(packageName, runtime);
|
|
734
|
+
if (!latestVersion) {
|
|
735
|
+
return false;
|
|
736
|
+
}
|
|
737
|
+
const now = runtime.now ?? Date.now;
|
|
738
|
+
const currentTime = now();
|
|
739
|
+
return Boolean(
|
|
740
|
+
updateStateWithLock(
|
|
741
|
+
paths,
|
|
742
|
+
currentTime,
|
|
743
|
+
(state) =>
|
|
744
|
+
runtime.expectedAttempt !== undefined &&
|
|
745
|
+
state.lastAttempt !== runtime.expectedAttempt
|
|
746
|
+
? null
|
|
747
|
+
: {
|
|
748
|
+
...state,
|
|
749
|
+
lastSuccess: currentTime,
|
|
750
|
+
latestVersion,
|
|
751
|
+
},
|
|
752
|
+
runtime.lockStaleAfter,
|
|
753
|
+
),
|
|
754
|
+
);
|
|
755
|
+
} catch {
|
|
756
|
+
return false;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function isWorkerInvocation(argv = process.argv) {
|
|
761
|
+
return (
|
|
762
|
+
argv[1] &&
|
|
763
|
+
path.resolve(argv[1]) === path.resolve(MODULE_FILENAME) &&
|
|
764
|
+
argv[2] === WORKER_FLAG
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (isWorkerInvocation()) {
|
|
769
|
+
const attemptToken = Number(process.argv[4]);
|
|
770
|
+
await runUpdateCheck(process.argv[3], {
|
|
771
|
+
expectedAttempt: Number.isSafeInteger(attemptToken)
|
|
772
|
+
? attemptToken
|
|
773
|
+
: undefined,
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
export {
|
|
778
|
+
compareSemver,
|
|
779
|
+
fetchLatestVersion,
|
|
780
|
+
formatCliUpdateNotification,
|
|
781
|
+
isCheckDue,
|
|
782
|
+
isCi,
|
|
783
|
+
isDisabled,
|
|
784
|
+
isNotificationDue,
|
|
785
|
+
isPackageManagerInvocation,
|
|
786
|
+
notifyOfCliUpdate,
|
|
787
|
+
parseSemver,
|
|
788
|
+
readLegacyConfig,
|
|
789
|
+
readState,
|
|
790
|
+
resolveCacheRoot,
|
|
791
|
+
resolvePaths,
|
|
792
|
+
runUpdateCheck,
|
|
793
|
+
};
|
package/cli.js
CHANGED
|
@@ -13,9 +13,13 @@ import { codsenCLI, isPlainObject } from "codsen-utils";
|
|
|
13
13
|
import { del, set } from "edit-package-json";
|
|
14
14
|
import objectPath from "object-path";
|
|
15
15
|
import pProgress, { PProgress } from "p-progress";
|
|
16
|
-
import pReduce from "p-reduce";
|
|
17
16
|
import packageJson from "package-json";
|
|
18
|
-
import
|
|
17
|
+
import { notifyOfCliUpdate } from "./cli-update-notifier.js";
|
|
18
|
+
import {
|
|
19
|
+
major,
|
|
20
|
+
updatedDependencySpec,
|
|
21
|
+
workspaceSpecPrefix,
|
|
22
|
+
} from "./dependency-spec.js";
|
|
19
23
|
|
|
20
24
|
const require1 = createRequire(import.meta.url);
|
|
21
25
|
const pkg = require1("./package.json");
|
|
@@ -202,12 +206,6 @@ function parseDependencySpec(dependencyName, currentSpec) {
|
|
|
202
206
|
};
|
|
203
207
|
}
|
|
204
208
|
|
|
205
|
-
function workspaceSpecPrefix(parsedSpec) {
|
|
206
|
-
return parsedSpec.kind === "workspace-alias"
|
|
207
|
-
? `workspace:${parsedSpec.targetName}@`
|
|
208
|
-
: "workspace:";
|
|
209
|
-
}
|
|
210
|
-
|
|
211
209
|
function pinnedDependencySpec(parsedSpec, currentSpec, pinnedSpec) {
|
|
212
210
|
if (parsedSpec.kind === "registry") {
|
|
213
211
|
return pinnedSpec;
|
|
@@ -223,26 +221,6 @@ function pinnedDependencySpec(parsedSpec, currentSpec, pinnedSpec) {
|
|
|
223
221
|
return `${workspaceSpecPrefix(parsedSpec)}${selector}`;
|
|
224
222
|
}
|
|
225
223
|
|
|
226
|
-
function updatedDependencySpec(parsedSpec, currentSpec, version) {
|
|
227
|
-
if (parsedSpec.kind === "registry") {
|
|
228
|
-
return `^${version}`;
|
|
229
|
-
}
|
|
230
|
-
if (parsedSpec.kind === "workspace-path") {
|
|
231
|
-
return currentSpec;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
let workspaceRange = parsedSpec.selector;
|
|
235
|
-
if (["*", "^", "~"].includes(workspaceRange)) {
|
|
236
|
-
return currentSpec;
|
|
237
|
-
}
|
|
238
|
-
let firstVersionDigit = workspaceRange.search(/\d/);
|
|
239
|
-
if (firstVersionDigit === -1) {
|
|
240
|
-
return currentSpec;
|
|
241
|
-
}
|
|
242
|
-
let rangePrefix = workspaceRange.slice(0, firstVersionDigit);
|
|
243
|
-
return `${workspaceSpecPrefix(parsedSpec)}${rangePrefix}${version}`;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
224
|
const helpText = `
|
|
247
225
|
Usage:
|
|
248
226
|
$ upd
|
|
@@ -325,15 +303,6 @@ export async function updateVersions({
|
|
|
325
303
|
.map((n) => `${n} ${updatedPackages[n]}`)
|
|
326
304
|
.join("\n");
|
|
327
305
|
}
|
|
328
|
-
function major(versNum) {
|
|
329
|
-
if (typeof versNum === "string") {
|
|
330
|
-
return (
|
|
331
|
-
versNum.match(/^(?:workspace:)?[^\d]*(\d+)(?:\.|$)/)?.[1] ?? versNum
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
|
-
return versNum;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
306
|
let configPath = path.join(cwd, "upd.config.json");
|
|
338
307
|
let newConfig;
|
|
339
308
|
try {
|
|
@@ -355,52 +324,46 @@ export async function updateVersions({
|
|
|
355
324
|
}
|
|
356
325
|
|
|
357
326
|
let inventoryFailures = [];
|
|
358
|
-
let
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
327
|
+
let inventory = {
|
|
328
|
+
namesList: [],
|
|
329
|
+
pathsList: [],
|
|
330
|
+
pathsByName: {},
|
|
331
|
+
contentsObj: {},
|
|
332
|
+
contentsStr: {},
|
|
333
|
+
};
|
|
334
|
+
for (const currentPath of packagePaths) {
|
|
335
|
+
let packagePath = path.join(cwd, currentPath);
|
|
336
|
+
let packContentsStr;
|
|
337
|
+
try {
|
|
338
|
+
packContentsStr = await readTextFile(packagePath, "utf8");
|
|
339
|
+
} catch (error) {
|
|
340
|
+
inventoryFailures.push(makeFailure("package read", currentPath, error));
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
369
343
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
);
|
|
377
|
-
}
|
|
378
|
-
} catch (error) {
|
|
379
|
-
inventoryFailures.push(
|
|
380
|
-
makeFailure("package parse", currentPath, error),
|
|
344
|
+
let parsedContents;
|
|
345
|
+
try {
|
|
346
|
+
parsedContents = JSON.parse(packContentsStr);
|
|
347
|
+
if (!isPlainObject(parsedContents)) {
|
|
348
|
+
throw new TypeError(
|
|
349
|
+
"update-versions/updateVersions(): [THROW_ID_08] package.json must contain a JSON object.",
|
|
381
350
|
);
|
|
382
|
-
return mapReceived;
|
|
383
351
|
}
|
|
352
|
+
} catch (error) {
|
|
353
|
+
inventoryFailures.push(makeFailure("package parse", currentPath, error));
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
384
356
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
},
|
|
392
|
-
{
|
|
393
|
-
namesList: [],
|
|
394
|
-
pathsList: [],
|
|
395
|
-
pathsByName: {},
|
|
396
|
-
contentsObj: {},
|
|
397
|
-
contentsStr: {},
|
|
398
|
-
},
|
|
399
|
-
);
|
|
357
|
+
inventory.namesList.push(parsedContents.name);
|
|
358
|
+
inventory.pathsList.push(currentPath);
|
|
359
|
+
inventory.pathsByName[parsedContents.name] = currentPath;
|
|
360
|
+
inventory.contentsStr[currentPath] = packContentsStr;
|
|
361
|
+
inventory.contentsObj[currentPath] = parsedContents;
|
|
362
|
+
}
|
|
400
363
|
|
|
401
364
|
if (inventoryFailures.length > 0) {
|
|
402
365
|
throw new UpdateVersionsError(inventoryFailures, {
|
|
403
|
-
unchangedFiles:
|
|
366
|
+
unchangedFiles: inventory.pathsList,
|
|
404
367
|
});
|
|
405
368
|
}
|
|
406
369
|
|
|
@@ -408,8 +371,8 @@ export async function updateVersions({
|
|
|
408
371
|
// makes a failed registry run atomic from the caller's point of view and also
|
|
409
372
|
// deduplicates lookups shared by packages in a monorepo.
|
|
410
373
|
let externalNames = new Set();
|
|
411
|
-
for (let oneOfPaths of
|
|
412
|
-
let parsedContents =
|
|
374
|
+
for (let oneOfPaths of inventory.pathsList) {
|
|
375
|
+
let parsedContents = inventory.contentsObj[oneOfPaths];
|
|
413
376
|
for (let dependencyKey of ["dependencies", "devDependencies"]) {
|
|
414
377
|
if (isPlainObject(parsedContents[dependencyKey])) {
|
|
415
378
|
for (let [name, spec] of Object.entries(
|
|
@@ -423,7 +386,7 @@ export async function updateVersions({
|
|
|
423
386
|
let parsedSpec = parseDependencySpec(name, spec);
|
|
424
387
|
if (
|
|
425
388
|
parsedSpec.targetName &&
|
|
426
|
-
!
|
|
389
|
+
!inventory.namesList.includes(parsedSpec.targetName)
|
|
427
390
|
) {
|
|
428
391
|
externalNames.add(parsedSpec.targetName);
|
|
429
392
|
}
|
|
@@ -464,7 +427,7 @@ export async function updateVersions({
|
|
|
464
427
|
|
|
465
428
|
if (registryFailures.length > 0) {
|
|
466
429
|
throw new UpdateVersionsError(registryFailures, {
|
|
467
|
-
unchangedFiles:
|
|
430
|
+
unchangedFiles: inventory.pathsList,
|
|
468
431
|
});
|
|
469
432
|
}
|
|
470
433
|
|
|
@@ -480,13 +443,13 @@ export async function updateVersions({
|
|
|
480
443
|
}
|
|
481
444
|
|
|
482
445
|
let allProgressPromise = PProgress.all(
|
|
483
|
-
|
|
446
|
+
inventory.pathsList.map((oneOfPaths) =>
|
|
484
447
|
pProgress(async (progress) => {
|
|
485
448
|
// call progress() like progress(0.14);
|
|
486
449
|
|
|
487
450
|
let amended = false;
|
|
488
|
-
let finalContents =
|
|
489
|
-
let parsedContents =
|
|
451
|
+
let finalContents = inventory.contentsStr[oneOfPaths];
|
|
452
|
+
let parsedContents = inventory.contentsObj[oneOfPaths];
|
|
490
453
|
let fileUpdates = {};
|
|
491
454
|
|
|
492
455
|
try {
|
|
@@ -527,10 +490,10 @@ export async function updateVersions({
|
|
|
527
490
|
? parsedContents.dependencies[singleDepName]
|
|
528
491
|
: parsedContents.devDependencies[singleDepName];
|
|
529
492
|
let parsedSpec = parseDependencySpec(singleDepName, singleDepValue);
|
|
530
|
-
if (
|
|
493
|
+
if (inventory.namesList.includes(parsedSpec.targetName)) {
|
|
531
494
|
let localVersion =
|
|
532
|
-
|
|
533
|
-
|
|
495
|
+
inventory.contentsObj[
|
|
496
|
+
inventory.pathsByName[parsedSpec.targetName]
|
|
534
497
|
].version;
|
|
535
498
|
compiledDepNameVersionPairs[singleDepName] =
|
|
536
499
|
typeof localVersion === "string" && localVersion.length > 0
|
|
@@ -864,7 +827,7 @@ async function runCli() {
|
|
|
864
827
|
moduleMode: Boolean(cli.flags.module),
|
|
865
828
|
reportProgress: true,
|
|
866
829
|
});
|
|
867
|
-
|
|
830
|
+
notifyOfCliUpdate({ pkg });
|
|
868
831
|
}
|
|
869
832
|
|
|
870
833
|
function isDirectExecution() {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export function workspaceSpecPrefix(parsedSpec) {
|
|
2
|
+
return parsedSpec.kind === "workspace-alias"
|
|
3
|
+
? `workspace:${parsedSpec.targetName}@`
|
|
4
|
+
: "workspace:";
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function updatedDependencySpec(parsedSpec, currentSpec, version) {
|
|
8
|
+
if (parsedSpec.kind === "registry") {
|
|
9
|
+
return `^${version}`;
|
|
10
|
+
}
|
|
11
|
+
if (parsedSpec.kind === "workspace-path") {
|
|
12
|
+
return currentSpec;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let workspaceRange = parsedSpec.selector;
|
|
16
|
+
if (["*", "^", "~"].includes(workspaceRange)) {
|
|
17
|
+
return currentSpec;
|
|
18
|
+
}
|
|
19
|
+
let firstVersionDigit = workspaceRange.search(/\d/);
|
|
20
|
+
if (firstVersionDigit === -1) {
|
|
21
|
+
return currentSpec;
|
|
22
|
+
}
|
|
23
|
+
let rangePrefix = workspaceRange.slice(0, firstVersionDigit);
|
|
24
|
+
return `${workspaceSpecPrefix(parsedSpec)}${rangePrefix}${version}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function major(versNum) {
|
|
28
|
+
if (typeof versNum === "string") {
|
|
29
|
+
return versNum.match(/^(?:workspace:)?[^\d]*(\d+)(?:\.|$)/)?.[1] ?? versNum;
|
|
30
|
+
}
|
|
31
|
+
return versNum;
|
|
32
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "update-versions",
|
|
3
|
-
"version": "7.2.
|
|
3
|
+
"version": "7.2.5",
|
|
4
4
|
"description": "Like npm-check-updates but supports Lerna monorepos and enforces strict semver values",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"app",
|
|
@@ -60,9 +60,12 @@
|
|
|
60
60
|
},
|
|
61
61
|
"c8": {
|
|
62
62
|
"all": true,
|
|
63
|
+
"branches": 100,
|
|
63
64
|
"check-coverage": true,
|
|
64
|
-
"exclude": ["**/test/**/*.*"],
|
|
65
|
-
"
|
|
65
|
+
"exclude": ["**/test/**/*.*", "cli-update-notifier.js"],
|
|
66
|
+
"functions": 100,
|
|
67
|
+
"lines": 100,
|
|
68
|
+
"statements": 100
|
|
66
69
|
},
|
|
67
70
|
"lect": {
|
|
68
71
|
"licence": {
|
|
@@ -71,17 +74,15 @@
|
|
|
71
74
|
},
|
|
72
75
|
"dependencies": {
|
|
73
76
|
"ansi-diff-stream": "^1.2.1",
|
|
74
|
-
"codsen-glob": "^1.1.
|
|
75
|
-
"codsen-utils": "^1.10.
|
|
76
|
-
"edit-package-json": "^0.10.
|
|
77
|
+
"codsen-glob": "^1.1.2",
|
|
78
|
+
"codsen-utils": "^1.10.1",
|
|
79
|
+
"edit-package-json": "^0.10.5",
|
|
77
80
|
"object-path": "^0.11.8",
|
|
78
81
|
"p-progress": "^1.0.0",
|
|
79
|
-
"
|
|
80
|
-
"package-json": "^10.0.1",
|
|
81
|
-
"update-notifier": "^7.3.1"
|
|
82
|
+
"package-json": "^10.0.1"
|
|
82
83
|
},
|
|
83
84
|
"devDependencies": {
|
|
84
|
-
"p-map": "^7.0.
|
|
85
|
+
"p-map": "^7.0.7"
|
|
85
86
|
},
|
|
86
87
|
"engines": {
|
|
87
88
|
"node": ">=18.20.8"
|