forked from coder/code-server
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrequirefs.ts
More file actions
168 lines (141 loc) · 4.34 KB
/
requirefs.ts
File metadata and controls
168 lines (141 loc) · 4.34 KB
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
import * as JSZip from "jszip";
import * as path from "path";
import * as resolve from "resolve";
import { Tar } from "./tarReader";
const textDecoder = new (typeof TextDecoder === "undefined" ? require("text-encoding").TextDecoder : TextDecoder)();
export interface IFileReader {
exists(path: string): boolean;
read(path: string): Uint8Array;
}
/**
* RequireFS allows users to require from a file system.
*/
export class RequireFS {
private readonly reader: IFileReader;
private readonly customModules: Map<string, { exports: object }>;
private readonly requireCache: Map<string, { exports: object }>;
private baseDir: string | undefined;
public constructor(reader: IFileReader) {
this.reader = reader;
this.customModules = new Map();
this.requireCache = new Map();
}
/**
* Add a base-directory to nest from.
*/
public basedir(path: string): void {
this.baseDir = path;
}
/**
* Provide custom modules to the require instance.
*/
// tslint:disable-next-line:no-any
public provide(module: string, value: any): void {
if (this.customModules.has(module)) {
throw new Error("custom module has already been registered with this name");
}
this.customModules.set(module, value);
}
public readFile(target: string, type?: "string"): string;
public readFile(target: string, type?: "buffer"): Buffer;
/**
* Read a file and returns its contents.
*/
public readFile(target: string, type?: "string" | "buffer"): string | Buffer {
target = path.normalize(target);
const read = this.reader.read(target);
return type === "string" ? textDecoder.decode(read) : Buffer.from(read);
}
/**
* Require a path from a file system.
*/
// tslint:disable-next-line:no-any
public require(target: string): any {
target = path.normalize(target);
return this.doRequire([target], `./${path.basename(target)}`);
}
/**
* Do require for a caller. Needed for resolving relative paths.
*/
private doRequire(callers: string[], resolvePath: string): object {
if (this.customModules.has(resolvePath)) {
return this.customModules.get(resolvePath)!.exports;
}
const caller = callers[callers.length - 1];
const reader = this.reader;
const newRelative = this.realizePath(caller, resolvePath);
if (this.requireCache.has(newRelative)) {
return this.requireCache.get(newRelative)!.exports;
}
const module = {
exports: {},
};
this.requireCache.set(newRelative, module);
const content = textDecoder.decode(reader.read(newRelative));
if (newRelative.endsWith(".json")) {
module.exports = JSON.parse(content);
} else {
eval("'use strict'; " + content);
}
return module.exports;
}
/**
* Attempts to find a module from a path
*/
private realizePath(caller: string, fullRelative: string): string {
const stripPrefix = (path: string): string => {
if (path.startsWith("/")) {
path = path.substr(1);
}
if (path.endsWith("/")) {
path = path.substr(0, path.length - 1);
}
return path;
};
const callerDirname = path.dirname(caller);
const resolvedPath = resolve.sync(fullRelative, {
basedir: this.baseDir ? callerDirname.startsWith(this.baseDir) ? callerDirname : path.join(this.baseDir, callerDirname) : callerDirname,
extensions: [".js"],
readFileSync: (file: string): string => {
return this.readFile(stripPrefix(file));
},
isFile: (file: string): boolean => {
return this.reader.exists(stripPrefix(file));
},
});
return stripPrefix(resolvedPath);
}
}
export const fromTar = (content: Uint8Array): RequireFS => {
const tar = Tar.fromUint8Array(content);
return new RequireFS({
exists: (path: string): boolean => {
return tar.files.has(path);
},
read: (path: string): Uint8Array => {
const file = tar.files.get(path);
if (!file) {
throw new Error(`file "${path}" not found`);
}
return file.read();
},
});
};
export const fromZip = (content: Uint8Array): RequireFS => {
const zip = new JSZip(content);
return new RequireFS({
exists: (fsPath: string): boolean => {
const file = zip.file(fsPath);
return typeof file !== "undefined" && file !== null;
},
read: (fsPath: string): Uint8Array => {
const file = zip.file(fsPath);
if (!file) {
throw new Error(`file "${fsPath}" not found`);
}
// TODO: Should refactor to allow a promise.
// tslint:disable-next-line no-any
return zip.file(fsPath).async("uint8array") as any;
},
});
};