fs:exports & file names

This commit is contained in:
lovebird 2025-03-17 14:01:33 +01:00
parent 365fe2c039
commit a6280d442a
14 changed files with 247 additions and 229 deletions

View File

@ -118,7 +118,6 @@ const checkContentAsync = (path, mode, options) => {
const writeAsync = async (path, stat, options) => {
const mode = normalizeFileMode(stat.mode);
return checkContentAsync(path, mode, options);
//.then()
};
const touchAsync = (path, options) => {
return writeASync(path, options.content !== undefined ? options.content : '', {

View File

@ -1,61 +1 @@
import { Options as AppendOptions } from './append.js';
import { IOptions as DirOptions } from './dir.js';
import { IOptions as FileOptions } from './file.js';
import { IOptions as FindOptions } from './find.js';
import { Options as InspectTreeOptions } from './inspect_tree.js';
import { IWriteOptions } from './interfaces.js';
import { ICopyOptions, INode, IInspectOptions } from './interfaces.js';
import { ReadWriteDataType, TCopyResult, ENodeType, TDeleteResult } from './interfaces.js';
export interface IJetpack {
cwd(w?: any): IJetpack | string;
path(): string;
append(path: string, data: string | Buffer | object, options?: AppendOptions): void;
appendAsync(path: string, data: string | Buffer | object, options?: AppendOptions): Promise<void>;
copy(from: string, to: string, options?: ICopyOptions): void;
copyAsync(from: string, to: string, options?: ICopyOptions): Promise<TCopyResult>;
createWriteStream(path: string, options?: {
flags?: string;
encoding?: string;
fd?: number;
mode?: number;
autoClose?: boolean;
start?: number;
}): any;
createReadStream(path: string, options?: {
flags?: string;
encoding?: string;
fd?: number;
mode?: number;
autoClose?: boolean;
start?: number;
end?: number;
}): any;
dir(path: string, criteria?: DirOptions): IJetpack;
dirAsync(path: string, criteria?: DirOptions): Promise<IJetpack>;
exists(path: string): boolean | string;
existsAsync(path: string): Promise<boolean | string | ENodeType>;
file(path: string, criteria?: FileOptions): void;
fileAsync(path: string, criteria?: FileOptions): Promise<null>;
find(startPath: string, options: FindOptions): string[];
findAsync(startPath: string, options: FindOptions): Promise<string[]>;
inspect(path: string, fieldsToInclude: IInspectOptions): INode;
inspectAsync(path: string, fieldsToInclude: IInspectOptions): Promise<INode>;
inspectTree(path: string, options?: InspectTreeOptions): INode;
inspectTreeAsync(path: string, options?: InspectTreeOptions): Promise<INode>;
list(path: string): string[];
listAsync(path: string): Promise<string[]>;
move(from: string, to: string): void;
moveAsync(from: string, to: string): Promise<null>;
read(path: string, returnAs?: string): ReadWriteDataType;
readAsync(path: string, returnAs?: string): Promise<ReadWriteDataType>;
remove(path: string): void;
removeAsync(path: string): Promise<TDeleteResult>;
rename(path: string, newName: string): void;
renameAsync(path: string, newName: string): Promise<null>;
symlink(symlinkValue: string, path: string): void;
symlinkAsync(symlinkValue: string, path: string): Promise<void>;
write(path: string, data: string | Buffer | object, options?: IWriteOptions): void;
writeAsync(path: string, data: string | Buffer | object, options?: IWriteOptions): Promise<null>;
}
export declare const fs: (cwdPath?: string) => IJetpack;
export default fs;
export {};

View File

@ -1,47 +1 @@
import * as util from 'util';
import * as pathUtil from 'path';
// The Jetpack Context object.
// It provides the public API, and resolves all paths regarding to
// passed cwdPath, or default process.cwd() if cwdPath was not specified.
export const fs = (cwdPath) => {
const getCwdPath = function () {
return cwdPath || process.cwd();
};
const cwd = function (w) {
let args;
let pathParts;
// return current CWD if no arguments specified...
if (arguments.length === 0) {
return getCwdPath();
}
// ...create new CWD context otherwise
args = Array.prototype.slice.call(arguments);
pathParts = [getCwdPath()].concat(args);
const res = fs(pathUtil.resolve.apply(null, pathParts));
return res;
};
// resolves path to inner CWD path of this jetpack instance
const resolvePath = function (path) {
return pathUtil.resolve(getCwdPath(), path);
};
const getPath = function () {
// add CWD base path as first element of arguments array
Array.prototype.unshift.call(arguments, getCwdPath());
return pathUtil.resolve.apply(null, arguments);
};
const normalizeOptions = function (options) {
return options || { cwd: getCwdPath() };
};
// API
const api = {};
if (util.inspect['custom'] !== undefined) {
// Without this console.log(jetpack) throws obscure error. Details:
// https://github.com/szwacz/fs-jetpack/issues/29
// https://nodejs.org/api/util.html#util_custom_inspection_functions_on_objects
api[util.inspect['custom']] = function () {
return getCwdPath();
};
}
return api;
};
export default fs;
export {};

6
packages/fs/dist/utils/index.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
export * from "./wildcard.js";
export * from "./paths.js";
export * from "./name.js";
export * from "./matcher.js";
export * from "./mime_match.js";
export * from "./validate.js";

6
packages/fs/dist/utils/index.js vendored Normal file
View File

@ -0,0 +1,6 @@
export * from "./wildcard.js";
export * from "./paths.js";
export * from "./name.js";
export * from "./matcher.js";
export * from "./mime_match.js";
export * from "./validate.js";

31
packages/fs/dist/utils/name.d.ts vendored Normal file
View File

@ -0,0 +1,31 @@
export declare enum E_FilenameError {
NONE = 0,// No error
EMPTY = 1,// Empty or whitespace-only filename
INVALID_CHAR = 2,// Contains invalid characters
RESERVED_NAME = 4,// Matches a reserved system name
LEADING_TRAILING_SPACE = 8,// Starts/ends with space
ONLY_DOTS = 16
}
export interface I_SanitizeOptions {
lowercase?: boolean;
whitespace?: boolean;
}
export interface I_ValidationResult {
isValid: boolean;
errorFlags: number;
}
/**
* Sanitizes a filename by removing invalid characters and normalizing it.
*
* @param filename - The original filename
* @param options - Configuration options
* @returns Sanitized filename
*/
export declare function sanitizeFilename(filename?: string, options?: I_SanitizeOptions): string;
/**
* Validates a filename and returns a flag-based error representation.
*
* @param filename - The filename to validate
* @returns I_ValidationResult object with bitwise error flags
*/
export declare function validateFilename(filename: string): I_ValidationResult;

81
packages/fs/dist/utils/name.js vendored Normal file
View File

@ -0,0 +1,81 @@
const RESERVED_NAMES = new Set([
"con", "prn", "aux", "nul",
"com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8", "com9",
"lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9"
]);
// Validation error flags (bitwise)
export var E_FilenameError;
(function (E_FilenameError) {
E_FilenameError[E_FilenameError["NONE"] = 0] = "NONE";
E_FilenameError[E_FilenameError["EMPTY"] = 1] = "EMPTY";
E_FilenameError[E_FilenameError["INVALID_CHAR"] = 2] = "INVALID_CHAR";
E_FilenameError[E_FilenameError["RESERVED_NAME"] = 4] = "RESERVED_NAME";
E_FilenameError[E_FilenameError["LEADING_TRAILING_SPACE"] = 8] = "LEADING_TRAILING_SPACE";
E_FilenameError[E_FilenameError["ONLY_DOTS"] = 16] = "ONLY_DOTS"; // Filename is only "." or ".."
})(E_FilenameError || (E_FilenameError = {}));
/**
* Sanitizes a filename by removing invalid characters and normalizing it.
*
* @param filename - The original filename
* @param options - Configuration options
* @returns Sanitized filename
*/
export function sanitizeFilename(filename = "", options = { lowercase: false, whitespace: false }) {
const { lowercase = false, whitespace = true } = options;
// Normalize Unicode (removes diacritics)
let sanitized = filename
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "") // Strip accents
.replace(/[^\w.\- ]/g, "") // Keep only alphanumeric, dot, hyphen, underscore, and space
.trim(); // Remove leading/trailing spaces
// Replace spaces with underscores if enabled
if (whitespace) {
sanitized = sanitized.replace(/\s+/g, "_");
}
// Convert to lowercase if enabled
if (lowercase) {
sanitized = sanitized.toLowerCase();
}
// Prevent reserved names (Windows)
if (RESERVED_NAMES.has(sanitized.toLowerCase())) {
return sanitized + "_safe";
}
// Prevent filenames that are just dots or empty
if (!sanitized || sanitized === "." || sanitized === "..") {
return "untitled";
}
return sanitized;
}
/**
* Validates a filename and returns a flag-based error representation.
*
* @param filename - The filename to validate
* @returns I_ValidationResult object with bitwise error flags
*/
export function validateFilename(filename) {
let errorFlags = E_FilenameError.NONE;
const trimmed = filename.trim();
if (!trimmed) {
errorFlags |= E_FilenameError.EMPTY;
}
// Detect invalid characters (only allow alphanumeric, dot, hyphen, underscore)
if (/[^a-zA-Z0-9._-]/.test(filename)) {
errorFlags |= E_FilenameError.INVALID_CHAR;
}
// Prevent reserved filenames (Windows)
if (RESERVED_NAMES.has(filename.toLowerCase())) {
errorFlags |= E_FilenameError.RESERVED_NAME;
}
// Check for leading or trailing spaces
if (/^\s|\s$/.test(filename)) {
errorFlags |= E_FilenameError.LEADING_TRAILING_SPACE;
}
// Prevent filenames that are only "." or ".."
if (filename === "." || filename === "..") {
errorFlags |= E_FilenameError.ONLY_DOTS;
}
return {
isValid: errorFlags === E_FilenameError.NONE,
errorFlags
};
}

View File

@ -7,6 +7,14 @@
"access": "public"
},
"exports": {
"./utils": {
"import": "./dist/utils/index.js",
"require": "./dist/utils/index.cjs"
},
"./append": {
"import": "./dist/append.js",
"require": "./dist/append.cjs"
},
"./write": {
"import": "./dist/write.js",
"require": "./dist/write.cjs"

View File

@ -136,7 +136,6 @@ const checkContentAsync = (path: string, mode: string, options: IOptions):Promis
const writeAsync = async (path: string, stat: Stats, options: IOptions): Promise<any> => {
const mode = normalizeFileMode(stat.mode);
return checkContentAsync(path, mode, options)
//.then()
}
const touchAsync = (path: string, options: IOptions) => {

View File

@ -29,111 +29,3 @@ import * as stats from './stats.js'
import { ICopyOptions, INode, IInspectOptions } from './interfaces.js'
import { ReadWriteDataType, TCopyResult, ENodeType, TDeleteResult } from './interfaces.js'
export interface IJetpack {
cwd(w?: any): IJetpack | string;
path(): string;
append(path: string, data: string | Buffer | object, options?: AppendOptions): void;
appendAsync(path: string, data: string | Buffer | object, options?: AppendOptions): Promise<void>;
copy(from: string, to: string, options?: ICopyOptions): void;
copyAsync(from: string, to: string, options?: ICopyOptions): Promise<TCopyResult>;
createWriteStream(path: string, options?: {
flags?: string;
encoding?: string;
fd?: number;
mode?: number;
autoClose?: boolean;
start?: number;
}): any;
createReadStream(path: string, options?: {
flags?: string;
encoding?: string;
fd?: number;
mode?: number;
autoClose?: boolean;
start?: number;
end?: number;
}): any;
dir(path: string, criteria?: DirOptions): IJetpack;
dirAsync(path: string, criteria?: DirOptions): Promise<IJetpack>;
exists(path: string): boolean | string;
existsAsync(path: string): Promise<boolean | string | ENodeType>;
file(path: string, criteria?: FileOptions): void;
fileAsync(path: string, criteria?: FileOptions): Promise<null>;
find(startPath: string, options: FindOptions): string[];
findAsync(startPath: string, options: FindOptions): Promise<string[]>;
inspect(path: string, fieldsToInclude: IInspectOptions): INode;
inspectAsync(path: string, fieldsToInclude: IInspectOptions): Promise<INode>;
inspectTree(path: string, options?: InspectTreeOptions): INode;
inspectTreeAsync(path: string, options?: InspectTreeOptions): Promise<INode>;
list(path: string): string[];
listAsync(path: string): Promise<string[]>;
move(from: string, to: string): void;
moveAsync(from: string, to: string): Promise<null>;
read(path: string, returnAs?: string): ReadWriteDataType;
readAsync(path: string, returnAs?: string): Promise<ReadWriteDataType>;
remove(path: string): void;
removeAsync(path: string): Promise<TDeleteResult>;
rename(path: string, newName: string): void;
renameAsync(path: string, newName: string): Promise<null>;
symlink(symlinkValue: string, path: string): void;
symlinkAsync(symlinkValue: string, path: string): Promise<void>;
write(path: string, data: string | Buffer | object, options?: IWriteOptions): void;
writeAsync(path: string, data: string | Buffer | object, options?: IWriteOptions): Promise<null>;
}
// The Jetpack Context object.
// It provides the public API, and resolves all paths regarding to
// passed cwdPath, or default process.cwd() if cwdPath was not specified.
export const fs = (cwdPath?: string): IJetpack => {
const getCwdPath = function () {
return cwdPath || process.cwd();
};
const cwd = function (w?: any): IJetpack | string {
let args;
let pathParts;
// return current CWD if no arguments specified...
if (arguments.length === 0) {
return getCwdPath();
}
// ...create new CWD context otherwise
args = Array.prototype.slice.call(arguments);
pathParts = [getCwdPath()].concat(args);
const res = fs(pathUtil.resolve.apply(null, pathParts));
return res;
};
// resolves path to inner CWD path of this jetpack instance
const resolvePath = function (path: string): string {
return pathUtil.resolve(getCwdPath(), path);
};
const getPath = function (): string {
// add CWD base path as first element of arguments array
Array.prototype.unshift.call(arguments, getCwdPath());
return pathUtil.resolve.apply(null, arguments);
};
const normalizeOptions = function (options: { cwd?: string }): any {
return options || { cwd: getCwdPath() };
};
// API
const api: any = {
};
if ((util.inspect as any)['custom'] !== undefined) {
// Without this console.log(jetpack) throws obscure error. Details:
// https://github.com/szwacz/fs-jetpack/issues/29
// https://nodejs.org/api/util.html#util_custom_inspection_functions_on_objects
(api as any)[(util.inspect as any)['custom']] = function () {
return getCwdPath();
};
}
return api;
};
export default fs;

View File

@ -1,11 +0,0 @@
#!/usr/bin/env node
import * as cli from 'yargs'
const argv: any = cli.argv
if (argv.h || argv.help) {
cli.showHelp();
process.exit();
} else if (argv.v || argv.version) {
process.exit();
}

View File

@ -8,3 +8,4 @@ export function isDotFile(str: string) {
const last = str.lastIndexOf('/');
return last !== -1 ? str.charCodeAt(last + 1) === 46 /* . */ : false;
}

View File

@ -0,0 +1,6 @@
export * from "./wildcard.js"
export * from "./paths.js"
export * from "./name.js"
export * from "./matcher.js"
export * from "./mime_match.js"
export * from "./validate.js"

View File

@ -0,0 +1,106 @@
const RESERVED_NAMES = new Set([
"con", "prn", "aux", "nul",
"com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8", "com9",
"lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9"
]);
// Validation error flags (bitwise)
export enum E_FilenameError {
NONE = 0, // No error
EMPTY = 1 << 0, // Empty or whitespace-only filename
INVALID_CHAR = 1 << 1, // Contains invalid characters
RESERVED_NAME = 1 << 2, // Matches a reserved system name
LEADING_TRAILING_SPACE = 1 << 3, // Starts/ends with space
ONLY_DOTS = 1 << 4 // Filename is only "." or ".."
}
export interface I_SanitizeOptions {
lowercase?: boolean; // Convert to lowercase (default: false)
whitespace?: boolean; // Replace spaces with underscores (default: true)
}
export interface I_ValidationResult {
isValid: boolean; // Overall validation status
errorFlags: number; // Bitwise error representation
}
/**
* Sanitizes a filename by removing invalid characters and normalizing it.
*
* @param filename - The original filename
* @param options - Configuration options
* @returns Sanitized filename
*/
export function sanitizeFilename(filename: string = "", options: I_SanitizeOptions = { lowercase: false, whitespace: false }): string {
const { lowercase = false, whitespace = true } = options;
// Normalize Unicode (removes diacritics)
let sanitized = filename
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "") // Strip accents
.replace(/[^\w.\- ]/g, "") // Keep only alphanumeric, dot, hyphen, underscore, and space
.trim(); // Remove leading/trailing spaces
// Replace spaces with underscores if enabled
if (whitespace) {
sanitized = sanitized.replace(/\s+/g, "_");
}
// Convert to lowercase if enabled
if (lowercase) {
sanitized = sanitized.toLowerCase();
}
// Prevent reserved names (Windows)
if (RESERVED_NAMES.has(sanitized.toLowerCase())) {
return sanitized + "_safe";
}
// Prevent filenames that are just dots or empty
if (!sanitized || sanitized === "." || sanitized === "..") {
return "untitled";
}
return sanitized;
}
/**
* Validates a filename and returns a flag-based error representation.
*
* @param filename - The filename to validate
* @returns I_ValidationResult object with bitwise error flags
*/
export function validateFilename(filename: string): I_ValidationResult {
let errorFlags = E_FilenameError.NONE;
const trimmed = filename.trim();
if (!trimmed) {
errorFlags |= E_FilenameError.EMPTY;
}
// Detect invalid characters (only allow alphanumeric, dot, hyphen, underscore)
if (/[^a-zA-Z0-9._-]/.test(filename)) {
errorFlags |= E_FilenameError.INVALID_CHAR;
}
// Prevent reserved filenames (Windows)
if (RESERVED_NAMES.has(filename.toLowerCase())) {
errorFlags |= E_FilenameError.RESERVED_NAME;
}
// Check for leading or trailing spaces
if (/^\s|\s$/.test(filename)) {
errorFlags |= E_FilenameError.LEADING_TRAILING_SPACE;
}
// Prevent filenames that are only "." or ".."
if (filename === "." || filename === "..") {
errorFlags |= E_FilenameError.ONLY_DOTS;
}
return {
isValid: errorFlags === E_FilenameError.NONE,
errorFlags
};
}