Skip to content

Commit 19fae76

Browse files
arturovtthePunderWoman
authored andcommitted
fix(zone.js): patch fs.realpath.native as macrotask (#54208)
This commit updates the implementation of the zone.js `fs` patch to restore the implementation of `realpath.native` and patches it as a macrotask, along with other functions of the `fs` package. This is the only nested function that must be patched. Closes: #45546 PR Close #54208
1 parent f8c02b6 commit 19fae76

2 files changed

Lines changed: 97 additions & 14 deletions

File tree

packages/zone.js/lib/node/fs.ts

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {patchMacroTask} from '../common/utils';
9+
import {patchMacroTask, zoneSymbol} from '../common/utils';
1010

11-
Zone.__load_patch('fs', () => {
11+
Zone.__load_patch('fs', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
1212
let fs: any;
1313
try {
1414
fs = require('fs');
1515
} catch (err) {
1616
}
1717

18+
if (!fs) return;
19+
1820
// watch, watchFile, unwatchFile has been patched
1921
// because EventEmitter has been patched
2022
const TO_PATCH_MACROTASK_METHODS = [
@@ -25,17 +27,28 @@ Zone.__load_patch('fs', () => {
2527
'symlink', 'truncate', 'unlink', 'utimes', 'write', 'writeFile',
2628
];
2729

28-
if (fs) {
29-
TO_PATCH_MACROTASK_METHODS.filter(name => !!fs[name] && typeof fs[name] === 'function')
30-
.forEach(name => {
31-
patchMacroTask(fs, name, (self: any, args: any[]) => {
32-
return {
33-
name: 'fs.' + name,
34-
args: args,
35-
cbIdx: args.length > 0 ? args.length - 1 : -1,
36-
target: self
37-
};
38-
});
30+
TO_PATCH_MACROTASK_METHODS.filter(name => !!fs[name] && typeof fs[name] === 'function')
31+
.forEach(name => {
32+
patchMacroTask(fs, name, (self: any, args: any[]) => {
33+
return {
34+
name: 'fs.' + name,
35+
args: args,
36+
cbIdx: args.length > 0 ? args.length - 1 : -1,
37+
target: self
38+
};
3939
});
40+
});
41+
42+
const realpathOriginalDelegate = fs.realpath?.[api.symbol('OriginalDelegate')];
43+
// This is the only specific method that should be additionally patched because the previous
44+
// `patchMacroTask` has overridden the `realpath` function and its `native` property.
45+
if (realpathOriginalDelegate?.native) {
46+
fs.realpath.native = realpathOriginalDelegate.native;
47+
patchMacroTask(fs.realpath, 'native', (self, args) => ({
48+
args,
49+
target: self,
50+
cbIdx: args.length > 0 ? args.length - 1 : -1,
51+
name: 'fs.realpath.native',
52+
}));
4053
}
4154
});

packages/zone.js/test/node/fs.spec.ts

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {closeSync, exists, fstatSync, openSync, read, unlink, unlinkSync, unwatchFile, watch, watchFile, write, writeFile} from 'fs';
9+
import {closeSync, exists, fstatSync, openSync, read, realpath, unlink, unlinkSync, unwatchFile, watch, watchFile, write, writeFile} from 'fs';
1010
import url from 'url';
1111
import util from 'util';
1212

@@ -41,6 +41,50 @@ describe('nodejs file system', () => {
4141
});
4242
});
4343
});
44+
45+
it('has patched realpath as macroTask', (done) => {
46+
const testZoneSpec = {
47+
name: 'test',
48+
onScheduleTask: (delegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, task: Task):
49+
Task => {
50+
return delegate.scheduleTask(targetZone, task);
51+
}
52+
};
53+
const testZone = Zone.current.fork(testZoneSpec);
54+
spyOn(testZoneSpec, 'onScheduleTask').and.callThrough();
55+
testZone.run(() => {
56+
realpath('testfile', () => {
57+
expect(Zone.current).toBe(testZone);
58+
expect(testZoneSpec.onScheduleTask).toHaveBeenCalled();
59+
done();
60+
});
61+
});
62+
});
63+
64+
// https://github.com/angular/angular/issues/45546
65+
// Note that this is intentionally marked with `xit` because `realpath.native`
66+
// is patched by Bazel's `node_patches.js` and doesn't allow further patching
67+
// of `realpath.native` in unit tests. Essentially, there's no original delegate
68+
// for `realpath` because it's also patched. The code below functions correctly
69+
// in the actual production environment.
70+
xit('has patched realpath.native as macroTask', (done) => {
71+
const testZoneSpec = {
72+
name: 'test',
73+
onScheduleTask: (delegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, task: Task):
74+
Task => {
75+
return delegate.scheduleTask(targetZone, task);
76+
}
77+
};
78+
const testZone = Zone.current.fork(testZoneSpec);
79+
spyOn(testZoneSpec, 'onScheduleTask').and.callThrough();
80+
testZone.run(() => {
81+
realpath.native('testfile', () => {
82+
expect(Zone.current).toBe(testZone);
83+
expect(testZoneSpec.onScheduleTask).toHaveBeenCalled();
84+
done();
85+
});
86+
});
87+
});
4488
});
4589

4690
describe('watcher related methods test', () => {
@@ -108,6 +152,32 @@ describe('util.promisify', () => {
108152
});
109153
});
110154

155+
it('fs.realpath should work with util.promisify', (done: DoneFn) => {
156+
const promisifyRealpath = util.promisify(realpath);
157+
promisifyRealpath(currentFile)
158+
.then(
159+
r => {
160+
expect(r).toBeDefined();
161+
done();
162+
},
163+
err => {
164+
fail(`should not be here with error: ${err}`);
165+
});
166+
});
167+
168+
it('fs.realpath.native should work with util.promisify', (done: DoneFn) => {
169+
const promisifyRealpathNative = util.promisify(realpath.native);
170+
promisifyRealpathNative(currentFile)
171+
.then(
172+
r => {
173+
expect(r).toBeDefined();
174+
done();
175+
},
176+
err => {
177+
fail(`should not be here with error: ${err}`);
178+
});
179+
});
180+
111181
it('fs.read should work with util.promisify', (done: DoneFn) => {
112182
const promisifyRead = util.promisify(read);
113183
const fd = openSync(currentFile, 'r');

0 commit comments

Comments
 (0)