@@ -8,18 +8,24 @@ const shelljs = require('shelljs');
88const path = require ( 'path' ) ;
99const fs = require ( 'fs' ) ;
1010
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+
1117/** Path to the project directory. */
1218const projectDir = path . join ( __dirname , '../..' ) ;
1319
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+
1426shelljs . set ( '-e' ) ;
1527shelljs . cd ( projectDir ) ;
1628
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-
2329// Workaround for https://github.com/angular/angular/issues/18810.
2430shelljs . exec ( 'ngc -p angular-tsconfig.json' ) ;
2531
@@ -69,7 +75,7 @@ searchAndReplace(
6975 const hasFlatModuleBundle = fs.existsSync(filePath.replace('.d.ts', '.metadata.json'));
7076 if ((filePath.includes('node_modules/') || !hasFlatModuleBundle) && $1` ,
7177 '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' ) ) ;
7379// The three replacements below ensure that metadata files can be read by NGC and
7480// that metadata files are collected as Bazel action inputs.
7581searchAndReplace (
@@ -90,7 +96,7 @@ searchAndReplace(
9096 'node_modules/@angular/bazel/src/ng_module.bzl' ) ;
9197
9298// 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' ) ) ;
94100
95101// Workaround for using Ngcc with "--create-ivy-entry-points". This is a special
96102// issue for our repository since we want to run Ivy by default in the module resolution,
@@ -136,18 +142,67 @@ shelljs.rm('-rf', [
136142 'node_modules/rxjs/Subscription.*' ,
137143] ) ;
138144
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+
139164/**
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.
142184 */
143185function searchAndReplace ( search , replacement , relativeFilePath ) {
144186 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+ }
147197
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+ }
151202
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 } ` ;
153208}
0 commit comments