@ecopages/file-system 0.2.0-alpha.8 → 0.2.1

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/CHANGELOG.md CHANGED
@@ -4,8 +4,8 @@ All notable changes to `@ecopages/file-system` are documented here.
4
4
 
5
5
  > **Note:** Changelog tracking begins at version `0.2.0`. Changes prior to this release are not recorded here but are available in the git history.
6
6
 
7
- ## [UNRELEASED] — TBD
7
+ ## [0.2.1] — 2026-04-16
8
8
 
9
9
  ### Bug Fixes
10
10
 
11
- - Switched Node-side globbing from `fast-glob` to native `node:fs/promises.glob()` so bundled ESM runtime paths avoid CommonJS interop failures.
11
+ - Switched Node globbing to native `node:fs/promises.glob()` and removed the stale `fast-glob` dependency.
package/README.md CHANGED
@@ -4,7 +4,7 @@ Runtime-agnostic file system utilities for Ecopages that automatically select th
4
4
 
5
5
  ## Features
6
6
 
7
- - **Runtime Detection**: Automatically uses `Bun.Glob`, `Bun.hash`, `Bun.file` for maximum performance on Bun, and falls back to `fast-glob` and `crypto` on Node.js.
7
+ - **Runtime Detection**: Automatically uses `Bun.Glob`, `Bun.hash`, `Bun.file` for maximum performance on Bun, and falls back to native `node:fs/promises.glob()` and `crypto` on Node.js.
8
8
  - **Unified Interface**: Write file system code once; let the runtime handle the optimization.
9
9
  - **Type Safe**: Full TypeScript support with a consistent API.
10
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopages/file-system",
3
- "version": "0.2.0-alpha.8",
3
+ "version": "0.2.1",
4
4
  "description": "Runtime-agnostic file system utilities for Ecopages with Bun and Node adapters",
5
5
  "keywords": [
6
6
  "ecopages",
@@ -44,9 +44,6 @@
44
44
  "default": "./src/adapters/node.js"
45
45
  }
46
46
  },
47
- "dependencies": {
48
- "fast-glob": "^3.3.2"
49
- },
50
47
  "peerDependencies": {
51
48
  "bun": ">=1.0.0"
52
49
  },
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @module @ecopages/file-system/adapters/node
3
- * @description Node.js file system adapter using fast-glob, crypto, and node:fs.
3
+ * @description Node.js file system adapter using node:fs/promises.glob(), crypto, and node:fs.
4
4
  */
5
5
  import type { FileSystem, GlobOptions } from '../types.js';
6
6
  import { BaseFileSystem } from '../utils/common.js';
@@ -1,83 +0,0 @@
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();
@@ -1,87 +0,0 @@
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
- glob as globAsync,
11
- readFile as readFileAsync,
12
- writeFile as writeFileAsync,
13
- } from 'node:fs/promises';
14
- import { dirname } from 'node:path';
15
- import type { FileSystem, GlobOptions } from '../types.ts';
16
- import { BaseFileSystem } from '../utils/common.ts';
17
-
18
- async function collectGlobMatches(pattern: string, options: GlobOptions): Promise<string[]> {
19
- const matches: string[] = [];
20
-
21
- for await (const entry of globAsync(pattern, {
22
- cwd: options.cwd ?? process.cwd(),
23
- exclude: options.ignore,
24
- })) {
25
- matches.push(entry);
26
- }
27
-
28
- return matches;
29
- }
30
-
31
- /**
32
- * Node.js implementation of the FileSystem interface.
33
- */
34
- export class NodeFileSystem extends BaseFileSystem implements FileSystem {
35
- async existsAsync(filePath: string): Promise<boolean> {
36
- try {
37
- await accessAsync(filePath);
38
- return true;
39
- } catch {
40
- return false;
41
- }
42
- }
43
-
44
- async readFile(path: string): Promise<string> {
45
- try {
46
- this.verifyFileExists(path);
47
- return await readFileAsync(path, 'utf-8');
48
- } catch (error) {
49
- const message = error instanceof Error ? error.message : String(error);
50
- throw new Error(`Error reading file: ${path}, ${message}`);
51
- }
52
- }
53
-
54
- async writeAsync(filepath: string, contents: string | Buffer): Promise<void> {
55
- try {
56
- await this.ensureDirAsync(dirname(filepath));
57
- await writeFileAsync(filepath, contents);
58
- } catch (error) {
59
- const message = error instanceof Error ? error.message : String(error);
60
- throw new Error(`Error writing file: ${filepath}. Cause: ${message}`);
61
- }
62
- }
63
-
64
- async copyFileAsync(source: string, destination: string): Promise<void> {
65
- await cpAsync(source, destination);
66
- }
67
-
68
- async glob(patterns: string[], options: GlobOptions = {}): Promise<string[]> {
69
- const files = await Promise.all(patterns.map((pattern) => collectGlobMatches(pattern, options)));
70
- return Array.from(new Set(files.flat()));
71
- }
72
-
73
- hash(path: string): string {
74
- try {
75
- const buffer = this.readFileAsBuffer(path);
76
- return crypto.createHash('sha256').update(buffer).digest('hex');
77
- } catch (error) {
78
- const message = error instanceof Error ? error.message : String(error);
79
- throw new Error(`Error hashing file: ${path}. Cause: ${message}`);
80
- }
81
- }
82
- }
83
-
84
- /**
85
- * Singleton instance for Node.js runtime.
86
- */
87
- export const nodeFs: NodeFileSystem = new NodeFileSystem();
package/src/index.ts DELETED
@@ -1,42 +0,0 @@
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 native `node:fs/promises.glob()` and `crypto`
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 DELETED
@@ -1,187 +0,0 @@
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
- }
@@ -1,223 +0,0 @@
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
- }