@mandujs/cli 0.9.45 β 0.10.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/package.json +1 -1
- package/src/commands/build.ts +27 -19
- package/src/commands/check.ts +290 -238
- package/src/commands/dev.ts +486 -440
- package/src/commands/init.ts +128 -21
- package/src/commands/lock.ts +434 -0
- package/src/main.ts +445 -428
package/src/commands/dev.ts
CHANGED
|
@@ -1,440 +1,486 @@
|
|
|
1
|
-
import {
|
|
2
|
-
startServer,
|
|
3
|
-
registerApiHandler,
|
|
4
|
-
registerPageLoader,
|
|
5
|
-
registerPageHandler,
|
|
6
|
-
registerLayoutLoader,
|
|
7
|
-
startDevBundler,
|
|
8
|
-
createHMRServer,
|
|
9
|
-
needsHydration,
|
|
10
|
-
loadEnv,
|
|
11
|
-
watchFSRoutes,
|
|
12
|
-
clearDefaultRegistry,
|
|
13
|
-
createGuardWatcher,
|
|
14
|
-
checkDirectory,
|
|
15
|
-
printReport,
|
|
16
|
-
formatReportForAgent,
|
|
17
|
-
formatReportAsAgentJSON,
|
|
18
|
-
getPreset,
|
|
19
|
-
validateAndReport,
|
|
20
|
-
isTailwindProject,
|
|
21
|
-
startCSSWatch,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
import
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
console.
|
|
80
|
-
console.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
1
|
+
import {
|
|
2
|
+
startServer,
|
|
3
|
+
registerApiHandler,
|
|
4
|
+
registerPageLoader,
|
|
5
|
+
registerPageHandler,
|
|
6
|
+
registerLayoutLoader,
|
|
7
|
+
startDevBundler,
|
|
8
|
+
createHMRServer,
|
|
9
|
+
needsHydration,
|
|
10
|
+
loadEnv,
|
|
11
|
+
watchFSRoutes,
|
|
12
|
+
clearDefaultRegistry,
|
|
13
|
+
createGuardWatcher,
|
|
14
|
+
checkDirectory,
|
|
15
|
+
printReport,
|
|
16
|
+
formatReportForAgent,
|
|
17
|
+
formatReportAsAgentJSON,
|
|
18
|
+
getPreset,
|
|
19
|
+
validateAndReport,
|
|
20
|
+
isTailwindProject,
|
|
21
|
+
startCSSWatch,
|
|
22
|
+
readLockfile,
|
|
23
|
+
readMcpConfig,
|
|
24
|
+
validateWithPolicy,
|
|
25
|
+
detectMode,
|
|
26
|
+
formatPolicyAction,
|
|
27
|
+
formatValidationResult,
|
|
28
|
+
type RoutesManifest,
|
|
29
|
+
type GuardConfig,
|
|
30
|
+
type Violation,
|
|
31
|
+
type CSSWatcher,
|
|
32
|
+
} from "@mandujs/core";
|
|
33
|
+
import { resolveFromCwd } from "../util/fs";
|
|
34
|
+
import { resolveOutputFormat } from "../util/output";
|
|
35
|
+
import { CLI_ERROR_CODES, printCLIError } from "../errors";
|
|
36
|
+
import { importFresh } from "../util/bun";
|
|
37
|
+
import { resolveManifest } from "../util/manifest";
|
|
38
|
+
import { resolveAvailablePort } from "../util/port";
|
|
39
|
+
import path from "path";
|
|
40
|
+
|
|
41
|
+
export interface DevOptions {
|
|
42
|
+
port?: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function dev(options: DevOptions = {}): Promise<void> {
|
|
46
|
+
const rootDir = resolveFromCwd(".");
|
|
47
|
+
const config = await validateAndReport(rootDir);
|
|
48
|
+
|
|
49
|
+
if (!config) {
|
|
50
|
+
printCLIError(CLI_ERROR_CODES.CONFIG_VALIDATION_FAILED);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Lockfile κ²μ¦ (μ€μ 무결μ±)
|
|
55
|
+
const lockfile = await readLockfile(rootDir);
|
|
56
|
+
let mcpConfig: Record<string, unknown> | null = null;
|
|
57
|
+
try {
|
|
58
|
+
mcpConfig = await readMcpConfig(rootDir);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.warn(
|
|
61
|
+
`β οΈ MCP μ€μ λ‘λ μ€ν¨: ${error instanceof Error ? error.message : String(error)}`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
const { result: lockResult, action, bypassed } = validateWithPolicy(
|
|
65
|
+
config,
|
|
66
|
+
lockfile,
|
|
67
|
+
detectMode(),
|
|
68
|
+
mcpConfig
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
if (action === "block") {
|
|
72
|
+
console.error("π μλ² μμ μ°¨λ¨: Lockfile λΆμΌμΉ");
|
|
73
|
+
console.error(" μ€μ μ΄ λ³κ²½λμμ΅λλ€. μλν λ³κ²½μ΄λΌλ©΄:");
|
|
74
|
+
console.error(" $ mandu lock");
|
|
75
|
+
console.error("");
|
|
76
|
+
console.error(" λ³κ²½ μ¬ν νμΈ:");
|
|
77
|
+
console.error(" $ mandu lock --diff");
|
|
78
|
+
if (lockResult) {
|
|
79
|
+
console.error("");
|
|
80
|
+
console.error(formatValidationResult(lockResult));
|
|
81
|
+
}
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const serverConfig = config.server ?? {};
|
|
86
|
+
const devConfig = config.dev ?? {};
|
|
87
|
+
const guardConfigFromFile = config.guard ?? {};
|
|
88
|
+
const HMR_OFFSET = 1;
|
|
89
|
+
|
|
90
|
+
console.log(`π₯ Mandu Dev Server`);
|
|
91
|
+
|
|
92
|
+
// Lockfile μν μΆλ ₯
|
|
93
|
+
if (action === "warn") {
|
|
94
|
+
console.log(`β οΈ ${formatPolicyAction(action, bypassed)}`);
|
|
95
|
+
} else if (lockfile && lockResult?.valid) {
|
|
96
|
+
console.log(`π μ€μ λ¬΄κ²°μ± νμΈλ¨ (${lockResult.currentHash?.slice(0, 8)})`);
|
|
97
|
+
} else if (!lockfile) {
|
|
98
|
+
console.log(`π‘ Lockfile μμ - 'mandu lock'μΌλ‘ μμ± κΆμ₯`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// .env νμΌ λ‘λ
|
|
102
|
+
const envResult = await loadEnv({
|
|
103
|
+
rootDir,
|
|
104
|
+
env: "development",
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (envResult.loaded.length > 0) {
|
|
108
|
+
console.log(`π νκ²½ λ³μ λ‘λ: ${envResult.loaded.join(", ")}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// λΌμ°νΈ μ€μΊ (FS Routes μ°μ , μμΌλ©΄ spec manifest)
|
|
112
|
+
console.log(`π λΌμ°νΈ μ€μΊ μ€...`);
|
|
113
|
+
let manifest: RoutesManifest;
|
|
114
|
+
let enableFsRoutes = false;
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const resolved = await resolveManifest(rootDir, { fsRoutes: config.fsRoutes });
|
|
118
|
+
manifest = resolved.manifest;
|
|
119
|
+
enableFsRoutes = resolved.source === "fs";
|
|
120
|
+
|
|
121
|
+
if (manifest.routes.length === 0) {
|
|
122
|
+
printCLIError(CLI_ERROR_CODES.DEV_NO_ROUTES);
|
|
123
|
+
console.log("π‘ app/ ν΄λμ page.tsx νμΌμ μμ±νμΈμ:");
|
|
124
|
+
console.log("");
|
|
125
|
+
console.log(" app/page.tsx β /");
|
|
126
|
+
console.log(" app/blog/page.tsx β /blog");
|
|
127
|
+
console.log(" app/api/users/route.ts β /api/users");
|
|
128
|
+
console.log("");
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
console.log(`β
${manifest.routes.length}κ° λΌμ°νΈ λ°κ²¬\n`);
|
|
133
|
+
} catch (error) {
|
|
134
|
+
printCLIError(CLI_ERROR_CODES.DEV_MANIFEST_NOT_FOUND);
|
|
135
|
+
console.error(error instanceof Error ? error.message : error);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
const guardPreset = guardConfigFromFile.preset || "mandu";
|
|
139
|
+
const guardFormat = resolveOutputFormat();
|
|
140
|
+
const guardConfig: GuardConfig | null =
|
|
141
|
+
guardConfigFromFile.realtime === false
|
|
142
|
+
? null
|
|
143
|
+
: {
|
|
144
|
+
preset: guardPreset,
|
|
145
|
+
srcDir: guardConfigFromFile.srcDir || "src",
|
|
146
|
+
realtime: guardConfigFromFile.realtime ?? true,
|
|
147
|
+
exclude: guardConfigFromFile.exclude,
|
|
148
|
+
realtimeOutput: guardFormat,
|
|
149
|
+
fsRoutes: enableFsRoutes
|
|
150
|
+
? {
|
|
151
|
+
noPageToPage: true,
|
|
152
|
+
pageCanImport: [
|
|
153
|
+
"client/pages",
|
|
154
|
+
"client/widgets",
|
|
155
|
+
"client/features",
|
|
156
|
+
"client/entities",
|
|
157
|
+
"client/shared",
|
|
158
|
+
"shared/contracts",
|
|
159
|
+
"shared/types",
|
|
160
|
+
"shared/utils/client",
|
|
161
|
+
],
|
|
162
|
+
layoutCanImport: [
|
|
163
|
+
"client/app",
|
|
164
|
+
"client/widgets",
|
|
165
|
+
"client/shared",
|
|
166
|
+
"shared/contracts",
|
|
167
|
+
"shared/types",
|
|
168
|
+
"shared/utils/client",
|
|
169
|
+
],
|
|
170
|
+
routeCanImport: [
|
|
171
|
+
"server/api",
|
|
172
|
+
"server/application",
|
|
173
|
+
"server/domain",
|
|
174
|
+
"server/infra",
|
|
175
|
+
"server/core",
|
|
176
|
+
"shared/contracts",
|
|
177
|
+
"shared/schema",
|
|
178
|
+
"shared/types",
|
|
179
|
+
"shared/utils/client",
|
|
180
|
+
"shared/utils/server",
|
|
181
|
+
"shared/env",
|
|
182
|
+
],
|
|
183
|
+
}
|
|
184
|
+
: undefined,
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
if (guardConfig) {
|
|
188
|
+
const preflightReport = await checkDirectory(guardConfig, rootDir);
|
|
189
|
+
if (preflightReport.bySeverity.error > 0) {
|
|
190
|
+
if (guardFormat === "json") {
|
|
191
|
+
console.log(formatReportAsAgentJSON(preflightReport, guardPreset));
|
|
192
|
+
} else if (guardFormat === "agent") {
|
|
193
|
+
console.log(formatReportForAgent(preflightReport, guardPreset));
|
|
194
|
+
} else {
|
|
195
|
+
printReport(preflightReport, getPreset(guardPreset).hierarchy);
|
|
196
|
+
}
|
|
197
|
+
console.error("\nβ Architecture Guard failed. Fix errors before starting dev server.");
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Layout κ²½λ‘ μΆμ (μ€λ³΅ λ±λ‘ λ°©μ§)
|
|
203
|
+
const registeredLayouts = new Set<string>();
|
|
204
|
+
|
|
205
|
+
// νΈλ€λ¬ λ±λ‘ ν¨μ
|
|
206
|
+
const registerHandlers = async (manifest: RoutesManifest, isReload = false) => {
|
|
207
|
+
// 리λ‘λ μ λ μ΄μμ μΊμ ν΄λ¦¬μ΄
|
|
208
|
+
if (isReload) {
|
|
209
|
+
registeredLayouts.clear();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
for (const route of manifest.routes) {
|
|
213
|
+
if (route.kind === "api") {
|
|
214
|
+
const modulePath = path.resolve(rootDir, route.module);
|
|
215
|
+
try {
|
|
216
|
+
// μΊμ 무ν¨ν (HMRμ©)
|
|
217
|
+
const module = await importFresh(modulePath);
|
|
218
|
+
let handler = module.default || module.handler || module;
|
|
219
|
+
|
|
220
|
+
// ManduFilling μΈμ€ν΄μ€λ₯Ό νΈλ€λ¬ ν¨μλ‘ λν
|
|
221
|
+
if (handler && typeof handler.handle === 'function') {
|
|
222
|
+
console.log(` π ManduFilling λν: ${route.id}`);
|
|
223
|
+
const filling = handler;
|
|
224
|
+
handler = async (req: Request, params?: Record<string, string>) => {
|
|
225
|
+
return filling.handle(req, params);
|
|
226
|
+
};
|
|
227
|
+
} else {
|
|
228
|
+
console.log(` β οΈ νΈλ€λ¬ νμ
: ${typeof handler}, handle: ${typeof handler?.handle}`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
registerApiHandler(route.id, handler);
|
|
232
|
+
console.log(` π‘ API: ${route.pattern} -> ${route.id}`);
|
|
233
|
+
} catch (error) {
|
|
234
|
+
console.error(` β API νΈλ€λ¬ λ‘λ μ€ν¨: ${route.id}`, error);
|
|
235
|
+
}
|
|
236
|
+
} else if (route.kind === "page" && route.componentModule) {
|
|
237
|
+
const componentPath = path.resolve(rootDir, route.componentModule);
|
|
238
|
+
const isIsland = needsHydration(route);
|
|
239
|
+
const hasLayout = route.layoutChain && route.layoutChain.length > 0;
|
|
240
|
+
|
|
241
|
+
// Layout λ‘λ λ±λ‘
|
|
242
|
+
if (route.layoutChain) {
|
|
243
|
+
for (const layoutPath of route.layoutChain) {
|
|
244
|
+
if (!registeredLayouts.has(layoutPath)) {
|
|
245
|
+
const absLayoutPath = path.resolve(rootDir, layoutPath);
|
|
246
|
+
registerLayoutLoader(layoutPath, async () => {
|
|
247
|
+
// μΊμ 무ν¨ν (HMRμ©)
|
|
248
|
+
return importFresh(absLayoutPath);
|
|
249
|
+
});
|
|
250
|
+
registeredLayouts.add(layoutPath);
|
|
251
|
+
console.log(` π¨ Layout: ${layoutPath}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// slotModuleμ΄ μμΌλ©΄ PageHandler μ¬μ© (filling.loader μ§μ)
|
|
257
|
+
if (route.slotModule) {
|
|
258
|
+
registerPageHandler(route.id, async () => {
|
|
259
|
+
const module = await importFresh(componentPath);
|
|
260
|
+
return module.default;
|
|
261
|
+
});
|
|
262
|
+
console.log(` π Page: ${route.pattern} -> ${route.id} (with loader)${isIsland ? " ποΈ" : ""}${hasLayout ? " π¨" : ""}`);
|
|
263
|
+
} else {
|
|
264
|
+
registerPageLoader(route.id, () => importFresh(componentPath));
|
|
265
|
+
console.log(` π Page: ${route.pattern} -> ${route.id}${isIsland ? " ποΈ" : ""}${hasLayout ? " π¨" : ""}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// μ΄κΈ° νΈλ€λ¬ λ±λ‘
|
|
272
|
+
await registerHandlers(manifest);
|
|
273
|
+
console.log("");
|
|
274
|
+
|
|
275
|
+
const envPort = process.env.PORT ? Number(process.env.PORT) : undefined;
|
|
276
|
+
const desiredPort =
|
|
277
|
+
options.port ??
|
|
278
|
+
(envPort && Number.isFinite(envPort) ? envPort : undefined) ??
|
|
279
|
+
serverConfig.port ??
|
|
280
|
+
3333;
|
|
281
|
+
|
|
282
|
+
const hasIslands = manifest.routes.some(
|
|
283
|
+
(r) => r.kind === "page" && r.clientModule && needsHydration(r)
|
|
284
|
+
);
|
|
285
|
+
const hmrEnabled = devConfig.hmr ?? true;
|
|
286
|
+
|
|
287
|
+
const { port } = await resolveAvailablePort(desiredPort, {
|
|
288
|
+
hostname: serverConfig.hostname,
|
|
289
|
+
offsets: hasIslands && hmrEnabled ? [0, HMR_OFFSET] : [0],
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
if (port !== desiredPort) {
|
|
293
|
+
console.warn(`β οΈ Port ${desiredPort} is in use. Using ${port} instead.`);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// HMR μλ² μμ (ν΄λΌμ΄μΈνΈ μ¬λ‘―μ΄ μλ κ²½μ°)
|
|
297
|
+
let hmrServer: ReturnType<typeof createHMRServer> | null = null;
|
|
298
|
+
let devBundler: Awaited<ReturnType<typeof startDevBundler>> | null = null;
|
|
299
|
+
let cssWatcher: CSSWatcher | null = null;
|
|
300
|
+
|
|
301
|
+
// CSS λΉλ μμ (Tailwind v4 κ°μ§ μμλ§)
|
|
302
|
+
const hasTailwind = await isTailwindProject(rootDir);
|
|
303
|
+
if (hasTailwind) {
|
|
304
|
+
cssWatcher = await startCSSWatch({
|
|
305
|
+
rootDir,
|
|
306
|
+
watch: true,
|
|
307
|
+
onBuild: (result) => {
|
|
308
|
+
if (result.success && hmrServer) {
|
|
309
|
+
// cssWatcher.serverPath μ¬μ© (κ²½λ‘ μΌκ΄μ±)
|
|
310
|
+
hmrServer.broadcast({
|
|
311
|
+
type: "css-update",
|
|
312
|
+
data: {
|
|
313
|
+
cssPath: cssWatcher?.serverPath || "/.mandu/client/globals.css",
|
|
314
|
+
timestamp: Date.now(),
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
onError: (error) => {
|
|
320
|
+
if (hmrServer) {
|
|
321
|
+
hmrServer.broadcast({
|
|
322
|
+
type: "error",
|
|
323
|
+
data: {
|
|
324
|
+
message: `CSS Error: ${error.message}`,
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (hasIslands && hmrEnabled) {
|
|
333
|
+
// HMR μλ² μμ
|
|
334
|
+
hmrServer = createHMRServer(port);
|
|
335
|
+
|
|
336
|
+
// Dev λ²λ€λ¬ μμ (νμΌ κ°μ)
|
|
337
|
+
devBundler = await startDevBundler({
|
|
338
|
+
rootDir,
|
|
339
|
+
manifest,
|
|
340
|
+
watchDirs: devConfig.watchDirs,
|
|
341
|
+
onRebuild: (result) => {
|
|
342
|
+
if (result.success) {
|
|
343
|
+
if (result.routeId === "*") {
|
|
344
|
+
hmrServer?.broadcast({
|
|
345
|
+
type: "reload",
|
|
346
|
+
data: {
|
|
347
|
+
timestamp: Date.now(),
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
} else {
|
|
351
|
+
hmrServer?.broadcast({
|
|
352
|
+
type: "island-update",
|
|
353
|
+
data: {
|
|
354
|
+
routeId: result.routeId,
|
|
355
|
+
timestamp: Date.now(),
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
} else {
|
|
360
|
+
hmrServer?.broadcast({
|
|
361
|
+
type: "error",
|
|
362
|
+
data: {
|
|
363
|
+
routeId: result.routeId,
|
|
364
|
+
message: result.error,
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
onError: (error, routeId) => {
|
|
370
|
+
hmrServer?.broadcast({
|
|
371
|
+
type: "error",
|
|
372
|
+
data: {
|
|
373
|
+
routeId,
|
|
374
|
+
message: error.message,
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// λ©μΈ μλ² μμ
|
|
382
|
+
const server = startServer(manifest, {
|
|
383
|
+
port,
|
|
384
|
+
hostname: serverConfig.hostname,
|
|
385
|
+
rootDir,
|
|
386
|
+
isDev: true,
|
|
387
|
+
hmrPort: hmrServer ? port : undefined,
|
|
388
|
+
bundleManifest: devBundler?.initialBuild.manifest,
|
|
389
|
+
cors: serverConfig.cors,
|
|
390
|
+
streaming: serverConfig.streaming,
|
|
391
|
+
// Tailwind κ°μ§ μμλ§ CSS λ§ν¬ μ£Όμ
|
|
392
|
+
cssPath: hasTailwind ? cssWatcher?.serverPath : false,
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
const actualPort = server.server.port ?? port;
|
|
396
|
+
if (actualPort !== port) {
|
|
397
|
+
if (hmrServer) {
|
|
398
|
+
hmrServer.close();
|
|
399
|
+
hmrServer = createHMRServer(actualPort);
|
|
400
|
+
server.registry.settings.hmrPort = actualPort;
|
|
401
|
+
console.log(`π HMR port updated: ${actualPort + HMR_OFFSET}`);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// FS Routes μ€μκ° κ°μ
|
|
406
|
+
const routesWatcher = await watchFSRoutes(rootDir, {
|
|
407
|
+
skipLegacy: true,
|
|
408
|
+
onChange: async (result) => {
|
|
409
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
410
|
+
console.log(`\nπ [${timestamp}] λΌμ°νΈ λ³κ²½ κ°μ§`);
|
|
411
|
+
|
|
412
|
+
// λ μ§μ€νΈλ¦¬ ν΄λ¦¬μ΄ (layout μΊμ ν¬ν¨)
|
|
413
|
+
clearDefaultRegistry();
|
|
414
|
+
|
|
415
|
+
// μ λ§€λνμ€νΈλ‘ μλ² μ
λ°μ΄νΈ
|
|
416
|
+
manifest = result.manifest;
|
|
417
|
+
console.log(` π λΌμ°νΈ: ${manifest.routes.length}κ°`);
|
|
418
|
+
|
|
419
|
+
// λΌμ°νΈ μ¬λ±λ‘ (isReload = true)
|
|
420
|
+
await registerHandlers(manifest, true);
|
|
421
|
+
|
|
422
|
+
// HMR λΈλ‘λμΊμ€νΈ (μ 체 리λ‘λ)
|
|
423
|
+
if (hmrServer) {
|
|
424
|
+
hmrServer.broadcast({
|
|
425
|
+
type: "reload",
|
|
426
|
+
data: { timestamp: Date.now() },
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// Architecture Guard μ€μκ° κ°μ (μ νμ )
|
|
433
|
+
let archGuardWatcher: ReturnType<typeof createGuardWatcher> | null = null;
|
|
434
|
+
let guardFailed = false;
|
|
435
|
+
|
|
436
|
+
// μ 리 ν¨μ
|
|
437
|
+
const cleanup = () => {
|
|
438
|
+
console.log("\nπ μλ² μ’
λ£ μ€...");
|
|
439
|
+
server.stop();
|
|
440
|
+
devBundler?.close();
|
|
441
|
+
hmrServer?.close();
|
|
442
|
+
cssWatcher?.close();
|
|
443
|
+
routesWatcher.close();
|
|
444
|
+
archGuardWatcher?.close();
|
|
445
|
+
process.exit(0);
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
const stopOnGuardError = (violation: Violation) => {
|
|
449
|
+
if (violation.severity !== "error" || guardFailed) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
guardFailed = true;
|
|
453
|
+
console.error("\nβ Architecture Guard violation detected. Stopping dev server.");
|
|
454
|
+
cleanup();
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
if (guardConfig) {
|
|
458
|
+
console.log(`π‘οΈ Architecture Guard νμ±ν (${guardPreset})`);
|
|
459
|
+
|
|
460
|
+
archGuardWatcher = createGuardWatcher({
|
|
461
|
+
config: guardConfig,
|
|
462
|
+
rootDir,
|
|
463
|
+
onViolation: stopOnGuardError,
|
|
464
|
+
onFileAnalyzed: (analysis, violations) => {
|
|
465
|
+
if (violations.length > 0) {
|
|
466
|
+
// HMR μλ¬λ‘ λΈλ‘λμΊμ€νΈ
|
|
467
|
+
hmrServer?.broadcast({
|
|
468
|
+
type: "guard-violation",
|
|
469
|
+
data: {
|
|
470
|
+
file: analysis.filePath,
|
|
471
|
+
violations: violations.map((v) => ({
|
|
472
|
+
line: v.line,
|
|
473
|
+
message: `${v.fromLayer} β ${v.toLayer}: ${v.ruleDescription}`,
|
|
474
|
+
})),
|
|
475
|
+
},
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
},
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
archGuardWatcher.start();
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
process.on("SIGINT", cleanup);
|
|
485
|
+
process.on("SIGTERM", cleanup);
|
|
486
|
+
}
|