Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
260 changes: 257 additions & 3 deletions packages/util-fs/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,265 @@
# Simple FS utils

Lightweight filesystem utilities for Node.js with promise-based API and TypeScript support.

## Install

```
```bash
npm install @davidwells/fs-utils
```

## Other packages
## Features

- ✅ Promise-based API for all operations
- ✅ TypeScript declarations included
- ✅ Comprehensive JSDoc annotations
- ✅ Recursive directory operations
- ✅ Pattern-based filtering for directory reads
- ✅ Zero dependencies (except rimraf)

## Usage

```javascript
const fs = require('@davidwells/fs-utils')

// Example: Read directory recursively, excluding node_modules
const files = await fs.readDir('./src', {
recursive: true,
exclude: ['node_modules', /\.test\.js$/]
})
```

## API

### File Operations

#### `fileExists(filePath)`
Check if a file or directory exists.

**Parameters:**
- `filePath` (string) - Path to check

**Returns:** `Promise<boolean>` - True if file exists, false otherwise

**Example:**
```javascript
const exists = await fs.fileExists('./package.json')
console.log(exists) // true or false
```

---

#### `readFile(filePath, options)`
Read data from a file. This is a direct export of Node.js `fs.promises.readFile`.

**Parameters:**
- `filePath` (string) - Path to file to read
- `options` (string | Object) - Encoding or options object

**Returns:** `Promise<Buffer | string>` - File contents

**Example:**
```javascript
const content = await fs.readFile('./package.json', 'utf8')
console.log(JSON.parse(content))
```

---

#### `writeFile(filePath, data, options)`
Write data to a file. This is a direct export of Node.js `fs.promises.writeFile`.

**Parameters:**
- `filePath` (string) - Path to file to write
- `data` (string | Buffer) - Data to write
- `options` (string | Object) - Encoding or options object

**Returns:** `Promise<void>`

**Example:**
```javascript
await fs.writeFile('./output.txt', 'Hello World', 'utf8')
```

---

#### `copyFile(src, dest, mode)`
Copy a file from source to destination. This is a direct export of Node.js `fs.promises.copyFile`.

**Parameters:**
- `src` (string) - Source file path
- `dest` (string) - Destination file path
- `mode` (number) - Optional copy mode flags

**Returns:** `Promise<void>`

**Example:**
```javascript
await fs.copyFile('./source.txt', './destination.txt')
```

---

#### `deleteFile(filePath)`
Delete a file, gracefully handling cases where the file doesn't exist (ignores ENOENT errors).

**Parameters:**
- `filePath` (string) - Path to file to delete

**Returns:** `Promise<void>`

**Example:**
```javascript
await fs.deleteFile('./temp.txt')
// Won't throw error if file doesn't exist
```

---

#### `getFileSize(filePath)`
Get file size information in bytes and megabytes.

**Parameters:**
- `filePath` (string) - Path to the file

**Returns:** `Promise<FileSizeResult>` - Object with `bytes` and `mb` properties

**Example:**
```javascript
const size = await fs.getFileSize('./video.mp4')
console.log(`Size: ${size.bytes} bytes (${size.mb} MB)`)
```

---

### Directory Operations

#### `directoryExists(dirPath)`
Check if a directory exists. This is an alias for `fileExists()`.

**Parameters:**
- `dirPath` (string) - Path to check

**Returns:** `Promise<boolean>` - True if directory exists, false otherwise

**Example:**
```javascript
const exists = await fs.directoryExists('./src')
```

---

#### `createDir(directoryPath, recursive)`
Create a directory, optionally creating parent directories.

**Parameters:**
- `directoryPath` (string) - Path to directory to create
- `recursive` (boolean) - Whether to create parent directories (default: `true`)

**Returns:** `Promise<void>`

**Example:**
```javascript
// Creates parent directories if they don't exist
await fs.createDir('./path/to/nested/dir')

// Only create directory if parent exists
await fs.createDir('./existing-parent/new-dir', false)
```

---

#### `readDir(dirPath, options)`
Recursively read directory contents with optional filtering.

**Parameters:**
- `dirPath` (string) - Directory path to read
- `options` (Object) - Options object
- `recursive` (boolean) - Whether to read recursively (default: `true`)
- `exclude` (Array<string | RegExp>) - Patterns to exclude from results (default: `[]`)

**Returns:** `Promise<string[]>` - Array of file paths

**Example:**
```javascript
// Read all files recursively
const allFiles = await fs.readDir('./src')

// Read only immediate children (non-recursive)
const topLevel = await fs.readDir('./src', { recursive: false })

// Exclude node_modules and test files
const sourceFiles = await fs.readDir('./project', {
recursive: true,
exclude: ['node_modules', /\.test\.js$/, /\.spec\.js$/]
})
```

---

#### `copyDir(src, dest, recursive)`
Copy a directory and its contents.

**Parameters:**
- `src` (string) - Source directory path
- `dest` (string) - Destination directory path
- `recursive` (boolean) - Whether to copy recursively (default: `true`)

**Returns:** `Promise<void>`

**Example:**
```javascript
// Copy entire directory tree
await fs.copyDir('./src', './backup')

// Copy only immediate children
await fs.copyDir('./templates', './output', false)
```

---

#### `deleteDir(dirPath)`
Recursively delete a directory and all its contents.

**Parameters:**
- `dirPath` (string) - Path to directory to delete

**Returns:** `Promise<void>`

**Example:**
```javascript
await fs.deleteDir('./temp')
```

---

## TypeScript Support

This package includes TypeScript declarations. No need to install separate `@types` packages.

```typescript
import * as fs from '@davidwells/fs-utils'

const files: string[] = await fs.readDir('./src', {
recursive: true,
exclude: ['node_modules']
})

const size: { bytes: number; mb: number } = await fs.getFileSize('./file.txt')
```

## Testing

This package includes a comprehensive test suite using [uvu](https://github.com/lukeed/uvu).

```bash
npm test
```

## Related Packages

- [quick-persist](https://www.npmjs.com/package/quick-persist) - Simple key-value persistence

## License

- https://www.npmjs.com/package/quick-persist
MIT
72 changes: 69 additions & 3 deletions packages/util-fs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ const fs = promises

const deleteDir = promisify(rimraf)

/**
* Delete a file, ignoring ENOENT errors
* @param {string} s - Path to file to delete
* @returns {Promise<void>}
*/
const deleteFile = (s) => fs.unlink(s).catch((e) => {
// console.log('e', e)
// ignore already deleted files
Expand All @@ -17,6 +22,11 @@ const deleteFile = (s) => fs.unlink(s).catch((e) => {
throw e
})

/**
* Check if a file exists
* @param {string} s - Path to check
* @returns {Promise<boolean>}
*/
const fileExists = (s) => fs.access(s, constants.F_OK).then(() => true).catch(() => false)

/* Recursive read dir
Expand All @@ -31,11 +41,25 @@ async function readDir(dir, recursive = true, allFiles = []) {
}
*/

/**
* @typedef {Object} ReadDirOptions
* @property {boolean} [recursive=true] - Whether to read directories recursively
* @property {(string|RegExp)[]} [exclude=[]] - Patterns to exclude from results
*/

// Recursive read dir
const readDirOpts = {
recursive: true,
exclude: []
}

/**
* Recursively read directory contents
* @param {string} dir - Directory path to read
* @param {ReadDirOptions} [opts] - Options for reading directory
* @param {string[]} [allFiles] - Internal parameter for recursive calls
* @returns {Promise<string[]>} Array of file paths
*/
async function readDir(dir, opts = readDirOpts, allFiles = []) {
let files = (await fs.readdir(dir)).map((file) => path.join(dir, file))
const exclude = !Array.isArray(opts.exclude) ? [opts.exclude] : opts.exclude
Expand All @@ -60,11 +84,24 @@ async function readDir(dir, opts = readDirOpts, allFiles = []) {
return allFiles
}

/**
* Create a directory recursively
* @param {string} directoryPath - Path to directory to create
* @param {boolean} [recursive=true] - Whether to create parent directories
* @returns {Promise<void>}
*/
async function createDir(directoryPath, recursive = true) {
// ignore errors - throws if the path already exists
return fs.mkdir(directoryPath, { recursive: recursive }).catch((e) => {})
}

/**
* Copy a directory recursively
* @param {string} src - Source directory path
* @param {string} dest - Destination directory path
* @param {boolean} [recursive=true] - Whether to copy recursively
* @returns {Promise<void>}
*/
async function copyDir(src, dest, recursive = true) {
await createDir(dest, recursive) // Ensure directory exists

Expand All @@ -84,6 +121,17 @@ async function copyDir(src, dest, recursive = true) {
}))
}

/**
* @typedef {Object} FileSizeResult
* @property {number} bytes - File size in bytes
* @property {number} mb - File size in megabytes (rounded to 2 decimal places)
*/

/**
* Get file size information
* @param {string} filePath - Path to the file
* @returns {Promise<FileSizeResult>} File size information
*/
async function getFileSize(filePath) {
return new Promise((resolve, reject) => {
stat(filePath, (err, stats) => {
Expand All @@ -101,15 +149,33 @@ async function getFileSize(filePath) {
})
}

/**
* Write data to a file
* @type {typeof import('fs').promises.writeFile}
*/
const writeFile = fs.writeFile

/**
* Read data from a file
* @type {typeof import('fs').promises.readFile}
*/
const readFile = fs.readFile

/**
* Copy a file from source to destination
* @type {typeof import('fs').promises.copyFile}
*/
const copyFile = fs.copyFile

module.exports = {
// Check if file exists
fileExists: fileExists,
// Write file
writeFile: fs.writeFile,
writeFile: writeFile,
// Read file
readFile: fs.readFile,
readFile: readFile,
// Copy file
copyFile: fs.copyFile,
copyFile: copyFile,
// Delete file
deleteFile: deleteFile,
// Check if directory exists
Expand Down
Loading