Merge from vscode bead496a613e475819f89f08e9e882b841bc1fe8 (#14883)

* Merge from vscode bead496a613e475819f89f08e9e882b841bc1fe8

* Bump distro

* Upgrade GCC to 4.9 due to yarn install errors

* Update build image

* Fix bootstrap base url

* Bump distro

* Fix build errors

* Update source map file

* Disable checkbox for blocking migration issues (#15131)

* disable checkbox for blocking issues

* wip

* disable checkbox fixes

* fix strings

* Remove duplicate tsec command

* Default to off for tab color if settings not present

* re-skip failing tests

* Fix mocha error

* Bump sqlite version & fix notebooks search view

* Turn off esbuild warnings

* Update esbuild log level

* Fix overflowactionbar tests

* Fix ts-ignore in dropdown tests

* cleanup/fixes

* Fix hygiene

* Bundle in entire zone.js module

* Remove extra constructor param

* bump distro for web compile break

* bump distro for web compile break v2

* Undo log level change

* New distro

* Fix integration test scripts

* remove the "no yarn.lock changes" workflow

* fix scripts v2

* Update unit test scripts

* Ensure ads-kerberos2 updates in .vscodeignore

* Try fix unit tests

* Upload crash reports

* remove nogpu

* always upload crashes

* Use bash script

* Consolidate data/ext dir names

* Create in tmp directory

Co-authored-by: chlafreniere <hichise@gmail.com>
Co-authored-by: Christopher Suh <chsuh@microsoft.com>
Co-authored-by: chgagnon <chgagnon@microsoft.com>
This commit is contained in:
Karl Burtram
2021-04-27 14:01:59 -07:00
committed by GitHub
parent 7e1c0076ba
commit 867a963882
1817 changed files with 81812 additions and 50843 deletions

View File

@@ -7,7 +7,6 @@ import * as fs from 'fs';
import { rtrim } from 'vs/base/common/strings';
import { sep, join, normalize, dirname, basename } from 'vs/base/common/path';
import { readdirSync } from 'vs/base/node/pfs';
import { promisify } from 'util';
/**
* Copied from: https://github.com/microsoft/vscode-node-debug/blob/master/src/node/pathUtilities.ts#L83
@@ -53,7 +52,7 @@ export function realcaseSync(path: string): string | null {
export async function realpath(path: string): Promise<string> {
try {
return await promisify(fs.realpath)(path);
return await fs.promises.realpath(path);
} catch (error) {
// We hit an error calling fs.realpath(). Since fs.realpath() is doing some path normalization
@@ -63,7 +62,7 @@ export async function realpath(path: string): Promise<string> {
// to not resolve links but to simply see if the path is read accessible or not.
const normalizedPath = normalizePath(path);
await promisify(fs.access)(normalizedPath, fs.constants.R_OK);
await fs.promises.access(normalizedPath, fs.constants.R_OK);
return normalizedPath;
}

View File

@@ -2,9 +2,9 @@
* 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
'use strict';
/**
* @param {NodeRequire} nodeRequire
@@ -30,22 +30,6 @@ function factory(nodeRequire, path, fs, perf) {
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>}
@@ -54,53 +38,20 @@ function factory(nodeRequire, path, fs, perf) {
return new Promise((c, e) => fs.mkdir(dir, { recursive: true }, 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;
});
return new Promise((c, e) => fs.rmdir(location, { recursive: true }, err => (err && err.code !== 'ENOENT') ? e(err) : c()));
}
/**
* @param {string} file
* @returns {Promise<string>}
*/
function readFile(file) {
return new Promise(function (resolve, reject) {
fs.readFile(file, 'utf8', function (err, data) {
if (err) {
reject(err);
return;
}
resolve(data);
});
});
return new Promise((c, e) => fs.readFile(file, 'utf8', (err, data) => err ? e(err) : c(data)));
}
/**
@@ -109,18 +60,9 @@ function factory(nodeRequire, path, fs, perf) {
* @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();
});
});
return new Promise((c, e) => fs.writeFile(file, content, 'utf8', err => err ? e(err) : c()));
}
/**
* @param {string} userDataPath
* @returns {object}
@@ -186,10 +128,10 @@ function factory(nodeRequire, path, fs, perf) {
const initialLocale = locale;
perf.mark('nlsGeneration:start');
perf.mark('code/willGenerateNls');
const defaultResult = function (locale) {
perf.mark('nlsGeneration:end');
perf.mark('code/didGenerateNls');
return Promise.resolve({ locale: locale, availableLanguages: {} });
};
try {
@@ -240,7 +182,7 @@ function factory(nodeRequire, path, fs, perf) {
if (fileExists) {
// We don't wait for this. No big harm if we can't touch
touch(coreLocation).catch(() => { });
perf.mark('nlsGeneration:end');
perf.mark('code/didGenerateNls');
return result;
}
return mkdirp(coreLocation).then(() => {
@@ -279,7 +221,7 @@ function factory(nodeRequire, path, fs, perf) {
writes.push(writeFile(translationsConfigFile, JSON.stringify(packConfig.translations)));
return Promise.all(writes);
}).then(() => {
perf.mark('nlsGeneration:end');
perf.mark('code/didGenerateNls');
return result;
}).catch(err => {
console.error('Generating translation files failed.', err);
@@ -301,8 +243,10 @@ function factory(nodeRequire, path, fs, perf) {
}
// @ts-ignore
if (typeof define === 'function') {
// amd
// @ts-ignore
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');

View File

@@ -3,17 +3,18 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fs from 'fs';
import { tmpdir } from 'os';
import { join } from 'vs/base/common/path';
import { Queue } from 'vs/base/common/async';
import * as fs from 'fs';
import * as os from 'os';
import * as platform from 'vs/base/common/platform';
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
import { Event } from 'vs/base/common/event';
import { promisify } from 'util';
import { isRootOrDriveLetter } from 'vs/base/common/extpath';
import { isEqualOrParent, isRootOrDriveLetter } from 'vs/base/common/extpath';
import { generateUuid } from 'vs/base/common/uuid';
import { normalizeNFC } from 'vs/base/common/normalization';
//#region Constants
// See https://github.com/microsoft/vscode/issues/30180
const WIN32_MAX_FILE_SIZE = 300 * 1024 * 1024; // 300 MB
const GENERAL_MAX_FILE_SIZE = 16 * 1024 * 1024 * 1024; // 16 GB
@@ -25,6 +26,10 @@ const GENERAL_MAX_HEAP_SIZE = 700 * 2 * 1024 * 1024; // 1400 MB
export const MAX_FILE_SIZE = process.arch === 'ia32' ? WIN32_MAX_FILE_SIZE : GENERAL_MAX_FILE_SIZE;
export const MAX_HEAP_SIZE = process.arch === 'ia32' ? WIN32_MAX_HEAP_SIZE : GENERAL_MAX_HEAP_SIZE;
//#endregion
//#region rimraf
export enum RimRafMode {
/**
@@ -40,12 +45,19 @@ export enum RimRafMode {
MOVE
}
/**
* Allows to delete the provied path (either file or folder) recursively
* with the options:
* - `UNLINK`: direct removal from disk
* - `MOVE`: faster variant that first moves the target to temp dir and then
* deletes it in the background without waiting for that to finish.
*/
export async function rimraf(path: string, mode = RimRafMode.UNLINK): Promise<void> {
if (isRootOrDriveLetter(path)) {
throw new Error('rimraf - will refuse to recursively delete root');
}
// delete: via unlink
// delete: via rmDir
if (mode === RimRafMode.UNLINK) {
return rimrafUnlink(path);
}
@@ -54,32 +66,17 @@ export async function rimraf(path: string, mode = RimRafMode.UNLINK): Promise<vo
return rimrafMove(path);
}
async function rimrafUnlink(path: string): Promise<void> {
async function rimrafMove(path: string): Promise<void> {
try {
const stat = await lstat(path);
// Folder delete (recursive) - NOT for symbolic links though!
if (stat.isDirectory() && !stat.isSymbolicLink()) {
// Children
const children = await readdir(path);
await Promise.all(children.map(child => rimrafUnlink(join(path, child))));
// Folder
await promisify(fs.rmdir)(path);
const pathInTemp = join(tmpdir(), generateUuid());
try {
await fs.promises.rename(path, pathInTemp);
} catch (error) {
return rimrafUnlink(path); // if rename fails, delete without tmp dir
}
// Single file delete
else {
// chmod as needed to allow for unlink
const mode = stat.mode;
if (!(mode & 128)) { // 128 === 0200
await chmod(path, mode | 128);
}
return unlink(path);
}
// Delete but do not return as promise
rimrafUnlink(pathInTemp).catch(error => {/* ignore */ });
} catch (error) {
if (error.code !== 'ENOENT') {
throw error;
@@ -87,22 +84,8 @@ async function rimrafUnlink(path: string): Promise<void> {
}
}
async function rimrafMove(path: string): Promise<void> {
try {
const pathInTemp = join(os.tmpdir(), generateUuid());
try {
await rename(path, pathInTemp);
} catch (error) {
return rimrafUnlink(path); // if rename fails, delete without tmp dir
}
// Delete but do not return as promise
rimrafUnlink(pathInTemp);
} catch (error) {
if (error.code !== 'ENOENT') {
throw error;
}
}
async function rimrafUnlink(path: string): Promise<void> {
return fs.promises.rmdir(path, { recursive: true, maxRetries: 3 });
}
export function rimrafSync(path: string): void {
@@ -110,170 +93,281 @@ export function rimrafSync(path: string): void {
throw new Error('rimraf - will refuse to recursively delete root');
}
fs.rmdirSync(path, { recursive: true });
}
//#endregion
//#region readdir with NFC support (macos)
export interface IDirent {
name: string;
isFile(): boolean;
isDirectory(): boolean;
isSymbolicLink(): boolean;
}
/**
* Drop-in replacement of `fs.readdir` with support
* for converting from macOS NFD unicon form to NFC
* (https://github.com/nodejs/node/issues/2165)
*/
export async function readdir(path: string): Promise<string[]>;
export async function readdir(path: string, options: { withFileTypes: true }): Promise<IDirent[]>;
export async function readdir(path: string, options?: { withFileTypes: true }): Promise<(string | IDirent)[]> {
return handleDirectoryChildren(await (options ? safeReaddirWithFileTypes(path) : fs.promises.readdir(path)));
}
async function safeReaddirWithFileTypes(path: string): Promise<IDirent[]> {
try {
const stat = fs.lstatSync(path);
// Folder delete (recursive) - NOT for symbolic links though!
if (stat.isDirectory() && !stat.isSymbolicLink()) {
// Children
const children = readdirSync(path);
children.map(child => rimrafSync(join(path, child)));
// Folder
fs.rmdirSync(path);
}
// Single file delete
else {
// chmod as needed to allow for unlink
const mode = stat.mode;
if (!(mode & 128)) { // 128 === 0200
fs.chmodSync(path, mode | 128);
}
return fs.unlinkSync(path);
}
return await fs.promises.readdir(path, { withFileTypes: true });
} catch (error) {
if (error.code !== 'ENOENT') {
throw error;
}
}
}
export async function readdir(path: string): Promise<string[]> {
return handleDirectoryChildren(await promisify(fs.readdir)(path));
}
export async function readdirWithFileTypes(path: string): Promise<fs.Dirent[]> {
const children = await promisify(fs.readdir)(path, { withFileTypes: true });
// Mac: uses NFD unicode form on disk, but we want NFC
// See also https://github.com/nodejs/node/issues/2165
if (platform.isMacintosh) {
for (const child of children) {
child.name = normalizeNFC(child.name);
}
console.warn('[node.js fs] readdir with filetypes failed with error: ', error);
}
return children;
// Fallback to manually reading and resolving each
// children of the folder in case we hit an error
// previously.
// This can only really happen on exotic file systems
// such as explained in #115645 where we get entries
// from `readdir` that we can later not `lstat`.
const result: IDirent[] = [];
const children = await readdir(path);
for (const child of children) {
let isFile = false;
let isDirectory = false;
let isSymbolicLink = false;
try {
const lstat = await fs.promises.lstat(join(path, child));
isFile = lstat.isFile();
isDirectory = lstat.isDirectory();
isSymbolicLink = lstat.isSymbolicLink();
} catch (error) {
console.warn('[node.js fs] unexpected error from lstat after readdir: ', error);
}
result.push({
name: child,
isFile: () => isFile,
isDirectory: () => isDirectory,
isSymbolicLink: () => isSymbolicLink
});
}
return result;
}
/**
* Drop-in replacement of `fs.readdirSync` with support
* for converting from macOS NFD unicon form to NFC
* (https://github.com/nodejs/node/issues/2165)
*/
export function readdirSync(path: string): string[] {
return handleDirectoryChildren(fs.readdirSync(path));
}
function handleDirectoryChildren(children: string[]): string[] {
// Mac: uses NFD unicode form on disk, but we want NFC
// See also https://github.com/nodejs/node/issues/2165
if (platform.isMacintosh) {
return children.map(child => normalizeNFC(child));
}
function handleDirectoryChildren(children: string[]): string[];
function handleDirectoryChildren(children: IDirent[]): IDirent[];
function handleDirectoryChildren(children: (string | IDirent)[]): (string | IDirent)[];
function handleDirectoryChildren(children: (string | IDirent)[]): (string | IDirent)[] {
return children.map(child => {
return children;
}
// Mac: uses NFD unicode form on disk, but we want NFC
// See also https://github.com/nodejs/node/issues/2165
export function exists(path: string): Promise<boolean> {
return promisify(fs.exists)(path);
}
export function chmod(path: string, mode: number): Promise<void> {
return promisify(fs.chmod)(path, mode);
}
export function stat(path: string): Promise<fs.Stats> {
return promisify(fs.stat)(path);
}
export interface IStatAndLink {
// The stats of the file. If the file is a symbolic
// link, the stats will be of that target file and
// not the link itself.
// If the file is a symbolic link pointing to a non
// existing file, the stat will be of the link and
// the `dangling` flag will indicate this.
stat: fs.Stats;
// Will be provided if the resource is a symbolic link
// on disk. Use the `dangling` flag to find out if it
// points to a resource that does not exist on disk.
symbolicLink?: { dangling: boolean };
}
export async function statLink(path: string): Promise<IStatAndLink> {
// First stat the link
let lstats: fs.Stats | undefined;
try {
lstats = await lstat(path);
// Return early if the stat is not a symbolic link at all
if (!lstats.isSymbolicLink()) {
return { stat: lstats };
}
} catch (error) {
/* ignore - use stat() instead */
}
// If the stat is a symbolic link or failed to stat, use fs.stat()
// which for symbolic links will stat the target they point to
try {
const stats = await stat(path);
return { stat: stats, symbolicLink: lstats?.isSymbolicLink() ? { dangling: false } : undefined };
} catch (error) {
// If the link points to a non-existing file we still want
// to return it as result while setting dangling: true flag
if (error.code === 'ENOENT' && lstats) {
return { stat: lstats, symbolicLink: { dangling: true } };
if (typeof child === 'string') {
return isMacintosh ? normalizeNFC(child) : child;
}
throw error;
child.name = isMacintosh ? normalizeNFC(child.name) : child.name;
return child;
});
}
/**
* A convinience method to read all children of a path that
* are directories.
*/
export async function readDirsInDir(dirPath: string): Promise<string[]> {
const children = await readdir(dirPath);
const directories: string[] = [];
for (const child of children) {
if (await SymlinkSupport.existsDirectory(join(dirPath, child))) {
directories.push(child);
}
}
return directories;
}
//#endregion
//#region whenDeleted()
/**
* A `Promise` that resolves when the provided `path`
* is deleted from disk.
*/
export function whenDeleted(path: string, intervalMs = 1000): Promise<void> {
return new Promise<void>(resolve => {
let running = false;
const interval = setInterval(() => {
if (!running) {
running = true;
fs.access(path, err => {
running = false;
if (err) {
clearInterval(interval);
resolve(undefined);
}
});
}
}, intervalMs);
});
}
//#endregion
//#region Methods with symbolic links support
export namespace SymlinkSupport {
export interface IStats {
// The stats of the file. If the file is a symbolic
// link, the stats will be of that target file and
// not the link itself.
// If the file is a symbolic link pointing to a non
// existing file, the stat will be of the link and
// the `dangling` flag will indicate this.
stat: fs.Stats;
// Will be provided if the resource is a symbolic link
// on disk. Use the `dangling` flag to find out if it
// points to a resource that does not exist on disk.
symbolicLink?: { dangling: boolean };
}
/**
* Resolves the `fs.Stats` of the provided path. If the path is a
* symbolic link, the `fs.Stats` will be from the target it points
* to. If the target does not exist, `dangling: true` will be returned
* as `symbolicLink` value.
*/
export async function stat(path: string): Promise<IStats> {
// First stat the link
let lstats: fs.Stats | undefined;
try {
lstats = await fs.promises.lstat(path);
// Return early if the stat is not a symbolic link at all
if (!lstats.isSymbolicLink()) {
return { stat: lstats };
}
} catch (error) {
/* ignore - use stat() instead */
}
// If the stat is a symbolic link or failed to stat, use fs.stat()
// which for symbolic links will stat the target they point to
try {
const stats = await fs.promises.stat(path);
return { stat: stats, symbolicLink: lstats?.isSymbolicLink() ? { dangling: false } : undefined };
} catch (error) {
// If the link points to a non-existing file we still want
// to return it as result while setting dangling: true flag
if (error.code === 'ENOENT' && lstats) {
return { stat: lstats, symbolicLink: { dangling: true } };
}
// Windows: workaround a node.js bug where reparse points
// are not supported (https://github.com/nodejs/node/issues/36790)
if (isWindows && error.code === 'EACCES') {
try {
const stats = await fs.promises.stat(await fs.promises.readlink(path));
return { stat: stats, symbolicLink: { dangling: false } };
} catch (error) {
// If the link points to a non-existing file we still want
// to return it as result while setting dangling: true flag
if (error.code === 'ENOENT' && lstats) {
return { stat: lstats, symbolicLink: { dangling: true } };
}
throw error;
}
}
throw error;
}
}
/**
* Figures out if the `path` exists and is a file with support
* for symlinks.
*
* Note: this will return `false` for a symlink that exists on
* disk but is dangling (pointing to a non-existing path).
*
* Use `exists` if you only care about the path existing on disk
* or not without support for symbolic links.
*/
export async function existsFile(path: string): Promise<boolean> {
try {
const { stat, symbolicLink } = await SymlinkSupport.stat(path);
return stat.isFile() && symbolicLink?.dangling !== true;
} catch (error) {
// Ignore, path might not exist
}
return false;
}
/**
* Figures out if the `path` exists and is a directory with support for
* symlinks.
*
* Note: this will return `false` for a symlink that exists on
* disk but is dangling (pointing to a non-existing path).
*
* Use `exists` if you only care about the path existing on disk
* or not without support for symbolic links.
*/
export async function existsDirectory(path: string): Promise<boolean> {
try {
const { stat, symbolicLink } = await SymlinkSupport.stat(path);
return stat.isDirectory() && symbolicLink?.dangling !== true;
} catch (error) {
// Ignore, path might not exist
}
return false;
}
}
export function lstat(path: string): Promise<fs.Stats> {
return promisify(fs.lstat)(path);
}
//#endregion
export function rename(oldPath: string, newPath: string): Promise<void> {
return promisify(fs.rename)(oldPath, newPath);
}
export function renameIgnoreError(oldPath: string, newPath: string): Promise<void> {
return new Promise(resolve => fs.rename(oldPath, newPath, () => resolve()));
}
export function unlink(path: string): Promise<void> {
return promisify(fs.unlink)(path);
}
export function symlink(target: string, path: string, type?: string): Promise<void> {
return promisify(fs.symlink)(target, path, type);
}
export function truncate(path: string, len: number): Promise<void> {
return promisify(fs.truncate)(path, len);
}
export function readFile(path: string): Promise<Buffer>;
export function readFile(path: string, encoding: string): Promise<string>;
export function readFile(path: string, encoding?: string): Promise<Buffer | string> {
return promisify(fs.readFile)(path, encoding);
}
export async function mkdirp(path: string, mode?: number): Promise<void> {
return promisify(fs.mkdir)(path, { mode, recursive: true });
}
// According to node.js docs (https://nodejs.org/docs/v6.5.0/api/fs.html#fs_fs_writefile_file_data_options_callback)
// it is not safe to call writeFile() on the same path multiple times without waiting for the callback to return.
// Therefor we use a Queue on the path that is given to us to sequentialize calls to the same path properly.
const writeFilePathQueues: Map<string, Queue<void>> = new Map();
//#region Write File
/**
* Same as `fs.writeFile` but with an additional call to
* `fs.fdatasync` after writing to ensure changes are
* flushed to disk.
*
* In addition, multiple writes to the same path are queued.
*/
export function writeFile(path: string, data: string, options?: IWriteFileOptions): Promise<void>;
export function writeFile(path: string, data: Buffer, options?: IWriteFileOptions): Promise<void>;
export function writeFile(path: string, data: Uint8Array, options?: IWriteFileOptions): Promise<void>;
@@ -288,9 +382,14 @@ export function writeFile(path: string, data: string | Buffer | Uint8Array, opti
});
}
// According to node.js docs (https://nodejs.org/docs/v6.5.0/api/fs.html#fs_fs_writefile_file_data_options_callback)
// it is not safe to call writeFile() on the same path multiple times without waiting for the callback to return.
// Therefor we use a Queue on the path that is given to us to sequentialize calls to the same path properly.
const writeFilePathQueues: Map<string, Queue<void>> = new Map();
function toQueueKey(path: string): string {
let queueKey = path;
if (platform.isWindows || platform.isMacintosh) {
if (isWindows || isMacintosh) {
queueKey = queueKey.toLowerCase(); // accommodate for case insensitive file systems
}
@@ -365,6 +464,11 @@ function doWriteFileAndFlush(path: string, data: string | Buffer | Uint8Array, o
});
}
/**
* Same as `fs.writeFileSync` but with an additional call to
* `fs.fdatasyncSync` after writing to ensure changes are
* flushed to disk.
*/
export function writeFileSync(path: string, data: string | Buffer, options?: IWriteFileOptions): void {
const ensuredOptions = ensureWriteOptions(options);
@@ -394,92 +498,57 @@ export function writeFileSync(path: string, data: string | Buffer, options?: IWr
function ensureWriteOptions(options?: IWriteFileOptions): IEnsuredWriteFileOptions {
if (!options) {
return { mode: 0o666, flag: 'w' };
return { mode: 0o666 /* default node.js mode for files */, flag: 'w' };
}
return {
mode: typeof options.mode === 'number' ? options.mode : 0o666,
mode: typeof options.mode === 'number' ? options.mode : 0o666 /* default node.js mode for files */,
flag: typeof options.flag === 'string' ? options.flag : 'w'
};
}
export async function readDirsInDir(dirPath: string): Promise<string[]> {
const children = await readdir(dirPath);
const directories: string[] = [];
//#endregion
for (const child of children) {
if (await dirExists(join(dirPath, child))) {
directories.push(child);
}
}
return directories;
}
export async function dirExists(path: string): Promise<boolean> {
try {
const fileStat = await stat(path);
return fileStat.isDirectory();
} catch (error) {
return false;
}
}
export async function fileExists(path: string): Promise<boolean> {
try {
const fileStat = await stat(path);
return fileStat.isFile();
} catch (error) {
return false;
}
}
export function whenDeleted(path: string): Promise<void> {
// Complete when wait marker file is deleted
return new Promise<void>(resolve => {
let running = false;
const interval = setInterval(() => {
if (!running) {
running = true;
fs.exists(path, exists => {
running = false;
if (!exists) {
clearInterval(interval);
resolve(undefined);
}
});
}
}, 1000);
});
}
//#region Move / Copy
/**
* A drop-in replacement for `fs.rename` that:
* - updates the `mtime` of the `source` after the operation
* - allows to move across multiple disks
*/
export async function move(source: string, target: string): Promise<void> {
if (source === target) {
return Promise.resolve();
return; // simulate node.js behaviour here and do a no-op if paths match
}
// We have been updating `mtime` for move operations for files since the
// beginning for reasons that are no longer quite clear, but changing
// this could be risky as well. As such, trying to reason about it:
// It is very common as developer to have file watchers enabled that watch
// the current workspace for changes. Updating the `mtime` might make it
// easier for these watchers to recognize an actual change. Since changing
// a source code file also updates the `mtime`, moving a file should do so
// as well because conceptually it is a change of a similar category.
async function updateMtime(path: string): Promise<void> {
const stat = await lstat(path);
if (stat.isDirectory() || stat.isSymbolicLink()) {
return Promise.resolve(); // only for files
}
const fd = await promisify(fs.open)(path, 'a');
try {
await promisify(fs.futimes)(fd, stat.atime, new Date());
} catch (error) {
//ignore
}
const stat = await fs.promises.lstat(path);
if (stat.isDirectory() || stat.isSymbolicLink()) {
return; // only for files
}
return promisify(fs.close)(fd);
const fh = await fs.promises.open(path, 'a');
try {
await fh.utimes(stat.atime, new Date());
} finally {
await fh.close();
}
} catch (error) {
// Ignore any error
}
}
try {
await rename(source, target);
await fs.promises.rename(source, target);
await updateMtime(target);
} catch (error) {
@@ -492,7 +561,7 @@ export async function move(source: string, target: string): Promise<void> {
// 2.) The user tries to rename a file/folder that ends with a dot. This is not
// really possible to move then, at least on UNC devices.
if (source.toLowerCase() !== target.toLowerCase() && error.code === 'EXDEV' || source.endsWith('.')) {
await copy(source, target);
await copy(source, target, { preserveSymlinks: false /* copying to another device */ });
await rimraf(source, RimRafMode.MOVE);
await updateMtime(target);
} else {
@@ -501,59 +570,119 @@ export async function move(source: string, target: string): Promise<void> {
}
}
export async function copy(source: string, target: string, copiedSourcesIn?: { [path: string]: boolean }): Promise<void> {
const copiedSources = copiedSourcesIn ? copiedSourcesIn : Object.create(null);
interface ICopyPayload {
readonly root: { source: string, target: string };
readonly options: { preserveSymlinks: boolean };
readonly handledSourcePaths: Set<string>;
}
const fileStat = await stat(source);
if (!fileStat.isDirectory()) {
return doCopyFile(source, target, fileStat.mode & 511);
/**
* Recursively copies all of `source` to `target`.
*
* The options `preserveSymlinks` configures how symbolic
* links should be handled when encountered. Set to
* `false` to not preserve them and `true` otherwise.
*/
export async function copy(source: string, target: string, options: { preserveSymlinks: boolean }): Promise<void> {
return doCopy(source, target, { root: { source, target }, options, handledSourcePaths: new Set<string>() });
}
// When copying a file or folder, we want to preserve the mode
// it had and as such provide it when creating. However, modes
// can go beyond what we expect (see link below), so we mask it.
// (https://github.com/nodejs/node-v0.x-archive/issues/3045#issuecomment-4862588)
const COPY_MODE_MASK = 0o777;
async function doCopy(source: string, target: string, payload: ICopyPayload): Promise<void> {
// Keep track of paths already copied to prevent
// cycles from symbolic links to cause issues
if (payload.handledSourcePaths.has(source)) {
return;
} else {
payload.handledSourcePaths.add(source);
}
if (copiedSources[source]) {
return Promise.resolve(); // escape when there are cycles (can happen with symlinks)
const { stat, symbolicLink } = await SymlinkSupport.stat(source);
// Symlink
if (symbolicLink) {
if (symbolicLink.dangling) {
return; // do not copy dangling symbolic links (https://github.com/microsoft/vscode/issues/111621)
}
// Try to re-create the symlink unless `preserveSymlinks: false`
if (payload.options.preserveSymlinks) {
try {
return await doCopySymlink(source, target, payload);
} catch (error) {
// in any case of an error fallback to normal copy via dereferencing
console.warn('[node.js fs] copy of symlink failed: ', error);
}
}
}
copiedSources[source] = true; // remember as copied
// Folder
if (stat.isDirectory()) {
return doCopyDirectory(source, target, stat.mode & COPY_MODE_MASK, payload);
}
// File or file-like
else {
return doCopyFile(source, target, stat.mode & COPY_MODE_MASK);
}
}
async function doCopyDirectory(source: string, target: string, mode: number, payload: ICopyPayload): Promise<void> {
// Create folder
await mkdirp(target, fileStat.mode & 511);
await fs.promises.mkdir(target, { recursive: true, mode });
// Copy each file recursively
const files = await readdir(source);
for (let i = 0; i < files.length; i++) {
const file = files[i];
await copy(join(source, file), join(target, file), copiedSources);
for (const file of files) {
await doCopy(join(source, file), join(target, file), payload);
}
}
async function doCopyFile(source: string, target: string, mode: number): Promise<void> {
return new Promise((resolve, reject) => {
const reader = fs.createReadStream(source);
const writer = fs.createWriteStream(target, { mode });
let finished = false;
const finish = (error?: Error) => {
if (!finished) {
finished = true;
// Copy file
await fs.promises.copyFile(source, target);
// in error cases, pass to callback
if (error) {
return reject(error);
}
// we need to explicitly chmod because of https://github.com/nodejs/node/issues/1104
fs.chmod(target, mode, error => error ? reject(error) : resolve());
}
};
// handle errors properly
reader.once('error', error => finish(error));
writer.once('error', error => finish(error));
// we are done (underlying fd has been closed)
writer.once('close', () => finish());
// start piping
reader.pipe(writer);
});
// restore mode (https://github.com/nodejs/node/issues/1104)
await fs.promises.chmod(target, mode);
}
async function doCopySymlink(source: string, target: string, payload: ICopyPayload): Promise<void> {
// Figure out link target
let linkTarget = await fs.promises.readlink(source);
// Special case: the symlink points to a target that is
// actually within the path that is being copied. In that
// case we want the symlink to point to the target and
// not the source
if (isEqualOrParent(linkTarget, payload.root.source, !isLinux)) {
linkTarget = join(payload.root.target, linkTarget.substr(payload.root.source.length + 1));
}
// Create symlink
await fs.promises.symlink(linkTarget, target);
}
//#endregion
//#region Async FS Methods
export async function exists(path: string): Promise<boolean> {
try {
await fs.promises.access(path);
return true;
} catch {
return false;
}
}
//#endregion

View File

@@ -0,0 +1,316 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as pfs from 'vs/base/node/pfs';
import * as os from 'os';
import * as path from 'vs/base/common/path';
import { env } from 'vs/base/common/process';
const WindowsPowerShell64BitLabel = 'Windows PowerShell';
const WindowsPowerShell32BitLabel = 'Windows PowerShell (x86)';
// This is required, since parseInt("7-preview") will return 7.
const IntRegex: RegExp = /^\d+$/;
const PwshMsixRegex: RegExp = /^Microsoft.PowerShell_.*/;
const PwshPreviewMsixRegex: RegExp = /^Microsoft.PowerShellPreview_.*/;
// The platform details descriptor for the platform we're on
const isProcess64Bit: boolean = process.arch === 'x64';
const isOS64Bit: boolean = isProcess64Bit || os.arch() === 'x64';
export interface IPowerShellExeDetails {
readonly displayName: string;
readonly exePath: string;
}
export interface IPossiblePowerShellExe extends IPowerShellExeDetails {
exists(): Promise<boolean>;
}
class PossiblePowerShellExe implements IPossiblePowerShellExe {
constructor(
public readonly exePath: string,
public readonly displayName: string,
private knownToExist?: boolean) { }
public async exists(): Promise<boolean> {
if (this.knownToExist === undefined) {
this.knownToExist = await pfs.SymlinkSupport.existsFile(this.exePath);
}
return this.knownToExist;
}
}
function getProgramFilesPath(
{ useAlternateBitness = false }: { useAlternateBitness?: boolean } = {}): string | null {
if (!useAlternateBitness) {
// Just use the native system bitness
return env.ProgramFiles || null;
}
// We might be a 64-bit process looking for 32-bit program files
if (isProcess64Bit) {
return env['ProgramFiles(x86)'] || null;
}
// We might be a 32-bit process looking for 64-bit program files
if (isOS64Bit) {
return env.ProgramW6432 || null;
}
// We're a 32-bit process on 32-bit Windows, there is no other Program Files dir
return null;
}
function getSystem32Path({ useAlternateBitness = false }: { useAlternateBitness?: boolean } = {}): string {
const windir: string = env.windir!;
if (!useAlternateBitness) {
// Just use the native system bitness
return path.join(windir, 'System32');
}
// We might be a 64-bit process looking for 32-bit system32
if (isProcess64Bit) {
return path.join(windir, 'SysWOW64');
}
// We might be a 32-bit process looking for 64-bit system32
if (isOS64Bit) {
return path.join(windir, 'Sysnative');
}
// We're on a 32-bit Windows, so no alternate bitness
return path.join(windir, 'System32');
}
async function findPSCoreWindowsInstallation(
{ useAlternateBitness = false, findPreview = false }:
{ useAlternateBitness?: boolean; findPreview?: boolean } = {}): Promise<IPossiblePowerShellExe | null> {
const programFilesPath = getProgramFilesPath({ useAlternateBitness });
if (!programFilesPath) {
return null;
}
const powerShellInstallBaseDir = path.join(programFilesPath, 'PowerShell');
// Ensure the base directory exists
if (!await pfs.SymlinkSupport.existsDirectory(powerShellInstallBaseDir)) {
return null;
}
let highestSeenVersion: number = -1;
let pwshExePath: string | null = null;
for (const item of await pfs.readdir(powerShellInstallBaseDir)) {
let currentVersion: number = -1;
if (findPreview) {
// We are looking for something like "7-preview"
// Preview dirs all have dashes in them
const dashIndex = item.indexOf('-');
if (dashIndex < 0) {
continue;
}
// Verify that the part before the dash is an integer
// and that the part after the dash is "preview"
const intPart: string = item.substring(0, dashIndex);
if (!IntRegex.test(intPart) || item.substring(dashIndex + 1) !== 'preview') {
continue;
}
currentVersion = parseInt(intPart, 10);
} else {
// Search for a directory like "6" or "7"
if (!IntRegex.test(item)) {
continue;
}
currentVersion = parseInt(item, 10);
}
// Ensure we haven't already seen a higher version
if (currentVersion <= highestSeenVersion) {
continue;
}
// Now look for the file
const exePath = path.join(powerShellInstallBaseDir, item, 'pwsh.exe');
if (!await pfs.SymlinkSupport.existsFile(exePath)) {
continue;
}
pwshExePath = exePath;
highestSeenVersion = currentVersion;
}
if (!pwshExePath) {
return null;
}
const bitness: string = programFilesPath.includes('x86') ? ' (x86)' : '';
const preview: string = findPreview ? ' Preview' : '';
return new PossiblePowerShellExe(pwshExePath, `PowerShell${preview}${bitness}`, true);
}
async function findPSCoreMsix({ findPreview }: { findPreview?: boolean } = {}): Promise<IPossiblePowerShellExe | null> {
// We can't proceed if there's no LOCALAPPDATA path
if (!env.LOCALAPPDATA) {
return null;
}
// Find the base directory for MSIX application exe shortcuts
const msixAppDir = path.join(env.LOCALAPPDATA, 'Microsoft', 'WindowsApps');
if (!await pfs.SymlinkSupport.existsDirectory(msixAppDir)) {
return null;
}
// Define whether we're looking for the preview or the stable
const { pwshMsixDirRegex, pwshMsixName } = findPreview
? { pwshMsixDirRegex: PwshPreviewMsixRegex, pwshMsixName: 'PowerShell Preview (Store)' }
: { pwshMsixDirRegex: PwshMsixRegex, pwshMsixName: 'PowerShell (Store)' };
// We should find only one such application, so return on the first one
for (const subdir of await pfs.readdir(msixAppDir)) {
if (pwshMsixDirRegex.test(subdir)) {
const pwshMsixPath = path.join(msixAppDir, subdir, 'pwsh.exe');
return new PossiblePowerShellExe(pwshMsixPath, pwshMsixName);
}
}
// If we find nothing, return null
return null;
}
function findPSCoreDotnetGlobalTool(): IPossiblePowerShellExe {
const dotnetGlobalToolExePath: string = path.join(os.homedir(), '.dotnet', 'tools', 'pwsh.exe');
return new PossiblePowerShellExe(dotnetGlobalToolExePath, '.NET Core PowerShell Global Tool');
}
function findWinPS({ useAlternateBitness = false }: { useAlternateBitness?: boolean } = {}): IPossiblePowerShellExe | null {
// x86 and ARM only have one WinPS on them
if (!isOS64Bit && useAlternateBitness) {
return null;
}
const systemFolderPath = getSystem32Path({ useAlternateBitness });
const winPSPath = path.join(systemFolderPath, 'WindowsPowerShell', 'v1.0', 'powershell.exe');
let displayName: string;
if (isProcess64Bit) {
displayName = useAlternateBitness
? WindowsPowerShell32BitLabel
: WindowsPowerShell64BitLabel;
} else if (isOS64Bit) {
displayName = useAlternateBitness
? WindowsPowerShell64BitLabel
: WindowsPowerShell32BitLabel;
} else {
// NOTE: ARM Windows devices also have Windows PowerShell x86 on them. There is no
// "ARM Windows PowerShell".
displayName = WindowsPowerShell32BitLabel;
}
return new PossiblePowerShellExe(winPSPath, displayName, true);
}
/**
* Iterates through all the possible well-known PowerShell installations on a machine.
* Returned values may not exist, but come with an .exists property
* which will check whether the executable exists.
*/
async function* enumerateDefaultPowerShellInstallations(): AsyncIterable<IPossiblePowerShellExe> {
// Find PSCore stable first
let pwshExe = await findPSCoreWindowsInstallation();
if (pwshExe) {
yield pwshExe;
}
// Windows may have a 32-bit pwsh.exe
pwshExe = await findPSCoreWindowsInstallation({ useAlternateBitness: true });
if (pwshExe) {
yield pwshExe;
}
// Also look for the MSIX/UWP installation
pwshExe = await findPSCoreMsix();
if (pwshExe) {
yield pwshExe;
}
// Look for the .NET global tool
// Some older versions of PowerShell have a bug in this where startup will fail,
// but this is fixed in newer versions
pwshExe = findPSCoreDotnetGlobalTool();
if (pwshExe) {
yield pwshExe;
}
// Look for PSCore preview
pwshExe = await findPSCoreWindowsInstallation({ findPreview: true });
if (pwshExe) {
yield pwshExe;
}
// Find a preview MSIX
pwshExe = await findPSCoreMsix({ findPreview: true });
if (pwshExe) {
yield pwshExe;
}
// Look for pwsh-preview with the opposite bitness
pwshExe = await findPSCoreWindowsInstallation({ useAlternateBitness: true, findPreview: true });
if (pwshExe) {
yield pwshExe;
}
// Finally, get Windows PowerShell
// Get the natural Windows PowerShell for the process bitness
pwshExe = findWinPS();
if (pwshExe) {
yield pwshExe;
}
// Get the alternate bitness Windows PowerShell
pwshExe = findWinPS({ useAlternateBitness: true });
if (pwshExe) {
yield pwshExe;
}
}
/**
* Iterates through PowerShell installations on the machine according
* to configuration passed in through the constructor.
* PowerShell items returned by this object are verified
* to exist on the filesystem.
*/
export async function* enumeratePowerShellInstallations(): AsyncIterable<IPowerShellExeDetails> {
// Get the default PowerShell installations first
for await (const defaultPwsh of enumerateDefaultPowerShellInstallations()) {
if (await defaultPwsh.exists()) {
yield defaultPwsh;
}
}
}
/**
* Returns the first available PowerShell executable found in the search order.
*/
export async function getFirstAvailablePowerShellInstallation(): Promise<IPowerShellExeDetails | null> {
for await (const pwsh of enumeratePowerShellInstallations()) {
return pwsh;
}
return null;
}

View File

@@ -5,7 +5,7 @@
import * as path from 'vs/base/common/path';
import * as fs from 'fs';
import { promisify } from 'util';
import * as pfs from 'vs/base/node/pfs';
import * as cp from 'child_process';
import * as nls from 'vs/nls';
import * as Types from 'vs/base/common/types';
@@ -50,7 +50,7 @@ function terminateProcess(process: cp.ChildProcess, cwd?: string): Promise<Termi
options.cwd = cwd;
}
const killProcess = cp.execFile('taskkill', ['/T', '/F', '/PID', process.pid.toString()], options);
return new Promise((resolve, reject) => {
return new Promise(resolve => {
killProcess.once('error', (err) => {
resolve({ success: false, error: err });
});
@@ -68,7 +68,7 @@ function terminateProcess(process: cp.ChildProcess, cwd?: string): Promise<Termi
} else if (Platform.isLinux || Platform.isMacintosh) {
try {
const cmd = FileAccess.asFileUri('vs/base/node/terminateProcess.sh', require).fsPath;
return new Promise((resolve, reject) => {
return new Promise(resolve => {
cp.execFile(cmd, [process.pid.toString()], { encoding: 'utf8', shell: true } as cp.ExecFileOptions, (err, stdout, stderr) => {
if (err) {
resolve({ success: false, error: err });
@@ -86,8 +86,8 @@ function terminateProcess(process: cp.ChildProcess, cwd?: string): Promise<Termi
return Promise.resolve({ success: true });
}
export function getWindowsShell(environment: Platform.IProcessEnvironment = process.env as Platform.IProcessEnvironment): string {
return environment['comspec'] || 'cmd.exe';
export function getWindowsShell(env = process.env as Platform.IProcessEnvironment): string {
return env['comspec'] || 'cmd.exe';
}
export abstract class AbstractProcess<TProgressData> {
@@ -447,8 +447,8 @@ export namespace win32 {
// to the current working directory.
return path.join(cwd, command);
}
if (paths === undefined && Types.isString(process.env.PATH)) {
paths = process.env.PATH.split(path.delimiter);
if (paths === undefined && Types.isString(process.env['PATH'])) {
paths = process.env['PATH'].split(path.delimiter);
}
// No PATH environment. Make path absolute to the cwd.
if (paths === undefined || paths.length === 0) {
@@ -456,8 +456,8 @@ export namespace win32 {
}
async function fileExists(path: string): Promise<boolean> {
if (await promisify(fs.exists)(path)) {
return !((await promisify(fs.stat)(path)).isDirectory());
if (await pfs.exists(path)) {
return !((await fs.promises.stat(path)).isDirectory());
}
return false;
}

93
src/vs/base/node/shell.ts Normal file
View File

@@ -0,0 +1,93 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as os from 'os';
import * as platform from 'vs/base/common/platform';
import { getFirstAvailablePowerShellInstallation } from 'vs/base/node/powershell';
import * as processes from 'vs/base/node/processes';
/**
* Gets the detected default shell for the _system_, not to be confused with VS Code's _default_
* shell that the terminal uses by default.
* @param p The platform to detect the shell of.
*/
export async function getSystemShell(p: platform.Platform, env = process.env as platform.IProcessEnvironment): Promise<string> {
if (p === platform.Platform.Windows) {
if (platform.isWindows) {
return getSystemShellWindows();
}
// Don't detect Windows shell when not on Windows
return processes.getWindowsShell(env);
}
return getSystemShellUnixLike(p, env);
}
export function getSystemShellSync(p: platform.Platform, env = process.env as platform.IProcessEnvironment): string {
if (p === platform.Platform.Windows) {
if (platform.isWindows) {
return getSystemShellWindowsSync(env);
}
// Don't detect Windows shell when not on Windows
return processes.getWindowsShell(env);
}
return getSystemShellUnixLike(p, env);
}
let _TERMINAL_DEFAULT_SHELL_UNIX_LIKE: string | null = null;
function getSystemShellUnixLike(p: platform.Platform, env: platform.IProcessEnvironment): string {
// Only use $SHELL for the current OS
if (platform.isLinux && p === platform.Platform.Mac || platform.isMacintosh && p === platform.Platform.Linux) {
return '/bin/bash';
}
if (!_TERMINAL_DEFAULT_SHELL_UNIX_LIKE) {
let unixLikeTerminal: string;
if (platform.isWindows) {
unixLikeTerminal = '/bin/bash'; // for WSL
} else {
unixLikeTerminal = env['SHELL'];
if (!unixLikeTerminal) {
try {
// It's possible for $SHELL to be unset, this API reads /etc/passwd. See https://github.com/github/codespaces/issues/1639
// Node docs: "Throws a SystemError if a user has no username or homedir."
unixLikeTerminal = os.userInfo().shell;
} catch (err) { }
}
if (!unixLikeTerminal) {
unixLikeTerminal = 'sh';
}
// Some systems have $SHELL set to /bin/false which breaks the terminal
if (unixLikeTerminal === '/bin/false') {
unixLikeTerminal = '/bin/bash';
}
}
_TERMINAL_DEFAULT_SHELL_UNIX_LIKE = unixLikeTerminal;
}
return _TERMINAL_DEFAULT_SHELL_UNIX_LIKE;
}
let _TERMINAL_DEFAULT_SHELL_WINDOWS: string | null = null;
async function getSystemShellWindows(): Promise<string> {
if (!_TERMINAL_DEFAULT_SHELL_WINDOWS) {
_TERMINAL_DEFAULT_SHELL_WINDOWS = (await getFirstAvailablePowerShellInstallation())!.exePath;
}
return _TERMINAL_DEFAULT_SHELL_WINDOWS;
}
function getSystemShellWindowsSync(env: platform.IProcessEnvironment): string {
if (_TERMINAL_DEFAULT_SHELL_WINDOWS) {
return _TERMINAL_DEFAULT_SHELL_WINDOWS;
}
const isAtLeastWindows10 = platform.isWindows && parseFloat(os.release()) >= 10;
const is32ProcessOn64Windows = env.hasOwnProperty('PROCESSOR_ARCHITEW6432');
const powerShellPath = `${env['windir']}\\${is32ProcessOn64Windows ? 'Sysnative' : 'System32'}\\WindowsPowerShell\\v1.0\\powershell.exe`;
return isAtLeastWindows10 ? powerShellPath : processes.getWindowsShell(env);
}

View File

@@ -5,10 +5,10 @@
import * as nls from 'vs/nls';
import * as path from 'vs/base/common/path';
import { createWriteStream, WriteStream } from 'fs';
import { promises, createWriteStream, WriteStream } from 'fs';
import { Readable } from 'stream';
import { Sequencer, createCancelablePromise } from 'vs/base/common/async';
import { mkdirp, rimraf } from 'vs/base/node/pfs';
import { 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';
@@ -86,7 +86,7 @@ function extractEntry(stream: Readable, fileName: string, mode: number, targetPa
}
});
return Promise.resolve(mkdirp(targetDirName)).then(() => new Promise<void>((c, e) => {
return Promise.resolve(promises.mkdir(targetDirName, { recursive: true })).then(() => new Promise<void>((c, e) => {
if (token.isCancellationRequested) {
return;
}
@@ -149,7 +149,7 @@ function extractZip(zipfile: ZipFile, targetPath: string, options: IOptions, tok
// directory file names end with '/'
if (/\/$/.test(fileName)) {
const targetFileName = path.join(targetPath, fileName);
last = createCancelablePromise(token => mkdirp(targetFileName).then(() => readNextEntry(token)).then(undefined, e));
last = createCancelablePromise(token => promises.mkdir(targetFileName, { recursive: true }).then(() => readNextEntry(token)).then(undefined, e));
return;
}