@ecopages/file-system 0.2.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-present Andrea Zanenghi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # @ecopages/file-system
2
+
3
+ Runtime-agnostic file system utilities for Ecopages with optimized Bun and Node.js adapters.
4
+
5
+ ## Features
6
+
7
+ - **Runtime Detection**: Automatically selects optimal adapter based on runtime
8
+ - **Bun Optimized**: Uses `Bun.Glob`, `Bun.hash`, `Bun.file` for maximum performance
9
+ - **Node.js Fallback**: Uses `fast-glob` and `crypto` for full compatibility
10
+ - **Type Safe**: Full TypeScript support with consistent interface
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ bun add @ecopages/file-system
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ```typescript
21
+ import { fileSystem } from '@ecopages/file-system';
22
+
23
+ // Glob files
24
+ const files = await fileSystem.glob(['**/*.ts'], { cwd: './src' });
25
+
26
+ // Read files
27
+ const content = await fileSystem.readFile('file.txt');
28
+
29
+ // Hash files
30
+ const hash = fileSystem.hash('file.txt');
31
+
32
+ // Write files
33
+ fileSystem.write('output.txt', 'Hello World');
34
+
35
+ // Check existence
36
+ if (fileSystem.exists('file.txt')) {
37
+ // ...
38
+ }
39
+ ```
40
+
41
+ ## API
42
+
43
+ | Method | Description |
44
+ | --------------------------- | --------------------------------- |
45
+ | `glob(patterns, options)` | Find files matching glob patterns |
46
+ | `readFile(path)` | Read file as string (async) |
47
+ | `readFileSync(path)` | Read file as string (sync) |
48
+ | `readFileAsBuffer(path)` | Read file as Buffer |
49
+ | `write(path, content)` | Write content to file |
50
+ | `writeAsync(path, content)` | Write content to file (async) |
51
+ | `exists(path)` | Check if path exists |
52
+ | `ensureDir(path)` | Ensure directory exists |
53
+ | `copyDir(src, dest)` | Copy directory recursively |
54
+ | `copyFile(src, dest)` | Copy single file |
55
+ | `remove(path)` | Remove file or directory |
56
+ | `removeAsync(path)` | Remove file or directory (async) |
57
+ | `hash(path)` | Get hash of file contents |
58
+ | `gzipFile(path)` | Gzip a single file |
59
+ | `gzipDir(path, extensions)` | Gzip files in directory |
60
+ | `isDirectory(path)` | Check if path is directory |
61
+ | `verifyFileExists(path)` | Throw if file doesn't exist |
62
+
63
+ ## Performance
64
+
65
+ Benchmark results (Apple M4):
66
+
67
+ | Operation | BunFileSystem | NodeFileSystem |
68
+ | ---------------- | ------------- | -------------- |
69
+ | glob (100 files) | 64.85 µs | 71.06 µs |
70
+ | hash (1MB file) | **87 µs** | **393 µs** |
71
+
72
+ Bun adapter is **4.5x faster** for file hashing.
73
+
74
+ ## License
75
+
76
+ MIT
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@ecopages/file-system",
3
+ "version": "0.2.0-alpha.0",
4
+ "description": "Runtime-agnostic file system utilities for Ecopages with Bun and Node adapters",
5
+ "keywords": [
6
+ "ecopages",
7
+ "fs",
8
+ "file-system",
9
+ "bun",
10
+ "node"
11
+ ],
12
+ "license": "MIT",
13
+ "main": "./src/index.ts",
14
+ "type": "module",
15
+ "files": [
16
+ "src"
17
+ ],
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/ecopages/ecopages.git",
21
+ "directory": "packages/file-system"
22
+ },
23
+ "scripts": {
24
+ "typecheck": "tsc --noEmit",
25
+ "test": "bun test",
26
+ "benchmark": "bun run test/benchmark.ts",
27
+ "release:jsr": "bunx jsr publish"
28
+ },
29
+ "exports": {
30
+ ".": {
31
+ "import": "./src/index.ts",
32
+ "require": "./src/index.ts",
33
+ "types": "./src/types.ts"
34
+ },
35
+ "./bun": {
36
+ "import": "./src/adapters/bun.ts",
37
+ "types": "./src/adapters/bun.ts"
38
+ },
39
+ "./node": {
40
+ "import": "./src/adapters/node.ts",
41
+ "types": "./src/adapters/node.ts"
42
+ }
43
+ },
44
+ "dependencies": {
45
+ "fast-glob": "^3.3.2"
46
+ },
47
+ "devDependencies": {
48
+ "@types/bun": "^1.3.9",
49
+ "@types/node": "^24",
50
+ "mitata": "^1.0.34"
51
+ },
52
+ "peerDependencies": {
53
+ "bun": ">=1.0.0"
54
+ },
55
+ "peerDependenciesMeta": {
56
+ "bun": {
57
+ "optional": true
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @module @ecopages/file-system/adapters/bun
3
+ * @description Bun-optimized file system adapter using Bun.Glob, Bun.hash, and Bun.file.
4
+ */
5
+
6
+ import { dirname } from 'node:path';
7
+ import type { GlobScanOptions } from 'bun';
8
+ import type { FileSystem, GlobOptions } from '../types.ts';
9
+ import { BaseFileSystem } from '../utils/common.ts';
10
+
11
+ /**
12
+ * Bun-optimized implementation of the FileSystem interface.
13
+ */
14
+ export class BunFileSystem extends BaseFileSystem implements FileSystem {
15
+ async existsAsync(path: string): Promise<boolean> {
16
+ return Bun.file(path).exists();
17
+ }
18
+
19
+ async readFile(path: string): Promise<string> {
20
+ try {
21
+ this.verifyFileExists(path);
22
+ return await Bun.file(path).text();
23
+ } catch (error) {
24
+ const message = error instanceof Error ? error.message : String(error);
25
+ throw new Error(`Error reading file: ${path}, ${message}`);
26
+ }
27
+ }
28
+
29
+ async writeAsync(filepath: string, contents: string | Buffer): Promise<void> {
30
+ try {
31
+ await this.ensureDirAsync(dirname(filepath));
32
+ await Bun.write(filepath, contents);
33
+ } catch (error) {
34
+ const message = error instanceof Error ? error.message : String(error);
35
+ throw new Error(`Error writing file: ${filepath}. Cause: ${message}`);
36
+ }
37
+ }
38
+
39
+ async copyFileAsync(source: string, destination: string): Promise<void> {
40
+ try {
41
+ await Bun.write(destination, Bun.file(source));
42
+ } catch (error) {
43
+ const message = error instanceof Error ? error.message : String(error);
44
+ throw new Error(`Error copying file: ${source} to ${destination}. Cause: ${message}`);
45
+ }
46
+ }
47
+
48
+ async glob(patterns: string[], options: GlobOptions = {}): Promise<string[]> {
49
+ const scanOptions: GlobScanOptions = {
50
+ cwd: options.cwd ?? process.cwd(),
51
+ };
52
+
53
+ const promises = patterns.map((pattern) => {
54
+ const glob = new Bun.Glob(pattern);
55
+ return Array.fromAsync(glob.scan(scanOptions));
56
+ });
57
+
58
+ const results = await Promise.all(promises);
59
+ let files = results.flat();
60
+
61
+ if (options.ignore?.length) {
62
+ const ignoreGlobs = options.ignore.map((pattern) => new Bun.Glob(pattern));
63
+ files = files.filter((file) => !ignoreGlobs.some((glob) => glob.match(file)));
64
+ }
65
+
66
+ return files;
67
+ }
68
+
69
+ hash(path: string): string {
70
+ try {
71
+ const buffer = this.readFileAsBuffer(path);
72
+ return Bun.hash(buffer).toString();
73
+ } catch (error) {
74
+ const message = error instanceof Error ? error.message : String(error);
75
+ throw new Error(`Error hashing file: ${path}. Cause: ${message}`);
76
+ }
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Singleton instance for Bun runtime.
82
+ */
83
+ export const bunFs: BunFileSystem = new BunFileSystem();
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @module @ecopages/file-system/adapters/node
3
+ * @description Node.js file system adapter using fast-glob, crypto, and node:fs.
4
+ */
5
+
6
+ import crypto from 'node:crypto';
7
+ import {
8
+ access as accessAsync,
9
+ cp as cpAsync,
10
+ readFile as readFileAsync,
11
+ writeFile as writeFileAsync,
12
+ } from 'node:fs/promises';
13
+ import { dirname } from 'node:path';
14
+ import fg from 'fast-glob';
15
+ import type { FileSystem, GlobOptions } from '../types.ts';
16
+ import { BaseFileSystem } from '../utils/common.ts';
17
+
18
+ /**
19
+ * Node.js implementation of the FileSystem interface.
20
+ */
21
+ export class NodeFileSystem extends BaseFileSystem implements FileSystem {
22
+ async existsAsync(filePath: string): Promise<boolean> {
23
+ try {
24
+ await accessAsync(filePath);
25
+ return true;
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+
31
+ async readFile(path: string): Promise<string> {
32
+ try {
33
+ this.verifyFileExists(path);
34
+ return await readFileAsync(path, 'utf-8');
35
+ } catch (error) {
36
+ const message = error instanceof Error ? error.message : String(error);
37
+ throw new Error(`Error reading file: ${path}, ${message}`);
38
+ }
39
+ }
40
+
41
+ async writeAsync(filepath: string, contents: string | Buffer): Promise<void> {
42
+ try {
43
+ await this.ensureDirAsync(dirname(filepath));
44
+ await writeFileAsync(filepath, contents);
45
+ } catch (error) {
46
+ const message = error instanceof Error ? error.message : String(error);
47
+ throw new Error(`Error writing file: ${filepath}. Cause: ${message}`);
48
+ }
49
+ }
50
+
51
+ async copyFileAsync(source: string, destination: string): Promise<void> {
52
+ await cpAsync(source, destination);
53
+ }
54
+
55
+ async glob(patterns: string[], options: GlobOptions = {}): Promise<string[]> {
56
+ return fg(patterns, {
57
+ cwd: options.cwd ?? process.cwd(),
58
+ ignore: options.ignore,
59
+ ...options,
60
+ });
61
+ }
62
+
63
+ hash(path: string): string {
64
+ try {
65
+ const buffer = this.readFileAsBuffer(path);
66
+ return crypto.createHash('sha256').update(buffer).digest('hex');
67
+ } catch (error) {
68
+ const message = error instanceof Error ? error.message : String(error);
69
+ throw new Error(`Error hashing file: ${path}. Cause: ${message}`);
70
+ }
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Singleton instance for Node.js runtime.
76
+ */
77
+ export const nodeFs: NodeFileSystem = new NodeFileSystem();
package/src/index.ts ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @module @ecopages/file-system
3
+ * @description Runtime-agnostic file system utilities for Ecopages.
4
+ *
5
+ * Automatically selects the optimal adapter based on runtime:
6
+ * - **Bun**: Uses `Bun.Glob`, `Bun.hash`, `Bun.file` for maximum performance
7
+ * - **Node.js**: Uses `fast-glob` and `crypto` for compatibility
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { fileSystem } from '@ecopages/file-system';
12
+ *
13
+ * const files = await fileSystem.glob(['**\/*.ts']);
14
+ * const content = await fileSystem.readFile('file.txt');
15
+ */
16
+
17
+ export * from './types.ts';
18
+ export { BaseFileSystem } from './utils/common.ts';
19
+ export { BunFileSystem, bunFs } from './adapters/bun.ts';
20
+ export { NodeFileSystem, nodeFs } from './adapters/node.ts';
21
+
22
+ import type { FileSystem } from './types.ts';
23
+
24
+ /**
25
+ * Creates a FileSystem instance based on the current runtime.
26
+ * Uses Bun adapter if Bun is available, otherwise Node adapter.
27
+ */
28
+ async function createFileSystem(): Promise<FileSystem> {
29
+ if (typeof Bun !== 'undefined') {
30
+ const { bunFs } = await import('./adapters/bun.ts');
31
+ return bunFs;
32
+ }
33
+
34
+ const { nodeFs } = await import('./adapters/node.ts');
35
+ return nodeFs;
36
+ }
37
+
38
+ /**
39
+ * Runtime-agnostic file system instance.
40
+ * Automatically uses Bun or Node adapter based on environment.
41
+ */
42
+ export const fileSystem: FileSystem = await createFileSystem();
package/src/types.ts ADDED
@@ -0,0 +1,187 @@
1
+ /**
2
+ * @module @ecopages/file-system/types
3
+ * @description Type definitions for runtime-agnostic file system operations.
4
+ */
5
+
6
+ /**
7
+ * Options for glob pattern matching.
8
+ */
9
+ export interface GlobOptions {
10
+ /** Working directory for glob patterns */
11
+ cwd?: string;
12
+ /** Patterns to ignore */
13
+ ignore?: string[];
14
+ }
15
+
16
+ /**
17
+ * Runtime-agnostic file system interface.
18
+ * Implementations exist for Bun (optimized) and Node.js (fallback).
19
+ */
20
+ export interface FileSystem {
21
+ /**
22
+ * Scan the file system for files matching glob patterns.
23
+ * @param patterns - Glob patterns to match
24
+ * @param options - Glob options
25
+ */
26
+ glob(patterns: string[], options?: GlobOptions): Promise<string[]>;
27
+
28
+ /**
29
+ * Read a file as a string asynchronously.
30
+ * @param path - Path to the file
31
+ */
32
+ readFile(path: string): Promise<string>;
33
+
34
+ /**
35
+ * Read a file as a string synchronously.
36
+ * @param path - Path to the file
37
+ */
38
+ readFileSync(path: string): string;
39
+
40
+ /**
41
+ * Read a file as a Buffer.
42
+ * @param path - Path to the file
43
+ */
44
+ readFileAsBuffer(path: string): Buffer;
45
+
46
+ /**
47
+ * Write contents to a file synchronously.
48
+ * Creates parent directories if they don't exist.
49
+ * @param filepath - Path to write to
50
+ * @param contents - Content to write
51
+ */
52
+ write(filepath: string, contents: string | Buffer): void;
53
+
54
+ /**
55
+ * Write contents to a file asynchronously.
56
+ * @param filepath - Path to write to
57
+ * @param contents - Content to write
58
+ */
59
+ writeAsync(filepath: string, contents: string | Buffer): Promise<void>;
60
+
61
+ /**
62
+ * Ensure a directory exists, optionally cleaning it first.
63
+ * @param dirPath - Directory path
64
+ * @param forceCleanup - If true, remove existing directory first
65
+ */
66
+ ensureDir(dirPath: string, forceCleanup?: boolean): void;
67
+
68
+ /**
69
+ * Ensure a directory exists asynchronously, optionally cleaning it first.
70
+ * @param dirPath - Directory path
71
+ * @param forceCleanup - If true, remove existing directory first
72
+ */
73
+ ensureDirAsync(dirPath: string, forceCleanup?: boolean): Promise<void>;
74
+
75
+ /**
76
+ * Copy a directory recursively.
77
+ * @param source - Source directory
78
+ * @param destination - Destination directory
79
+ */
80
+ copyDir(source: string, destination: string): void;
81
+
82
+ /**
83
+ * Copy a directory recursively asynchronously.
84
+ * @param source - Source directory
85
+ * @param destination - Destination directory
86
+ */
87
+ copyDirAsync(source: string, destination: string): Promise<void>;
88
+
89
+ /**
90
+ * Remove all contents of a directory.
91
+ * @param path - Directory path
92
+ */
93
+ emptyDir(path: string): void;
94
+
95
+ /**
96
+ * Remove all contents of a directory asynchronously.
97
+ * @param path - Directory path
98
+ */
99
+ emptyDirAsync(path: string): Promise<void>;
100
+
101
+ /**
102
+ * Check if a path is a directory.
103
+ * @param path - Path to check
104
+ */
105
+ isDirectory(path: string): boolean;
106
+
107
+ /**
108
+ * Check if a path is a directory asynchronously.
109
+ * @param path - Path to check
110
+ */
111
+ isDirectoryAsync(path: string): Promise<boolean>;
112
+
113
+ /**
114
+ * Check if a file or directory exists.
115
+ * @param path - Path to check
116
+ */
117
+ exists(path: string): boolean;
118
+
119
+ /**
120
+ * Check if a file or directory exists asynchronously.
121
+ * @param path - Path to check
122
+ */
123
+ existsAsync(path: string): Promise<boolean>;
124
+
125
+ /**
126
+ * Copy a file.
127
+ * @param source - Source file path
128
+ * @param destination - Destination file path
129
+ */
130
+ copyFile(source: string, destination: string): void;
131
+
132
+ /**
133
+ * Copy a file asynchronously.
134
+ * @param source - Source file path
135
+ * @param destination - Destination file path
136
+ */
137
+ copyFileAsync(source: string, destination: string): Promise<void>;
138
+
139
+ /**
140
+ * Remove a file or directory synchronously.
141
+ * @param path - Path to remove
142
+ */
143
+ remove(path: string): void;
144
+
145
+ /**
146
+ * Remove a file or directory asynchronously.
147
+ * @param path - Path to remove
148
+ */
149
+ removeAsync(path: string): Promise<void>;
150
+
151
+ /**
152
+ * Get a hash of a file's contents.
153
+ * @param path - Path to the file
154
+ */
155
+ hash(path: string): string;
156
+
157
+ /**
158
+ * Gzip a single file.
159
+ * @param path - Path to the file
160
+ */
161
+ gzipFile(path: string): void;
162
+
163
+ /**
164
+ * Gzip all files with specified extensions in a directory.
165
+ * @param path - Directory path
166
+ * @param extensions - File extensions to gzip (without dot)
167
+ */
168
+ gzipDir(path: string, extensions: string[]): void;
169
+
170
+ /**
171
+ * Verify that a file exists, throw FileNotFoundError if not.
172
+ * @param path - Path to verify
173
+ * @throws FileNotFoundError
174
+ */
175
+ verifyFileExists(path: string): void;
176
+ }
177
+
178
+ /**
179
+ * Error thrown when a file is not found.
180
+ */
181
+ export class FileNotFoundError extends Error {
182
+ code = 'ENOENT';
183
+ constructor(path: string) {
184
+ super(`File: ${path} not found`);
185
+ this.name = 'FileNotFoundError';
186
+ }
187
+ }
@@ -0,0 +1,223 @@
1
+ /**
2
+ * @module @ecopages/file-system/utils/common
3
+ * @description Shared utilities used by both Bun and Node adapters.
4
+ */
5
+
6
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
7
+ import { cp as cpAsync, mkdir as mkdirAsync, readdir, rm as rmAsync, stat as statAsync } from 'node:fs/promises';
8
+ import { dirname, extname, join as pathJoin } from 'node:path';
9
+ import zlib from 'node:zlib';
10
+ import { FileNotFoundError, type GlobOptions } from '../types.ts';
11
+
12
+ /**
13
+ * Base implementation for file system operations.
14
+ * Subclasses override specific methods with runtime-optimized versions.
15
+ */
16
+ export abstract class BaseFileSystem {
17
+ /**
18
+ * Check if a file or directory exists.
19
+ */
20
+ exists(filePath: string): boolean {
21
+ return existsSync(filePath);
22
+ }
23
+
24
+ /**
25
+ * Verify that a file exists, throw FileNotFoundError if not.
26
+ */
27
+ verifyFileExists(filePath: string): void {
28
+ if (!this.exists(filePath)) {
29
+ throw new FileNotFoundError(filePath);
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Read a file as a string synchronously.
35
+ */
36
+ readFileSync(filePath: string): string {
37
+ try {
38
+ return readFileSync(filePath, 'utf-8');
39
+ } catch (error) {
40
+ const message = error instanceof Error ? error.message : String(error);
41
+ throw new Error(`Error reading file: ${filePath}, ${message}`, { cause: error });
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Read a file as a Buffer.
47
+ */
48
+ readFileAsBuffer(filePath: string): Buffer {
49
+ try {
50
+ return readFileSync(filePath);
51
+ } catch (error) {
52
+ const message = error instanceof Error ? error.message : String(error);
53
+ throw new Error(`Error reading file: ${filePath}, ${message}`, { cause: error });
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Write contents to a file synchronously.
59
+ */
60
+ write(filepath: string, contents: string | Buffer): void {
61
+ try {
62
+ this.ensureDir(dirname(filepath));
63
+ writeFileSync(filepath, contents);
64
+ } catch (error) {
65
+ const message = error instanceof Error ? error.message : String(error);
66
+ throw new Error(`Error writing file: ${filepath}. Cause: ${message}`);
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Ensure a directory exists.
72
+ */
73
+ ensureDir(dirPath: string, forceCleanup?: boolean): void {
74
+ if (forceCleanup && existsSync(dirPath)) {
75
+ rmSync(dirPath, { recursive: true, force: true });
76
+ }
77
+ mkdirSync(dirPath, { recursive: true });
78
+ }
79
+
80
+ /**
81
+ * Ensure a directory exists asynchronously.
82
+ */
83
+ async ensureDirAsync(dirPath: string, forceCleanup?: boolean): Promise<void> {
84
+ if (forceCleanup && (await this.existsAsync(dirPath))) {
85
+ await rmAsync(dirPath, { recursive: true, force: true });
86
+ }
87
+ await mkdirAsync(dirPath, { recursive: true });
88
+ }
89
+
90
+ /**
91
+ * Copy a directory recursively.
92
+ */
93
+ copyDir(source: string, destination: string): void {
94
+ cpSync(source, destination, { recursive: true });
95
+ }
96
+
97
+ /**
98
+ * Copy a directory recursively asynchronously.
99
+ */
100
+ async copyDirAsync(source: string, destination: string): Promise<void> {
101
+ await cpAsync(source, destination, { recursive: true });
102
+ }
103
+
104
+ /**
105
+ * Copy a file.
106
+ */
107
+ copyFile(source: string, destination: string): void {
108
+ cpSync(source, destination);
109
+ }
110
+
111
+ /**
112
+ * Remove all contents of a directory.
113
+ */
114
+ emptyDir(dirPath: string): void {
115
+ if (!this.isDirectory(dirPath)) {
116
+ return;
117
+ }
118
+ const entries = readdirSync(dirPath);
119
+ for (const entry of entries) {
120
+ const fullPath = pathJoin(dirPath, entry);
121
+ rmSync(fullPath, { recursive: true, force: true });
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Remove a directory's contents asynchronously.
127
+ */
128
+ async emptyDirAsync(dirPath: string): Promise<void> {
129
+ if (!(await this.isDirectoryAsync(dirPath))) {
130
+ return;
131
+ }
132
+ const entries = await readdir(dirPath);
133
+ await Promise.all(entries.map((entry) => rmAsync(pathJoin(dirPath, entry), { recursive: true, force: true })));
134
+ }
135
+
136
+ /**
137
+ * Remove a file or directory synchronously.
138
+ */
139
+ remove(filePath: string): void {
140
+ rmSync(filePath, { recursive: true, force: true });
141
+ }
142
+
143
+ /**
144
+ * Remove a file or directory asynchronously.
145
+ */
146
+ async removeAsync(filePath: string): Promise<void> {
147
+ await rmAsync(filePath, { recursive: true, force: true });
148
+ }
149
+
150
+ /**
151
+ * Check if a path is a directory.
152
+ */
153
+ isDirectory(filePath: string): boolean {
154
+ return existsSync(filePath) && statSync(filePath).isDirectory();
155
+ }
156
+
157
+ /**
158
+ * Check if a path is a directory asynchronously.
159
+ */
160
+ async isDirectoryAsync(filePath: string): Promise<boolean> {
161
+ try {
162
+ const stats = await statAsync(filePath);
163
+ return stats.isDirectory();
164
+ } catch {
165
+ return false;
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Gzip a single file.
171
+ */
172
+ gzipFile(filePath: string): void {
173
+ const data = this.readFileAsBuffer(filePath);
174
+ const compressedData = zlib.gzipSync(Buffer.from(data));
175
+ const gzipFile = `${filePath}.gz`;
176
+ writeFileSync(gzipFile, compressedData);
177
+ }
178
+
179
+ /**
180
+ * Gzip all files with specified extensions in a directory.
181
+ */
182
+ gzipDir(dirPath: string, extensionsToGzip: string[]): void {
183
+ const entries = readdirSync(dirPath, { recursive: true, withFileTypes: true });
184
+ for (const entry of entries) {
185
+ if (entry.isFile()) {
186
+ const ext = extname(entry.name).slice(1);
187
+ if (extensionsToGzip.includes(ext)) {
188
+ this.gzipFile(pathJoin(entry.parentPath, entry.name));
189
+ }
190
+ }
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Glob patterns - must be implemented by runtime-specific adapters.
196
+ */
197
+ abstract glob(patterns: string[], options?: GlobOptions): Promise<string[]>;
198
+
199
+ /**
200
+ * Read file async - must be implemented by runtime-specific adapters.
201
+ */
202
+ abstract readFile(path: string): Promise<string>;
203
+
204
+ /**
205
+ * Write file async - must be implemented by runtime-specific adapters.
206
+ */
207
+ abstract writeAsync(filepath: string, contents: string | Buffer): Promise<void>;
208
+
209
+ /**
210
+ * Check if a file exists asynchronously - must be implemented by runtime-specific adapters.
211
+ */
212
+ abstract existsAsync(filePath: string): Promise<boolean>;
213
+
214
+ /**
215
+ * Copy a file asynchronously - must be implemented by runtime-specific adapters.
216
+ */
217
+ abstract copyFileAsync(source: string, destination: string): Promise<void>;
218
+
219
+ /**
220
+ * Hash file - must be implemented by runtime-specific adapters.
221
+ */
222
+ abstract hash(path: string): string;
223
+ }