@jisan901/fs-browser 1.0.0 → 1.0.2

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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # fs-browser
1
+ # @jisan901/fs-browser
2
2
 
3
3
  Browser-compatible filesystem API with Vite plugin support. Write Node.js-style fs code that works in the browser!
4
4
 
@@ -14,7 +14,7 @@ Browser-compatible filesystem API with Vite plugin support. Write Node.js-style
14
14
  ## Installation
15
15
 
16
16
  ```bash
17
- npm install fs-browser
17
+ npm install @jisan901/fs-browser
18
18
  ```
19
19
 
20
20
  ## Quick Start
@@ -26,7 +26,7 @@ npm install fs-browser
26
26
  ```javascript
27
27
  // vite.config.js
28
28
  import { defineConfig } from 'vite';
29
- import fsPlugin from 'fs-browser/plugin';
29
+ import fsPlugin from '@jisan901/fs-browser/plugin';
30
30
 
31
31
  export default defineConfig({
32
32
  plugins: [
@@ -41,13 +41,13 @@ export default defineConfig({
41
41
  **2. Use in Browser Code**
42
42
 
43
43
  ```javascript
44
- import { readFile, writeFile, mkdir } from 'fs-browser';
44
+ import { readFile, writeFile, mkdir } from '@jisan901/fs-browser';
45
45
 
46
46
  // Write a file
47
47
  await writeFile('hello.txt', 'Hello World!');
48
48
 
49
49
  // Read a file
50
- const content = await readFile('hello.txt');
50
+ const content = await readFile('hello.txt', {encoding: 'utf-8'});
51
51
  console.log(content); // "Hello World!"
52
52
 
53
53
  // Create directory
@@ -115,7 +115,7 @@ withfs ./dist --host --open
115
115
 
116
116
  ## Development with Other Frameworks
117
117
 
118
- While `fs-browser` includes a Vite plugin, you can use it with any framework or development setup by running the `withfs` server separately.
118
+ While `@jisan901/fs-browser` includes a Vite plugin, you can use it with any framework or development setup by running the `withfs` server separately.
119
119
 
120
120
  ### React (Create React App, Next.js)
121
121
 
@@ -138,7 +138,7 @@ npm start
138
138
 
139
139
  ```javascript
140
140
  // App.js
141
- import { readFile, writeFile } from 'fs-browser';
141
+ import { readFile, writeFile } from '@jisan901/fs-browser';
142
142
 
143
143
  function App() {
144
144
  const handleSave = async () => {
@@ -153,7 +153,7 @@ function App() {
153
153
 
154
154
  ```javascript
155
155
  // lib/fs.js
156
- import fs from 'fs-browser';
156
+ import fs from '@jisan901/fs-browser';
157
157
 
158
158
  export default fs.configure({
159
159
  apiBase: 'http://localhost:5001/api/fs'
@@ -202,7 +202,7 @@ npm run serve
202
202
  ```vue
203
203
  <!-- App.vue -->
204
204
  <script setup>
205
- import { readFile, writeFile } from 'fs-browser';
205
+ import { readFile, writeFile } from '@jisan901/fs-browser';
206
206
 
207
207
  const saveFile = async () => {
208
208
  await writeFile('vue-data.json', { framework: 'Vue' });
@@ -218,7 +218,7 @@ const saveFile = async () => {
218
218
 
219
219
  ```javascript
220
220
  // composables/useFs.js
221
- import fs from 'fs-browser';
221
+ import fs from '@jisan901/fs-browser';
222
222
 
223
223
  export const useFs = () => {
224
224
  return fs.configure({
@@ -274,7 +274,7 @@ ng serve
274
274
 
275
275
  ```typescript
276
276
  // app.component.ts
277
- import { readFile, writeFile } from 'fs-browser';
277
+ import { readFile, writeFile } from '@jisan901/fs-browser';
278
278
 
279
279
  export class AppComponent {
280
280
  async saveFile() {
@@ -315,7 +315,7 @@ npm run dev
315
315
  ```svelte
316
316
  <!-- +page.svelte -->
317
317
  <script>
318
- import { readFile, writeFile } from 'fs-browser';
318
+ import { readFile, writeFile } from '@jisan901/fs-browser';
319
319
 
320
320
  async function saveFile() {
321
321
  await writeFile('svelte-data.txt', 'Hello SvelteKit!');
@@ -360,7 +360,7 @@ npm run dev
360
360
  // src/pages/index.astro
361
361
  ---
362
362
  <script>
363
- import { writeFile } from 'fs-browser';
363
+ import { writeFile } from '@jisan901/fs-browser';
364
364
 
365
365
  document.querySelector('#save')?.addEventListener('click', async () => {
366
366
  await writeFile('astro-data.json', { framework: 'Astro' });
@@ -378,7 +378,7 @@ npm run dev
378
378
  <!DOCTYPE html>
379
379
  <html>
380
380
  <head>
381
- <title>fs-browser Demo</title>
381
+ <title>@jisan901/fs-browser Demo</title>
382
382
  </head>
383
383
  <body>
384
384
  <button id="save">Save File</button>
@@ -386,7 +386,7 @@ npm run dev
386
386
  <pre id="output"></pre>
387
387
 
388
388
  <script type="module">
389
- import fs from 'https://cdn.jsdelivr.net/npm/fs-browser/src/index.js';
389
+ import fs from 'https://cdn.jsdelivr.net/npm/@jisan901/fs-browser/src/index.js';
390
390
 
391
391
  // Configure to point to your fs server
392
392
  const myFs = fs.configure({
@@ -399,7 +399,7 @@ npm run dev
399
399
  };
400
400
 
401
401
  document.getElementById('read').onclick = async () => {
402
- const content = await myFs.readFile('demo.txt');
402
+ const content = await myFs.readFile('demo.txt', {encoding: 'utf-8'});
403
403
  document.getElementById('output').textContent = content;
404
404
  };
405
405
  </script>
@@ -448,7 +448,7 @@ npx parcel index.html
448
448
 
449
449
  ```javascript
450
450
  // Configure in your JS
451
- import { configure } from 'fs-browser';
451
+ import { configure } from '@jisan901/fs-browser';
452
452
 
453
453
  configure({ apiBase: 'http://localhost:5001/api/fs' });
454
454
  ```
@@ -493,7 +493,7 @@ Set API base via environment variables:
493
493
 
494
494
  ```javascript
495
495
  // config/fs.js
496
- import fs from 'fs-browser';
496
+ import fs from '@jisan901/fs-browser';
497
497
 
498
498
  const API_BASE = import.meta.env.VITE_FS_API_BASE ||
499
499
  process.env.REACT_APP_FS_API_BASE ||
@@ -517,8 +517,8 @@ FROM node:18
517
517
 
518
518
  WORKDIR /app
519
519
 
520
- # Install fs-browser globally
521
- RUN npm install -g fs-browser
520
+ # Install @jisan901/fs-browser globally
521
+ RUN npm install -g @jisan901/fs-browser
522
522
 
523
523
  # Copy your built frontend
524
524
  COPY ./dist ./dist
@@ -584,7 +584,7 @@ services:
584
584
  Full TypeScript definitions included:
585
585
 
586
586
  ```typescript
587
- import { readFile, writeFile, Stats, Dirent } from 'fs-browser';
587
+ import { readFile, writeFile, Stats, Dirent } from '@jisan901/fs-browser';
588
588
 
589
589
  const content: string = await readFile('test.txt', 'utf8');
590
590
  const stats: Stats = await stat('test.txt');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jisan901/fs-browser",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Browser-compatible filesystem API with Vite plugin support",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -2,418 +2,527 @@
2
2
  * Shared FS API handlers for both Vite plugin and withfs CLI
3
3
  */
4
4
 
5
- import fs from 'fs/promises';
6
- import path from 'path';
5
+ import fs from "fs/promises";
6
+ import path from "path";
7
7
 
8
8
  /**
9
9
  * Helper to read raw body from request
10
10
  */
11
- export const getRawBody = (req) => {
12
- return new Promise((resolve, reject) => {
13
- const chunks = [];
14
- req.on('data', (chunk) => chunks.push(chunk));
15
- req.on('end', () => resolve(Buffer.concat(chunks)));
16
- req.on('error', reject);
17
- });
11
+ export const getRawBody = req => {
12
+ return new Promise((resolve, reject) => {
13
+ const chunks = [];
14
+ req.on("data", chunk => chunks.push(chunk));
15
+ req.on("end", () => resolve(Buffer.concat(chunks)));
16
+ req.on("error", reject);
17
+ });
18
18
  };
19
19
 
20
20
  /**
21
21
  * Helper to parse query string
22
22
  */
23
- export const parseQuery = (url) => {
24
- const queryString = url.split('?')[1];
25
- if (!queryString) return {};
26
- return Object.fromEntries(new URLSearchParams(queryString));
23
+ export const parseQuery = url => {
24
+ const queryString = url.split("?")[1];
25
+ if (!queryString) return {};
26
+ return Object.fromEntries(new URLSearchParams(queryString));
27
27
  };
28
28
 
29
29
  /**
30
30
  * Create path resolver with base directory restriction
31
31
  */
32
- export const createPathResolver = (baseDir) => {
33
- return (filePath) => {
34
- // Remove leading slash to treat all paths as relative
35
- const normalizedPath = filePath.startsWith('/') ? filePath.slice(1) : filePath;
36
- const resolved = path.resolve(baseDir, normalizedPath);
37
- if (!resolved.startsWith(baseDir)) {
38
- throw new Error('Invalid path: outside base directory');
39
- }
40
- return resolved;
41
- };
32
+ export const createPathResolver = baseDir => {
33
+ return filePath => {
34
+ // Remove leading slash to treat all paths as relative
35
+ const normalizedPath = filePath.startsWith("/")
36
+ ? filePath.slice(1)
37
+ : filePath;
38
+ const resolved = path.resolve(baseDir, normalizedPath);
39
+ if (!resolved.startsWith(baseDir)) {
40
+ throw new Error("Invalid path: outside base directory");
41
+ }
42
+ return resolved;
43
+ };
42
44
  };
43
45
 
46
+
44
47
  /**
45
48
  * Create FS API handlers
46
49
  * @param {string} baseDir - Base directory for file operations
47
50
  * @returns {Object} - Handler functions mapped by route key
48
51
  */
49
- export const createFsHandlers = (baseDir) => {
50
- const resolvePath = createPathResolver(baseDir);
51
-
52
- return {
53
- // Root endpoint
54
- 'GET /': async (req, res) => {
55
- res.writeHead(200, { 'Content-Type': 'application/json' });
56
- res.end(JSON.stringify({
57
- message: 'fs-browser API',
58
- baseDirectory: baseDir
59
- }));
60
- },
61
-
62
- // List all methods
63
- 'GET /methods': async (req, res) => {
64
- res.writeHead(200, { 'Content-Type': 'application/json' });
65
- res.end(JSON.stringify({
66
- methods: [
67
- 'readFile', 'writeFile', 'appendFile', 'copyFile',
68
- 'readdir', 'mkdir', 'rmdir', 'rm',
69
- 'rename', 'unlink',
70
- 'stat', 'lstat', 'readlink', 'realpath'
71
- ]
72
- }));
73
- },
74
-
75
- // Read file
76
- 'GET /readFile': async (req, res) => {
77
- const query = parseQuery(req.url);
78
- const { path: filePath, encoding = 'utf8' } = query;
79
- const fullPath = resolvePath(filePath);
80
- const data = await fs.readFile(fullPath, encoding);
81
- res.writeHead(200, { 'Content-Type': 'application/json' });
82
- res.end(JSON.stringify({ data }));
83
- },
84
-
85
- // Read directory
86
- 'GET /readdir': async (req, res) => {
87
- const query = parseQuery(req.url);
88
- const { path: dirPath = '.', withFileTypes = 'false' } = query;
89
- const fullPath = resolvePath(dirPath);
90
- const files = await fs.readdir(fullPath, {
91
- withFileTypes: withFileTypes === 'true'
92
- });
93
- res.writeHead(200, { 'Content-Type': 'application/json' });
94
- res.end(JSON.stringify({ files }));
95
- },
96
-
97
- // Get file stats
98
- 'GET /stat': async (req, res) => {
99
- const query = parseQuery(req.url);
100
- const { path: filePath } = query;
101
- const fullPath = resolvePath(filePath);
102
- const stats = await fs.stat(fullPath);
103
- res.writeHead(200, { 'Content-Type': 'application/json' });
104
- res.end(JSON.stringify({
105
- stats: {
106
- isFile: stats.isFile(),
107
- isDirectory: stats.isDirectory(),
108
- isSymbolicLink: stats.isSymbolicLink(),
109
- size: stats.size,
110
- mode: stats.mode,
111
- mtime: stats.mtime,
112
- atime: stats.atime,
113
- ctime: stats.ctime,
114
- birthtime: stats.birthtime
115
- }
116
- }));
117
- },
118
-
119
- // Get file stats (no symlink follow)
120
- 'GET /lstat': async (req, res) => {
121
- const query = parseQuery(req.url);
122
- const { path: filePath } = query;
123
- const fullPath = resolvePath(filePath);
124
- const stats = await fs.lstat(fullPath);
125
- res.writeHead(200, { 'Content-Type': 'application/json' });
126
- res.end(JSON.stringify({
127
- stats: {
128
- isFile: stats.isFile(),
129
- isDirectory: stats.isDirectory(),
130
- isSymbolicLink: stats.isSymbolicLink(),
131
- size: stats.size,
132
- mode: stats.mode,
133
- mtime: stats.mtime,
134
- atime: stats.atime,
135
- ctime: stats.ctime,
136
- birthtime: stats.birthtime
137
- }
138
- }));
139
- },
140
-
141
- // Get real path
142
- 'GET /realpath': async (req, res) => {
143
- const query = parseQuery(req.url);
144
- const { path: filePath } = query;
145
- const fullPath = resolvePath(filePath);
146
- const realPath = await fs.realpath(fullPath);
147
- res.writeHead(200, { 'Content-Type': 'application/json' });
148
- res.end(JSON.stringify({ realPath }));
149
- },
150
-
151
- // Read symlink
152
- 'GET /readlink': async (req, res) => {
153
- const query = parseQuery(req.url);
154
- const { path: linkPath } = query;
155
- const fullPath = resolvePath(linkPath);
156
- const target = await fs.readlink(fullPath);
157
- res.writeHead(200, { 'Content-Type': 'application/json' });
158
- res.end(JSON.stringify({ target }));
159
- },
160
-
161
- // Write file
162
- 'POST /writeFile': async (req, res) => {
163
- const contentType = req.headers['content-type'] || '';
164
- const query = parseQuery(req.url);
165
- let filePath = query.path;
166
- let writeData;
167
- let type;
168
-
169
- const rawBody = await getRawBody(req);
170
-
171
- // Handle binary content types
172
- if (contentType.includes('application/octet-stream') ||
173
- contentType.includes('image/') ||
174
- contentType.includes('video/') ||
175
- contentType.includes('audio/') ||
176
- contentType.includes('application/pdf')) {
177
- writeData = rawBody;
178
- type = 'binary';
179
- }
180
- // Handle plain text
181
- else if (contentType.includes('text/plain')) {
182
- writeData = rawBody;
183
- type = 'text';
184
- }
185
- // Handle JSON
186
- else if (contentType.includes('application/json')) {
187
- // If path is in query string, treat rawBody as the content to write
188
- if (filePath) {
189
- writeData = rawBody;
190
- type = 'json';
191
- }
192
- // Otherwise, parse as structured request body
193
- else {
194
- if (rawBody.length === 0) {
195
- res.writeHead(400, { 'Content-Type': 'application/json' });
196
- res.end(JSON.stringify({ error: 'Empty request body' }));
197
- return;
198
- }
199
-
200
- const body = JSON.parse(rawBody.toString());
201
- filePath = body.path;
202
-
203
- if (!filePath) {
204
- res.writeHead(400, { 'Content-Type': 'application/json' });
205
- res.end(JSON.stringify({ error: 'Path is required' }));
206
- return;
207
- }
208
-
209
- const { data, type: dataType = 'text', encoding = 'utf8' } = body;
210
-
211
- if (data === undefined || data === null) {
212
- res.writeHead(400, { 'Content-Type': 'application/json' });
213
- res.end(JSON.stringify({ error: 'Data is required' }));
214
- return;
215
- }
216
-
217
- type = dataType;
218
-
219
- switch (dataType) {
220
- case 'text':
221
- writeData = Buffer.from(String(data), encoding);
222
- break;
223
- case 'json':
224
- writeData = Buffer.from(JSON.stringify(data, null, 2), encoding);
225
- break;
226
- case 'buffer':
227
- writeData = Array.isArray(data) ? Buffer.from(data) : Buffer.from(data, 'base64');
228
- break;
229
- default:
230
- writeData = Buffer.from(String(data), encoding);
231
- }
52
+ export const createFsHandlers = baseDir => {
53
+ const resolvePath = createPathResolver(baseDir);
54
+
55
+ return {
56
+ // Root endpoint
57
+ "GET /": async (req, res) => {
58
+ res.writeHead(200, { "Content-Type": "application/json" });
59
+ res.end(
60
+ JSON.stringify({
61
+ message: "fs-browser API",
62
+ baseDirectory: baseDir
63
+ })
64
+ );
65
+ },
66
+
67
+ // List all methods
68
+ "GET /methods": async (req, res) => {
69
+ res.writeHead(200, { "Content-Type": "application/json" });
70
+ res.end(
71
+ JSON.stringify({
72
+ methods: [
73
+ "readFile",
74
+ "writeFile",
75
+ "appendFile",
76
+ "copyFile",
77
+ "readdir",
78
+ "mkdir",
79
+ "rmdir",
80
+ "rm",
81
+ "rename",
82
+ "unlink",
83
+ "stat",
84
+ "lstat",
85
+ "readlink",
86
+ "realpath"
87
+ ]
88
+ })
89
+ );
90
+ },
91
+
92
+ // Read file
93
+
94
+ "GET /readFile": async (req, res) => {
95
+ try {
96
+ const query = parseQuery(req.url);
97
+ const { path: filePath, encoding } = query;
98
+ const fullPath = resolvePath(filePath);
99
+
100
+
101
+ const contentType = encoding ? "text/plain" : "application/octet-stream";
102
+
103
+ // 2. Reading file WITHOUT an encoding (returns a Buffer/binary)
104
+ let options = {}
105
+ if (encoding) {options.encoding = encoding || 'utf8';}
106
+ const data = await fs.readFile(fullPath, options );
107
+
108
+ // 3. Write the headers and send the data
109
+ res.writeHead(200, { "Content-Type": contentType });
110
+ res.end(data);
111
+ } catch (err) {
112
+ res.writeHead(404, { "Content-Type": "text/plain" });
113
+ res.end("File not found or error reading file");
114
+ }
115
+ },
116
+
117
+ // Read directory
118
+ "GET /readdir": async (req, res) => {
119
+ const query = parseQuery(req.url);
120
+ const { path: dirPath = ".", withFileTypes = "false" } = query;
121
+ const fullPath = resolvePath(dirPath);
122
+ const files = await fs.readdir(fullPath, {
123
+ withFileTypes: withFileTypes === "true"
124
+ });
125
+ res.writeHead(200, { "Content-Type": "application/json" });
126
+ res.end(JSON.stringify({ files }));
127
+ },
128
+
129
+ // Get file stats
130
+ "GET /stat": async (req, res) => {
131
+ const query = parseQuery(req.url);
132
+ const { path: filePath } = query;
133
+ const fullPath = resolvePath(filePath);
134
+ const stats = await fs.stat(fullPath);
135
+ res.writeHead(200, { "Content-Type": "application/json" });
136
+ res.end(
137
+ JSON.stringify({
138
+ stats: {
139
+ isFile: stats.isFile(),
140
+ isDirectory: stats.isDirectory(),
141
+ isSymbolicLink: stats.isSymbolicLink(),
142
+ size: stats.size,
143
+ mode: stats.mode,
144
+ mtime: stats.mtime,
145
+ atime: stats.atime,
146
+ ctime: stats.ctime,
147
+ birthtime: stats.birthtime
148
+ }
149
+ })
150
+ );
151
+ },
152
+
153
+ // Get file stats (no symlink follow)
154
+ "GET /lstat": async (req, res) => {
155
+ const query = parseQuery(req.url);
156
+ const { path: filePath } = query;
157
+ const fullPath = resolvePath(filePath);
158
+ const stats = await fs.lstat(fullPath);
159
+ res.writeHead(200, { "Content-Type": "application/json" });
160
+ res.end(
161
+ JSON.stringify({
162
+ stats: {
163
+ isFile: stats.isFile(),
164
+ isDirectory: stats.isDirectory(),
165
+ isSymbolicLink: stats.isSymbolicLink(),
166
+ size: stats.size,
167
+ mode: stats.mode,
168
+ mtime: stats.mtime,
169
+ atime: stats.atime,
170
+ ctime: stats.ctime,
171
+ birthtime: stats.birthtime
172
+ }
173
+ })
174
+ );
175
+ },
176
+
177
+ // Get real path
178
+ "GET /realpath": async (req, res) => {
179
+ const query = parseQuery(req.url);
180
+ const { path: filePath } = query;
181
+ const fullPath = resolvePath(filePath);
182
+ const realPath = await fs.realpath(fullPath);
183
+ res.writeHead(200, { "Content-Type": "application/json" });
184
+ res.end(JSON.stringify({ realPath }));
185
+ },
186
+
187
+ // Read symlink
188
+ "GET /readlink": async (req, res) => {
189
+ const query = parseQuery(req.url);
190
+ const { path: linkPath } = query;
191
+ const fullPath = resolvePath(linkPath);
192
+ const target = await fs.readlink(fullPath);
193
+ res.writeHead(200, { "Content-Type": "application/json" });
194
+ res.end(JSON.stringify({ target }));
195
+ },
196
+
197
+ // Write file
198
+ "POST /writeFile": async (req, res) => {
199
+ const contentType = req.headers["content-type"] || "";
200
+ const query = parseQuery(req.url);
201
+ let filePath = query.path;
202
+ let writeData;
203
+ let type;
204
+
205
+ const rawBody = await getRawBody(req);
206
+
207
+ // Handle binary content types
208
+ if (
209
+ contentType.includes("application/octet-stream") ||
210
+ contentType.includes("image/") ||
211
+ contentType.includes("video/") ||
212
+ contentType.includes("audio/") ||
213
+ contentType.includes("application/pdf")
214
+ ) {
215
+ writeData = rawBody;
216
+ type = "binary";
217
+ }
218
+ // Handle plain text
219
+ else if (contentType.includes("text/plain")) {
220
+ writeData = rawBody;
221
+ type = "text";
222
+ }
223
+ // Handle JSON
224
+ else if (contentType.includes("application/json")) {
225
+ // If path is in query string, treat rawBody as the content to write
226
+ if (filePath) {
227
+ writeData = rawBody;
228
+ type = "json";
229
+ }
230
+ // Otherwise, parse as structured request body
231
+ else {
232
+ if (rawBody.length === 0) {
233
+ res.writeHead(400, {
234
+ "Content-Type": "application/json"
235
+ });
236
+ res.end(
237
+ JSON.stringify({ error: "Empty request body" })
238
+ );
239
+ return;
240
+ }
241
+
242
+ const body = JSON.parse(rawBody.toString());
243
+ filePath = body.path;
244
+
245
+ if (!filePath) {
246
+ res.writeHead(400, {
247
+ "Content-Type": "application/json"
248
+ });
249
+ res.end(JSON.stringify({ error: "Path is required" }));
250
+ return;
251
+ }
252
+
253
+ const {
254
+ data,
255
+ type: dataType = "text",
256
+ encoding = "utf8"
257
+ } = body;
258
+
259
+ if (data === undefined || data === null) {
260
+ res.writeHead(400, {
261
+ "Content-Type": "application/json"
262
+ });
263
+ res.end(JSON.stringify({ error: "Data is required" }));
264
+ return;
265
+ }
266
+
267
+ type = dataType;
268
+
269
+ switch (dataType) {
270
+ case "text":
271
+ writeData = Buffer.from(String(data), encoding);
272
+ break;
273
+ case "json":
274
+ writeData = Buffer.from(
275
+ JSON.stringify(data, null, 2),
276
+ encoding
277
+ );
278
+ break;
279
+ case "buffer":
280
+ writeData = Array.isArray(data)
281
+ ? Buffer.from(data)
282
+ : Buffer.from(data, "base64");
283
+ break;
284
+ default:
285
+ writeData = Buffer.from(String(data), encoding);
286
+ }
287
+ }
288
+ }
289
+ // Fallback for unknown content types
290
+ else {
291
+ writeData = rawBody;
292
+ type = "unknown";
293
+ }
294
+
295
+ // Validate we have data to write
296
+ if (!writeData || writeData.length === 0) {
297
+ res.writeHead(400, { "Content-Type": "application/json" });
298
+ res.end(JSON.stringify({ error: "No data to write" }));
299
+ return;
300
+ }
301
+
302
+ const fullPath = resolvePath(filePath);
303
+ await fs.writeFile(fullPath, writeData);
304
+
305
+ res.writeHead(200, { "Content-Type": "application/json" });
306
+ res.end(
307
+ JSON.stringify({
308
+ message: "File written successfully",
309
+ path: filePath,
310
+ type,
311
+ size: writeData.length
312
+ })
313
+ );
314
+ },
315
+
316
+ // Append file
317
+ "POST /appendFile": async (req, res) => {
318
+ const contentType = req.headers["content-type"] || "";
319
+ const query = parseQuery(req.url);
320
+ let filePath = query.path;
321
+ let appendData;
322
+ let type;
323
+
324
+ const rawBody = await getRawBody(req);
325
+
326
+ // Handle binary content types
327
+ if (
328
+ contentType.includes("application/octet-stream") ||
329
+ contentType.includes("image/") ||
330
+ contentType.includes("video/") ||
331
+ contentType.includes("audio/") ||
332
+ contentType.includes("application/pdf")
333
+ ) {
334
+ appendData = rawBody;
335
+ type = "binary";
336
+ }
337
+ // Handle plain text
338
+ else if (contentType.includes("text/plain")) {
339
+ appendData = rawBody;
340
+ type = "text";
341
+ }
342
+ // Handle JSON
343
+ else if (contentType.includes("application/json")) {
344
+ // If path is in query string, treat rawBody as the content to append
345
+ if (filePath) {
346
+ appendData = rawBody;
347
+ type = "json";
348
+ }
349
+ // Otherwise, parse as structured request body
350
+ else {
351
+ if (rawBody.length === 0) {
352
+ res.writeHead(400, {
353
+ "Content-Type": "application/json"
354
+ });
355
+ res.end(
356
+ JSON.stringify({ error: "Empty request body" })
357
+ );
358
+ return;
359
+ }
360
+
361
+ const body = JSON.parse(rawBody.toString());
362
+ filePath = body.path;
363
+
364
+ if (!filePath) {
365
+ res.writeHead(400, {
366
+ "Content-Type": "application/json"
367
+ });
368
+ res.end(JSON.stringify({ error: "Path is required" }));
369
+ return;
370
+ }
371
+
372
+ const {
373
+ data,
374
+ type: dataType = "text",
375
+ encoding = "utf8"
376
+ } = body;
377
+
378
+ if (data === undefined || data === null) {
379
+ res.writeHead(400, {
380
+ "Content-Type": "application/json"
381
+ });
382
+ res.end(JSON.stringify({ error: "Data is required" }));
383
+ return;
384
+ }
385
+
386
+ type = dataType;
387
+
388
+ switch (dataType) {
389
+ case "text":
390
+ appendData = Buffer.from(String(data), encoding);
391
+ break;
392
+ case "json":
393
+ appendData = Buffer.from(
394
+ JSON.stringify(data, null, 2),
395
+ encoding
396
+ );
397
+ break;
398
+ case "buffer":
399
+ appendData = Array.isArray(data)
400
+ ? Buffer.from(data)
401
+ : Buffer.from(data, "base64");
402
+ break;
403
+ default:
404
+ appendData = Buffer.from(String(data), encoding);
405
+ }
406
+ }
407
+ }
408
+ // Fallback
409
+ else {
410
+ appendData = rawBody;
411
+ type = "unknown";
412
+ }
413
+
414
+ // Validate we have data
415
+ if (!appendData || appendData.length === 0) {
416
+ res.writeHead(400, { "Content-Type": "application/json" });
417
+ res.end(JSON.stringify({ error: "No data to append" }));
418
+ return;
419
+ }
420
+
421
+ const fullPath = resolvePath(filePath);
422
+ await fs.appendFile(fullPath, appendData);
423
+
424
+ res.writeHead(200, { "Content-Type": "application/json" });
425
+ res.end(
426
+ JSON.stringify({
427
+ message: "Data appended successfully",
428
+ path: filePath,
429
+ type,
430
+ size: appendData.length
431
+ })
432
+ );
433
+ },
434
+
435
+ // Copy file
436
+ "POST /copyFile": async (req, res) => {
437
+ const rawBody = await getRawBody(req);
438
+ const { src, dest, flags = 0 } = JSON.parse(rawBody.toString());
439
+ const srcPath = resolvePath(src);
440
+ const destPath = resolvePath(dest);
441
+ await fs.copyFile(srcPath, destPath, flags);
442
+ res.writeHead(200, { "Content-Type": "application/json" });
443
+ res.end(
444
+ JSON.stringify({
445
+ message: "File copied successfully",
446
+ src,
447
+ dest
448
+ })
449
+ );
450
+ },
451
+
452
+ // Create directory
453
+ "POST /mkdir": async (req, res) => {
454
+ const rawBody = await getRawBody(req);
455
+ const { path: dirPath, recursive = true } = JSON.parse(
456
+ rawBody.toString()
457
+ );
458
+ const fullPath = resolvePath(dirPath);
459
+ await fs.mkdir(fullPath, { recursive });
460
+ res.writeHead(200, { "Content-Type": "application/json" });
461
+ res.end(
462
+ JSON.stringify({ message: "Directory created", path: dirPath })
463
+ );
464
+ },
465
+
466
+ // Remove directory
467
+ "DELETE /rmdir": async (req, res) => {
468
+ const rawBody = await getRawBody(req);
469
+ const { path: dirPath, recursive = false } = JSON.parse(
470
+ rawBody.toString()
471
+ );
472
+ const fullPath = resolvePath(dirPath);
473
+ await fs.rmdir(fullPath, { recursive });
474
+ res.writeHead(200, { "Content-Type": "application/json" });
475
+ res.end(
476
+ JSON.stringify({ message: "Directory removed", path: dirPath })
477
+ );
478
+ },
479
+
480
+ // Remove file or directory
481
+ "DELETE /rm": async (req, res) => {
482
+ const rawBody = await getRawBody(req);
483
+ const {
484
+ path: targetPath,
485
+ recursive = false,
486
+ force = false
487
+ } = JSON.parse(rawBody.toString());
488
+ const fullPath = resolvePath(targetPath);
489
+ await fs.rm(fullPath, { recursive, force });
490
+ res.writeHead(200, { "Content-Type": "application/json" });
491
+ res.end(
492
+ JSON.stringify({
493
+ message: "Removed successfully",
494
+ path: targetPath
495
+ })
496
+ );
497
+ },
498
+
499
+ // Rename
500
+ "PUT /rename": async (req, res) => {
501
+ const rawBody = await getRawBody(req);
502
+ const { oldPath, newPath } = JSON.parse(rawBody.toString());
503
+ const oldFullPath = resolvePath(oldPath);
504
+ const newFullPath = resolvePath(newPath);
505
+ await fs.rename(oldFullPath, newFullPath);
506
+ res.writeHead(200, { "Content-Type": "application/json" });
507
+ res.end(
508
+ JSON.stringify({
509
+ message: "Renamed successfully",
510
+ oldPath,
511
+ newPath
512
+ })
513
+ );
514
+ },
515
+
516
+ // Delete file
517
+ "DELETE /unlink": async (req, res) => {
518
+ const rawBody = await getRawBody(req);
519
+ const { path: filePath } = JSON.parse(rawBody.toString());
520
+ const fullPath = resolvePath(filePath);
521
+ await fs.unlink(fullPath);
522
+ res.writeHead(200, { "Content-Type": "application/json" });
523
+ res.end(
524
+ JSON.stringify({ message: "File deleted", path: filePath })
525
+ );
232
526
  }
233
- }
234
- // Fallback for unknown content types
235
- else {
236
- writeData = rawBody;
237
- type = 'unknown';
238
- }
239
-
240
- // Validate we have data to write
241
- if (!writeData || writeData.length === 0) {
242
- res.writeHead(400, { 'Content-Type': 'application/json' });
243
- res.end(JSON.stringify({ error: 'No data to write' }));
244
- return;
245
- }
246
-
247
- const fullPath = resolvePath(filePath);
248
- await fs.writeFile(fullPath, writeData);
249
-
250
- res.writeHead(200, { 'Content-Type': 'application/json' });
251
- res.end(JSON.stringify({
252
- message: 'File written successfully',
253
- path: filePath,
254
- type,
255
- size: writeData.length
256
- }));
257
- },
258
-
259
- // Append file
260
- 'POST /appendFile': async (req, res) => {
261
- const contentType = req.headers['content-type'] || '';
262
- const query = parseQuery(req.url);
263
- let filePath = query.path;
264
- let appendData;
265
- let type;
266
-
267
- const rawBody = await getRawBody(req);
268
-
269
- // Handle binary content types
270
- if (contentType.includes('application/octet-stream') ||
271
- contentType.includes('image/') ||
272
- contentType.includes('video/') ||
273
- contentType.includes('audio/') ||
274
- contentType.includes('application/pdf')) {
275
- appendData = rawBody;
276
- type = 'binary';
277
- }
278
- // Handle plain text
279
- else if (contentType.includes('text/plain')) {
280
- appendData = rawBody;
281
- type = 'text';
282
- }
283
- // Handle JSON
284
- else if (contentType.includes('application/json')) {
285
- // If path is in query string, treat rawBody as the content to append
286
- if (filePath) {
287
- appendData = rawBody;
288
- type = 'json';
289
- }
290
- // Otherwise, parse as structured request body
291
- else {
292
- if (rawBody.length === 0) {
293
- res.writeHead(400, { 'Content-Type': 'application/json' });
294
- res.end(JSON.stringify({ error: 'Empty request body' }));
295
- return;
296
- }
297
-
298
- const body = JSON.parse(rawBody.toString());
299
- filePath = body.path;
300
-
301
- if (!filePath) {
302
- res.writeHead(400, { 'Content-Type': 'application/json' });
303
- res.end(JSON.stringify({ error: 'Path is required' }));
304
- return;
305
- }
306
-
307
- const { data, type: dataType = 'text', encoding = 'utf8' } = body;
308
-
309
- if (data === undefined || data === null) {
310
- res.writeHead(400, { 'Content-Type': 'application/json' });
311
- res.end(JSON.stringify({ error: 'Data is required' }));
312
- return;
313
- }
314
-
315
- type = dataType;
316
-
317
- switch (dataType) {
318
- case 'text':
319
- appendData = Buffer.from(String(data), encoding);
320
- break;
321
- case 'json':
322
- appendData = Buffer.from(JSON.stringify(data, null, 2), encoding);
323
- break;
324
- case 'buffer':
325
- appendData = Array.isArray(data) ? Buffer.from(data) : Buffer.from(data, 'base64');
326
- break;
327
- default:
328
- appendData = Buffer.from(String(data), encoding);
329
- }
330
- }
331
- }
332
- // Fallback
333
- else {
334
- appendData = rawBody;
335
- type = 'unknown';
336
- }
337
-
338
- // Validate we have data
339
- if (!appendData || appendData.length === 0) {
340
- res.writeHead(400, { 'Content-Type': 'application/json' });
341
- res.end(JSON.stringify({ error: 'No data to append' }));
342
- return;
343
- }
344
-
345
- const fullPath = resolvePath(filePath);
346
- await fs.appendFile(fullPath, appendData);
347
-
348
- res.writeHead(200, { 'Content-Type': 'application/json' });
349
- res.end(JSON.stringify({
350
- message: 'Data appended successfully',
351
- path: filePath,
352
- type,
353
- size: appendData.length
354
- }));
355
- },
356
-
357
- // Copy file
358
- 'POST /copyFile': async (req, res) => {
359
- const rawBody = await getRawBody(req);
360
- const { src, dest, flags = 0 } = JSON.parse(rawBody.toString());
361
- const srcPath = resolvePath(src);
362
- const destPath = resolvePath(dest);
363
- await fs.copyFile(srcPath, destPath, flags);
364
- res.writeHead(200, { 'Content-Type': 'application/json' });
365
- res.end(JSON.stringify({ message: 'File copied successfully', src, dest }));
366
- },
367
-
368
- // Create directory
369
- 'POST /mkdir': async (req, res) => {
370
- const rawBody = await getRawBody(req);
371
- const { path: dirPath, recursive = true } = JSON.parse(rawBody.toString());
372
- const fullPath = resolvePath(dirPath);
373
- await fs.mkdir(fullPath, { recursive });
374
- res.writeHead(200, { 'Content-Type': 'application/json' });
375
- res.end(JSON.stringify({ message: 'Directory created', path: dirPath }));
376
- },
377
-
378
- // Remove directory
379
- 'DELETE /rmdir': async (req, res) => {
380
- const rawBody = await getRawBody(req);
381
- const { path: dirPath, recursive = false } = JSON.parse(rawBody.toString());
382
- const fullPath = resolvePath(dirPath);
383
- await fs.rmdir(fullPath, { recursive });
384
- res.writeHead(200, { 'Content-Type': 'application/json' });
385
- res.end(JSON.stringify({ message: 'Directory removed', path: dirPath }));
386
- },
387
-
388
- // Remove file or directory
389
- 'DELETE /rm': async (req, res) => {
390
- const rawBody = await getRawBody(req);
391
- const { path: targetPath, recursive = false, force = false } = JSON.parse(rawBody.toString());
392
- const fullPath = resolvePath(targetPath);
393
- await fs.rm(fullPath, { recursive, force });
394
- res.writeHead(200, { 'Content-Type': 'application/json' });
395
- res.end(JSON.stringify({ message: 'Removed successfully', path: targetPath }));
396
- },
397
-
398
- // Rename
399
- 'PUT /rename': async (req, res) => {
400
- const rawBody = await getRawBody(req);
401
- const { oldPath, newPath } = JSON.parse(rawBody.toString());
402
- const oldFullPath = resolvePath(oldPath);
403
- const newFullPath = resolvePath(newPath);
404
- await fs.rename(oldFullPath, newFullPath);
405
- res.writeHead(200, { 'Content-Type': 'application/json' });
406
- res.end(JSON.stringify({ message: 'Renamed successfully', oldPath, newPath }));
407
- },
408
-
409
- // Delete file
410
- 'DELETE /unlink': async (req, res) => {
411
- const rawBody = await getRawBody(req);
412
- const { path: filePath } = JSON.parse(rawBody.toString());
413
- const fullPath = resolvePath(filePath);
414
- await fs.unlink(fullPath);
415
- res.writeHead(200, { 'Content-Type': 'application/json' });
416
- res.end(JSON.stringify({ message: 'File deleted', path: filePath }));
417
- }
418
- };
419
- };
527
+ };
528
+ };
package/src/index.js CHANGED
@@ -46,8 +46,8 @@ export function configure(options = {}) {
46
46
  * @param {string|Object} options - Encoding string or options object
47
47
  * @returns {Promise<string|Buffer>}
48
48
  */
49
- export async function readFile(path, options = 'utf8') {
50
- const encoding = typeof options === 'string' ? options : options?.encoding || 'utf8';
49
+ export async function readFile(path, options = '') {
50
+ const encoding = typeof options === 'string' ? options : options?.encoding || '';
51
51
  const response = await fetch(`${API_BASE}/readFile?path=${encodeURIComponent(path)}&encoding=${encoding}`);
52
52
 
53
53
  if (!response.ok) {
@@ -55,8 +55,17 @@ export async function readFile(path, options = 'utf8') {
55
55
  throw new Error(error.error?.message || 'Failed to read file');
56
56
  }
57
57
 
58
- const { data } = await response.json();
59
- return data;
58
+ const contentType = response.headers.get("content-type");
59
+
60
+ if (contentType?.includes("text/plain")) {
61
+ const data = await response.text();
62
+ return data;
63
+ } else {
64
+ const buffer = await response.arrayBuffer();
65
+ return buffer;
66
+ }
67
+
68
+ return response;
60
69
  }
61
70
 
62
71
  /**