@juit/check-updates 2.0.7 → 3.0.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.
- package/README.md +109 -20
- package/dist/debug.cjs +51 -0
- package/dist/debug.cjs.map +6 -0
- package/dist/debug.d.ts +8 -0
- package/dist/debug.mjs +20 -0
- package/dist/debug.mjs.map +6 -0
- package/dist/index.cjs +37 -0
- package/dist/index.cjs.map +6 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.mjs +10 -0
- package/dist/index.mjs.map +6 -0
- package/dist/main.d.mts +2 -0
- package/dist/main.mjs +168 -0
- package/dist/main.mjs.map +6 -0
- package/dist/npmrc.cjs +74 -0
- package/dist/npmrc.cjs.map +6 -0
- package/dist/npmrc.d.ts +1 -0
- package/dist/npmrc.mjs +39 -0
- package/dist/npmrc.mjs.map +6 -0
- package/dist/updater.cjs +314 -0
- package/dist/updater.cjs.map +6 -0
- package/dist/updater.d.ts +50 -0
- package/dist/updater.mjs +279 -0
- package/dist/updater.mjs.map +6 -0
- package/dist/versions.cjs +65 -0
- package/dist/versions.cjs.map +6 -0
- package/dist/versions.d.ts +8 -0
- package/dist/versions.mjs +30 -0
- package/dist/versions.mjs.map +6 -0
- package/package.json +23 -10
- package/src/debug.ts +14 -0
- package/src/index.ts +5 -0
- package/src/main.mts +165 -0
- package/src/npmrc.ts +4 -4
- package/src/updater.ts +303 -149
- package/src/versions.ts +43 -0
- package/check-updates.js +0 -228
- package/src/main.ts +0 -75
package/src/updater.ts
CHANGED
|
@@ -1,212 +1,366 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
import { EventEmitter } from 'node:events'
|
|
1
3
|
import { readFile, writeFile } from 'node:fs/promises'
|
|
4
|
+
import { relative, resolve } from 'node:path'
|
|
2
5
|
|
|
3
|
-
import * as glob from 'glob'
|
|
4
6
|
import semver from 'semver'
|
|
5
|
-
import fetch from 'npm-registry-fetch'
|
|
6
7
|
|
|
8
|
+
|
|
9
|
+
import { B, G, R, X, Y, makeDebug } from './debug'
|
|
7
10
|
import { readNpmRc } from './npmrc'
|
|
8
11
|
|
|
9
12
|
import type { ReleaseType } from 'semver'
|
|
13
|
+
import type { VersionsCache } from './versions'
|
|
10
14
|
|
|
11
15
|
export type UpdaterOptions = {
|
|
12
|
-
bump
|
|
13
|
-
|
|
14
|
-
quick
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
bump: ReleaseType | undefined,
|
|
17
|
+
debug: boolean,
|
|
18
|
+
quick: boolean,
|
|
19
|
+
strict: boolean,
|
|
20
|
+
workspaces: boolean,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const dependencyTypes = [
|
|
24
|
+
'dependencies',
|
|
25
|
+
'devDependencies',
|
|
26
|
+
'peerDependencies',
|
|
27
|
+
'optionalDependencies',
|
|
28
|
+
] as const
|
|
29
|
+
|
|
30
|
+
type DependencyType = (typeof dependencyTypes)[number]
|
|
31
|
+
|
|
32
|
+
interface PackageData {
|
|
33
|
+
name?: string,
|
|
34
|
+
version?: string,
|
|
35
|
+
dependencies?: Record<string, string>,
|
|
36
|
+
devDependencies?: Record<string, string>,
|
|
37
|
+
peerDependencies?: Record<string, string>,
|
|
38
|
+
optionalDependencies?: Record<string, string>,
|
|
17
39
|
}
|
|
18
40
|
|
|
19
|
-
interface
|
|
41
|
+
interface DependencyChange {
|
|
20
42
|
name: string,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
43
|
+
declared: string,
|
|
44
|
+
updated: string,
|
|
45
|
+
type: DependencyType,
|
|
24
46
|
}
|
|
25
47
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
):
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
48
|
+
class Workspaces {
|
|
49
|
+
private _versions: Record<string, string> = {}
|
|
50
|
+
private _emitter = new EventEmitter()
|
|
51
|
+
|
|
52
|
+
get length(): number {
|
|
53
|
+
return Object.entries(this._versions).length
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
onUpdate(handler: (name: string, version: string) => void): void {
|
|
57
|
+
this._emitter.on('update', handler)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
register(name: string, version?: string): void {
|
|
61
|
+
assert(! this._versions[name], `Package "${name}" already registered`)
|
|
62
|
+
this._versions[name] = version || '0.0.0'
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
update(name: string, version: string): void {
|
|
66
|
+
assert(this._versions[name], `Package "${name}" not registered`)
|
|
67
|
+
const oldVersion = this._versions[name] || '0.0.0'
|
|
68
|
+
assert(semver.gte(version, oldVersion), `Package "${name}" new version ${version} less than old ${oldVersion}`)
|
|
69
|
+
if (semver.eq(version, oldVersion)) return
|
|
70
|
+
this._versions[name] = version
|
|
71
|
+
this._emitter.emit('update', name, version)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
has(name: string): boolean {
|
|
75
|
+
return !! this._versions[name]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
* [Symbol.iterator](): Generator<[ name: string, version: string ]> {
|
|
79
|
+
for (const [ name, version ] of Object.entries(this._versions)) {
|
|
80
|
+
yield [ name, version ]
|
|
58
81
|
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
59
84
|
|
|
60
|
-
$debug(`Retrieving versions for package ${Y}${name}${K}`)
|
|
61
85
|
|
|
62
|
-
|
|
86
|
+
export class Updater {
|
|
87
|
+
private _packageData?: PackageData
|
|
88
|
+
private _npmRc?: Record<string, any>
|
|
89
|
+
private _originalVersion?: string
|
|
90
|
+
|
|
91
|
+
private _debug: (...args: any[]) => void
|
|
92
|
+
private _children: Updater[]
|
|
93
|
+
private _changed = false
|
|
94
|
+
|
|
95
|
+
constructor(
|
|
96
|
+
private readonly _packageFile: string,
|
|
97
|
+
private readonly _options: UpdaterOptions,
|
|
98
|
+
private readonly _cache: VersionsCache,
|
|
99
|
+
private readonly _workspaces: Workspaces = new Workspaces(),
|
|
100
|
+
) {
|
|
101
|
+
this._packageFile = resolve(_packageFile)
|
|
102
|
+
this._debug = makeDebug(_options.debug)
|
|
103
|
+
this._children = []
|
|
104
|
+
|
|
105
|
+
_workspaces.onUpdate((name, version) => {
|
|
106
|
+
if (! this._packageData) return
|
|
107
|
+
|
|
108
|
+
for (const type of dependencyTypes) {
|
|
109
|
+
const dependencies = this._packageData[type]
|
|
110
|
+
if (! dependencies) continue
|
|
111
|
+
if (! dependencies[name]) continue
|
|
112
|
+
if (dependencies[name] === version) continue
|
|
113
|
+
|
|
114
|
+
dependencies[name] = version
|
|
115
|
+
this._changed = true
|
|
116
|
+
this._bump()
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
}
|
|
63
120
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
121
|
+
get name(): string | undefined {
|
|
122
|
+
assert(this._packageData, 'Updater not initialized')
|
|
123
|
+
return this._packageData.name
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
get version(): string {
|
|
127
|
+
assert(this._packageData, 'Updater not initialized')
|
|
128
|
+
return this._packageData.version || '0.0.0'
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
set version(version: string) {
|
|
132
|
+
assert(this._originalVersion && this._packageData, 'Updater not initialized')
|
|
133
|
+
|
|
134
|
+
assert(semver.lte(this._originalVersion, version), [
|
|
135
|
+
`Unable to set version for "${this.packageFile}" to "${version}"`,
|
|
136
|
+
`as it's less than original version "${this._originalVersion}"`,
|
|
137
|
+
].join(' '))
|
|
138
|
+
|
|
139
|
+
if (semver.eq(this._originalVersion, version)) return
|
|
140
|
+
if (this._packageData.version === version) return
|
|
141
|
+
|
|
142
|
+
this._changed = true
|
|
143
|
+
console.log(`Updating ${this._details} version to ${Y}${version}${X}`)
|
|
144
|
+
this._packageData.version = version
|
|
145
|
+
if (this.name) this._workspaces.update(this.name, version)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
get packageFile(): string {
|
|
149
|
+
return relative(process.cwd(), this._packageFile)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
get changed(): boolean {
|
|
153
|
+
if (this._changed) return true
|
|
154
|
+
return this._children.reduce((changed, child) => changed || child.changed, false)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async init(): Promise<this> {
|
|
158
|
+
this._debug('Reading package file', this.packageFile)
|
|
159
|
+
|
|
160
|
+
/* Parse our package file */
|
|
161
|
+
const json = await readFile(this._packageFile, 'utf8')
|
|
162
|
+
const data = this._packageData = JSON.parse(json)
|
|
163
|
+
assert(data && (typeof data === 'object') && (! Array.isArray(data)),
|
|
164
|
+
`File ${this.packageFile} is not a valid "pacakge.json" file`)
|
|
165
|
+
|
|
166
|
+
/* Parse the ".npmrc" relative to the package file */
|
|
167
|
+
const npmrc = await readNpmRc(this._packageFile)
|
|
168
|
+
|
|
169
|
+
/* Register this package in our workspaces and set the original version */
|
|
170
|
+
if (data.name) this._workspaces.register(data.name, data.version)
|
|
171
|
+
this._originalVersion = data.version || '0.0.0'
|
|
172
|
+
|
|
173
|
+
/* Read up our workspaces */
|
|
174
|
+
if (this._options.workspaces && data.workspaces) {
|
|
175
|
+
for (const path of data.workspaces) {
|
|
176
|
+
const packageFile = resolve(this._packageFile, '..', path, 'package.json')
|
|
177
|
+
const updater = new Updater(packageFile, this._options, this._cache, this._workspaces)
|
|
178
|
+
this._children.push(await updater.init())
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* Done */
|
|
183
|
+
this._packageData = data
|
|
184
|
+
this._npmRc = npmrc
|
|
185
|
+
return this
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private get _details(): string {
|
|
189
|
+
let string = `${G}${this.packageFile}${X}`
|
|
190
|
+
if (this.name || this.version) {
|
|
191
|
+
string += ` [${Y}`
|
|
192
|
+
if (this.name) string += `${this.name}`
|
|
193
|
+
if (this.name && this.version) string += ' '
|
|
194
|
+
if (this.version) string += `${this.version}`
|
|
195
|
+
string += `${X}]`
|
|
196
|
+
}
|
|
197
|
+
return string
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private _bump(): void {
|
|
201
|
+
assert(this._originalVersion, 'Updater not initialized')
|
|
202
|
+
|
|
203
|
+
if (this._options.bump) {
|
|
204
|
+
this.version = semver.inc(this._originalVersion, this._options.bump) || this._originalVersion
|
|
205
|
+
}
|
|
72
206
|
}
|
|
73
207
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
208
|
+
/** Update a single dependency, returning the highest matching version */
|
|
209
|
+
private async _updateDependency(name: string, rangeString: string): Promise<string> {
|
|
210
|
+
assert(this._npmRc, 'Updater not initialized')
|
|
211
|
+
|
|
212
|
+
/* Check if this is a workspace package */
|
|
213
|
+
if (this._workspaces.has(name)) {
|
|
214
|
+
this._debug(`Not processing workspace package ${Y}${name}${X}`)
|
|
215
|
+
return rangeString
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/* Check that we have a proper range (^x... or ~x...) */
|
|
78
219
|
const match = /^\s*([~^])\s*(\d+(\.\d+(\.\d+)?)?)\s*$/.exec(rangeString)
|
|
79
220
|
if (! match) {
|
|
80
|
-
|
|
221
|
+
this._debug(`Not processing range ${G}${rangeString}${X} for ${Y}${name}${X}`)
|
|
81
222
|
return rangeString
|
|
82
223
|
}
|
|
83
224
|
|
|
225
|
+
/* Extract specifier and version from the string range*/
|
|
84
226
|
const [ , specifier = '', version = '' ] = match
|
|
85
227
|
|
|
86
|
-
if
|
|
228
|
+
/* Extend range if not in strict mode */
|
|
229
|
+
if (! this._options.strict) {
|
|
87
230
|
const r = rangeString
|
|
88
231
|
rangeString = `>=${version}`
|
|
89
232
|
if (specifier === '~') rangeString += ` <${semver.inc(version, 'major')}`
|
|
90
|
-
|
|
233
|
+
this._debug(`Extending version for ${Y}${name}${X} from ${G}${r}${X} to ${G}${rangeString}${X}`)
|
|
91
234
|
}
|
|
92
235
|
|
|
236
|
+
/* Get the highest matching version and return it */
|
|
93
237
|
const range = new semver.Range(rangeString)
|
|
94
|
-
const versions = await getVersions(name,
|
|
95
|
-
|
|
238
|
+
const versions = await this._cache.getVersions(name, this._npmRc)
|
|
96
239
|
for (const v of versions) {
|
|
97
240
|
if (range.test(v)) return `${specifier}${v}`
|
|
98
241
|
}
|
|
242
|
+
|
|
243
|
+
/* No version found, return the original one cleaned up */
|
|
99
244
|
return `${specifier}${version}`
|
|
100
245
|
}
|
|
101
246
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
247
|
+
/** Update a dependencies group, populating the "updated" version field */
|
|
248
|
+
private async _updateDependenciesGroup(type: DependencyType): Promise<DependencyChange []> {
|
|
249
|
+
assert(this._packageData, 'Updater not initialized')
|
|
250
|
+
const dependencies = this._packageData[type]
|
|
251
|
+
if (! dependencies) return []
|
|
252
|
+
|
|
253
|
+
/* Parallelize updates for this group */
|
|
254
|
+
const promises = Object.entries(dependencies)
|
|
255
|
+
.map(async ([ name, declared ]) => {
|
|
256
|
+
const updated = await this._updateDependency(name, declared)
|
|
257
|
+
if (! this._options.debug) process.stdout.write('.')
|
|
258
|
+
if (updated === declared) return
|
|
259
|
+
|
|
260
|
+
dependencies[name] = updated
|
|
261
|
+
return { name, declared, updated, type } satisfies DependencyChange
|
|
262
|
+
})
|
|
117
263
|
|
|
118
|
-
|
|
119
|
-
|
|
264
|
+
/* Await all updates and return changes */
|
|
265
|
+
return (await Promise.all(promises))
|
|
266
|
+
.filter((change): change is DependencyChange => !! change)
|
|
267
|
+
}
|
|
120
268
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
269
|
+
/** Update dependencies and return the version number of this package */
|
|
270
|
+
async update(): Promise<void> {
|
|
271
|
+
assert(this._packageData, 'Updater not initialized')
|
|
124
272
|
|
|
125
|
-
|
|
273
|
+
/* Start by processing all workspaces first */
|
|
274
|
+
for (const child of this._children) await child.update()
|
|
126
275
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const to = await updateDependency(name, from, npmrc)
|
|
131
|
-
if (! debug) process.stdout.write('.')
|
|
132
|
-
if (from !== to) {
|
|
133
|
-
changes.push({ name, from, to, kind })
|
|
134
|
-
if (type === 'dependencies') mainDependencyChanges ++
|
|
135
|
-
}
|
|
136
|
-
dependencies[name] = to
|
|
137
|
-
})
|
|
276
|
+
/* Some pretty printing of our package name and version */
|
|
277
|
+
process.stdout.write(`Processing ${this._details} `)
|
|
278
|
+
if (this._options.debug) process.stdout.write('\n')
|
|
138
279
|
|
|
139
|
-
|
|
280
|
+
/* Process the _main_ dependencies group first */
|
|
281
|
+
const changes = await this._updateDependenciesGroup('dependencies')
|
|
140
282
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return deps
|
|
147
|
-
}, {} as Record<string, string>)
|
|
148
|
-
} else {
|
|
149
|
-
delete data[type]
|
|
150
|
-
}
|
|
283
|
+
/* Process all the other dependencies if we need to do so */
|
|
284
|
+
if (changes.length || (! this._options.quick)) {
|
|
285
|
+
changes.push(...await this._updateDependenciesGroup('devDependencies'))
|
|
286
|
+
changes.push(...await this._updateDependenciesGroup('optionalDependencies'))
|
|
287
|
+
changes.push(...await this._updateDependenciesGroup('peerDependencies'))
|
|
151
288
|
}
|
|
152
289
|
|
|
153
|
-
if
|
|
154
|
-
|
|
155
|
-
if (! changes.length) {
|
|
156
|
-
|
|
157
|
-
return 0
|
|
158
|
-
}
|
|
290
|
+
/* Simply return if no changes were detected or mark this as changed */
|
|
291
|
+
if (this._options.debug) process.stdout.write('Updated with')
|
|
292
|
+
if (! changes.length) return void console.log(` ${R}no changes${X}`)
|
|
293
|
+
this._changed = true
|
|
159
294
|
|
|
160
|
-
/* Really pretty print */
|
|
295
|
+
/* Really pretty print all our changed dependencies */
|
|
161
296
|
changes.sort(({ name: a }, { name: b }) => a < b ? -1 : a > b ? 1 : 0)
|
|
162
|
-
console.log(` ${R}${changes.length} changes${
|
|
163
|
-
let lname = 0
|
|
164
|
-
|
|
297
|
+
console.log(` ${R}${changes.length} changes${X}`)
|
|
298
|
+
let lname = 0
|
|
299
|
+
let ldeclared = 0
|
|
300
|
+
let lupdated = 0
|
|
301
|
+
for (const { name, declared, updated } of changes) {
|
|
165
302
|
lname = lname > name.length ? lname : name.length
|
|
166
|
-
|
|
167
|
-
|
|
303
|
+
ldeclared = ldeclared > declared.length ? ldeclared : declared.length
|
|
304
|
+
lupdated = lupdated > updated.length ? lupdated : updated.length
|
|
168
305
|
}
|
|
169
306
|
|
|
170
|
-
for (const { name,
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
console.log(
|
|
177
|
-
|
|
307
|
+
for (const { name, declared, updated, type } of changes) {
|
|
308
|
+
const kind =
|
|
309
|
+
type === 'devDependencies' ? 'dev' :
|
|
310
|
+
type === 'peerDependencies' ? 'peer' :
|
|
311
|
+
type === 'optionalDependencies' ? 'optional' :
|
|
312
|
+
'main'
|
|
313
|
+
console.log([
|
|
314
|
+
` * ${Y}${name.padEnd(lname)}${X}`,
|
|
315
|
+
` : ${G}${declared.padStart(ldeclared)}${X}`,
|
|
316
|
+
` -> ${G}${updated.padEnd(lupdated)} ${B}${kind}${X}`,
|
|
317
|
+
].join(''))
|
|
178
318
|
}
|
|
179
319
|
|
|
180
320
|
/* Bump the package if we need to */
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
321
|
+
this._bump()
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
align(version?: string): void {
|
|
325
|
+
if (this._workspaces.length < 2) {
|
|
326
|
+
return this._debug(`No workspaces found in ${this._details}`)
|
|
185
327
|
}
|
|
186
328
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
return changes.length
|
|
329
|
+
if (! version) {
|
|
330
|
+
let aligned = '0.0.0'
|
|
331
|
+
for (const [ , version ] of this._workspaces) {
|
|
332
|
+
if (semver.gt(version, aligned)) aligned = version
|
|
333
|
+
}
|
|
334
|
+
version = aligned
|
|
194
335
|
}
|
|
336
|
+
|
|
337
|
+
this.version = version
|
|
338
|
+
for (const child of this._children) child.version = version
|
|
339
|
+
console.log(`Workspaces versions aligned to ${Y}${version}${X}`)
|
|
195
340
|
}
|
|
196
341
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const files = await glob.glob(patterns)
|
|
201
|
-
let changes = 0
|
|
342
|
+
/** Write out the new package file */
|
|
343
|
+
async write(): Promise<void> {
|
|
344
|
+
assert(this._packageData, 'Updater not initialized')
|
|
202
345
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
346
|
+
/* Sort all our dependencies */
|
|
347
|
+
for (const type of dependencyTypes) {
|
|
348
|
+
const dependencies = Object.entries(this._packageData[type] || {})
|
|
349
|
+
if (dependencies.length) {
|
|
350
|
+
this._packageData[type] = dependencies
|
|
351
|
+
.sort(([ nameA ], [ nameB ]) => nameA.localeCompare(nameB))
|
|
352
|
+
.reduce((deps, [ name, version ]) => {
|
|
353
|
+
deps[name] = version
|
|
354
|
+
return deps
|
|
355
|
+
}, {} as Record<string, string>)
|
|
356
|
+
} else {
|
|
357
|
+
delete this._packageData[type]
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
const json = JSON.stringify(this._packageData, null, 2)
|
|
361
|
+
this._debug(`${Y}>>>`, this.packageFile, `<<<${X}\n${json}`)
|
|
362
|
+
await writeFile(this.packageFile, json + '\n')
|
|
210
363
|
|
|
211
|
-
|
|
364
|
+
for (const child of this._children) await child.write()
|
|
365
|
+
}
|
|
212
366
|
}
|
package/src/versions.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import fetch from 'npm-registry-fetch'
|
|
2
|
+
import semver from 'semver'
|
|
3
|
+
|
|
4
|
+
import { X, Y, makeDebug } from './debug'
|
|
5
|
+
|
|
6
|
+
/** Cache of versions */
|
|
7
|
+
export class VersionsCache {
|
|
8
|
+
private _cache: Record<string, Promise<string[]>>
|
|
9
|
+
private _debug: (...args: any) => void
|
|
10
|
+
|
|
11
|
+
constructor(debug: boolean = false) {
|
|
12
|
+
this._debug = makeDebug(debug)
|
|
13
|
+
this._cache = {}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Return the available versions for a package, sorted */
|
|
17
|
+
getVersions(
|
|
18
|
+
name: string,
|
|
19
|
+
npmrc: Record<string, any>,
|
|
20
|
+
): Promise<string[]> {
|
|
21
|
+
if (this._cache[name]) {
|
|
22
|
+
this._debug(`Returning cached versions for ${Y}${name}${X}`)
|
|
23
|
+
return this._cache[name]!
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
this._debug(`Retrieving versions for package ${Y}${name}${X}`)
|
|
27
|
+
|
|
28
|
+
const range = new semver.Range('>=0.0.0', { includePrerelease: false })
|
|
29
|
+
|
|
30
|
+
/* we cache the promise, so that multiple concurrent requests for the same
|
|
31
|
+
* package will generate only one http fetch request */
|
|
32
|
+
const promise = fetch.json(name, Object.assign({}, npmrc, { spec: name }))
|
|
33
|
+
.then((data: any) => {
|
|
34
|
+
return Object.entries(data.versions as Record<string, Record<string, any>>)
|
|
35
|
+
.filter(([ , info ]) => ! info.deprecated) // no deprecated
|
|
36
|
+
.map(([ version ]) => version) // extract key (version)
|
|
37
|
+
.filter((version) => range.test(version)) // range match
|
|
38
|
+
.sort(semver.rcompare)
|
|
39
|
+
})
|
|
40
|
+
this._cache[name] = promise
|
|
41
|
+
return promise
|
|
42
|
+
}
|
|
43
|
+
}
|