aiexecode 1.0.54 → 1.0.56
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.
Potentially problematic release.
This version of aiexecode might be problematic. Click here for more details.
- package/index.js +7 -10
- package/package.json +1 -1
- package/payload_viewer/out/404/index.html +1 -1
- package/payload_viewer/out/404.html +1 -1
- package/payload_viewer/out/index.html +1 -1
- package/payload_viewer/out/index.txt +1 -1
- package/src/ai_based/pip_package_lookup.js +3 -3
- package/src/system/ai_request.js +3 -3
- package/src/system/code_executer.js +13 -13
- package/src/system/command_loader.js +2 -2
- package/src/system/file_integrity.js +9 -9
- package/src/system/log.js +4 -4
- package/src/system/session.js +7 -3
- package/src/system/session_memory.js +52 -26
- package/src/tools/code_editor.js +21 -21
- package/src/tools/file_reader.js +3 -3
- package/src/tools/glob.js +1 -1
- package/src/tools/web_downloader.js +2 -2
- package/src/ui/components/HistoryItemDisplay.js +11 -5
- package/src/util/config.js +9 -9
- package/src/util/debug_log.js +11 -6
- package/src/util/mcp_config_manager.js +5 -5
- package/src/util/output_formatter.js +1 -1
- package/src/util/prompt_loader.js +3 -3
- package/src/util/safe_fs.js +522 -0
- /package/payload_viewer/out/_next/static/{CkG9PSc7KAlXzs3w8IZSf → h5BNZL0hrewfv11rqfUcZ}/_buildManifest.js +0 -0
- /package/payload_viewer/out/_next/static/{CkG9PSc7KAlXzs3w8IZSf → h5BNZL0hrewfv11rqfUcZ}/_clientMiddlewareManifest.json +0 -0
- /package/payload_viewer/out/_next/static/{CkG9PSc7KAlXzs3w8IZSf → h5BNZL0hrewfv11rqfUcZ}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safe File System Operations Wrapper
|
|
3
|
+
*
|
|
4
|
+
* 이 모듈은 파일 시스템의 파괴적 작업들을 안전하게 수행하기 위한 wrapper 함수들을 제공합니다.
|
|
5
|
+
* 모든 파괴적 작업은 검증과 에러 핸들링을 포함합니다.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'fs/promises';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { homedir } from 'os';
|
|
11
|
+
|
|
12
|
+
// 허용된 디렉토리들
|
|
13
|
+
const ALLOWED_DIRECTORIES = [
|
|
14
|
+
process.cwd(), // current working directory
|
|
15
|
+
path.join(homedir(), '.aiexe') // ~/.aiexe
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 파일/디렉토리 존재 여부 확인 (비동기)
|
|
20
|
+
* @param {string} filePath - 확인할 경로
|
|
21
|
+
* @returns {Promise<boolean>}
|
|
22
|
+
*/
|
|
23
|
+
async function exists(filePath) {
|
|
24
|
+
try {
|
|
25
|
+
await fs.access(filePath);
|
|
26
|
+
return true;
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function debugLog(str) {
|
|
33
|
+
const logDir = path.join(homedir(), '.aiexe', 'debuglog');
|
|
34
|
+
const logFile = path.join(logDir, 'safe_fs.log');
|
|
35
|
+
|
|
36
|
+
if (!(await exists(logDir))) {
|
|
37
|
+
await fs.mkdir(logDir, { recursive: true }).catch(() => {});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
await fs.appendFile(logFile, str).catch(() => {});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 경로를 절대경로로 변환
|
|
46
|
+
* @param {string} filePath - 변환할 파일 경로
|
|
47
|
+
* @returns {string} 절대 경로
|
|
48
|
+
*/
|
|
49
|
+
function toAbsolutePath(filePath) {
|
|
50
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
51
|
+
return filePath;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 이미 절대 경로이면 그대로 반환
|
|
55
|
+
if (path.isAbsolute(filePath)) {
|
|
56
|
+
return path.normalize(filePath);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 상대 경로를 절대 경로로 변환
|
|
60
|
+
return path.resolve(process.cwd(), filePath);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 경로 유효성 검증
|
|
65
|
+
* @param {string} filePath - 검증할 파일 경로
|
|
66
|
+
* @returns {boolean} 유효한 경로인지 여부
|
|
67
|
+
*/
|
|
68
|
+
function validatePath(filePath) {
|
|
69
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 경로가 허용된 디렉토리 내부에 있는지 확인
|
|
78
|
+
* @param {string} filePath - 확인할 파일 경로
|
|
79
|
+
* @returns {boolean} 허용된 디렉토리 내부에 있는지 여부
|
|
80
|
+
*/
|
|
81
|
+
function isWithinAllowedDirectory(filePath) {
|
|
82
|
+
const normalizedPath = path.normalize(path.resolve(filePath));
|
|
83
|
+
|
|
84
|
+
return ALLOWED_DIRECTORIES.some(allowedDir => {
|
|
85
|
+
const normalizedAllowedDir = path.normalize(path.resolve(allowedDir));
|
|
86
|
+
return normalizedPath.startsWith(normalizedAllowedDir + path.sep) ||
|
|
87
|
+
normalizedPath === normalizedAllowedDir;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 보호된 경로인지 확인 (deprecated - isWithinAllowedDirectory로 대체됨)
|
|
93
|
+
* @param {string} filePath - 확인할 파일 경로
|
|
94
|
+
* @returns {boolean} 보호된 경로인지 여부
|
|
95
|
+
*/
|
|
96
|
+
function isProtectedPath(filePath) {
|
|
97
|
+
// 허용된 디렉토리가 아니면 보호된 경로로 간주
|
|
98
|
+
return !isWithinAllowedDirectory(filePath);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 안전한 파일 쓰기
|
|
103
|
+
* @param {string} filePath - 쓸 파일 경로
|
|
104
|
+
* @param {string|Buffer} content - 파일 내용
|
|
105
|
+
* @param {string} encoding - 인코딩 (기본값: 'utf8')
|
|
106
|
+
* @returns {Promise<void>}
|
|
107
|
+
*/
|
|
108
|
+
export async function safeWriteFile(filePath, content, encoding = 'utf8') {
|
|
109
|
+
const absolutePath = toAbsolutePath(filePath);
|
|
110
|
+
|
|
111
|
+
if (!validatePath(absolutePath)) {
|
|
112
|
+
const error = `Invalid file path: ${filePath}`;
|
|
113
|
+
await debugLog(`[ERROR] safeWriteFile: ${error}\n`);
|
|
114
|
+
throw new Error(error);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (isProtectedPath(absolutePath)) {
|
|
118
|
+
const error = `Protected path cannot be modified: ${absolutePath}`;
|
|
119
|
+
await debugLog(`[ERROR] safeWriteFile: ${error}\n`);
|
|
120
|
+
throw new Error(error);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
await fs.writeFile(absolutePath, content, encoding);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
const errorMsg = `Failed to write file ${absolutePath}: ${error.message}`;
|
|
127
|
+
await debugLog(`[ERROR] safeWriteFile: ${errorMsg}\n`);
|
|
128
|
+
throw new Error(errorMsg);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 안전한 파일 삭제
|
|
134
|
+
* @param {string} filePath - 삭제할 파일 경로
|
|
135
|
+
* @returns {Promise<void>}
|
|
136
|
+
*/
|
|
137
|
+
export async function safeUnlink(filePath) {
|
|
138
|
+
const absolutePath = toAbsolutePath(filePath);
|
|
139
|
+
|
|
140
|
+
if (!validatePath(absolutePath)) {
|
|
141
|
+
const error = `Invalid file path: ${filePath}`;
|
|
142
|
+
await debugLog(`[ERROR] safeUnlink: ${error}\n`);
|
|
143
|
+
throw new Error(error);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (isProtectedPath(absolutePath)) {
|
|
147
|
+
const error = `Protected path cannot be deleted: ${absolutePath}`;
|
|
148
|
+
await debugLog(`[ERROR] safeUnlink: ${error}\n`);
|
|
149
|
+
throw new Error(error);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
if (!(await exists(absolutePath))) {
|
|
154
|
+
return; // 파일이 없으면 조용히 반환
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
await fs.unlink(absolutePath);
|
|
158
|
+
} catch (error) {
|
|
159
|
+
const errorMsg = `Failed to delete file ${absolutePath}: ${error.message}`;
|
|
160
|
+
await debugLog(`[ERROR] safeUnlink: ${errorMsg}\n`);
|
|
161
|
+
throw new Error(errorMsg);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 안전한 파일 이름 변경
|
|
167
|
+
* @param {string} oldPath - 기존 파일 경로
|
|
168
|
+
* @param {string} newPath - 새 파일 경로
|
|
169
|
+
* @returns {Promise<void>}
|
|
170
|
+
*/
|
|
171
|
+
export async function safeRename(oldPath, newPath) {
|
|
172
|
+
const absoluteOldPath = toAbsolutePath(oldPath);
|
|
173
|
+
const absoluteNewPath = toAbsolutePath(newPath);
|
|
174
|
+
|
|
175
|
+
if (!validatePath(absoluteOldPath) || !validatePath(absoluteNewPath)) {
|
|
176
|
+
const error = `Invalid file path: ${oldPath} -> ${newPath}`;
|
|
177
|
+
await debugLog(`[ERROR] safeRename: ${error}\n`);
|
|
178
|
+
throw new Error(error);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (isProtectedPath(absoluteOldPath) || isProtectedPath(absoluteNewPath)) {
|
|
182
|
+
const error = `Protected path cannot be modified`;
|
|
183
|
+
await debugLog(`[ERROR] safeRename: ${error} (${absoluteOldPath} -> ${absoluteNewPath})\n`);
|
|
184
|
+
throw new Error(error);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
if (!(await exists(absoluteOldPath))) {
|
|
189
|
+
const errorMsg = `Source file does not exist: ${absoluteOldPath}`;
|
|
190
|
+
await debugLog(`[ERROR] safeRename: ${errorMsg}\n`);
|
|
191
|
+
throw new Error(errorMsg);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
await fs.rename(absoluteOldPath, absoluteNewPath);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
const errorMsg = `Failed to rename file ${absoluteOldPath} -> ${absoluteNewPath}: ${error.message}`;
|
|
197
|
+
await debugLog(`[ERROR] safeRename: ${errorMsg}\n`);
|
|
198
|
+
throw new Error(errorMsg);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* 안전한 파일 절삭 (내용 비우기)
|
|
204
|
+
* @param {string} filePath - 절삭할 파일 경로
|
|
205
|
+
* @param {number} length - 절삭할 길이 (기본값: 0 - 파일 비우기)
|
|
206
|
+
* @returns {Promise<void>}
|
|
207
|
+
*/
|
|
208
|
+
export async function safeTruncate(filePath, length = 0) {
|
|
209
|
+
const absolutePath = toAbsolutePath(filePath);
|
|
210
|
+
|
|
211
|
+
if (!validatePath(absolutePath)) {
|
|
212
|
+
const error = `Invalid file path: ${filePath}`;
|
|
213
|
+
await debugLog(`[ERROR] safeTruncate: ${error}\n`);
|
|
214
|
+
throw new Error(error);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (isProtectedPath(absolutePath)) {
|
|
218
|
+
const error = `Protected path cannot be modified: ${absolutePath}`;
|
|
219
|
+
await debugLog(`[ERROR] safeTruncate: ${error}\n`);
|
|
220
|
+
throw new Error(error);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
if (!(await exists(absolutePath))) {
|
|
225
|
+
const errorMsg = `File does not exist: ${absolutePath}`;
|
|
226
|
+
await debugLog(`[ERROR] safeTruncate: ${errorMsg}\n`);
|
|
227
|
+
throw new Error(errorMsg);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
await fs.truncate(absolutePath, length);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
const errorMsg = `Failed to truncate file ${absolutePath}: ${error.message}`;
|
|
233
|
+
await debugLog(`[ERROR] safeTruncate: ${errorMsg}\n`);
|
|
234
|
+
throw new Error(errorMsg);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* 안전한 디렉토리 삭제
|
|
240
|
+
* @param {string} dirPath - 삭제할 디렉토리 경로
|
|
241
|
+
* @param {Object} options - 추가 옵션
|
|
242
|
+
* @param {boolean} options.recursive - 재귀적으로 삭제 여부
|
|
243
|
+
* @returns {Promise<void>}
|
|
244
|
+
*/
|
|
245
|
+
export async function safeRmdir(dirPath, options = {}) {
|
|
246
|
+
const absolutePath = toAbsolutePath(dirPath);
|
|
247
|
+
|
|
248
|
+
if (!validatePath(absolutePath)) {
|
|
249
|
+
const error = `Invalid directory path: ${dirPath}`;
|
|
250
|
+
await debugLog(`[ERROR] safeRmdir: ${error}\n`);
|
|
251
|
+
throw new Error(error);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (isProtectedPath(absolutePath)) {
|
|
255
|
+
const error = `Protected path cannot be deleted: ${absolutePath}`;
|
|
256
|
+
await debugLog(`[ERROR] safeRmdir: ${error}\n`);
|
|
257
|
+
throw new Error(error);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
if (!(await exists(absolutePath))) {
|
|
262
|
+
return; // 디렉토리가 없으면 조용히 반환
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
await fs.rmdir(absolutePath, options);
|
|
266
|
+
} catch (error) {
|
|
267
|
+
const errorMsg = `Failed to delete directory ${absolutePath}: ${error.message}`;
|
|
268
|
+
await debugLog(`[ERROR] safeRmdir: ${errorMsg}\n`);
|
|
269
|
+
throw new Error(errorMsg);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* 안전한 재귀적 삭제 (파일 또는 디렉토리)
|
|
275
|
+
* @param {string} targetPath - 삭제할 경로
|
|
276
|
+
* @param {Object} options - 추가 옵션
|
|
277
|
+
* @returns {Promise<void>}
|
|
278
|
+
*/
|
|
279
|
+
export async function safeRm(targetPath, options = {}) {
|
|
280
|
+
const absolutePath = toAbsolutePath(targetPath);
|
|
281
|
+
|
|
282
|
+
if (!validatePath(absolutePath)) {
|
|
283
|
+
const error = `Invalid path: ${targetPath}`;
|
|
284
|
+
await debugLog(`[ERROR] safeRm: ${error}\n`);
|
|
285
|
+
throw new Error(error);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (isProtectedPath(absolutePath)) {
|
|
289
|
+
const error = `Protected path cannot be deleted: ${absolutePath}`;
|
|
290
|
+
await debugLog(`[ERROR] safeRm: ${error}\n`);
|
|
291
|
+
throw new Error(error);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
if (!(await exists(absolutePath))) {
|
|
296
|
+
return; // 경로가 없으면 조용히 반환
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
await fs.rm(absolutePath, { ...options, recursive: true, force: true });
|
|
300
|
+
} catch (error) {
|
|
301
|
+
const errorMsg = `Failed to remove ${absolutePath}: ${error.message}`;
|
|
302
|
+
await debugLog(`[ERROR] safeRm: ${errorMsg}\n`);
|
|
303
|
+
throw new Error(errorMsg);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ============================================================
|
|
308
|
+
// 읽기 전용 작업 (Read-only operations)
|
|
309
|
+
// ============================================================
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* 파일 읽기
|
|
313
|
+
* @param {string} filePath - 읽을 파일 경로
|
|
314
|
+
* @param {string} encoding - 인코딩 (기본값: 'utf8')
|
|
315
|
+
* @returns {Promise<string|Buffer>}
|
|
316
|
+
*/
|
|
317
|
+
export async function safeReadFile(filePath, encoding = 'utf8') {
|
|
318
|
+
const absolutePath = toAbsolutePath(filePath);
|
|
319
|
+
|
|
320
|
+
try {
|
|
321
|
+
return await fs.readFile(absolutePath, encoding);
|
|
322
|
+
} catch (error) {
|
|
323
|
+
const errorMsg = `Failed to read file ${absolutePath}: ${error.message}`;
|
|
324
|
+
await debugLog(`[ERROR] safeReadFile: ${errorMsg}\n`);
|
|
325
|
+
throw new Error(errorMsg);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* 파일 존재 여부 확인 (비동기)
|
|
331
|
+
* @param {string} filePath - 확인할 파일 경로
|
|
332
|
+
* @returns {Promise<boolean>}
|
|
333
|
+
*/
|
|
334
|
+
export async function safeExists(filePath) {
|
|
335
|
+
const absolutePath = toAbsolutePath(filePath);
|
|
336
|
+
return await exists(absolutePath);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* 디렉토리 읽기
|
|
341
|
+
* @param {string} dirPath - 읽을 디렉토리 경로
|
|
342
|
+
* @param {Object} options - 추가 옵션
|
|
343
|
+
* @returns {Promise<string[]>}
|
|
344
|
+
*/
|
|
345
|
+
export async function safeReaddir(dirPath, options = {}) {
|
|
346
|
+
const absolutePath = toAbsolutePath(dirPath);
|
|
347
|
+
|
|
348
|
+
try {
|
|
349
|
+
return await fs.readdir(absolutePath, options);
|
|
350
|
+
} catch (error) {
|
|
351
|
+
const errorMsg = `Failed to read directory ${absolutePath}: ${error.message}`;
|
|
352
|
+
await debugLog(`[ERROR] safeReaddir: ${errorMsg}\n`);
|
|
353
|
+
throw new Error(errorMsg);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* 파일/디렉토리 상태 정보 가져오기
|
|
359
|
+
* @param {string} targetPath - 확인할 경로
|
|
360
|
+
* @returns {Promise<import('fs').Stats>}
|
|
361
|
+
*/
|
|
362
|
+
export async function safeStat(targetPath) {
|
|
363
|
+
const absolutePath = toAbsolutePath(targetPath);
|
|
364
|
+
|
|
365
|
+
try {
|
|
366
|
+
return await fs.stat(absolutePath);
|
|
367
|
+
} catch (error) {
|
|
368
|
+
const errorMsg = `Failed to stat ${absolutePath}: ${error.message}`;
|
|
369
|
+
await debugLog(`[ERROR] safeStat: ${errorMsg}\n`);
|
|
370
|
+
throw new Error(errorMsg);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* 파일/디렉토리 상태 정보 가져오기 (심볼릭 링크를 따라가지 않음)
|
|
376
|
+
* @param {string} targetPath - 확인할 경로
|
|
377
|
+
* @returns {Promise<import('fs').Stats>}
|
|
378
|
+
*/
|
|
379
|
+
export async function safeLstat(targetPath) {
|
|
380
|
+
const absolutePath = toAbsolutePath(targetPath);
|
|
381
|
+
|
|
382
|
+
try {
|
|
383
|
+
return await fs.lstat(absolutePath);
|
|
384
|
+
} catch (error) {
|
|
385
|
+
const errorMsg = `Failed to lstat ${absolutePath}: ${error.message}`;
|
|
386
|
+
await debugLog(`[ERROR] safeLstat: ${errorMsg}\n`);
|
|
387
|
+
throw new Error(errorMsg);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* 파일 접근 권한 확인
|
|
393
|
+
* @param {string} filePath - 확인할 파일 경로
|
|
394
|
+
* @param {number} mode - 접근 모드 (fs.constants.F_OK, R_OK, W_OK, X_OK)
|
|
395
|
+
* @returns {Promise<void>}
|
|
396
|
+
*/
|
|
397
|
+
export async function safeAccess(filePath, mode) {
|
|
398
|
+
const absolutePath = toAbsolutePath(filePath);
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
return await fs.access(absolutePath, mode);
|
|
402
|
+
} catch (error) {
|
|
403
|
+
const errorMsg = `Failed to access ${absolutePath}: ${error.message}`;
|
|
404
|
+
await debugLog(`[ERROR] safeAccess: ${errorMsg}\n`);
|
|
405
|
+
throw new Error(errorMsg);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* 디렉토리 생성
|
|
411
|
+
* @param {string} dirPath - 생성할 디렉토리 경로
|
|
412
|
+
* @param {Object} options - 추가 옵션
|
|
413
|
+
* @param {boolean} options.recursive - 재귀적으로 생성 여부
|
|
414
|
+
* @returns {Promise<string|undefined>}
|
|
415
|
+
*/
|
|
416
|
+
export async function safeMkdir(dirPath, options = {}) {
|
|
417
|
+
const absolutePath = toAbsolutePath(dirPath);
|
|
418
|
+
|
|
419
|
+
if (!validatePath(absolutePath)) {
|
|
420
|
+
const error = `Invalid directory path: ${dirPath}`;
|
|
421
|
+
await debugLog(`[ERROR] safeMkdir: ${error}\n`);
|
|
422
|
+
throw new Error(error);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (isProtectedPath(absolutePath)) {
|
|
426
|
+
const error = `Protected path cannot be modified: ${absolutePath}`;
|
|
427
|
+
await debugLog(`[ERROR] safeMkdir: ${error}\n`);
|
|
428
|
+
throw new Error(error);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
try {
|
|
432
|
+
return await fs.mkdir(absolutePath, options);
|
|
433
|
+
} catch (error) {
|
|
434
|
+
// recursive: true 일 때 이미 존재하는 디렉토리는 에러가 아님
|
|
435
|
+
if (error.code === 'EEXIST' && options.recursive) {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const errorMsg = `Failed to create directory ${absolutePath}: ${error.message}`;
|
|
439
|
+
await debugLog(`[ERROR] safeMkdir: ${errorMsg}\n`);
|
|
440
|
+
throw new Error(errorMsg);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* 실제 경로 찾기 (심볼릭 링크 해석)
|
|
446
|
+
* @param {string} targetPath - 확인할 경로
|
|
447
|
+
* @returns {Promise<string>}
|
|
448
|
+
*/
|
|
449
|
+
export async function safeRealpath(targetPath) {
|
|
450
|
+
const absolutePath = toAbsolutePath(targetPath);
|
|
451
|
+
|
|
452
|
+
try {
|
|
453
|
+
return await fs.realpath(absolutePath);
|
|
454
|
+
} catch (error) {
|
|
455
|
+
const errorMsg = `Failed to resolve realpath ${absolutePath}: ${error.message}`;
|
|
456
|
+
await debugLog(`[ERROR] safeRealpath: ${errorMsg}\n`);
|
|
457
|
+
throw new Error(errorMsg);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* 파일 복사
|
|
463
|
+
* @param {string} src - 원본 파일 경로
|
|
464
|
+
* @param {string} dest - 대상 파일 경로
|
|
465
|
+
* @param {number} mode - 복사 모드
|
|
466
|
+
* @returns {Promise<void>}
|
|
467
|
+
*/
|
|
468
|
+
export async function safeCopyFile(src, dest, mode) {
|
|
469
|
+
const absoluteSrc = toAbsolutePath(src);
|
|
470
|
+
const absoluteDest = toAbsolutePath(dest);
|
|
471
|
+
|
|
472
|
+
if (!validatePath(absoluteSrc) || !validatePath(absoluteDest)) {
|
|
473
|
+
const error = `Invalid path: ${src} -> ${dest}`;
|
|
474
|
+
await debugLog(`[ERROR] safeCopyFile: ${error}\n`);
|
|
475
|
+
throw new Error(error);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (isProtectedPath(absoluteDest)) {
|
|
479
|
+
const error = `Protected path cannot be modified: ${absoluteDest}`;
|
|
480
|
+
await debugLog(`[ERROR] safeCopyFile: ${error}\n`);
|
|
481
|
+
throw new Error(error);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
try {
|
|
485
|
+
return await fs.copyFile(absoluteSrc, absoluteDest, mode);
|
|
486
|
+
} catch (error) {
|
|
487
|
+
const errorMsg = `Failed to copy file ${absoluteSrc} -> ${absoluteDest}: ${error.message}`;
|
|
488
|
+
await debugLog(`[ERROR] safeCopyFile: ${errorMsg}\n`);
|
|
489
|
+
throw new Error(errorMsg);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* 파일에 내용 추가
|
|
495
|
+
* @param {string} filePath - 파일 경로
|
|
496
|
+
* @param {string|Buffer} data - 추가할 데이터
|
|
497
|
+
* @param {string} encoding - 인코딩 (기본값: 'utf8')
|
|
498
|
+
* @returns {Promise<void>}
|
|
499
|
+
*/
|
|
500
|
+
export async function safeAppendFile(filePath, data, encoding = 'utf8') {
|
|
501
|
+
const absolutePath = toAbsolutePath(filePath);
|
|
502
|
+
|
|
503
|
+
if (!validatePath(absolutePath)) {
|
|
504
|
+
const error = `Invalid file path: ${filePath}`;
|
|
505
|
+
await debugLog(`[ERROR] safeAppendFile: ${error}\n`);
|
|
506
|
+
throw new Error(error);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (isProtectedPath(absolutePath)) {
|
|
510
|
+
const error = `Protected path cannot be modified: ${absolutePath}`;
|
|
511
|
+
await debugLog(`[ERROR] safeAppendFile: ${error}\n`);
|
|
512
|
+
throw new Error(error);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
try {
|
|
516
|
+
return await fs.appendFile(absolutePath, data, encoding);
|
|
517
|
+
} catch (error) {
|
|
518
|
+
const errorMsg = `Failed to append to file ${absolutePath}: ${error.message}`;
|
|
519
|
+
await debugLog(`[ERROR] safeAppendFile: ${errorMsg}\n`);
|
|
520
|
+
throw new Error(errorMsg);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|