/
allProjectsAreUpToDate.ts
131 lines (125 loc) · 4.57 KB
/
allProjectsAreUpToDate.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
import path from 'path'
import { ProjectOptions } from '@pnpm/get-context'
import {
Lockfile,
ProjectSnapshot,
} from '@pnpm/lockfile-file'
import { satisfiesPackageManifest } from '@pnpm/lockfile-utils'
import { safeReadPackageFromDir as safeReadPkgFromDir } from '@pnpm/read-package-json'
import { WorkspacePackages } from '@pnpm/resolver-base'
import {
DEPENDENCIES_FIELDS,
DependencyManifest,
ProjectManifest,
} from '@pnpm/types'
import pEvery from 'p-every'
import any from 'ramda/src/any'
import semver from 'semver'
export default async function allProjectsAreUpToDate (
projects: Array<ProjectOptions & { id: string }>,
opts: {
linkWorkspacePackages: boolean
wantedLockfile: Lockfile
workspacePackages: WorkspacePackages
}
) {
const manifestsByDir = opts.workspacePackages ? getWorkspacePackagesByDirectory(opts.workspacePackages) : {}
const _satisfiesPackageManifest = satisfiesPackageManifest.bind(null, opts.wantedLockfile)
const _linkedPackagesAreUpToDate = linkedPackagesAreUpToDate.bind(null, {
linkWorkspacePackages: opts.linkWorkspacePackages,
manifestsByDir,
workspacePackages: opts.workspacePackages,
})
return pEvery(projects, (project) => {
const importer = opts.wantedLockfile.importers[project.id]
return !hasLocalTarballDepsInRoot(importer) &&
_satisfiesPackageManifest(project.manifest, project.id) &&
_linkedPackagesAreUpToDate({
dir: project.rootDir,
manifest: project.manifest,
snapshot: importer,
})
})
}
function getWorkspacePackagesByDirectory (workspacePackages: WorkspacePackages) {
const workspacePackagesByDirectory = {}
Object.keys(workspacePackages || {}).forEach((pkgName) => {
Object.keys(workspacePackages[pkgName] || {}).forEach((pkgVersion) => {
workspacePackagesByDirectory[workspacePackages[pkgName][pkgVersion].dir] = workspacePackages[pkgName][pkgVersion].manifest
})
})
return workspacePackagesByDirectory
}
async function linkedPackagesAreUpToDate (
{
linkWorkspacePackages,
manifestsByDir,
workspacePackages,
}: {
linkWorkspacePackages: boolean
manifestsByDir: Record<string, DependencyManifest>
workspacePackages: WorkspacePackages
},
project: {
dir: string
manifest: ProjectManifest
snapshot: ProjectSnapshot
}
) {
for (const depField of DEPENDENCIES_FIELDS) {
const lockfileDeps = project.snapshot[depField]
const manifestDeps = project.manifest[depField]
if ((lockfileDeps == null) || (manifestDeps == null)) continue
const depNames = Object.keys(lockfileDeps)
for (const depName of depNames) {
const currentSpec = manifestDeps[depName]
if (!currentSpec) continue
const lockfileRef = lockfileDeps[depName]
const isLinked = lockfileRef.startsWith('link:')
if (
isLinked &&
(
currentSpec.startsWith('link:') ||
currentSpec.startsWith('file:') ||
currentSpec.startsWith('workspace:.')
)
) {
continue
}
const linkedDir = isLinked
? path.join(project.dir, lockfileRef.slice(5))
: workspacePackages?.[depName]?.[lockfileRef]?.dir
if (!linkedDir) continue
if (!linkWorkspacePackages && !currentSpec.startsWith('workspace:')) {
// we found a linked dir, but we don't want to use it, because it's not specified as a
// workspace:x.x.x dependency
continue
}
const linkedPkg = manifestsByDir[linkedDir] ?? await safeReadPkgFromDir(linkedDir)
const availableRange = getVersionRange(currentSpec)
// This should pass the same options to semver as @pnpm/npm-resolver
const localPackageSatisfiesRange = availableRange === '*' ||
linkedPkg && semver.satisfies(linkedPkg.version, availableRange, { loose: true })
if (isLinked !== localPackageSatisfiesRange) return false
}
}
return true
}
function getVersionRange (spec: string) {
if (spec.startsWith('workspace:')) return spec.slice(10)
if (spec.startsWith('npm:')) {
spec = spec.slice(4)
const index = spec.indexOf('@', 1)
if (index === -1) return '*'
return spec.slice(index + 1) || '*'
}
return spec
}
function hasLocalTarballDepsInRoot (importer: ProjectSnapshot) {
return any(refIsLocalTarball, Object.values(importer.dependencies ?? {})) ||
any(refIsLocalTarball, Object.values(importer.devDependencies ?? {})) ||
any(refIsLocalTarball, Object.values(importer.optionalDependencies ?? {}))
}
function refIsLocalTarball (ref: string) {
return ref.startsWith('file:') && (ref.endsWith('.tgz') || ref.endsWith('.tar.gz') || ref.endsWith('.tar'))
}