From 994cb0e89bdb10f38038cb5cf723dc19563e8b7b Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Sat, 14 Jun 2025 22:14:26 +0000 Subject: [PATCH 1/4] feat: add JSDoc types to fs-utils package - Add comprehensive JSDoc type annotations to all exported functions - Create tsconfig.json configured for declaration-only output - Add TypeScript and Node.js types as devDependencies - Add 'types' npm script that runs tsc to emit TypeScript declarations - Generate TypeScript declarations in types/index.d.ts Closes #6 Co-authored-by: DavidWells --- packages/util-fs/index.js | 48 ++++++++++++++++++++++++++++++++++ packages/util-fs/package.json | 5 ++++ packages/util-fs/tsconfig.json | 24 +++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 packages/util-fs/tsconfig.json diff --git a/packages/util-fs/index.js b/packages/util-fs/index.js index 85336a6..0a5a4a8 100644 --- a/packages/util-fs/index.js +++ b/packages/util-fs/index.js @@ -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} + */ const deleteFile = (s) => fs.unlink(s).catch((e) => { // console.log('e', e) // ignore already deleted files @@ -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} + */ const fileExists = (s) => fs.access(s, constants.F_OK).then(() => true).catch(() => false) /* Recursive read dir @@ -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} 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 @@ -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} + */ 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} + */ async function copyDir(src, dest, recursive = true) { await createDir(dest, recursive) // Ensure directory exists @@ -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} File size information + */ async function getFileSize(filePath) { return new Promise((resolve, reject) => { stat(filePath, (err, stats) => { diff --git a/packages/util-fs/package.json b/packages/util-fs/package.json index c394708..0567c7c 100644 --- a/packages/util-fs/package.json +++ b/packages/util-fs/package.json @@ -8,6 +8,7 @@ "url": "https://github.com/DavidWells/components/packages/util-fs" }, "scripts": { + "types": "tsc", "release:patch": "npm version patch && npm publish", "release:minor": "npm version minor && npm publish", "release:major": "npm version major && npm publish" @@ -16,5 +17,9 @@ "license": "MIT", "dependencies": { "rimraf": "^3.0.2" + }, + "devDependencies": { + "typescript": "^5.0.0", + "@types/node": "^20.0.0" } } diff --git a/packages/util-fs/tsconfig.json b/packages/util-fs/tsconfig.json new file mode 100644 index 0000000..c4844e3 --- /dev/null +++ b/packages/util-fs/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2018", + "module": "commonjs", + "declaration": true, + "emitDeclarationOnly": true, + "outDir": "./types", + "allowJs": true, + "checkJs": false, + "strict": false, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "types": ["node"] + }, + "include": [ + "index.js" + ], + "exclude": [ + "node_modules", + "types" + ] +} \ No newline at end of file From 1262ad6a0114a9266e0a74ebf6d4d73e4a8529be Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Sat, 14 Jun 2025 22:27:52 +0000 Subject: [PATCH 2/4] feat: add comprehensive test suite for util-fs package - Add uvu testing framework as dev dependency - Add test script to package.json - Create comprehensive test suite covering all exported functions: - fileExists and directoryExists - writeFile, readFile, copyFile, deleteFile - createDir, readDir, copyDir, deleteDir - getFileSize with bytes and MB calculations - Include tests for edge cases and error handling - Test recursive operations and filtering options - Verify ENOENT error handling in deleteFile Co-authored-by: DavidWells --- packages/util-fs/package.json | 4 +- packages/util-fs/tests/index.test.js | 349 +++++++++++++++++++++++++++ 2 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 packages/util-fs/tests/index.test.js diff --git a/packages/util-fs/package.json b/packages/util-fs/package.json index 0567c7c..3b1f3b0 100644 --- a/packages/util-fs/package.json +++ b/packages/util-fs/package.json @@ -8,6 +8,7 @@ "url": "https://github.com/DavidWells/components/packages/util-fs" }, "scripts": { + "test": "uvu tests", "types": "tsc", "release:patch": "npm version patch && npm publish", "release:minor": "npm version minor && npm publish", @@ -20,6 +21,7 @@ }, "devDependencies": { "typescript": "^5.0.0", - "@types/node": "^20.0.0" + "@types/node": "^20.0.0", + "uvu": "^0.5.6" } } diff --git a/packages/util-fs/tests/index.test.js b/packages/util-fs/tests/index.test.js new file mode 100644 index 0000000..71610bf --- /dev/null +++ b/packages/util-fs/tests/index.test.js @@ -0,0 +1,349 @@ +const { test } = require('uvu') +const assert = require('uvu/assert') +const path = require('path') +const fs = require('fs').promises +const { + fileExists, + writeFile, + readFile, + copyFile, + deleteFile, + directoryExists, + createDir, + readDir, + copyDir, + deleteDir, + getFileSize +} = require('../index.js') + +// Test directory setup +const testDir = path.join(__dirname, 'temp') +const testFile = path.join(testDir, 'test.txt') +const testSubDir = path.join(testDir, 'subdir') +const testContent = 'Hello, World!' + +// Setup test directory before all tests +test.before(async () => { + try { + await createDir(testDir) + } catch (err) { + // Directory might already exist + } +}) + +// Cleanup after all tests +test.after(async () => { + try { + await deleteDir(testDir) + } catch (err) { + // Directory might not exist + } +}) + +// Test fileExists function +test('fileExists - should return true for existing files', async () => { + await writeFile(testFile, testContent) + const exists = await fileExists(testFile) + assert.is(exists, true) + await deleteFile(testFile) +}) + +test('fileExists - should return false for non-existing files', async () => { + const exists = await fileExists(path.join(testDir, 'nonexistent.txt')) + assert.is(exists, false) +}) + +// Test directoryExists function (alias for fileExists) +test('directoryExists - should return true for existing directories', async () => { + const exists = await directoryExists(testDir) + assert.is(exists, true) +}) + +test('directoryExists - should return false for non-existing directories', async () => { + const exists = await directoryExists(path.join(testDir, 'nonexistent')) + assert.is(exists, false) +}) + +// Test writeFile and readFile functions +test('writeFile and readFile - should write and read file correctly', async () => { + await writeFile(testFile, testContent) + const content = await readFile(testFile, 'utf8') + assert.is(content, testContent) + await deleteFile(testFile) +}) + +test('writeFile and readFile - should handle binary data', async () => { + const binaryData = Buffer.from([1, 2, 3, 4, 5]) + const binaryFile = path.join(testDir, 'binary.bin') + + await writeFile(binaryFile, binaryData) + const readData = await readFile(binaryFile) + assert.equal(readData, binaryData) + await deleteFile(binaryFile) +}) + +// Test copyFile function +test('copyFile - should copy file correctly', async () => { + const sourceFile = path.join(testDir, 'source.txt') + const destFile = path.join(testDir, 'dest.txt') + + await writeFile(sourceFile, testContent) + await copyFile(sourceFile, destFile) + + const sourceContent = await readFile(sourceFile, 'utf8') + const destContent = await readFile(destFile, 'utf8') + + assert.is(sourceContent, destContent) + assert.is(destContent, testContent) + + await deleteFile(sourceFile) + await deleteFile(destFile) +}) + +// Test deleteFile function +test('deleteFile - should delete existing file', async () => { + await writeFile(testFile, testContent) + assert.is(await fileExists(testFile), true) + + await deleteFile(testFile) + assert.is(await fileExists(testFile), false) +}) + +test('deleteFile - should not throw for non-existing file (ENOENT)', async () => { + const nonExistentFile = path.join(testDir, 'nonexistent.txt') + // Should not throw an error + await deleteFile(nonExistentFile) + assert.ok(true) // If we reach here, no error was thrown +}) + +// Test createDir function +test('createDir - should create directory', async () => { + const newDir = path.join(testDir, 'newdir') + await createDir(newDir) + + const exists = await directoryExists(newDir) + assert.is(exists, true) + + await deleteDir(newDir) +}) + +test('createDir - should create nested directories', async () => { + const nestedDir = path.join(testDir, 'nested', 'deep', 'dir') + await createDir(nestedDir) + + const exists = await directoryExists(nestedDir) + assert.is(exists, true) + + await deleteDir(path.join(testDir, 'nested')) +}) + +test('createDir - should not throw if directory already exists', async () => { + await createDir(testSubDir) + await createDir(testSubDir) // Should not throw + + const exists = await directoryExists(testSubDir) + assert.is(exists, true) + + await deleteDir(testSubDir) +}) + +// Test readDir function +test('readDir - should read directory contents', async () => { + const file1 = path.join(testDir, 'file1.txt') + const file2 = path.join(testDir, 'file2.txt') + + await writeFile(file1, 'content1') + await writeFile(file2, 'content2') + + const files = await readDir(testDir, { recursive: false }) + const fileNames = files.map(f => path.basename(f)).sort() + + assert.ok(fileNames.includes('file1.txt')) + assert.ok(fileNames.includes('file2.txt')) + + await deleteFile(file1) + await deleteFile(file2) +}) + +test('readDir - should read directory recursively', async () => { + await createDir(testSubDir) + const subFile = path.join(testSubDir, 'subfile.txt') + const rootFile = path.join(testDir, 'rootfile.txt') + + await writeFile(subFile, 'subcontent') + await writeFile(rootFile, 'rootcontent') + + const files = await readDir(testDir, { recursive: true }) + const relativePaths = files.map(f => path.relative(testDir, f)).sort() + + assert.ok(relativePaths.includes('rootfile.txt')) + assert.ok(relativePaths.includes(path.join('subdir', 'subfile.txt'))) + + await deleteFile(subFile) + await deleteFile(rootFile) + await deleteDir(testSubDir) +}) + +test('readDir - should exclude files based on string patterns', async () => { + const file1 = path.join(testDir, 'include.txt') + const file2 = path.join(testDir, 'exclude.txt') + + await writeFile(file1, 'content1') + await writeFile(file2, 'content2') + + const files = await readDir(testDir, { + recursive: false, + exclude: ['exclude'] + }) + const fileNames = files.map(f => path.basename(f)) + + assert.ok(fileNames.includes('include.txt')) + assert.ok(!fileNames.includes('exclude.txt')) + + await deleteFile(file1) + await deleteFile(file2) +}) + +test('readDir - should exclude files based on regex patterns', async () => { + const file1 = path.join(testDir, 'test.js') + const file2 = path.join(testDir, 'test.txt') + + await writeFile(file1, 'content1') + await writeFile(file2, 'content2') + + const files = await readDir(testDir, { + recursive: false, + exclude: [/\.js$/] + }) + const fileNames = files.map(f => path.basename(f)) + + assert.ok(!fileNames.includes('test.js')) + assert.ok(fileNames.includes('test.txt')) + + await deleteFile(file1) + await deleteFile(file2) +}) + +// Test copyDir function +test('copyDir - should copy directory and contents', async () => { + const sourceDir = path.join(testDir, 'source') + const destDir = path.join(testDir, 'dest') + const sourceFile = path.join(sourceDir, 'file.txt') + + await createDir(sourceDir) + await writeFile(sourceFile, testContent) + + await copyDir(sourceDir, destDir) + + const destFile = path.join(destDir, 'file.txt') + const exists = await fileExists(destFile) + assert.is(exists, true) + + const content = await readFile(destFile, 'utf8') + assert.is(content, testContent) + + await deleteDir(sourceDir) + await deleteDir(destDir) +}) + +test('copyDir - should copy directory recursively', async () => { + const sourceDir = path.join(testDir, 'source') + const sourceSubDir = path.join(sourceDir, 'subdir') + const destDir = path.join(testDir, 'dest') + const sourceFile = path.join(sourceSubDir, 'file.txt') + + await createDir(sourceSubDir) + await writeFile(sourceFile, testContent) + + await copyDir(sourceDir, destDir, true) + + const destFile = path.join(destDir, 'subdir', 'file.txt') + const exists = await fileExists(destFile) + assert.is(exists, true) + + const content = await readFile(destFile, 'utf8') + assert.is(content, testContent) + + await deleteDir(sourceDir) + await deleteDir(destDir) +}) + +test('copyDir - should not copy subdirectories when recursive=false', async () => { + const sourceDir = path.join(testDir, 'source') + const sourceSubDir = path.join(sourceDir, 'subdir') + const destDir = path.join(testDir, 'dest') + const sourceFile = path.join(sourceDir, 'file.txt') + const sourceSubFile = path.join(sourceSubDir, 'subfile.txt') + + await createDir(sourceSubDir) + await writeFile(sourceFile, testContent) + await writeFile(sourceSubFile, 'subcontent') + + await copyDir(sourceDir, destDir, false) + + const destFile = path.join(destDir, 'file.txt') + const destSubFile = path.join(destDir, 'subdir', 'subfile.txt') + + assert.is(await fileExists(destFile), true) + assert.is(await fileExists(destSubFile), false) + + await deleteDir(sourceDir) + await deleteDir(destDir) +}) + +// Test deleteDir function +test('deleteDir - should delete directory and contents', async () => { + const dirToDelete = path.join(testDir, 'todelete') + const fileInDir = path.join(dirToDelete, 'file.txt') + + await createDir(dirToDelete) + await writeFile(fileInDir, testContent) + + assert.is(await directoryExists(dirToDelete), true) + + await deleteDir(dirToDelete) + + assert.is(await directoryExists(dirToDelete), false) +}) + +// Test getFileSize function +test('getFileSize - should return correct file size', async () => { + const content = 'A'.repeat(1000) // 1000 bytes + await writeFile(testFile, content) + + const size = await getFileSize(testFile) + + assert.is(size.bytes, 1000) + assert.is(typeof size.mb, 'number') + assert.ok(size.mb > 0) + + // Check MB calculation (1000 bytes = ~0.001 MB) + const expectedMB = Math.round((1000 / (1024 * 1024)) * 100) / 100 + assert.is(size.mb, expectedMB) + + await deleteFile(testFile) +}) + +test('getFileSize - should handle empty files', async () => { + await writeFile(testFile, '') + + const size = await getFileSize(testFile) + + assert.is(size.bytes, 0) + assert.is(size.mb, 0) + + await deleteFile(testFile) +}) + +test('getFileSize - should reject for non-existent files', async () => { + const nonExistentFile = path.join(testDir, 'nonexistent.txt') + + try { + await getFileSize(nonExistentFile) + assert.unreachable('Should have thrown an error') + } catch (err) { + assert.ok(err.code === 'ENOENT') + } +}) + +test.run() \ No newline at end of file From 0ccf43937940d33acd88206ebb64d4a4452deeb4 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 16:07:45 +0000 Subject: [PATCH 3/4] fix: update getFileSize test to use larger file size for proper MB calculation The test was failing because 1000 bytes rounds to 0.00 MB when rounded to 2 decimal places, making the assertion `size.mb > 0` fail. Updated to use 100000 bytes (~0.1 MB) instead. Co-authored-by: DavidWells --- packages/util-fs/tests/index.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/util-fs/tests/index.test.js b/packages/util-fs/tests/index.test.js index 71610bf..ba90a0a 100644 --- a/packages/util-fs/tests/index.test.js +++ b/packages/util-fs/tests/index.test.js @@ -308,17 +308,17 @@ test('deleteDir - should delete directory and contents', async () => { // Test getFileSize function test('getFileSize - should return correct file size', async () => { - const content = 'A'.repeat(1000) // 1000 bytes + const content = 'A'.repeat(100000) // 100000 bytes (~0.1 MB) await writeFile(testFile, content) const size = await getFileSize(testFile) - assert.is(size.bytes, 1000) + assert.is(size.bytes, 100000) assert.is(typeof size.mb, 'number') assert.ok(size.mb > 0) - // Check MB calculation (1000 bytes = ~0.001 MB) - const expectedMB = Math.round((1000 / (1024 * 1024)) * 100) / 100 + // Check MB calculation (100000 bytes = ~0.095 MB) + const expectedMB = Math.round((100000 / (1024 * 1024)) * 100) / 100 assert.is(size.mb, expectedMB) await deleteFile(testFile) From 942892efbabdddb315540bff4c8a52c56c8f1aab Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 17:37:09 +0000 Subject: [PATCH 4/4] docs: improve JSDoc annotations and add comprehensive README for util-fs - Add proper JSDoc type annotations for writeFile, readFile, and copyFile - Create comprehensive README.md with detailed API documentation - Document all 11 exported functions with parameters, return types, and examples - Add usage examples for common scenarios - Include TypeScript support information - Add testing section Co-authored-by: David Wells --- packages/util-fs/README.md | 260 ++++++++++++++++++++++++++++++++++++- packages/util-fs/index.js | 24 +++- 2 files changed, 278 insertions(+), 6 deletions(-) diff --git a/packages/util-fs/README.md b/packages/util-fs/README.md index 84fdb10..8eabf6c 100644 --- a/packages/util-fs/README.md +++ b/packages/util-fs/README.md @@ -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` - 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` - 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` + +**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` + +**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` + +**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` - 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` - 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` + +**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) - Patterns to exclude from results (default: `[]`) + +**Returns:** `Promise` - 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` + +**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` + +**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 diff --git a/packages/util-fs/index.js b/packages/util-fs/index.js index 0a5a4a8..1995bca 100644 --- a/packages/util-fs/index.js +++ b/packages/util-fs/index.js @@ -149,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