@ecopages/file-system 0.2.0-alpha.0 → 0.2.0-alpha.10

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 ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@ecopages/file-system` are documented here.
4
+
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
+
7
+ ## [UNRELEASED] — TBD
8
+
9
+ ### Bug Fixes
10
+
11
+ - Switched Node-side globbing from `fast-glob` to native `node:fs/promises.glob()` so bundled ESM runtime paths avoid CommonJS interop failures.
package/README.md CHANGED
@@ -1,18 +1,17 @@
1
1
  # @ecopages/file-system
2
2
 
3
- Runtime-agnostic file system utilities for Ecopages with optimized Bun and Node.js adapters.
3
+ Runtime-agnostic file system utilities for Ecopages that automatically select the optimal adapter (Bun or Node.js) based on the execution environment.
4
4
 
5
5
  ## Features
6
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
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.
8
+ - **Unified Interface**: Write file system code once; let the runtime handle the optimization.
9
+ - **Type Safe**: Full TypeScript support with a consistent API.
11
10
 
12
11
  ## Installation
13
12
 
14
13
  ```bash
15
- bun add @ecopages/file-system
14
+ bunx jsr add @ecopages/file-system
16
15
  ```
17
16
 
18
17
  ## Usage
@@ -41,11 +40,11 @@ if (fileSystem.exists('file.txt')) {
41
40
  ## API
42
41
 
43
42
  | Method | Description |
44
- | --------------------------- | --------------------------------- |
43
+ | :-------------------------- | :-------------------------------- |
45
44
  | `glob(patterns, options)` | Find files matching glob patterns |
46
45
  | `readFile(path)` | Read file as string (async) |
47
46
  | `readFileSync(path)` | Read file as string (sync) |
48
- | `readFileAsBuffer(path)` | Read file as Buffer |
47
+ | `readFileAsBuffer(path)` | Read file as `Buffer` |
49
48
  | `write(path, content)` | Write content to file |
50
49
  | `writeAsync(path, content)` | Write content to file (async) |
51
50
  | `exists(path)` | Check if path exists |
@@ -59,18 +58,3 @@ if (fileSystem.exists('file.txt')) {
59
58
  | `gzipDir(path, extensions)` | Gzip files in directory |
60
59
  | `isDirectory(path)` | Check if path is directory |
61
60
  | `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 CHANGED
@@ -1,60 +1,59 @@
1
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
- }
2
+ "name": "@ecopages/file-system",
3
+ "version": "0.2.0-alpha.10",
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.js",
14
+ "type": "module",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/ecopages/ecopages.git",
18
+ "directory": "packages/file-system"
19
+ },
20
+ "exports": {
21
+ ".": {
22
+ "import": "./src/index.js",
23
+ "types": "./src/types.d.ts",
24
+ "default": "./src/index.js"
25
+ },
26
+ "./bun": {
27
+ "import": "./src/adapters/bun.js",
28
+ "types": "./src/adapters/bun.d.ts",
29
+ "default": "./src/adapters/bun.js"
30
+ },
31
+ "./node": {
32
+ "import": "./src/adapters/node.js",
33
+ "types": "./src/adapters/node.d.ts",
34
+ "default": "./src/adapters/node.js"
35
+ },
36
+ "./bun.ts": {
37
+ "import": "./src/adapters/bun.js",
38
+ "types": "./src/adapters/bun.d.ts",
39
+ "default": "./src/adapters/bun.js"
40
+ },
41
+ "./node.ts": {
42
+ "import": "./src/adapters/node.js",
43
+ "types": "./src/adapters/node.d.ts",
44
+ "default": "./src/adapters/node.js"
45
+ }
46
+ },
47
+ "dependencies": {
48
+ "fast-glob": "^3.3.2"
49
+ },
50
+ "peerDependencies": {
51
+ "bun": ">=1.0.0"
52
+ },
53
+ "peerDependenciesMeta": {
54
+ "bun": {
55
+ "optional": true
56
+ }
57
+ },
58
+ "types": "./src/types.d.ts"
60
59
  }
@@ -0,0 +1,21 @@
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
+ import type { FileSystem, GlobOptions } from '../types.js';
6
+ import { BaseFileSystem } from '../utils/common.js';
7
+ /**
8
+ * Bun-optimized implementation of the FileSystem interface.
9
+ */
10
+ export declare class BunFileSystem extends BaseFileSystem implements FileSystem {
11
+ existsAsync(path: string): Promise<boolean>;
12
+ readFile(path: string): Promise<string>;
13
+ writeAsync(filepath: string, contents: string | Buffer): Promise<void>;
14
+ copyFileAsync(source: string, destination: string): Promise<void>;
15
+ glob(patterns: string[], options?: GlobOptions): Promise<string[]>;
16
+ hash(path: string): string;
17
+ }
18
+ /**
19
+ * Singleton instance for Bun runtime.
20
+ */
21
+ export declare const bunFs: BunFileSystem;
@@ -0,0 +1,63 @@
1
+ import { dirname } from "node:path";
2
+ import { BaseFileSystem } from "../utils/common.js";
3
+ class BunFileSystem extends BaseFileSystem {
4
+ async existsAsync(path) {
5
+ return Bun.file(path).exists();
6
+ }
7
+ async readFile(path) {
8
+ try {
9
+ this.verifyFileExists(path);
10
+ return await Bun.file(path).text();
11
+ } catch (error) {
12
+ const message = error instanceof Error ? error.message : String(error);
13
+ throw new Error(`Error reading file: ${path}, ${message}`);
14
+ }
15
+ }
16
+ async writeAsync(filepath, contents) {
17
+ try {
18
+ await this.ensureDirAsync(dirname(filepath));
19
+ await Bun.write(filepath, contents);
20
+ } catch (error) {
21
+ const message = error instanceof Error ? error.message : String(error);
22
+ throw new Error(`Error writing file: ${filepath}. Cause: ${message}`);
23
+ }
24
+ }
25
+ async copyFileAsync(source, destination) {
26
+ try {
27
+ await Bun.write(destination, Bun.file(source));
28
+ } catch (error) {
29
+ const message = error instanceof Error ? error.message : String(error);
30
+ throw new Error(`Error copying file: ${source} to ${destination}. Cause: ${message}`);
31
+ }
32
+ }
33
+ async glob(patterns, options = {}) {
34
+ const scanOptions = {
35
+ cwd: options.cwd ?? process.cwd()
36
+ };
37
+ const promises = patterns.map((pattern) => {
38
+ const glob = new Bun.Glob(pattern);
39
+ return Array.fromAsync(glob.scan(scanOptions));
40
+ });
41
+ const results = await Promise.all(promises);
42
+ let files = results.flat();
43
+ if (options.ignore?.length) {
44
+ const ignoreGlobs = options.ignore.map((pattern) => new Bun.Glob(pattern));
45
+ files = files.filter((file) => !ignoreGlobs.some((glob) => glob.match(file)));
46
+ }
47
+ return files;
48
+ }
49
+ hash(path) {
50
+ try {
51
+ const buffer = this.readFileAsBuffer(path);
52
+ return Bun.hash(buffer).toString();
53
+ } catch (error) {
54
+ const message = error instanceof Error ? error.message : String(error);
55
+ throw new Error(`Error hashing file: ${path}. Cause: ${message}`);
56
+ }
57
+ }
58
+ }
59
+ const bunFs = new BunFileSystem();
60
+ export {
61
+ BunFileSystem,
62
+ bunFs
63
+ };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @module @ecopages/file-system/adapters/node
3
+ * @description Node.js file system adapter using fast-glob, crypto, and node:fs.
4
+ */
5
+ import type { FileSystem, GlobOptions } from '../types.js';
6
+ import { BaseFileSystem } from '../utils/common.js';
7
+ /**
8
+ * Node.js implementation of the FileSystem interface.
9
+ */
10
+ export declare class NodeFileSystem extends BaseFileSystem implements FileSystem {
11
+ existsAsync(filePath: string): Promise<boolean>;
12
+ readFile(path: string): Promise<string>;
13
+ writeAsync(filepath: string, contents: string | Buffer): Promise<void>;
14
+ copyFileAsync(source: string, destination: string): Promise<void>;
15
+ glob(patterns: string[], options?: GlobOptions): Promise<string[]>;
16
+ hash(path: string): string;
17
+ }
18
+ /**
19
+ * Singleton instance for Node.js runtime.
20
+ */
21
+ export declare const nodeFs: NodeFileSystem;
@@ -0,0 +1,69 @@
1
+ import crypto from "node:crypto";
2
+ import {
3
+ access as accessAsync,
4
+ cp as cpAsync,
5
+ glob as globAsync,
6
+ readFile as readFileAsync,
7
+ writeFile as writeFileAsync
8
+ } from "node:fs/promises";
9
+ import { dirname } from "node:path";
10
+ import { BaseFileSystem } from "../utils/common.js";
11
+ async function collectGlobMatches(pattern, options) {
12
+ const matches = [];
13
+ for await (const entry of globAsync(pattern, {
14
+ cwd: options.cwd ?? process.cwd(),
15
+ exclude: options.ignore
16
+ })) {
17
+ matches.push(entry);
18
+ }
19
+ return matches;
20
+ }
21
+ class NodeFileSystem extends BaseFileSystem {
22
+ async existsAsync(filePath) {
23
+ try {
24
+ await accessAsync(filePath);
25
+ return true;
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+ async readFile(path) {
31
+ try {
32
+ this.verifyFileExists(path);
33
+ return await readFileAsync(path, "utf-8");
34
+ } catch (error) {
35
+ const message = error instanceof Error ? error.message : String(error);
36
+ throw new Error(`Error reading file: ${path}, ${message}`);
37
+ }
38
+ }
39
+ async writeAsync(filepath, contents) {
40
+ try {
41
+ await this.ensureDirAsync(dirname(filepath));
42
+ await writeFileAsync(filepath, contents);
43
+ } catch (error) {
44
+ const message = error instanceof Error ? error.message : String(error);
45
+ throw new Error(`Error writing file: ${filepath}. Cause: ${message}`);
46
+ }
47
+ }
48
+ async copyFileAsync(source, destination) {
49
+ await cpAsync(source, destination);
50
+ }
51
+ async glob(patterns, options = {}) {
52
+ const files = await Promise.all(patterns.map((pattern) => collectGlobMatches(pattern, options)));
53
+ return Array.from(new Set(files.flat()));
54
+ }
55
+ hash(path) {
56
+ try {
57
+ const buffer = this.readFileAsBuffer(path);
58
+ return crypto.createHash("sha256").update(buffer).digest("hex");
59
+ } catch (error) {
60
+ const message = error instanceof Error ? error.message : String(error);
61
+ throw new Error(`Error hashing file: ${path}. Cause: ${message}`);
62
+ }
63
+ }
64
+ }
65
+ const nodeFs = new NodeFileSystem();
66
+ export {
67
+ NodeFileSystem,
68
+ nodeFs
69
+ };
package/src/index.d.ts ADDED
@@ -0,0 +1,25 @@
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
+ export * from './types.js';
17
+ export { BaseFileSystem } from './utils/common.js';
18
+ export { BunFileSystem, bunFs } from './adapters/bun.js';
19
+ export { NodeFileSystem, nodeFs } from './adapters/node.js';
20
+ import type { FileSystem } from './types.js';
21
+ /**
22
+ * Runtime-agnostic file system instance.
23
+ * Automatically uses Bun or Node adapter based on environment.
24
+ */
25
+ export declare const fileSystem: FileSystem;
package/src/index.js ADDED
@@ -0,0 +1,21 @@
1
+ export * from "./types.js";
2
+ import { BaseFileSystem } from "./utils/common.js";
3
+ import { BunFileSystem, bunFs } from "./adapters/bun.js";
4
+ import { NodeFileSystem, nodeFs } from "./adapters/node.js";
5
+ async function createFileSystem() {
6
+ if (typeof Bun !== "undefined") {
7
+ const { bunFs: bunFs2 } = await import("./adapters/bun.js");
8
+ return bunFs2;
9
+ }
10
+ const { nodeFs: nodeFs2 } = await import("./adapters/node.js");
11
+ return nodeFs2;
12
+ }
13
+ const fileSystem = await createFileSystem();
14
+ export {
15
+ BaseFileSystem,
16
+ BunFileSystem,
17
+ NodeFileSystem,
18
+ bunFs,
19
+ fileSystem,
20
+ nodeFs
21
+ };
package/src/types.d.ts ADDED
@@ -0,0 +1,158 @@
1
+ /**
2
+ * @module @ecopages/file-system/types
3
+ * @description Type definitions for runtime-agnostic file system operations.
4
+ */
5
+ /**
6
+ * Options for glob pattern matching.
7
+ */
8
+ export interface GlobOptions {
9
+ /** Working directory for glob patterns */
10
+ cwd?: string;
11
+ /** Patterns to ignore */
12
+ ignore?: string[];
13
+ }
14
+ /**
15
+ * Runtime-agnostic file system interface.
16
+ * Implementations exist for Bun (optimized) and Node.js (fallback).
17
+ */
18
+ export interface FileSystem {
19
+ /**
20
+ * Scan the file system for files matching glob patterns.
21
+ * @param patterns - Glob patterns to match
22
+ * @param options - Glob options
23
+ */
24
+ glob(patterns: string[], options?: GlobOptions): Promise<string[]>;
25
+ /**
26
+ * Read a file as a string asynchronously.
27
+ * @param path - Path to the file
28
+ */
29
+ readFile(path: string): Promise<string>;
30
+ /**
31
+ * Read a file as a string synchronously.
32
+ * @param path - Path to the file
33
+ */
34
+ readFileSync(path: string): string;
35
+ /**
36
+ * Read a file as a Buffer.
37
+ * @param path - Path to the file
38
+ */
39
+ readFileAsBuffer(path: string): Buffer;
40
+ /**
41
+ * Write contents to a file synchronously.
42
+ * Creates parent directories if they don't exist.
43
+ * @param filepath - Path to write to
44
+ * @param contents - Content to write
45
+ */
46
+ write(filepath: string, contents: string | Buffer): void;
47
+ /**
48
+ * Write contents to a file asynchronously.
49
+ * @param filepath - Path to write to
50
+ * @param contents - Content to write
51
+ */
52
+ writeAsync(filepath: string, contents: string | Buffer): Promise<void>;
53
+ /**
54
+ * Ensure a directory exists, optionally cleaning it first.
55
+ * @param dirPath - Directory path
56
+ * @param forceCleanup - If true, remove existing directory first
57
+ */
58
+ ensureDir(dirPath: string, forceCleanup?: boolean): void;
59
+ /**
60
+ * Ensure a directory exists asynchronously, optionally cleaning it first.
61
+ * @param dirPath - Directory path
62
+ * @param forceCleanup - If true, remove existing directory first
63
+ */
64
+ ensureDirAsync(dirPath: string, forceCleanup?: boolean): Promise<void>;
65
+ /**
66
+ * Copy a directory recursively.
67
+ * @param source - Source directory
68
+ * @param destination - Destination directory
69
+ */
70
+ copyDir(source: string, destination: string): void;
71
+ /**
72
+ * Copy a directory recursively asynchronously.
73
+ * @param source - Source directory
74
+ * @param destination - Destination directory
75
+ */
76
+ copyDirAsync(source: string, destination: string): Promise<void>;
77
+ /**
78
+ * Remove all contents of a directory.
79
+ * @param path - Directory path
80
+ */
81
+ emptyDir(path: string): void;
82
+ /**
83
+ * Remove all contents of a directory asynchronously.
84
+ * @param path - Directory path
85
+ */
86
+ emptyDirAsync(path: string): Promise<void>;
87
+ /**
88
+ * Check if a path is a directory.
89
+ * @param path - Path to check
90
+ */
91
+ isDirectory(path: string): boolean;
92
+ /**
93
+ * Check if a path is a directory asynchronously.
94
+ * @param path - Path to check
95
+ */
96
+ isDirectoryAsync(path: string): Promise<boolean>;
97
+ /**
98
+ * Check if a file or directory exists.
99
+ * @param path - Path to check
100
+ */
101
+ exists(path: string): boolean;
102
+ /**
103
+ * Check if a file or directory exists asynchronously.
104
+ * @param path - Path to check
105
+ */
106
+ existsAsync(path: string): Promise<boolean>;
107
+ /**
108
+ * Copy a file.
109
+ * @param source - Source file path
110
+ * @param destination - Destination file path
111
+ */
112
+ copyFile(source: string, destination: string): void;
113
+ /**
114
+ * Copy a file asynchronously.
115
+ * @param source - Source file path
116
+ * @param destination - Destination file path
117
+ */
118
+ copyFileAsync(source: string, destination: string): Promise<void>;
119
+ /**
120
+ * Remove a file or directory synchronously.
121
+ * @param path - Path to remove
122
+ */
123
+ remove(path: string): void;
124
+ /**
125
+ * Remove a file or directory asynchronously.
126
+ * @param path - Path to remove
127
+ */
128
+ removeAsync(path: string): Promise<void>;
129
+ /**
130
+ * Get a hash of a file's contents.
131
+ * @param path - Path to the file
132
+ */
133
+ hash(path: string): string;
134
+ /**
135
+ * Gzip a single file.
136
+ * @param path - Path to the file
137
+ */
138
+ gzipFile(path: string): void;
139
+ /**
140
+ * Gzip all files with specified extensions in a directory.
141
+ * @param path - Directory path
142
+ * @param extensions - File extensions to gzip (without dot)
143
+ */
144
+ gzipDir(path: string, extensions: string[]): void;
145
+ /**
146
+ * Verify that a file exists, throw FileNotFoundError if not.
147
+ * @param path - Path to verify
148
+ * @throws FileNotFoundError
149
+ */
150
+ verifyFileExists(path: string): void;
151
+ }
152
+ /**
153
+ * Error thrown when a file is not found.
154
+ */
155
+ export declare class FileNotFoundError extends Error {
156
+ code: string;
157
+ constructor(path: string);
158
+ }
package/src/types.js ADDED
@@ -0,0 +1,10 @@
1
+ class FileNotFoundError extends Error {
2
+ code = "ENOENT";
3
+ constructor(path) {
4
+ super(`File: ${path} not found`);
5
+ this.name = "FileNotFoundError";
6
+ }
7
+ }
8
+ export {
9
+ FileNotFoundError
10
+ };