@mandujs/core 0.12.1 β 0.13.0
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 +304 -304
- package/README.md +653 -653
- package/package.json +8 -8
- package/src/brain/architecture/analyzer.ts +28 -26
- package/src/brain/doctor/analyzer.ts +1 -1
- package/src/bundler/build.ts +91 -91
- package/src/bundler/css.ts +302 -302
- package/src/bundler/dev.ts +0 -1
- package/src/change/history.ts +3 -3
- package/src/change/snapshot.ts +10 -9
- package/src/change/transaction.ts +2 -2
- 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 +94 -96
- package/src/config/validate.ts +213 -215
- 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/classifier.ts +2 -2
- package/src/error/domains.ts +265 -265
- package/src/error/formatter.ts +32 -32
- package/src/error/result.ts +46 -46
- package/src/error/stack-analyzer.ts +5 -0
- 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 +569 -569
- package/src/filling/deps.ts +238 -238
- package/src/generator/contract-glue.ts +2 -1
- package/src/generator/generate.ts +12 -10
- package/src/generator/index.ts +3 -3
- package/src/generator/templates.ts +80 -79
- package/src/guard/analyzer.ts +360 -360
- package/src/guard/ast-analyzer.ts +806 -806
- package/src/guard/auto-correct.ts +1 -1
- package/src/guard/check.ts +128 -128
- package/src/guard/contract-guard.ts +9 -9
- package/src/guard/file-type.test.ts +24 -24
- package/src/guard/healing.ts +2 -0
- package/src/guard/index.ts +2 -0
- package/src/guard/negotiation.ts +430 -4
- package/src/guard/presets/atomic.ts +70 -70
- package/src/guard/presets/clean.ts +77 -77
- package/src/guard/presets/cqrs.test.ts +175 -0
- package/src/guard/presets/cqrs.ts +107 -0
- package/src/guard/presets/fsd.ts +79 -79
- package/src/guard/presets/hexagonal.ts +68 -68
- package/src/guard/presets/index.ts +291 -288
- 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 -352
- package/src/guard/types.ts +348 -347
- package/src/guard/validator.ts +834 -834
- package/src/guard/watcher.ts +404 -404
- package/src/index.ts +1 -0
- 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/paths.test.ts +47 -0
- package/src/paths.ts +47 -0
- package/src/plugins/index.ts +38 -38
- package/src/plugins/registry.ts +377 -377
- package/src/plugins/types.ts +363 -363
- package/src/report/build.ts +1 -1
- package/src/report/index.ts +1 -1
- package/src/router/fs-patterns.ts +387 -387
- package/src/router/fs-routes.ts +344 -401
- package/src/router/fs-scanner.ts +497 -497
- package/src/router/fs-types.ts +270 -278
- package/src/router/index.ts +81 -81
- package/src/runtime/boundary.tsx +232 -232
- package/src/runtime/compose.ts +222 -222
- 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 +24 -24
- package/src/runtime/session-key.ts +328 -328
- package/src/runtime/ssr.ts +367 -367
- package/src/runtime/streaming-ssr.ts +1245 -1245
- 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/watcher/rules.ts +5 -5
package/src/guard/watcher.ts
CHANGED
|
@@ -1,404 +1,404 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mandu Guard Watcher
|
|
3
|
-
*
|
|
4
|
-
* μ€μκ° νμΌ κ°μ
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { watch, type FSWatcher } from "chokidar";
|
|
8
|
-
import { resolve } from "path";
|
|
9
|
-
import type {
|
|
10
|
-
GuardConfig,
|
|
11
|
-
GuardWatcher,
|
|
12
|
-
LayerDefinition,
|
|
13
|
-
ViolationReport,
|
|
14
|
-
WatcherEvent,
|
|
15
|
-
Violation,
|
|
16
|
-
Severity,
|
|
17
|
-
ViolationType,
|
|
18
|
-
FileAnalysis,
|
|
19
|
-
} from "./types";
|
|
20
|
-
import { WATCH_EXTENSIONS, DEFAULT_GUARD_CONFIG } from "./types";
|
|
21
|
-
import { analyzeFile, shouldAnalyzeFile } from "./analyzer";
|
|
22
|
-
import { validateFileAnalysis, detectCircularDependencies } from "./validator";
|
|
23
|
-
import {
|
|
24
|
-
printRealtimeViolation,
|
|
25
|
-
formatViolationForAgent,
|
|
26
|
-
formatViolationAsAgentJSON,
|
|
27
|
-
} from "./reporter";
|
|
28
|
-
import { getPreset } from "./presets";
|
|
29
|
-
|
|
30
|
-
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
31
|
-
// Types
|
|
32
|
-
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
33
|
-
|
|
34
|
-
interface WatcherOptions {
|
|
35
|
-
config: GuardConfig;
|
|
36
|
-
rootDir: string;
|
|
37
|
-
onViolation?: (violation: Violation) => void;
|
|
38
|
-
onFileAnalyzed?: (analysis: FileAnalysis, violations: Violation[]) => void;
|
|
39
|
-
silent?: boolean;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
43
|
-
// Cache
|
|
44
|
-
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
45
|
-
|
|
46
|
-
const analysisCache = new Map<string, FileAnalysis>();
|
|
47
|
-
|
|
48
|
-
let globModulePromise: Promise<typeof import("glob")> | null = null;
|
|
49
|
-
|
|
50
|
-
async function getGlobModule(): Promise<typeof import("glob")> {
|
|
51
|
-
if (!globModulePromise) {
|
|
52
|
-
globModulePromise = import("glob");
|
|
53
|
-
}
|
|
54
|
-
return globModulePromise;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* μΊμ μ΄κΈ°ν
|
|
59
|
-
*/
|
|
60
|
-
export function clearAnalysisCache(): void {
|
|
61
|
-
analysisCache.clear();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
65
|
-
// Debounce
|
|
66
|
-
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
67
|
-
|
|
68
|
-
const debounceTimers = new Map<string, NodeJS.Timeout>();
|
|
69
|
-
|
|
70
|
-
function debounce(key: string, fn: () => void, ms: number): void {
|
|
71
|
-
const existing = debounceTimers.get(key);
|
|
72
|
-
if (existing) {
|
|
73
|
-
clearTimeout(existing);
|
|
74
|
-
}
|
|
75
|
-
debounceTimers.set(
|
|
76
|
-
key,
|
|
77
|
-
setTimeout(() => {
|
|
78
|
-
debounceTimers.delete(key);
|
|
79
|
-
fn();
|
|
80
|
-
}, ms)
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
85
|
-
// Guard Watcher Implementation
|
|
86
|
-
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Guard Watcher μμ±
|
|
90
|
-
*/
|
|
91
|
-
export function createGuardWatcher(options: WatcherOptions): GuardWatcher {
|
|
92
|
-
const { config, rootDir, onViolation, onFileAnalyzed, silent } = options;
|
|
93
|
-
|
|
94
|
-
// λ μ΄μ΄ μ μ κ°μ Έμ€κΈ°
|
|
95
|
-
const layers = resolveLayerDefinitions(config);
|
|
96
|
-
const hierarchy = resolveHierarchy(config);
|
|
97
|
-
|
|
98
|
-
// μ€μ κΈ°λ³Έκ° μ μ©
|
|
99
|
-
const srcDir = config.srcDir ?? DEFAULT_GUARD_CONFIG.srcDir;
|
|
100
|
-
const debounceMs = config.debounceMs ?? DEFAULT_GUARD_CONFIG.debounceMs;
|
|
101
|
-
const exclude = config.exclude ?? DEFAULT_GUARD_CONFIG.exclude;
|
|
102
|
-
|
|
103
|
-
let watcher: FSWatcher | null = null;
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* νμΌ λΆμ λ° κ²μ¦
|
|
107
|
-
*/
|
|
108
|
-
async function processFile(filePath: string, event: WatcherEvent): Promise<void> {
|
|
109
|
-
// μμ λ νμΌ
|
|
110
|
-
if (event === "unlink") {
|
|
111
|
-
analysisCache.delete(filePath);
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// λΆμ λμμΈμ§ νμΈ
|
|
116
|
-
if (!shouldAnalyzeFile(filePath, config, rootDir)) {
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
// νμΌ λΆμ
|
|
122
|
-
const analysis = await analyzeFile(filePath, layers, rootDir);
|
|
123
|
-
|
|
124
|
-
// μΊμ μ μ₯
|
|
125
|
-
if (config.cache !== false) {
|
|
126
|
-
analysisCache.set(filePath, analysis);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// κ²μ¦
|
|
130
|
-
const violations = validateFileAnalysis(analysis, layers, config);
|
|
131
|
-
|
|
132
|
-
// μ½λ°± νΈμΆ
|
|
133
|
-
onFileAnalyzed?.(analysis, violations);
|
|
134
|
-
|
|
135
|
-
const realtimeOutput = config.realtimeOutput ?? DEFAULT_GUARD_CONFIG.realtimeOutput;
|
|
136
|
-
|
|
137
|
-
// μλ° μ²λ¦¬
|
|
138
|
-
for (const violation of violations) {
|
|
139
|
-
onViolation?.(violation);
|
|
140
|
-
|
|
141
|
-
if (!silent) {
|
|
142
|
-
switch (realtimeOutput) {
|
|
143
|
-
case "agent":
|
|
144
|
-
console.log(formatViolationForAgent(violation, config.preset));
|
|
145
|
-
break;
|
|
146
|
-
case "json":
|
|
147
|
-
console.log(formatViolationAsAgentJSON(violation, config.preset));
|
|
148
|
-
break;
|
|
149
|
-
case "console":
|
|
150
|
-
default:
|
|
151
|
-
printRealtimeViolation(violation);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
} catch (error) {
|
|
156
|
-
if (!silent) {
|
|
157
|
-
console.error(`[Guard] Error analyzing ${filePath}:`, error);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* νμΌ λ³κ²½ νΈλ€λ¬
|
|
164
|
-
*/
|
|
165
|
-
function handleFileChange(event: WatcherEvent, filePath: string): void {
|
|
166
|
-
debounce(filePath, () => processFile(filePath, event), debounceMs);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* μ 체 μ€μΊ
|
|
171
|
-
*/
|
|
172
|
-
async function scanAll(): Promise<ViolationReport> {
|
|
173
|
-
const startTime = Date.now();
|
|
174
|
-
const violations: Violation[] = [];
|
|
175
|
-
const files: string[] = [];
|
|
176
|
-
const analyses: FileAnalysis[] = [];
|
|
177
|
-
|
|
178
|
-
// κΈλ‘λΈλ‘ λͺ¨λ νμΌ μ°ΎκΈ°
|
|
179
|
-
const { glob } = await getGlobModule();
|
|
180
|
-
const extensions = WATCH_EXTENSIONS.map((ext) => ext.slice(1)).join(",");
|
|
181
|
-
const scanRoots = new Set<string>([srcDir]);
|
|
182
|
-
if (config.fsRoutes) {
|
|
183
|
-
scanRoots.add("app");
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const foundFilesSet = new Set<string>();
|
|
187
|
-
for (const root of scanRoots) {
|
|
188
|
-
const pattern = `${root}/**/*.{${extensions}}`;
|
|
189
|
-
const foundFiles = await glob(pattern, {
|
|
190
|
-
cwd: rootDir,
|
|
191
|
-
ignore: exclude,
|
|
192
|
-
absolute: true,
|
|
193
|
-
});
|
|
194
|
-
for (const file of foundFiles) {
|
|
195
|
-
foundFilesSet.add(file);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const foundFiles = Array.from(foundFilesSet);
|
|
200
|
-
|
|
201
|
-
// κ° νμΌ λΆμ
|
|
202
|
-
for (const filePath of foundFiles) {
|
|
203
|
-
if (!shouldAnalyzeFile(filePath, config, rootDir)) {
|
|
204
|
-
continue;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
files.push(filePath);
|
|
208
|
-
|
|
209
|
-
try {
|
|
210
|
-
const analysis = await analyzeFile(filePath, layers, rootDir);
|
|
211
|
-
analyses.push(analysis);
|
|
212
|
-
const fileViolations = validateFileAnalysis(analysis, layers, config);
|
|
213
|
-
violations.push(...fileViolations);
|
|
214
|
-
|
|
215
|
-
if (config.cache !== false) {
|
|
216
|
-
analysisCache.set(filePath, analysis);
|
|
217
|
-
}
|
|
218
|
-
} catch (error) {
|
|
219
|
-
if (!silent) {
|
|
220
|
-
console.error(`[Guard] Error analyzing ${filePath}:`, error);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const endTime = Date.now();
|
|
226
|
-
|
|
227
|
-
// μν μμ‘΄μ± κ²μ¬ (μ 체 μ€μΊμμλ§)
|
|
228
|
-
if (analyses.length > 0) {
|
|
229
|
-
violations.push(...detectCircularDependencies(analyses, layers, config));
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// 리ν¬νΈ μμ±
|
|
233
|
-
const report: ViolationReport = {
|
|
234
|
-
totalViolations: violations.length,
|
|
235
|
-
bySeverity: countBySeverity(violations),
|
|
236
|
-
byType: countByType(violations),
|
|
237
|
-
violations,
|
|
238
|
-
filesAnalyzed: files.length,
|
|
239
|
-
analysisTime: endTime - startTime,
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
return report;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return {
|
|
246
|
-
start(): void {
|
|
247
|
-
const scanRoots = new Set<string>([srcDir]);
|
|
248
|
-
if (config.fsRoutes) {
|
|
249
|
-
scanRoots.add("app");
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const watchPatterns = Array.from(scanRoots).flatMap((root) =>
|
|
253
|
-
WATCH_EXTENSIONS.map((ext) => `${root}/**/*${ext}`)
|
|
254
|
-
);
|
|
255
|
-
|
|
256
|
-
watcher = watch(watchPatterns, {
|
|
257
|
-
cwd: rootDir,
|
|
258
|
-
ignored: exclude,
|
|
259
|
-
persistent: true,
|
|
260
|
-
ignoreInitial: false,
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
watcher.on("add", (path) => handleFileChange("add", resolve(rootDir, path)));
|
|
264
|
-
watcher.on("change", (path) => handleFileChange("change", resolve(rootDir, path)));
|
|
265
|
-
watcher.on("unlink", (path) => handleFileChange("unlink", resolve(rootDir, path)));
|
|
266
|
-
|
|
267
|
-
const realtimeOutput = config.realtimeOutput ?? DEFAULT_GUARD_CONFIG.realtimeOutput;
|
|
268
|
-
if (!silent && realtimeOutput === "console") {
|
|
269
|
-
console.log(`[Guard] π‘οΈ Watching ${Array.from(scanRoots).join(", ")} for architecture violations...`);
|
|
270
|
-
}
|
|
271
|
-
},
|
|
272
|
-
|
|
273
|
-
close(): void {
|
|
274
|
-
if (watcher) {
|
|
275
|
-
watcher.close();
|
|
276
|
-
watcher = null;
|
|
277
|
-
}
|
|
278
|
-
clearAnalysisCache();
|
|
279
|
-
},
|
|
280
|
-
|
|
281
|
-
scanAll,
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
286
|
-
// Helpers
|
|
287
|
-
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* λ μ΄μ΄ μ μ ν΄μ
|
|
291
|
-
*/
|
|
292
|
-
function resolveLayerDefinitions(config: GuardConfig): LayerDefinition[] {
|
|
293
|
-
// 컀μ€ν
λ μ΄μ΄κ° μμΌλ©΄ μ¬μ©
|
|
294
|
-
if (config.layers && config.layers.length > 0) {
|
|
295
|
-
return config.layers;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// ν리μ
μ¬μ©
|
|
299
|
-
if (config.preset) {
|
|
300
|
-
const preset = getPreset(config.preset);
|
|
301
|
-
let layers = [...preset.layers];
|
|
302
|
-
|
|
303
|
-
// μ€λ²λΌμ΄λ μ μ©
|
|
304
|
-
if (config.override?.layers) {
|
|
305
|
-
layers = layers.map((layer) => {
|
|
306
|
-
const override = config.override?.layers?.[layer.name];
|
|
307
|
-
if (override) {
|
|
308
|
-
return { ...layer, ...override };
|
|
309
|
-
}
|
|
310
|
-
return layer;
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
return layers;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
return [];
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* κ³μΈ΅ ꡬ쑰 ν΄μ
|
|
322
|
-
*/
|
|
323
|
-
function resolveHierarchy(config: GuardConfig): string[] {
|
|
324
|
-
if (config.preset) {
|
|
325
|
-
const preset = getPreset(config.preset);
|
|
326
|
-
return preset.hierarchy;
|
|
327
|
-
}
|
|
328
|
-
return [];
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* μ¬κ°λλ³ μΉ΄μ΄νΈ
|
|
333
|
-
*/
|
|
334
|
-
function countBySeverity(violations: Violation[]): Record<Severity, number> {
|
|
335
|
-
const counts: Record<Severity, number> = {
|
|
336
|
-
error: 0,
|
|
337
|
-
warn: 0,
|
|
338
|
-
info: 0,
|
|
339
|
-
};
|
|
340
|
-
|
|
341
|
-
for (const v of violations) {
|
|
342
|
-
counts[v.severity]++;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return counts;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* νμ
λ³ μΉ΄μ΄νΈ
|
|
350
|
-
*/
|
|
351
|
-
function countByType(violations: Violation[]): Record<ViolationType, number> {
|
|
352
|
-
const counts: Record<ViolationType, number> = {
|
|
353
|
-
"layer-violation": 0,
|
|
354
|
-
"circular-dependency": 0,
|
|
355
|
-
"cross-slice": 0,
|
|
356
|
-
"deep-nesting": 0,
|
|
357
|
-
"file-type": 0,
|
|
358
|
-
"invalid-shared-segment": 0,
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
for (const v of violations) {
|
|
362
|
-
counts[v.type]++;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
return counts;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
369
|
-
// Convenience Functions
|
|
370
|
-
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* λ¨μΌ νμΌ κ²μ¬ (μΌνμ±)
|
|
374
|
-
*/
|
|
375
|
-
export async function checkFile(
|
|
376
|
-
filePath: string,
|
|
377
|
-
config: GuardConfig,
|
|
378
|
-
rootDir: string
|
|
379
|
-
): Promise<Violation[]> {
|
|
380
|
-
const layers = resolveLayerDefinitions(config);
|
|
381
|
-
|
|
382
|
-
if (!shouldAnalyzeFile(filePath, config, rootDir)) {
|
|
383
|
-
return [];
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
const analysis = await analyzeFile(filePath, layers, rootDir);
|
|
387
|
-
return validateFileAnalysis(analysis, layers, config);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* λλ ν 리 μ 체 κ²μ¬ (μΌνμ±)
|
|
392
|
-
*/
|
|
393
|
-
export async function checkDirectory(
|
|
394
|
-
config: GuardConfig,
|
|
395
|
-
rootDir: string
|
|
396
|
-
): Promise<ViolationReport> {
|
|
397
|
-
const watcher = createGuardWatcher({
|
|
398
|
-
config,
|
|
399
|
-
rootDir,
|
|
400
|
-
silent: true,
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
return watcher.scanAll();
|
|
404
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Mandu Guard Watcher
|
|
3
|
+
*
|
|
4
|
+
* μ€μκ° νμΌ κ°μ
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { watch, type FSWatcher } from "chokidar";
|
|
8
|
+
import { resolve } from "path";
|
|
9
|
+
import type {
|
|
10
|
+
GuardConfig,
|
|
11
|
+
GuardWatcher,
|
|
12
|
+
LayerDefinition,
|
|
13
|
+
ViolationReport,
|
|
14
|
+
WatcherEvent,
|
|
15
|
+
Violation,
|
|
16
|
+
Severity,
|
|
17
|
+
ViolationType,
|
|
18
|
+
FileAnalysis,
|
|
19
|
+
} from "./types";
|
|
20
|
+
import { WATCH_EXTENSIONS, DEFAULT_GUARD_CONFIG } from "./types";
|
|
21
|
+
import { analyzeFile, shouldAnalyzeFile } from "./analyzer";
|
|
22
|
+
import { validateFileAnalysis, detectCircularDependencies } from "./validator";
|
|
23
|
+
import {
|
|
24
|
+
printRealtimeViolation,
|
|
25
|
+
formatViolationForAgent,
|
|
26
|
+
formatViolationAsAgentJSON,
|
|
27
|
+
} from "./reporter";
|
|
28
|
+
import { getPreset } from "./presets";
|
|
29
|
+
|
|
30
|
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
31
|
+
// Types
|
|
32
|
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
33
|
+
|
|
34
|
+
interface WatcherOptions {
|
|
35
|
+
config: GuardConfig;
|
|
36
|
+
rootDir: string;
|
|
37
|
+
onViolation?: (violation: Violation) => void;
|
|
38
|
+
onFileAnalyzed?: (analysis: FileAnalysis, violations: Violation[]) => void;
|
|
39
|
+
silent?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
43
|
+
// Cache
|
|
44
|
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
45
|
+
|
|
46
|
+
const analysisCache = new Map<string, FileAnalysis>();
|
|
47
|
+
|
|
48
|
+
let globModulePromise: Promise<typeof import("glob")> | null = null;
|
|
49
|
+
|
|
50
|
+
async function getGlobModule(): Promise<typeof import("glob")> {
|
|
51
|
+
if (!globModulePromise) {
|
|
52
|
+
globModulePromise = import("glob");
|
|
53
|
+
}
|
|
54
|
+
return globModulePromise;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* μΊμ μ΄κΈ°ν
|
|
59
|
+
*/
|
|
60
|
+
export function clearAnalysisCache(): void {
|
|
61
|
+
analysisCache.clear();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
65
|
+
// Debounce
|
|
66
|
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
67
|
+
|
|
68
|
+
const debounceTimers = new Map<string, NodeJS.Timeout>();
|
|
69
|
+
|
|
70
|
+
function debounce(key: string, fn: () => void, ms: number): void {
|
|
71
|
+
const existing = debounceTimers.get(key);
|
|
72
|
+
if (existing) {
|
|
73
|
+
clearTimeout(existing);
|
|
74
|
+
}
|
|
75
|
+
debounceTimers.set(
|
|
76
|
+
key,
|
|
77
|
+
setTimeout(() => {
|
|
78
|
+
debounceTimers.delete(key);
|
|
79
|
+
fn();
|
|
80
|
+
}, ms)
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
85
|
+
// Guard Watcher Implementation
|
|
86
|
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Guard Watcher μμ±
|
|
90
|
+
*/
|
|
91
|
+
export function createGuardWatcher(options: WatcherOptions): GuardWatcher {
|
|
92
|
+
const { config, rootDir, onViolation, onFileAnalyzed, silent } = options;
|
|
93
|
+
|
|
94
|
+
// λ μ΄μ΄ μ μ κ°μ Έμ€κΈ°
|
|
95
|
+
const layers = resolveLayerDefinitions(config);
|
|
96
|
+
const hierarchy = resolveHierarchy(config);
|
|
97
|
+
|
|
98
|
+
// μ€μ κΈ°λ³Έκ° μ μ©
|
|
99
|
+
const srcDir = config.srcDir ?? DEFAULT_GUARD_CONFIG.srcDir;
|
|
100
|
+
const debounceMs = config.debounceMs ?? DEFAULT_GUARD_CONFIG.debounceMs;
|
|
101
|
+
const exclude = config.exclude ?? DEFAULT_GUARD_CONFIG.exclude;
|
|
102
|
+
|
|
103
|
+
let watcher: FSWatcher | null = null;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* νμΌ λΆμ λ° κ²μ¦
|
|
107
|
+
*/
|
|
108
|
+
async function processFile(filePath: string, event: WatcherEvent): Promise<void> {
|
|
109
|
+
// μμ λ νμΌ
|
|
110
|
+
if (event === "unlink") {
|
|
111
|
+
analysisCache.delete(filePath);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// λΆμ λμμΈμ§ νμΈ
|
|
116
|
+
if (!shouldAnalyzeFile(filePath, config, rootDir)) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
// νμΌ λΆμ
|
|
122
|
+
const analysis = await analyzeFile(filePath, layers, rootDir);
|
|
123
|
+
|
|
124
|
+
// μΊμ μ μ₯
|
|
125
|
+
if (config.cache !== false) {
|
|
126
|
+
analysisCache.set(filePath, analysis);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// κ²μ¦
|
|
130
|
+
const violations = validateFileAnalysis(analysis, layers, config);
|
|
131
|
+
|
|
132
|
+
// μ½λ°± νΈμΆ
|
|
133
|
+
onFileAnalyzed?.(analysis, violations);
|
|
134
|
+
|
|
135
|
+
const realtimeOutput = config.realtimeOutput ?? DEFAULT_GUARD_CONFIG.realtimeOutput;
|
|
136
|
+
|
|
137
|
+
// μλ° μ²λ¦¬
|
|
138
|
+
for (const violation of violations) {
|
|
139
|
+
onViolation?.(violation);
|
|
140
|
+
|
|
141
|
+
if (!silent) {
|
|
142
|
+
switch (realtimeOutput) {
|
|
143
|
+
case "agent":
|
|
144
|
+
console.log(formatViolationForAgent(violation, config.preset));
|
|
145
|
+
break;
|
|
146
|
+
case "json":
|
|
147
|
+
console.log(formatViolationAsAgentJSON(violation, config.preset));
|
|
148
|
+
break;
|
|
149
|
+
case "console":
|
|
150
|
+
default:
|
|
151
|
+
printRealtimeViolation(violation);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
} catch (error) {
|
|
156
|
+
if (!silent) {
|
|
157
|
+
console.error(`[Guard] Error analyzing ${filePath}:`, error);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* νμΌ λ³κ²½ νΈλ€λ¬
|
|
164
|
+
*/
|
|
165
|
+
function handleFileChange(event: WatcherEvent, filePath: string): void {
|
|
166
|
+
debounce(filePath, () => processFile(filePath, event), debounceMs);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* μ 체 μ€μΊ
|
|
171
|
+
*/
|
|
172
|
+
async function scanAll(): Promise<ViolationReport> {
|
|
173
|
+
const startTime = Date.now();
|
|
174
|
+
const violations: Violation[] = [];
|
|
175
|
+
const files: string[] = [];
|
|
176
|
+
const analyses: FileAnalysis[] = [];
|
|
177
|
+
|
|
178
|
+
// κΈλ‘λΈλ‘ λͺ¨λ νμΌ μ°ΎκΈ°
|
|
179
|
+
const { glob } = await getGlobModule();
|
|
180
|
+
const extensions = WATCH_EXTENSIONS.map((ext) => ext.slice(1)).join(",");
|
|
181
|
+
const scanRoots = new Set<string>([srcDir]);
|
|
182
|
+
if (config.fsRoutes) {
|
|
183
|
+
scanRoots.add("app");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const foundFilesSet = new Set<string>();
|
|
187
|
+
for (const root of scanRoots) {
|
|
188
|
+
const pattern = `${root}/**/*.{${extensions}}`;
|
|
189
|
+
const foundFiles = await glob(pattern, {
|
|
190
|
+
cwd: rootDir,
|
|
191
|
+
ignore: exclude,
|
|
192
|
+
absolute: true,
|
|
193
|
+
});
|
|
194
|
+
for (const file of foundFiles) {
|
|
195
|
+
foundFilesSet.add(file);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const foundFiles = Array.from(foundFilesSet);
|
|
200
|
+
|
|
201
|
+
// κ° νμΌ λΆμ
|
|
202
|
+
for (const filePath of foundFiles) {
|
|
203
|
+
if (!shouldAnalyzeFile(filePath, config, rootDir)) {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
files.push(filePath);
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
const analysis = await analyzeFile(filePath, layers, rootDir);
|
|
211
|
+
analyses.push(analysis);
|
|
212
|
+
const fileViolations = validateFileAnalysis(analysis, layers, config);
|
|
213
|
+
violations.push(...fileViolations);
|
|
214
|
+
|
|
215
|
+
if (config.cache !== false) {
|
|
216
|
+
analysisCache.set(filePath, analysis);
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
if (!silent) {
|
|
220
|
+
console.error(`[Guard] Error analyzing ${filePath}:`, error);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const endTime = Date.now();
|
|
226
|
+
|
|
227
|
+
// μν μμ‘΄μ± κ²μ¬ (μ 체 μ€μΊμμλ§)
|
|
228
|
+
if (analyses.length > 0) {
|
|
229
|
+
violations.push(...detectCircularDependencies(analyses, layers, config));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// 리ν¬νΈ μμ±
|
|
233
|
+
const report: ViolationReport = {
|
|
234
|
+
totalViolations: violations.length,
|
|
235
|
+
bySeverity: countBySeverity(violations),
|
|
236
|
+
byType: countByType(violations),
|
|
237
|
+
violations,
|
|
238
|
+
filesAnalyzed: files.length,
|
|
239
|
+
analysisTime: endTime - startTime,
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
return report;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
start(): void {
|
|
247
|
+
const scanRoots = new Set<string>([srcDir]);
|
|
248
|
+
if (config.fsRoutes) {
|
|
249
|
+
scanRoots.add("app");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const watchPatterns = Array.from(scanRoots).flatMap((root) =>
|
|
253
|
+
WATCH_EXTENSIONS.map((ext) => `${root}/**/*${ext}`)
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
watcher = watch(watchPatterns, {
|
|
257
|
+
cwd: rootDir,
|
|
258
|
+
ignored: exclude,
|
|
259
|
+
persistent: true,
|
|
260
|
+
ignoreInitial: false,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
watcher.on("add", (path) => handleFileChange("add", resolve(rootDir, path)));
|
|
264
|
+
watcher.on("change", (path) => handleFileChange("change", resolve(rootDir, path)));
|
|
265
|
+
watcher.on("unlink", (path) => handleFileChange("unlink", resolve(rootDir, path)));
|
|
266
|
+
|
|
267
|
+
const realtimeOutput = config.realtimeOutput ?? DEFAULT_GUARD_CONFIG.realtimeOutput;
|
|
268
|
+
if (!silent && realtimeOutput === "console") {
|
|
269
|
+
console.log(`[Guard] π‘οΈ Watching ${Array.from(scanRoots).join(", ")} for architecture violations...`);
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
close(): void {
|
|
274
|
+
if (watcher) {
|
|
275
|
+
watcher.close();
|
|
276
|
+
watcher = null;
|
|
277
|
+
}
|
|
278
|
+
clearAnalysisCache();
|
|
279
|
+
},
|
|
280
|
+
|
|
281
|
+
scanAll,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
286
|
+
// Helpers
|
|
287
|
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* λ μ΄μ΄ μ μ ν΄μ
|
|
291
|
+
*/
|
|
292
|
+
function resolveLayerDefinitions(config: GuardConfig): LayerDefinition[] {
|
|
293
|
+
// 컀μ€ν
λ μ΄μ΄κ° μμΌλ©΄ μ¬μ©
|
|
294
|
+
if (config.layers && config.layers.length > 0) {
|
|
295
|
+
return config.layers;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ν리μ
μ¬μ©
|
|
299
|
+
if (config.preset) {
|
|
300
|
+
const preset = getPreset(config.preset);
|
|
301
|
+
let layers = [...preset.layers];
|
|
302
|
+
|
|
303
|
+
// μ€λ²λΌμ΄λ μ μ©
|
|
304
|
+
if (config.override?.layers) {
|
|
305
|
+
layers = layers.map((layer) => {
|
|
306
|
+
const override = config.override?.layers?.[layer.name];
|
|
307
|
+
if (override) {
|
|
308
|
+
return { ...layer, ...override };
|
|
309
|
+
}
|
|
310
|
+
return layer;
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return layers;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return [];
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* κ³μΈ΅ ꡬ쑰 ν΄μ
|
|
322
|
+
*/
|
|
323
|
+
function resolveHierarchy(config: GuardConfig): string[] {
|
|
324
|
+
if (config.preset) {
|
|
325
|
+
const preset = getPreset(config.preset);
|
|
326
|
+
return preset.hierarchy;
|
|
327
|
+
}
|
|
328
|
+
return [];
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* μ¬κ°λλ³ μΉ΄μ΄νΈ
|
|
333
|
+
*/
|
|
334
|
+
function countBySeverity(violations: Violation[]): Record<Severity, number> {
|
|
335
|
+
const counts: Record<Severity, number> = {
|
|
336
|
+
error: 0,
|
|
337
|
+
warn: 0,
|
|
338
|
+
info: 0,
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
for (const v of violations) {
|
|
342
|
+
counts[v.severity]++;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return counts;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* νμ
λ³ μΉ΄μ΄νΈ
|
|
350
|
+
*/
|
|
351
|
+
function countByType(violations: Violation[]): Record<ViolationType, number> {
|
|
352
|
+
const counts: Record<ViolationType, number> = {
|
|
353
|
+
"layer-violation": 0,
|
|
354
|
+
"circular-dependency": 0,
|
|
355
|
+
"cross-slice": 0,
|
|
356
|
+
"deep-nesting": 0,
|
|
357
|
+
"file-type": 0,
|
|
358
|
+
"invalid-shared-segment": 0,
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
for (const v of violations) {
|
|
362
|
+
counts[v.type]++;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return counts;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
369
|
+
// Convenience Functions
|
|
370
|
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* λ¨μΌ νμΌ κ²μ¬ (μΌνμ±)
|
|
374
|
+
*/
|
|
375
|
+
export async function checkFile(
|
|
376
|
+
filePath: string,
|
|
377
|
+
config: GuardConfig,
|
|
378
|
+
rootDir: string
|
|
379
|
+
): Promise<Violation[]> {
|
|
380
|
+
const layers = resolveLayerDefinitions(config);
|
|
381
|
+
|
|
382
|
+
if (!shouldAnalyzeFile(filePath, config, rootDir)) {
|
|
383
|
+
return [];
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const analysis = await analyzeFile(filePath, layers, rootDir);
|
|
387
|
+
return validateFileAnalysis(analysis, layers, config);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* λλ ν 리 μ 체 κ²μ¬ (μΌνμ±)
|
|
392
|
+
*/
|
|
393
|
+
export async function checkDirectory(
|
|
394
|
+
config: GuardConfig,
|
|
395
|
+
rootDir: string
|
|
396
|
+
): Promise<ViolationReport> {
|
|
397
|
+
const watcher = createGuardWatcher({
|
|
398
|
+
config,
|
|
399
|
+
rootDir,
|
|
400
|
+
silent: true,
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
return watcher.scanAll();
|
|
404
|
+
}
|