Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/build/lib/util.ts
4770 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import es from 'event-stream';
7
import _debounce from 'debounce';
8
import _filter from 'gulp-filter';
9
import rename from 'gulp-rename';
10
import path from 'path';
11
import fs from 'fs';
12
import _rimraf from 'rimraf';
13
import VinylFile from 'vinyl';
14
import through from 'through';
15
import sm from 'source-map';
16
import { pathToFileURL } from 'url';
17
import ternaryStream from 'ternary-stream';
18
19
const root = path.dirname(path.dirname(import.meta.dirname));
20
21
export interface ICancellationToken {
22
isCancellationRequested(): boolean;
23
}
24
25
const NoCancellationToken: ICancellationToken = { isCancellationRequested: () => false };
26
27
export interface IStreamProvider {
28
(cancellationToken?: ICancellationToken): NodeJS.ReadWriteStream;
29
}
30
31
export function incremental(streamProvider: IStreamProvider, initial: NodeJS.ReadWriteStream, supportsCancellation?: boolean): NodeJS.ReadWriteStream {
32
const input = es.through();
33
const output = es.through();
34
let state = 'idle';
35
let buffer = Object.create(null);
36
37
const token: ICancellationToken | undefined = !supportsCancellation ? undefined : { isCancellationRequested: () => Object.keys(buffer).length > 0 };
38
39
const run = (input: NodeJS.ReadWriteStream, isCancellable: boolean) => {
40
state = 'running';
41
42
const stream = !supportsCancellation ? streamProvider() : streamProvider(isCancellable ? token : NoCancellationToken);
43
44
input
45
.pipe(stream)
46
.pipe(es.through(undefined, () => {
47
state = 'idle';
48
eventuallyRun();
49
}))
50
.pipe(output);
51
};
52
53
if (initial) {
54
run(initial, false);
55
}
56
57
const eventuallyRun = _debounce(() => {
58
const paths = Object.keys(buffer);
59
60
if (paths.length === 0) {
61
return;
62
}
63
64
const data = paths.map(path => buffer[path]);
65
buffer = Object.create(null);
66
run(es.readArray(data), true);
67
}, 500);
68
69
input.on('data', (f: any) => {
70
buffer[f.path] = f;
71
72
if (state === 'idle') {
73
eventuallyRun();
74
}
75
});
76
77
return es.duplex(input, output);
78
}
79
80
export function debounce(task: () => NodeJS.ReadWriteStream, duration = 500): NodeJS.ReadWriteStream {
81
const input = es.through();
82
const output = es.through();
83
let state = 'idle';
84
85
const run = () => {
86
state = 'running';
87
88
task()
89
.pipe(es.through(undefined, () => {
90
const shouldRunAgain = state === 'stale';
91
state = 'idle';
92
93
if (shouldRunAgain) {
94
eventuallyRun();
95
}
96
}))
97
.pipe(output);
98
};
99
100
run();
101
102
const eventuallyRun = _debounce(() => run(), duration);
103
104
input.on('data', () => {
105
if (state === 'idle') {
106
eventuallyRun();
107
} else {
108
state = 'stale';
109
}
110
});
111
112
return es.duplex(input, output);
113
}
114
115
export function fixWin32DirectoryPermissions(): NodeJS.ReadWriteStream {
116
if (!/win32/.test(process.platform)) {
117
return es.through();
118
}
119
120
return es.mapSync<VinylFile, VinylFile>(f => {
121
if (f.stat && f.stat.isDirectory && f.stat.isDirectory()) {
122
f.stat.mode = 16877;
123
}
124
125
return f;
126
});
127
}
128
129
export function setExecutableBit(pattern?: string | string[]): NodeJS.ReadWriteStream {
130
const setBit = es.mapSync<VinylFile, VinylFile>(f => {
131
if (!f.stat) {
132
const stat: Pick<fs.Stats, 'isFile' | 'mode'> = { isFile() { return true; }, mode: 0 };
133
f.stat = stat as fs.Stats;
134
}
135
f.stat!.mode = /* 100755 */ 33261;
136
return f;
137
});
138
139
if (!pattern) {
140
return setBit;
141
}
142
143
const input = es.through();
144
const filter = _filter(pattern, { restore: true });
145
const output = input
146
.pipe(filter)
147
.pipe(setBit)
148
.pipe(filter.restore);
149
150
return es.duplex(input, output);
151
}
152
153
export function toFileUri(filePath: string): string {
154
const match = filePath.match(/^([a-z])\:(.*)$/i);
155
156
if (match) {
157
filePath = '/' + match[1].toUpperCase() + ':' + match[2];
158
}
159
160
return 'file://' + filePath.replace(/\\/g, '/');
161
}
162
163
export function skipDirectories(): NodeJS.ReadWriteStream {
164
return es.mapSync<VinylFile, VinylFile | undefined>(f => {
165
if (!f.isDirectory()) {
166
return f;
167
}
168
});
169
}
170
171
export function cleanNodeModules(rulePath: string): NodeJS.ReadWriteStream {
172
const rules = fs.readFileSync(rulePath, 'utf8')
173
.split(/\r?\n/g)
174
.map(line => line.trim())
175
.filter(line => line && !/^#/.test(line));
176
177
const excludes = rules.filter(line => !/^!/.test(line)).map(line => `!**/node_modules/${line}`);
178
const includes = rules.filter(line => /^!/.test(line)).map(line => `**/node_modules/${line.substr(1)}`);
179
180
const input = es.through();
181
const output = es.merge(
182
input.pipe(_filter(['**', ...excludes])),
183
input.pipe(_filter(includes))
184
);
185
186
return es.duplex(input, output);
187
}
188
189
type FileSourceMap = VinylFile & { sourceMap: sm.RawSourceMap };
190
191
export function loadSourcemaps(): NodeJS.ReadWriteStream {
192
const input = es.through();
193
194
const output = input
195
.pipe(es.map<FileSourceMap, FileSourceMap | undefined>((f, cb): FileSourceMap | undefined => {
196
if (f.sourceMap) {
197
cb(undefined, f);
198
return;
199
}
200
201
if (!f.contents) {
202
cb(undefined, f);
203
return;
204
}
205
206
const contents = (f.contents as Buffer).toString('utf8');
207
const reg = /\/\/# sourceMappingURL=(.*)$/g;
208
let lastMatch: RegExpExecArray | null = null;
209
let match: RegExpExecArray | null = null;
210
211
while (match = reg.exec(contents)) {
212
lastMatch = match;
213
}
214
215
if (!lastMatch) {
216
f.sourceMap = {
217
version: '3',
218
names: [],
219
mappings: '',
220
sources: [f.relative.replace(/\\/g, '/')],
221
sourcesContent: [contents]
222
};
223
224
cb(undefined, f);
225
return;
226
}
227
228
f.contents = Buffer.from(contents.replace(/\/\/# sourceMappingURL=(.*)$/g, ''), 'utf8');
229
230
fs.readFile(path.join(path.dirname(f.path), lastMatch[1]), 'utf8', (err, contents) => {
231
if (err) { return cb(err); }
232
233
f.sourceMap = JSON.parse(contents);
234
cb(undefined, f);
235
});
236
}));
237
238
return es.duplex(input, output);
239
}
240
241
export function stripSourceMappingURL(): NodeJS.ReadWriteStream {
242
const input = es.through();
243
244
const output = input
245
.pipe(es.mapSync<VinylFile, VinylFile>(f => {
246
const contents = (f.contents as Buffer).toString('utf8');
247
f.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, ''), 'utf8');
248
return f;
249
}));
250
251
return es.duplex(input, output);
252
}
253
254
/** Splits items in the stream based on the predicate, sending them to onTrue if true, or onFalse otherwise */
255
export function $if(test: boolean | ((f: VinylFile) => boolean), onTrue: NodeJS.ReadWriteStream, onFalse: NodeJS.ReadWriteStream = es.through()) {
256
if (typeof test === 'boolean') {
257
return test ? onTrue : onFalse;
258
}
259
260
return ternaryStream(test, onTrue, onFalse);
261
}
262
263
/** Operator that appends the js files' original path a sourceURL, so debug locations map */
264
export function appendOwnPathSourceURL(): NodeJS.ReadWriteStream {
265
const input = es.through();
266
267
const output = input
268
.pipe(es.mapSync<VinylFile, VinylFile>(f => {
269
if (!(f.contents instanceof Buffer)) {
270
throw new Error(`contents of ${f.path} are not a buffer`);
271
}
272
273
f.contents = Buffer.concat([f.contents, Buffer.from(`\n//# sourceURL=${pathToFileURL(f.path)}`)]);
274
return f;
275
}));
276
277
return es.duplex(input, output);
278
}
279
280
export function rewriteSourceMappingURL(sourceMappingURLBase: string): NodeJS.ReadWriteStream {
281
const input = es.through();
282
283
const output = input
284
.pipe(es.mapSync<VinylFile, VinylFile>(f => {
285
const contents = (f.contents as Buffer).toString('utf8');
286
const str = `//# sourceMappingURL=${sourceMappingURLBase}/${path.dirname(f.relative).replace(/\\/g, '/')}/$1`;
287
f.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, str));
288
return f;
289
}));
290
291
return es.duplex(input, output);
292
}
293
294
export function rimraf(dir: string): () => Promise<void> {
295
const result = () => new Promise<void>((c, e) => {
296
let retries = 0;
297
298
const retry = () => {
299
_rimraf(dir, { maxBusyTries: 1 }, (err: any) => {
300
if (!err) {
301
return c();
302
}
303
304
if (err.code === 'ENOTEMPTY' && ++retries < 5) {
305
return setTimeout(() => retry(), 10);
306
}
307
308
return e(err);
309
});
310
};
311
312
retry();
313
});
314
315
result.taskName = `clean-${path.basename(dir).toLowerCase()}`;
316
return result;
317
}
318
319
function _rreaddir(dirPath: string, prepend: string, result: string[]): void {
320
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
321
for (const entry of entries) {
322
if (entry.isDirectory()) {
323
_rreaddir(path.join(dirPath, entry.name), `${prepend}/${entry.name}`, result);
324
} else {
325
result.push(`${prepend}/${entry.name}`);
326
}
327
}
328
}
329
330
export function rreddir(dirPath: string): string[] {
331
const result: string[] = [];
332
_rreaddir(dirPath, '', result);
333
return result;
334
}
335
336
export function ensureDir(dirPath: string): void {
337
if (fs.existsSync(dirPath)) {
338
return;
339
}
340
ensureDir(path.dirname(dirPath));
341
fs.mkdirSync(dirPath);
342
}
343
344
export function rebase(count: number): NodeJS.ReadWriteStream {
345
return rename(f => {
346
const parts = f.dirname ? f.dirname.split(/[\/\\]/) : [];
347
f.dirname = parts.slice(count).join(path.sep);
348
});
349
}
350
351
export interface FilterStream extends NodeJS.ReadWriteStream {
352
restore: through.ThroughStream;
353
}
354
355
export function filter(fn: (data: any) => boolean): FilterStream {
356
const result = es.through(function (data) {
357
if (fn(data)) {
358
this.emit('data', data);
359
} else {
360
result.restore.push(data);
361
}
362
}) as unknown as FilterStream;
363
364
result.restore = es.through();
365
return result;
366
}
367
368
export function streamToPromise(stream: NodeJS.ReadWriteStream): Promise<void> {
369
return new Promise((c, e) => {
370
stream.on('error', err => e(err));
371
stream.on('end', () => c());
372
});
373
}
374
375
export function getElectronVersion(): Record<string, string> {
376
const npmrc = fs.readFileSync(path.join(root, '.npmrc'), 'utf8');
377
const electronVersion = /^target="(.*)"$/m.exec(npmrc)![1];
378
const msBuildId = /^ms_build_id="(.*)"$/m.exec(npmrc)![1];
379
return { electronVersion, msBuildId };
380
}
381
382
export class VinylStat implements fs.Stats {
383
384
readonly dev: number;
385
readonly ino: number;
386
readonly mode: number;
387
readonly nlink: number;
388
readonly uid: number;
389
readonly gid: number;
390
readonly rdev: number;
391
readonly size: number;
392
readonly blksize: number;
393
readonly blocks: number;
394
readonly atimeMs: number;
395
readonly mtimeMs: number;
396
readonly ctimeMs: number;
397
readonly birthtimeMs: number;
398
readonly atime: Date;
399
readonly mtime: Date;
400
readonly ctime: Date;
401
readonly birthtime: Date;
402
403
constructor(stat: Partial<fs.Stats>) {
404
this.dev = stat.dev ?? 0;
405
this.ino = stat.ino ?? 0;
406
this.mode = stat.mode ?? 0;
407
this.nlink = stat.nlink ?? 0;
408
this.uid = stat.uid ?? 0;
409
this.gid = stat.gid ?? 0;
410
this.rdev = stat.rdev ?? 0;
411
this.size = stat.size ?? 0;
412
this.blksize = stat.blksize ?? 0;
413
this.blocks = stat.blocks ?? 0;
414
this.atimeMs = stat.atimeMs ?? 0;
415
this.mtimeMs = stat.mtimeMs ?? 0;
416
this.ctimeMs = stat.ctimeMs ?? 0;
417
this.birthtimeMs = stat.birthtimeMs ?? 0;
418
this.atime = stat.atime ?? new Date(0);
419
this.mtime = stat.mtime ?? new Date(0);
420
this.ctime = stat.ctime ?? new Date(0);
421
this.birthtime = stat.birthtime ?? new Date(0);
422
}
423
424
isFile(): boolean { return true; }
425
isDirectory(): boolean { return false; }
426
isBlockDevice(): boolean { return false; }
427
isCharacterDevice(): boolean { return false; }
428
isSymbolicLink(): boolean { return false; }
429
isFIFO(): boolean { return false; }
430
isSocket(): boolean { return false; }
431
}
432
433