Skip to content

Commit 98a84e2

Browse files
author
David Sheldrick
committed
first working version of patch maker
1 parent 059cef6 commit 98a84e2

File tree

13 files changed

+225
-4
lines changed

13 files changed

+225
-4
lines changed

README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# patch-package
2+
3+
### When forking just won't work, patch it.
4+
5+
patch-package lets you easily fix bugs in (or add functionality to) packages in your
6+
`node_modules` folder and share the results with your team. You simply make the changes in situ,
7+
run `patch-package <package-name>` and patch-package will create a patch file
8+
for you to commit, which gets applied any time the contents of `node_modules` is updated by yarn/npm.
9+
10+
## Set-up
11+
12+
yarn add -D patch-package
13+
14+
In package.json
15+
16+
"scripts": {
17+
"prepare": "patch-package"
18+
}
19+
20+
## Usage
21+
22+
Make changes to the files of a particular module in your node_modules folder,
23+
e.g. react-native. Then run:
24+
25+
patch-package react-native
26+
27+
If this is the first
28+
time you've used `patch-package`, it will create a folder called `patches` in
29+
the root dir of your app. Inside will be a file called `react-native:0.44.0.patch`
30+
which is a diff between normal old react-native and your special version. Commit this and you and your team will enjoy the same changes from here on out.
31+
32+
Do exactly the same thing to update the patch file, or just delete it
33+
if you don't need the changes anymore.
34+
35+
## License
36+
37+
MIT

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,15 @@
3434
"devDependencies": {
3535
"@types/app-root-path": "^1.2.4",
3636
"@types/node": "^7.0.18",
37+
"@types/tmp": "^0.0.33",
3738
"jest": "^20.0.0",
3839
"ts-jest": "^20.0.1",
3940
"tslint": "^5.2.0",
4041
"typescript": "^2.3.2"
4142
},
4243
"dependencies": {
43-
"app-root-path": "^2.0.1"
44+
"app-root-path": "^2.0.1",
45+
"shell-escape": "^0.2.0",
46+
"tmp": "^0.0.31"
4447
}
4548
}

src/Npm.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { execSync as exec } from "child_process"
2+
import { PackageManager } from "./PackageManager"
3+
4+
export default class Npm implements PackageManager {
5+
constructor(public cwd: string) { }
6+
public add(packageName: string, version: string) {
7+
exec(`npm i ${packageName}@${version}`, { cwd: this.cwd })
8+
}
9+
}

src/PackageManager.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export interface PackageManager {
2+
add(packageName: string, version: string): void
3+
}

src/Yarn.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { execSync as exec } from "child_process"
2+
import { PackageManager } from "./PackageManager"
3+
4+
export default class Yarn implements PackageManager {
5+
constructor(public cwd: string) { }
6+
public add(packageName: string, version: string) {
7+
exec(`yarn add ${packageName}@${version}`, { cwd: this.cwd })
8+
}
9+
}

src/applyPatch.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { execSync as exec } from "child_process"
2+
3+
export default function applyPatch(patchFilePath: string, packageName: string) {
4+
try {
5+
exec("patch --forward -p1 -i " + patchFilePath)
6+
console.log(`Successfully patched ${packageName}`)
7+
} catch (e) {
8+
// patch cli tool has no way to fail gracefully if patch was already applied,
9+
// so to check, we need to try a dry-run of applying the patch in reverse, and
10+
// if that works it means the patch was already applied sucessfully. Otherwise
11+
// the patch just failed for some reason.
12+
exec("patch --reverse --dry-run -p1 -i " + patchFilePath)
13+
console.log(`Already patched ${packageName}`)
14+
}
15+
}

src/findPatches.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import * as fs from "fs"
2+
import * as path from "path"
3+
import { env } from "process"
4+
5+
export default function findPatchFiles(appPath: string): Array<{ patchFilePath: string, packageName: string }> {
6+
const patchesDirectory = path.join(appPath, "patches")
7+
if (!fs.existsSync(patchesDirectory)) {
8+
return []
9+
}
10+
return fs
11+
.readdirSync(patchesDirectory)
12+
.filter((filename) => filename.match(/^.+!!.+\.patch$/))
13+
.map((filename) => {
14+
const [packageName, version] = filename.slice(0, -6).split(":")
15+
const packageDir = path.join(appPath, "node_modules" + packageName)
16+
17+
if (!fs.exists(packageDir)) {
18+
console.warn(`Patch file found for package ${packageName} which is not present in node_modules/`)
19+
return null
20+
}
21+
22+
const packageJson = require(path.join(packageDir, "package.json"))
23+
24+
if (packageJson.version !== version) {
25+
console.warn(
26+
`Patch file for package ${packageName}:${version} found,`
27+
+ ` but node_modules/${packageName} has version ${packageJson.version}`,
28+
)
29+
console.warn(
30+
`Attempting to apply the patch anyway. Update the version number`
31+
+ ` in the patch filename ${path.join(patchesDirectory, filename)} to silence this warning.`,
32+
)
33+
}
34+
35+
return {
36+
patchFilePath: path.resolve(patchesDirectory, filename),
37+
packageName,
38+
}
39+
})
40+
.filter(Boolean) as any
41+
}

src/getAppRootPath.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import * as fs from "fs"
2+
import * as path from "path"
3+
import * as process from "process"
4+
5+
export default function getAppRootPath() {
6+
let cwd = process.cwd()
7+
while (!fs.existsSync(path.join(cwd, "package.json"))) {
8+
cwd = path.resolve(cwd, "../")
9+
}
10+
return cwd
11+
}

src/index.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,20 @@
1-
console.log("hello world")
1+
import { argv } from "process"
2+
import applyPatch from "./applyPatch"
3+
import findPatches from "./findPatches"
4+
import getAppRootPath from "./getAppRootPath"
5+
import makePatch from "./makePatch"
6+
7+
const appPath = getAppRootPath()
8+
if (argv.length === 2) {
9+
console.log("Applying patches...")
10+
findPatches(appPath)
11+
.forEach(({ patchFilePath, packageName }) => {
12+
applyPatch(patchFilePath, packageName)
13+
})
14+
} else {
15+
console.log("Making patches")
16+
const packageNames = [].slice.call(argv, 2)
17+
packageNames.forEach((packageName: string) => {
18+
makePatch(packageName, appPath)
19+
})
20+
}

src/makePatch.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { execSync as exec } from "child_process"
2+
import * as fs from "fs"
3+
import * as path from "path"
4+
import * as shellEscape from "shell-escape"
5+
import * as tmp from "tmp"
6+
import Npm from "./Npm"
7+
import { PackageManager } from "./PackageManager"
8+
import Yarn from "./Yarn"
9+
10+
export default function makePatch(packageName: string, appPath: string) {
11+
const packagePath = path.join(appPath, "node_modules", packageName)
12+
const packageJsonPath = path.join(packagePath, "package.json")
13+
if (!fs.existsSync(packageJsonPath)) {
14+
throw new Error(`Unable to find local ${packageName} package.json at ${packageJsonPath}`)
15+
}
16+
17+
const packageVersion = require(packageJsonPath).version
18+
19+
const tmpDir = tmp.dirSync({ unsafeCleanup: true })
20+
21+
try {
22+
const packageManager = getPackageManager(tmpDir.name)
23+
24+
packageManager.add(packageName, packageVersion)
25+
26+
exec(`git init`, { cwd: tmpDir.name })
27+
exec(shellEscape(["git", "add", "-f", path.join("node_modules", packageName)]), { cwd: tmpDir.name })
28+
exec(shellEscape(["cp", "-RL", packagePath, path.join(tmpDir.name, "node_modules")]))
29+
const patch = exec(`git diff`, { cwd: tmpDir.name }).toString()
30+
31+
const patchesDir = path.join(appPath, "patches")
32+
if (!fs.existsSync(patchesDir)) {
33+
fs.mkdirSync(patchesDir)
34+
}
35+
36+
const patchFileName = `${packageName}:${packageVersion}.patch`
37+
console.log(`Creating patch file ${patchFileName}`)
38+
fs.writeFileSync(path.join(patchesDir, patchFileName), patch)
39+
} finally {
40+
tmpDir.removeCallback()
41+
}
42+
}
43+
44+
function getPackageManager(cwd: string): PackageManager {
45+
try {
46+
exec("which yarn")
47+
return new Yarn(cwd)
48+
} catch (e) {
49+
return new Npm(cwd)
50+
}
51+
}

0 commit comments

Comments
 (0)