@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mandujs/cli",
3
- "version": "0.15.4",
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.13.2",
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
+ }
@@ -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)