@nasriya/atomix
Version:
Composable helper functions for building reliable systems
182 lines (181 loc) • 7.7 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = __importDefault(require("path"));
const runtime_1 = __importDefault(require("../runtime/runtime"));
const mimes_1 = __importDefault(require("../http/mimes/mimes"));
const valueIs_1 = __importDefault(require("../../valueIs"));
class PathUtils {
/**
* Normalizes the given path by resolving it to an absolute path and converting
* it to lowercase if the current platform is Windows.
* @param path_ The path to normalize.
* @returns The normalized path.
* @since v1.0.0
*/
normalizePath(path_) {
const resolved = path_1.default.resolve(path_1.default.normalize(path_));
return runtime_1.default.platform.isWindows() ? resolved.toLowerCase() : resolved;
}
/**
* Returns a sanitized and safe file or folder name by replacing or removing illegal characters.
* Illegal characters differ between platforms, so this method handles Windows and POSIX systems.
*
* @param name - The file or folder name to sanitize.
* @param replacement - The character(s) to replace illegal characters with. Defaults to '_'.
* @returns The sanitized safe name.
* @since v1.0.0
* @example
* const safeName = sanitizeName('my*illegal:file?.txt');
* // safeName === 'my_illegal_file_.txt'
*/
sanitizeName(name, replacement = '_') {
if (typeof name !== 'string')
throw new TypeError('Name must be a string');
// Characters illegal on Windows filenames:
// < > : " / \ | ? * and also control chars 0-31
// POSIX forbids '/' in names
const illegalRe = runtime_1.default.platform.isWindows()
? /[<>:"/\\|?*\x00-\x1F]/g
: /[/\x00]/g;
// Also forbid trailing dots or spaces on Windows
let sanitized = name.replace(illegalRe, replacement);
if (runtime_1.default.platform.isWindows()) {
sanitized = sanitized.replace(/[. ]+$/, ''); // remove trailing dots/spaces
}
// Prevent empty names
if (sanitized.length === 0)
sanitized = '_';
return sanitized;
}
/**
* Determines if the given child path is a sub-path of the given parent path.
*
* @param childPath - The path to check as a sub-path.
* @param parentPath - The path to check as the parent.
* @returns True if the child path is a sub-path of the parent path, false otherwise.
* @since v1.0.0
* @example
* const isSub = isSubPath('/path/to/child', '/path/to');
* isSub is true
*/
isSubPath(childPath, parentPath) {
const normalizedChild = this.normalizePath(childPath);
const normalizedParent = this.normalizePath(parentPath);
const relative = path_1.default.relative(normalizedParent, normalizedChild);
return relative && !relative.startsWith('..') && !path_1.default.isAbsolute(relative) ? true : false;
}
/**
* Gets the filename of the given path without the file extension.
* @param filePath - The path to get the filename from.
* @returns The filename without extension.
* @since v1.0.0
* @example
* const filename = getFileNameWithoutExtension('/path/to/file.txt');
* filename is 'file'
*/
getFileNameWithoutExtension(filePath) {
filePath = this.normalizePath(filePath);
return path_1.default.basename(filePath, path_1.default.extname(filePath));
}
/**
* Changes the file extension of the given path to the specified new extension.
* @param filePath - The path to change the extension of.
* @param newExt - The new file extension to set. Must be a valid file extension.
* @returns The modified path with the new extension.
* @throws Error if the provided new extension is not a valid file extension.
* @since v1.0.0
* @example
* const newFilePath = changeExtension('/path/to/file.txt', '.json');
* newFilePath is '/path/to/file.json'
*/
changeExtension(filePath, newExt) {
if (!mimes_1.default.isValid.extension(newExt))
throw new Error(`Invalid file extension: ${newExt}`);
filePath = this.normalizePath(filePath);
return filePath.replace(path_1.default.extname(filePath), newExt);
}
/**
* Checks if the given string is a valid file path.
*
* On Windows, this checks that the path does not contain any of the following characters:
* - `<>:"|?*`
* - ASCII control characters (characters with code points less than 32)
*
* On Unix-like platforms, this only checks that the path is not empty and does not contain the null byte (`\0`).
*
* @param path_ - The path to check.
* @returns True if the path is valid, false otherwise.
* @since v1.0.0
*/
isValidPath(path_) {
if (!path_ || typeof path_ !== 'string')
return false;
// Basic invalid Windows path characters (adjust as needed)
const invalidChars = /[<>:"|?*\x00-\x1F]/;
if (runtime_1.default.platform.isWindows()) {
return !invalidChars.test(path_);
}
// On Unix-like, just check if it's not empty and no null byte
return !path_.includes('\0');
}
/**
* Gets the relative path from the current working directory to the given path.
*
* @param path_ - The path to get the relative path from the current working directory.
* @returns The relative path from the current working directory to the given path.
* @since v1.0.0
* @example
* const relativePath = pathUtils.relativeToCwd('/Users/username/Documents/file.txt');
* relativePath is 'Users/username/Documents/file.txt'
*/
relativeToCwd(path_) {
const cwd = process.cwd();
const relative = path_1.default.relative(cwd, path_);
const normalized = path_1.default.normalize(relative);
return runtime_1.default.platform.isWindows() ? normalized.toLowerCase() : normalized;
}
/**
* Heuristically determines if the given string is likely a file path.
*
* @param str - The string to check.
* @returns True if the string is likely a file path, false otherwise.
* @since v1.0.17
* @example
* const isLikelyPath = atomix.path.isLikelyPath('./foo/bar.txt');
* console.log(isLikelyPath); // true
*/
isLikelyPath(str) {
// Must not be empty or only whitespace
if (!valueIs_1.default.string(str) || valueIs_1.default.emptyString(str)) {
return false;
}
// Skipping URI schemes
if (/^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(str)) {
return false;
}
// Absolute path check (e.g., /foo/bar or C:\foo\bar)
if (path_1.default.isAbsolute(str)) {
return true;
}
// Has OS path separators (e.g., / or \)
if (str.includes(path_1.default.sep))
return true;
// Has typical file extensions
if (mimes_1.default.extensions.some(ext => str.endsWith(ext))) {
return true;
}
// Dot-prefixed (relative paths) like ./ or ../
if (str.startsWith(`.${path_1.default.sep}`) || str.startsWith(`..${path_1.default.sep}`)) {
return true;
}
if (runtime_1.default.platform.isWindows() && /^[a-zA-Z]:\\/.test(str)) {
return true;
}
return false;
}
}
const pathUtils = new PathUtils;
exports.default = pathUtils;