@nasriya/atomix
Version:
Composable helper functions for building reliable systems
177 lines (176 loc) • 7.16 kB
JavaScript
import path from 'path';
import runtime from '../runtime/runtime.js';
import mimes from '../http/mimes/mimes.js';
import valueIs from '../../valueIs.js';
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.resolve(path.normalize(path_));
return runtime.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.platform.isWindows()
? /[<>:"/\\|?*\x00-\x1F]/g
: /[/\x00]/g;
// Also forbid trailing dots or spaces on Windows
let sanitized = name.replace(illegalRe, replacement);
if (runtime.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.relative(normalizedParent, normalizedChild);
return relative && !relative.startsWith('..') && !path.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.basename(filePath, path.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.isValid.extension(newExt))
throw new Error(`Invalid file extension: ${newExt}`);
filePath = this.normalizePath(filePath);
return filePath.replace(path.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.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.relative(cwd, path_);
const normalized = path.normalize(relative);
return runtime.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.string(str) || valueIs.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.isAbsolute(str)) {
return true;
}
// Has OS path separators (e.g., / or \)
if (str.includes(path.sep))
return true;
// Has typical file extensions
if (mimes.extensions.some(ext => str.endsWith(ext))) {
return true;
}
// Dot-prefixed (relative paths) like ./ or ../
if (str.startsWith(`.${path.sep}`) || str.startsWith(`..${path.sep}`)) {
return true;
}
if (runtime.platform.isWindows() && /^[a-zA-Z]:\\/.test(str)) {
return true;
}
return false;
}
}
const pathUtils = new PathUtils;
export default pathUtils;