@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 +2 -2
- package/package.json +1 -1
- package/plugin/fs-handlers.js +502 -393
- package/src/index.js +13 -4
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
package/plugin/fs-handlers.js
CHANGED
|
@@ -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
|
|
6
|
-
import path from
|
|
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 =
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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 =
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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 =
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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 =
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
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 = '
|
|
50
|
-
const encoding = typeof options === 'string' ? options : options?.encoding || '
|
|
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
|
|
59
|
-
|
|
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
|
/**
|