@@ -8,18 +8,24 @@ const shelljs = require('shelljs');
8
8
const path = require ( 'path' ) ;
9
9
const fs = require ( 'fs' ) ;
10
10
11
+ /**
12
+ * Version of the post install patch. Needs to be incremented when patches
13
+ * have been added or removed.
14
+ */
15
+ const PATCH_VERSION = 1 ;
16
+
11
17
/** Path to the project directory. */
12
18
const projectDir = path . join ( __dirname , '../..' ) ;
13
19
20
+ /**
21
+ * Object that maps a given file path to a list of patches that need to be
22
+ * applied.
23
+ */
24
+ const PATCHES_PER_FILE = { } ;
25
+
14
26
shelljs . set ( '-e' ) ;
15
27
shelljs . cd ( projectDir ) ;
16
28
17
- // Do not apply postinstall patches when running "postinstall" outside. The
18
- // "generate_build_file.js" file indicates that we run in Bazel managed node modules.
19
- if ( ! shelljs . test ( '-e' , 'generate_build_file.js' ) ) {
20
- return ;
21
- }
22
-
23
29
// Workaround for https://github.com/angular/angular/issues/18810.
24
30
shelljs . exec ( 'ngc -p angular-tsconfig.json' ) ;
25
31
@@ -69,7 +75,7 @@ searchAndReplace(
69
75
const hasFlatModuleBundle = fs.existsSync(filePath.replace('.d.ts', '.metadata.json'));
70
76
if ((filePath.includes('node_modules/') || !hasFlatModuleBundle) && $1` ,
71
77
'node_modules/@angular/compiler-cli/src/transformers/compiler_host.js' ) ;
72
- shelljs . cat ( path . join ( __dirname , './flat_module_factory_resolution.patch' ) ) . exec ( 'patch -p0' ) ;
78
+ applyPatch ( path . join ( __dirname , './flat_module_factory_resolution.patch' ) ) ;
73
79
// The three replacements below ensure that metadata files can be read by NGC and
74
80
// that metadata files are collected as Bazel action inputs.
75
81
searchAndReplace (
@@ -90,7 +96,7 @@ searchAndReplace(
90
96
'node_modules/@angular/bazel/src/ng_module.bzl' ) ;
91
97
92
98
// Workaround for: https://github.com/bazelbuild/rules_nodejs/issues/1208.
93
- shelljs . cat ( path . join ( __dirname , './manifest_externs_hermeticity.patch' ) ) . exec ( 'patch -p0' ) ;
99
+ applyPatch ( path . join ( __dirname , './manifest_externs_hermeticity.patch' ) ) ;
94
100
95
101
// Workaround for using Ngcc with "--create-ivy-entry-points". This is a special
96
102
// issue for our repository since we want to run Ivy by default in the module resolution,
@@ -136,18 +142,67 @@ shelljs.rm('-rf', [
136
142
'node_modules/rxjs/Subscription.*' ,
137
143
] ) ;
138
144
145
+ // Apply all collected patches on a per-file basis. This is necessary because
146
+ // multiple edits might apply to the same file, and we only want to mark a given
147
+ // file as patched once all edits have been made.
148
+ Object . keys ( PATCHES_PER_FILE ) . forEach ( filePath => {
149
+ if ( hasFileBeenPatched ( filePath ) ) {
150
+ console . info ( 'File ' + filePath + ' is already patched. Skipping..' ) ;
151
+ return ;
152
+ }
153
+
154
+ let content = fs . readFileSync ( filePath , 'utf8' ) ;
155
+ const patchFunctions = PATCHES_PER_FILE [ filePath ] ;
156
+
157
+ console . info ( `Patching file ${ filePath } with ${ patchFunctions . length } edits..` ) ;
158
+ patchFunctions . forEach ( patchFn => content = patchFn ( content ) ) ;
159
+
160
+ fs . writeFileSync ( filePath , content , 'utf8' ) ;
161
+ writePatchMarker ( filePath ) ;
162
+ } ) ;
163
+
139
164
/**
140
- * Reads the specified file and replaces matches of the search expression
141
- * with the given replacement. Throws if no changes were made.
165
+ * Applies the given patch if not done already. Throws if the patch does
166
+ * not apply cleanly.
167
+ */
168
+ function applyPatch ( patchFile ) {
169
+ const patchMarkerFileName = `${ path . basename ( patchFile ) } .patch_marker` ;
170
+ const patchMarkerPath = path . join ( projectDir , 'node_modules/' , patchMarkerFileName ) ;
171
+
172
+ if ( hasFileBeenPatched ( patchMarkerPath ) ) {
173
+ return ;
174
+ }
175
+
176
+ writePatchMarker ( patchMarkerPath ) ;
177
+ shelljs . cat ( patchFile ) . exec ( 'patch -p0' ) ;
178
+ }
179
+
180
+ /**
181
+ * Schedules an edit where the specified file is read and its content replaced based on
182
+ * the given search expression and corresponding replacement. Throws if no changes were made
183
+ * and the patch has not been applied.
142
184
*/
143
185
function searchAndReplace ( search , replacement , relativeFilePath ) {
144
186
const filePath = path . join ( projectDir , relativeFilePath ) ;
145
- const originalContent = fs . readFileSync ( filePath , 'utf8' ) ;
146
- const newFileContent = originalContent . replace ( search , replacement ) ;
187
+ const fileEdits = PATCHES_PER_FILE [ filePath ] || ( PATCHES_PER_FILE [ filePath ] = [ ] ) ;
188
+
189
+ fileEdits . push ( originalContent => {
190
+ const newFileContent = originalContent . replace ( search , replacement ) ;
191
+ if ( originalContent === newFileContent ) {
192
+ throw Error ( `Could not perform replacement in: ${ filePath } .` ) ;
193
+ }
194
+ return newFileContent ;
195
+ } ) ;
196
+ }
147
197
148
- if ( originalContent === newFileContent ) {
149
- throw Error ( `Could not perform replacement in: ${ filePath } .` ) ;
150
- }
198
+ /** Marks the specified file as patched. */
199
+ function writePatchMarker ( filePath ) {
200
+ new shelljs . ShellString ( PATCH_VERSION ) . to ( `${ filePath } .patch_marker` ) ;
201
+ }
151
202
152
- fs . writeFileSync ( filePath , newFileContent , 'utf8' ) ;
203
+ /** Checks if the given file has been patched. */
204
+ function hasFileBeenPatched ( filePath ) {
205
+ const markerFilePath = `${ filePath } .patch_marker` ;
206
+ return shelljs . test ( '-e' , markerFilePath ) &&
207
+ shelljs . cat ( markerFilePath ) . toString ( ) . trim ( ) === `${ PATCH_VERSION } ` ;
153
208
}
0 commit comments