@@ -2,13 +2,19 @@ import chalk from "chalk"
2
2
import { existsSync } from "fs-extra"
3
3
import { posix } from "path"
4
4
import semver from "semver"
5
- import { PackageDetails } from "./PackageDetails"
5
+ import { hashFile } from "./hash"
6
+ import { PackageDetails , PatchedPackageDetails } from "./PackageDetails"
6
7
import { packageIsDevDependency } from "./packageIsDevDependency"
7
8
import { executeEffects } from "./patch/apply"
8
9
import { readPatch } from "./patch/read"
9
10
import { reversePatch } from "./patch/reverse"
10
11
import { getGroupedPatches } from "./patchFs"
11
12
import { join , relative , resolve } from "./path"
13
+ import {
14
+ getPatchApplicationState ,
15
+ PatchState ,
16
+ savePatchApplicationState ,
17
+ } from "./stateFile"
12
18
13
19
class PatchApplicationError extends Error {
14
20
constructor ( msg : string ) {
@@ -68,6 +74,20 @@ function getInstalledPackageVersion({
68
74
return result as string
69
75
}
70
76
77
+ function logPatchApplication ( patchDetails : PatchedPackageDetails ) {
78
+ const sequenceString =
79
+ patchDetails . sequenceNumber != null
80
+ ? ` (${ patchDetails . sequenceNumber } ${
81
+ patchDetails . sequenceName ? " " + patchDetails . sequenceName : ""
82
+ } )`
83
+ : ""
84
+ console . log (
85
+ `${ chalk . bold ( patchDetails . pathSpecifier ) } @${
86
+ patchDetails . version
87
+ } ${ sequenceString } ${ chalk . green ( "✔" ) } `,
88
+ )
89
+ }
90
+
71
91
export function applyPatchesForApp ( {
72
92
appPath,
73
93
reverse,
@@ -92,10 +112,42 @@ export function applyPatchesForApp({
92
112
const errors : string [ ] = [ ]
93
113
const warnings : string [ ] = [ ...groupedPatches . warnings ]
94
114
95
- for ( const [ pathSpecifier , details ] of Object . entries (
115
+ for ( const [ pathSpecifier , patches ] of Object . entries (
96
116
groupedPatches . pathSpecifierToPatchFiles ,
97
117
) ) {
98
- packageLoop: for ( const patchDetails of details ) {
118
+ const state =
119
+ patches . length > 1 ? getPatchApplicationState ( patches [ 0 ] ) : null
120
+ const unappliedPatches = patches . slice ( 0 )
121
+ const newState : PatchState [ ] | null = patches . length > 1 ? [ ] : null
122
+ // if there are multiple patches to apply, we can't rely on the reverse-patch-dry-run behavior to make this operation
123
+ // idempotent, so instead we need to check the state file to see whether we have already applied any of the patches
124
+ // todo: once this is battle tested we might want to use the same approach for single patches as well, but it's not biggie since the dry run thing is fast
125
+ if ( unappliedPatches && state ) {
126
+ for ( let i = 0 ; i < state . patches . length ; i ++ ) {
127
+ const patchThatWasApplied = state . patches [ i ]
128
+ const patchToApply = unappliedPatches [ 0 ]
129
+ const currentPatchHash = hashFile (
130
+ join ( appPath , patchDir , patchToApply . patchFilename ) ,
131
+ )
132
+ if ( patchThatWasApplied . patchContentHash === currentPatchHash ) {
133
+ // this patch was applied we can skip it
134
+ unappliedPatches . shift ( )
135
+ } else {
136
+ console . error (
137
+ chalk . red ( "Error:" ) ,
138
+ `The patches for ${ chalk . bold ( pathSpecifier ) } have changed.` ,
139
+ `You should reinstall your node_modules folder to make sure the package is up to date` ,
140
+ )
141
+ process . exit ( 1 )
142
+ }
143
+ }
144
+ }
145
+ if ( unappliedPatches . length === 0 ) {
146
+ // all patches have already been applied
147
+ patches . forEach ( logPatchApplication )
148
+ continue
149
+ }
150
+ packageLoop: for ( const patchDetails of patches ) {
99
151
try {
100
152
const { name, version, path, isDevOnly, patchFilename } = patchDetails
101
153
@@ -132,6 +184,11 @@ export function applyPatchesForApp({
132
184
cwd : process . cwd ( ) ,
133
185
} )
134
186
) {
187
+ newState ?. push ( {
188
+ patchFilename,
189
+ patchContentHash : hashFile ( join ( appPath , patchDir , patchFilename ) ) ,
190
+ didApply : true ,
191
+ } )
135
192
// yay patch was applied successfully
136
193
// print warning if version mismatch
137
194
if ( installedPackageVersion !== version ) {
@@ -145,19 +202,7 @@ export function applyPatchesForApp({
145
202
} ) ,
146
203
)
147
204
}
148
- const sequenceString =
149
- patchDetails . sequenceNumber != null
150
- ? ` (${ patchDetails . sequenceNumber } ${
151
- patchDetails . sequenceName
152
- ? " " + patchDetails . sequenceName
153
- : ""
154
- } )`
155
- : ""
156
- console . log (
157
- `${ chalk . bold (
158
- pathSpecifier ,
159
- ) } @${ version } ${ sequenceString } ${ chalk . green ( "✔" ) } `,
160
- )
205
+ logPatchApplication ( patchDetails )
161
206
} else if ( installedPackageVersion === version ) {
162
207
// completely failed to apply patch
163
208
// TODO: propagate useful error messages from patch application
@@ -203,6 +248,10 @@ export function applyPatchesForApp({
203
248
break packageLoop
204
249
}
205
250
}
251
+
252
+ if ( newState ) {
253
+ savePatchApplicationState ( patches [ 0 ] , newState )
254
+ }
206
255
}
207
256
208
257
for ( const warning of warnings ) {
0 commit comments