@synstack/fs
Version:
File system operations made easy
1,569 lines (1,563 loc) • 48.9 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/fs.index.ts
var fs_index_exports = {};
__export(fs_index_exports, {
FsDir: () => FsDir2,
FsFile: () => FsFile,
dir: () => dir,
file: () => file,
files: () => files,
filesFromDir: () => filesFromDir,
fsDir: () => fsDir,
fsFile: () => fsFile,
fsFiles: () => fsFiles,
fsFilesFromDir: () => fsFilesFromDir
});
module.exports = __toCommonJS(fs_index_exports);
// src/dir.lib.ts
var import_git = require("@synstack/git");
var import_glob2 = require("@synstack/glob");
var import_path4 = require("@synstack/path");
var import_pipe2 = require("@synstack/pipe");
var fsSync2 = __toESM(require("fs"), 1);
var fs2 = __toESM(require("fs/promises"), 1);
// src/dirs-array.lib.ts
var import_enhance = require("@synstack/enhance");
var import_path = require("@synstack/path");
var dirsArrayMethods = {
filter(fn) {
return fsDirs(this.filter(fn));
},
toPaths() {
return this.map((dir2) => dir2.path);
},
relativePathsFrom(dirOrFileOrPath) {
return this.map((dir2) => dir2.relativePathFrom(dirOrFileOrPath));
}
};
var fsDirs = (dirs2) => (0, import_enhance.enhance)("dirs_array", dirs2.map(fsDir), dirsArrayMethods);
var dirs = fsDirs;
// src/file.lib.ts
var import_glob = require("@synstack/glob");
var import_json = require("@synstack/json");
var import_markdown = require("@synstack/markdown");
var import_path2 = require("@synstack/path");
var import_pipe = require("@synstack/pipe");
var import_str = require("@synstack/str");
var import_xml = require("@synstack/xml");
var import_yaml = require("@synstack/yaml");
var fsSync = __toESM(require("fs"), 1);
var fs = __toESM(require("fs/promises"), 1);
var FsFile = class _FsFile extends import_pipe.Pipeable {
_path;
_encoding;
_schema;
/**
* Create a new FsFile instance from a path, a list of paths to be resolved, or an existing FsFile instance.
* The resulting path will be an absolute path.
*
* @param paths - A path or an existing FsFile instance
* @returns A new FsFile instance with UTF-8 encoding
*
* ```typescript
* import { fsFile } from "@synstack/fs";
*
* const relativeFile = fsFile("./relative/path.txt");
* const existingFile = fsFile(fsFile("/path/to/existing.txt"));
* ```
*/
static from(arg) {
if (arg instanceof _FsFile) return arg;
return new _FsFile(import_path2.path.resolve(arg), "utf-8");
}
constructor(path3, encoding, schema) {
super();
this._path = path3;
this._encoding = encoding ?? "utf-8";
this._schema = schema ?? void 0;
}
/**
* Provide a validation schema for the file content. To be used with:
* - `.read.json`
* - `.write.json`
* - `.read.yaml`
* - `.write.yaml`
*/
/**
* Provide a validation schema for JSON/YAML operations.
* The schema will be used to validate data when reading or writing JSON/YAML files.
*
* @typeParam NewSchema - The Zod schema type for validation
* @param schema - A Zod schema to validate JSON/YAML data
* @returns A new FsFile instance with the schema attached
*
* ```typescript
* import { fsFile } from "@synstack/fs";
* import { z } from "zod";
*
* const ConfigSchema = z.object({
* port: z.number(),
* host: z.string()
* });
*
* const config = await fsFile("config.json")
* .schema(ConfigSchema)
* .read.json();
* // config is typed as { port: number, host: string }
* ```
*/
schema(schema) {
return new _FsFile(this._path, this._encoding, schema);
}
/**
* Get the path of the file.
*
* @returns The absolute path of the file
*/
valueOf() {
return this._path;
}
/**
* Get the current instance of the file.
* Used for type compatibility with Pipeable.
*
* @returns The current FsFile instance
*/
instanceOf() {
return this;
}
// #region sub actions
/**
* Access the read operations for the file.
* Provides methods for reading file contents in various formats.
*
* @returns An FsFileRead instance with methods for reading the file
*
* ```typescript
* const content = await fsFile("data.txt").read.text();
* const json = await fsFile("config.json").read.json();
* const yaml = await fsFile("config.yml").read.yaml();
* ```
*/
get read() {
return new FsFileRead(
this._path,
this._encoding,
this._schema
);
}
/**
* Access the write operations for the file.
* Provides methods for writing content to the file in various formats.
*
* @returns An FsFileWrite instance with methods for writing to the file
*
* ```typescript
* await fsFile("data.txt").write.text("Hello");
* await fsFile("config.json").write.json({ hello: "world" });
* await fsFile("config.yml").write.yaml({ config: true });
* ```
*/
get write() {
return new FsFileWrite(
this._path,
this._encoding,
"overwrite",
this._schema
);
}
// #endregion
// #region sync
/**
* Get the absolute path of the file.
*
* @returns The absolute path as a string
*/
get path() {
return this._path;
}
/**
* Get the absolute path of the directory containing the file.
*
* @returns The directory path as a string
*/
dirPath() {
return import_path2.path.dirname(this._path);
}
/**
* Get an FsDir instance representing the directory containing the file.
*
* @returns An FsDir instance for the parent directory
*
* ```typescript
* const file = fsFile("/path/to/file.txt");
* const parentDir = file.dir(); // FsDir for "/path/to"
* ```
*/
dir() {
return FsDir2.cwd(this.dirPath());
}
/**
* Get the name of the file including its extension.
*
* @returns The file name with extension
*
* ```typescript
* const file = fsFile("/path/to/document.txt");
* console.log(file.fileName()); // "document.txt"
* ```
*/
fileName() {
return import_path2.path.filename(this._path);
}
/**
* Get the extension of the file.
*
* @returns The file extension including the dot (e.g., ".txt")
*
* ```typescript
* const file = fsFile("/path/to/document.txt");
* console.log(file.fileExtension()); // ".txt"
* ```
*/
fileExtension() {
return import_path2.path.fileExtension(this._path);
}
/**
* Get the name of the file without its extension.
*
* @returns The file name without extension
*
* ```typescript
* const file = fsFile("/path/to/document.txt");
* console.log(file.fileNameWithoutExtension()); // "document"
* ```
*/
fileNameWithoutExtension() {
return import_path2.path.filenameWithoutExtension(this._path);
}
/**
* Get the MIME type of the file based on its extension.
*
* @returns The MIME type string or null if it cannot be determined
*
* ```typescript
* const file = fsFile("/path/to/image.png");
* console.log(file.mimeType()); // "image/png"
* ```
*/
mimeType() {
return import_path2.path.mimeType(this._path);
}
/**
* Create a new FsFile instance with a path relative to this file's directory.
*
* @param relativePath - The relative path from this file's directory
* @returns A new FsFile instance for the target path
*
* ```typescript
* import { fsFile } from "@synstack/fs";
*
* const sourceFile = fsFile("/path/to/source.txt");
* const targetFile = sourceFile.toFile("../output/target.txt");
* // targetFile.path === "/path/output/target.txt"
* ```
*/
toFile(relativePath) {
const newPath = import_path2.path.resolve(this.dirPath(), relativePath);
return new _FsFile(newPath);
}
/**
* Create a new FsDir instance with a path relative to this file's directory.
*
* @param relativePath - The relative path from this file's directory
* @returns A new FsDir instance for the target directory
*
* ```typescript
* import { fsFile } from "@synstack/fs";
*
* const sourceFile = fsFile("/path/to/source.txt");
* const outputDir = sourceFile.toDir("../output");
* // outputDir.path === "/path/output"
* ```
*/
toDir(relativePath) {
const newPath = import_path2.path.join(this.dirPath(), relativePath);
return FsDir2.cwd(newPath);
}
/**
* Get the relative path from another file to this file
*
* ```ts
* import { fsFile } from "@synstack/fs";
*
* const file1 = fsFile("/path/to/file1.txt");
* const file2 = fsFile("/path/to-other/file2.txt");
*
* console.log(file1.relativePathFrom(file2)); // ../to/file1.txt
* ```
*/
relativePathFrom(dirOrFile) {
if (dirOrFile instanceof _FsFile)
return this.relativePathFrom(dirOrFile.dir());
return import_path2.path.relative(dirOrFile.path, this.path);
}
/**
* Get the relative path to go from this file to another
*
* ```ts
* import { fsFile } from "@synstack/fs";
*
* const file1 = fsFile("/path/to/file1.txt");
* const file2 = fsFile("/path/to-other/file2.txt");
*
* console.log(file1.relativePathTo(file2)); // ../to-other/file2.txt
* ```
*/
relativePathTo(dirOrFileOrPath) {
return import_path2.path.relative(this.dirPath(), dirOrFileOrPath.path);
}
/**
* Check if the file is located within the specified directory.
*
* @param dirOrPath - The directory or path to check against
* @returns True if the file is in the directory, false otherwise
*
* ```typescript
* import { fsFile, fsDir } from "@synstack/fs";
*
* const sourceFile = fsFile("/path/to/file.txt");
* console.log(sourceFile.isInDir(fsDir("/path"))); // true
* console.log(sourceFile.isInDir(fsDir("/other"))); // false
* ```
*/
isInDir(dirOrPath) {
return import_path2.path.isInPath(dirOrPath.valueOf(), this._path.valueOf());
}
/**
* Delete the file from the file system.
* If the file doesn't exist, the operation is silently ignored.
*
* @returns A promise that resolves when the file is deleted
*
* ```typescript
* import { fsFile } from "@synstack/fs";
*
* const tempFile = fsFile("./temp.txt");
* await tempFile.write.text("temporary content");
* await tempFile.remove(); // File is deleted
* ```
*/
async remove() {
await fs.rm(this._path, { recursive: true }).catch((e) => {
if (e.code === "ENOENT") return;
throw e;
});
}
/**
* @deprecated Use {@link remove} instead.
*/
rm() {
return this.remove();
}
/**
* Delete the file from the file system synchronously.
* If the file doesn't exist, the operation is silently ignored.
*
* @synchronous
*
* ```typescript
* import { fsFile } from "@synstack/fs";
*
* const tempFile = fsFile("./temp.txt");
* tempFile.write.textSync("temporary content");
* tempFile.removeSync(); // File is deleted immediately
* ```
*/
removeSync() {
try {
fsSync.rmSync(this._path, { recursive: false });
} catch (error) {
if (error.code === "ENOENT") return;
throw error;
}
}
/**
* @deprecated Use {@link removeSync} instead.
*/
rmSync() {
this.removeSync();
}
/**
* Move the file to a new location.
*
* @param newPath - The new path for the file or an existing FsFile instance
* @returns A promise that resolves the new file
*/
async move(newPath) {
const newFile = _FsFile.from(newPath);
await fs.rename(this._path, newFile.path);
return newFile;
}
/**
* Move the file to a new location synchronously.
*
* @param newPath - The new path for the file or an existing FsFile instance
* @returns The new file
*/
moveSync(newPath) {
const newFile = _FsFile.from(newPath);
fsSync.renameSync(this._path, newFile.path);
return newFile;
}
/**
* Check if the file exists in the file system.
*
* @returns A promise that resolves to true if the file exists, false otherwise
*
* ```typescript
* import { fsFile } from "@synstack/fs";
*
* const configFile = fsFile("./config.json");
* if (await configFile.exists()) {
* const config = await configFile.read.json();
* }
* ```
*/
async exists() {
return fs.access(this._path, fs.constants.F_OK).then(() => true).catch(() => false);
}
/**
* Check if the file exists in the file system synchronously.
*
* @synchronous
* @returns True if the file exists, false otherwise
*
* ```typescript
* import { fsFile } from "@synstack/fs";
*
* const configFile = fsFile("./config.json");
* if (configFile.existsSync()) {
* const config = configFile.read.jsonSync();
* }
* ```
*/
existsSync() {
try {
fsSync.accessSync(this._path);
return true;
} catch (error) {
if (error.code === "ENOENT") return false;
throw error;
}
}
/**
* Get the creation date of the file.
*
* @returns A promise that resolves to the file's creation date
* @throws If the file doesn't exist or cannot be accessed
*
* ```typescript
* import { fsFile } from "@synstack/fs";
*
* const sourceFile = fsFile("./source.txt");
* const created = await sourceFile.creationDate();
* console.log(`File created on: ${created.toISOString()}`);
* ```
*/
async creationDate() {
const fileStats = await fs.stat(this._path);
return fileStats.birthtime;
}
/**
* Get the creation date of the file synchronously.
*
* @synchronous
* @returns The file's creation date
* @throws If the file doesn't exist or cannot be accessed
*
* ```typescript
* import { fsFile } from "@synstack/fs";
*
* const sourceFile = fsFile("./source.txt");
* const created = sourceFile.creationDateSync();
* console.log(`File created on: ${created.toISOString()}`);
* ```
*/
creationDateSync() {
const stats = fsSync.statSync(this._path);
return stats.birthtime;
}
/**
* Check if the file path matches any of the provided glob patterns.
*
* @param globs - One or more glob patterns to match against, either as separate arguments or an array
* @returns True if the file matches any pattern, false otherwise
*
* ```typescript
* import { fsFile } from "@synstack/fs";
*
* const sourceFile = fsFile("./src/components/Button.tsx");
* console.log(sourceFile.matchesGlobs("**\/*.tsx")); // true
* console.log(sourceFile.matchesGlobs(["*.css", "*.html"])); // false
* console.log(sourceFile.matchesGlobs("**\/*.ts", "**\/*.tsx")); // true
* ```
*/
matchesGlobs(...globs) {
return import_glob.glob.matches(this._path, ...globs);
}
/**
* Capture parts of the file path using a glob pattern
*
* ```ts
* import { fsFile } from "@synstack/fs";
*
* const myFile = fsFile("/my-domain/my-sub-domain/features/feature-name.controller.ts");
* const res = myFile.globCapture("/(*)/(*)/features/(*).controller.ts");
* if (!res) throw new Error("File doesn't match glob pattern");
* console.log(res[1]); // my-domain
* console.log(res[2]); // my-sub-domain
* console.log(res[3]); // feature-name.controller.ts
* ```
*/
globCapture(globPattern) {
return import_glob.glob.capture(globPattern, this._path);
}
};
var FsFileRead = class {
_path;
_encoding;
_schema;
constructor(path3, encoding, schema) {
this._path = path3;
this._encoding = encoding;
this._schema = schema;
}
get path() {
return this._path;
}
// #region sync
/**
* Read the file contents as a string.
*
* @returns A promise that resolves to the file contents as a string
* @throws If the file doesn't exist or cannot be read
*
* ```typescript
* const content = await fsFile("data.txt").read.text();
* console.log(content); // "Hello, World!"
* ```
*/
async text() {
return fs.readFile(this._path, this._encoding);
}
/**
* Read the file contents as a string synchronously.
*
* @synchronous
* @returns The file contents as a string
* @throws If the file doesn't exist or cannot be read
*
* ```typescript
* const content = fsFile("data.txt").read.textSync();
* console.log(content); // "Hello, World!"
* ```
*/
textSync() {
return fsSync.readFileSync(this._path, this._encoding);
}
/**
* Read the file contents and return a chainable string instance.
* Used for further manipulation of the content using @synstack/str methods.
*
* @returns A promise that resolves to a chainable string instance
* @throws If the file doesn't exist or cannot be read
*
* ```typescript
* const content = await fsFile("data.txt").read.str();
* const lines = content
* .split("\n")
* .filter((line) => line.trim().length > 0);
* ```
*/
async str() {
return this.text().then(import_str.str);
}
/**
* Read the file contents and return a chainable string instance synchronously.
* Used for further manipulation of the content using @synstack/str methods.
*
* @synchronous
* @returns A chainable string instance
* @throws If the file doesn't exist or cannot be read
*
* ```typescript
* const content = fsFile("data.txt").read.strSync();
* const lines = content
* .split("\n")
* .filter((line) => line.trim().length > 0);
* ```
*/
strSync() {
return (0, import_str.str)(this.textSync());
}
/**
* Read and parse the file contents as JSON.
* If a schema is provided, the parsed data will be validated against it.
*
* @returns A promise that resolves to the parsed JSON data
* @throws If the file doesn't exist, cannot be read, or contains invalid JSON
* @throws If schema validation fails when a schema is provided
*
* ```typescript
* interface Config {
* port: number;
* host: string;
* }
*
* const config = await fsFile("config.json")
* .schema(ConfigSchema)
* .read.json();
* // config is automatically typed as the schema's output type
* ```
*/
json() {
return this.text().then(
(t) => import_json.json.deserialize(t, { schema: this._schema })
);
}
/**
* Read and parse the file contents as JSON synchronously.
* If a schema is provided, the parsed data will be validated against it.
*
* @synchronous
* @returns The parsed JSON data
* @throws If the file doesn't exist, cannot be read, or contains invalid JSON
* @throws If schema validation fails when a schema is provided
*
* ```typescript
* const config = fsFile("config.json")
* .schema(ConfigSchema)
* .read.jsonSync();
* // config is automatically typed as the schema's output type
* ```
*/
jsonSync() {
return import_json.json.deserialize(this.textSync(), {
schema: this._schema
});
}
/**
* Read and parse the file contents as YAML.
* If a schema is provided, the parsed data will be validated against it.
*
* @typeParam T - The type of the parsed YAML data
* @returns A promise that resolves to the parsed YAML data
* @throws If the file doesn't exist, cannot be read, or contains invalid YAML
* @throws If schema validation fails when a schema is provided
*
* ```typescript
* interface Config {
* environment: string;
* settings: Record<string, unknown>;
* }
*
* const config = await fsFile("config.yml")
* .schema(ConfigSchema)
* .read.yaml();
* // config is automatically typed as the schema's output type
* ```
*/
yaml() {
return this.text().then(
(t) => import_yaml.yaml.deserialize(t, { schema: this._schema })
);
}
/**
* Read and parse the file contents as YAML synchronously.
* If a schema is provided, the parsed data will be validated against it.
*
* @typeParam T - The type of the parsed YAML data
* @synchronous
* @returns The parsed YAML data
* @throws If the file doesn't exist, cannot be read, or contains invalid YAML
* @throws If schema validation fails when a schema is provided
*
* ```typescript
* const config = fsFile("config.yml")
* .schema(ConfigSchema)
* .read.yamlSync();
* // config is automatically typed as the schema's output type
* ```
*/
yamlSync() {
return import_yaml.yaml.deserialize(this.textSync(), {
schema: this._schema
});
}
/**
* Read and parse the file contents as XML using @synstack/xml.
* This parser is specifically designed for LLM-related XML processing.
*
* @typeParam T - The type of the parsed XML nodes array, must extend Array<Xml.Node>
* @returns A promise that resolves to the parsed XML nodes
* @throws If the file doesn't exist, cannot be read, or contains invalid XML
* @see {@link https://github.com/pAIrprogio/synscript/tree/main/packages/xml|@synstack/xml documentation}
*
* ```typescript
* interface XmlNode {
* tag: string;
* attributes: Record<string, string>;
* children: Array<XmlNode>;
* }
*
* const nodes = await fsFile("data.xml").read.xml<XmlNode[]>();
* console.log(nodes[0].tag); // "root"
* console.log(nodes[0].attributes.id); // "main"
* ```
*
* @remarks
* - Uses a non-spec-compliant XML parser tailored for LLM use cases
* - Optimized for simple XML structures commonly used in LLM responses
* - Does not support all XML features (see documentation for details)
*/
async xml() {
return this.text().then((content) => import_xml.xml.parse(content));
}
/**
* Read and parse the file contents as XML synchronously using @synstack/xml.
* This parser is specifically designed for LLM-related XML processing.
*
* @typeParam T - The type of the parsed XML nodes array, must extend Array<Xml.Node>
* @synchronous
* @returns The parsed XML nodes
* @throws If the file doesn't exist, cannot be read, or contains invalid XML
* @see {@link https://github.com/pAIrprogio/synscript/tree/main/packages/xml|@synstack/xml documentation}
*
* ```typescript
* const nodes = fsFile("data.xml").read.xmlSync<XmlNode[]>();
* console.log(nodes[0].tag); // "root"
* console.log(nodes[0].attributes.id); // "main"
* ```
*
* @remarks
* - Uses a non-spec-compliant XML parser tailored for LLM use cases
* - Optimized for simple XML structures commonly used in LLM responses
* - Does not support all XML features (see documentation for details)
*/
xmlSync() {
return import_xml.xml.parse(this.textSync());
}
/**
* Read the file contents and encode them as a base64 string.
* Useful for handling binary data or preparing content for data URLs.
*
* @returns A promise that resolves to the file contents as a base64-encoded string
* @throws If the file doesn't exist or cannot be read
*
* ```typescript
* // Read and encode an image file
* const imageBase64 = await fsFile("image.png").read.base64();
* console.log(imageBase64); // "iVBORw0KGgoAAAANSUhEUgAA..."
*
* // Create a data URL for use in HTML/CSS
* const dataUrl = `data:image/png;base64,${imageBase64}`;
* ```
*/
async base64() {
return fs.readFile(this._path, "base64");
}
/**
* Read the file contents and encode them as a base64 string synchronously.
* Useful for handling binary data or preparing content for data URLs.
*
* @synchronous
* @returns The file contents as a base64-encoded string
* @throws If the file doesn't exist or cannot be read
*
* ```typescript
* const imageBase64 = fsFile("image.png").read.base64Sync();
* const dataUrl = `data:image/png;base64,${imageBase64}`;
* ```
*/
base64Sync() {
return fsSync.readFileSync(this._path, "base64");
}
/**
* Read the file contents and create a synstack-compatible Base64Data object.
* This format includes MIME type information along with the base64-encoded data.
*
* @param defaultMimeType - The MIME type to use if it cannot be determined from the file extension
* @returns A promise that resolves to a Base64Data object containing the encoded content and MIME type
* @throws If the file doesn't exist or cannot be read
*
* ```typescript
* // Read an image with automatic MIME type detection
* const imageData = await fsFile("image.png").read.base64Data();
* console.log(imageData);
* // {
* // type: "base64",
* // data: "iVBORw0KGgoAAAANSUhEUgAA...",
* // mimeType: "image/png"
* // }
*
* // Specify a custom MIME type for a binary file
* const data = await fsFile("custom.bin")
* .read.base64Data("application/custom");
* ```
*/
async base64Data(defaultMimeType = "application/octet-stream") {
return {
type: "base64",
data: await this.base64(),
mimeType: defaultMimeType
};
}
/**
* Read the file contents and create a synstack-compatible Base64Data object synchronously.
* This format includes MIME type information along with the base64-encoded data.
*
* @param defaultMimeType - The MIME type to use if it cannot be determined from the file extension
* @synchronous
* @returns A Base64Data object containing the encoded content and MIME type
* @throws If the file doesn't exist or cannot be read
*
* ```typescript
* const imageData = fsFile("image.png").read.base64DataSync();
* console.log(imageData);
* // {
* // type: "base64",
* // data: "iVBORw0KGgoAAAANSUhEUgAA...",
* // mimeType: "image/png"
* // }
* ```
*/
base64DataSync(defaultMimeType = "application/octet-stream") {
return {
type: "base64",
data: this.base64Sync(),
mimeType: defaultMimeType
};
}
/**
* Read the file contents and parse them as a markdown document.
* If a schema is provided, the header data will be validated before returning.
*
* @returns A promise that resolves to the markdown document
* @throws If the file doesn't exist, cannot be read, or contains invalid markdown
* @throws If schema validation fails when a schema is provided
*/
md() {
return this.text().then(
(t) => import_markdown.MdDoc.withOptions({
schema: this._schema
}).fromString(t)
);
}
/**
* Read the file contents and parse them as a markdown document synchronously.
* If a schema is provided, the header data will be validated before returning.
*
* @synchronous
* @returns The markdown document
* @throws If the file doesn't exist, cannot be read, or contains invalid markdown
* @throws If schema validation fails when a schema is provided
*/
mdSync() {
return import_markdown.MdDoc.withOptions({
schema: this._schema
}).fromString(this.textSync());
}
};
var FsFileWrite = class _FsFileWrite {
_path;
_encoding;
_schema;
_mode;
constructor(path3, encoding, mode = "overwrite", schema) {
this._path = path3;
this._encoding = encoding;
this._schema = schema;
this._mode = mode;
}
/**
* Set the write mode of the file
* @argument preserve: If the file already exists, it will be left untouched
* @argument overwrite: If the file already exists, it will be overwritten
*/
mode(writeMode) {
return new _FsFileWrite(this._path, this._encoding, writeMode, this._schema);
}
/**
* Write text content to a file asynchronously.
* Creates parent directories if they don't exist.
* Respects the write mode (overwrite/preserve) setting.
*
* @param content - The content to write, will be converted to string using toString()
* @returns A promise that resolves when the write operation is complete
* @throws If the write operation fails or if parent directory creation fails
*/
async text(content) {
if (this._mode === "preserve" && await FsFile.from(this._path).exists())
return;
const dirname = import_path2.path.dirname(this._path);
await fs.mkdir(dirname, { recursive: true });
await fs.writeFile(this._path, content.toString(), this._encoding);
}
/**
* Write text content to a file synchronously.
* Creates parent directories if they don't exist.
* Respects the write mode (overwrite/preserve) setting.
*
* @param content - The content to write, will be converted to string using toString()
* @synchronous
* @throws If the write operation fails or if parent directory creation fails
*/
textSync(content) {
if (this._mode === "preserve" && FsFile.from(this._path).existsSync())
return;
const dirname = import_path2.path.dirname(this._path);
fsSync.mkdirSync(dirname, { recursive: true });
fsSync.writeFileSync(this._path, content.toString(), this._encoding);
}
/**
* Write data as JSON to a file asynchronously.
* The data will be serialized using JSON.stringify.
* If a schema is provided, the data will be validated before writing.
*
* @typeParam T - The type of data being written
* @param data - The data to write, must be JSON-serializable
* @returns A promise that resolves when the write operation is complete
* @throws If schema validation fails or if the write operation fails
*/
async json(data) {
return this.text(import_json.json.serialize(data, { schema: this._schema }));
}
// Todo: add mergeJson
/**
* Write data as formatted JSON to a file asynchronously.
* The data will be serialized using JSON.stringify with pretty printing.
* If a schema is provided, the data will be validated before writing.
*
* @typeParam T - The type of data being written
* @param data - The data to write, must be JSON-serializable
* @returns A promise that resolves when the write operation is complete
* @throws If schema validation fails or if the write operation fails
*/
async prettyJson(data) {
return this.text(
import_json.json.serialize(data, { schema: this._schema, pretty: true }) + "\n"
);
}
/**
* Write data as JSON to a file synchronously.
* The data will be serialized using JSON.stringify.
* If a schema is provided, the data will be validated before writing.
*
* @typeParam T - The type of data being written
* @param data - The data to write, must be JSON-serializable
* @synchronous
* @throws If schema validation fails or if the write operation fails
*/
jsonSync(data) {
return this.textSync(import_json.json.serialize(data, { schema: this._schema }));
}
/**
* Write data as formatted JSON to a file synchronously.
* The data will be serialized using JSON.stringify with pretty printing.
* If a schema is provided, the data will be validated before writing.
*
* @typeParam T - The type of data being written
* @param data - The data to write, must be JSON-serializable
* @synchronous
* @throws If schema validation fails or if the write operation fails
*/
prettyJsonSync(data) {
return this.textSync(
import_json.json.serialize(data, { schema: this._schema, pretty: true }) + "\n"
);
}
/**
* Write data as YAML to a file asynchronously.
* The data will be serialized using YAML.stringify.
* If a schema is provided, the data will be validated before writing.
*
* @typeParam T - The type of data being written
* @param data - The data to write, must be YAML-serializable
* @throws If schema validation fails or if the write operation fails
*/
async yaml(data) {
return this.text(import_yaml.yaml.serialize(data, { schema: this._schema }));
}
/**
* Write data as YAML to a file synchronously.
* The data will be serialized using YAML.stringify.
* If a schema is provided, the data will be validated before writing.
*
* @typeParam T - The type of data being written
* @param data - The data to write, must be YAML-serializable
* @synchronous
* @throws If schema validation fails or if the write operation fails
*/
yamlSync(data) {
return this.textSync(import_yaml.yaml.serialize(data, { schema: this._schema }));
}
/**
* Write base64-encoded data to a file asynchronously.
* Creates parent directories if they don't exist.
* Respects the write mode (overwrite/preserve) setting.
*
* @param data - The base64-encoded string to write
* @throws If the write operation fails or if parent directory creation fails
*/
async base64(data) {
if (this._mode === "preserve" && await FsFile.from(this._path).exists())
return;
const dirname = import_path2.path.dirname(this._path);
await fs.mkdir(dirname, { recursive: true });
await fs.writeFile(this._path, data.toString(), "base64");
}
/**
* Write base64-encoded data to a file synchronously.
* Creates parent directories if they don't exist.
* Respects the write mode (overwrite/preserve) setting.
*
* @param data - The base64-encoded string to write
* @synchronous
* @throws If the write operation fails or if parent directory creation fails
*/
base64Sync(data) {
if (this._mode === "preserve" && FsFile.from(this._path).existsSync())
return;
const dirname = import_path2.path.dirname(this._path);
fsSync.mkdirSync(dirname, { recursive: true });
return fsSync.writeFileSync(this._path, data.toString(), "base64");
}
/**
* Write a markdown document to a file asynchronously.
* The markdown document will be serialized using MdDoc.toMd.
* If a schema is provided, the data will be validated before writing.
*
* @param data - The markdown document to write
* @throws If schema validation fails or if the write operation fails
*/
md(data) {
return this.text(data.toMd());
}
/**
* Write a markdown document to a file synchronously.
* The markdown document will be serialized using MdDoc.toMd.
* If a schema is provided, the data will be validated before writing.
*
* @param data - The markdown document to write
* @throws If schema validation fails or if the write operation fails
* @synchronous
*/
mdSync(data) {
return this.textSync(data.toMd());
}
};
var fsFile = FsFile.from;
var file = FsFile.from;
// src/files-array.lib.ts
var import_enhance2 = require("@synstack/enhance");
var import_path3 = require("@synstack/path");
var filesArrayMethods = {
filter(fn) {
return fsFiles(this.filter(fn));
},
filterGlobs(...patterns) {
return this.filter((file2) => file2.matchesGlobs(...patterns));
},
filterMimeTypes(...mimeTypes) {
const types = new Set(mimeTypes);
return this.filter((file2) => {
const mimeType = file2.mimeType();
if (mimeType === null) return false;
return types.has(mimeType);
});
},
filterDir(dirOrPath) {
return this.filter((file2) => file2.isInDir(dirOrPath));
},
toPaths() {
return this.map((file2) => file2.path);
},
relativePathsFrom(dirOrFileOrPath) {
return this.map((file2) => file2.relativePathFrom(dirOrFileOrPath));
}
};
var fsFiles = (files2) => (0, import_enhance2.enhance)(
"files_array",
files2.map((f) => {
if (f instanceof FsFile) return f;
return fsFile(f);
}),
filesArrayMethods
);
var files = fsFiles;
var fsFilesFromDir = (dir2) => (files2) => (0, import_enhance2.enhance)(
"files_array",
files2.map((f) => {
if (f instanceof FsFile) return f;
return dir2.file(f);
}),
filesArrayMethods
);
var filesFromDir = fsFilesFromDir;
// src/dir.lib.ts
var FsDir2 = class _FsDir extends import_pipe2.Pipeable {
_path;
constructor(path3) {
super();
this._path = path3;
}
/**
* Get the string representation of the directory path.
*
* @returns The absolute path of the directory as a string
*
* ```typescript
* const srcDir = fsDir("./src");
* console.log(srcDir.toString()); // "/absolute/path/to/src"
* ```
*/
toString() {
return this._path;
}
/**
* Get the primitive value of the directory path.
*
* @returns The absolute path of the directory
*/
valueOf() {
return this._path;
}
/**
* Get the current instance of the directory.
* Used for type compatibility with Pipeable.
*
* @returns The current FsDir instance
*/
instanceOf() {
return this;
}
/**
* Get the absolute path of the directory.
*
* @returns The absolute path as a string
*
* ```typescript
* const srcDir = fsDir("./src");
* console.log(srcDir.path); // "/absolute/path/to/src"
* ```
*/
get path() {
return this._path;
}
/**
* Get the relative path from this directory to another file/directory
*
* @param dirOrfile - The other directory
* @returns The relative path as a string
*/
relativePathTo(dirOrfile) {
return import_path4.path.relative(this.path, dirOrfile.path);
}
/**
* Get the relative path from another file/directory to this directory
*
* @param dirOrFile - The other directory
* @returns The relative path as a string
*/
relativePathFrom(dirOrFile) {
if (dirOrFile instanceof FsFile)
return this.relativePathFrom(dirOrFile.dir());
return import_path4.path.relative(dirOrFile.path, this.path);
}
static cwd(arg) {
if (arg instanceof _FsDir) return arg;
return new _FsDir(import_path4.path.resolve(arg));
}
/**
* Create a new directory instance with a path relative to this directory.
*
* @alias {@link to}
* @param relativePath - The relative path to append to the current directory
* @returns A new FsDir instance representing the combined path
*
* ```typescript
* const projectDir = fsDir("/path/to/project");
*
* // Navigate to subdirectories
* const srcDir = projectDir.toDir("src");
* const testDir = projectDir.toDir("tests");
*
* // Navigate up and down
* const siblingDir = srcDir.toDir("../other");
* ```
*/
toDir(relativePath) {
const newPath = import_path4.path.join(this._path, relativePath);
return new _FsDir(newPath);
}
/**
* Create a new directory instance with a path relative to this directory.
*
* @alias {@link toDir}
* @param relativePath - The relative path to append to the current directory
* @returns A new FsDir instance representing the combined path
*
* ```typescript
* const projectDir = fsDir("/path/to/project");
*
* // Navigate to subdirectories
* const srcDir = projectDir.to("src");
* const testDir = projectDir.to("tests");
*
* // Navigate up and down
* const siblingDir = srcDir.to("../other");
* ```
*/
to(relativePath) {
return this.toDir(relativePath);
}
/**
* Create a new file instance with a path relative to this directory.
*
* @alias {@link file}
* @param relativePath - The relative path to the file from this directory
* @returns A new FsFile instance for the specified path
* @throws If an absolute path is provided
*
* ```typescript
* const srcDir = fsDir("./src");
*
* // Access files in the directory
* const configFile = srcDir.toFile("config.json");
* const deepFile = srcDir.toFile("components/Button.tsx");
*
* // Error: Cannot use absolute paths
* srcDir.toFile("/absolute/path"); // throws Error
* ```
*/
toFile(relativePath) {
if (import_path4.path.isAbsolute(relativePath))
throw new Error(`
Trying to access a dir file from an absolute paths:
- Folder path: ${this._path}
- File path: ${relativePath}
`);
return FsFile.from(import_path4.path.resolve(this._path, relativePath));
}
/**
* Create a new file instance with a path relative to this directory.
*
* @alias {@link toFile}
* @param relativePath - The relative path to the file from this directory
* @returns A new FsFile instance for the specified path
* @throws If an absolute path is provided
*
* ```typescript
* const srcDir = fsDir("./src");
*
* // Access files in the directory
* const configFile = srcDir.file("config.json");
* const deepFile = srcDir.file("components/Button.tsx");
*
* // Error: Cannot use absolute paths
* srcDir.file("/absolute/path"); // throws Error
* ```
*/
file(relativePath) {
return this.toFile(relativePath);
}
/**
* Get the name of the directory (last segment of the path).
*
* @returns The directory name without the full path
*
* ```typescript
* const srcDir = fsDir("/path/to/project/src");
* console.log(srcDir.name()); // "src"
* ```
*/
name() {
return import_path4.path.filename(this._path);
}
/**
* Check if the directory exists in the file system.
*
* @returns A promise that resolves to true if the directory exists, false otherwise
*
* ```typescript
* const configDir = fsDir("./config");
*
* if (await configDir.exists()) {
* // Directory exists, safe to use
* const files = await configDir.glob("*.json");
* } else {
* // Create the directory first
* await configDir.make();
* }
* ```
*/
async exists() {
return fs2.access(this._path, fsSync2.constants.F_OK).then(() => true).catch(() => false);
}
/**
* Check if the directory exists in the file system synchronously.
*
* @synchronous
* @returns True if the directory exists, false otherwise
*
* ```typescript
* const configDir = fsDir("./config");
*
* if (configDir.existsSync()) {
* // Directory exists, safe to use
* const files = configDir.globSync("*.json");
* } else {
* // Create the directory first
* configDir.makeSync();
* }
* ```
*/
existsSync() {
try {
fsSync2.accessSync(this._path, fsSync2.constants.F_OK);
return true;
} catch (error) {
if (error.code === "ENOENT") return false;
throw error;
}
}
/**
* Create the directory and any necessary parent directories.
*
* @returns A promise that resolves when the directory is created
*
* ```typescript
* const assetsDir = fsDir("./dist/assets/images");
*
* // Creates all necessary parent directories
* await assetsDir.make();
* ```
*/
async make() {
await fs2.mkdir(this._path, { recursive: true });
}
/**
* Create the directory and any necessary parent directories synchronously.
*
* @synchronous
*
* ```typescript
* const assetsDir = fsDir("./dist/assets/images");
*
* // Creates all necessary parent directories immediately
* assetsDir.makeSync();
* ```
*/
makeSync() {
fsSync2.mkdirSync(this._path, { recursive: true });
}
/**
* Move the directory to a new location.
*
* @param newPath - The new path for the directory
* @returns A promise that resolves the new directory
*/
async move(newPath) {
const newDir = _FsDir.cwd(newPath);
await fs2.rename(this._path, newDir.path);
return newDir;
}
/**
* Move the directory to a new location synchronously.
*
* @param newPath - The new path for the directory
* @returns The new directory
*/
moveSync(newPath) {
const newDir = _FsDir.cwd(newPath);
fsSync2.renameSync(this._path, newDir.path);
return newDir;
}
/**
* Remove the directory and all its contents recursively.
* If the directory doesn't exist, the operation is silently ignored.
*
* @returns A promise that resolves when the directory is removed
*
* ```typescript
* const tempDir = fsDir("./temp");
*
* // Remove directory and all contents
* await tempDir.rm();
* ```
*/
async rm() {
await fs2.rm(this._path, { recursive: true }).catch((e) => {
if (e.code === "ENOENT") return;
throw e;
});
}
/**
* Remove the directory and all its contents recursively synchronously.
* If the directory doesn't exist, the operation is silently ignored.
*
* @synchronous
*
* ```typescript
* const tempDir = fsDir("./temp");
*
* // Remove directory and all contents immediately
* tempDir.rmSync();
* ```
*/
rmSync() {
try {
fsSync2.rmSync(this._path, { recursive: true });
} catch (error) {
if (error.code === "ENOENT") return;
throw error;
}
}
/**
* Find files in the directory that match the specified glob patterns.
*
* @param patterns - One or more glob patterns to match against
* @returns A promise that resolves to an FsFileArray containing the matching files
*
* ```typescript
* const srcDir = fsDir("./src");
*
* // Find all TypeScript files
* const tsFiles = await srcDir.glob("**\/*.ts");
*
* // Find multiple file types
* const assets = await srcDir.glob("**\/*.{png,jpg,svg}");
*
* // Use array of patterns
* const configs = await srcDir.glob(["*.json", "*.yaml"]);
* ```
*/
glob(...patterns) {
return import_glob2.glob.cwd(this._path).options({ absolute: true }).find(...patterns).then(fsFiles);
}
/**
* Find files in the directory that match the specified glob patterns synchronously.
*
* @param patterns - One or more glob patterns to match against
* @returns An FsFileArray containing the matching files
* @synchronous
*
* ```typescript
* const srcDir = fsDir("./src");
*
* // Find all TypeScript files synchronously
* const tsFiles = srcDir.globSync("**\/*.ts");
*
* // Find multiple file types
* const assets = srcDir.globSync("**\/*.{png,jpg,svg}");
* ```
*/
globSync(...patterns) {
return fsFiles(
import_glob2.glob.cwd(this._path).options({ absolute: true }).findSync(...patterns)
);
}
/**
* Find folders in the directory that match the specified glob patterns.
*
* @param patterns - One or more glob patterns to match against
* @returns A promise that resolves to an FsDirArray containing the matching folders
*/
globDirs(...patterns) {
const patternsWithTrailingSlash = patterns.flat().map((p) => import_glob2.glob.ensureDirTrailingSlash(p));
return import_glob2.glob.cwd(this._path).options({ nodir: false, absolute: true }).find(patternsWithTrailingSlash).then(dirs);
}
/**
* Find folders in the directory that match the specified glob patterns synchronously.
*
* @param patterns - One or more glob patterns to match against
* @returns An FsDirArray containing the matching folders
* @synchronous
*/
globDirsSync(...patterns) {
const patternsWithTrailingSlash = patterns.flat().map((p) => import_glob2.glob.ensureDirTrailingSlash(p));
return dirs(
import_glob2.glob.cwd(this._path).options({ nodir: false, absolute: true }).findSync(patternsWithTrailingSlash)
);
}
/**
* Find files tracked by git in the directory.
*
* @returns A promise that resolves to an FsFileArray containing the git-tracked files
*
* ```typescript
* const projectDir = fsDir("./project");
*
* // Get all git-tracked files in the directory
* const trackedFiles = await projectDir.gitLs();
* ```
*/
async gitLs() {
return import_git.git.ls(this._path).then(fsFilesFromDir(this));
}
/**
* Execute a command in the directory.
*/
async exec(template, ...args) {
const { execa } = await import("execa").catch(() => {
throw new Error(
"The `execa` package is not installed. Please install it first."
);
});
return execa({ cwd: this.path })(template, ...args);
}
};
var fsDir = FsDir2.cwd;
var dir = FsDir2.cwd;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
FsDir,
FsFile,
dir,
file,
files,
filesFromDir,
fsDir,
fsFile,
fsFiles,
fsFilesFromDir
});
//# sourceMappingURL=fs.index.cjs.map