@mandujs/core 0.13.0 → 0.13.1
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.ko.md +4 -4
- package/README.md +653 -653
- package/package.json +1 -1
- package/src/bundler/build.ts +91 -91
- package/src/bundler/css.ts +302 -302
- package/src/client/Link.tsx +227 -227
- package/src/client/globals.ts +44 -44
- package/src/client/hooks.ts +267 -267
- package/src/client/index.ts +5 -5
- package/src/client/island.ts +8 -8
- package/src/client/router.ts +435 -435
- package/src/client/runtime.ts +23 -23
- package/src/client/serialize.ts +404 -404
- package/src/client/window-state.ts +101 -101
- package/src/config/mandu.ts +9 -0
- package/src/config/validate.ts +12 -0
- package/src/config/watcher.ts +311 -311
- package/src/constants.ts +40 -40
- package/src/content/content-layer.ts +314 -314
- package/src/content/content.test.ts +433 -433
- package/src/content/data-store.ts +245 -245
- package/src/content/digest.ts +133 -133
- package/src/content/index.ts +164 -164
- package/src/content/loader-context.ts +172 -172
- package/src/content/loaders/api.ts +216 -216
- package/src/content/loaders/file.ts +169 -169
- package/src/content/loaders/glob.ts +252 -252
- package/src/content/loaders/index.ts +34 -34
- package/src/content/loaders/types.ts +137 -137
- package/src/content/meta-store.ts +209 -209
- package/src/content/types.ts +282 -282
- package/src/content/watcher.ts +135 -135
- package/src/contract/client-safe.test.ts +42 -42
- package/src/contract/client-safe.ts +114 -114
- package/src/contract/client.ts +16 -16
- package/src/contract/define.ts +459 -459
- package/src/contract/handler.ts +10 -10
- package/src/contract/normalize.test.ts +276 -276
- package/src/contract/normalize.ts +404 -404
- package/src/contract/registry.test.ts +206 -206
- package/src/contract/registry.ts +568 -568
- package/src/contract/schema.ts +48 -48
- package/src/contract/types.ts +58 -58
- package/src/contract/validator.ts +32 -32
- package/src/devtools/ai/context-builder.ts +375 -375
- package/src/devtools/ai/index.ts +25 -25
- package/src/devtools/ai/mcp-connector.ts +465 -465
- package/src/devtools/client/catchers/error-catcher.ts +327 -327
- package/src/devtools/client/catchers/index.ts +18 -18
- package/src/devtools/client/catchers/network-proxy.ts +363 -363
- package/src/devtools/client/components/index.ts +39 -39
- package/src/devtools/client/components/kitchen-root.tsx +362 -362
- package/src/devtools/client/components/mandu-character.tsx +241 -241
- package/src/devtools/client/components/overlay.tsx +368 -368
- package/src/devtools/client/components/panel/errors-panel.tsx +259 -259
- package/src/devtools/client/components/panel/guard-panel.tsx +244 -244
- package/src/devtools/client/components/panel/index.ts +32 -32
- package/src/devtools/client/components/panel/islands-panel.tsx +304 -304
- package/src/devtools/client/components/panel/network-panel.tsx +292 -292
- package/src/devtools/client/components/panel/panel-container.tsx +259 -259
- package/src/devtools/client/filters/context-filters.ts +282 -282
- package/src/devtools/client/filters/index.ts +16 -16
- package/src/devtools/client/index.ts +63 -63
- package/src/devtools/client/persistence.ts +335 -335
- package/src/devtools/client/state-manager.ts +478 -478
- package/src/devtools/design-tokens.ts +263 -263
- package/src/devtools/hook/create-hook.ts +207 -207
- package/src/devtools/hook/index.ts +13 -13
- package/src/devtools/index.ts +439 -439
- package/src/devtools/init.ts +266 -266
- package/src/devtools/protocol.ts +237 -237
- package/src/devtools/server/index.ts +17 -17
- package/src/devtools/server/source-context.ts +444 -444
- package/src/devtools/types.ts +319 -319
- package/src/devtools/worker/index.ts +25 -25
- package/src/devtools/worker/redaction-worker.ts +222 -222
- package/src/devtools/worker/worker-manager.ts +409 -409
- package/src/error/domains.ts +265 -265
- package/src/error/result.ts +46 -46
- package/src/error/types.ts +6 -6
- package/src/errors/extractor.ts +409 -409
- package/src/errors/index.ts +19 -19
- package/src/filling/auth.ts +308 -308
- package/src/filling/context.ts +24 -1
- package/src/filling/deps.ts +238 -238
- package/src/filling/index.ts +2 -0
- package/src/filling/sse.test.ts +168 -0
- package/src/filling/sse.ts +162 -0
- package/src/generator/index.ts +3 -3
- package/src/guard/analyzer.ts +360 -360
- package/src/guard/ast-analyzer.ts +806 -806
- package/src/guard/contract-guard.ts +9 -9
- package/src/guard/file-type.test.ts +24 -24
- package/src/guard/presets/atomic.ts +70 -70
- package/src/guard/presets/clean.ts +77 -77
- package/src/guard/presets/fsd.ts +79 -79
- package/src/guard/presets/hexagonal.ts +68 -68
- package/src/guard/presets/index.ts +291 -291
- package/src/guard/reporter.ts +445 -445
- package/src/guard/rules.ts +12 -12
- package/src/guard/statistics.ts +578 -578
- package/src/guard/suggestions.ts +358 -358
- package/src/guard/types.ts +348 -348
- package/src/guard/validator.ts +834 -834
- package/src/guard/watcher.ts +404 -404
- package/src/index.ts +6 -1
- package/src/intent/index.ts +310 -310
- package/src/island/index.ts +304 -304
- package/src/logging/index.ts +22 -22
- package/src/logging/transports.ts +365 -365
- package/src/plugins/index.ts +38 -38
- package/src/plugins/registry.ts +377 -377
- package/src/plugins/types.ts +363 -363
- package/src/report/index.ts +1 -1
- package/src/router/fs-patterns.ts +387 -387
- package/src/router/fs-scanner.ts +497 -497
- package/src/runtime/boundary.tsx +232 -232
- package/src/runtime/compose.ts +222 -222
- package/src/runtime/escape.ts +44 -0
- package/src/runtime/lifecycle.ts +381 -381
- package/src/runtime/logger.test.ts +345 -345
- package/src/runtime/logger.ts +677 -677
- package/src/runtime/router.test.ts +476 -476
- package/src/runtime/router.ts +105 -105
- package/src/runtime/security.ts +155 -155
- package/src/runtime/server.ts +257 -0
- package/src/runtime/session-key.ts +328 -328
- package/src/runtime/ssr.ts +16 -21
- package/src/runtime/streaming-ssr.ts +24 -33
- package/src/runtime/trace.ts +144 -144
- package/src/seo/index.ts +214 -214
- package/src/seo/integration/ssr.ts +307 -307
- package/src/seo/render/basic.ts +427 -427
- package/src/seo/render/index.ts +143 -143
- package/src/seo/render/jsonld.ts +539 -539
- package/src/seo/render/opengraph.ts +191 -191
- package/src/seo/render/robots.ts +116 -116
- package/src/seo/render/sitemap.ts +137 -137
- package/src/seo/render/twitter.ts +126 -126
- package/src/seo/resolve/index.ts +353 -353
- package/src/seo/resolve/opengraph.ts +143 -143
- package/src/seo/resolve/robots.ts +73 -73
- package/src/seo/resolve/title.ts +94 -94
- package/src/seo/resolve/twitter.ts +73 -73
- package/src/seo/resolve/url.ts +97 -97
- package/src/seo/routes/index.ts +290 -290
- package/src/seo/types.ts +575 -575
- package/src/slot/validator.ts +39 -39
- package/src/spec/index.ts +3 -3
- package/src/spec/load.ts +76 -76
- package/src/spec/lock.ts +56 -56
- package/src/utils/bun.ts +8 -8
- package/src/utils/lru-cache.ts +75 -75
- package/src/utils/safe-io.ts +188 -188
- package/src/utils/string-safe.ts +298 -298
package/src/utils/safe-io.ts
CHANGED
|
@@ -1,188 +1,188 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 안전한 파일 I/O 유틸리티
|
|
3
|
-
*
|
|
4
|
-
* Result<T> 패턴을 사용하여 에러 정보를 보존합니다.
|
|
5
|
-
* try-catch 대신 이 함수들을 사용하면 에러 컨텍스트가 유지됩니다.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { readFile, readdir, access, stat } from "fs/promises";
|
|
9
|
-
import type { Dirent, Stats } from "fs";
|
|
10
|
-
import type { Result } from "../error/result";
|
|
11
|
-
import { ok, err } from "../error/result";
|
|
12
|
-
import { FileError, DirectoryError } from "../error/domains";
|
|
13
|
-
|
|
14
|
-
// ============================================================
|
|
15
|
-
// 파일 읽기
|
|
16
|
-
// ============================================================
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* 안전한 파일 읽기 (텍스트)
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* const result = await safeReadFile("path/to/file.ts");
|
|
23
|
-
* if (!result.ok) {
|
|
24
|
-
* console.error(result.error.message);
|
|
25
|
-
* return;
|
|
26
|
-
* }
|
|
27
|
-
* const content = result.value;
|
|
28
|
-
*/
|
|
29
|
-
export async function safeReadFile(filePath: string): Promise<Result<string>> {
|
|
30
|
-
try {
|
|
31
|
-
const content = await readFile(filePath, "utf-8");
|
|
32
|
-
return ok(content);
|
|
33
|
-
} catch (e) {
|
|
34
|
-
return err(new FileError(filePath, "read", e).toManduError());
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* 안전한 파일 읽기 (Bun 최적화 버전)
|
|
40
|
-
* Bun.file().text()는 readFile보다 빠릅니다.
|
|
41
|
-
*/
|
|
42
|
-
export async function safeReadFileBun(filePath: string): Promise<Result<string>> {
|
|
43
|
-
try {
|
|
44
|
-
const content = await Bun.file(filePath).text();
|
|
45
|
-
return ok(content);
|
|
46
|
-
} catch (e) {
|
|
47
|
-
return err(new FileError(filePath, "read", e).toManduError());
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* 파일 존재 여부 확인
|
|
53
|
-
*/
|
|
54
|
-
export async function safeFileExists(filePath: string): Promise<Result<boolean>> {
|
|
55
|
-
try {
|
|
56
|
-
await access(filePath);
|
|
57
|
-
return ok(true);
|
|
58
|
-
} catch (e) {
|
|
59
|
-
// ENOENT는 정상적인 "없음" 상태
|
|
60
|
-
if ((e as NodeJS.ErrnoException).code === "ENOENT") {
|
|
61
|
-
return ok(false);
|
|
62
|
-
}
|
|
63
|
-
// 그 외는 실제 에러 (권한 등)
|
|
64
|
-
return err(new FileError(filePath, "access", e).toManduError());
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* 파일 정보 조회
|
|
70
|
-
*/
|
|
71
|
-
export async function safeFileStat(filePath: string): Promise<Result<Stats>> {
|
|
72
|
-
try {
|
|
73
|
-
const stats = await stat(filePath);
|
|
74
|
-
return ok(stats);
|
|
75
|
-
} catch (e) {
|
|
76
|
-
return err(new FileError(filePath, "stat", e).toManduError());
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// ============================================================
|
|
81
|
-
// 디렉토리 읽기
|
|
82
|
-
// ============================================================
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* 안전한 디렉토리 읽기
|
|
86
|
-
*/
|
|
87
|
-
export async function safeReadDir(dirPath: string): Promise<Result<string[]>> {
|
|
88
|
-
try {
|
|
89
|
-
const entries = await readdir(dirPath);
|
|
90
|
-
return ok(entries);
|
|
91
|
-
} catch (e) {
|
|
92
|
-
return err(new DirectoryError(dirPath, e).toManduError());
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* 안전한 디렉토리 읽기 (Dirent 포함)
|
|
98
|
-
*/
|
|
99
|
-
export async function safeReadDirWithTypes(
|
|
100
|
-
dirPath: string
|
|
101
|
-
): Promise<Result<Dirent[]>> {
|
|
102
|
-
try {
|
|
103
|
-
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
104
|
-
return ok(entries);
|
|
105
|
-
} catch (e) {
|
|
106
|
-
return err(new DirectoryError(dirPath, e).toManduError());
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// ============================================================
|
|
111
|
-
// 조합 유틸리티
|
|
112
|
-
// ============================================================
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* 여러 파일을 병렬로 읽기
|
|
116
|
-
*
|
|
117
|
-
* @returns 성공한 파일들만 반환, 실패는 errors에 수집
|
|
118
|
-
*/
|
|
119
|
-
export async function safeReadFiles(
|
|
120
|
-
filePaths: string[]
|
|
121
|
-
): Promise<{
|
|
122
|
-
results: Map<string, string>;
|
|
123
|
-
errors: Map<string, Error>;
|
|
124
|
-
}> {
|
|
125
|
-
const results = new Map<string, string>();
|
|
126
|
-
const errors = new Map<string, Error>();
|
|
127
|
-
|
|
128
|
-
await Promise.all(
|
|
129
|
-
filePaths.map(async (filePath) => {
|
|
130
|
-
const result = await safeReadFileBun(filePath);
|
|
131
|
-
if (result.ok) {
|
|
132
|
-
results.set(filePath, result.value);
|
|
133
|
-
} else {
|
|
134
|
-
errors.set(filePath, new Error(result.error.message));
|
|
135
|
-
}
|
|
136
|
-
})
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
return { results, errors };
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* 파일이 있으면 읽고, 없으면 기본값 반환
|
|
144
|
-
*/
|
|
145
|
-
export async function safeReadFileOrDefault(
|
|
146
|
-
filePath: string,
|
|
147
|
-
defaultValue: string
|
|
148
|
-
): Promise<string> {
|
|
149
|
-
const result = await safeReadFileBun(filePath);
|
|
150
|
-
return result.ok ? result.value : defaultValue;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* 디렉토리의 모든 파일 경로 수집 (재귀)
|
|
155
|
-
*/
|
|
156
|
-
export async function safeGlobDir(
|
|
157
|
-
dirPath: string,
|
|
158
|
-
pattern?: RegExp
|
|
159
|
-
): Promise<Result<string[]>> {
|
|
160
|
-
const result = await safeReadDirWithTypes(dirPath);
|
|
161
|
-
if (!result.ok) return result;
|
|
162
|
-
|
|
163
|
-
const files: string[] = [];
|
|
164
|
-
const subDirPromises: Promise<Result<string[]>>[] = [];
|
|
165
|
-
|
|
166
|
-
for (const entry of result.value) {
|
|
167
|
-
const fullPath = `${dirPath}/${entry.name}`;
|
|
168
|
-
|
|
169
|
-
if (entry.isDirectory()) {
|
|
170
|
-
subDirPromises.push(safeGlobDir(fullPath, pattern));
|
|
171
|
-
} else if (entry.isFile()) {
|
|
172
|
-
if (!pattern || pattern.test(entry.name)) {
|
|
173
|
-
files.push(fullPath);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// 서브 디렉토리 결과 수집
|
|
179
|
-
const subResults = await Promise.all(subDirPromises);
|
|
180
|
-
for (const subResult of subResults) {
|
|
181
|
-
if (subResult.ok) {
|
|
182
|
-
files.push(...subResult.value);
|
|
183
|
-
}
|
|
184
|
-
// 서브 디렉토리 에러는 무시 (접근 불가 디렉토리 스킵)
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
return ok(files);
|
|
188
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* 안전한 파일 I/O 유틸리티
|
|
3
|
+
*
|
|
4
|
+
* Result<T> 패턴을 사용하여 에러 정보를 보존합니다.
|
|
5
|
+
* try-catch 대신 이 함수들을 사용하면 에러 컨텍스트가 유지됩니다.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFile, readdir, access, stat } from "fs/promises";
|
|
9
|
+
import type { Dirent, Stats } from "fs";
|
|
10
|
+
import type { Result } from "../error/result";
|
|
11
|
+
import { ok, err } from "../error/result";
|
|
12
|
+
import { FileError, DirectoryError } from "../error/domains";
|
|
13
|
+
|
|
14
|
+
// ============================================================
|
|
15
|
+
// 파일 읽기
|
|
16
|
+
// ============================================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 안전한 파일 읽기 (텍스트)
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* const result = await safeReadFile("path/to/file.ts");
|
|
23
|
+
* if (!result.ok) {
|
|
24
|
+
* console.error(result.error.message);
|
|
25
|
+
* return;
|
|
26
|
+
* }
|
|
27
|
+
* const content = result.value;
|
|
28
|
+
*/
|
|
29
|
+
export async function safeReadFile(filePath: string): Promise<Result<string>> {
|
|
30
|
+
try {
|
|
31
|
+
const content = await readFile(filePath, "utf-8");
|
|
32
|
+
return ok(content);
|
|
33
|
+
} catch (e) {
|
|
34
|
+
return err(new FileError(filePath, "read", e).toManduError());
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 안전한 파일 읽기 (Bun 최적화 버전)
|
|
40
|
+
* Bun.file().text()는 readFile보다 빠릅니다.
|
|
41
|
+
*/
|
|
42
|
+
export async function safeReadFileBun(filePath: string): Promise<Result<string>> {
|
|
43
|
+
try {
|
|
44
|
+
const content = await Bun.file(filePath).text();
|
|
45
|
+
return ok(content);
|
|
46
|
+
} catch (e) {
|
|
47
|
+
return err(new FileError(filePath, "read", e).toManduError());
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 파일 존재 여부 확인
|
|
53
|
+
*/
|
|
54
|
+
export async function safeFileExists(filePath: string): Promise<Result<boolean>> {
|
|
55
|
+
try {
|
|
56
|
+
await access(filePath);
|
|
57
|
+
return ok(true);
|
|
58
|
+
} catch (e) {
|
|
59
|
+
// ENOENT는 정상적인 "없음" 상태
|
|
60
|
+
if ((e as NodeJS.ErrnoException).code === "ENOENT") {
|
|
61
|
+
return ok(false);
|
|
62
|
+
}
|
|
63
|
+
// 그 외는 실제 에러 (권한 등)
|
|
64
|
+
return err(new FileError(filePath, "access", e).toManduError());
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 파일 정보 조회
|
|
70
|
+
*/
|
|
71
|
+
export async function safeFileStat(filePath: string): Promise<Result<Stats>> {
|
|
72
|
+
try {
|
|
73
|
+
const stats = await stat(filePath);
|
|
74
|
+
return ok(stats);
|
|
75
|
+
} catch (e) {
|
|
76
|
+
return err(new FileError(filePath, "stat", e).toManduError());
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ============================================================
|
|
81
|
+
// 디렉토리 읽기
|
|
82
|
+
// ============================================================
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 안전한 디렉토리 읽기
|
|
86
|
+
*/
|
|
87
|
+
export async function safeReadDir(dirPath: string): Promise<Result<string[]>> {
|
|
88
|
+
try {
|
|
89
|
+
const entries = await readdir(dirPath);
|
|
90
|
+
return ok(entries);
|
|
91
|
+
} catch (e) {
|
|
92
|
+
return err(new DirectoryError(dirPath, e).toManduError());
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 안전한 디렉토리 읽기 (Dirent 포함)
|
|
98
|
+
*/
|
|
99
|
+
export async function safeReadDirWithTypes(
|
|
100
|
+
dirPath: string
|
|
101
|
+
): Promise<Result<Dirent[]>> {
|
|
102
|
+
try {
|
|
103
|
+
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
104
|
+
return ok(entries);
|
|
105
|
+
} catch (e) {
|
|
106
|
+
return err(new DirectoryError(dirPath, e).toManduError());
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ============================================================
|
|
111
|
+
// 조합 유틸리티
|
|
112
|
+
// ============================================================
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 여러 파일을 병렬로 읽기
|
|
116
|
+
*
|
|
117
|
+
* @returns 성공한 파일들만 반환, 실패는 errors에 수집
|
|
118
|
+
*/
|
|
119
|
+
export async function safeReadFiles(
|
|
120
|
+
filePaths: string[]
|
|
121
|
+
): Promise<{
|
|
122
|
+
results: Map<string, string>;
|
|
123
|
+
errors: Map<string, Error>;
|
|
124
|
+
}> {
|
|
125
|
+
const results = new Map<string, string>();
|
|
126
|
+
const errors = new Map<string, Error>();
|
|
127
|
+
|
|
128
|
+
await Promise.all(
|
|
129
|
+
filePaths.map(async (filePath) => {
|
|
130
|
+
const result = await safeReadFileBun(filePath);
|
|
131
|
+
if (result.ok) {
|
|
132
|
+
results.set(filePath, result.value);
|
|
133
|
+
} else {
|
|
134
|
+
errors.set(filePath, new Error(result.error.message));
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
return { results, errors };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 파일이 있으면 읽고, 없으면 기본값 반환
|
|
144
|
+
*/
|
|
145
|
+
export async function safeReadFileOrDefault(
|
|
146
|
+
filePath: string,
|
|
147
|
+
defaultValue: string
|
|
148
|
+
): Promise<string> {
|
|
149
|
+
const result = await safeReadFileBun(filePath);
|
|
150
|
+
return result.ok ? result.value : defaultValue;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* 디렉토리의 모든 파일 경로 수집 (재귀)
|
|
155
|
+
*/
|
|
156
|
+
export async function safeGlobDir(
|
|
157
|
+
dirPath: string,
|
|
158
|
+
pattern?: RegExp
|
|
159
|
+
): Promise<Result<string[]>> {
|
|
160
|
+
const result = await safeReadDirWithTypes(dirPath);
|
|
161
|
+
if (!result.ok) return result;
|
|
162
|
+
|
|
163
|
+
const files: string[] = [];
|
|
164
|
+
const subDirPromises: Promise<Result<string[]>>[] = [];
|
|
165
|
+
|
|
166
|
+
for (const entry of result.value) {
|
|
167
|
+
const fullPath = `${dirPath}/${entry.name}`;
|
|
168
|
+
|
|
169
|
+
if (entry.isDirectory()) {
|
|
170
|
+
subDirPromises.push(safeGlobDir(fullPath, pattern));
|
|
171
|
+
} else if (entry.isFile()) {
|
|
172
|
+
if (!pattern || pattern.test(entry.name)) {
|
|
173
|
+
files.push(fullPath);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// 서브 디렉토리 결과 수집
|
|
179
|
+
const subResults = await Promise.all(subDirPromises);
|
|
180
|
+
for (const subResult of subResults) {
|
|
181
|
+
if (subResult.ok) {
|
|
182
|
+
files.push(...subResult.value);
|
|
183
|
+
}
|
|
184
|
+
// 서브 디렉토리 에러는 무시 (접근 불가 디렉토리 스킵)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return ok(files);
|
|
188
|
+
}
|