@mandujs/cli 0.9.21 β 0.9.23
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 +57 -4
- package/README.md +62 -15
- package/package.json +2 -2
- package/src/commands/check.ts +41 -5
- package/src/commands/contract.ts +135 -9
- package/src/commands/dev.ts +155 -95
- package/src/commands/guard-arch.ts +39 -9
- package/src/commands/guard-check.ts +3 -3
- package/src/commands/init.ts +264 -9
- package/src/commands/monitor.ts +301 -0
- package/src/main.ts +421 -361
- package/templates/default/app/globals.css +37 -0
- package/templates/default/app/layout.tsx +27 -0
- package/templates/default/app/page.tsx +27 -49
- package/templates/default/package.json +20 -11
- package/templates/default/postcss.config.js +6 -0
- package/templates/default/src/client/app/index.ts +1 -0
- package/templates/default/src/client/entities/index.ts +1 -0
- package/templates/default/src/client/features/index.ts +1 -0
- package/templates/default/src/client/pages/index.ts +1 -0
- package/templates/default/src/client/shared/index.ts +1 -0
- package/templates/default/src/client/shared/lib/utils.ts +16 -0
- package/templates/default/src/client/shared/ui/button.tsx +57 -0
- package/templates/default/src/client/shared/ui/card.tsx +78 -0
- package/templates/default/src/client/shared/ui/index.ts +21 -0
- package/templates/default/src/client/shared/ui/input.tsx +24 -0
- package/templates/default/src/client/widgets/index.ts +1 -0
- package/templates/default/src/server/api/index.ts +1 -0
- package/templates/default/src/server/application/index.ts +1 -0
- package/templates/default/src/server/core/index.ts +1 -0
- package/templates/default/src/server/domain/index.ts +1 -0
- package/templates/default/src/server/infra/index.ts +1 -0
- package/templates/default/src/shared/contracts/index.ts +1 -0
- package/templates/default/src/shared/env/index.ts +1 -0
- package/templates/default/src/shared/schema/index.ts +1 -0
- package/templates/default/src/shared/types/index.ts +1 -0
- package/templates/default/src/shared/utils/client/index.ts +1 -0
- package/templates/default/src/shared/utils/server/index.ts +1 -0
- package/templates/default/tailwind.config.ts +64 -0
- package/templates/default/tsconfig.json +14 -3
package/src/commands/dev.ts
CHANGED
|
@@ -4,19 +4,24 @@ import {
|
|
|
4
4
|
registerPageLoader,
|
|
5
5
|
registerPageHandler,
|
|
6
6
|
registerLayoutLoader,
|
|
7
|
-
startDevBundler,
|
|
8
|
-
createHMRServer,
|
|
9
|
-
needsHydration,
|
|
10
|
-
loadEnv,
|
|
11
|
-
generateManifest,
|
|
12
|
-
watchFSRoutes,
|
|
13
|
-
clearDefaultRegistry,
|
|
14
|
-
createGuardWatcher,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
7
|
+
startDevBundler,
|
|
8
|
+
createHMRServer,
|
|
9
|
+
needsHydration,
|
|
10
|
+
loadEnv,
|
|
11
|
+
generateManifest,
|
|
12
|
+
watchFSRoutes,
|
|
13
|
+
clearDefaultRegistry,
|
|
14
|
+
createGuardWatcher,
|
|
15
|
+
checkDirectory,
|
|
16
|
+
printReport,
|
|
17
|
+
formatReportForAgent,
|
|
18
|
+
formatReportAsAgentJSON,
|
|
19
|
+
getPreset,
|
|
20
|
+
type RoutesManifest,
|
|
21
|
+
type GuardConfig,
|
|
22
|
+
type GuardPreset,
|
|
23
|
+
type Violation,
|
|
24
|
+
} from "@mandujs/core";
|
|
20
25
|
import { isDirectory, resolveFromCwd } from "../util/fs";
|
|
21
26
|
import { resolveOutputFormat, type OutputFormat } from "../util/output";
|
|
22
27
|
import path from "path";
|
|
@@ -35,10 +40,10 @@ export interface DevOptions {
|
|
|
35
40
|
guardFormat?: OutputFormat;
|
|
36
41
|
}
|
|
37
42
|
|
|
38
|
-
export async function dev(options: DevOptions = {}): Promise<void> {
|
|
39
|
-
const rootDir = resolveFromCwd(".");
|
|
40
|
-
|
|
41
|
-
console.log(`π₯ Mandu Dev Server (FS Routes)`);
|
|
43
|
+
export async function dev(options: DevOptions = {}): Promise<void> {
|
|
44
|
+
const rootDir = resolveFromCwd(".");
|
|
45
|
+
|
|
46
|
+
console.log(`π₯ Mandu Dev Server (FS Routes)`);
|
|
42
47
|
|
|
43
48
|
// .env νμΌ λ‘λ
|
|
44
49
|
const envResult = await loadEnv({
|
|
@@ -70,11 +75,75 @@ export async function dev(options: DevOptions = {}): Promise<void> {
|
|
|
70
75
|
process.exit(1);
|
|
71
76
|
}
|
|
72
77
|
|
|
73
|
-
let manifest = result.manifest;
|
|
74
|
-
console.log(`β
${manifest.routes.length}κ° λΌμ°νΈ λ°κ²¬\n`);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
+
let manifest = result.manifest;
|
|
79
|
+
console.log(`β
${manifest.routes.length}κ° λΌμ°νΈ λ°κ²¬\n`);
|
|
80
|
+
|
|
81
|
+
const enableFsRoutes = !options.legacy && await isDirectory(path.resolve(rootDir, "app"));
|
|
82
|
+
const guardPreset = options.guardPreset || "mandu";
|
|
83
|
+
const guardFormat = resolveOutputFormat(options.guardFormat);
|
|
84
|
+
const guardConfig: GuardConfig | null =
|
|
85
|
+
options.guard === false
|
|
86
|
+
? null
|
|
87
|
+
: {
|
|
88
|
+
preset: guardPreset,
|
|
89
|
+
srcDir: "src",
|
|
90
|
+
realtime: true,
|
|
91
|
+
realtimeOutput: guardFormat,
|
|
92
|
+
fsRoutes: enableFsRoutes
|
|
93
|
+
? {
|
|
94
|
+
noPageToPage: true,
|
|
95
|
+
pageCanImport: [
|
|
96
|
+
"client/pages",
|
|
97
|
+
"client/widgets",
|
|
98
|
+
"client/features",
|
|
99
|
+
"client/entities",
|
|
100
|
+
"client/shared",
|
|
101
|
+
"shared/contracts",
|
|
102
|
+
"shared/types",
|
|
103
|
+
"shared/utils/client",
|
|
104
|
+
],
|
|
105
|
+
layoutCanImport: [
|
|
106
|
+
"client/app",
|
|
107
|
+
"client/widgets",
|
|
108
|
+
"client/shared",
|
|
109
|
+
"shared/contracts",
|
|
110
|
+
"shared/types",
|
|
111
|
+
"shared/utils/client",
|
|
112
|
+
],
|
|
113
|
+
routeCanImport: [
|
|
114
|
+
"server/api",
|
|
115
|
+
"server/application",
|
|
116
|
+
"server/domain",
|
|
117
|
+
"server/infra",
|
|
118
|
+
"server/core",
|
|
119
|
+
"shared/contracts",
|
|
120
|
+
"shared/schema",
|
|
121
|
+
"shared/types",
|
|
122
|
+
"shared/utils/client",
|
|
123
|
+
"shared/utils/server",
|
|
124
|
+
"shared/env",
|
|
125
|
+
],
|
|
126
|
+
}
|
|
127
|
+
: undefined,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
if (guardConfig) {
|
|
131
|
+
const preflightReport = await checkDirectory(guardConfig, rootDir);
|
|
132
|
+
if (preflightReport.bySeverity.error > 0) {
|
|
133
|
+
if (guardFormat === "json") {
|
|
134
|
+
console.log(formatReportAsAgentJSON(preflightReport, guardPreset));
|
|
135
|
+
} else if (guardFormat === "agent") {
|
|
136
|
+
console.log(formatReportForAgent(preflightReport, guardPreset));
|
|
137
|
+
} else {
|
|
138
|
+
printReport(preflightReport, getPreset(guardPreset).hierarchy);
|
|
139
|
+
}
|
|
140
|
+
console.error("\nβ Architecture Guard failed. Fix errors before starting dev server.");
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Layout κ²½λ‘ μΆμ (μ€λ³΅ λ±λ‘ λ°©μ§)
|
|
146
|
+
const registeredLayouts = new Set<string>();
|
|
78
147
|
|
|
79
148
|
// νΈλ€λ¬ λ±λ‘ ν¨μ
|
|
80
149
|
const registerHandlers = async (manifest: RoutesManifest, isReload = false) => {
|
|
@@ -145,9 +214,9 @@ export async function dev(options: DevOptions = {}): Promise<void> {
|
|
|
145
214
|
let hmrServer: ReturnType<typeof createHMRServer> | null = null;
|
|
146
215
|
let devBundler: Awaited<ReturnType<typeof startDevBundler>> | null = null;
|
|
147
216
|
|
|
148
|
-
const hasIslands = manifest.routes.some(
|
|
149
|
-
(r) => r.kind === "page" && r.clientModule && needsHydration(r)
|
|
150
|
-
);
|
|
217
|
+
const hasIslands = manifest.routes.some(
|
|
218
|
+
(r) => r.kind === "page" && r.clientModule && needsHydration(r)
|
|
219
|
+
);
|
|
151
220
|
|
|
152
221
|
if (hasIslands && !options.noHmr) {
|
|
153
222
|
// HMR μλ² μμ
|
|
@@ -206,59 +275,10 @@ export async function dev(options: DevOptions = {}): Promise<void> {
|
|
|
206
275
|
bundleManifest: devBundler?.initialBuild.manifest,
|
|
207
276
|
});
|
|
208
277
|
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const guardPreset = options.guardPreset || "mandu";
|
|
214
|
-
const guardFormat = resolveOutputFormat(options.guardFormat);
|
|
215
|
-
const enableFsRoutes = !options.legacy && await isDirectory(path.resolve(rootDir, "app"));
|
|
216
|
-
const guardConfig: GuardConfig = {
|
|
217
|
-
preset: guardPreset,
|
|
218
|
-
srcDir: "src",
|
|
219
|
-
realtime: true,
|
|
220
|
-
realtimeOutput: guardFormat,
|
|
221
|
-
fsRoutes: enableFsRoutes
|
|
222
|
-
? {
|
|
223
|
-
noPageToPage: true,
|
|
224
|
-
pageCanImport: ["widgets", "features", "entities", "shared"],
|
|
225
|
-
layoutCanImport: ["widgets", "shared"],
|
|
226
|
-
}
|
|
227
|
-
: undefined,
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
console.log(`π‘οΈ Architecture Guard νμ±ν (${guardPreset})`);
|
|
231
|
-
|
|
232
|
-
archGuardWatcher = createGuardWatcher({
|
|
233
|
-
config: guardConfig,
|
|
234
|
-
rootDir,
|
|
235
|
-
onViolation: (violation) => {
|
|
236
|
-
// μ€μκ° κ²½κ³ λ watcher λ΄λΆμμ μ²λ¦¬
|
|
237
|
-
},
|
|
238
|
-
onFileAnalyzed: (analysis, violations) => {
|
|
239
|
-
if (violations.length > 0) {
|
|
240
|
-
// HMR μλ¬λ‘ λΈλ‘λμΊμ€νΈ
|
|
241
|
-
hmrServer?.broadcast({
|
|
242
|
-
type: "guard-violation",
|
|
243
|
-
data: {
|
|
244
|
-
file: analysis.filePath,
|
|
245
|
-
violations: violations.map((v) => ({
|
|
246
|
-
line: v.line,
|
|
247
|
-
message: `${v.fromLayer} β ${v.toLayer}: ${v.ruleDescription}`,
|
|
248
|
-
})),
|
|
249
|
-
},
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
},
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
archGuardWatcher.start();
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// FS Routes μ€μκ° κ°μ
|
|
259
|
-
const routesWatcher = await watchFSRoutes(rootDir, {
|
|
260
|
-
skipLegacy: true,
|
|
261
|
-
onChange: async (result) => {
|
|
278
|
+
// FS Routes μ€μκ° κ°μ
|
|
279
|
+
const routesWatcher = await watchFSRoutes(rootDir, {
|
|
280
|
+
skipLegacy: true,
|
|
281
|
+
onChange: async (result) => {
|
|
262
282
|
const timestamp = new Date().toLocaleTimeString();
|
|
263
283
|
console.log(`\nπ [${timestamp}] λΌμ°νΈ λ³κ²½ κ°μ§`);
|
|
264
284
|
|
|
@@ -279,20 +299,60 @@ export async function dev(options: DevOptions = {}): Promise<void> {
|
|
|
279
299
|
data: { timestamp: Date.now() },
|
|
280
300
|
});
|
|
281
301
|
}
|
|
282
|
-
},
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
//
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// Architecture Guard μ€μκ° κ°μ (μ νμ )
|
|
306
|
+
let archGuardWatcher: ReturnType<typeof createGuardWatcher> | null = null;
|
|
307
|
+
let guardFailed = false;
|
|
308
|
+
|
|
309
|
+
// μ 리 ν¨μ
|
|
310
|
+
const cleanup = () => {
|
|
311
|
+
console.log("\nπ μλ² μ’
λ£ μ€...");
|
|
312
|
+
server.stop();
|
|
313
|
+
devBundler?.close();
|
|
314
|
+
hmrServer?.close();
|
|
315
|
+
routesWatcher.close();
|
|
316
|
+
archGuardWatcher?.close();
|
|
317
|
+
process.exit(0);
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
const stopOnGuardError = (violation: Violation) => {
|
|
321
|
+
if (violation.severity !== "error" || guardFailed) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
guardFailed = true;
|
|
325
|
+
console.error("\nβ Architecture Guard violation detected. Stopping dev server.");
|
|
326
|
+
cleanup();
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
if (guardConfig) {
|
|
330
|
+
console.log(`π‘οΈ Architecture Guard νμ±ν (${guardPreset})`);
|
|
331
|
+
|
|
332
|
+
archGuardWatcher = createGuardWatcher({
|
|
333
|
+
config: guardConfig,
|
|
334
|
+
rootDir,
|
|
335
|
+
onViolation: stopOnGuardError,
|
|
336
|
+
onFileAnalyzed: (analysis, violations) => {
|
|
337
|
+
if (violations.length > 0) {
|
|
338
|
+
// HMR μλ¬λ‘ λΈλ‘λμΊμ€νΈ
|
|
339
|
+
hmrServer?.broadcast({
|
|
340
|
+
type: "guard-violation",
|
|
341
|
+
data: {
|
|
342
|
+
file: analysis.filePath,
|
|
343
|
+
violations: violations.map((v) => ({
|
|
344
|
+
line: v.line,
|
|
345
|
+
message: `${v.fromLayer} β ${v.toLayer}: ${v.ruleDescription}`,
|
|
346
|
+
})),
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
archGuardWatcher.start();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
process.on("SIGINT", cleanup);
|
|
357
|
+
process.on("SIGTERM", cleanup);
|
|
358
|
+
}
|
|
@@ -109,8 +109,37 @@ export async function guardArch(options: GuardArchOptions = {}): Promise<boolean
|
|
|
109
109
|
fsRoutes: enableFsRoutes
|
|
110
110
|
? {
|
|
111
111
|
noPageToPage: true,
|
|
112
|
-
pageCanImport: [
|
|
113
|
-
|
|
112
|
+
pageCanImport: [
|
|
113
|
+
"client/pages",
|
|
114
|
+
"client/widgets",
|
|
115
|
+
"client/features",
|
|
116
|
+
"client/entities",
|
|
117
|
+
"client/shared",
|
|
118
|
+
"shared/contracts",
|
|
119
|
+
"shared/types",
|
|
120
|
+
"shared/utils/client",
|
|
121
|
+
],
|
|
122
|
+
layoutCanImport: [
|
|
123
|
+
"client/app",
|
|
124
|
+
"client/widgets",
|
|
125
|
+
"client/shared",
|
|
126
|
+
"shared/contracts",
|
|
127
|
+
"shared/types",
|
|
128
|
+
"shared/utils/client",
|
|
129
|
+
],
|
|
130
|
+
routeCanImport: [
|
|
131
|
+
"server/api",
|
|
132
|
+
"server/application",
|
|
133
|
+
"server/domain",
|
|
134
|
+
"server/infra",
|
|
135
|
+
"server/core",
|
|
136
|
+
"shared/contracts",
|
|
137
|
+
"shared/schema",
|
|
138
|
+
"shared/types",
|
|
139
|
+
"shared/utils/client",
|
|
140
|
+
"shared/utils/server",
|
|
141
|
+
"shared/env",
|
|
142
|
+
],
|
|
114
143
|
}
|
|
115
144
|
: undefined,
|
|
116
145
|
};
|
|
@@ -238,19 +267,20 @@ export async function guardArch(options: GuardArchOptions = {}): Promise<boolean
|
|
|
238
267
|
}
|
|
239
268
|
|
|
240
269
|
// CI λͺ¨λμμ μλ¬κ° μμΌλ©΄ μ€ν¨
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
return false;
|
|
244
|
-
}
|
|
270
|
+
const hasErrors = report.bySeverity.error > 0;
|
|
271
|
+
const hasWarnings = report.bySeverity.warn > 0;
|
|
245
272
|
|
|
246
273
|
if (report.totalViolations === 0) {
|
|
247
274
|
console.log("\nβ
Architecture check passed");
|
|
248
275
|
return true;
|
|
249
276
|
}
|
|
250
277
|
|
|
251
|
-
if (
|
|
252
|
-
|
|
253
|
-
|
|
278
|
+
if (hasErrors || (ci && hasWarnings)) {
|
|
279
|
+
const reason = hasErrors
|
|
280
|
+
? `${report.bySeverity.error} error(s)`
|
|
281
|
+
: `${report.bySeverity.warn} warning(s)`;
|
|
282
|
+
console.log(`\nβ Architecture check failed: ${reason}`);
|
|
283
|
+
return false;
|
|
254
284
|
}
|
|
255
285
|
|
|
256
286
|
console.log(`\nβ οΈ ${report.totalViolations} issue(s) found`);
|
|
@@ -13,13 +13,13 @@ export interface GuardCheckOptions {
|
|
|
13
13
|
autoCorrect?: boolean;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export async function guardCheck(options: GuardCheckOptions = {}): Promise<boolean> {
|
|
17
|
-
const { autoCorrect = true } = options;
|
|
16
|
+
export async function guardCheck(options: GuardCheckOptions = {}): Promise<boolean> {
|
|
17
|
+
const { autoCorrect = true } = options;
|
|
18
18
|
|
|
19
19
|
const specPath = resolveFromCwd("spec/routes.manifest.json");
|
|
20
20
|
const rootDir = getRootDir();
|
|
21
21
|
|
|
22
|
-
console.log(`π₯ Mandu Guard`);
|
|
22
|
+
console.log(`π₯ Mandu Guard (Legacy Spec)`);
|
|
23
23
|
console.log(`π Spec νμΌ: ${specPath}`);
|
|
24
24
|
console.log(`π§ Auto-correct: ${autoCorrect ? "ON" : "OFF"}\n`);
|
|
25
25
|
|