-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
VirtualFS.ts
122 lines (91 loc) Β· 3.65 KB
/
VirtualFS.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
import {FakeFS, ExtractHintOptions} from './FakeFS';
import {NodeFS} from './NodeFS';
import {ProxiedFS} from './ProxiedFS';
import {Filename, PortablePath, ppath} from './path';
const NUMBER_REGEXP = /^[0-9]+$/;
// $0: full path
// $1: virtual folder
// $2: virtual segment
// $3: hash
// $4: depth
// $5: subpath
const VIRTUAL_REGEXP = /^(\/(?:[^/]+\/)*?(?:\$\$virtual|__virtual__))((?:\/((?:[^/]+-)?[a-f0-9]+)(?:\/([^/]+))?)?((?:\/.*)?))$/;
const VALID_COMPONENT = /^([^/]+-)?[a-f0-9]+$/;
export type VirtualFSOptions = {
baseFs?: FakeFS<PortablePath>;
folderName?: Filename;
};
export class VirtualFS extends ProxiedFS<PortablePath, PortablePath> {
protected readonly baseFs: FakeFS<PortablePath>;
static makeVirtualPath(base: PortablePath, component: Filename, to: PortablePath) {
if (ppath.basename(base) !== `__virtual__`)
throw new Error(`Assertion failed: Virtual folders must be named "__virtual__"`);
if (!ppath.basename(component).match(VALID_COMPONENT))
throw new Error(`Assertion failed: Virtual components must be ended by an hexadecimal hash`);
// Obtains the relative distance between the virtual path and its actual target
const target = ppath.relative(ppath.dirname(base), to);
const segments = target.split(`/`);
// Counts how many levels we need to go back to start applying the rest of the path
let depth = 0;
while (depth < segments.length && segments[depth] === `..`)
depth += 1;
const finalSegments = segments.slice(depth) as Array<Filename>;
const fullVirtualPath = ppath.join(base, component, String(depth) as Filename, ...finalSegments);
return fullVirtualPath;
}
static resolveVirtual(p: PortablePath): PortablePath {
const match = p.match(VIRTUAL_REGEXP);
if (!match || (!match[3] && match[5]))
return p;
const target = ppath.dirname(match[1] as PortablePath);
if (!match[3] || !match[4])
return target;
const isnum = NUMBER_REGEXP.test(match[4]);
if (!isnum)
return p;
const depth = Number(match[4]);
const backstep = `../`.repeat(depth) as PortablePath;
const subpath = (match[5] || `.`) as PortablePath;
return VirtualFS.resolveVirtual(ppath.join(target, backstep, subpath));
}
constructor({baseFs = new NodeFS()}: VirtualFSOptions = {}) {
super(ppath);
this.baseFs = baseFs;
}
getExtractHint(hints: ExtractHintOptions) {
return this.baseFs.getExtractHint(hints);
}
getRealPath() {
return this.baseFs.getRealPath();
}
realpathSync(p: PortablePath) {
const match = p.match(VIRTUAL_REGEXP);
if (!match)
return this.baseFs.realpathSync(p);
if (!match[5])
return p;
const realpath = this.baseFs.realpathSync(this.mapToBase(p));
return VirtualFS.makeVirtualPath(match[1] as PortablePath, match[3] as Filename, realpath);
}
async realpathPromise(p: PortablePath) {
const match = p.match(VIRTUAL_REGEXP);
if (!match)
return await this.baseFs.realpathPromise(p);
if (!match[5])
return p;
const realpath = await this.baseFs.realpathPromise(this.mapToBase(p));
return VirtualFS.makeVirtualPath(match[1] as PortablePath, match[3] as Filename, realpath);
}
mapToBase(p: PortablePath): PortablePath {
if (p === ``)
return p;
if (this.pathUtils.isAbsolute(p))
return VirtualFS.resolveVirtual(p);
const resolvedRoot = VirtualFS.resolveVirtual(this.baseFs.resolve(PortablePath.dot));
const resolvedP = VirtualFS.resolveVirtual(this.baseFs.resolve(p));
return ppath.relative(resolvedRoot, resolvedP) || PortablePath.dot;
}
mapFromBase(p: PortablePath) {
return p;
}
}