@mandujs/cli 0.15.4 → 0.16.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 +3 -2
- package/src/commands/add.ts +64 -0
- package/src/commands/registry.ts +39 -0
- package/src/commands/test-auto.ts +59 -0
- package/src/commands/test-heal.ts +15 -0
- package/src/main.ts +7 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mandujs/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Agent-Native Fullstack Framework - 에이전트가 코딩해도 아키텍처가 무너지지 않는 개발 OS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/main.ts",
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
"access": "public"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@mandujs/core": "^0.
|
|
35
|
+
"@mandujs/core": "^0.16.0",
|
|
36
|
+
"@mandujs/ate": "0.16.0",
|
|
36
37
|
"cfonts": "^3.3.0"
|
|
37
38
|
},
|
|
38
39
|
"engines": {
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { theme } from "../terminal";
|
|
5
|
+
|
|
6
|
+
function run(cmd: string, args: string[], cwd: string): void {
|
|
7
|
+
const res = spawnSync(cmd, args, { cwd, stdio: "inherit" });
|
|
8
|
+
if (res.error) throw res.error;
|
|
9
|
+
if (res.status !== 0) throw new Error(`Command failed: ${cmd} ${args.join(" ")}`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function runTry(cmd: string, args: string[], cwd: string): boolean {
|
|
13
|
+
try {
|
|
14
|
+
run(cmd, args, cwd);
|
|
15
|
+
return true;
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function addTest({ cwd = process.cwd() }: { cwd?: string } = {}): Promise<boolean> {
|
|
22
|
+
const pkgPath = join(cwd, "package.json");
|
|
23
|
+
if (!existsSync(pkgPath)) {
|
|
24
|
+
console.error(theme.error("package.json not found. Run inside a Mandu project."));
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log(theme.heading("🥟 Mandu ATE: installing test automation deps..."));
|
|
29
|
+
|
|
30
|
+
// Install in project (no external SaaS deps)
|
|
31
|
+
// NOTE: @mandujs/ate might not be published yet. In that case, fallback to local file: install.
|
|
32
|
+
const installed = runTry("bun", ["add", "-d", "@mandujs/ate", "@playwright/test", "playwright"], cwd);
|
|
33
|
+
if (!installed) {
|
|
34
|
+
const localAtePath = join(cwd, "..", "mandu", "packages", "ate");
|
|
35
|
+
const localPkg = join(localAtePath, "package.json");
|
|
36
|
+
if (existsSync(localPkg)) {
|
|
37
|
+
console.warn(theme.warn(`@mandujs/ate not found on npm. Falling back to local install: file:${localAtePath}`));
|
|
38
|
+
run("bun", ["add", "-d", `file:${localAtePath}`, "@playwright/test", "playwright"], cwd);
|
|
39
|
+
} else {
|
|
40
|
+
console.error(theme.error("@mandujs/ate not found on npm, and local fallback path not found."));
|
|
41
|
+
console.error(theme.muted(`Expected: ${localPkg}`));
|
|
42
|
+
console.error(theme.muted("Tip: install manually with: bun add -d file:/abs/path/to/mandu/packages/ate"));
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const browserInstalled = runTry("bunx", ["playwright", "install", "chromium"], cwd);
|
|
48
|
+
if (!browserInstalled) {
|
|
49
|
+
console.error(theme.error("Failed to install Playwright browsers. Try manually: bunx playwright install chromium"));
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Create directories and baseline config
|
|
54
|
+
const dirs = [
|
|
55
|
+
join(cwd, "tests", "e2e", "auto"),
|
|
56
|
+
join(cwd, "tests", "e2e", "manual"),
|
|
57
|
+
join(cwd, ".mandu", "scenarios"),
|
|
58
|
+
join(cwd, ".mandu", "reports"),
|
|
59
|
+
];
|
|
60
|
+
for (const d of dirs) mkdirSync(d, { recursive: true });
|
|
61
|
+
|
|
62
|
+
console.log(theme.success("✅ ATE installed. Next: bunx mandu test:auto"));
|
|
63
|
+
return true;
|
|
64
|
+
}
|
package/src/commands/registry.ts
CHANGED
|
@@ -348,6 +348,45 @@ registerCommand({
|
|
|
348
348
|
},
|
|
349
349
|
});
|
|
350
350
|
|
|
351
|
+
// ============================================================================
|
|
352
|
+
// ATE (Automation Test Engine)
|
|
353
|
+
// ============================================================================
|
|
354
|
+
|
|
355
|
+
registerCommand({
|
|
356
|
+
id: "add",
|
|
357
|
+
description: "프로젝트에 기능 추가",
|
|
358
|
+
subcommands: ["test"],
|
|
359
|
+
async run(ctx) {
|
|
360
|
+
const sub = ctx.args[1];
|
|
361
|
+
if (sub !== "test") return false;
|
|
362
|
+
const { addTest } = await import("./add");
|
|
363
|
+
return addTest({ cwd: process.cwd() });
|
|
364
|
+
},
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
registerCommand({
|
|
368
|
+
id: "test:auto",
|
|
369
|
+
description: "ATE 자동 E2E 생성/실행",
|
|
370
|
+
async run(ctx) {
|
|
371
|
+
const { testAuto } = await import("./test-auto");
|
|
372
|
+
return testAuto({
|
|
373
|
+
ci: ctx.options.ci === "true",
|
|
374
|
+
impact: ctx.options.impact === "true",
|
|
375
|
+
baseURL: ctx.options["base-url"] || ctx.options.baseURL || ctx.options.baseUrl,
|
|
376
|
+
});
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
registerCommand({
|
|
381
|
+
id: "test:heal",
|
|
382
|
+
description: "ATE healing 제안 생성(자동 커밋 금지)",
|
|
383
|
+
async run() {
|
|
384
|
+
const { testHeal } = await import("./test-heal");
|
|
385
|
+
return testHeal();
|
|
386
|
+
},
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
|
|
351
390
|
// 레거시 명령어 (DEPRECATED)
|
|
352
391
|
registerCommand({
|
|
353
392
|
id: "spec-upsert",
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { theme } from "../terminal";
|
|
2
|
+
import { ateExtract, ateGenerate, ateRun, ateReport, ateImpact } from "@mandujs/ate";
|
|
3
|
+
|
|
4
|
+
function jsonOut(obj: unknown) {
|
|
5
|
+
console.log(JSON.stringify(obj, null, 2));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export async function testAuto(opts: { ci?: boolean; impact?: boolean; baseURL?: string } = {}): Promise<boolean> {
|
|
9
|
+
const repoRoot = process.cwd();
|
|
10
|
+
const oracleLevel = "L0" as const;
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
// 1) extract
|
|
14
|
+
const extractRes = await ateExtract({ repoRoot });
|
|
15
|
+
|
|
16
|
+
// 2) impact subset (optional)
|
|
17
|
+
let onlyRoutes: string[] | undefined;
|
|
18
|
+
let impactInfo: any = { mode: "full", changedFiles: [], selectedRoutes: [] };
|
|
19
|
+
if (opts.impact) {
|
|
20
|
+
const impactRes = ateImpact({ repoRoot });
|
|
21
|
+
onlyRoutes = impactRes.selectedRoutes.length ? impactRes.selectedRoutes : undefined;
|
|
22
|
+
impactInfo = {
|
|
23
|
+
mode: onlyRoutes ? "subset" : "full",
|
|
24
|
+
changedFiles: impactRes.changedFiles,
|
|
25
|
+
selectedRoutes: impactRes.selectedRoutes,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 3) generate
|
|
30
|
+
const genRes = ateGenerate({ repoRoot, oracleLevel, onlyRoutes });
|
|
31
|
+
|
|
32
|
+
// 4) run
|
|
33
|
+
const runRes = await ateRun({ repoRoot, ci: opts.ci, headless: opts.ci, baseURL: opts.baseURL });
|
|
34
|
+
|
|
35
|
+
// 5) report
|
|
36
|
+
const repRes = await ateReport({
|
|
37
|
+
repoRoot,
|
|
38
|
+
runId: runRes.runId,
|
|
39
|
+
startedAt: runRes.startedAt,
|
|
40
|
+
finishedAt: runRes.finishedAt,
|
|
41
|
+
exitCode: runRes.exitCode,
|
|
42
|
+
oracleLevel,
|
|
43
|
+
impact: impactInfo,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
jsonOut({
|
|
47
|
+
ok: repRes.summary.ok,
|
|
48
|
+
extract: extractRes,
|
|
49
|
+
generate: genRes,
|
|
50
|
+
run: { runId: runRes.runId, exitCode: runRes.exitCode },
|
|
51
|
+
report: { summaryPath: repRes.summaryPath },
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return repRes.summary.ok;
|
|
55
|
+
} catch (err) {
|
|
56
|
+
console.error(theme.error("ATE test:auto failed"), err);
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { theme } from "../terminal";
|
|
2
|
+
import { ateHeal } from "@mandujs/ate";
|
|
3
|
+
|
|
4
|
+
export async function testHeal(): Promise<boolean> {
|
|
5
|
+
const repoRoot = process.cwd();
|
|
6
|
+
try {
|
|
7
|
+
const runId = "latest"; // minimal skeleton
|
|
8
|
+
const res = ateHeal({ repoRoot, runId });
|
|
9
|
+
console.log(JSON.stringify(res, null, 2));
|
|
10
|
+
return true;
|
|
11
|
+
} catch (err) {
|
|
12
|
+
console.error(theme.error("ATE test:heal failed"), err);
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
package/src/main.ts
CHANGED
|
@@ -60,6 +60,13 @@ Commands:
|
|
|
60
60
|
lock --verify Lockfile 검증 (설정 무결성 확인)
|
|
61
61
|
lock --diff Lockfile과 현재 설정 비교
|
|
62
62
|
|
|
63
|
+
add test ATE 설치 + Playwright 브라우저 준비
|
|
64
|
+
test:auto ATE extract→generate→run→report
|
|
65
|
+
test:auto --ci CI 모드(headless/아티팩트 강화)
|
|
66
|
+
test:auto --impact 변경 파일 기반 subset 실행
|
|
67
|
+
test:auto --base-url <url> 대상 서버 baseURL 지정 (기본: http://localhost:3333)
|
|
68
|
+
test:heal 최근 실패 기반 healing 제안 생성(자동 커밋 금지)
|
|
69
|
+
|
|
63
70
|
Options:
|
|
64
71
|
--name <name> init 시 프로젝트 이름 (기본: my-mandu-app)
|
|
65
72
|
--template <name> init 템플릿: default, realtime-chat (기본: default)
|