file-share-mcp 1.0.2 → 1.0.5
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/dist/gui.js +71 -33
- package/dist/mcp-server.js +107 -20
- package/dist/utils/file-manager.js +59 -30
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/public/assets/index-CH-j1g0K.js +69 -0
- package/public/assets/index-CXzfXp4f.css +1 -0
- package/public/index.html +2 -2
- package/public/assets/index-CD6a3a8W.js +0 -69
- package/public/assets/index-hXhXe8uC.css +0 -1
package/dist/gui.js
CHANGED
|
@@ -4,50 +4,42 @@ import cors from 'cors';
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import fs from 'fs';
|
|
7
|
+
import os from 'os';
|
|
7
8
|
import axios from 'axios';
|
|
8
9
|
import { setupLogging } from './utils/logger.js';
|
|
9
10
|
import { getConfig, saveConfig } from './utils/config-manager.js';
|
|
10
|
-
import { getSharedFiles, validateSafePath, deleteFile } from './utils/file-manager.js';
|
|
11
|
+
import { getSharedFiles, validateSafePath, deleteFile, createDirectory, moveFile, deleteDirectory } from './utils/file-manager.js';
|
|
11
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
13
|
const __dirname = path.dirname(__filename);
|
|
13
14
|
setupLogging('GUI');
|
|
14
15
|
const app = express();
|
|
15
16
|
app.use(cors());
|
|
16
17
|
app.use(express.json());
|
|
17
|
-
|
|
18
|
-
// but we want to be sure no other library writes to stdout.
|
|
19
|
-
// Multer configuration for file uploads
|
|
20
|
-
const storage = multer.diskStorage({
|
|
21
|
-
destination: (req, file, cb) => {
|
|
22
|
-
const config = getConfig();
|
|
23
|
-
if (!fs.existsSync(config.shared_folder_path)) {
|
|
24
|
-
fs.mkdirSync(config.shared_folder_path, { recursive: true });
|
|
25
|
-
}
|
|
26
|
-
cb(null, config.shared_folder_path);
|
|
27
|
-
},
|
|
28
|
-
filename: (req, file, cb) => {
|
|
29
|
-
cb(null, file.originalname);
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
const upload = multer({ storage });
|
|
18
|
+
const upload = multer({ dest: os.tmpdir() });
|
|
33
19
|
// Endpoints
|
|
34
20
|
app.get('/proxy/health', (req, res) => {
|
|
35
21
|
res.json({ status: 'healthy' });
|
|
36
22
|
});
|
|
37
23
|
app.get('/api/files', (req, res) => {
|
|
38
24
|
try {
|
|
39
|
-
const
|
|
40
|
-
|
|
25
|
+
const relativePath = req.query.path || '';
|
|
26
|
+
const entries = getSharedFiles(relativePath);
|
|
27
|
+
res.json(entries);
|
|
41
28
|
}
|
|
42
29
|
catch (error) {
|
|
43
30
|
res.status(500).json({ error: error.message });
|
|
44
31
|
}
|
|
45
32
|
});
|
|
46
|
-
app.get('/api/download
|
|
33
|
+
app.get('/api/download', async (req, res) => {
|
|
47
34
|
try {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
35
|
+
const relativePath = req.query.path || '';
|
|
36
|
+
const safePath = validateSafePath(relativePath);
|
|
37
|
+
if (fs.existsSync(safePath) && fs.statSync(safePath).isFile()) {
|
|
38
|
+
res.download(safePath, path.basename(safePath), (err) => {
|
|
39
|
+
if (err && !res.headersSent) {
|
|
40
|
+
res.status(500).send('Download failed');
|
|
41
|
+
}
|
|
42
|
+
});
|
|
51
43
|
}
|
|
52
44
|
else {
|
|
53
45
|
res.status(404).send('File not found');
|
|
@@ -57,9 +49,10 @@ app.get('/api/download/:filename', (req, res) => {
|
|
|
57
49
|
res.status(400).send(error.message);
|
|
58
50
|
}
|
|
59
51
|
});
|
|
60
|
-
app.delete('/api/files
|
|
52
|
+
app.delete('/api/files', (req, res) => {
|
|
61
53
|
try {
|
|
62
|
-
|
|
54
|
+
const relativePath = req.query.path || '';
|
|
55
|
+
deleteFile(relativePath);
|
|
63
56
|
res.json({ message: 'File deleted successfully' });
|
|
64
57
|
}
|
|
65
58
|
catch (error) {
|
|
@@ -68,13 +61,14 @@ app.delete('/api/files/:filename', (req, res) => {
|
|
|
68
61
|
});
|
|
69
62
|
const PREVIEWABLE_EXTENSIONS = ['.txt', '.md'];
|
|
70
63
|
const PREVIEW_MAX_SIZE = 1024 * 1024; // 1 MB
|
|
71
|
-
app.get('/api/preview
|
|
64
|
+
app.get('/api/preview', (req, res) => {
|
|
72
65
|
try {
|
|
73
|
-
const
|
|
66
|
+
const relativePath = req.query.path || '';
|
|
67
|
+
const ext = path.extname(relativePath).toLowerCase();
|
|
74
68
|
if (!PREVIEWABLE_EXTENSIONS.includes(ext)) {
|
|
75
69
|
return res.status(400).json({ error: 'Preview not supported for this file type' });
|
|
76
70
|
}
|
|
77
|
-
const safePath = validateSafePath(
|
|
71
|
+
const safePath = validateSafePath(relativePath);
|
|
78
72
|
if (!fs.existsSync(safePath)) {
|
|
79
73
|
return res.status(404).json({ error: 'File not found' });
|
|
80
74
|
}
|
|
@@ -90,11 +84,56 @@ app.get('/api/preview/:filename', (req, res) => {
|
|
|
90
84
|
}
|
|
91
85
|
});
|
|
92
86
|
app.post('/api/upload', upload.single('file'), (req, res) => {
|
|
93
|
-
if (req.file)
|
|
94
|
-
res.json({
|
|
87
|
+
if (!req.file)
|
|
88
|
+
return res.status(400).json({ error: 'No file uploaded' });
|
|
89
|
+
const targetPath = req.body.targetPath || '';
|
|
90
|
+
try {
|
|
91
|
+
const dir = validateSafePath(targetPath);
|
|
92
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
93
|
+
const dest = path.join(dir, path.basename(req.file.originalname));
|
|
94
|
+
fs.renameSync(req.file.path, dest);
|
|
95
|
+
res.json({ message: 'File uploaded successfully', filename: path.basename(req.file.originalname) });
|
|
95
96
|
}
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
catch (err) {
|
|
98
|
+
try {
|
|
99
|
+
fs.unlinkSync(req.file.path);
|
|
100
|
+
}
|
|
101
|
+
catch { }
|
|
102
|
+
res.status(400).json({ error: err.message });
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
app.delete('/api/folder', (req, res) => {
|
|
106
|
+
try {
|
|
107
|
+
const relativePath = req.query.path || '';
|
|
108
|
+
deleteDirectory(relativePath);
|
|
109
|
+
res.json({ message: 'Directory deleted successfully' });
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
res.status(400).json({ error: error.message });
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
app.post('/api/mkdir', (req, res) => {
|
|
116
|
+
try {
|
|
117
|
+
const { path: dirPath } = req.body;
|
|
118
|
+
if (!dirPath)
|
|
119
|
+
return res.status(400).json({ error: 'path is required' });
|
|
120
|
+
createDirectory(dirPath);
|
|
121
|
+
res.json({ message: 'Directory created successfully' });
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
res.status(400).json({ error: error.message });
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
app.post('/api/move', (req, res) => {
|
|
128
|
+
try {
|
|
129
|
+
const { from, to } = req.body;
|
|
130
|
+
if (!from || !to)
|
|
131
|
+
return res.status(400).json({ error: 'from and to are required' });
|
|
132
|
+
moveFile(from, to);
|
|
133
|
+
res.json({ message: 'File moved successfully' });
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
res.status(400).json({ error: error.message });
|
|
98
137
|
}
|
|
99
138
|
});
|
|
100
139
|
app.get('/api/config', (req, res) => {
|
|
@@ -155,7 +194,6 @@ async function registerWithProxy() {
|
|
|
155
194
|
if (!config.PROXY_URL || !config.PROXY_APP_PATH)
|
|
156
195
|
return null;
|
|
157
196
|
try {
|
|
158
|
-
// Check if proxy is up
|
|
159
197
|
await axios.get(`${config.PROXY_URL}/proxy/health`, { timeout: 2000 });
|
|
160
198
|
const response = await axios.post(`${config.PROXY_URL}/proxy/register`, {
|
|
161
199
|
path: config.PROXY_APP_PATH,
|
package/dist/mcp-server.js
CHANGED
|
@@ -9,7 +9,7 @@ import { fileURLToPath } from 'url';
|
|
|
9
9
|
import fs from 'fs';
|
|
10
10
|
import { setupLogging } from './utils/logger.js';
|
|
11
11
|
import { getConfig } from './utils/config-manager.js';
|
|
12
|
-
import { getSharedFiles, validateSafePath, writeSharedFile, copyToSharedFolder } from './utils/file-manager.js';
|
|
12
|
+
import { getSharedFiles, validateSafePath, writeSharedFile, copyToSharedFolder, createDirectory, deleteDirectory, moveFile, } from './utils/file-manager.js';
|
|
13
13
|
import { VERSION } from './version.js';
|
|
14
14
|
const __filename = fileURLToPath(import.meta.url);
|
|
15
15
|
const __dirname = path.dirname(__filename);
|
|
@@ -99,10 +99,15 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
99
99
|
},
|
|
100
100
|
{
|
|
101
101
|
name: 'list_shared_files',
|
|
102
|
-
description: 'List
|
|
102
|
+
description: 'List files and directories in the shared folder. Use the optional path parameter to navigate into a subdirectory.',
|
|
103
103
|
inputSchema: {
|
|
104
104
|
type: 'object',
|
|
105
|
-
properties: {
|
|
105
|
+
properties: {
|
|
106
|
+
path: {
|
|
107
|
+
type: 'string',
|
|
108
|
+
description: 'Relative path of the subdirectory to list (e.g. "docs/reports"). Defaults to the root shared folder.',
|
|
109
|
+
},
|
|
110
|
+
},
|
|
106
111
|
},
|
|
107
112
|
},
|
|
108
113
|
{
|
|
@@ -125,12 +130,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
125
130
|
inputSchema: {
|
|
126
131
|
type: 'object',
|
|
127
132
|
properties: {
|
|
128
|
-
|
|
133
|
+
path: {
|
|
129
134
|
type: 'string',
|
|
130
|
-
description: '
|
|
135
|
+
description: 'Relative path to the file in the shared folder (e.g. "docs/notes.md").',
|
|
131
136
|
},
|
|
132
137
|
},
|
|
133
|
-
required: ['
|
|
138
|
+
required: ['path'],
|
|
134
139
|
},
|
|
135
140
|
},
|
|
136
141
|
{
|
|
@@ -147,6 +152,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
147
152
|
type: 'string',
|
|
148
153
|
description: 'Text content to write into the file.',
|
|
149
154
|
},
|
|
155
|
+
path: {
|
|
156
|
+
type: 'string',
|
|
157
|
+
description: 'Target subdirectory relative to the shared folder (e.g. "docs/reports"). The directory is created if it does not exist.',
|
|
158
|
+
},
|
|
150
159
|
},
|
|
151
160
|
required: ['filename', 'content'],
|
|
152
161
|
},
|
|
@@ -165,10 +174,60 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
165
174
|
type: 'string',
|
|
166
175
|
description: 'Optional target filename in the shared folder. Defaults to the source file basename.',
|
|
167
176
|
},
|
|
177
|
+
path: {
|
|
178
|
+
type: 'string',
|
|
179
|
+
description: 'Target subdirectory relative to the shared folder (e.g. "exports"). The directory is created if it does not exist.',
|
|
180
|
+
},
|
|
168
181
|
},
|
|
169
182
|
required: ['source_path'],
|
|
170
183
|
},
|
|
171
184
|
},
|
|
185
|
+
{
|
|
186
|
+
name: 'create_directory',
|
|
187
|
+
description: 'Create a new subdirectory inside the shared folder.',
|
|
188
|
+
inputSchema: {
|
|
189
|
+
type: 'object',
|
|
190
|
+
properties: {
|
|
191
|
+
path: {
|
|
192
|
+
type: 'string',
|
|
193
|
+
description: 'Relative path of the directory to create (e.g. "docs/reports"). Parent directories are created automatically.',
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
required: ['path'],
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
name: 'delete_directory',
|
|
201
|
+
description: 'Delete an empty subdirectory from the shared folder. Fails if the directory contains files or subdirectories.',
|
|
202
|
+
inputSchema: {
|
|
203
|
+
type: 'object',
|
|
204
|
+
properties: {
|
|
205
|
+
path: {
|
|
206
|
+
type: 'string',
|
|
207
|
+
description: 'Relative path of the directory to delete (e.g. "docs/drafts").',
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
required: ['path'],
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
name: 'move_file',
|
|
215
|
+
description: 'Move or rename a file within the shared folder.',
|
|
216
|
+
inputSchema: {
|
|
217
|
+
type: 'object',
|
|
218
|
+
properties: {
|
|
219
|
+
from: {
|
|
220
|
+
type: 'string',
|
|
221
|
+
description: 'Relative path of the source file (e.g. "report.md" or "docs/draft.md").',
|
|
222
|
+
},
|
|
223
|
+
to: {
|
|
224
|
+
type: 'string',
|
|
225
|
+
description: 'Relative path of the destination (e.g. "archive/report.md").',
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
required: ['from', 'to'],
|
|
229
|
+
},
|
|
230
|
+
},
|
|
172
231
|
],
|
|
173
232
|
};
|
|
174
233
|
});
|
|
@@ -183,15 +242,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
183
242
|
};
|
|
184
243
|
}
|
|
185
244
|
case 'list_shared_files': {
|
|
186
|
-
const
|
|
187
|
-
|
|
245
|
+
const { path: relativePath } = z.object({ path: z.string().optional() }).parse(args);
|
|
246
|
+
const entries = getSharedFiles(relativePath ?? '');
|
|
247
|
+
if (entries.length === 0) {
|
|
248
|
+
const location = relativePath ? `"${relativePath}"` : 'the shared folder';
|
|
188
249
|
return {
|
|
189
|
-
content: [{ type: 'text', text:
|
|
250
|
+
content: [{ type: 'text', text: `${location} is empty.` }],
|
|
190
251
|
};
|
|
191
252
|
}
|
|
192
|
-
const
|
|
253
|
+
const dirs = entries.filter(e => e.type === 'directory').map(e => `📁 ${e.name}/`);
|
|
254
|
+
const files = entries.filter(e => e.type === 'file').map(e => `📄 ${e.name} (${((e.size ?? 0) / 1024).toFixed(2)} KB)`);
|
|
255
|
+
const list = [...dirs, ...files].join('\n');
|
|
256
|
+
const location = relativePath ? `"${relativePath}"` : 'shared folder';
|
|
193
257
|
return {
|
|
194
|
-
content: [{ type: 'text', text: `
|
|
258
|
+
content: [{ type: 'text', text: `Contents of ${location}:\n${list}` }],
|
|
195
259
|
};
|
|
196
260
|
}
|
|
197
261
|
case 'share_file': {
|
|
@@ -201,18 +265,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
201
265
|
};
|
|
202
266
|
}
|
|
203
267
|
case 'preview_file': {
|
|
204
|
-
const {
|
|
205
|
-
const ext = path.extname(
|
|
268
|
+
const { path: relativePath } = z.object({ path: z.string() }).parse(args);
|
|
269
|
+
const ext = path.extname(relativePath).toLowerCase();
|
|
206
270
|
if (!['.txt', '.md'].includes(ext)) {
|
|
207
271
|
return {
|
|
208
272
|
content: [{ type: 'text', text: `Error: Preview is only supported for .txt and .md files.` }],
|
|
209
273
|
isError: true,
|
|
210
274
|
};
|
|
211
275
|
}
|
|
212
|
-
const safePath = validateSafePath(
|
|
276
|
+
const safePath = validateSafePath(relativePath);
|
|
213
277
|
if (!fs.existsSync(safePath)) {
|
|
214
278
|
return {
|
|
215
|
-
content: [{ type: 'text', text: `Error: File "${
|
|
279
|
+
content: [{ type: 'text', text: `Error: File "${relativePath}" not found in shared folder.` }],
|
|
216
280
|
isError: true,
|
|
217
281
|
};
|
|
218
282
|
}
|
|
@@ -229,25 +293,48 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
229
293
|
};
|
|
230
294
|
}
|
|
231
295
|
case 'write_file': {
|
|
232
|
-
const { filename, content } = z.object({
|
|
296
|
+
const { filename, content, path: relativePath } = z.object({
|
|
233
297
|
filename: z.string(),
|
|
234
298
|
content: z.string(),
|
|
299
|
+
path: z.string().optional(),
|
|
235
300
|
}).parse(args);
|
|
236
|
-
const dest = writeSharedFile(filename, content);
|
|
301
|
+
const dest = writeSharedFile(filename, content, relativePath ?? '');
|
|
237
302
|
return {
|
|
238
|
-
content: [{ type: 'text', text: `File "${path.basename(dest)}" written to shared folder.` }],
|
|
303
|
+
content: [{ type: 'text', text: `File "${path.basename(dest)}" written to shared folder${relativePath ? ` (${relativePath})` : ''}.` }],
|
|
239
304
|
};
|
|
240
305
|
}
|
|
241
306
|
case 'copy_file': {
|
|
242
|
-
const { source_path, filename } = z.object({
|
|
307
|
+
const { source_path, filename, path: relativePath } = z.object({
|
|
243
308
|
source_path: z.string(),
|
|
244
309
|
filename: z.string().optional(),
|
|
310
|
+
path: z.string().optional(),
|
|
245
311
|
}).parse(args);
|
|
246
|
-
const copiedName = copyToSharedFolder(source_path, filename);
|
|
312
|
+
const copiedName = copyToSharedFolder(source_path, filename, relativePath ?? '');
|
|
247
313
|
return {
|
|
248
314
|
content: [{ type: 'text', text: `File "${copiedName}" copied to shared folder.` }],
|
|
249
315
|
};
|
|
250
316
|
}
|
|
317
|
+
case 'create_directory': {
|
|
318
|
+
const { path: dirPath } = z.object({ path: z.string() }).parse(args);
|
|
319
|
+
createDirectory(dirPath);
|
|
320
|
+
return {
|
|
321
|
+
content: [{ type: 'text', text: `Directory "${dirPath}" created successfully.` }],
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
case 'delete_directory': {
|
|
325
|
+
const { path: dirPath } = z.object({ path: z.string() }).parse(args);
|
|
326
|
+
deleteDirectory(dirPath);
|
|
327
|
+
return {
|
|
328
|
+
content: [{ type: 'text', text: `Directory "${dirPath}" deleted successfully.` }],
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
case 'move_file': {
|
|
332
|
+
const { from, to } = z.object({ from: z.string(), to: z.string() }).parse(args);
|
|
333
|
+
moveFile(from, to);
|
|
334
|
+
return {
|
|
335
|
+
content: [{ type: 'text', text: `File moved from "${from}" to "${to}".` }],
|
|
336
|
+
};
|
|
337
|
+
}
|
|
251
338
|
default:
|
|
252
339
|
throw new Error(`Tool not found: ${name}`);
|
|
253
340
|
}
|
|
@@ -1,54 +1,83 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { getConfig } from './config-manager.js';
|
|
4
|
-
export function
|
|
4
|
+
export function validateSafePath(relativePath) {
|
|
5
5
|
const { shared_folder_path } = getConfig();
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const resolvedBase = path.resolve(shared_folder_path);
|
|
7
|
+
const fullPath = path.resolve(shared_folder_path, relativePath);
|
|
8
|
+
if (fullPath !== resolvedBase && !fullPath.startsWith(resolvedBase + path.sep)) {
|
|
9
|
+
throw new Error('Path traversal detected');
|
|
10
|
+
}
|
|
11
|
+
return fullPath;
|
|
12
|
+
}
|
|
13
|
+
export function getSharedFiles(relativePath = '') {
|
|
14
|
+
const dirPath = validateSafePath(relativePath);
|
|
15
|
+
if (!fs.existsSync(dirPath)) {
|
|
16
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
8
17
|
return [];
|
|
9
18
|
}
|
|
10
|
-
return fs.readdirSync(
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
return fs.readdirSync(dirPath).map(name => {
|
|
20
|
+
const fullPath = path.join(dirPath, name);
|
|
21
|
+
const stats = fs.statSync(fullPath);
|
|
22
|
+
if (stats.isDirectory()) {
|
|
23
|
+
return { name, type: 'directory', mtime: stats.mtime };
|
|
24
|
+
}
|
|
16
25
|
return {
|
|
17
|
-
name
|
|
26
|
+
name,
|
|
27
|
+
type: 'file',
|
|
18
28
|
size: stats.size,
|
|
19
29
|
mtime: stats.mtime,
|
|
20
|
-
extension: path.extname(
|
|
30
|
+
extension: path.extname(name).toLowerCase(),
|
|
21
31
|
};
|
|
32
|
+
}).sort((a, b) => {
|
|
33
|
+
if (a.type !== b.type)
|
|
34
|
+
return a.type === 'directory' ? -1 : 1;
|
|
35
|
+
return a.name.localeCompare(b.name);
|
|
22
36
|
});
|
|
23
37
|
}
|
|
24
|
-
export function
|
|
25
|
-
const
|
|
26
|
-
const safeName = path.basename(filename);
|
|
27
|
-
const fullPath = path.join(shared_folder_path, safeName);
|
|
28
|
-
// Extra safety check
|
|
29
|
-
if (!fullPath.startsWith(path.resolve(shared_folder_path))) {
|
|
30
|
-
throw new Error('Path traversal detected');
|
|
31
|
-
}
|
|
32
|
-
return fullPath;
|
|
33
|
-
}
|
|
34
|
-
export function deleteFile(filename) {
|
|
35
|
-
const fullPath = validateSafePath(filename);
|
|
38
|
+
export function deleteFile(relativePath) {
|
|
39
|
+
const fullPath = validateSafePath(relativePath);
|
|
36
40
|
if (fs.existsSync(fullPath)) {
|
|
37
41
|
fs.unlinkSync(fullPath);
|
|
38
42
|
}
|
|
39
43
|
}
|
|
40
|
-
export function writeSharedFile(filename, content) {
|
|
41
|
-
const
|
|
44
|
+
export function writeSharedFile(filename, content, relativePath = '') {
|
|
45
|
+
const dir = validateSafePath(relativePath);
|
|
46
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
47
|
+
const dest = path.join(dir, path.basename(filename));
|
|
42
48
|
fs.writeFileSync(dest, content, 'utf-8');
|
|
43
49
|
return dest;
|
|
44
50
|
}
|
|
45
|
-
export function copyToSharedFolder(sourcePath, filename) {
|
|
46
|
-
const targetFilename = filename
|
|
51
|
+
export function copyToSharedFolder(sourcePath, filename, relativePath = '') {
|
|
52
|
+
const targetFilename = filename ? path.basename(filename) : path.basename(sourcePath);
|
|
47
53
|
const stats = fs.statSync(sourcePath);
|
|
48
54
|
if (!stats.isFile()) {
|
|
49
55
|
throw new Error(`Source path is not a file: ${sourcePath}`);
|
|
50
56
|
}
|
|
51
|
-
const
|
|
52
|
-
fs.
|
|
53
|
-
|
|
57
|
+
const dir = validateSafePath(relativePath);
|
|
58
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
59
|
+
fs.copyFileSync(sourcePath, path.join(dir, targetFilename));
|
|
60
|
+
return relativePath ? `${relativePath}/${targetFilename}` : targetFilename;
|
|
61
|
+
}
|
|
62
|
+
export function createDirectory(relativePath) {
|
|
63
|
+
const fullPath = validateSafePath(relativePath);
|
|
64
|
+
fs.mkdirSync(fullPath, { recursive: true });
|
|
65
|
+
}
|
|
66
|
+
export function deleteDirectory(relativePath) {
|
|
67
|
+
const fullPath = validateSafePath(relativePath);
|
|
68
|
+
if (!fs.existsSync(fullPath))
|
|
69
|
+
throw new Error('Directory not found');
|
|
70
|
+
const contents = fs.readdirSync(fullPath);
|
|
71
|
+
if (contents.length > 0)
|
|
72
|
+
throw new Error('Directory is not empty');
|
|
73
|
+
fs.rmdirSync(fullPath);
|
|
74
|
+
}
|
|
75
|
+
export function moveFile(fromRelative, toRelative) {
|
|
76
|
+
const fromPath = validateSafePath(fromRelative);
|
|
77
|
+
const toPath = validateSafePath(toRelative);
|
|
78
|
+
if (!fs.existsSync(fromPath)) {
|
|
79
|
+
throw new Error(`File not found: ${fromRelative}`);
|
|
80
|
+
}
|
|
81
|
+
fs.mkdirSync(path.dirname(toPath), { recursive: true });
|
|
82
|
+
fs.renameSync(fromPath, toPath);
|
|
54
83
|
}
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '1.0.
|
|
1
|
+
export const VERSION = '1.0.5';
|