forked from ds300/patch-package
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmakePatch.ts
296 lines (257 loc) Β· 8.31 KB
/
makePatch.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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
import chalk from "chalk"
import { join, dirname, resolve } from "./path"
import { spawnSafeSync } from "./spawnSafe"
import { PackageManager } from "./detectPackageManager"
import { removeIgnoredFiles } from "./filterFiles"
import {
writeFileSync,
existsSync,
mkdirSync,
unlinkSync,
mkdirpSync,
} from "fs-extra"
import { sync as rimraf } from "rimraf"
import { copySync } from "fs-extra"
import { dirSync } from "tmp"
import { getPatchFiles } from "./patchFs"
import {
getPatchDetailsFromCliString,
getPackageDetailsFromPatchFilename,
} from "./PackageDetails"
import { resolveRelativeFileDependencies } from "./resolveRelativeFileDependencies"
import { getPackageResolution } from "./getPackageResolution"
import { parsePatchFile } from "./patch/parse"
import { gzipSync } from "zlib"
function printNoPackageFoundError(
packageName: string,
packageJsonPath: string,
) {
console.error(
`No such package ${packageName}
File not found: ${packageJsonPath}`,
)
}
export function makePatch({
packagePathSpecifier,
appPath,
packageManager,
includePaths,
excludePaths,
patchDir,
}: {
packagePathSpecifier: string
appPath: string
packageManager: PackageManager
includePaths: RegExp
excludePaths: RegExp
patchDir: string
}) {
const packageDetails = getPatchDetailsFromCliString(packagePathSpecifier)
if (!packageDetails) {
console.error("No such package", packagePathSpecifier)
return
}
const appPackageJson = require(join(appPath, "package.json"))
const packagePath = join(appPath, packageDetails.path)
const packageJsonPath = join(packagePath, "package.json")
if (!existsSync(packageJsonPath)) {
printNoPackageFoundError(packagePathSpecifier, packageJsonPath)
process.exit(1)
}
const tmpRepo = dirSync({ unsafeCleanup: true })
const tmpRepoPackagePath = join(tmpRepo.name, packageDetails.path)
const tmpRepoNpmRoot = tmpRepoPackagePath.slice(
0,
-`/node_modules/${packageDetails.name}`.length,
)
const tmpRepoPackageJsonPath = join(tmpRepoNpmRoot, "package.json")
try {
const patchesDir = resolve(join(appPath, patchDir))
console.info(chalk.grey("β’"), "Creating temporary folder")
// make a blank package.json
mkdirpSync(tmpRepoNpmRoot)
writeFileSync(
tmpRepoPackageJsonPath,
JSON.stringify({
dependencies: {
[packageDetails.name]: getPackageResolution({
packageDetails,
packageManager,
appPath,
}),
},
resolutions: resolveRelativeFileDependencies(
appPath,
appPackageJson.resolutions || {},
),
}),
)
const packageVersion = require(join(
resolve(packageDetails.path),
"package.json",
)).version as string
// copy .npmrc/.yarnrc in case packages are hosted in private registry
[".npmrc", ".yarnrc"].forEach(rcFile => {
const rcPath = join(appPath, rcFile)
if (existsSync(rcPath)) {
copySync(rcPath, join(tmpRepo.name, rcFile))
}
})
if (packageManager === "yarn") {
console.info(
chalk.grey("β’"),
`Installing ${packageDetails.name}@${packageVersion} with yarn`,
)
try {
// try first without ignoring scripts in case they are required
// this works in 99.99% of cases
spawnSafeSync(`yarn`, ["install", "--ignore-engines"], {
cwd: tmpRepoNpmRoot,
logStdErrOnError: false,
})
} catch (e) {
// try again while ignoring scripts in case the script depends on
// an implicit context which we havn't reproduced
spawnSafeSync(
`yarn`,
["install", "--ignore-engines", "--ignore-scripts"],
{
cwd: tmpRepoNpmRoot,
},
)
}
} else {
console.info(
chalk.grey("β’"),
`Installing ${packageDetails.name}@${packageVersion} with npm`,
)
try {
// try first without ignoring scripts in case they are required
// this works in 99.99% of cases
spawnSafeSync(`npm`, ["i"], {
cwd: tmpRepoNpmRoot,
logStdErrOnError: false,
})
} catch (e) {
// try again while ignoring scripts in case the script depends on
// an implicit context which we havn't reproduced
spawnSafeSync(`npm`, ["i", "--ignore-scripts"], {
cwd: tmpRepoNpmRoot,
})
}
}
const git = (...args: string[]) =>
spawnSafeSync("git", args, {
cwd: tmpRepo.name,
env: { ...process.env, HOME: tmpRepo.name },
maxBuffer: 1024 * 1024 * 100,
})
// remove nested node_modules just to be safe
rimraf(join(tmpRepoPackagePath, "node_modules"))
// remove .git just to be safe
rimraf(join(tmpRepoPackagePath, ".git"))
// commit the package
console.info(chalk.grey("β’"), "Diffing your files with clean files")
writeFileSync(join(tmpRepo.name, ".gitignore"), "!/node_modules\n\n")
git("init")
git("config", "--local", "user.name", "patch-package")
git("config", "--local", "user.email", "[email protected]")
// remove ignored files first
removeIgnoredFiles(tmpRepoPackagePath, includePaths, excludePaths)
git("add", "-f", packageDetails.path)
git("commit", "--allow-empty", "-m", "init")
// replace package with user's version
rimraf(tmpRepoPackagePath)
copySync(packagePath, tmpRepoPackagePath)
// remove nested node_modules just to be safe
rimraf(join(tmpRepoPackagePath, "node_modules"))
// remove .git just to be safe
rimraf(join(tmpRepoPackagePath, "node_modules"))
// also remove ignored files like before
removeIgnoredFiles(tmpRepoPackagePath, includePaths, excludePaths)
// stage all files
git("add", "-f", packageDetails.path)
// get diff of changes
const diffResult = git(
"diff",
"--cached",
"--no-color",
"--ignore-space-at-eol",
"--no-ext-diff",
)
if (diffResult.stdout.length === 0) {
console.warn(
`βοΈ Not creating patch file for package '${packagePathSpecifier}'`,
)
console.warn(`βοΈ There don't appear to be any changes.`)
process.exit(1)
return
}
try {
parsePatchFile(diffResult.stdout.toString())
} catch (e) {
if (
(e as Error).message.includes("Unexpected file mode string: 120000")
) {
console.error(`
βοΈ ${chalk.red.bold("ERROR")}
Your changes involve creating symlinks. patch-package does not yet support
symlinks.
οΈPlease use ${chalk.bold("--include")} and/or ${chalk.bold(
"--exclude",
)} to narrow the scope of your patch if
this was unintentional.
`)
} else {
const outPath = "./patch-package-error.json.gz"
writeFileSync(
outPath,
gzipSync(
JSON.stringify({
error: { message: e.message, stack: e.stack },
patch: diffResult.stdout.toString(),
}),
),
)
console.error(`
βοΈ ${chalk.red.bold("ERROR")}
patch-package was unable to read the patch-file made by git. This should not
happen.
A diagnostic file was written to
${outPath}
Please attach it to a github issue
https://github.com/ds300/patch-package/issues/new?title=New+patch+parse+failed&body=Please+attach+the+diagnostic+file+by+dragging+it+into+here+π
Note that this diagnostic file will contain code from the package you were
attempting to patch.
`)
}
process.exit(1)
return
}
const packageNames = packageDetails.packageNames
.map(name => name.replace(/\//g, "+"))
.join("++")
// maybe delete existing
getPatchFiles(patchDir).forEach(filename => {
const deets = getPackageDetailsFromPatchFilename(filename)
if (deets && deets.path === packageDetails.path) {
unlinkSync(join(patchDir, filename))
}
})
const patchFileName = `${packageNames}+${packageVersion}.patch`
const patchPath = join(patchesDir, patchFileName)
if (!existsSync(dirname(patchPath))) {
// scoped package
mkdirSync(dirname(patchPath))
}
writeFileSync(patchPath, diffResult.stdout)
console.log(
`${chalk.green("β")} Created file ${join(patchDir, patchFileName)}`,
)
} catch (e) {
console.error(e)
throw e
} finally {
tmpRepo.removeCallback()
}
}