node-comfortv2.0.0

nc.fs

Files and folders without try/catch: read and write text or JSON (atomically), copy, move, delete, list, glob, watch, hash.

Paths: absolute paths are used as they are. Paths starting with ./ or ../ start from the file that calls the function, so a script works wherever it's run from. Other relative paths start from the working directory, like Node's fs.

Everyday failures don't throw: you get undefined when something doesn't exist and false when an operation fails.

const { fs } = require("@ix-xs/node-comfort");
import { getEnv } from "@ix-xs/node-comfort/fs";

These functions are also available at the top level: nc.getEnv() is nc.fs.getEnv().

nc.writeJSON("./data/settings.json", { theme: "dark" });
const settings = nc.readJSON("./data/settings.json", {});
const sources = nc.glob("src/**\/*.{js,ts}");
GuideExplanations and examples for nc.fs.
Read the guide →

Functions

nc.fs.getEnv()

getEnv(name: string, fallback?: string): string | undefined

Reads an environment variable. For typed and checked values, see nc.env.

Parameters

NameType
namestring
fallbackoptionalstring

Returns string | undefined

Example

nc.getEnv("REGION", "eu-west-1");

nc.fs.createPath()

createPath(path?: string): string

The absolute version of a path, whether it exists or not.

Parameters

NameTypeDescription
pathoptionalstringDefaults to the working directory.

Returns string

Example

nc.createPath("./config/app.json"); // next to the calling file
nc.createPath("logs/app.log");      // from the working directory

nc.fs.getFolder()

getFolder(path?: string): string | undefined

The absolute path of a folder, or undefined if it doesn't exist.

Parameters

NameTypeDescription
pathoptionalstringDefaults to the working directory.

Returns string | undefined

Example

if (!nc.getFolder("./uploads")) nc.createFolder("./uploads");

nc.fs.getFile()

getFile(path: string): string | undefined

The absolute path of a file, or undefined if it doesn't exist.

Parameters

NameType
pathstring

Returns string | undefined

nc.fs.getFoldersIn()

getFoldersIn(path?: string, recursive?: boolean | ListOptions): string[] | undefined

The folders inside a folder, nested ones included by default. node_modules is skipped.

Parameters

NameTypeDescription
pathoptionalstringDefaults to the working directory.
recursiveoptionalboolean | ListOptionsDefault: true

Returns string[] | undefined: Absolute paths, or undefined if the folder doesn't exist.

Example

nc.getFoldersIn("./src");        // everything
nc.getFoldersIn("./src", false); // direct children only

nc.fs.getFilesIn()

getFilesIn(path?: string, recursive?: boolean | ListOptions): string[] | undefined

The files inside a folder, nested ones included by default. node_modules is skipped.

Parameters

NameTypeDescription
pathoptionalstringDefaults to the working directory.
recursiveoptionalboolean | ListOptionsDefault: true

Returns string[] | undefined: Absolute paths, or undefined if the folder doesn't exist.

Example

nc.getFilesIn("./src").filter((file) => file.endsWith(".js"));

nc.fs.glob()

glob(pattern: string | string[], options?: GlobOptions): string[]

Finds paths matching a glob pattern: * (anything but /), ** (any depth), ?, [abc] and {js,ts}.

A pattern starting from the root of the disk searches there, and its results are absolute.

Parameters

NameTypeDescription
patternstring | string[]One or more patterns.
optionsoptionalGlobOptions

Returns string[]: Sorted, relative to cwd with / separators, unless the pattern is absolute or absolute is set.

Example

nc.glob("src/**\/*.js");                           // ["src/index.js", "src/lib/a.js"]
nc.glob("**\/*.{png,jpg}", { cwd: "./assets", absolute: true });
nc.glob("packages/*", { type: "folders" });
nc.glob(`${folder}/**\/*.json`);                   // absolute pattern, absolute results

nc.fs.find()

find(name: string, options?: FindOptions): string | undefined

Searches a folder tree for the first file or folder with this name, closest first.

Parameters

NameType
namestring
optionsoptionalFindOptions

Returns string | undefined: The absolute path.

Example

nc.find("config.json");
nc.find("fixtures", { type: "folder", cwd: "./test" });

nc.fs.readFile()2 overloads

readFile(path: string, encoding: "buffer"): Buffer<ArrayBufferLike> | undefined
readFile(path: string, encoding?: BufferEncoding): string | undefined

Reads a file as text, or as a Buffer with "buffer".

Parameters

NameType
pathstring
encoding"buffer"

Returns Buffer | undefined: undefined if it can't be read.

Example

const text = nc.readFile("./README.md");
const bytes = nc.readFile("./logo.png", "buffer");

nc.fs.readLines()

readLines(path: string): string[] | undefined

Reads a text file as lines. A final newline doesn't add an empty line.

Parameters

NameType
pathstring

Returns string[] | undefined: undefined if it can't be read.

Example

for (const url of nc.readLines("./urls.txt") ?? []) await check(url);

nc.fs.readJSON()

readJSON<T = any>(path: string, fallback?: T, options?: ReadJSONOptions): T

Reads a JSON file. If it's missing or invalid you get fallback, never an error.

Parameters

NameType
pathstring
fallbackoptionalT
optionsoptionalReadJSONOptions

Returns T

Example

const settings = nc.readJSON("./settings.json", { theme: "light" });
const tsconfig = nc.readJSON("./tsconfig.json", {}, { comments: true });

nc.fs.writeFile()

writeFile(path: string, data: string | object | Buffer<ArrayBufferLike> | Uint8Array<ArrayBufferLike>, options?: WriteFileOptions): boolean

Writes a file atomically, creating its folders. Objects are written as JSON.

Parameters

NameType
pathstring
datastring | Buffer | Uint8Array | object
optionsoptionalWriteFileOptions

Returns boolean: false if it failed.

Example

nc.writeFile("./dist/index.html", html);
nc.writeFile("./.secrets/token", token, { mode: 0o600 });

nc.fs.writeJSON()

writeJSON(path: string, data: unknown, options?: WriteJSONOptions): boolean

Writes a value as JSON, atomically, creating folders.

Parameters

NameType
pathstring
dataunknown
optionsoptionalWriteJSONOptions

Returns boolean: false if it failed, for example on a circular structure.

Example

nc.writeJSON("./data/users.json", users);

nc.fs.appendFile()

appendFile(path: string, data: string | Buffer<ArrayBufferLike>): boolean

Appends to a file, creating it and its folders if needed.

Parameters

NameType
pathstring
datastring | Buffer

Returns boolean

Example

nc.appendFile("./logs/audit.log", `${new Date().toISOString()} login ${user}\n`);

nc.fs.createFile()

createFile(path: string, force?: boolean, data?: string | object | Buffer<ArrayBufferLike> | null): boolean | undefined

Creates a file and its folders.

Parameters

NameTypeDescription
pathstring
forceoptionalbooleanReplace the file if it exists.Default: false
dataoptionalstring | Buffer | object | nullObjects are written as JSON.

Returns boolean | undefined: false if it already existed or failed.

Example

nc.createFile("./notes.txt", false, "hello");    // only if it doesn't exist
nc.createFile("./data.json", true, { ok: true }); // replace it

nc.fs.createFolder()

createFolder(path: string, force?: boolean): boolean | undefined

Creates a folder and its parents.

Parameters

NameTypeDescription
pathstring
forceoptionalbooleanDelete and recreate it if it exists.Default: false

Returns boolean | undefined: false if it already existed or failed.

Example

nc.createFolder("./uploads");
nc.createFolder("./cache", true); // start from an empty folder

nc.fs.ensureFolder()

ensureFolder(path: string): string

Makes sure a folder exists and returns its absolute path.

Parameters

NameType
pathstring

Returns string

Throws Error If it can't be created.

Example

const dir = nc.ensureFolder("./storage/uploads");

nc.fs.ensureFile()

ensureFile(path: string): string

Makes sure a file exists (an empty one if needed) and returns its absolute path. An existing file is left as is.

Parameters

NameType
pathstring

Returns string

Throws Error If it can't be created.

nc.fs.touch()

touch(path: string): boolean

Updates a file's modification time, creating the file if needed, like the Unix touch command.

Parameters

NameType
pathstring

Returns boolean

nc.fs.deleteFolder()

deleteFolder(path: string): boolean | undefined

Deletes a folder and everything in it.

Parameters

NameType
pathstring

Returns boolean | undefined: undefined if it doesn't exist.

nc.fs.deleteFile()

deleteFile(path: string): boolean | undefined

Deletes a file.

Parameters

NameType
pathstring

Returns boolean | undefined: undefined if it doesn't exist.

nc.fs.remove()

remove(path: string): boolean

Deletes a file or a folder, whichever it is. Nothing happens if the path doesn't exist.

Parameters

NameType
pathstring

Returns boolean: true if something was deleted.

Example

nc.remove("./dist");

nc.fs.deleteFoldersIn()

deleteFoldersIn(path?: string, filter?: ((folder: string) => boolean) | undefined): number | undefined

Deletes the folders inside a folder that pass a test.

Parameters

NameTypeDescription
pathoptionalstringDefaults to the working directory.
filteroptional(folder: string) => booleanGets absolute paths. Defaults to every folder.

Returns number | undefined: How many were deleted.

Example

nc.deleteFoldersIn("./packages", (folder) => folder.endsWith("dist"));

nc.fs.deleteFilesIn()

deleteFilesIn(path?: string, recursive?: boolean, filter?: ((file: string) => boolean) | undefined): number | undefined

Deletes the files inside a folder that pass a test.

Parameters

NameTypeDescription
pathoptionalstringDefaults to the working directory.
recursiveoptionalbooleanDefault: false
filteroptional(file: string) => booleanGets absolute paths. Defaults to every file.

Returns number | undefined: How many were deleted.

Example

nc.deleteFilesIn("./logs", false, (file) => file.endsWith(".log"));

nc.fs.emptyFolder()

emptyFolder(path: string): boolean | undefined

Deletes everything inside a folder, keeping the folder.

Parameters

NameType
pathstring

Returns boolean | undefined: undefined if it doesn't exist.

nc.fs.copy()

copy(source: string, destination: string, options?: CopyOptions): boolean

Copies a file or a whole folder, like cp -r.

Parameters

NameType
sourcestring
destinationstring
optionsoptionalCopyOptions

Returns boolean: false if the source is missing or the copy failed.

Example

nc.copy("./templates", "./dist/templates");
nc.copy("./.env.example", "./.env", { overwrite: false });

nc.fs.move()

move(source: string, destination: string, options?: { overwrite?: boolean; } | undefined): boolean

Moves or renames a file or folder, across drives too.

Parameters

NameTypeDescription
sourcestring
destinationstring
optionsoptional{ overwrite?: boolean }Replace an existing destination. Defaults to true.

Returns boolean

Example

nc.move("./report.pdf", "./archive/2024/report.pdf");

nc.fs.copyFoldersIn()

copyFoldersIn(options: CopyFoldersOptions): number | undefined

Copies the folders inside a folder into another one. copy() is simpler for most cases.

Parameters

NameType
optionsCopyFoldersOptions

Returns number | undefined: How many were copied.

nc.fs.copyFolder()

copyFolder(options: CopyFolderOptions): boolean | undefined

Copies a folder. Without recursive or withFiles, only the empty folder is created; copy() copies everything in one call.

Parameters

NameType
optionsCopyFolderOptions

Returns boolean | undefined

nc.fs.copyFilesIn()

copyFilesIn(options: CopyFilesOptions): number | undefined

Copies the files inside a folder into another one, keeping the tree.

Parameters

NameType
optionsCopyFilesOptions

Returns number | undefined: How many were copied.

Example

nc.copyFilesIn({ path: "./src", dest: "./dist", filter: (file) => file.endsWith(".css") });

nc.fs.copyFile()

copyFile(options: CopyFileOptions): boolean | undefined

Copies one file. If dest ends with /, the file keeps its name inside that folder.

Parameters

NameType
optionsCopyFileOptions

Returns boolean | undefined

Example

nc.copyFile({ path: "./src/index.js", dest: "./dist/" });

nc.fs.moveFoldersIn()

moveFoldersIn(options: CopyFoldersOptions): number | undefined

Moves the folders inside a folder.

Parameters

NameType
optionsCopyFoldersOptions

Returns number | undefined: How many were moved.

nc.fs.moveFolder()

moveFolder(options: CopyFolderOptions & { path: string; }): boolean | undefined

Moves a folder.

Parameters

NameType
optionsCopyFolderOptions & { path: string }

Returns boolean | undefined

nc.fs.moveFilesIn()

moveFilesIn(options: CopyFilesOptions): number | undefined

Moves the files inside a folder.

Parameters

NameType
optionsCopyFilesOptions

Returns number | undefined: How many were moved.

nc.fs.moveFile()

moveFile(options: CopyFileOptions): boolean | undefined

Moves one file.

Parameters

NameType
optionsCopyFileOptions

Returns boolean | undefined

Example

nc.moveFile({ path: "./logs/app.log", dest: "./logs/archive/" });

nc.fs.exists()

exists(path: string): boolean

Does a file or folder exist at this path?

Parameters

NameType
pathstring

Returns boolean

nc.fs.isFile()

isFile(path: string): boolean

Is it an existing file?

Parameters

NameType
pathstring

Returns boolean

nc.fs.isFolder()

isFolder(path: string): boolean

Is it an existing folder?

Parameters

NameType
pathstring

Returns boolean

nc.fs.stat()

stat(path: string): Stats | undefined

Size, dates and type of a path.

Parameters

NameType
pathstring

Returns fs.Stats | undefined: undefined if it doesn't exist.

Example

nc.stat("./video.mp4")?.mtime;

nc.fs.fileSize()

fileSize(path: string): number | undefined

A file's size in bytes.

Parameters

NameType
pathstring

Returns number | undefined: undefined if it doesn't exist.

Example

nc.num.formatBytes(nc.fileSize("./backup.zip") ?? 0); // "1.2 GB"

nc.fs.folderSize()

folderSize(path: string): number | undefined

The total size of every file in a folder.

Parameters

NameType
pathstring

Returns number | undefined: undefined if it doesn't exist.

nc.fs.hashFile()

hashFile(path: string, options?: { algorithm?: string; encoding?: "base64" | "base64url" | "hex"; } | undefined): string | undefined

Hashes a file in chunks, so huge files are fine.

Parameters

NameTypeDescription
pathstring
optionsoptional{ algorithm?: string, encoding?: "hex" | "base64" | "base64url" }Defaults to sha256 in hex.

Returns string | undefined: undefined if it can't be read.

Example

nc.hashFile("./release.zip");                     // sha256, hex
nc.hashFile("./release.zip", { algorithm: "md5" });

nc.fs.tempFolder()

tempFolder(prefix?: string): string

Creates a new, unique folder in the system's temp directory.

Parameters

NameTypeDescription
prefixoptionalstringDefault: "nc-"

Returns string

Example

const dir = nc.tempFolder("export-");
try { await exportTo(dir); } finally { nc.remove(dir); }

nc.fs.sanitizeFilename()

sanitizeFilename(name: string, options?: { replacement?: string; } | undefined): string

Turns any text into a file name that works on Windows, macOS and Linux: forbidden characters are replaced, reserved names like CON are prefixed, and the length is capped.

Parameters

NameTypeDescription
namestring
optionsoptional{ replacement?: string }Defaults to "_".

Returns string

Example

nc.sanitizeFilename('Report: Q1/Q2 "final"?.pdf'); // "Report_ Q1_Q2 _final__.pdf"
nc.sanitizeFilename("CON.txt");                    // "_CON.txt"

nc.fs.watch()

watch(options?: WatchOptions): Watcher | undefined

Watches a file or folder for changes.

Parameters

NameType
optionsoptionalWatchOptions

Returns Watcher | undefined: undefined if the path doesn't exist.

Example

const watcher = nc.watch({ path: "./src", recursive: true, debounce: 100 })
  .on("change", (file) => nc.info(`Changed: ${file}`))
  .on("rename", (file) => nc.info(`Added or removed: ${file}`));
watcher.stop();

Types

Import any of them in TypeScript with import type { CopyFileOptions } from "@ix-xs/node-comfort", or in JavaScript with import("@ix-xs/node-comfort").CopyFileOptions.

CopyFileOptions

Options for copyFile() and moveFile().

PropertyTypeDescription
pathstringSource file.
deststringDestination file, or a folder if it ends with /.
forceoptionalbooleanDelete the destination first.

CopyFilesOptions

Options for copyFilesIn() and moveFilesIn().

PropertyTypeDescription
deststringDestination folder.
pathoptionalstringSource folder. Defaults to the working directory.
recursiveoptionalbooleanInclude files from nested folders, keeping the tree. Defaults to true.
forceoptionalbooleanDelete existing destination files first.
filteroptional((file: string) => boolean)Keep a file when it returns true. Gets absolute paths.

CopyFolderOptions

Options for copyFolder() and moveFolder().

PropertyTypeDescription
deststringDestination folder.
pathoptionalstringSource folder. Defaults to the working directory.
recursiveoptionalbooleanCopy nested folders too.
withFilesoptionalbooleanCopy files too. copy() does all of this in one call.
forceoptionalbooleanDelete an existing destination first.

CopyFoldersOptions

Options for copyFoldersIn() and moveFoldersIn().

PropertyTypeDescription
deststringDestination folder.
pathoptionalstringSource folder. Defaults to the working directory.
recursiveoptionalbooleanInclude nested folders. Defaults to true.
withFilesoptionalbooleanCopy the files in each folder too.
forceoptionalbooleanDelete an existing destination folder first.
filteroptional((folder: string) => boolean)Keep a folder when it returns true. Gets absolute paths.

CopyOptions

Options for copy().

PropertyTypeDescription
overwriteoptionalbooleanReplace existing files. Defaults to true.
filteroptional((source: string, destination: string) => boolean)Return false to skip a file or folder.
preserveTimestampsoptionalbooleanKeep modification times.

FindOptions

Options for find().

PropertyTypeDescription
cwdoptionalstringWhere to start. Defaults to the working directory.
typeoptional"file" | "folder" | "any"Defaults to "file".
ignoreoptionalstring[]Folder names to skip. Defaults to node_modules and .git.

GlobOptions

Options for glob().

PropertyTypeDescription
cwdoptionalstringWhere the pattern starts. Defaults to the working directory.
absoluteoptionalbooleanReturn absolute paths.
dotoptionalbooleanLet * and ** match names starting with a dot.
ignoreoptionalstring[]Patterns to leave out. Defaults to node_modules and .git.
typeoptional"files" | "folders" | "all"Defaults to "files".

ListOptions

Options for getFilesIn() and getFoldersIn().

PropertyTypeDescription
recursiveoptionalbooleanGo into nested folders. Defaults to true.
hiddenoptionalbooleanInclude names starting with a dot. Defaults to true.
ignoreoptionalstring[]Folder names to skip. Defaults to ["node_modules"].

ReadJSONOptions

Options for readJSON().

PropertyTypeDescription
commentsoptionalbooleanAllow comments and trailing commas, like in tsconfig.json.
reviveroptional((key: string, value: any) => any)Passed to JSON.parse.

Watcher

Returned by watch().

PropertyTypeDescription
on<E extends "change" | "rename" | "all">(event: E, callback: E extends "all" ? (event: "rename" | "change", file: string) => void : (file: string) => void) => WatcherListens for changes. "all" also gets the event name.
stop() => voidStops watching.
pause() => WatcherIgnores events until resume().
resume() => Watcher

WatchOptions

Options for watch().

PropertyTypeDescription
pathoptionalstringWhat to watch. Defaults to the working directory.
recursiveoptionalbooleanWatch nested folders too.
filteroptional((event: "rename" | "change", file: string) => boolean)Return false to ignore an event.
debounceoptionalnumberMerge events on the same file within this many ms. Editors often save in several steps.

WriteFileOptions

Options for writeFile().

PropertyTypeDescription
atomicoptionalbooleanWrite to a temporary file and rename it, so nobody reads a half-written file and a crash can't corrupt it. Defaults to true.
encodingoptionalBufferEncodingDefaults to "utf8".
modeoptionalnumberPermissions, like 0o600 for secrets.
spacesoptionalnumberIndentation when writing an object as JSON. Defaults to 2.

WriteJSONOptions

Options for writeJSON().

PropertyTypeDescription
spacesoptionalnumberDefaults to 2.
atomicoptionalbooleanDefaults to true.
replaceroptional((key: string, value: unknown) => unknown)Passed to JSON.stringify.
node-comfort v2.0.0View the source