@mandujs/cli 0.3.2 β 0.3.4
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 +181 -181
- package/README.md +181 -181
- package/package.json +3 -3
- package/src/commands/change/begin.ts +33 -0
- package/src/commands/change/commit.ts +23 -0
- package/src/commands/change/index.ts +6 -0
- package/src/commands/change/list.ts +57 -0
- package/src/commands/change/prune.ts +32 -0
- package/src/commands/change/rollback.ts +40 -0
- package/src/commands/change/status.ts +36 -0
- package/src/commands/guard-check.ts +27 -0
- package/src/commands/init.ts +88 -88
- package/src/main.ts +52 -0
- package/src/util/fs.ts +9 -9
- package/templates/default/apps/web/entry.tsx +35 -35
- package/templates/default/spec/routes.manifest.json +18 -18
- package/templates/default/tests/example.test.ts +58 -58
- package/templates/default/tests/helpers.ts +52 -52
- package/templates/default/tests/setup.ts +9 -9
- package/templates/default/tsconfig.json +14 -14
package/src/commands/init.ts
CHANGED
|
@@ -1,88 +1,88 @@
|
|
|
1
|
-
import path from "path";
|
|
2
|
-
import fs from "fs/promises";
|
|
3
|
-
|
|
4
|
-
export interface InitOptions {
|
|
5
|
-
name?: string;
|
|
6
|
-
template?: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
async function copyDir(src: string, dest: string, projectName: string): Promise<void> {
|
|
10
|
-
await fs.mkdir(dest, { recursive: true });
|
|
11
|
-
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
12
|
-
|
|
13
|
-
for (const entry of entries) {
|
|
14
|
-
const srcPath = path.join(src, entry.name);
|
|
15
|
-
const destPath = path.join(dest, entry.name);
|
|
16
|
-
|
|
17
|
-
if (entry.isDirectory()) {
|
|
18
|
-
await copyDir(srcPath, destPath, projectName);
|
|
19
|
-
} else {
|
|
20
|
-
let content = await fs.readFile(srcPath, "utf-8");
|
|
21
|
-
// Replace template variables
|
|
22
|
-
content = content.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
|
|
23
|
-
await fs.writeFile(destPath, content);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function getTemplatesDir(): string {
|
|
29
|
-
// When installed via npm, templates are in the CLI package
|
|
30
|
-
const commandsDir = import.meta.dir;
|
|
31
|
-
// packages/cli/src/commands -> go up 2 levels to cli package root
|
|
32
|
-
return path.resolve(commandsDir, "../../templates");
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export async function init(options: InitOptions = {}): Promise<boolean> {
|
|
36
|
-
const projectName = options.name || "my-mandu-app";
|
|
37
|
-
const template = options.template || "default";
|
|
38
|
-
const targetDir = path.resolve(process.cwd(), projectName);
|
|
39
|
-
|
|
40
|
-
console.log(`π₯ Mandu Init`);
|
|
41
|
-
console.log(`π νλ‘μ νΈ: ${projectName}`);
|
|
42
|
-
console.log(`π¦ ν
νλ¦Ώ: ${template}\n`);
|
|
43
|
-
|
|
44
|
-
// Check if target directory exists
|
|
45
|
-
try {
|
|
46
|
-
await fs.access(targetDir);
|
|
47
|
-
console.error(`β λλ ν λ¦¬κ° μ΄λ―Έ μ‘΄μ¬ν©λλ€: ${targetDir}`);
|
|
48
|
-
return false;
|
|
49
|
-
} catch {
|
|
50
|
-
// Directory doesn't exist, good to proceed
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const templatesDir = getTemplatesDir();
|
|
54
|
-
const templateDir = path.join(templatesDir, template);
|
|
55
|
-
|
|
56
|
-
// Check if template exists
|
|
57
|
-
try {
|
|
58
|
-
await fs.access(templateDir);
|
|
59
|
-
} catch {
|
|
60
|
-
console.error(`β ν
νλ¦Ώμ μ°Ύμ μ μμ΅λλ€: ${template}`);
|
|
61
|
-
console.error(` μ¬μ© κ°λ₯ν ν
νλ¦Ώ: default`);
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
console.log(`π ν
νλ¦Ώ λ³΅μ¬ μ€...`);
|
|
66
|
-
|
|
67
|
-
try {
|
|
68
|
-
await copyDir(templateDir, targetDir, projectName);
|
|
69
|
-
} catch (error) {
|
|
70
|
-
console.error(`β ν
νλ¦Ώ λ³΅μ¬ μ€ν¨:`, error);
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Create empty generated directories
|
|
75
|
-
await fs.mkdir(path.join(targetDir, "apps/server/generated/routes"), { recursive: true });
|
|
76
|
-
await fs.mkdir(path.join(targetDir, "apps/web/generated/routes"), { recursive: true });
|
|
77
|
-
|
|
78
|
-
console.log(`\nβ
νλ‘μ νΈ μμ± μλ£!\n`);
|
|
79
|
-
console.log(`π μμΉ: ${targetDir}`);
|
|
80
|
-
console.log(`\nπ μμνκΈ°:`);
|
|
81
|
-
console.log(` cd ${projectName}`);
|
|
82
|
-
console.log(` bun install`);
|
|
83
|
-
console.log(` bun run spec`);
|
|
84
|
-
console.log(` bun run generate`);
|
|
85
|
-
console.log(` bun run dev`);
|
|
86
|
-
|
|
87
|
-
return true;
|
|
88
|
-
}
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
|
|
4
|
+
export interface InitOptions {
|
|
5
|
+
name?: string;
|
|
6
|
+
template?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async function copyDir(src: string, dest: string, projectName: string): Promise<void> {
|
|
10
|
+
await fs.mkdir(dest, { recursive: true });
|
|
11
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
12
|
+
|
|
13
|
+
for (const entry of entries) {
|
|
14
|
+
const srcPath = path.join(src, entry.name);
|
|
15
|
+
const destPath = path.join(dest, entry.name);
|
|
16
|
+
|
|
17
|
+
if (entry.isDirectory()) {
|
|
18
|
+
await copyDir(srcPath, destPath, projectName);
|
|
19
|
+
} else {
|
|
20
|
+
let content = await fs.readFile(srcPath, "utf-8");
|
|
21
|
+
// Replace template variables
|
|
22
|
+
content = content.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
|
|
23
|
+
await fs.writeFile(destPath, content);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getTemplatesDir(): string {
|
|
29
|
+
// When installed via npm, templates are in the CLI package
|
|
30
|
+
const commandsDir = import.meta.dir;
|
|
31
|
+
// packages/cli/src/commands -> go up 2 levels to cli package root
|
|
32
|
+
return path.resolve(commandsDir, "../../templates");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function init(options: InitOptions = {}): Promise<boolean> {
|
|
36
|
+
const projectName = options.name || "my-mandu-app";
|
|
37
|
+
const template = options.template || "default";
|
|
38
|
+
const targetDir = path.resolve(process.cwd(), projectName);
|
|
39
|
+
|
|
40
|
+
console.log(`π₯ Mandu Init`);
|
|
41
|
+
console.log(`π νλ‘μ νΈ: ${projectName}`);
|
|
42
|
+
console.log(`π¦ ν
νλ¦Ώ: ${template}\n`);
|
|
43
|
+
|
|
44
|
+
// Check if target directory exists
|
|
45
|
+
try {
|
|
46
|
+
await fs.access(targetDir);
|
|
47
|
+
console.error(`β λλ ν λ¦¬κ° μ΄λ―Έ μ‘΄μ¬ν©λλ€: ${targetDir}`);
|
|
48
|
+
return false;
|
|
49
|
+
} catch {
|
|
50
|
+
// Directory doesn't exist, good to proceed
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const templatesDir = getTemplatesDir();
|
|
54
|
+
const templateDir = path.join(templatesDir, template);
|
|
55
|
+
|
|
56
|
+
// Check if template exists
|
|
57
|
+
try {
|
|
58
|
+
await fs.access(templateDir);
|
|
59
|
+
} catch {
|
|
60
|
+
console.error(`β ν
νλ¦Ώμ μ°Ύμ μ μμ΅λλ€: ${template}`);
|
|
61
|
+
console.error(` μ¬μ© κ°λ₯ν ν
νλ¦Ώ: default`);
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log(`π ν
νλ¦Ώ λ³΅μ¬ μ€...`);
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
await copyDir(templateDir, targetDir, projectName);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error(`β ν
νλ¦Ώ λ³΅μ¬ μ€ν¨:`, error);
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Create empty generated directories
|
|
75
|
+
await fs.mkdir(path.join(targetDir, "apps/server/generated/routes"), { recursive: true });
|
|
76
|
+
await fs.mkdir(path.join(targetDir, "apps/web/generated/routes"), { recursive: true });
|
|
77
|
+
|
|
78
|
+
console.log(`\nβ
νλ‘μ νΈ μμ± μλ£!\n`);
|
|
79
|
+
console.log(`π μμΉ: ${targetDir}`);
|
|
80
|
+
console.log(`\nπ μμνκΈ°:`);
|
|
81
|
+
console.log(` cd ${projectName}`);
|
|
82
|
+
console.log(` bun install`);
|
|
83
|
+
console.log(` bun run spec`);
|
|
84
|
+
console.log(` bun run generate`);
|
|
85
|
+
console.log(` bun run dev`);
|
|
86
|
+
|
|
87
|
+
return true;
|
|
88
|
+
}
|
package/src/main.ts
CHANGED
|
@@ -5,6 +5,14 @@ import { generateApply } from "./commands/generate-apply";
|
|
|
5
5
|
import { guardCheck } from "./commands/guard-check";
|
|
6
6
|
import { dev } from "./commands/dev";
|
|
7
7
|
import { init } from "./commands/init";
|
|
8
|
+
import {
|
|
9
|
+
changeBegin,
|
|
10
|
+
changeCommit,
|
|
11
|
+
changeRollback,
|
|
12
|
+
changeStatus,
|
|
13
|
+
changeList,
|
|
14
|
+
changePrune,
|
|
15
|
+
} from "./commands/change";
|
|
8
16
|
|
|
9
17
|
const HELP_TEXT = `
|
|
10
18
|
π₯ Mandu CLI - Agent-Native Fullstack Framework
|
|
@@ -18,11 +26,21 @@ Commands:
|
|
|
18
26
|
guard Guard κ·μΉ κ²μ¬
|
|
19
27
|
dev κ°λ° μλ² μ€ν
|
|
20
28
|
|
|
29
|
+
change begin λ³κ²½ νΈλμμ
μμ (μ€λ
μ· μμ±)
|
|
30
|
+
change commit λ³κ²½ νμ
|
|
31
|
+
change rollback μ€λ
μ·μΌλ‘ 볡μ
|
|
32
|
+
change status νμ¬ νΈλμμ
μν
|
|
33
|
+
change list λ³κ²½ μ΄λ ₯ μ‘°ν
|
|
34
|
+
change prune μ€λλ μ€λ
μ· μ 리
|
|
35
|
+
|
|
21
36
|
Options:
|
|
22
37
|
--name <name> init μ νλ‘μ νΈ μ΄λ¦ (κΈ°λ³Έ: my-mandu-app)
|
|
23
38
|
--file <path> spec-upsert μ μ¬μ©ν spec νμΌ κ²½λ‘
|
|
24
39
|
--port <port> dev μλ² ν¬νΈ (κΈ°λ³Έ: 3000)
|
|
25
40
|
--no-auto-correct guard μ μλ μμ λΉνμ±ν
|
|
41
|
+
--message <msg> change begin μ μ€λͺ
λ©μμ§
|
|
42
|
+
--id <id> change rollback μ νΉμ λ³κ²½ ID
|
|
43
|
+
--keep <n> change prune μ μ μ§ν μ€λ
μ· μ (κΈ°λ³Έ: 5)
|
|
26
44
|
--help, -h λμλ§ νμ
|
|
27
45
|
|
|
28
46
|
Examples:
|
|
@@ -31,6 +49,9 @@ Examples:
|
|
|
31
49
|
bunx mandu generate
|
|
32
50
|
bunx mandu guard
|
|
33
51
|
bunx mandu dev --port 3000
|
|
52
|
+
bunx mandu change begin --message "Add new route"
|
|
53
|
+
bunx mandu change commit
|
|
54
|
+
bunx mandu change rollback
|
|
34
55
|
|
|
35
56
|
Workflow:
|
|
36
57
|
1. init β 2. spec-upsert β 3. generate β 4. guard β 5. dev
|
|
@@ -93,6 +114,37 @@ async function main(): Promise<void> {
|
|
|
93
114
|
await dev({ port: options.port ? Number(options.port) : undefined });
|
|
94
115
|
break;
|
|
95
116
|
|
|
117
|
+
case "change": {
|
|
118
|
+
const subCommand = args[1];
|
|
119
|
+
switch (subCommand) {
|
|
120
|
+
case "begin":
|
|
121
|
+
success = await changeBegin({ message: options.message });
|
|
122
|
+
break;
|
|
123
|
+
case "commit":
|
|
124
|
+
success = await changeCommit();
|
|
125
|
+
break;
|
|
126
|
+
case "rollback":
|
|
127
|
+
success = await changeRollback({ id: options.id });
|
|
128
|
+
break;
|
|
129
|
+
case "status":
|
|
130
|
+
success = await changeStatus();
|
|
131
|
+
break;
|
|
132
|
+
case "list":
|
|
133
|
+
success = await changeList();
|
|
134
|
+
break;
|
|
135
|
+
case "prune":
|
|
136
|
+
success = await changePrune({
|
|
137
|
+
keep: options.keep ? Number(options.keep) : undefined,
|
|
138
|
+
});
|
|
139
|
+
break;
|
|
140
|
+
default:
|
|
141
|
+
console.error(`β Unknown change subcommand: ${subCommand}`);
|
|
142
|
+
console.log(`\nUsage: bunx mandu change <begin|commit|rollback|status|list|prune>`);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
|
|
96
148
|
default:
|
|
97
149
|
console.error(`β Unknown command: ${command}`);
|
|
98
150
|
console.log(HELP_TEXT);
|
package/src/util/fs.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import path from "path";
|
|
2
|
-
|
|
3
|
-
export function resolveFromCwd(...paths: string[]): string {
|
|
4
|
-
return path.resolve(process.cwd(), ...paths);
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function getRootDir(): string {
|
|
8
|
-
return process.cwd();
|
|
9
|
-
}
|
|
1
|
+
import path from "path";
|
|
2
|
+
|
|
3
|
+
export function resolveFromCwd(...paths: string[]): string {
|
|
4
|
+
return path.resolve(process.cwd(), ...paths);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function getRootDir(): string {
|
|
8
|
+
return process.cwd();
|
|
9
|
+
}
|
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import type { ReactElement } from "react";
|
|
3
|
-
|
|
4
|
-
export interface AppContext {
|
|
5
|
-
routeId: string;
|
|
6
|
-
url: string;
|
|
7
|
-
params: Record<string, string>;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
type RouteComponent = (props: { params: Record<string, string> }) => ReactElement;
|
|
11
|
-
|
|
12
|
-
const routeComponents: Record<string, RouteComponent> = {};
|
|
13
|
-
|
|
14
|
-
export function registerRoute(routeId: string, component: RouteComponent): void {
|
|
15
|
-
routeComponents[routeId] = component;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function createApp(context: AppContext): ReactElement {
|
|
19
|
-
const Component = routeComponents[context.routeId];
|
|
20
|
-
|
|
21
|
-
if (!Component) {
|
|
22
|
-
return (
|
|
23
|
-
<div>
|
|
24
|
-
<h1>404 - Route Not Found</h1>
|
|
25
|
-
<p>Route ID: {context.routeId}</p>
|
|
26
|
-
</div>
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return <Component params={context.params} />;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function getRegisteredRoutes(): string[] {
|
|
34
|
-
return Object.keys(routeComponents);
|
|
35
|
-
}
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { ReactElement } from "react";
|
|
3
|
+
|
|
4
|
+
export interface AppContext {
|
|
5
|
+
routeId: string;
|
|
6
|
+
url: string;
|
|
7
|
+
params: Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type RouteComponent = (props: { params: Record<string, string> }) => ReactElement;
|
|
11
|
+
|
|
12
|
+
const routeComponents: Record<string, RouteComponent> = {};
|
|
13
|
+
|
|
14
|
+
export function registerRoute(routeId: string, component: RouteComponent): void {
|
|
15
|
+
routeComponents[routeId] = component;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createApp(context: AppContext): ReactElement {
|
|
19
|
+
const Component = routeComponents[context.routeId];
|
|
20
|
+
|
|
21
|
+
if (!Component) {
|
|
22
|
+
return (
|
|
23
|
+
<div>
|
|
24
|
+
<h1>404 - Route Not Found</h1>
|
|
25
|
+
<p>Route ID: {context.routeId}</p>
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return <Component params={context.params} />;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getRegisteredRoutes(): string[] {
|
|
34
|
+
return Object.keys(routeComponents);
|
|
35
|
+
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 1,
|
|
3
|
-
"routes": [
|
|
4
|
-
{
|
|
5
|
-
"id": "home",
|
|
6
|
-
"pattern": "/",
|
|
7
|
-
"kind": "page",
|
|
8
|
-
"module": "apps/server/generated/routes/home.route.ts",
|
|
9
|
-
"componentModule": "apps/web/generated/routes/home.route.tsx"
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
"id": "health",
|
|
13
|
-
"pattern": "/api/health",
|
|
14
|
-
"kind": "api",
|
|
15
|
-
"module": "apps/server/generated/routes/health.route.ts"
|
|
16
|
-
}
|
|
17
|
-
]
|
|
18
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"routes": [
|
|
4
|
+
{
|
|
5
|
+
"id": "home",
|
|
6
|
+
"pattern": "/",
|
|
7
|
+
"kind": "page",
|
|
8
|
+
"module": "apps/server/generated/routes/home.route.ts",
|
|
9
|
+
"componentModule": "apps/web/generated/routes/home.route.tsx"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"id": "health",
|
|
13
|
+
"pattern": "/api/health",
|
|
14
|
+
"kind": "api",
|
|
15
|
+
"module": "apps/server/generated/routes/health.route.ts"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
// Mandu Example Test
|
|
2
|
-
// μ΄ νμΌμ ν
μ€νΈ μμ± λ°©λ²μ 보μ¬μ£Όλ μμ μ
λλ€.
|
|
3
|
-
|
|
4
|
-
import { describe, it, expect } from "bun:test";
|
|
5
|
-
import { createTestRequest, parseJsonResponse, assertStatus } from "./helpers";
|
|
6
|
-
|
|
7
|
-
describe("Example Tests", () => {
|
|
8
|
-
describe("Basic Assertions", () => {
|
|
9
|
-
it("should pass basic equality test", () => {
|
|
10
|
-
expect(1 + 1).toBe(2);
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it("should pass object equality test", () => {
|
|
14
|
-
const obj = { status: "ok", data: { message: "hello" } };
|
|
15
|
-
expect(obj).toEqual({
|
|
16
|
-
status: "ok",
|
|
17
|
-
data: { message: "hello" },
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
describe("Test Helpers", () => {
|
|
23
|
-
it("should create test request", () => {
|
|
24
|
-
const req = createTestRequest("http://localhost:3000/api/test", {
|
|
25
|
-
method: "POST",
|
|
26
|
-
body: { name: "test" },
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
expect(req.method).toBe("POST");
|
|
30
|
-
expect(req.url).toBe("http://localhost:3000/api/test");
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("should parse JSON response", async () => {
|
|
34
|
-
const mockResponse = new Response(
|
|
35
|
-
JSON.stringify({ status: "ok" }),
|
|
36
|
-
{ status: 200 }
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
const data = await parseJsonResponse<{ status: string }>(mockResponse);
|
|
40
|
-
expect(data.status).toBe("ok");
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
// API νΈλ€λ¬ ν
μ€νΈ μμ (μ€μ νΈλ€λ¬ import ν μ¬μ©)
|
|
46
|
-
// import handler from "../apps/server/generated/routes/health.route";
|
|
47
|
-
//
|
|
48
|
-
// describe("API: GET /api/health", () => {
|
|
49
|
-
// it("should return 200 with status ok", async () => {
|
|
50
|
-
// const req = createTestRequest("http://localhost:3000/api/health");
|
|
51
|
-
// const response = handler(req, {});
|
|
52
|
-
//
|
|
53
|
-
// assertStatus(response, 200);
|
|
54
|
-
//
|
|
55
|
-
// const data = await parseJsonResponse<{ status: string }>(response);
|
|
56
|
-
// expect(data.status).toBe("ok");
|
|
57
|
-
// });
|
|
58
|
-
// });
|
|
1
|
+
// Mandu Example Test
|
|
2
|
+
// μ΄ νμΌμ ν
μ€νΈ μμ± λ°©λ²μ 보μ¬μ£Όλ μμ μ
λλ€.
|
|
3
|
+
|
|
4
|
+
import { describe, it, expect } from "bun:test";
|
|
5
|
+
import { createTestRequest, parseJsonResponse, assertStatus } from "./helpers";
|
|
6
|
+
|
|
7
|
+
describe("Example Tests", () => {
|
|
8
|
+
describe("Basic Assertions", () => {
|
|
9
|
+
it("should pass basic equality test", () => {
|
|
10
|
+
expect(1 + 1).toBe(2);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("should pass object equality test", () => {
|
|
14
|
+
const obj = { status: "ok", data: { message: "hello" } };
|
|
15
|
+
expect(obj).toEqual({
|
|
16
|
+
status: "ok",
|
|
17
|
+
data: { message: "hello" },
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("Test Helpers", () => {
|
|
23
|
+
it("should create test request", () => {
|
|
24
|
+
const req = createTestRequest("http://localhost:3000/api/test", {
|
|
25
|
+
method: "POST",
|
|
26
|
+
body: { name: "test" },
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
expect(req.method).toBe("POST");
|
|
30
|
+
expect(req.url).toBe("http://localhost:3000/api/test");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should parse JSON response", async () => {
|
|
34
|
+
const mockResponse = new Response(
|
|
35
|
+
JSON.stringify({ status: "ok" }),
|
|
36
|
+
{ status: 200 }
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const data = await parseJsonResponse<{ status: string }>(mockResponse);
|
|
40
|
+
expect(data.status).toBe("ok");
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// API νΈλ€λ¬ ν
μ€νΈ μμ (μ€μ νΈλ€λ¬ import ν μ¬μ©)
|
|
46
|
+
// import handler from "../apps/server/generated/routes/health.route";
|
|
47
|
+
//
|
|
48
|
+
// describe("API: GET /api/health", () => {
|
|
49
|
+
// it("should return 200 with status ok", async () => {
|
|
50
|
+
// const req = createTestRequest("http://localhost:3000/api/health");
|
|
51
|
+
// const response = handler(req, {});
|
|
52
|
+
//
|
|
53
|
+
// assertStatus(response, 200);
|
|
54
|
+
//
|
|
55
|
+
// const data = await parseJsonResponse<{ status: string }>(response);
|
|
56
|
+
// expect(data.status).toBe("ok");
|
|
57
|
+
// });
|
|
58
|
+
// });
|
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
// Mandu Test Helpers
|
|
2
|
-
// ν
μ€νΈμμ μ¬μ©ν μ νΈλ¦¬ν° ν¨μλ€
|
|
3
|
-
|
|
4
|
-
import type { Request } from "bun";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* API νΈλ€λ¬ ν
μ€νΈμ© Request μμ±
|
|
8
|
-
*/
|
|
9
|
-
export function createTestRequest(
|
|
10
|
-
url: string,
|
|
11
|
-
options?: {
|
|
12
|
-
method?: string;
|
|
13
|
-
body?: unknown;
|
|
14
|
-
headers?: Record<string, string>;
|
|
15
|
-
}
|
|
16
|
-
): Request {
|
|
17
|
-
const { method = "GET", body, headers = {} } = options || {};
|
|
18
|
-
|
|
19
|
-
return new Request(url, {
|
|
20
|
-
method,
|
|
21
|
-
headers: {
|
|
22
|
-
"Content-Type": "application/json",
|
|
23
|
-
...headers,
|
|
24
|
-
},
|
|
25
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Responseλ₯Ό JSONμΌλ‘ νμ±
|
|
31
|
-
*/
|
|
32
|
-
export async function parseJsonResponse<T = unknown>(response: Response): Promise<T> {
|
|
33
|
-
return response.json() as Promise<T>;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Response μν κ²μ¦
|
|
38
|
-
*/
|
|
39
|
-
export function assertStatus(response: Response, expectedStatus: number): void {
|
|
40
|
-
if (response.status !== expectedStatus) {
|
|
41
|
-
throw new Error(
|
|
42
|
-
`Expected status ${expectedStatus}, got ${response.status}`
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* ν
μ€νΈμ© λΌμ°νΈ νλΌλ―Έν° μμ±
|
|
49
|
-
*/
|
|
50
|
-
export function createParams(params: Record<string, string>): Record<string, string> {
|
|
51
|
-
return params;
|
|
52
|
-
}
|
|
1
|
+
// Mandu Test Helpers
|
|
2
|
+
// ν
μ€νΈμμ μ¬μ©ν μ νΈλ¦¬ν° ν¨μλ€
|
|
3
|
+
|
|
4
|
+
import type { Request } from "bun";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* API νΈλ€λ¬ ν
μ€νΈμ© Request μμ±
|
|
8
|
+
*/
|
|
9
|
+
export function createTestRequest(
|
|
10
|
+
url: string,
|
|
11
|
+
options?: {
|
|
12
|
+
method?: string;
|
|
13
|
+
body?: unknown;
|
|
14
|
+
headers?: Record<string, string>;
|
|
15
|
+
}
|
|
16
|
+
): Request {
|
|
17
|
+
const { method = "GET", body, headers = {} } = options || {};
|
|
18
|
+
|
|
19
|
+
return new Request(url, {
|
|
20
|
+
method,
|
|
21
|
+
headers: {
|
|
22
|
+
"Content-Type": "application/json",
|
|
23
|
+
...headers,
|
|
24
|
+
},
|
|
25
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Responseλ₯Ό JSONμΌλ‘ νμ±
|
|
31
|
+
*/
|
|
32
|
+
export async function parseJsonResponse<T = unknown>(response: Response): Promise<T> {
|
|
33
|
+
return response.json() as Promise<T>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Response μν κ²μ¦
|
|
38
|
+
*/
|
|
39
|
+
export function assertStatus(response: Response, expectedStatus: number): void {
|
|
40
|
+
if (response.status !== expectedStatus) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`Expected status ${expectedStatus}, got ${response.status}`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* ν
μ€νΈμ© λΌμ°νΈ νλΌλ―Έν° μμ±
|
|
49
|
+
*/
|
|
50
|
+
export function createParams(params: Record<string, string>): Record<string, string> {
|
|
51
|
+
return params;
|
|
52
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
// Mandu Test Setup
|
|
2
|
-
// Bun ν
μ€νΈ νκ²½ μ€μ
|
|
3
|
-
|
|
4
|
-
// ν
μ€νΈ νμμμ μ€μ (νμ μ)
|
|
5
|
-
// import { setDefaultTimeout } from "bun:test";
|
|
6
|
-
// setDefaultTimeout(10000);
|
|
7
|
-
|
|
8
|
-
// νκ²½ λ³μ μ€μ
|
|
9
|
-
process.env.NODE_ENV = "test";
|
|
1
|
+
// Mandu Test Setup
|
|
2
|
+
// Bun ν
μ€νΈ νκ²½ μ€μ
|
|
3
|
+
|
|
4
|
+
// ν
μ€νΈ νμμμ μ€μ (νμ μ)
|
|
5
|
+
// import { setDefaultTimeout } from "bun:test";
|
|
6
|
+
// setDefaultTimeout(10000);
|
|
7
|
+
|
|
8
|
+
// νκ²½ λ³μ μ€μ
|
|
9
|
+
process.env.NODE_ENV = "test";
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ESNext",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"esModuleInterop": true,
|
|
7
|
-
"strict": true,
|
|
8
|
-
"skipLibCheck": true,
|
|
9
|
-
"jsx": "react-jsx",
|
|
10
|
-
"types": ["bun-types"]
|
|
11
|
-
},
|
|
12
|
-
"include": ["apps/**/*.ts", "apps/**/*.tsx"],
|
|
13
|
-
"exclude": ["node_modules"]
|
|
14
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"jsx": "react-jsx",
|
|
10
|
+
"types": ["bun-types"]
|
|
11
|
+
},
|
|
12
|
+
"include": ["apps/**/*.ts", "apps/**/*.tsx"],
|
|
13
|
+
"exclude": ["node_modules"]
|
|
14
|
+
}
|