Skip to content

Commit 9dd98c3

Browse files
committed
Use lockfiles to determine package resolutions.
1 parent c30be71 commit 9dd98c3

File tree

7 files changed

+133
-30
lines changed

7 files changed

+133
-30
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
"typescript": "^3.2.2"
6969
},
7070
"dependencies": {
71+
"@yarnpkg/lockfile": "^1.1.0",
7172
"chalk": "^1.1.3",
7273
"cross-spawn": "^6.0.5",
7374
"fs-extra": "^4.0.1",

src/PackageDetails.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { join } from "path"
22

3-
interface PackageDetails {
3+
export interface PackageDetails {
44
humanReadablePathSpecifier: string
55
pathSpecifier: string
66
path: string

src/getPackageResolution.ts

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { join, resolve } from "./path"
2+
import { PackageDetails, getPatchDetailsFromCliString } from "./PackageDetails"
3+
import { PackageManager, detectPackageManager } from "./detectPackageManager"
4+
import { readFileSync } from "fs-extra"
5+
import { parse as parseYarnLockFile } from "@yarnpkg/lockfile"
6+
7+
export function getPackageResolution({
8+
packageDetails,
9+
packageManager,
10+
appPath,
11+
}: {
12+
packageDetails: PackageDetails
13+
packageManager: PackageManager
14+
appPath: string
15+
}) {
16+
if (packageManager === "yarn") {
17+
const appLockFile = parseYarnLockFile(readFileSync("yarn.lock").toString())
18+
if (appLockFile.type !== "success") {
19+
throw new Error("Can't parse lock file")
20+
}
21+
22+
const installedVersion = require(join(
23+
resolve(appPath, packageDetails.path),
24+
"package.json",
25+
)).version as string
26+
27+
const entries = Object.entries(appLockFile.object).filter(
28+
([k, v]) =>
29+
k.startsWith(packageDetails.name + "@") &&
30+
v.version === installedVersion,
31+
)
32+
33+
const resolutions = entries.map(([_, v]) => {
34+
return v.resolved
35+
})
36+
37+
if (resolutions.length === 0) {
38+
throw new Error(
39+
`Can't find lockfile entry for ${packageDetails.pathSpecifier}`,
40+
)
41+
}
42+
43+
if (new Set(resolutions).size !== 1) {
44+
console.warn(
45+
`Ambigious lockfile entries for ${
46+
packageDetails.pathSpecifier
47+
}. Using version ${installedVersion}`,
48+
)
49+
return installedVersion
50+
}
51+
52+
if (resolutions[0]) {
53+
return resolutions[0]
54+
}
55+
56+
const resolution = entries[0][0].slice(packageDetails.name.length + 1)
57+
58+
// resolve relative file path
59+
if (resolution.startsWith("file:.")) {
60+
return `file:${resolve(appPath, resolution.slice("file:".length))}`
61+
}
62+
63+
return resolution
64+
} else {
65+
const lockfile = require(join(
66+
appPath,
67+
packageManager === "npm-shrinkwrap"
68+
? "npm-shrinkwrap.json"
69+
: "package-lock.json",
70+
))
71+
const lockFileStack = [lockfile]
72+
for (const name of packageDetails.packageNames.slice(0, -1)) {
73+
const child = lockFileStack[0].dependencies
74+
if (child && name in child) {
75+
lockFileStack.push(child[name])
76+
}
77+
}
78+
lockFileStack.reverse()
79+
const relevantStackEntry = lockFileStack.find(
80+
entry => entry.dependencies && packageDetails.name in entry.dependencies,
81+
)
82+
return relevantStackEntry.dependencies[packageDetails.name].resolved
83+
}
84+
}
85+
86+
if (require.main === module) {
87+
const packageDetails = getPatchDetailsFromCliString(process.argv[2])
88+
if (!packageDetails) {
89+
console.error(`Can't find package ${process.argv[2]}`)
90+
process.exit(1)
91+
throw new Error()
92+
}
93+
console.log(
94+
getPackageResolution({
95+
appPath: process.cwd(),
96+
packageDetails,
97+
packageManager: detectPackageManager(process.cwd(), null),
98+
}),
99+
)
100+
}

src/makePatch.ts

+11-28
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
getPackageDetailsFromPatchFilename,
2020
} from "./PackageDetails"
2121
import { resolveRelativeFileDependencies } from "./resolveRelativeFileDependencies"
22+
import { getPackageResolution } from "./getPackageResolution"
2223

2324
function printNoPackageFoundError(
2425
packageName: string,
@@ -54,33 +55,6 @@ export const makePatch = (
5455
process.exit(1)
5556
}
5657

57-
const packageVersion = require(packageJsonPath).version as string
58-
59-
// packageVersionSpecifier is the version string used by the app package.json
60-
// it won't be present for nested deps.
61-
// We need it only for patching deps specified with file:./
62-
// which I think only happens in tests
63-
// but might happen in real life too.
64-
let packageVersionSpecifier: null | string = null
65-
if (!packageDetails.isNested) {
66-
const { devDependencies = {}, dependencies = {} } = appPackageJson
67-
packageVersionSpecifier =
68-
dependencies[packageDetails.name] ||
69-
devDependencies[packageDetails.name] ||
70-
null
71-
}
72-
73-
if (
74-
packageVersionSpecifier &&
75-
packageVersionSpecifier.startsWith("file:") &&
76-
packageVersionSpecifier[5] !== "/"
77-
) {
78-
packageVersionSpecifier =
79-
"file:" + resolve(appPath, packageVersionSpecifier.slice(5))
80-
} else {
81-
packageVersionSpecifier = null
82-
}
83-
8458
const tmpRepo = dirSync({ unsafeCleanup: true })
8559
const tmpRepoPackagePath = join(tmpRepo.name, packageDetails.path)
8660
const tmpRepoNpmRoot = tmpRepoPackagePath.slice(
@@ -101,7 +75,11 @@ export const makePatch = (
10175
tmpRepoPackageJsonPath,
10276
JSON.stringify({
10377
dependencies: {
104-
[packageDetails.name]: packageVersionSpecifier || packageVersion,
78+
[packageDetails.name]: getPackageResolution({
79+
packageDetails,
80+
packageManager,
81+
appPath,
82+
}),
10583
},
10684
resolutions: resolveRelativeFileDependencies(
10785
appPath,
@@ -110,6 +88,11 @@ export const makePatch = (
11088
}),
11189
)
11290

91+
const packageVersion = require(join(
92+
resolve(packageDetails.path),
93+
"package.json",
94+
)).version as string
95+
11396
if (packageManager === "yarn") {
11497
console.info(
11598
grey("•"),

tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"compilerOptions": {
33
"target": "es5",
44
"module": "commonjs",
5-
"lib": ["es2015"],
5+
"lib": ["es2015", "es2016", "es2017"],
66
"strict": true,
77
"esModuleInterop": true,
88
"outDir": "dist",

typings/@yarnpkg/lockfile.d.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
declare module "@yarnpkg/lockfile" {
2+
export function parse(
3+
s: string,
4+
): {
5+
type: "success" | "error"
6+
object: {
7+
[identifier: string]: {
8+
resolved?: string
9+
version: string
10+
}
11+
}
12+
}
13+
}

yarn.lock

+6
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@
7474
version "0.0.33"
7575
resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.0.33.tgz#1073c4bc824754ae3d10cfab88ab0237ba964e4d"
7676

77+
"@yarnpkg/lockfile@^1.1.0":
78+
version "1.1.0"
79+
resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
80+
integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
81+
7782
abab@^2.0.0:
7883
version "2.0.0"
7984
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f"
@@ -3390,6 +3395,7 @@ path-key@^1.0.0:
33903395
path-key@^2.0.0, path-key@^2.0.1:
33913396
version "2.0.1"
33923397
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
3398+
integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
33933399

33943400
path-parse@^1.0.5:
33953401
version "1.0.5"

0 commit comments

Comments
 (0)