Skip to content

Commit 0676295

Browse files
committed
make work with pnpm and git, use pnpm-lock.yaml
1 parent 5c2c92b commit 0676295

File tree

12 files changed

+171
-76
lines changed

12 files changed

+171
-76
lines changed

integration-tests/runIntegrationTest.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { resolveRelativeFileDependencies } from "../src/resolveRelativeFileDepen
77
export const patchPackageTarballPath = resolve(
88
fs
99
.readdirSync(".")
10-
.filter(nm => nm.match(/^patch-package\.test\.\d+\.tgz$/))[0],
10+
.filter((nm) => nm.match(/^patch-package\.test\.\d+\.tgz$/))[0],
1111
)
1212

1313
export function runIntegrationTest({
@@ -64,7 +64,7 @@ export function runIntegrationTest({
6464
expect(snapshots && snapshots.length).toBeTruthy()
6565
})
6666
if (snapshots) {
67-
snapshots.forEach(snapshot => {
67+
snapshots.forEach((snapshot) => {
6868
const snapshotDescriptionMatch = snapshot.match(/SNAPSHOT: (.*)/)
6969
if (snapshotDescriptionMatch) {
7070
it(snapshotDescriptionMatch[1], () => {

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"@types/tmp": "^0.0.34",
6262
"husky": "^1.3.1",
6363
"jest": "^24.5.0",
64+
"js-yaml": "^4.0.0",
6465
"lint-staged": "^8.1.5",
6566
"np": "^7.4.0",
6667
"prettier": "^2.2.1",

property-based-tests/executeTestCase.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,32 +24,31 @@ jest.mock("fs-extra", () => {
2424
setWorkingFiles,
2525
getWorkingFiles,
2626
ensureDirSync: jest.fn(),
27-
readFileSync: jest.fn(path => getWorkingFiles()[path].contents),
27+
readFileSync: jest.fn((filePath) => getWorkingFiles()[filePath].contents),
2828
writeFileSync: jest.fn(
29-
(path: string, contents: string, opts?: { mode?: number }) => {
30-
getWorkingFiles()[path] = {
29+
(filePath: string, contents: string, opts?: { mode?: number }) => {
30+
getWorkingFiles()[filePath] = {
3131
contents,
3232
mode: opts && typeof opts.mode === "number" ? opts.mode : 0o644,
3333
}
3434
},
3535
),
36-
unlinkSync: jest.fn(path => delete getWorkingFiles()[path]),
36+
unlinkSync: jest.fn((filePath) => delete getWorkingFiles()[filePath]),
3737
moveSync: jest.fn((from, to) => {
3838
getWorkingFiles()[to] = getWorkingFiles()[from]
3939
delete getWorkingFiles()[from]
4040
}),
41-
statSync: jest.fn(path => getWorkingFiles()[path]),
42-
chmodSync: jest.fn((path, mode) => {
43-
const { contents } = getWorkingFiles()[path]
44-
getWorkingFiles()[path] = { contents, mode }
41+
statSync: jest.fn((filePath) => getWorkingFiles()[filePath]),
42+
chmodSync: jest.fn((filePath, mode) => {
43+
const { contents } = getWorkingFiles()[filePath]
44+
getWorkingFiles()[filePath] = { contents, mode }
4545
}),
4646
}
4747
})
4848

4949
function writeFiles(cwd: string, files: Files): void {
5050
const mkdirpSync = require("fs-extra/lib/mkdirs/index.js").mkdirpSync
51-
const writeFileSync = require("fs").writeFileSync
52-
Object.keys(files).forEach(filePath => {
51+
Object.keys(files).forEach((filePath) => {
5352
if (!filePath.startsWith(".git/")) {
5453
mkdirpSync(path.join(cwd, path.dirname(filePath)))
5554
writeFileSync(path.join(cwd, filePath), files[filePath].contents, {
@@ -62,7 +61,7 @@ function writeFiles(cwd: string, files: Files): void {
6261
function removeLeadingSpaceOnBlankLines(patchFileContents: string): string {
6362
return patchFileContents
6463
.split("\n")
65-
.map(line => (line === " " ? "" : line))
64+
.map((line) => (line === " " ? "" : line))
6665
.join("\n")
6766
}
6867

property-based-tests/testCases.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ function insertLinesIntoFile(file: File): File {
121121
function getUniqueFilename(files: Files) {
122122
let filename = makeFileName()
123123
const ks = Object.keys(files)
124-
while (ks.some(k => k.startsWith(filename))) {
124+
while (ks.some((k) => k.startsWith(filename))) {
125125
filename = makeFileName()
126126
}
127127
return filename

src/detectPackageManager.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import chalk from "chalk"
44
import process from "process"
55
import findWorkspaceRoot from "find-yarn-workspace-root"
66

7-
export type PackageManager = "yarn" | "npm" | "npm-shrinkwrap"
7+
export type PackageManager = "yarn" | "npm" | "npm-shrinkwrap" | "pnpm"
88

99
function printNoYarnLockfileError() {
1010
console.error(`
@@ -64,6 +64,8 @@ export const detectPackageManager = (
6464
}
6565
} else if (yarnLockExists || findWorkspaceRoot()) {
6666
return "yarn"
67+
} else if (fs.existsSync(join(appRootPath, "pnpm-lock.yaml"))) {
68+
return "pnpm"
6769
} else {
6870
printNoLockfilesError()
6971
process.exit(1)

src/filterFiles.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ export function removeIgnoredFiles(
88
excludePaths: RegExp,
99
) {
1010
klawSync(dir, { nodir: true })
11-
.map(item => item.path.slice(`${dir}/`.length))
11+
.map((item) => item.path.slice(`${dir}/`.length))
1212
.filter(
13-
relativePath =>
13+
(relativePath) =>
1414
!relativePath.match(includePaths) || relativePath.match(excludePaths),
1515
)
16-
.forEach(relativePath => removeSync(join(dir, relativePath)))
16+
.forEach((relativePath) => removeSync(join(dir, relativePath)))
1717
}

src/getPackageResolution.ts

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import { readFileSync, existsSync } from "fs-extra"
55
import { parse as parseYarnLockFile } from "@yarnpkg/lockfile"
66
import findWorkspaceRoot from "find-yarn-workspace-root"
77
import { getPackageVersion } from "./getPackageVersion"
8+
import { execSync } from "child_process"
9+
10+
const isVerbose = true // TODO expose to CLI
811

912
export function getPackageResolution({
1013
packageDetails,
@@ -56,21 +59,69 @@ export function getPackageResolution({
5659
console.warn(
5760
`Ambigious lockfile entries for ${packageDetails.pathSpecifier}. Using version ${installedVersion}`,
5861
)
59-
return installedVersion
62+
return { version: installedVersion }
6063
}
6164

6265
if (resolutions[0]) {
63-
return resolutions[0]
66+
return { version: resolutions[0] }
6467
}
6568

6669
const resolution = entries[0][0].slice(packageDetails.name.length + 1)
6770

6871
// resolve relative file path
6972
if (resolution.startsWith("file:.")) {
70-
return `file:${resolve(appPath, resolution.slice("file:".length))}`
73+
return {
74+
version: `file:${resolve(appPath, resolution.slice("file:".length))}`,
75+
}
7176
}
72-
73-
return resolution
77+
return { version: resolution }
78+
} else if (packageManager === "pnpm") {
79+
const lockfile = require("js-yaml").load(
80+
require("fs").readFileSync(join(appPath, "pnpm-lock.yaml"), "utf8"),
81+
)
82+
let resolvedVersion =
83+
(lockfile.dependencies && lockfile.dependencies[packageDetails.name]) ||
84+
(lockfile.devDependencies &&
85+
lockfile.devDependencies[packageDetails.name])
86+
if (resolvedVersion.startsWith("link:")) {
87+
const localPath = resolve(resolvedVersion.slice(5))
88+
if (isVerbose) {
89+
console.log(`pnpm installed ${packageDetails.name} from ${localPath}`)
90+
}
91+
if (existsSync(localPath + "/.git")) {
92+
// we hope that the originCommit will be available for future downloads
93+
// otherwise our patch will not work ...
94+
// ideally, we would use the last stable release before originCommit from npm or github
95+
function exec(cmd: string) {
96+
return execSync(cmd, {
97+
cwd: localPath,
98+
windowsHide: true,
99+
encoding: "utf8",
100+
}).trim()
101+
}
102+
const originUrl = exec("git remote get-url origin")
103+
const originCommit = exec("git rev-parse origin/HEAD") // npm needs the long commit hash
104+
resolvedVersion = `git+${originUrl}#${originCommit}`
105+
if (isVerbose) {
106+
console.log(
107+
`using ${packageDetails.name} version ${resolvedVersion} from git origin/HEAD in ${localPath}`,
108+
)
109+
}
110+
return { version: resolvedVersion, originCommit }
111+
}
112+
const pkgJson = localPath + "/package.json"
113+
if (existsSync(pkgJson)) {
114+
resolvedVersion = require(pkgJson).version
115+
console.warn(
116+
`warning: using ${packageDetails.name} version ${resolvedVersion} from ${pkgJson}`,
117+
)
118+
return { version: resolvedVersion }
119+
}
120+
}
121+
if (isVerbose) {
122+
console.log(`using ${packageDetails.name} version ${resolvedVersion}`)
123+
}
124+
return { version: resolvedVersion }
74125
} else {
75126
const lockfile = require(join(
76127
appPath,
@@ -91,7 +142,7 @@ export function getPackageResolution({
91142
entry.dependencies && packageDetails.name in entry.dependencies,
92143
)
93144
const pkg = relevantStackEntry.dependencies[packageDetails.name]
94-
return pkg.resolved || pkg.from || pkg.version
145+
return { version: pkg.resolved || pkg.from || pkg.version }
95146
}
96147
}
97148

src/makePatch.ts

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
unlinkSync,
1111
mkdirpSync,
1212
realpathSync,
13+
renameSync,
1314
} from "fs-extra"
1415
import { sync as rimraf } from "rimraf"
1516
import { copySync } from "fs-extra"
@@ -30,6 +31,8 @@ import {
3031
openIssueCreationLink,
3132
} from "./createIssue"
3233

34+
const isVerbose = true // TODO expose to CLI
35+
3336
function printNoPackageFoundError(
3437
packageName: string,
3538
packageJsonPath: string,
@@ -87,17 +90,19 @@ export function makePatch({
8790

8891
console.info(chalk.grey("•"), "Creating temporary folder")
8992

93+
const resolvedVersion = getPackageResolution({
94+
packageDetails,
95+
packageManager,
96+
appPath,
97+
})
98+
9099
// make a blank package.json
91100
mkdirpSync(tmpRepoNpmRoot)
92101
writeFileSync(
93102
tmpRepoPackageJsonPath,
94103
JSON.stringify({
95104
dependencies: {
96-
[packageDetails.name]: getPackageResolution({
97-
packageDetails,
98-
packageManager,
99-
appPath,
100-
}),
105+
[packageDetails.name]: resolvedVersion.version,
101106
},
102107
resolutions: resolveRelativeFileDependencies(
103108
appPath,
@@ -106,9 +111,10 @@ export function makePatch({
106111
}),
107112
)
108113

109-
const packageVersion = getPackageVersion(
110-
join(resolve(packageDetails.path), "package.json"),
111-
)
114+
// originCommit is more precise than pkg.version
115+
const packageVersion =
116+
resolvedVersion.originCommit ||
117+
getPackageVersion(join(resolve(packageDetails.path), "package.json"))
112118

113119
// copy .npmrc/.yarnrc in case packages are hosted in private registry
114120
// tslint:disable-next-line:align
@@ -143,26 +149,42 @@ export function makePatch({
143149
)
144150
}
145151
} else {
152+
const npmCmd = packageManager === "pnpm" ? "pnpm" : "npm"
146153
console.info(
147154
chalk.grey("•"),
148-
`Installing ${packageDetails.name}@${packageVersion} with npm`,
155+
`Installing ${packageDetails.name}@${packageVersion} with ${npmCmd}`,
149156
)
150157
try {
151158
// try first without ignoring scripts in case they are required
152159
// this works in 99.99% of cases
153-
spawnSafeSync(`npm`, ["i", "--force"], {
160+
if (isVerbose) {
161+
console.log(`run "${npmCmd} install --force" in ${tmpRepoNpmRoot}`)
162+
}
163+
spawnSafeSync(npmCmd, ["install", "--force"], {
154164
cwd: tmpRepoNpmRoot,
155165
logStdErrOnError: false,
156-
stdio: "ignore",
166+
stdio: isVerbose ? "inherit" : "ignore",
157167
})
158168
} catch (e) {
159169
// try again while ignoring scripts in case the script depends on
160170
// an implicit context which we havn't reproduced
161-
spawnSafeSync(`npm`, ["i", "--ignore-scripts", "--force"], {
171+
if (isVerbose) {
172+
console.log(
173+
`run "${npmCmd} install --ignore-scripts --force" in ${tmpRepoNpmRoot}`,
174+
)
175+
}
176+
spawnSafeSync(npmCmd, ["install", "--ignore-scripts", "--force"], {
162177
cwd: tmpRepoNpmRoot,
163-
stdio: "ignore",
178+
stdio: isVerbose ? "inherit" : "ignore",
164179
})
165180
}
181+
if (packageManager === "pnpm") {
182+
// workaround for `git diff`: replace symlink with hardlink
183+
const pkgPath = tmpRepoNpmRoot + "/node_modules/" + packageDetails.name
184+
const realPath = realpathSync(pkgPath)
185+
unlinkSync(pkgPath) // rm symlink
186+
renameSync(realPath, pkgPath)
187+
}
166188
}
167189

168190
const git = (...args: string[]) =>
@@ -193,8 +215,16 @@ export function makePatch({
193215
// replace package with user's version
194216
rimraf(tmpRepoPackagePath)
195217

218+
if (isVerbose) {
219+
console.log(`copy ${realpathSync(packagePath)} to ${tmpRepoPackagePath}`)
220+
}
221+
196222
// pnpm installs packages as symlinks, copySync would copy only the symlink
197-
copySync(realpathSync(packagePath), tmpRepoPackagePath)
223+
copySync(realpathSync(packagePath), tmpRepoPackagePath, {
224+
filter: (path) => {
225+
return path.indexOf("/node_modules/") === -1
226+
},
227+
})
198228

199229
// remove nested node_modules just to be safe
200230
rimraf(join(tmpRepoPackagePath, "node_modules"))
@@ -207,13 +237,23 @@ export function makePatch({
207237
// stage all files
208238
git("add", "-f", packageDetails.path)
209239

240+
// TODO allow to add more paths via CLI, to exclude cache files like 'test/stubs*/**'
241+
const ignorePaths = [
242+
"package-lock.json",
243+
"pnpm-lock.yaml",
244+
// 'test/stubs*/**',
245+
]
246+
210247
// get diff of changes
211248
const diffResult = git(
212249
"diff",
213250
"--cached",
214251
"--no-color",
215252
"--ignore-space-at-eol",
216253
"--no-ext-diff",
254+
...ignorePaths.map(
255+
(path) => `:(exclude,top)${packageDetails.path}/${path}`,
256+
),
217257
)
218258

219259
if (diffResult.stdout.length === 0) {

src/packageIsDevDependency.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,7 @@ export function packageIsDevDependency({
1414
return false
1515
}
1616
const { devDependencies } = require(packageJsonPath)
17-
return Boolean(devDependencies && devDependencies[packageDetails.packageNames[0]])
17+
return Boolean(
18+
devDependencies && devDependencies[packageDetails.packageNames[0]],
19+
)
1820
}

src/patch/apply.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export const executeEffects = (
77
effects: ParsedPatchFile,
88
{ dryRun }: { dryRun: boolean },
99
) => {
10-
effects.forEach(eff => {
10+
effects.forEach((eff) => {
1111
switch (eff.type) {
1212
case "file deletion":
1313
if (dryRun) {

0 commit comments

Comments
 (0)