mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-01 09:35:41 -05:00
Vscode merge (#4582)
* Merge from vscode 37cb23d3dd4f9433d56d4ba5ea3203580719a0bd * fix issues with merges * bump node version in azpipe * replace license headers * remove duplicate launch task * fix build errors * fix build errors * fix tslint issues * working through package and linux build issues * more work * wip * fix packaged builds * working through linux build errors * wip * wip * wip * fix mac and linux file limits * iterate linux pipeline * disable editor typing * revert series to parallel * remove optimize vscode from linux * fix linting issues * revert testing change * add work round for new node * readd packaging for extensions * fix issue with angular not resolving decorator dependencies
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import { dirname, basename } from 'path';
|
||||
import { dirname, basename } from 'vs/base/common/path';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
|
||||
@@ -24,8 +24,8 @@ export class LineDecoder {
|
||||
}
|
||||
|
||||
public write(buffer: Buffer): string[] {
|
||||
let result: string[] = [];
|
||||
let value = this.remaining
|
||||
const result: string[] = [];
|
||||
const value = this.remaining
|
||||
? this.remaining + this.stringDecoder.write(buffer)
|
||||
: this.stringDecoder.write(buffer);
|
||||
|
||||
@@ -41,7 +41,7 @@ export class LineDecoder {
|
||||
result.push(value.substring(start, idx));
|
||||
idx++;
|
||||
if (idx < value.length) {
|
||||
let lastChar = ch;
|
||||
const lastChar = ch;
|
||||
ch = value.charCodeAt(idx);
|
||||
if ((lastChar === CharCode.CarriageReturn && ch === CharCode.LineFeed) || (lastChar === CharCode.LineFeed && ch === CharCode.CarriageReturn)) {
|
||||
idx++;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as paths from 'path';
|
||||
import * as paths from 'vs/base/common/path';
|
||||
import { nfcall } from 'vs/base/common/async';
|
||||
import { normalizeNFC } from 'vs/base/common/normalization';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
@@ -219,7 +219,7 @@ export function del(path: string, tmpFolder: string, callback: (error: Error | n
|
||||
}
|
||||
|
||||
function rmRecursive(path: string, callback: (error: Error | null) => void): void {
|
||||
if (path === '\\' || path === '/') {
|
||||
if (path === paths.win32.sep || path === paths.posix.sep) {
|
||||
return callback(new Error('Will not delete root!'));
|
||||
}
|
||||
|
||||
@@ -277,6 +277,10 @@ function rmRecursive(path: string, callback: (error: Error | null) => void): voi
|
||||
}
|
||||
|
||||
export function delSync(path: string): void {
|
||||
if (path === paths.win32.sep || path === paths.posix.sep) {
|
||||
throw new Error('Will not delete root!');
|
||||
}
|
||||
|
||||
try {
|
||||
const stat = fs.lstatSync(path);
|
||||
if (stat.isDirectory() && !stat.isSymbolicLink()) {
|
||||
|
||||
@@ -10,8 +10,8 @@ import * as assert from 'assert';
|
||||
* array to the callback (callback). The resulting errors and results are evaluated by calling the provided callback function.
|
||||
*/
|
||||
export function parallel<T, E>(list: T[], fn: (item: T, callback: (err: Error | null, result: E | null) => void) => void, callback: (err: Array<Error | null> | null, result: E[]) => void): void {
|
||||
let results = new Array(list.length);
|
||||
let errors = new Array<Error | null>(list.length);
|
||||
const results = new Array(list.length);
|
||||
const errors = new Array<Error | null>(list.length);
|
||||
let didErrorOccur = false;
|
||||
let doneCount = 0;
|
||||
|
||||
@@ -68,9 +68,9 @@ export function loop<E>(param: any, fn: (item: any, callback: (error: Error | nu
|
||||
|
||||
// Expect the param to be an array and loop over it
|
||||
else {
|
||||
let results: E[] = [];
|
||||
const results: E[] = [];
|
||||
|
||||
let looper: (i: number) => void = function (i: number): void {
|
||||
const looper: (i: number) => void = function (i: number): void {
|
||||
|
||||
// Still work to do
|
||||
if (i < param.length) {
|
||||
@@ -126,11 +126,11 @@ function Sequence(sequences: { (...param: any[]): void; }[]): void {
|
||||
});
|
||||
|
||||
// Execute in Loop
|
||||
let errorHandler = sequences.splice(0, 1)[0]; //Remove error handler
|
||||
const errorHandler = sequences.splice(0, 1)[0]; //Remove error handler
|
||||
let sequenceResult: any = null;
|
||||
|
||||
loop(sequences, (sequence, clb) => {
|
||||
let sequenceFunction = function (error: any, result: any): void {
|
||||
const sequenceFunction = function (error: any, result: any): void {
|
||||
|
||||
// A method might only send a boolean value as return value (e.g. fs.exists), support this case gracefully
|
||||
if (error === true || error === false) {
|
||||
|
||||
@@ -97,7 +97,7 @@ function getMacMachineId(): Promise<string> {
|
||||
// TODO@sbatten: Remove this when getmac is patched
|
||||
setTimeout(() => {
|
||||
resolve(undefined);
|
||||
}, 1000);
|
||||
}, 10000);
|
||||
} catch (err) {
|
||||
errors.onUnexpectedError(err);
|
||||
resolve(undefined);
|
||||
|
||||
23
src/vs/base/node/languagePacks.d.ts
vendored
Normal file
23
src/vs/base/node/languagePacks.d.ts
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export interface NLSConfiguration {
|
||||
locale: string;
|
||||
availableLanguages: {
|
||||
[key: string]: string;
|
||||
};
|
||||
pseudo?: boolean;
|
||||
}
|
||||
|
||||
export interface InternalNLSConfiguration extends NLSConfiguration {
|
||||
_languagePackId: string;
|
||||
_translationsConfigFile: string;
|
||||
_cacheRoot: string;
|
||||
_resolvedLanguagePackCoreLocation: string;
|
||||
_corruptedFile: string;
|
||||
_languagePackSupport?: boolean;
|
||||
}
|
||||
|
||||
export function getNLSConfiguration(commit: string, userDataPath: string, metaDataFile: string, locale: string): Promise<NLSConfiguration>;
|
||||
332
src/vs/base/node/languagePacks.js
Normal file
332
src/vs/base/node/languagePacks.js
Normal file
@@ -0,0 +1,332 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
//@ts-check
|
||||
|
||||
/**
|
||||
* @param {NodeRequire} nodeRequire
|
||||
* @param {typeof import('path')} path
|
||||
* @param {typeof import('fs')} fs
|
||||
* @param {typeof import('../common/performance')} perf
|
||||
*/
|
||||
function factory(nodeRequire, path, fs, perf) {
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
function exists(file) {
|
||||
return new Promise(c => fs.exists(file, c));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function touch(file) {
|
||||
return new Promise((c, e) => { const d = new Date(); fs.utimes(file, d, d, err => err ? e(err) : c()); });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
function lstat(file) {
|
||||
return new Promise((c, e) => fs.lstat(file, (err, stats) => err ? e(err) : c(stats)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} dir
|
||||
* @returns {Promise<string[]>}
|
||||
*/
|
||||
function readdir(dir) {
|
||||
return new Promise((c, e) => fs.readdir(dir, (err, files) => err ? e(err) : c(files)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} dir
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
function mkdir(dir) {
|
||||
return new Promise((c, e) => fs.mkdir(dir, err => (err && err.code !== 'EEXIST') ? e(err) : c(dir)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} dir
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function rmdir(dir) {
|
||||
return new Promise((c, e) => fs.rmdir(dir, err => err ? e(err) : c(undefined)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function unlink(file) {
|
||||
return new Promise((c, e) => fs.unlink(file, err => err ? e(err) : c(undefined)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} location
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function rimraf(location) {
|
||||
return lstat(location).then(stat => {
|
||||
if (stat.isDirectory() && !stat.isSymbolicLink()) {
|
||||
return readdir(location)
|
||||
.then(children => Promise.all(children.map(child => rimraf(path.join(location, child)))))
|
||||
.then(() => rmdir(location));
|
||||
} else {
|
||||
return unlink(location);
|
||||
}
|
||||
}, err => {
|
||||
if (err.code === 'ENOENT') {
|
||||
return undefined;
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} dir
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
function mkdirp(dir) {
|
||||
return mkdir(dir).then(null, err => {
|
||||
if (err && err.code === 'ENOENT') {
|
||||
const parent = path.dirname(dir);
|
||||
|
||||
if (parent !== dir) { // if not arrived at root
|
||||
return mkdirp(parent).then(() => mkdir(dir));
|
||||
}
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
function readFile(file) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
fs.readFile(file, 'utf8', function (err, data) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
* @param {string} content
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function writeFile(file, content) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
fs.writeFile(file, content, 'utf8', function (err) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} userDataPath
|
||||
* @returns {object}
|
||||
*/
|
||||
function getLanguagePackConfigurations(userDataPath) {
|
||||
const configFile = path.join(userDataPath, 'languagepacks.json');
|
||||
try {
|
||||
return nodeRequire(configFile);
|
||||
} catch (err) {
|
||||
// Do nothing. If we can't read the file we have no
|
||||
// language pack config.
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} config
|
||||
* @param {string} locale
|
||||
*/
|
||||
function resolveLanguagePackLocale(config, locale) {
|
||||
try {
|
||||
while (locale) {
|
||||
if (config[locale]) {
|
||||
return locale;
|
||||
} else {
|
||||
const index = locale.lastIndexOf('-');
|
||||
if (index > 0) {
|
||||
locale = locale.substring(0, index);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Resolving language pack configuration failed.', err);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} commit
|
||||
* @param {string} userDataPath
|
||||
* @param {string} metaDataFile
|
||||
* @param {string} locale
|
||||
*/
|
||||
function getNLSConfiguration(commit, userDataPath, metaDataFile, locale) {
|
||||
if (locale === 'pseudo') {
|
||||
return Promise.resolve({ locale: locale, availableLanguages: {}, pseudo: true });
|
||||
}
|
||||
|
||||
if (process.env['VSCODE_DEV']) {
|
||||
return Promise.resolve({ locale: locale, availableLanguages: {} });
|
||||
}
|
||||
|
||||
// We have a built version so we have extracted nls file. Try to find
|
||||
// the right file to use.
|
||||
|
||||
// Check if we have an English or English US locale. If so fall to default since that is our
|
||||
// English translation (we don't ship *.nls.en.json files)
|
||||
if (locale && (locale === 'en' || locale === 'en-us')) {
|
||||
return Promise.resolve({ locale: locale, availableLanguages: {} });
|
||||
}
|
||||
|
||||
const initialLocale = locale;
|
||||
|
||||
perf.mark('nlsGeneration:start');
|
||||
|
||||
const defaultResult = function (locale) {
|
||||
perf.mark('nlsGeneration:end');
|
||||
return Promise.resolve({ locale: locale, availableLanguages: {} });
|
||||
};
|
||||
try {
|
||||
if (!commit) {
|
||||
return defaultResult(initialLocale);
|
||||
}
|
||||
const configs = getLanguagePackConfigurations(userDataPath);
|
||||
if (!configs) {
|
||||
return defaultResult(initialLocale);
|
||||
}
|
||||
locale = resolveLanguagePackLocale(configs, locale);
|
||||
if (!locale) {
|
||||
return defaultResult(initialLocale);
|
||||
}
|
||||
const packConfig = configs[locale];
|
||||
let mainPack;
|
||||
if (!packConfig || typeof packConfig.hash !== 'string' || !packConfig.translations || typeof (mainPack = packConfig.translations['vscode']) !== 'string') {
|
||||
return defaultResult(initialLocale);
|
||||
}
|
||||
return exists(mainPack).then(fileExists => {
|
||||
if (!fileExists) {
|
||||
return defaultResult(initialLocale);
|
||||
}
|
||||
const packId = packConfig.hash + '.' + locale;
|
||||
const cacheRoot = path.join(userDataPath, 'clp', packId);
|
||||
const coreLocation = path.join(cacheRoot, commit);
|
||||
const translationsConfigFile = path.join(cacheRoot, 'tcf.json');
|
||||
const corruptedFile = path.join(cacheRoot, 'corrupted.info');
|
||||
const result = {
|
||||
locale: initialLocale,
|
||||
availableLanguages: { '*': locale },
|
||||
_languagePackId: packId,
|
||||
_translationsConfigFile: translationsConfigFile,
|
||||
_cacheRoot: cacheRoot,
|
||||
_resolvedLanguagePackCoreLocation: coreLocation,
|
||||
_corruptedFile: corruptedFile
|
||||
};
|
||||
return exists(corruptedFile).then(corrupted => {
|
||||
// The nls cache directory is corrupted.
|
||||
let toDelete;
|
||||
if (corrupted) {
|
||||
toDelete = rimraf(cacheRoot);
|
||||
} else {
|
||||
toDelete = Promise.resolve(undefined);
|
||||
}
|
||||
return toDelete.then(() => {
|
||||
return exists(coreLocation).then(fileExists => {
|
||||
if (fileExists) {
|
||||
// We don't wait for this. No big harm if we can't touch
|
||||
touch(coreLocation).catch(() => { });
|
||||
perf.mark('nlsGeneration:end');
|
||||
return result;
|
||||
}
|
||||
return mkdirp(coreLocation).then(() => {
|
||||
return Promise.all([readFile(metaDataFile), readFile(mainPack)]);
|
||||
}).then(values => {
|
||||
const metadata = JSON.parse(values[0]);
|
||||
const packData = JSON.parse(values[1]).contents;
|
||||
const bundles = Object.keys(metadata.bundles);
|
||||
const writes = [];
|
||||
for (let bundle of bundles) {
|
||||
const modules = metadata.bundles[bundle];
|
||||
const target = Object.create(null);
|
||||
for (let module of modules) {
|
||||
const keys = metadata.keys[module];
|
||||
const defaultMessages = metadata.messages[module];
|
||||
const translations = packData[module];
|
||||
let targetStrings;
|
||||
if (translations) {
|
||||
targetStrings = [];
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const elem = keys[i];
|
||||
const key = typeof elem === 'string' ? elem : elem.key;
|
||||
let translatedMessage = translations[key];
|
||||
if (translatedMessage === undefined) {
|
||||
translatedMessage = defaultMessages[i];
|
||||
}
|
||||
targetStrings.push(translatedMessage);
|
||||
}
|
||||
} else {
|
||||
targetStrings = defaultMessages;
|
||||
}
|
||||
target[module] = targetStrings;
|
||||
}
|
||||
writes.push(writeFile(path.join(coreLocation, bundle.replace(/\//g, '!') + '.nls.json'), JSON.stringify(target)));
|
||||
}
|
||||
writes.push(writeFile(translationsConfigFile, JSON.stringify(packConfig.translations)));
|
||||
return Promise.all(writes);
|
||||
}).then(() => {
|
||||
perf.mark('nlsGeneration:end');
|
||||
return result;
|
||||
}).catch(err => {
|
||||
console.error('Generating translation files failed.', err);
|
||||
return defaultResult(locale);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Generating translation files failed.', err);
|
||||
return defaultResult(locale);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
getNLSConfiguration
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if (typeof define === 'function') {
|
||||
// amd
|
||||
define(['path', 'fs', 'vs/base/common/performance'], function (path, fs, perf) { return factory(require.__$__nodeRequire, path, fs, perf); });
|
||||
} else if (typeof module === 'object' && typeof module.exports === 'object') {
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const perf = require('../common/performance');
|
||||
module.exports = factory(require, path, fs, perf);
|
||||
} else {
|
||||
throw new Error('Unknown context');
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as extfs from 'vs/base/node/extfs';
|
||||
import { join } from 'path';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { nfcall, Queue } from 'vs/base/common/async';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
|
||||
@@ -9,8 +9,8 @@ import * as net from 'net';
|
||||
* @returns Returns a random port between 1025 and 65535.
|
||||
*/
|
||||
export function randomPort(): number {
|
||||
let min = 1025;
|
||||
let max = 65535;
|
||||
const min = 1025;
|
||||
const max = 65535;
|
||||
return min + Math.floor((max - min) * Math.random());
|
||||
}
|
||||
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as path from 'path';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import * as fs from 'fs';
|
||||
import * as cp from 'child_process';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as Types from 'vs/base/common/types';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import * as Objects from 'vs/base/common/objects';
|
||||
import * as TPath from 'vs/base/common/paths';
|
||||
import * as extpath from 'vs/base/common/extpath';
|
||||
import * as Platform from 'vs/base/common/platform';
|
||||
import { LineDecoder } from 'vs/base/node/decoder';
|
||||
import { CommandOptions, ForkOptions, SuccessData, Source, TerminateResponse, TerminateResponseCode, Executable } from 'vs/base/common/processes';
|
||||
@@ -42,7 +42,7 @@ function getWindowsCode(status: number): TerminateResponseCode {
|
||||
export function terminateProcess(process: cp.ChildProcess, cwd?: string): TerminateResponse {
|
||||
if (Platform.isWindows) {
|
||||
try {
|
||||
let options: any = {
|
||||
const options: any = {
|
||||
stdio: ['pipe', 'pipe', 'ignore']
|
||||
};
|
||||
if (cwd) {
|
||||
@@ -54,8 +54,8 @@ export function terminateProcess(process: cp.ChildProcess, cwd?: string): Termin
|
||||
}
|
||||
} else if (Platform.isLinux || Platform.isMacintosh) {
|
||||
try {
|
||||
let cmd = getPathFromAmdModule(require, 'vs/base/node/terminateProcess.sh');
|
||||
let result = cp.spawnSync(cmd, [process.pid.toString()]);
|
||||
const cmd = getPathFromAmdModule(require, 'vs/base/node/terminateProcess.sh');
|
||||
const result = cp.spawnSync(cmd, [process.pid.toString()]);
|
||||
if (result.error) {
|
||||
return { success: false, error: result.error };
|
||||
}
|
||||
@@ -72,26 +72,6 @@ export function getWindowsShell(): string {
|
||||
return process.env['comspec'] || 'cmd.exe';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes a VS Code process environment by removing all Electron/VS Code-related values.
|
||||
*/
|
||||
export function sanitizeProcessEnvironment(env: Platform.IProcessEnvironment): void {
|
||||
const keysToRemove = [
|
||||
/^ELECTRON_.+$/,
|
||||
/^GOOGLE_API_KEY$/,
|
||||
/^VSCODE_.+$/
|
||||
];
|
||||
const envKeys = Object.keys(env);
|
||||
envKeys.forEach(envKey => {
|
||||
for (let i = 0; i < keysToRemove.length; i++) {
|
||||
if (envKey.search(keysToRemove[i]) !== -1) {
|
||||
delete env[envKey];
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export abstract class AbstractProcess<TProgressData> {
|
||||
private cmd: string;
|
||||
private args: string[];
|
||||
@@ -133,7 +113,7 @@ export abstract class AbstractProcess<TProgressData> {
|
||||
this.shell = arg3;
|
||||
this.options = arg4;
|
||||
} else {
|
||||
let executable = <Executable>arg1;
|
||||
const executable = <Executable>arg1;
|
||||
this.cmd = executable.command;
|
||||
this.shell = executable.isShellCommand;
|
||||
this.args = executable.args.slice(0);
|
||||
@@ -144,7 +124,7 @@ export abstract class AbstractProcess<TProgressData> {
|
||||
this.terminateRequested = false;
|
||||
|
||||
if (this.options.env) {
|
||||
let newEnv: IStringDictionary<string> = Object.create(null);
|
||||
const newEnv: IStringDictionary<string> = Object.create(null);
|
||||
Object.keys(process.env).forEach((key) => {
|
||||
newEnv[key] = process.env[key]!;
|
||||
});
|
||||
@@ -157,7 +137,7 @@ export abstract class AbstractProcess<TProgressData> {
|
||||
|
||||
public getSanitizedCommand(): string {
|
||||
let result = this.cmd.toLowerCase();
|
||||
let index = result.lastIndexOf(path.sep);
|
||||
const index = result.lastIndexOf(path.sep);
|
||||
if (index !== -1) {
|
||||
result = result.substring(index + 1);
|
||||
}
|
||||
@@ -168,13 +148,13 @@ export abstract class AbstractProcess<TProgressData> {
|
||||
}
|
||||
|
||||
public start(pp: ProgressCallback<TProgressData>): Promise<SuccessData> {
|
||||
if (Platform.isWindows && ((this.options && this.options.cwd && TPath.isUNC(this.options.cwd)) || !this.options && TPath.isUNC(process.cwd()))) {
|
||||
if (Platform.isWindows && ((this.options && this.options.cwd && extpath.isUNC(this.options.cwd)) || !this.options && extpath.isUNC(process.cwd()))) {
|
||||
return Promise.reject(new Error(nls.localize('TaskRunner.UNC', 'Can\'t execute a shell command on a UNC drive.')));
|
||||
}
|
||||
return this.useExec().then((useExec) => {
|
||||
let cc: ValueCallback<SuccessData>;
|
||||
let ee: ErrorCallback;
|
||||
let result = new Promise<any>((c, e) => {
|
||||
const result = new Promise<any>((c, e) => {
|
||||
cc = c;
|
||||
ee = e;
|
||||
});
|
||||
@@ -186,7 +166,7 @@ export abstract class AbstractProcess<TProgressData> {
|
||||
}
|
||||
this.childProcess = cp.exec(cmd, this.options, (error, stdout, stderr) => {
|
||||
this.childProcess = null;
|
||||
let err: any = error;
|
||||
const err: any = error;
|
||||
// This is tricky since executing a command shell reports error back in case the executed command return an
|
||||
// error or the command didn't exist at all. So we can't blindly treat an error as a failed command. So we
|
||||
// always parse the output and report success unless the job got killed.
|
||||
@@ -198,11 +178,11 @@ export abstract class AbstractProcess<TProgressData> {
|
||||
});
|
||||
} else {
|
||||
let childProcess: cp.ChildProcess | null = null;
|
||||
let closeHandler = (data: any) => {
|
||||
const closeHandler = (data: any) => {
|
||||
this.childProcess = null;
|
||||
this.childProcessPromise = null;
|
||||
this.handleClose(data, cc, pp, ee);
|
||||
let result: SuccessData = {
|
||||
const result: SuccessData = {
|
||||
terminated: this.terminateRequested
|
||||
};
|
||||
if (Types.isNumber(data)) {
|
||||
@@ -211,12 +191,12 @@ export abstract class AbstractProcess<TProgressData> {
|
||||
cc(result);
|
||||
};
|
||||
if (this.shell && Platform.isWindows) {
|
||||
let options: any = Objects.deepClone(this.options);
|
||||
const options: any = Objects.deepClone(this.options);
|
||||
options.windowsVerbatimArguments = true;
|
||||
options.detached = false;
|
||||
let quotedCommand: boolean = false;
|
||||
let quotedArg: boolean = false;
|
||||
let commandLine: string[] = [];
|
||||
const commandLine: string[] = [];
|
||||
let quoted = this.ensureQuotes(this.cmd);
|
||||
commandLine.push(quoted.value);
|
||||
quotedCommand = quoted.quoted;
|
||||
@@ -227,7 +207,7 @@ export abstract class AbstractProcess<TProgressData> {
|
||||
quotedArg = quotedArg && quoted.quoted;
|
||||
});
|
||||
}
|
||||
let args: string[] = [
|
||||
const args: string[] = [
|
||||
'/s',
|
||||
'/c',
|
||||
];
|
||||
@@ -307,7 +287,7 @@ export abstract class AbstractProcess<TProgressData> {
|
||||
}
|
||||
return this.childProcessPromise.then((childProcess) => {
|
||||
this.terminateRequested = true;
|
||||
let result = terminateProcess(childProcess, this.options.cwd);
|
||||
const result = terminateProcess(childProcess, this.options.cwd);
|
||||
if (result.success) {
|
||||
this.childProcess = null;
|
||||
}
|
||||
@@ -320,14 +300,14 @@ export abstract class AbstractProcess<TProgressData> {
|
||||
private useExec(): Promise<boolean> {
|
||||
return new Promise<boolean>((c, e) => {
|
||||
if (!this.shell || !Platform.isWindows) {
|
||||
c(false);
|
||||
return c(false);
|
||||
}
|
||||
let cmdShell = cp.spawn(getWindowsShell(), ['/s', '/c']);
|
||||
const cmdShell = cp.spawn(getWindowsShell(), ['/s', '/c']);
|
||||
cmdShell.on('error', (error: Error) => {
|
||||
c(true);
|
||||
return c(true);
|
||||
});
|
||||
cmdShell.on('exit', (data: any) => {
|
||||
c(false);
|
||||
return c(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -346,12 +326,12 @@ export class LineProcess extends AbstractProcess<LineData> {
|
||||
|
||||
protected handleExec(cc: ValueCallback<SuccessData>, pp: ProgressCallback<LineData>, error: Error, stdout: Buffer, stderr: Buffer) {
|
||||
[stdout, stderr].forEach((buffer: Buffer, index: number) => {
|
||||
let lineDecoder = new LineDecoder();
|
||||
let lines = lineDecoder.write(buffer);
|
||||
const lineDecoder = new LineDecoder();
|
||||
const lines = lineDecoder.write(buffer);
|
||||
lines.forEach((line) => {
|
||||
pp({ line: line, source: index === 0 ? Source.stdout : Source.stderr });
|
||||
});
|
||||
let line = lineDecoder.end();
|
||||
const line = lineDecoder.end();
|
||||
if (line) {
|
||||
pp({ line: line, source: index === 0 ? Source.stdout : Source.stderr });
|
||||
}
|
||||
@@ -363,11 +343,11 @@ export class LineProcess extends AbstractProcess<LineData> {
|
||||
this.stdoutLineDecoder = new LineDecoder();
|
||||
this.stderrLineDecoder = new LineDecoder();
|
||||
childProcess.stdout.on('data', (data: Buffer) => {
|
||||
let lines = this.stdoutLineDecoder.write(data);
|
||||
const lines = this.stdoutLineDecoder.write(data);
|
||||
lines.forEach(line => pp({ line: line, source: Source.stdout }));
|
||||
});
|
||||
childProcess.stderr.on('data', (data: Buffer) => {
|
||||
let lines = this.stderrLineDecoder.write(data);
|
||||
const lines = this.stderrLineDecoder.write(data);
|
||||
lines.forEach(line => pp({ line: line, source: Source.stderr }));
|
||||
});
|
||||
}
|
||||
@@ -400,7 +380,7 @@ export function createQueuedSender(childProcess: cp.ChildProcess): IQueuedSender
|
||||
return;
|
||||
}
|
||||
|
||||
let result = childProcess.send(msg, (error: Error) => {
|
||||
const result = childProcess.send(msg, (error: Error) => {
|
||||
if (error) {
|
||||
console.error(error); // unlikely to happen, best we can do is log this error
|
||||
}
|
||||
@@ -432,7 +412,7 @@ export namespace win32 {
|
||||
if (cwd === undefined) {
|
||||
cwd = process.cwd();
|
||||
}
|
||||
let dir = path.dirname(command);
|
||||
const dir = path.dirname(command);
|
||||
if (dir !== '.') {
|
||||
// We have a directory and the directory is relative (see above). Make the path absolute
|
||||
// to the current working directory.
|
||||
@@ -469,4 +449,4 @@ export namespace win32 {
|
||||
}
|
||||
return path.join(cwd, command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
|
||||
rootItem = processItems.get(rootPid);
|
||||
if (rootItem) {
|
||||
processItems.forEach(item => {
|
||||
let parent = processItems.get(item.ppid);
|
||||
const parent = processItems.get(item.ppid);
|
||||
if (parent) {
|
||||
if (!parent.children) {
|
||||
parent.children = [];
|
||||
@@ -186,7 +186,7 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
|
||||
|
||||
const lines = stdout.toString().split('\n');
|
||||
for (const line of lines) {
|
||||
let matches = PID_CMD.exec(line.trim());
|
||||
const matches = PID_CMD.exec(line.trim());
|
||||
if (matches && matches.length === 6) {
|
||||
addToTree(parseInt(matches[1]), parseInt(matches[2]), matches[5], parseFloat(matches[3]), parseFloat(matches[4]));
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ export function asText(context: IRequestContext): Promise<string | null> {
|
||||
return c(null);
|
||||
}
|
||||
|
||||
let buffer: string[] = [];
|
||||
const buffer: string[] = [];
|
||||
context.stream.on('data', (d: string) => buffer.push(d));
|
||||
context.stream.on('end', () => c(buffer.join('')));
|
||||
context.stream.on('error', e);
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { readdir, stat, exists, readFile } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { parse, ParseError } from 'vs/base/common/json';
|
||||
|
||||
export interface WorkspaceStatItem {
|
||||
name: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface WorkspaceStats {
|
||||
fileTypes: WorkspaceStatItem[];
|
||||
configFiles: WorkspaceStatItem[];
|
||||
fileCount: number;
|
||||
maxFilesReached: boolean;
|
||||
}
|
||||
|
||||
function asSortedItems(map: Map<string, number>): WorkspaceStatItem[] {
|
||||
let a: WorkspaceStatItem[] = [];
|
||||
map.forEach((value, index) => a.push({ name: index, count: value }));
|
||||
return a.sort((a, b) => b.count - a.count);
|
||||
}
|
||||
|
||||
export function collectLaunchConfigs(folder: string): Promise<WorkspaceStatItem[]> {
|
||||
let launchConfigs = new Map<string, number>();
|
||||
|
||||
let launchConfig = join(folder, '.vscode', 'launch.json');
|
||||
return new Promise((resolve, reject) => {
|
||||
exists(launchConfig, (doesExist) => {
|
||||
if (doesExist) {
|
||||
readFile(launchConfig, (err, contents) => {
|
||||
if (err) {
|
||||
return resolve([]);
|
||||
}
|
||||
|
||||
const errors: ParseError[] = [];
|
||||
const json = parse(contents.toString(), errors);
|
||||
if (errors.length) {
|
||||
console.log(`Unable to parse ${launchConfig}`);
|
||||
return resolve([]);
|
||||
}
|
||||
|
||||
if (json['configurations']) {
|
||||
for (const each of json['configurations']) {
|
||||
const type = each['type'];
|
||||
if (type) {
|
||||
if (launchConfigs.has(type)) {
|
||||
launchConfigs.set(type, launchConfigs.get(type)! + 1);
|
||||
} else {
|
||||
launchConfigs.set(type, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resolve(asSortedItems(launchConfigs));
|
||||
});
|
||||
} else {
|
||||
return resolve([]);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function collectWorkspaceStats(folder: string, filter: string[]): Promise<WorkspaceStats> {
|
||||
const configFilePatterns = [
|
||||
{ 'tag': 'grunt.js', 'pattern': /^gruntfile\.js$/i },
|
||||
{ 'tag': 'gulp.js', 'pattern': /^gulpfile\.js$/i },
|
||||
{ 'tag': 'tsconfig.json', 'pattern': /^tsconfig\.json$/i },
|
||||
{ 'tag': 'package.json', 'pattern': /^package\.json$/i },
|
||||
{ 'tag': 'jsconfig.json', 'pattern': /^jsconfig\.json$/i },
|
||||
{ 'tag': 'tslint.json', 'pattern': /^tslint\.json$/i },
|
||||
{ 'tag': 'eslint.json', 'pattern': /^eslint\.json$/i },
|
||||
{ 'tag': 'tasks.json', 'pattern': /^tasks\.json$/i },
|
||||
{ 'tag': 'launch.json', 'pattern': /^launch\.json$/i },
|
||||
{ 'tag': 'settings.json', 'pattern': /^settings\.json$/i },
|
||||
{ 'tag': 'webpack.config.js', 'pattern': /^webpack\.config\.js$/i },
|
||||
{ 'tag': 'project.json', 'pattern': /^project\.json$/i },
|
||||
{ 'tag': 'makefile', 'pattern': /^makefile$/i },
|
||||
{ 'tag': 'sln', 'pattern': /^.+\.sln$/i },
|
||||
{ 'tag': 'csproj', 'pattern': /^.+\.csproj$/i },
|
||||
{ 'tag': 'cmake', 'pattern': /^.+\.cmake$/i }
|
||||
];
|
||||
|
||||
let fileTypes = new Map<string, number>();
|
||||
let configFiles = new Map<string, number>();
|
||||
|
||||
const MAX_FILES = 20000;
|
||||
|
||||
function walk(dir: string, filter: string[], token, done: (allFiles: string[]) => void): void {
|
||||
let results: string[] = [];
|
||||
readdir(dir, async (err, files) => {
|
||||
// Ignore folders that can't be read
|
||||
if (err) {
|
||||
return done(results);
|
||||
}
|
||||
|
||||
let pending = files.length;
|
||||
if (pending === 0) {
|
||||
return done(results);
|
||||
}
|
||||
|
||||
for (const file of files) {
|
||||
if (token.maxReached) {
|
||||
return done(results);
|
||||
}
|
||||
|
||||
stat(join(dir, file), (err, stats) => {
|
||||
// Ignore files that can't be read
|
||||
if (err) {
|
||||
if (--pending === 0) {
|
||||
return done(results);
|
||||
}
|
||||
} else {
|
||||
if (stats.isDirectory()) {
|
||||
if (filter.indexOf(file) === -1) {
|
||||
walk(join(dir, file), filter, token, (res: string[]) => {
|
||||
results = results.concat(res);
|
||||
|
||||
if (--pending === 0) {
|
||||
return done(results);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (--pending === 0) {
|
||||
done(results);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (token.count >= MAX_FILES) {
|
||||
token.maxReached = true;
|
||||
}
|
||||
|
||||
token.count++;
|
||||
results.push(file);
|
||||
|
||||
if (--pending === 0) {
|
||||
done(results);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let addFileType = (fileType: string) => {
|
||||
if (fileTypes.has(fileType)) {
|
||||
fileTypes.set(fileType, fileTypes.get(fileType)! + 1);
|
||||
}
|
||||
else {
|
||||
fileTypes.set(fileType, 1);
|
||||
}
|
||||
};
|
||||
|
||||
let addConfigFiles = (fileName: string) => {
|
||||
for (const each of configFilePatterns) {
|
||||
if (each.pattern.test(fileName)) {
|
||||
if (configFiles.has(each.tag)) {
|
||||
configFiles.set(each.tag, configFiles.get(each.tag)! + 1);
|
||||
} else {
|
||||
configFiles.set(each.tag, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let acceptFile = (name: string) => {
|
||||
if (name.lastIndexOf('.') >= 0) {
|
||||
let suffix: string | undefined = name.split('.').pop();
|
||||
if (suffix) {
|
||||
addFileType(suffix);
|
||||
}
|
||||
}
|
||||
addConfigFiles(name);
|
||||
};
|
||||
|
||||
let token: { count: number, maxReached: boolean } = { count: 0, maxReached: false };
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
walk(folder, filter, token, (files) => {
|
||||
files.forEach(acceptFile);
|
||||
|
||||
resolve({
|
||||
configFiles: asSortedItems(configFiles),
|
||||
fileTypes: asSortedItems(fileTypes),
|
||||
fileCount: token.count,
|
||||
maxFilesReached: token.maxReached
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -9,8 +9,7 @@ import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { ThrottledDelayer, timeout } from 'vs/base/common/async';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { mapToString, setToString } from 'vs/base/common/map';
|
||||
import { basename } from 'path';
|
||||
import { mark } from 'vs/base/common/performance';
|
||||
import { basename } from 'vs/base/common/path';
|
||||
import { copy, renameIgnoreError, unlink } from 'vs/base/node/pfs';
|
||||
import { fill } from 'vs/base/common/arrays';
|
||||
|
||||
@@ -62,8 +61,8 @@ export interface IStorage extends IDisposable {
|
||||
getBoolean(key: string, fallbackValue: boolean): boolean;
|
||||
getBoolean(key: string, fallbackValue?: boolean): boolean | undefined;
|
||||
|
||||
getInteger(key: string, fallbackValue: number): number;
|
||||
getInteger(key: string, fallbackValue?: number): number | undefined;
|
||||
getNumber(key: string, fallbackValue: number): number;
|
||||
getNumber(key: string, fallbackValue?: number): number | undefined;
|
||||
|
||||
set(key: string, value: string | boolean | number): Promise<void>;
|
||||
delete(key: string): Promise<void>;
|
||||
@@ -84,7 +83,7 @@ export class Storage extends Disposable implements IStorage {
|
||||
|
||||
private static readonly DEFAULT_FLUSH_DELAY = 100;
|
||||
|
||||
private _onDidChangeStorage: Emitter<string> = this._register(new Emitter<string>());
|
||||
private readonly _onDidChangeStorage: Emitter<string> = this._register(new Emitter<string>());
|
||||
get onDidChangeStorage(): Event<string> { return this._onDidChangeStorage.event; }
|
||||
|
||||
private state = StorageState.None;
|
||||
@@ -196,9 +195,9 @@ export class Storage extends Disposable implements IStorage {
|
||||
return value === 'true';
|
||||
}
|
||||
|
||||
getInteger(key: string, fallbackValue: number): number;
|
||||
getInteger(key: string, fallbackValue?: number): number | undefined;
|
||||
getInteger(key: string, fallbackValue?: number): number | undefined {
|
||||
getNumber(key: string, fallbackValue: number): number;
|
||||
getNumber(key: string, fallbackValue?: number): number | undefined;
|
||||
getNumber(key: string, fallbackValue?: number): number | undefined {
|
||||
const value = this.get(key);
|
||||
|
||||
if (isUndefinedOrNull(value)) {
|
||||
@@ -326,8 +325,6 @@ export class SQLiteStorageDatabase implements IStorageDatabase {
|
||||
|
||||
get onDidChangeItemsExternal(): Event<IStorageItemsChangeEvent> { return Event.None; } // since we are the only client, there can be no external changes
|
||||
|
||||
private static measuredRequireDuration: boolean; // TODO@Ben remove me after a while
|
||||
|
||||
private static BUSY_OPEN_TIMEOUT = 2000; // timeout in ms to retry when opening DB fails with SQLITE_BUSY
|
||||
private static MAX_HOST_PARAMETERS = 256; // maximum number of parameters within a statement
|
||||
|
||||
@@ -355,7 +352,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase {
|
||||
rows.forEach(row => items.set(row.key, row.value));
|
||||
|
||||
if (this.logger.isTracing) {
|
||||
this.logger.trace(`[storage ${this.name}] getItems(): ${mapToString(items)}`);
|
||||
this.logger.trace(`[storage ${this.name}] getItems(): ${items.size} rows`);
|
||||
}
|
||||
|
||||
return items;
|
||||
@@ -598,21 +595,8 @@ export class SQLiteStorageDatabase implements IStorageDatabase {
|
||||
}
|
||||
|
||||
private doConnect(path: string): Promise<IDatabaseConnection> {
|
||||
|
||||
// TODO@Ben clean up performance markers
|
||||
return new Promise((resolve, reject) => {
|
||||
let measureRequireDuration = false;
|
||||
if (!SQLiteStorageDatabase.measuredRequireDuration) {
|
||||
SQLiteStorageDatabase.measuredRequireDuration = true;
|
||||
measureRequireDuration = true;
|
||||
|
||||
mark('willRequireSQLite');
|
||||
}
|
||||
import('vscode-sqlite3').then(sqlite3 => {
|
||||
if (measureRequireDuration) {
|
||||
mark('didRequireSQLite');
|
||||
}
|
||||
|
||||
const connection: IDatabaseConnection = {
|
||||
db: new (this.logger.isTracing ? sqlite3.verbose().Database : sqlite3.Database)(path, error => {
|
||||
if (error) {
|
||||
@@ -622,17 +606,12 @@ export class SQLiteStorageDatabase implements IStorageDatabase {
|
||||
// The following exec() statement serves two purposes:
|
||||
// - create the DB if it does not exist yet
|
||||
// - validate that the DB is not corrupt (the open() call does not throw otherwise)
|
||||
mark('willSetupSQLiteSchema');
|
||||
return this.exec(connection, [
|
||||
'PRAGMA user_version = 1;',
|
||||
'CREATE TABLE IF NOT EXISTS ItemTable (key TEXT UNIQUE ON CONFLICT REPLACE, value BLOB)'
|
||||
].join('')).then(() => {
|
||||
mark('didSetupSQLiteSchema');
|
||||
|
||||
return resolve(connection);
|
||||
}, error => {
|
||||
mark('didSetupSQLiteSchema');
|
||||
|
||||
return connection.db.close(() => reject(error));
|
||||
});
|
||||
}),
|
||||
|
||||
@@ -92,7 +92,7 @@ export function readToMatchingString(file: string, matchingString: string, chunk
|
||||
});
|
||||
}
|
||||
|
||||
let buffer = Buffer.allocUnsafe(maximumBytesToRead);
|
||||
const buffer = Buffer.allocUnsafe(maximumBytesToRead);
|
||||
let offset = 0;
|
||||
|
||||
function readChunk(): void {
|
||||
|
||||
BIN
src/vs/base/node/test/fixtures/extract.zip
vendored
Normal file
BIN
src/vs/base/node/test/fixtures/extract.zip
vendored
Normal file
Binary file not shown.
28
src/vs/base/node/test/zip.test.ts
Normal file
28
src/vs/base/node/test/zip.test.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import * as os from 'os';
|
||||
import { extract } from 'vs/base/node/zip';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { rimraf, exists } from 'vs/base/node/pfs';
|
||||
import { getPathFromAmdModule } from 'vs/base/common/amd';
|
||||
import { createCancelablePromise } from 'vs/base/common/async';
|
||||
|
||||
const fixtures = getPathFromAmdModule(require, './fixtures');
|
||||
|
||||
suite('Zip', () => {
|
||||
|
||||
test('extract should handle directories', () => {
|
||||
const fixture = path.join(fixtures, 'extract.zip');
|
||||
const target = path.join(os.tmpdir(), generateUuid());
|
||||
|
||||
return createCancelablePromise(token => extract(fixture, target, {}, token)
|
||||
.then(() => exists(path.join(target, 'extension')))
|
||||
.then(exists => assert(exists))
|
||||
.then(() => rimraf(target)));
|
||||
});
|
||||
});
|
||||
231
src/vs/base/node/zip.ts
Normal file
231
src/vs/base/node/zip.ts
Normal file
@@ -0,0 +1,231 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import { createWriteStream, WriteStream } from 'fs';
|
||||
import { Readable } from 'stream';
|
||||
import { nfcall, ninvoke, Sequencer, createCancelablePromise } from 'vs/base/common/async';
|
||||
import { mkdirp, rimraf } from 'vs/base/node/pfs';
|
||||
import { open as _openZip, Entry, ZipFile } from 'yauzl';
|
||||
import * as yazl from 'yazl';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
export interface IExtractOptions {
|
||||
overwrite?: boolean;
|
||||
|
||||
/**
|
||||
* Source path within the ZIP archive. Only the files contained in this
|
||||
* path will be extracted.
|
||||
*/
|
||||
sourcePath?: string;
|
||||
}
|
||||
|
||||
interface IOptions {
|
||||
sourcePathRegex: RegExp;
|
||||
}
|
||||
|
||||
export type ExtractErrorType = 'CorruptZip' | 'Incomplete';
|
||||
|
||||
export class ExtractError extends Error {
|
||||
|
||||
readonly type?: ExtractErrorType;
|
||||
readonly cause: Error;
|
||||
|
||||
constructor(type: ExtractErrorType | undefined, cause: Error) {
|
||||
let message = cause.message;
|
||||
|
||||
switch (type) {
|
||||
case 'CorruptZip': message = `Corrupt ZIP: ${message}`; break;
|
||||
}
|
||||
|
||||
super(message);
|
||||
this.type = type;
|
||||
this.cause = cause;
|
||||
}
|
||||
}
|
||||
|
||||
function modeFromEntry(entry: Entry) {
|
||||
const attr = entry.externalFileAttributes >> 16 || 33188;
|
||||
|
||||
return [448 /* S_IRWXU */, 56 /* S_IRWXG */, 7 /* S_IRWXO */]
|
||||
.map(mask => attr & mask)
|
||||
.reduce((a, b) => a + b, attr & 61440 /* S_IFMT */);
|
||||
}
|
||||
|
||||
function toExtractError(err: Error): ExtractError {
|
||||
if (err instanceof ExtractError) {
|
||||
return err;
|
||||
}
|
||||
|
||||
let type: ExtractErrorType | undefined = undefined;
|
||||
|
||||
if (/end of central directory record signature not found/.test(err.message)) {
|
||||
type = 'CorruptZip';
|
||||
}
|
||||
|
||||
return new ExtractError(type, err);
|
||||
}
|
||||
|
||||
function extractEntry(stream: Readable, fileName: string, mode: number, targetPath: string, options: IOptions, token: CancellationToken): Promise<void> {
|
||||
const dirName = path.dirname(fileName);
|
||||
const targetDirName = path.join(targetPath, dirName);
|
||||
if (targetDirName.indexOf(targetPath) !== 0) {
|
||||
return Promise.reject(new Error(nls.localize('invalid file', "Error extracting {0}. Invalid file.", fileName)));
|
||||
}
|
||||
const targetFileName = path.join(targetPath, fileName);
|
||||
|
||||
let istream: WriteStream;
|
||||
|
||||
Event.once(token.onCancellationRequested)(() => {
|
||||
if (istream) {
|
||||
istream.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.resolve(mkdirp(targetDirName, undefined, token)).then(() => new Promise<void>((c, e) => {
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
istream = createWriteStream(targetFileName, { mode });
|
||||
istream.once('close', () => c());
|
||||
istream.once('error', e);
|
||||
stream.once('error', e);
|
||||
stream.pipe(istream);
|
||||
} catch (error) {
|
||||
e(error);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
function extractZip(zipfile: ZipFile, targetPath: string, options: IOptions, token: CancellationToken): Promise<void> {
|
||||
let last = createCancelablePromise<void>(() => Promise.resolve());
|
||||
let extractedEntriesCount = 0;
|
||||
|
||||
Event.once(token.onCancellationRequested)(() => {
|
||||
last.cancel();
|
||||
zipfile.close();
|
||||
});
|
||||
|
||||
return new Promise((c, e) => {
|
||||
const throttler = new Sequencer();
|
||||
|
||||
const readNextEntry = (token: CancellationToken) => {
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
extractedEntriesCount++;
|
||||
zipfile.readEntry();
|
||||
};
|
||||
|
||||
zipfile.once('error', e);
|
||||
zipfile.once('close', () => last.then(() => {
|
||||
if (token.isCancellationRequested || zipfile.entryCount === extractedEntriesCount) {
|
||||
c();
|
||||
} else {
|
||||
e(new ExtractError('Incomplete', new Error(nls.localize('incompleteExtract', "Incomplete. Found {0} of {1} entries", extractedEntriesCount, zipfile.entryCount))));
|
||||
}
|
||||
}, e));
|
||||
zipfile.readEntry();
|
||||
zipfile.on('entry', (entry: Entry) => {
|
||||
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!options.sourcePathRegex.test(entry.fileName)) {
|
||||
readNextEntry(token);
|
||||
return;
|
||||
}
|
||||
|
||||
const fileName = entry.fileName.replace(options.sourcePathRegex, '');
|
||||
|
||||
// directory file names end with '/'
|
||||
if (/\/$/.test(fileName)) {
|
||||
const targetFileName = path.join(targetPath, fileName);
|
||||
last = createCancelablePromise(token => mkdirp(targetFileName, undefined, token).then(() => readNextEntry(token)).then(undefined, e));
|
||||
return;
|
||||
}
|
||||
|
||||
const stream = ninvoke(zipfile, zipfile.openReadStream, entry);
|
||||
const mode = modeFromEntry(entry);
|
||||
|
||||
last = createCancelablePromise(token => throttler.queue(() => stream.then(stream => extractEntry(stream, fileName, mode, targetPath, options, token).then(() => readNextEntry(token)))).then(null!, e));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function openZip(zipFile: string, lazy: boolean = false): Promise<ZipFile> {
|
||||
return nfcall<ZipFile>(_openZip, zipFile, lazy ? { lazyEntries: true } : undefined)
|
||||
.then(undefined, err => Promise.reject(toExtractError(err)));
|
||||
}
|
||||
|
||||
export interface IFile {
|
||||
path: string;
|
||||
contents?: Buffer | string;
|
||||
localPath?: string;
|
||||
}
|
||||
|
||||
export function zip(zipPath: string, files: IFile[]): Promise<string> {
|
||||
return new Promise<string>((c, e) => {
|
||||
const zip = new yazl.ZipFile();
|
||||
files.forEach(f => {
|
||||
if (f.contents) {
|
||||
zip.addBuffer(typeof f.contents === 'string' ? Buffer.from(f.contents, 'utf8') : f.contents, f.path);
|
||||
} else if (f.localPath) {
|
||||
zip.addFile(f.localPath, f.path);
|
||||
}
|
||||
});
|
||||
zip.end();
|
||||
|
||||
const zipStream = createWriteStream(zipPath);
|
||||
zip.outputStream.pipe(zipStream);
|
||||
|
||||
zip.outputStream.once('error', e);
|
||||
zipStream.once('error', e);
|
||||
zipStream.once('finish', () => c(zipPath));
|
||||
});
|
||||
}
|
||||
|
||||
export function extract(zipPath: string, targetPath: string, options: IExtractOptions = {}, token: CancellationToken): Promise<void> {
|
||||
const sourcePathRegex = new RegExp(options.sourcePath ? `^${options.sourcePath}` : '');
|
||||
|
||||
let promise = openZip(zipPath, true);
|
||||
|
||||
if (options.overwrite) {
|
||||
promise = promise.then(zipfile => rimraf(targetPath).then(() => zipfile));
|
||||
}
|
||||
|
||||
return promise.then(zipfile => extractZip(zipfile, targetPath, { sourcePathRegex }, token));
|
||||
}
|
||||
|
||||
function read(zipPath: string, filePath: string): Promise<Readable> {
|
||||
return openZip(zipPath).then(zipfile => {
|
||||
return new Promise<Readable>((c, e) => {
|
||||
zipfile.on('entry', (entry: Entry) => {
|
||||
if (entry.fileName === filePath) {
|
||||
ninvoke<Readable>(zipfile, zipfile.openReadStream, entry).then(stream => c(stream), err => e(err));
|
||||
}
|
||||
});
|
||||
|
||||
zipfile.once('close', () => e(new Error(nls.localize('notFound', "{0} not found inside zip.", filePath))));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function buffer(zipPath: string, filePath: string): Promise<Buffer> {
|
||||
return read(zipPath, filePath).then(stream => {
|
||||
return new Promise<Buffer>((c, e) => {
|
||||
const buffers: Buffer[] = [];
|
||||
stream.once('error', e);
|
||||
stream.on('data', b => buffers.push(b as Buffer));
|
||||
stream.on('end', () => c(Buffer.concat(buffers)));
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user