@rushstack/heft
Version:
Build all your JavaScript projects the same way: A way that works.
245 lines • 10.7 kB
JavaScript
;
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.StaticFileSystemAdapter = void 0;
const path = __importStar(require("path"));
const node_core_library_1 = require("@rushstack/node-core-library");
/* eslint-enable @rushstack/no-new-null */
const IS_WINDOWS = process.platform === 'win32';
/**
* A filesystem adapter for use with the "fast-glob" package. This adapter uses a static set of paths
* to provide a virtual filesystem.
*
* @remarks This adapter only implements methods required to allow for globbing. This means that it
* does not support returning true-to-disk file stats or dirent objects. Instead, the returned file
* stats and dirent objects only implement the `isDirectory` and `isFile` methods, which are
* required for filesystem traversal performed by the globber.
*/
class StaticFileSystemAdapter {
/**
* Create a new StaticFileSystemAdapter instance with the provided file paths.
*/
constructor(filePaths) {
this._directoryMap = new Map();
/** { @inheritdoc fs.lstat } */
this.lstat = ((filePath, callback) => {
process.nextTick(() => {
let result;
try {
result = this.lstatSync(filePath);
}
catch (e) {
callback(e, {});
return;
}
callback(null, result);
});
});
/** { @inheritdoc fs.lstatSync } */
this.lstatSync = ((filePath) => {
filePath = this._normalizePath(filePath);
const entry = this._directoryMap.get(filePath);
if (!entry) {
const error = new Error(`ENOENT: no such file or directory, stat '${filePath}'`);
error.code = 'ENOENT';
error.syscall = 'stat';
error.errno = -4058;
error.path = filePath;
throw error;
}
// We should only need to implement these methods for the purposes of fast-glob
return {
isFile: () => !entry.children,
isDirectory: () => !!entry.children,
isBlockDevice: () => false,
isCharacterDevice: () => false,
isSymbolicLink: () => false,
isFIFO: () => false,
isSocket: () => false
};
});
/** { @inheritdoc fs.stat } */
this.stat = ((filePath, callback) => {
this.lstat(filePath, callback);
});
/** { @inheritdoc fs.statSync } */
this.statSync = ((filePath) => {
return this.lstatSync(filePath);
});
/** { @inheritdoc fs.readdir } */
this.readdir = ((filePath, optionsOrCallback, callback) => {
// Default to no options, which will return a string callback
let options;
if (typeof optionsOrCallback === 'object') {
options = optionsOrCallback;
}
else if (typeof optionsOrCallback === 'function') {
callback = optionsOrCallback;
}
// Perform the readdir on the next tick to avoid blocking the event loop
process.nextTick(() => {
let result;
try {
if (options === null || options === void 0 ? void 0 : options.withFileTypes) {
result = this.readdirSync(filePath, options);
}
else {
result = this.readdirSync(filePath);
}
}
catch (e) {
callback(e, []);
return;
}
// When "withFileTypes" is false or undefined, the callback is expected to return a string array.
// Otherwise, we return a fs.Dirent array.
if (options === null || options === void 0 ? void 0 : options.withFileTypes) {
callback(null, result);
}
else {
callback(null, result);
}
});
});
/** { @inheritdoc fs.readdirSync } */
this.readdirSync = ((filePath, options) => {
filePath = this._normalizePath(filePath);
const virtualDirectory = this._directoryMap.get(filePath);
if (!virtualDirectory) {
// Immitate a missing directory read from fs.readdir
const error = new Error(`ENOENT: no such file or directory, scandir '${filePath}'`);
error.code = 'ENOENT';
error.syscall = 'scandir';
error.errno = -4058;
error.path = filePath;
throw error;
}
else if (!virtualDirectory.children) {
// Immitate a directory read of a file from fs.readdir
const error = new Error(`ENOTDIR: not a directory, scandir '${filePath}'`);
error.code = 'ENOTDIR';
error.syscall = 'scandir';
error.errno = -4052;
error.path = filePath;
throw error;
}
// When "withFileTypes" is false or undefined, the method is expected to return a string array.
// Otherwise, we return a fs.Dirent array.
const result = Array.from(virtualDirectory.children);
if (options === null || options === void 0 ? void 0 : options.withFileTypes) {
return result.map((entry) => {
// Partially implement the fs.Dirent interface, only including the properties used by fast-glob
return {
name: entry.name,
isFile: () => !entry.children,
isDirectory: () => !!entry.children,
isBlockDevice: () => false,
isCharacterDevice: () => false,
isSymbolicLink: () => false,
isFIFO: () => false,
isSocket: () => false
};
});
}
else {
return result.map((entry) => entry.name);
}
});
for (const filePath of filePaths || []) {
this.addFile(filePath);
}
}
/**
* Add a file and it's parent directories to the static virtual filesystem.
*/
addFile(filePath) {
filePath = this._normalizePath(filePath);
const existingPath = this._directoryMap.get(filePath);
if (!existingPath) {
// Set an entry without children for the file. Entries with undefined children are assumed to be files.
let childPath = filePath;
let childEntry = { name: path.basename(childPath) };
this._directoryMap.set(childPath, childEntry);
// Loop through the path segments and create entries for each directory, if they don't already exist.
// If they do, append to the existing children set and continue.
let parentPath;
while ((parentPath = path.dirname(childPath)) !== childPath) {
const existingParentEntry = this._directoryMap.get(parentPath);
if (existingParentEntry) {
// If there is already an existing parent entry, add the child entry to the existing children set
// and exit early, since the parent entries already exist.
existingParentEntry.children.add(childEntry);
break;
}
else {
// If there is no existing parent entry, create a new entry with the child entry as the only child.
const parentEntry = {
name: path.basename(parentPath),
children: new Set([childEntry])
};
this._directoryMap.set(parentPath, parentEntry);
childEntry = parentEntry;
childPath = parentPath;
}
}
}
}
/**
* Remove a file from the static virtual filesystem.
*/
removeFile(filePath) {
filePath = this._normalizePath(filePath);
const existingEntry = this._directoryMap.get(filePath);
if (existingEntry) {
// Remove the entry from the map and the parent's children set
this._directoryMap.delete(filePath);
this._directoryMap.get(path.dirname(filePath)).children.delete(existingEntry);
}
}
/**
* Remove all files from the static virtual filesystem.
*/
removeAllFiles() {
this._directoryMap.clear();
}
_normalizePath(filePath) {
// On Windows, normalize to backslashes so that errors have the correct path format
return IS_WINDOWS ? node_core_library_1.Path.convertToBackslashes(filePath) : filePath;
}
}
exports.StaticFileSystemAdapter = StaticFileSystemAdapter;
//# sourceMappingURL=StaticFileSystemAdapter.js.map