-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathresolve.ts
148 lines (115 loc) · 4.39 KB
/
resolve.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/**
* Resolves dependencies of npm packages.
*/
import pacote from 'pacote'
import prefix from 'si-prefix'
import getHrefSize from './request'
import cache from './cache'
import agents from './agents'
import { Agent } from 'https'
import path from 'path'
export async function getDownloadSize (name: string, wanted = 'latest'): Promise<PkgDownloadSize> {
let { version, dependencies: deps } = await getManifest(name, wanted)
let downloadSizeCache = cache.pkgSizes.find(name, version)
if (downloadSizeCache !== undefined) {
// use wanted from request, not db
return { ...downloadSizeCache, wanted }
}
let pool = await agents
let agent = await pool.get()
let dependencies = await Promise.all(
deps.map(([name, wanted]) => getDownloadSizeSimple(name, wanted, agent))
)
let pkg = await getDownloadSizeSimple(name, wanted, agent)
pool.put(agent)
cache.pkgSizes.insert({ ...pkg, dependencies })
return { ...pkg, dependencies }
}
let pacoteOptions = {
cache: path.join(process.env.APPDATA || process.env.HOME || path.resolve('~'), '.npm', '_cacache'),
}
async function getManifest (name: string, wanted: string): Promise<Manifest> {
let { version, dependencies, _resolved: tarball } = await pacote.manifest(spec(name, wanted), pacoteOptions)
return { name, version, dependencies: Object.entries(dependencies || {}), tarball }
}
function spec (name: string, wanted: string) {
return `${name}@${wanted}`
}
export async function getDownloadSizeSimple (name: string, wanted: string, agent: Agent): Promise<PkgDownloadSizeSimple> {
let { version, tarball } = await getManifest(name, wanted)
let tarballSize, tarballs
let tarballsCached = cache.tarballs.find(name, version)
if (tarballsCached !== undefined) {
tarballs = new Map(tarballsCached)
} else {
tarballs = await getAllTarballs(name, version)
cache.tarballs.insert({ name, version, tarballs: Array.from(tarballs.entries()) })
}
tarballSize = await getTarballSize(tarball, agent)
let totalDependencies = tarballs.size - 1 // minus me
let size = await getTotalSize(tarballs.values(), agent)
let prettySize = pretty(size)
return { name, wanted, version, tarballSize, totalDependencies, size, prettySize }
}
async function getAllTarballs (name: string, wanted: string): Promise<Map<string, string>> {
let tarballs: Map<string, string> = new Map()
let queue: Map<string, Promise<Manifest>> = new Map()
queue.set(spec(name, wanted), getManifest(name, wanted))
for (let [spec_, manifestPromise] of queue) {
let { name, version, tarball, dependencies } = await manifestPromise
tarballs.set(spec_, tarball)
tarballs.set(spec(name, version), tarball)
dependencies.forEach(([name, wanted]) => {
if (!tarballs.has(spec(name, wanted))) {
queue.set(spec(name, wanted), getManifest(name, wanted))
}
})
}
return getResolvedTarballs(tarballs)
}
/**
* Only get tarballs with spec x.x.x, not ^x.x.x or other ranges.
* @param tarballs
*/
function getResolvedTarballs (tarballs: Map<string, string>) {
return new Map(
Array.from(tarballs)
.filter(
([spec, _]) => isNotRange(spec)
)
)
}
function isNotRange (spec: string): boolean {
let i = spec.lastIndexOf('@')
let version = spec.substr(i + 1)
return !isRange(version)
}
function isRange (version: string): boolean {
let match = version.match(/^\d+\.\d+\.\d+-?[^ ]*$/)
return match === null
}
async function getTotalSize (urls: IterableIterator<string>, agent: Agent): Promise<number> {
let promises = []
for (let url of urls) {
promises.push(getTarballSize(url, agent))
}
let sizes = await Promise.all(promises)
return sizes.reduce((sum, n) => sum + n, 0)
}
async function getTarballSize (href: string, agent: Agent): Promise<number> {
let cacheSize = cache.hrefSizes.find(href)
if (cacheSize) {
return cacheSize.size
}
const size = await getHrefSize(href, agent)
cache.hrefSizes.insert({ href, size })
return size
}
function pretty (size: number): string {
if (size === 0) {
// avoid NaN, https://github.com/mal/si-prefix/issues/1
return '0 B'
}
let [prettySize, postFix] = prefix.byte.convert(size)
return prettySize.toFixed(2) + ' ' + postFix
}