UNPKG

@theia/filesystem

Version:
788 lines • 33.7 kB
"use strict"; // ***************************************************************************** // Copyright (C) 2020 TypeFox and others. // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License v. 2.0 which is available at // http://www.eclipse.org/legal/epl-2.0. // // This Source Code may also be made available under the following Secondary // Licenses when the conditions for such availability set forth in the Eclipse // Public License v. 2.0 are satisfied: GNU General Public License, version 2 // with the GNU Classpath Exception which is available at // https://www.gnu.org/software/classpath/license.html. // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // based on https://github.com/microsoft/vscode/blob/04c36be045a94fee58e5f8992d3e3fd980294a84/src/vs/platform/files/node/diskFileSystemProvider.ts Object.defineProperty(exports, "__esModule", { value: true }); exports.DiskFileSystemProvider = void 0; const tslib_1 = require("tslib"); /* eslint-disable no-null/no-null */ /* eslint-disable @typescript-eslint/no-shadow */ const inversify_1 = require("@theia/core/shared/inversify"); const path_1 = require("path"); const uuid_1 = require("@theia/core/lib/common/uuid"); const os = require("os"); const fs = require("fs"); const fs_1 = require("fs"); const util_1 = require("util"); const uri_1 = require("@theia/core/lib/common/uri"); const path_2 = require("@theia/core/lib/common/path"); const file_uri_1 = require("@theia/core/lib/common/file-uri"); const event_1 = require("@theia/core/lib/common/event"); const disposable_1 = require("@theia/core/lib/common/disposable"); const os_1 = require("@theia/core/lib/common/os"); const promise_util_1 = require("@theia/core/lib/common/promise-util"); const files_1 = require("../common/files"); const filesystem_watcher_protocol_1 = require("../common/filesystem-watcher-protocol"); const trash = require("trash"); const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument"); const encoding_service_1 = require("@theia/core/lib/common/encoding-service"); const buffer_1 = require("@theia/core/lib/common/buffer"); const stream_1 = require("@theia/core/lib/common/stream"); const io_1 = require("../common/io"); const stat_mode_1 = require("stat-mode"); let DiskFileSystemProvider = class DiskFileSystemProvider { constructor() { this.BUFFER_SIZE = 64 * 1024; this.onDidChangeFileEmitter = new event_1.Emitter(); this.onDidChangeFile = this.onDidChangeFileEmitter.event; this.onFileWatchErrorEmitter = new event_1.Emitter(); this.onFileWatchError = this.onFileWatchErrorEmitter.event; this.toDispose = new disposable_1.DisposableCollection(this.onDidChangeFileEmitter); // #region File Capabilities this.onDidChangeCapabilities = event_1.Event.None; this.mapHandleToPos = new Map(); this.writeHandles = new Set(); this.canFlush = true; } init() { this.toDispose.push(this.watcher); this.watcher.setClient({ onDidFilesChanged: params => this.onDidChangeFileEmitter.fire(params.changes.map(({ uri, type }) => ({ resource: new uri_1.default(uri), type }))), onError: () => this.onFileWatchErrorEmitter.fire() }); } get capabilities() { if (!this._capabilities) { this._capabilities = 2 /* FileSystemProviderCapabilities.FileReadWrite */ | 4 /* FileSystemProviderCapabilities.FileOpenReadWriteClose */ | 16 /* FileSystemProviderCapabilities.FileReadStream */ | 8 /* FileSystemProviderCapabilities.FileFolderCopy */ | 16777216 /* FileSystemProviderCapabilities.Access */ | 4096 /* FileSystemProviderCapabilities.Trash */ | 33554432 /* FileSystemProviderCapabilities.Update */; if (os_1.OS.type() === os_1.OS.Type.Linux) { this._capabilities |= 1024 /* FileSystemProviderCapabilities.PathCaseSensitive */; } } return this._capabilities; } // #endregion // #region File Metadata Resolving async stat(resource) { try { const { stat, symbolicLink } = await this.statLink(this.toFilePath(resource)); // cannot use fs.stat() here to support links properly const mode = new stat_mode_1.Mode(stat); return { type: this.toType(stat, symbolicLink), ctime: stat.birthtime.getTime(), // intentionally not using ctime here, we want the creation time mtime: stat.mtime.getTime(), size: stat.size, permissions: !mode.owner.write ? files_1.FilePermission.Readonly : undefined, }; } catch (error) { throw this.toFileSystemProviderError(error); } } async access(resource, mode) { try { await (0, util_1.promisify)(fs.access)(this.toFilePath(resource), mode); } catch (error) { throw this.toFileSystemProviderError(error); } } async fsPath(resource) { return file_uri_1.FileUri.fsPath(resource); } async statLink(path) { // First stat the link let lstats; try { lstats = await (0, util_1.promisify)(fs_1.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 (0, util_1.promisify)(fs_1.stat)(path); return { stat: stats, symbolicLink: (lstats === null || lstats === void 0 ? void 0 : 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 } }; } throw error; } } async readdir(resource) { try { const children = await (0, util_1.promisify)(fs.readdir)(this.toFilePath(resource)); const result = []; await Promise.all(children.map(async (child) => { try { const stat = await this.stat(resource.resolve(child)); result.push([child, stat.type]); } catch (error) { console.trace(error); // ignore errors for individual entries that can arise from permission denied } })); return result; } catch (error) { throw this.toFileSystemProviderError(error); } } toType(entry, symbolicLink) { // Signal file type by checking for file / directory, except: // - symbolic links pointing to non-existing files are FileType.Unknown // - files that are neither file nor directory are FileType.Unknown let type; if (symbolicLink === null || symbolicLink === void 0 ? void 0 : symbolicLink.dangling) { type = files_1.FileType.Unknown; } else if (entry.isFile()) { type = files_1.FileType.File; } else if (entry.isDirectory()) { type = files_1.FileType.Directory; } else { type = files_1.FileType.Unknown; } // Always signal symbolic link as file type additionally if (symbolicLink) { type |= files_1.FileType.SymbolicLink; } return type; } // #endregion // #region File Reading/Writing async readFile(resource) { try { const filePath = this.toFilePath(resource); return await (0, util_1.promisify)(fs_1.readFile)(filePath); } catch (error) { throw this.toFileSystemProviderError(error); } } readFileStream(resource, opts, token) { const stream = (0, stream_1.newWriteableStream)(data => buffer_1.BinaryBuffer.concat(data.map(data => buffer_1.BinaryBuffer.wrap(data))).buffer); (0, io_1.readFileIntoStream)(this, resource, stream, data => data.buffer, { ...opts, bufferSize: this.BUFFER_SIZE }, token); return stream; } async writeFile(resource, content, opts) { let handle = undefined; try { const filePath = this.toFilePath(resource); // Validate target unless { create: true, overwrite: true } if (!opts.create || !opts.overwrite) { const fileExists = await (0, util_1.promisify)(fs_1.exists)(filePath); if (fileExists) { if (!opts.overwrite) { throw (0, files_1.createFileSystemProviderError)('File already exists', files_1.FileSystemProviderErrorCode.FileExists); } } else { if (!opts.create) { throw (0, files_1.createFileSystemProviderError)('File does not exist', files_1.FileSystemProviderErrorCode.FileNotFound); } } } // Open handle = await this.open(resource, { create: true }); // Write content at once await this.write(handle, 0, content, 0, content.byteLength); } catch (error) { throw this.toFileSystemProviderError(error); } finally { if (typeof handle === 'number') { await this.close(handle); } } } async open(resource, opts) { try { const filePath = this.toFilePath(resource); let flags = undefined; if (opts.create) { if (os_1.isWindows && await (0, util_1.promisify)(fs_1.exists)(filePath)) { try { // On Windows and if the file exists, we use a different strategy of saving the file // by first truncating the file and then writing with r+ flag. This helps to save hidden files on Windows // (see https://github.com/Microsoft/vscode/issues/931) and prevent removing alternate data streams // (see https://github.com/Microsoft/vscode/issues/6363) await (0, util_1.promisify)(fs_1.truncate)(filePath, 0); // After a successful truncate() the flag can be set to 'r+' which will not truncate. flags = 'r+'; } catch (error) { console.trace(error); } } // we take opts.create as a hint that the file is opened for writing // as such we use 'w' to truncate an existing or create the // file otherwise. we do not allow reading. if (!flags) { flags = 'w'; } } else { // otherwise we assume the file is opened for reading // as such we use 'r' to neither truncate, nor create // the file. flags = 'r'; } const handle = await (0, util_1.promisify)(fs_1.open)(filePath, flags); // remember this handle to track file position of the handle // we init the position to 0 since the file descriptor was // just created and the position was not moved so far (see // also http://man7.org/linux/man-pages/man2/open.2.html - // "The file offset is set to the beginning of the file.") this.mapHandleToPos.set(handle, 0); // remember that this handle was used for writing if (opts.create) { this.writeHandles.add(handle); } return handle; } catch (error) { throw this.toFileSystemProviderError(error); } } async close(fd) { try { // remove this handle from map of positions this.mapHandleToPos.delete(fd); // if a handle is closed that was used for writing, ensure // to flush the contents to disk if possible. if (this.writeHandles.delete(fd) && this.canFlush) { try { await (0, util_1.promisify)(fs_1.fdatasync)(fd); } catch (error) { // In some exotic setups it is well possible that node fails to sync // In that case we disable flushing and log the error to our logger this.canFlush = false; console.error(error); } } return await (0, util_1.promisify)(fs_1.close)(fd); } catch (error) { throw this.toFileSystemProviderError(error); } } async read(fd, pos, data, offset, length) { const normalizedPos = this.normalizePos(fd, pos); let bytesRead = null; try { const result = await (0, util_1.promisify)(fs_1.read)(fd, data, offset, length, normalizedPos); if (typeof result === 'number') { bytesRead = result; // node.d.ts fail } else { bytesRead = result.bytesRead; } return bytesRead; } catch (error) { throw this.toFileSystemProviderError(error); } finally { this.updatePos(fd, normalizedPos, bytesRead); } } normalizePos(fd, pos) { // when calling fs.read/write we try to avoid passing in the "pos" argument and // rather prefer to pass in "null" because this avoids an extra seek(pos) // call that in some cases can even fail (e.g. when opening a file over FTP - // see https://github.com/microsoft/vscode/issues/73884). // // as such, we compare the passed in position argument with our last known // position for the file descriptor and use "null" if they match. if (pos === this.mapHandleToPos.get(fd)) { return null; } return pos; } updatePos(fd, pos, bytesLength) { const lastKnownPos = this.mapHandleToPos.get(fd); if (typeof lastKnownPos === 'number') { // pos !== null signals that previously a position was used that is // not null. node.js documentation explains, that in this case // the internal file pointer is not moving and as such we do not move // our position pointer. // // Docs: "If position is null, data will be read from the current file position, // and the file position will be updated. If position is an integer, the file position // will remain unchanged." if (typeof pos === 'number') { // do not modify the position } else if (typeof bytesLength === 'number') { this.mapHandleToPos.set(fd, lastKnownPos + bytesLength); } else { this.mapHandleToPos.delete(fd); } } } async write(fd, pos, data, offset, length) { // we know at this point that the file to write to is truncated and thus empty // if the write now fails, the file remains empty. as such we really try hard // to ensure the write succeeds by retrying up to three times. return (0, promise_util_1.retry)(() => this.doWrite(fd, pos, data, offset, length), 100 /* ms delay */, 3 /* retries */); } async doWrite(fd, pos, data, offset, length) { const normalizedPos = this.normalizePos(fd, pos); let bytesWritten = null; try { const result = await (0, util_1.promisify)(fs_1.write)(fd, data, offset, length, normalizedPos); if (typeof result === 'number') { bytesWritten = result; // node.d.ts fail } else { bytesWritten = result.bytesWritten; } return bytesWritten; } catch (error) { throw this.toFileSystemProviderError(error); } finally { this.updatePos(fd, normalizedPos, bytesWritten); } } // #endregion // #region Move/Copy/Delete/Create Folder async mkdir(resource) { try { await (0, util_1.promisify)(fs_1.mkdir)(this.toFilePath(resource)); } catch (error) { throw this.toFileSystemProviderError(error); } } async delete(resource, opts) { try { const filePath = this.toFilePath(resource); await this.doDelete(filePath, opts); } catch (error) { throw this.toFileSystemProviderError(error); } } async doDelete(filePath, opts) { if (!opts.useTrash) { if (opts.recursive) { await this.rimraf(filePath); } else { const stat = await (0, util_1.promisify)(fs_1.lstat)(filePath); if (stat.isDirectory() && !stat.isSymbolicLink()) { await (0, util_1.promisify)(fs_1.rmdir)(filePath); } else { await (0, util_1.promisify)(fs_1.unlink)(filePath); } } } else { await trash(filePath); } } rimraf(path) { if (new path_2.Path(path).isRoot) { throw new Error('rimraf - will refuse to recursively delete root'); } return this.rimrafMove(path); } async rimrafMove(path) { try { const pathInTemp = (0, path_1.join)(os.tmpdir(), (0, uuid_1.generateUuid)()); try { await (0, util_1.promisify)(fs_1.rename)(path, pathInTemp); } catch (error) { return this.rimrafUnlink(path); // if rename fails, delete without tmp dir } // Delete but do not return as promise this.rimrafUnlink(pathInTemp); } catch (error) { if (error.code !== 'ENOENT') { throw error; } } } async rimrafUnlink(path) { try { const stat = await (0, util_1.promisify)(fs_1.lstat)(path); // Folder delete (recursive) - NOT for symbolic links though! if (stat.isDirectory() && !stat.isSymbolicLink()) { // Children const children = await (0, util_1.promisify)(fs_1.readdir)(path); await Promise.all(children.map(child => this.rimrafUnlink((0, path_1.join)(path, child)))); // Folder await (0, util_1.promisify)(fs_1.rmdir)(path); } else { // chmod as needed to allow for unlink const mode = stat.mode; if (!(mode & 128)) { // 128 === 0200 await (0, util_1.promisify)(fs_1.chmod)(path, mode | 128); } return (0, util_1.promisify)(fs_1.unlink)(path); } } catch (error) { if (error.code !== 'ENOENT') { throw error; } } } async rename(from, to, opts) { const fromFilePath = this.toFilePath(from); const toFilePath = this.toFilePath(to); if (fromFilePath === toFilePath) { return; // simulate node.js behaviour here and do a no-op if paths match } try { // Ensure target does not exist await this.validateTargetDeleted(from, to, 'move', opts.overwrite); // Move await this.move(fromFilePath, toFilePath); } catch (error) { // rewrite some typical errors that can happen especially around symlinks // to something the user can better understand if (error.code === 'EINVAL' || error.code === 'EBUSY' || error.code === 'ENAMETOOLONG') { error = new Error(`Unable to move '${(0, path_1.basename)(fromFilePath)}' into '${(0, path_1.basename)((0, path_1.dirname)(toFilePath))}' (${error.toString()}).`); } throw this.toFileSystemProviderError(error); } } async move(source, target) { if (source === target) { return Promise.resolve(); } async function updateMtime(path) { const stat = await (0, util_1.promisify)(fs_1.lstat)(path); if (stat.isDirectory() || stat.isSymbolicLink()) { return Promise.resolve(); // only for files } const fd = await (0, util_1.promisify)(fs_1.open)(path, 'a'); try { await (0, util_1.promisify)(fs_1.futimes)(fd, stat.atime, new Date()); } catch (error) { // ignore } return (0, util_1.promisify)(fs_1.close)(fd); } try { await (0, util_1.promisify)(fs_1.rename)(source, target); await updateMtime(target); } catch (error) { // In two cases we fallback to classic copy and delete: // // 1.) The EXDEV error indicates that source and target are on different devices // In this case, fallback to using a copy() operation as there is no way to // rename() between different devices. // // 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 this.doCopy(source, target); await this.rimraf(source); await updateMtime(target); } else { throw error; } } } async copy(from, to, opts) { const fromFilePath = this.toFilePath(from); const toFilePath = this.toFilePath(to); if (fromFilePath === toFilePath) { return; // simulate node.js behaviour here and do a no-op if paths match } try { // Ensure target does not exist await this.validateTargetDeleted(from, to, 'copy', opts.overwrite); // Copy await this.doCopy(fromFilePath, toFilePath); } catch (error) { // rewrite some typical errors that can happen especially around symlinks // to something the user can better understand if (error.code === 'EINVAL' || error.code === 'EBUSY' || error.code === 'ENAMETOOLONG') { error = new Error(`Unable to copy '${(0, path_1.basename)(fromFilePath)}' into '${(0, path_1.basename)((0, path_1.dirname)(toFilePath))}' (${error.toString()}).`); } throw this.toFileSystemProviderError(error); } } async validateTargetDeleted(from, to, mode, overwrite) { const isPathCaseSensitive = !!(this.capabilities & 1024 /* FileSystemProviderCapabilities.PathCaseSensitive */); const fromFilePath = this.toFilePath(from); const toFilePath = this.toFilePath(to); let isSameResourceWithDifferentPathCase = false; if (!isPathCaseSensitive) { isSameResourceWithDifferentPathCase = fromFilePath.toLowerCase() === toFilePath.toLowerCase(); } if (isSameResourceWithDifferentPathCase && mode === 'copy') { throw (0, files_1.createFileSystemProviderError)("'File cannot be copied to same path with different path case", files_1.FileSystemProviderErrorCode.FileExists); } // handle existing target (unless this is a case change) if (!isSameResourceWithDifferentPathCase && await (0, util_1.promisify)(fs_1.exists)(toFilePath)) { if (!overwrite) { throw (0, files_1.createFileSystemProviderError)('File at target already exists', files_1.FileSystemProviderErrorCode.FileExists); } // Delete target await this.delete(to, { recursive: true, useTrash: false }); } } async doCopy(source, target, copiedSourcesIn) { const copiedSources = copiedSourcesIn ? copiedSourcesIn : Object.create(null); const fileStat = await (0, util_1.promisify)(fs_1.stat)(source); if (!fileStat.isDirectory()) { return this.doCopyFile(source, target, fileStat.mode & 511); } if (copiedSources[source]) { return Promise.resolve(); // escape when there are cycles (can happen with symlinks) } copiedSources[source] = true; // remember as copied // Create folder await this.mkdirp(target, fileStat.mode & 511); // Copy each file recursively const files = await (0, util_1.promisify)(fs_1.readdir)(source); for (let i = 0; i < files.length; i++) { const file = files[i]; await this.doCopy((0, path_1.join)(source, file), (0, path_1.join)(target, file), copiedSources); } } async mkdirp(path, mode) { const mkdir = async () => { try { await (0, util_1.promisify)(fs.mkdir)(path, mode); } catch (error) { // ENOENT: a parent folder does not exist yet if (error.code === 'ENOENT') { throw error; } // Any other error: check if folder exists and // return normally in that case if its a folder let targetIsFile = false; try { const fileStat = await (0, util_1.promisify)(fs.stat)(path); targetIsFile = !fileStat.isDirectory(); } catch (statError) { throw error; // rethrow original error if stat fails } if (targetIsFile) { throw new Error(`'${path}' exists and is not a directory.`); } } }; // stop at root if (path === (0, path_1.dirname)(path)) { return; } try { await mkdir(); } catch (error) { // ENOENT: a parent folder does not exist yet, continue // to create the parent folder and then try again. if (error.code === 'ENOENT') { await this.mkdirp((0, path_1.dirname)(path), mode); return mkdir(); } // Any other error throw error; } } doCopyFile(source, target, mode) { return new Promise((resolve, reject) => { const reader = fs.createReadStream(source); const writer = fs.createWriteStream(target, { mode }); let finished = false; const finish = (error) => { if (!finished) { finished = true; // 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); }); } // #endregion // #region File Watching watch(resource, opts) { const watcherService = this.watcher; /** * Disposable handle. Can be disposed early (before the watcher is allocated.) */ const handle = { disposed: false, watcherId: undefined, dispose() { if (this.disposed) { return; } if (this.watcherId !== undefined) { watcherService.unwatchFileChanges(this.watcherId); } this.disposed = true; }, }; watcherService.watchFileChanges(resource.toString(), { // Convert from `files.WatchOptions` to internal `watcher-protocol.WatchOptions`: ignored: opts.excludes }).then(watcherId => { if (handle.disposed) { watcherService.unwatchFileChanges(watcherId); } else { handle.watcherId = watcherId; } }); this.toDispose.push(handle); return handle; } // #endregion async updateFile(resource, changes, opts) { try { const content = await this.readFile(resource); const decoded = this.encodingService.decode(buffer_1.BinaryBuffer.wrap(content), opts.readEncoding); const newContent = vscode_languageserver_textdocument_1.TextDocument.update(vscode_languageserver_textdocument_1.TextDocument.create('', '', 1, decoded), changes, 2).getText(); const encoding = await this.encodingService.toResourceEncoding(opts.writeEncoding, { overwriteEncoding: opts.overwriteEncoding, read: async (length) => { const fd = await this.open(resource, { create: false }); try { const data = new Uint8Array(length); await this.read(fd, 0, data, 0, length); return data; } finally { await this.close(fd); } } }); const encoded = this.encodingService.encode(newContent, encoding); await this.writeFile(resource, encoded.buffer, { create: false, overwrite: true }); const stat = await this.stat(resource); return Object.assign(stat, { encoding: encoding.encoding }); } catch (error) { throw this.toFileSystemProviderError(error); } } // #region Helpers toFilePath(resource) { return (0, path_1.normalize)(file_uri_1.FileUri.fsPath(resource)); } toFileSystemProviderError(error) { if (error instanceof files_1.FileSystemProviderError) { return error; // avoid double conversion } let code; switch (error.code) { case 'ENOENT': code = files_1.FileSystemProviderErrorCode.FileNotFound; break; case 'EISDIR': code = files_1.FileSystemProviderErrorCode.FileIsADirectory; break; case 'ENOTDIR': code = files_1.FileSystemProviderErrorCode.FileNotADirectory; break; case 'EEXIST': code = files_1.FileSystemProviderErrorCode.FileExists; break; case 'EPERM': case 'EACCES': code = files_1.FileSystemProviderErrorCode.NoPermissions; break; default: code = files_1.FileSystemProviderErrorCode.Unknown; } return (0, files_1.createFileSystemProviderError)(error, code); } // #endregion dispose() { this.toDispose.dispose(); } }; exports.DiskFileSystemProvider = DiskFileSystemProvider; tslib_1.__decorate([ (0, inversify_1.inject)(filesystem_watcher_protocol_1.FileSystemWatcherServer), tslib_1.__metadata("design:type", Object) ], DiskFileSystemProvider.prototype, "watcher", void 0); tslib_1.__decorate([ (0, inversify_1.inject)(encoding_service_1.EncodingService), tslib_1.__metadata("design:type", encoding_service_1.EncodingService) ], DiskFileSystemProvider.prototype, "encodingService", void 0); tslib_1.__decorate([ (0, inversify_1.postConstruct)(), tslib_1.__metadata("design:type", Function), tslib_1.__metadata("design:paramtypes", []), tslib_1.__metadata("design:returntype", void 0) ], DiskFileSystemProvider.prototype, "init", null); exports.DiskFileSystemProvider = DiskFileSystemProvider = tslib_1.__decorate([ (0, inversify_1.injectable)() ], DiskFileSystemProvider); //# sourceMappingURL=disk-file-system-provider.js.map