Skip to content

Commit 574d483

Browse files
authored
Enhance performance of get peer deps (#1554)
1 parent c55567b commit 574d483

File tree

4 files changed

+86
-20
lines changed

4 files changed

+86
-20
lines changed

src/lib/cache.ts

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import fs from 'fs'
22
import os from 'os'
33
import path from 'path'
44
import { CacheData, Cacher } from '../types/Cacher'
5+
import { Index } from '../types/IndexType'
56
import { Options } from '../types/Options'
7+
import { Version } from '../types/Version'
68
import { print } from './logging'
79

810
export const CACHE_DELIMITER = '___'
@@ -55,7 +57,7 @@ export default async function cacher(options: Omit<Options, 'cacher'>): Promise<
5557

5658
const cacheFile = resolveCacheFile(options.cacheFile)
5759
let cacheData: CacheData = {}
58-
const cacheUpdates: Record<string, string> = {}
60+
const cacheHits = new Set<string>()
5961

6062
try {
6163
cacheData = JSON.parse(await fs.promises.readFile(cacheFile, 'utf-8'))
@@ -76,32 +78,53 @@ export default async function cacher(options: Omit<Options, 'cacher'>): Promise<
7678
if (!cacheData.packages) {
7779
cacheData.packages = {}
7880
}
81+
if (!cacheData.peers) {
82+
cacheData.peers = {}
83+
}
7984

8085
return {
8186
get: (name: string, target: string) => {
87+
if (!cacheData.packages) return
8288
const key = `${name}${CACHE_DELIMITER}${target}`
83-
if (!key || !cacheData.packages) return
8489
const cached = cacheData.packages[key]
8590
if (cached && !key.includes(cached)) {
86-
const [name] = key.split(CACHE_DELIMITER)
87-
cacheUpdates[name] = cached
91+
cacheHits.add(name)
8892
}
8993
return cached
9094
},
9195
set: (name: string, target: string, version: string) => {
96+
if (!cacheData.packages) return
9297
const key = `${name}${CACHE_DELIMITER}${target}`
93-
if (!key || !cacheData.packages) return
9498
cacheData.packages[key] = version
9599
},
100+
getPeers: (name: string, version: Version) => {
101+
if (!cacheData.peers) return
102+
const key = `${name}${CACHE_DELIMITER}${version}`
103+
const cached = cacheData.peers[key]
104+
if (cached) {
105+
cacheHits.add(name)
106+
}
107+
return cached
108+
},
109+
setPeers: (name: string, version: Version, peers: Index<string>) => {
110+
const key = `${name}${CACHE_DELIMITER}${version}`
111+
if (!cacheData.peers) return
112+
cacheData.peers[key] = peers
113+
},
96114
save: async () => {
97115
await fs.promises.writeFile(cacheFile, JSON.stringify(cacheData))
98116
},
99-
log: () => {
100-
const cacheCount = Object.keys(cacheUpdates).length
117+
log: (peers?: boolean) => {
118+
const cacheCount = cacheHits.size
101119
if (cacheCount === 0) return
102120

103-
print(options, `\nUsing ${cacheCount} cached package version${cacheCount > 1 ? 's' : ''}`, 'warn')
104-
print(options, cacheUpdates, 'verbose')
121+
print(
122+
options,
123+
`\nUsing ${cacheCount} cached package ${peers ? 'peer' : 'version'}${cacheCount > 1 ? 's' : ''}`,
124+
'warn',
125+
)
126+
print(options, cacheHits, 'verbose')
127+
cacheHits.clear()
105128
},
106-
} as Cacher
129+
} satisfies Cacher
107130
}

src/lib/getPeerDependenciesFromRegistry.ts

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import pMap from 'p-map'
12
import ProgressBar from 'progress'
23
import { Index } from '../types/IndexType'
34
import { Options } from '../types/Options'
@@ -59,19 +60,47 @@ async function getPeerDependenciesFromRegistry(packageMap: Index<Version>, optio
5960
bar.render()
6061
}
6162

62-
return Object.entries(packageMap).reduce(async (accumPromise, [pkg, version]) => {
63-
const dep = await packageManager.getPeerDependencies!(pkg, version, { cwd: options.cwd })
63+
const packageEntries = Object.entries(packageMap)
64+
65+
/**
66+
* Fetches peer dependencies for a package
67+
* @param pkg - The package name
68+
* @param version - The package version
69+
* @returns Promise that resolves to package name and its peer dependencies
70+
*/
71+
const getPeerDepsForPackage = async ([pkg, version]: [string, Version]): Promise<{
72+
pkg: string
73+
dependencies: Index<string>
74+
}> => {
75+
let dependencies: Index<string>
76+
const cached = options.cacher?.getPeers(pkg, version)
77+
if (cached) {
78+
dependencies = cached
79+
} else {
80+
dependencies = await packageManager.getPeerDependencies!(pkg, version, { cwd: options.cwd })
81+
options.cacher?.setPeers(pkg, version, dependencies)
82+
}
6483
if (bar) {
6584
bar.tick()
6685
}
67-
const accum = await accumPromise
68-
const newAcc: Index<Index<string>> = { ...accum, [pkg]: dep }
69-
const circularData = isCircularPeer(newAcc, pkg)
86+
return { pkg, dependencies }
87+
}
88+
89+
const results = await pMap(packageEntries, getPeerDepsForPackage, { concurrency: options.concurrency })
90+
91+
const accum: Index<Index<string>> = {}
92+
for (const { pkg, dependencies } of results) {
93+
accum[pkg] = dependencies
94+
const circularData = isCircularPeer(accum, pkg)
7095
if (circularData.isCircular) {
71-
delete newAcc[pkg][circularData.offendingPackage]
96+
delete accum[pkg][circularData.offendingPackage]
7297
}
73-
return newAcc
74-
}, Promise.resolve<Index<Index<string>>>({}))
98+
}
99+
100+
await options.cacher?.save()
101+
options.cacher?.log(true)
102+
103+
return accum
75104
}
76105

77106
export default getPeerDependenciesFromRegistry

src/types/Cacher.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1+
import { Index } from './IndexType'
2+
import { Version } from './Version'
3+
14
export interface CacheData {
25
timestamp?: number
36
packages?: Record<string, string | undefined>
7+
peers?: Record<string, Index<string> | undefined>
48
}
59

610
export type Cacher = {
711
get(name: string, target: string): string | undefined
812
set(name: string, target: string, version: string): void
13+
getPeers(name: string, version: Version): Index<string> | undefined
14+
setPeers(name: string, version: Version, peers: Index<string>): void
915
save(): Promise<void>
10-
log(): void
16+
log(peers?: boolean): void
1117
}

test/cache.test.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ describe('cache', () => {
2424
},
2525
}
2626

27-
await ncu({ packageData, cache: true })
27+
await ncu({ packageData, cache: true, peer: true })
2828

2929
const cacheData: CacheData = await fs.readFile(resolvedDefaultCacheFile, 'utf-8').then(JSON.parse)
3030

@@ -34,6 +34,13 @@ describe('cache', () => {
3434
[`ncu-test-tag${CACHE_DELIMITER}latest`]: '1.1.0',
3535
[`ncu-test-alpha${CACHE_DELIMITER}latest`]: '1.0.0',
3636
})
37+
expect(cacheData.peers).deep.eq({
38+
[`ncu-test-alpha${CACHE_DELIMITER}1.0.0`]: {},
39+
[`ncu-test-tag${CACHE_DELIMITER}1.0.0`]: {},
40+
[`ncu-test-tag${CACHE_DELIMITER}1.1.0`]: {},
41+
[`ncu-test-v2${CACHE_DELIMITER}1.0.0`]: {},
42+
[`ncu-test-v2${CACHE_DELIMITER}2.0.0`]: {},
43+
})
3744
} finally {
3845
await fs.rm(resolvedDefaultCacheFile, { recursive: true, force: true })
3946
stub.restore()
@@ -75,6 +82,7 @@ describe('cache', () => {
7582
[`ncu-test-tag${CACHE_DELIMITER}latest`]: '1.1.0',
7683
[`ncu-test-alpha${CACHE_DELIMITER}latest`]: '1.0.0',
7784
})
85+
expect(cacheData1.peers).deep.eq({})
7886

7987
// second run has a different target so should not use the cache
8088
const result2 = await ncu({ packageData, cache: true, target: 'greatest' })

0 commit comments

Comments
 (0)