@mandujs/core 0.9.46 → 0.11.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.
Files changed (56) hide show
  1. package/README.md +79 -10
  2. package/package.json +1 -1
  3. package/src/brain/doctor/config-analyzer.ts +498 -0
  4. package/src/brain/doctor/index.ts +10 -0
  5. package/src/change/snapshot.ts +46 -1
  6. package/src/change/types.ts +13 -0
  7. package/src/config/index.ts +9 -2
  8. package/src/config/mcp-ref.ts +348 -0
  9. package/src/config/mcp-status.ts +348 -0
  10. package/src/config/metadata.test.ts +308 -0
  11. package/src/config/metadata.ts +293 -0
  12. package/src/config/symbols.ts +144 -0
  13. package/src/config/validate.ts +122 -65
  14. package/src/config/watcher.ts +311 -0
  15. package/src/contract/index.ts +26 -25
  16. package/src/contract/protection.ts +364 -0
  17. package/src/error/domains.ts +265 -0
  18. package/src/error/index.ts +25 -13
  19. package/src/errors/extractor.ts +409 -0
  20. package/src/errors/index.ts +19 -0
  21. package/src/filling/context.ts +29 -1
  22. package/src/filling/deps.ts +238 -0
  23. package/src/filling/filling.ts +94 -8
  24. package/src/filling/index.ts +18 -0
  25. package/src/guard/analyzer.ts +7 -2
  26. package/src/guard/config-guard.ts +281 -0
  27. package/src/guard/decision-memory.test.ts +293 -0
  28. package/src/guard/decision-memory.ts +532 -0
  29. package/src/guard/healing.test.ts +259 -0
  30. package/src/guard/healing.ts +874 -0
  31. package/src/guard/index.ts +119 -0
  32. package/src/guard/negotiation.test.ts +282 -0
  33. package/src/guard/negotiation.ts +975 -0
  34. package/src/guard/semantic-slots.test.ts +379 -0
  35. package/src/guard/semantic-slots.ts +796 -0
  36. package/src/index.ts +4 -1
  37. package/src/lockfile/generate.ts +259 -0
  38. package/src/lockfile/index.ts +186 -0
  39. package/src/lockfile/lockfile.test.ts +410 -0
  40. package/src/lockfile/types.ts +184 -0
  41. package/src/lockfile/validate.ts +308 -0
  42. package/src/logging/index.ts +22 -0
  43. package/src/logging/transports.ts +365 -0
  44. package/src/plugins/index.ts +38 -0
  45. package/src/plugins/registry.ts +377 -0
  46. package/src/plugins/types.ts +363 -0
  47. package/src/runtime/security.ts +155 -0
  48. package/src/runtime/server.ts +318 -256
  49. package/src/runtime/session-key.ts +328 -0
  50. package/src/utils/differ.test.ts +342 -0
  51. package/src/utils/differ.ts +482 -0
  52. package/src/utils/hasher.test.ts +326 -0
  53. package/src/utils/hasher.ts +319 -0
  54. package/src/utils/index.ts +29 -0
  55. package/src/utils/safe-io.ts +188 -0
  56. package/src/utils/string-safe.ts +298 -0
@@ -0,0 +1,293 @@
1
+ /**
2
+ * Mandu Symbol 메타데이터 유틸리티 🏷️
3
+ *
4
+ * Zod 스키마에 메타데이터를 부착하고 조회하는 유틸리티
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { z } from "zod";
9
+ * import { withMetadata, getMetadata, SENSITIVE_FIELD } from "./metadata";
10
+ *
11
+ * const tokenSchema = withMetadata(
12
+ * z.string(),
13
+ * SENSITIVE_FIELD,
14
+ * { redactIn: ["log", "diff"] }
15
+ * );
16
+ *
17
+ * const metadata = getMetadata(tokenSchema, SENSITIVE_FIELD);
18
+ * // { redactIn: ["log", "diff"] }
19
+ * ```
20
+ */
21
+
22
+ import type { z } from "zod";
23
+ import {
24
+ type SymbolMetadataMap,
25
+ ALL_METADATA_SYMBOLS,
26
+ isManduMetadataSymbol,
27
+ SENSITIVE_FIELD,
28
+ PROTECTED_FIELD,
29
+ FIELD_SOURCE,
30
+ SCHEMA_REFERENCE,
31
+ VALIDATION_CONTEXT,
32
+ } from "./symbols.js";
33
+
34
+ // ============================================
35
+ // 메타데이터 부착
36
+ // ============================================
37
+
38
+ /**
39
+ * Zod 스키마에 메타데이터 부착
40
+ *
41
+ * @param schema Zod 스키마
42
+ * @param key 메타데이터 심볼 키
43
+ * @param value 메타데이터 값
44
+ * @returns 메타데이터가 부착된 스키마 (원본 수정)
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * const schema = withMetadata(
49
+ * z.string(),
50
+ * SENSITIVE_FIELD,
51
+ * { redactIn: ["log"] }
52
+ * );
53
+ * ```
54
+ */
55
+ export function withMetadata<
56
+ T extends z.ZodType,
57
+ K extends keyof SymbolMetadataMap,
58
+ >(
59
+ schema: T,
60
+ key: K,
61
+ value: SymbolMetadataMap[K]
62
+ ): T {
63
+ (schema as any)[key] = value;
64
+ return schema;
65
+ }
66
+
67
+ /**
68
+ * 여러 메타데이터를 한 번에 부착
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * const schema = withMetadataMultiple(z.string(), [
73
+ * [SENSITIVE_FIELD, { redactIn: ["log"] }],
74
+ * [PROTECTED_FIELD, { reason: "Security" }],
75
+ * ]);
76
+ * ```
77
+ */
78
+ export function withMetadataMultiple<T extends z.ZodType>(
79
+ schema: T,
80
+ entries: Array<[symbol, unknown]>
81
+ ): T {
82
+ for (const [key, value] of entries) {
83
+ (schema as any)[key] = value;
84
+ }
85
+ return schema;
86
+ }
87
+
88
+ // ============================================
89
+ // 메타데이터 조회
90
+ // ============================================
91
+
92
+ /**
93
+ * 스키마에서 메타데이터 조회
94
+ *
95
+ * @param schema Zod 스키마
96
+ * @param key 메타데이터 심볼 키
97
+ * @returns 메타데이터 값 또는 undefined
98
+ */
99
+ export function getMetadata<K extends keyof SymbolMetadataMap>(
100
+ schema: z.ZodType,
101
+ key: K
102
+ ): SymbolMetadataMap[K] | undefined {
103
+ return (schema as any)[key];
104
+ }
105
+
106
+ /**
107
+ * 스키마에 특정 메타데이터가 있는지 확인
108
+ */
109
+ export function hasMetadata(schema: z.ZodType, key: symbol): boolean {
110
+ return key in (schema as any);
111
+ }
112
+
113
+ /**
114
+ * 스키마의 모든 mandu 메타데이터 조회
115
+ */
116
+ export function getAllMetadata(
117
+ schema: z.ZodType
118
+ ): Partial<SymbolMetadataMap> {
119
+ const result: Partial<SymbolMetadataMap> = {};
120
+
121
+ for (const sym of ALL_METADATA_SYMBOLS) {
122
+ if (sym in (schema as any)) {
123
+ (result as any)[sym] = (schema as any)[sym];
124
+ }
125
+ }
126
+
127
+ return result;
128
+ }
129
+
130
+ // ============================================
131
+ // 메타데이터 제거
132
+ // ============================================
133
+
134
+ /**
135
+ * 스키마에서 메타데이터 제거
136
+ */
137
+ export function removeMetadata<T extends z.ZodType>(
138
+ schema: T,
139
+ key: symbol
140
+ ): T {
141
+ delete (schema as any)[key];
142
+ return schema;
143
+ }
144
+
145
+ /**
146
+ * 스키마에서 모든 mandu 메타데이터 제거
147
+ */
148
+ export function clearAllMetadata<T extends z.ZodType>(schema: T): T {
149
+ for (const sym of ALL_METADATA_SYMBOLS) {
150
+ if (sym in (schema as any)) {
151
+ delete (schema as any)[sym];
152
+ }
153
+ }
154
+ return schema;
155
+ }
156
+
157
+ // ============================================
158
+ // 메타데이터 복사
159
+ // ============================================
160
+
161
+ /**
162
+ * 한 스키마의 메타데이터를 다른 스키마로 복사
163
+ */
164
+ export function copyMetadata<T extends z.ZodType>(
165
+ from: z.ZodType,
166
+ to: T
167
+ ): T {
168
+ for (const sym of ALL_METADATA_SYMBOLS) {
169
+ if (sym in (from as any)) {
170
+ (to as any)[sym] = (from as any)[sym];
171
+ }
172
+ }
173
+ return to;
174
+ }
175
+
176
+ // ============================================
177
+ // 스키마 체인 헬퍼
178
+ // ============================================
179
+
180
+ /**
181
+ * 메타데이터와 함께 스키마를 체이닝하기 위한 빌더
182
+ *
183
+ * @example
184
+ * ```typescript
185
+ * const schema = schemaWithMeta(z.string())
186
+ * .sensitive({ redactIn: ["log"] })
187
+ * .protected({ reason: "Security" })
188
+ * .build();
189
+ * ```
190
+ */
191
+ export function schemaWithMeta<T extends z.ZodType>(schema: T) {
192
+ return new SchemaMetaBuilder(schema);
193
+ }
194
+
195
+ class SchemaMetaBuilder<T extends z.ZodType> {
196
+ constructor(private schema: T) {}
197
+
198
+ /**
199
+ * 민감 필드로 마킹
200
+ */
201
+ sensitive(meta: SymbolMetadataMap[typeof SENSITIVE_FIELD]) {
202
+ withMetadata(this.schema, SENSITIVE_FIELD, meta);
203
+ return this;
204
+ }
205
+
206
+ /**
207
+ * 보호된 필드로 마킹
208
+ */
209
+ protected(meta: SymbolMetadataMap[typeof PROTECTED_FIELD]) {
210
+ withMetadata(this.schema, PROTECTED_FIELD, meta);
211
+ return this;
212
+ }
213
+
214
+ /**
215
+ * 필드 소스 설정
216
+ */
217
+ source(meta: SymbolMetadataMap[typeof FIELD_SOURCE]) {
218
+ withMetadata(this.schema, FIELD_SOURCE, meta);
219
+ return this;
220
+ }
221
+
222
+ /**
223
+ * 스키마 참조 설정
224
+ */
225
+ ref(meta: SymbolMetadataMap[typeof SCHEMA_REFERENCE]) {
226
+ withMetadata(this.schema, SCHEMA_REFERENCE, meta);
227
+ return this;
228
+ }
229
+
230
+ /**
231
+ * 검증 컨텍스트 설정
232
+ */
233
+ validation(meta: SymbolMetadataMap[typeof VALIDATION_CONTEXT]) {
234
+ withMetadata(this.schema, VALIDATION_CONTEXT, meta);
235
+ return this;
236
+ }
237
+
238
+ /**
239
+ * 커스텀 메타데이터 추가
240
+ */
241
+ meta<K extends keyof SymbolMetadataMap>(key: K, value: SymbolMetadataMap[K]) {
242
+ withMetadata(this.schema, key, value);
243
+ return this;
244
+ }
245
+
246
+ /**
247
+ * 스키마 반환
248
+ */
249
+ build(): T {
250
+ return this.schema;
251
+ }
252
+ }
253
+
254
+ // ============================================
255
+ // 유틸리티
256
+ // ============================================
257
+
258
+ /**
259
+ * 객체의 모든 Symbol 키 가져오기
260
+ */
261
+ export function getSymbolKeys(obj: object): symbol[] {
262
+ return Object.getOwnPropertySymbols(obj);
263
+ }
264
+
265
+ /**
266
+ * mandu 메타데이터 심볼만 필터링
267
+ */
268
+ export function getManduSymbolKeys(obj: object): symbol[] {
269
+ return getSymbolKeys(obj).filter(isManduMetadataSymbol);
270
+ }
271
+
272
+ /**
273
+ * 스키마에 메타데이터가 있는지 확인
274
+ */
275
+ export function hasAnyMetadata(schema: z.ZodType): boolean {
276
+ return getManduSymbolKeys(schema as any).length > 0;
277
+ }
278
+
279
+ /**
280
+ * 메타데이터를 일반 객체로 직렬화 (디버깅용)
281
+ */
282
+ export function serializeMetadata(
283
+ schema: z.ZodType
284
+ ): Record<string, unknown> {
285
+ const result: Record<string, unknown> = {};
286
+
287
+ for (const sym of getManduSymbolKeys(schema as any)) {
288
+ const name = sym.description ?? sym.toString();
289
+ result[name] = (schema as any)[sym];
290
+ }
291
+
292
+ return result;
293
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Mandu Symbol 상수 정의 🔣
3
+ *
4
+ * ont-run의 Symbol 기반 메타데이터 패턴 참고
5
+ * @see DNA/ont-run/src/config/categorical.ts
6
+ * @see docs/plans/08_ont-run_adoption_plan.md - 섹션 3.2
7
+ *
8
+ * Symbol.for()를 사용하여 전역 심볼 레지스트리에 등록
9
+ * → 모듈 간에도 동일한 심볼 공유 가능
10
+ */
11
+
12
+ // ============================================
13
+ // 메타데이터 심볼
14
+ // ============================================
15
+
16
+ /**
17
+ * MCP 서버 상태 메타데이터
18
+ * - connected, disconnected, error 등
19
+ */
20
+ export const MCP_SERVER_STATUS = Symbol.for("mandu:mcpServerStatus");
21
+
22
+ /**
23
+ * 검증 컨텍스트 메타데이터
24
+ * - 커스텀 검증 규칙, 에러 메시지 등
25
+ */
26
+ export const VALIDATION_CONTEXT = Symbol.for("mandu:validationContext");
27
+
28
+ /**
29
+ * 스키마 참조 메타데이터
30
+ * - 다른 스키마/함수 참조
31
+ */
32
+ export const SCHEMA_REFERENCE = Symbol.for("mandu:schemaReference");
33
+
34
+ /**
35
+ * 필드 소스 메타데이터
36
+ * - 필드 값의 출처 (env, config, default 등)
37
+ */
38
+ export const FIELD_SOURCE = Symbol.for("mandu:fieldSource");
39
+
40
+ /**
41
+ * 민감 정보 마커
42
+ * - 로깅/출력 시 마스킹 필요
43
+ */
44
+ export const SENSITIVE_FIELD = Symbol.for("mandu:sensitiveField");
45
+
46
+ /**
47
+ * 보호된 필드 마커
48
+ * - AI 에이전트 수정 불가
49
+ */
50
+ export const PROTECTED_FIELD = Symbol.for("mandu:protectedField");
51
+
52
+ /**
53
+ * 기본값 출처 메타데이터
54
+ * - 기본값이 어디서 왔는지 추적
55
+ */
56
+ export const DEFAULT_SOURCE = Symbol.for("mandu:defaultSource");
57
+
58
+ /**
59
+ * 런타임 주입 메타데이터
60
+ * - 런타임에 주입되는 값 표시
61
+ */
62
+ export const RUNTIME_INJECTED = Symbol.for("mandu:runtimeInjected");
63
+
64
+ // ============================================
65
+ // 타입 정의
66
+ // ============================================
67
+
68
+ export interface McpServerStatusMetadata {
69
+ status: "connected" | "disconnected" | "error" | "unknown";
70
+ lastChecked?: string;
71
+ error?: string;
72
+ }
73
+
74
+ export interface ValidationContextMetadata {
75
+ customMessage?: string;
76
+ severity?: "error" | "warning" | "info";
77
+ autoFix?: boolean;
78
+ }
79
+
80
+ export interface SchemaReferenceMetadata {
81
+ type: "mcpServer" | "function" | "schema" | "env";
82
+ name: string;
83
+ optional?: boolean;
84
+ }
85
+
86
+ export interface FieldSourceMetadata {
87
+ source: "env" | "config" | "default" | "computed" | "injected";
88
+ key?: string;
89
+ fallback?: unknown;
90
+ }
91
+
92
+ export interface SensitiveFieldMetadata {
93
+ redactIn: ("log" | "diff" | "snapshot")[];
94
+ mask?: string;
95
+ }
96
+
97
+ export interface ProtectedFieldMetadata {
98
+ reason: string;
99
+ allowedModifiers?: string[];
100
+ }
101
+
102
+ // ============================================
103
+ // 심볼-타입 매핑
104
+ // ============================================
105
+
106
+ export type SymbolMetadataMap = {
107
+ [MCP_SERVER_STATUS]: McpServerStatusMetadata;
108
+ [VALIDATION_CONTEXT]: ValidationContextMetadata;
109
+ [SCHEMA_REFERENCE]: SchemaReferenceMetadata;
110
+ [FIELD_SOURCE]: FieldSourceMetadata;
111
+ [SENSITIVE_FIELD]: SensitiveFieldMetadata;
112
+ [PROTECTED_FIELD]: ProtectedFieldMetadata;
113
+ [DEFAULT_SOURCE]: string;
114
+ [RUNTIME_INJECTED]: boolean;
115
+ };
116
+
117
+ // ============================================
118
+ // 심볼 목록 (순회용)
119
+ // ============================================
120
+
121
+ export const ALL_METADATA_SYMBOLS = [
122
+ MCP_SERVER_STATUS,
123
+ VALIDATION_CONTEXT,
124
+ SCHEMA_REFERENCE,
125
+ FIELD_SOURCE,
126
+ SENSITIVE_FIELD,
127
+ PROTECTED_FIELD,
128
+ DEFAULT_SOURCE,
129
+ RUNTIME_INJECTED,
130
+ ] as const;
131
+
132
+ /**
133
+ * 심볼이 mandu 메타데이터 심볼인지 확인
134
+ */
135
+ export function isManduMetadataSymbol(sym: symbol): boolean {
136
+ return ALL_METADATA_SYMBOLS.includes(sym as any);
137
+ }
138
+
139
+ /**
140
+ * 심볼 이름 가져오기 (디버깅용)
141
+ */
142
+ export function getSymbolName(sym: symbol): string | undefined {
143
+ return sym.description?.replace("mandu:", "");
144
+ }
@@ -1,78 +1,135 @@
1
- import { z, ZodError } from "zod";
1
+ import { z, ZodError, ZodIssueCode } from "zod";
2
2
  import path from "path";
3
3
  import { pathToFileURL } from "url";
4
4
  import { CONFIG_FILES, coerceConfig } from "./mandu";
5
5
  import { readJsonFile } from "../utils/bun";
6
6
 
7
7
  /**
8
- * Mandu 설정 스키마
8
+ * DNA-003: Strict mode schema helper
9
+ *
10
+ * Creates a schema that warns about unknown keys instead of failing
11
+ * This provides the benefits of .strict() while maintaining compatibility
12
+ */
13
+ function strictWithWarnings<T extends z.ZodRawShape>(
14
+ schema: z.ZodObject<T>,
15
+ schemaName: string
16
+ ): z.ZodObject<T> {
17
+ return schema.superRefine((data, ctx) => {
18
+ if (typeof data !== "object" || data === null) return;
19
+
20
+ const knownKeys = new Set(Object.keys(schema.shape));
21
+ const unknownKeys = Object.keys(data).filter((key) => !knownKeys.has(key));
22
+
23
+ if (unknownKeys.length > 0 && process.env.MANDU_STRICT !== "0") {
24
+ // In strict mode (default), add warnings to issues
25
+ for (const key of unknownKeys) {
26
+ ctx.addIssue({
27
+ code: ZodIssueCode.unrecognized_keys,
28
+ keys: [key],
29
+ message: `Unknown key '${key}' in ${schemaName}. Did you mean one of: ${[...knownKeys].join(", ")}?`,
30
+ });
31
+ }
32
+ }
33
+ });
34
+ }
35
+
36
+ /**
37
+ * Server 설정 스키마 (strict)
38
+ */
39
+ const ServerConfigSchema = z
40
+ .object({
41
+ port: z.number().min(1).max(65535).default(3000),
42
+ hostname: z.string().default("localhost"),
43
+ cors: z
44
+ .union([
45
+ z.boolean(),
46
+ z.object({
47
+ origin: z.union([z.string(), z.array(z.string())]).optional(),
48
+ methods: z.array(z.string()).optional(),
49
+ credentials: z.boolean().optional(),
50
+ }).strict(),
51
+ ])
52
+ .default(false),
53
+ streaming: z.boolean().default(false),
54
+ })
55
+ .strict();
56
+
57
+ /**
58
+ * Guard 설정 스키마 (strict)
59
+ */
60
+ const GuardConfigSchema = z
61
+ .object({
62
+ preset: z.enum(["mandu", "fsd", "clean", "hexagonal", "atomic"]).default("mandu"),
63
+ srcDir: z.string().default("src"),
64
+ exclude: z.array(z.string()).default([]),
65
+ realtime: z.boolean().default(true),
66
+ rules: z.record(z.enum(["error", "warn", "warning", "off"])).optional(),
67
+ })
68
+ .strict();
69
+
70
+ /**
71
+ * Build 설정 스키마 (strict)
72
+ */
73
+ const BuildConfigSchema = z
74
+ .object({
75
+ outDir: z.string().default(".mandu"),
76
+ minify: z.boolean().default(true),
77
+ sourcemap: z.boolean().default(false),
78
+ splitting: z.boolean().default(false),
79
+ })
80
+ .strict();
81
+
82
+ /**
83
+ * Dev 설정 스키마 (strict)
84
+ */
85
+ const DevConfigSchema = z
86
+ .object({
87
+ hmr: z.boolean().default(true),
88
+ watchDirs: z.array(z.string()).default([]),
89
+ })
90
+ .strict();
91
+
92
+ /**
93
+ * FS Routes 설정 스키마 (strict)
94
+ */
95
+ const FsRoutesConfigSchema = z
96
+ .object({
97
+ routesDir: z.string().default("app"),
98
+ extensions: z.array(z.string()).default([".tsx", ".ts", ".jsx", ".js"]),
99
+ exclude: z.array(z.string()).default([]),
100
+ islandSuffix: z.string().default(".island"),
101
+ legacyManifestPath: z.string().optional(),
102
+ mergeWithLegacy: z.boolean().default(true),
103
+ })
104
+ .strict();
105
+
106
+ /**
107
+ * SEO 설정 스키마 (strict)
108
+ */
109
+ const SeoConfigSchema = z
110
+ .object({
111
+ enabled: z.boolean().default(true),
112
+ defaultTitle: z.string().optional(),
113
+ titleTemplate: z.string().optional(),
114
+ })
115
+ .strict();
116
+
117
+ /**
118
+ * Mandu 설정 스키마 (DNA-003: strict mode)
119
+ *
120
+ * 알 수 없는 키가 있으면 오류 발생 → 오타 즉시 감지
121
+ * MANDU_STRICT=0 으로 비활성화 가능
9
122
  */
10
123
  export const ManduConfigSchema = z
11
124
  .object({
12
- server: z
13
- .object({
14
- port: z.number().min(1).max(65535).default(3000),
15
- hostname: z.string().default("localhost"),
16
- cors: z
17
- .union([
18
- z.boolean(),
19
- z.object({
20
- origin: z.union([z.string(), z.array(z.string())]).optional(),
21
- methods: z.array(z.string()).optional(),
22
- credentials: z.boolean().optional(),
23
- }),
24
- ])
25
- .default(false),
26
- streaming: z.boolean().default(false),
27
- })
28
- .default({}),
29
-
30
- guard: z
31
- .object({
32
- preset: z.enum(["mandu", "fsd", "clean", "hexagonal", "atomic"]).default("mandu"),
33
- srcDir: z.string().default("src"),
34
- exclude: z.array(z.string()).default([]),
35
- realtime: z.boolean().default(true),
36
- rules: z.record(z.enum(["error", "warn", "warning", "off"])).optional(),
37
- })
38
- .default({}),
39
-
40
- build: z
41
- .object({
42
- outDir: z.string().default(".mandu"),
43
- minify: z.boolean().default(true),
44
- sourcemap: z.boolean().default(false),
45
- splitting: z.boolean().default(false),
46
- })
47
- .default({}),
48
-
49
- dev: z
50
- .object({
51
- hmr: z.boolean().default(true),
52
- watchDirs: z.array(z.string()).default([]),
53
- })
54
- .default({}),
55
-
56
- fsRoutes: z
57
- .object({
58
- routesDir: z.string().default("app"),
59
- extensions: z.array(z.string()).default([".tsx", ".ts", ".jsx", ".js"]),
60
- exclude: z.array(z.string()).default([]),
61
- islandSuffix: z.string().default(".island"),
62
- legacyManifestPath: z.string().optional(),
63
- mergeWithLegacy: z.boolean().default(true),
64
- })
65
- .default({}),
66
-
67
- seo: z
68
- .object({
69
- enabled: z.boolean().default(true),
70
- defaultTitle: z.string().optional(),
71
- titleTemplate: z.string().optional(),
72
- })
73
- .default({}),
125
+ server: ServerConfigSchema.default({}),
126
+ guard: GuardConfigSchema.default({}),
127
+ build: BuildConfigSchema.default({}),
128
+ dev: DevConfigSchema.default({}),
129
+ fsRoutes: FsRoutesConfigSchema.default({}),
130
+ seo: SeoConfigSchema.default({}),
74
131
  })
75
- .passthrough();
132
+ .strict();
76
133
 
77
134
  export type ValidatedManduConfig = z.infer<typeof ManduConfigSchema>;
78
135