@jisan901/fs-browser 1.0.1 → 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
@@ -47,7 +47,7 @@ import { readFile, writeFile, mkdir } from '@jisan901/fs-browser';
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
@@ -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>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jisan901/fs-browser",
3
- "version": "1.0.1",
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
  /**