@ahnseungchan/device-farm 0.0.0-stage → 0.1.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/CHANGELOG.md +20 -0
- package/LICENSE +21 -0
- package/README.md +195 -2
- package/dist/bin.js +79 -0
- package/dist/chaos-CIB7G5X5.js +453 -0
- package/dist/chunk-3GCH6TN3.js +86 -0
- package/dist/chunk-4UOXXMXL.js +74 -0
- package/dist/chunk-7OJDY276.js +16 -0
- package/dist/chunk-AVECSGEH.js +1462 -0
- package/dist/chunk-AXQNUPGC.js +416 -0
- package/dist/chunk-F4TMB4WY.js +54 -0
- package/dist/chunk-F6PZOBU6.js +167 -0
- package/dist/chunk-JD2L6T7E.js +56 -0
- package/dist/chunk-JZDYF3HL.js +169 -0
- package/dist/chunk-NJNTEWXE.js +134 -0
- package/dist/chunk-NLX3V7ZJ.js +134 -0
- package/dist/chunk-OUNLQI2N.js +34 -0
- package/dist/chunk-P2M55S53.js +110 -0
- package/dist/chunk-PLMSQ5KF.js +173 -0
- package/dist/chunk-PYZWMVJ6.js +302 -0
- package/dist/chunk-PZZQWRSU.js +283 -0
- package/dist/chunk-Q6CXF3NJ.js +102 -0
- package/dist/chunk-S6GHOP4B.js +111 -0
- package/dist/chunk-SWW42KTA.js +223 -0
- package/dist/chunk-UAHWEUDI.js +68 -0
- package/dist/chunk-VGPCTMUA.js +258 -0
- package/dist/chunk-WTTCWFOX.js +161 -0
- package/dist/chunk-ZE2GGXFO.js +169 -0
- package/dist/device-explore-PHS6SVEJ.js +64 -0
- package/dist/device-run-XPNRYXEL.js +60 -0
- package/dist/explore-allowlist-F44YQDVK.js +44 -0
- package/dist/index.d.ts +192 -0
- package/dist/index.js +24 -0
- package/dist/init-QFFEOJWY.js +29 -0
- package/dist/policy-check-AUWGFNHZ.js +75 -0
- package/dist/policy-watch-QPHI2P7Z.js +66 -0
- package/dist/qa-run-5LLQMLAM.js +59 -0
- package/dist/record-EGHVQFR7.js +152 -0
- package/dist/serve-XT32MAEN.js +1687 -0
- package/dist/server-3NM7ODUG.js +251 -0
- package/dist/templates/env.example +48 -0
- package/dist/templates/farm.config.ts +104 -0
- package/dist/web/assets/index-BCuSKs27.js +17 -0
- package/dist/web/assets/index-C3is0x-E.css +1 -0
- package/dist/web/favicon.svg +1 -0
- package/dist/web/index.html +14 -0
- package/package.json +95 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
`@ahnseungchan/device-farm`의 바뀐 점을 적는다.
|
|
4
|
+
|
|
5
|
+
## [0.1.0] - 2026-11-11
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- npm 패키지로 첫 배포. `npm i -D @ahnseungchan/device-farm` 후 `npx device-farm <명령>`으로 쓴다
|
|
10
|
+
- `init`: 작업 폴더에 `farm.config.ts`·`.env.example` 생성(있으면 건너뜀)
|
|
11
|
+
- `dev [--port]`: 미리 빌드한 화면을 vite preview로 열고 기기 API·스트림을 붙인다
|
|
12
|
+
- `check`·`explore`·`chaos`·`record`·`qa`·`policy-watch`·`policy-check`·`explore-allowlist`: 기존 CLI
|
|
13
|
+
- `mcp`: Claude Code용 MCP 서버(stdio). 결과·`.env`·설정은 작업 폴더 기준
|
|
14
|
+
- 패키지 루트에서 `defineAppConfig`·`AppConfigInput`·`AppConfig` 타입과 데이터 QA 도우미(`equalWon`·`formatWon`·`numberAt`·`textHas`·`pick`)를 내보낸다
|
|
15
|
+
- 빌드: tsup으로 워크스페이스를 `dist/`에 묶고, 화면은 `vite build`, 설정 타입은 `dist/index.d.ts` 하나
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- `explore/allowlist.json`·`explore/form-values.json`을 작업 폴더에서 읽는다. 없으면 빈 허용 목록(모든 쓰기 요청 차단)·빈 입력값
|
|
20
|
+
- MCP 서버는 `FARM_ROOT`가 있으면 그 폴더를 결과·`.env` 기준으로 쓴다
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ahnseungc
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,196 @@
|
|
|
1
|
-
#
|
|
1
|
+
# device-farm
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@ahnseungchan/device-farm)
|
|
4
|
+
[](https://github.com/Ahnseungc/device-farm/blob/main/LICENSE)
|
|
5
|
+
[](https://nodejs.org/)
|
|
6
|
+
|
|
7
|
+
**WebView로 만든 모바일 앱을 실기기에서 QA 자동화하는 디바이스팜. 내 프로젝트에 설치하고 `farm.config.ts` 하나로 점검할 앱을 정한다.**
|
|
8
|
+
|
|
9
|
+
Android는 adb + Chrome DevTools Protocol(Playwright), iOS는 Appium(XCUITest·WebDriverAgent)으로 앱 안 WebView를 직접 조작합니다. 기준 화면 비교, 버튼 눌러보기·화면 지도, 플로우 녹화, 카오스 탐색, 정책·피그마 대조를 웹 화면과 CLI로 돌리고, Claude Code가 MCP 도구로 기기를 탐색할 수 있습니다.
|
|
10
|
+
|
|
11
|
+
## Why
|
|
12
|
+
|
|
13
|
+
| 손으로 하는 QA | device-farm |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| 새 빌드가 나오면 기기마다 깔고 화면을 하나씩 열어 본다 | Firebase App Distribution·TestFlight 새 빌드를 받아 연결된 기기 전부에 깔고, 고정 주소 화면을 딥링크로 열어 기준 화면과 픽셀 비교 |
|
|
16
|
+
| 버튼을 다 눌러 봤는지 기억에 의존한다 | 화면의 버튼을 하나씩 눌러 반응(이동·팝업·반응 없음·오류)을 남기고, 결과가 **화면 지도**가 된다 |
|
|
17
|
+
| 시나리오 테스트를 코드로 짜고 고친다 | 사람이 기기를 움직이면 녹화해 플로우 후보로 올리고, 확정한 것만 돈다 |
|
|
18
|
+
| 뒤로·연타·백그라운드 같은 엣지 케이스는 운에 맡긴다 | 시드 고정 카오스 탐색으로 재현 가능한 무작위 조작 |
|
|
19
|
+
| 정책 문서·피그마 문구와 화면이 맞는지 눈으로 대조한다 | Confluence 정책·피그마 문구를 화면 글자와 맞춰 본다 |
|
|
20
|
+
| 탐색 자동화가 실제 결제·탈퇴를 누를까 무섭다 | 쓰기 요청 가드(허용 목록 밖 POST/PUT/PATCH/DELETE 차단)와 금지 버튼 목록. 가드를 못 걸면 시작하지 않는다 |
|
|
21
|
+
|
|
22
|
+
점검할 앱은 코드에 없습니다. 패키지 id·웹 주소·하단 탭·금지어·시나리오처럼 앱마다 다른 값은 전부 `farm.config.ts`로 **주입**합니다.
|
|
23
|
+
|
|
24
|
+
## Features
|
|
25
|
+
|
|
26
|
+
- **빌드 점검**: 새 빌드 감시 → 설치 → 고정 주소 화면 점검. 앱 오류 문구·로그인 튕김·빈 화면·기준 화면 픽셀 비교로 판정
|
|
27
|
+
- **버튼 눌러보기·화면 지도**: 버튼별 반응을 기록하고, 화면 간 이동을 지도로 쌓는다. 지도 블록을 이어 붙여 플로우를 만든다
|
|
28
|
+
- **플로우**: 홈에서 버튼·링크만 눌러 가는 시나리오. 녹화, 화면 지도 조립, PRD로 LLM 후보 생성(선택)
|
|
29
|
+
- **데이터 QA**: 화면이 부르는 GET API를 다시 불러 합계·표시 값을 규칙으로 맞춰 본다(LLM 없음)
|
|
30
|
+
- **탐색·카오스 탐색**: 너비 우선 탐색과 시드 고정 카오스(뒤로·연타·로딩 중 뒤로·백그라운드·재시작)
|
|
31
|
+
- **정책·피그마 대조**: Confluence 정책 바뀜 감지, 규칙 뽑기, 피그마 문구 대조
|
|
32
|
+
- **원격 조작**: 웹 화면에서 기기 화면을 보며(Android H.264, iOS MJPEG) 직접 누른다. 조작 중인 기기는 잠근다
|
|
33
|
+
- **Claude Code MCP**: `device_start`·`device_look`·`device_press`… 도구로 Claude가 기기를 탐색하고, 찾은 경로를 플로우 후보로 올린다
|
|
34
|
+
|
|
35
|
+
## 준비물
|
|
36
|
+
|
|
37
|
+
| 무엇 | 언제 필요 |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| macOS, Node.js 22 이상 | 항상 |
|
|
40
|
+
| `adb`(Android platform-tools), USB 디버깅을 켠 기기 | Android |
|
|
41
|
+
| WebView 디버깅을 켠 dev 빌드: `WebView.setWebContentsDebuggingEnabled(true)` | Android |
|
|
42
|
+
| Xcode, Appium + XCUITest 드라이버(WebDriverAgent), `isInspectable = true`인 빌드 | iOS |
|
|
43
|
+
| Firebase 서비스 계정, App Store Connect API 키 | 새 빌드 자동 받기(선택) |
|
|
44
|
+
| Confluence API 토큰, Figma 토큰 | 정책·피그마 대조(선택) |
|
|
45
|
+
|
|
46
|
+
점검할 앱이 맞춰야 할 것:
|
|
47
|
+
|
|
48
|
+
1. **WebView 디버깅**이 켜진 빌드. 디바이스팜은 이 디버깅 소켓으로 들어간다.
|
|
49
|
+
2. **경로형 딥링크**: `<scheme>://<웹 경로>`를 받으면 그 웹 경로를 WebView로 연다.
|
|
50
|
+
3. **하단 탭이 웹 링크**(`a[href]` 또는 `role=link`)이고, 화면 상단 뒤로 버튼에 이름(`aria-label`)이 있다.
|
|
51
|
+
4. 점검용 계정으로 로그인해 둔다. 로그인 화면으로 튕기면 「로그인 필요」로 남는다.
|
|
52
|
+
|
|
53
|
+
호스트 맥·기기 준비는 [docs/operations.md](https://github.com/Ahnseungc/device-farm/blob/main/docs/operations.md)에 있습니다.
|
|
54
|
+
|
|
55
|
+
## 설치
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm install -D @ahnseungchan/device-farm
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Quick start
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npx device-farm init # farm.config.ts, .env.example 생성(이미 있으면 건너뜀)
|
|
65
|
+
# farm.config.ts를 내 앱 값으로 고친다. 토큰이 필요하면 .env.example → .env
|
|
66
|
+
npx device-farm dev # http://127.0.0.1:5173 (--port 로 변경)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`init`이 만드는 설정은 가상의 쇼핑 앱 「Sample Shop」 예시입니다. 필수 값만 남기면 이 정도입니다.
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
// farm.config.ts
|
|
73
|
+
import { defineAppConfig } from "@ahnseungchan/device-farm";
|
|
74
|
+
|
|
75
|
+
export default defineAppConfig({
|
|
76
|
+
name: "My App",
|
|
77
|
+
appIds: { dev: "com.example.app.dev", prod: "com.example.app" },
|
|
78
|
+
web: { dev: "https://dev.app.example.com", prod: "https://app.example.com" },
|
|
79
|
+
homePath: "/home",
|
|
80
|
+
homeTabName: "홈",
|
|
81
|
+
tabPaths: { 검색: "/search", 마이: "/my" },
|
|
82
|
+
deepLink: { scheme: "myapp", homeHost: "home" },
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`.gitignore`에 `.env`와 `test-results/`를 넣어 두세요. 결과(실행 기록·스크린샷·기준 화면·화면 지도)는 전부 작업 폴더의 `test-results/`에 쌓입니다.
|
|
87
|
+
|
|
88
|
+
## 앱 설정 (`farm.config.ts`)
|
|
89
|
+
|
|
90
|
+
`defineAppConfig()`가 빠진 값을 기본값으로 채웁니다. 타입은 `AppConfigInput`·`AppConfig`로 패키지에서 가져올 수 있습니다.
|
|
91
|
+
|
|
92
|
+
| 필드 | 필수 | 설명 |
|
|
93
|
+
| --- | --- | --- |
|
|
94
|
+
| `name` | ✓ | 앱 이름. 화면 제목·프롬프트에 쓴다 |
|
|
95
|
+
| `appIds` | ✓ | `{ dev, prod }` Android 패키지 = iOS 번들 id. 다르면 `iosBundleIds` |
|
|
96
|
+
| `homePath`·`homeTabName`·`tabPaths` | ✓ | 홈 경로, 홈 탭 이름, 하단 탭 이름 → 경로 |
|
|
97
|
+
| `web` | | `{ dev, prod }` WebView 웹 주소. 비면 `FARM_WEB_ORIGIN_DEV`·`FARM_WEB_ORIGIN_PROD` |
|
|
98
|
+
| `apiOrigin` | | API 호스트(웹과 다를 때). 가드가 내부로 보고 데이터 QA가 부른다 |
|
|
99
|
+
| `inPageBackNames` | | 화면 상단 뒤로 버튼 이름. 기본 `["뒤로가기", "Back"]` |
|
|
100
|
+
| `loginPaths`·`appErrorText` | | 로그인 화면 경로, 앱 오류 문구. 기본 `["/login"]`, `"Application error"` |
|
|
101
|
+
| `deepLink` | | `{ scheme, homeHost }` 경로형 딥링크 |
|
|
102
|
+
| `android.mainActivity` | | WebView 액티비티. 다른 네이티브 화면이 위에 있으면 뒤로 키로 닫는다 |
|
|
103
|
+
| `webview` | | `stackResetScript`(WebView 스택 닫기 브리지), `sheetCloseName`(시트 닫기 버튼) |
|
|
104
|
+
| `guard` | | 기본 금지어·금지 API에 더할 `extraForbiddenButtonWords`·`extraForbiddenClickNames`·`extraForbiddenApiPatterns`, 늘 통과시킬 `pushTokenRequests`, 누르지 않을 `avoidedNamePattern`·`noPressPathPattern` |
|
|
105
|
+
| `scenarios`·`excludedScenarioPaths` | | 고정 주소 화면 점검 목록과 뺄 경로 |
|
|
106
|
+
| `qa` | | `{ screens, sessionPath }` 데이터 QA 화면·규칙, 로그인 세션 경로 |
|
|
107
|
+
| `policy.confluence` | | `{ cloudId, spaceKey, titleQuery, siteUrl }` 정책 정본 |
|
|
108
|
+
| `figma` | | `{ frontRepo, ref, appDir }` 화면 → 피그마 노드를 뽑을 프론트 체크아웃 |
|
|
109
|
+
| `install` | | `sources`(플랫폼·빌드별 `firebase`/`testflight`), `firebase`(앱 id), `testflightDevAppName` |
|
|
110
|
+
| `ios` | | `{ teamId, wdaBundleId }` WDA 서명. 비면 `IOS_TEAM_ID`·`IOS_WDA_BUNDLE_ID` |
|
|
111
|
+
| `access.allowedDomains` | | 화면에 들어올 수 있는 Tailscale 계정 도메인. 비면 이 맥에서만 |
|
|
112
|
+
|
|
113
|
+
데이터 QA 규칙용 도우미 `equalWon`·`formatWon`·`numberAt`·`textHas`·`pick`도 패키지 루트에서 내보냅니다.
|
|
114
|
+
|
|
115
|
+
설정 파일은 `FARM_CONFIG` → 작업 폴더 `farm.config.ts` → `farm.config.example.ts` 순으로 찾습니다. 비밀 값과 이 맥에만 해당하는 값은 작업 폴더 `.env`에 둡니다(항목은 `.env.example` 참고).
|
|
116
|
+
|
|
117
|
+
## CLI
|
|
118
|
+
|
|
119
|
+
모든 명령은 작업 폴더(내 프로젝트) 기준으로 설정·`.env`를 읽고 결과를 씁니다. `npx device-farm <명령> --help`로 옵션을 볼 수 있습니다.
|
|
120
|
+
|
|
121
|
+
| 명령 | 내용 |
|
|
122
|
+
| --- | --- |
|
|
123
|
+
| `device-farm init` | `farm.config.ts`·`.env.example` 생성 |
|
|
124
|
+
| `device-farm dev [--port 5173]` | 화면 + API 서버(127.0.0.1). 새 빌드 감시·정책 감시가 함께 돈다 |
|
|
125
|
+
| `device-farm check --build dev\|prod --version 2.1.5 [--devices all\|id,id] [--port 5173]` | 떠 있는 서버에 빌드 점검을 시키고 끝날 때까지 진행을 출력 |
|
|
126
|
+
| `device-farm explore [--devices all\|id,id] [--build dev] [--port 5173]` | 탐색 점검(서버 필요) |
|
|
127
|
+
| `device-farm chaos --device <serial> [--seed 42] [--episodes 3] [--steps 50] [--minutes 60] [--no-guard]` | 카오스 탐색(Android dev) |
|
|
128
|
+
| `device-farm record --device <serial> [--seconds 300] [--name "플로우 이름"]` | 사람 조작 녹화 → 화면 지도·플로우 후보 |
|
|
129
|
+
| `device-farm qa --device <id> [--path /cart]` | 데이터 QA만 |
|
|
130
|
+
| `device-farm policy-watch [--extract <pageId>] [--figma]` | 정책 바뀜 감지·규칙 뽑기·피그마 문구 |
|
|
131
|
+
| `device-farm policy-check --device <id> [--path /my/orders]` | 정책·피그마 대조를 기기 한 대에서 |
|
|
132
|
+
| `device-farm explore-allowlist` | OpenAPI(`FARM_OPENAPI_URL`)의 쓰기 API를 `explore/allowlist.json`에 deny로 추가 |
|
|
133
|
+
| `device-farm mcp` | Claude Code용 MCP 서버(stdio) |
|
|
134
|
+
|
|
135
|
+
탐색 허용 목록 `explore/allowlist.json`, 입력칸 테스트 값 `explore/form-values.json`, 정책 등록 `policy/sources.json`은 작업 폴더에 두고 커밋합니다. 허용 목록이 없으면 모든 쓰기 요청을 막습니다.
|
|
136
|
+
|
|
137
|
+
## Claude Code (MCP)
|
|
138
|
+
|
|
139
|
+
내 프로젝트 폴더에서 MCP 서버를 등록합니다.
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
claude mcp add --scope project device-farm -- npx device-farm mcp
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
도구 흐름: `device_start` → `device_look` / `device_press` / `device_go_home` / `device_report_issue` → `device_finish`. 끝나면 화면 지도에 합치고, 홈부터 다시 돌 수 있는 경로면 플로우 후보로 올립니다. 보고서는 `test-results/agent/<시각>/report.json`. 탐색은 Android dev 빌드에서 쓰기 요청 가드를 켠 채로만 돕니다.
|
|
146
|
+
|
|
147
|
+
이 저장소는 Claude Code 플러그인(`.claude-plugin`, `skills/run-check`·`chaos-explore`·`policy-figma-check`)으로도 쓸 수 있습니다. 저장소를 받아 `npm install`한 뒤 플러그인으로 추가하세요.
|
|
148
|
+
|
|
149
|
+
## 안전
|
|
150
|
+
|
|
151
|
+
- 탐색·버튼 눌러보기·카오스·MCP 탐색은 **Android dev 빌드**에서, 쓰기 요청 가드를 켠 채로 돈다. 가드를 못 걸면 시작하지 않는다(fail closed).
|
|
152
|
+
- 가드 판단 순서: 읽기(GET·HEAD·OPTIONS) 통과 → `pushTokenRequests` 통과 → 금지 경로 차단 → 허용 목록 allow 통과 → deny·모름 차단.
|
|
153
|
+
- 기본 금지 버튼(구매·결제·매도·매수·송금·출금·탈퇴·주문·환불·해지·삭제·로그아웃 등)은 목록에서 빠지고, 눌러도 거절한다. 앱 설정은 금지어를 **더할** 뿐 빼지 못한다. 비밀번호·PIN 화면은 누르지 않고 나가기만 한다.
|
|
154
|
+
- `chaos --no-guard`는 쓰기 요청을 막지 않는다. **dev 서버에서만** 쓴다. 금지 버튼은 그대로 누르지 않는다.
|
|
155
|
+
- 화면 서버는 127.0.0.1에만 열린다. 밖에서는 `tailscale serve`로만 들어오고, `access.allowedDomains`로 계정 도메인을 제한한다.
|
|
156
|
+
|
|
157
|
+
## FAQ
|
|
158
|
+
|
|
159
|
+
**기기 없이 화면만 열어 볼 수 있나요?**
|
|
160
|
+
네. `npx device-farm init && npx device-farm dev`면 예시 설정으로 화면이 열립니다. 기기 목록은 비어 있고 점검은 돌지 않습니다.
|
|
161
|
+
|
|
162
|
+
**iOS도 탐색·카오스가 되나요?**
|
|
163
|
+
빌드 점검·원격 조작은 iOS(Appium)를 지원합니다. 탐색·카오스·녹화·MCP 탐색은 지금 Android dev 빌드만 지원합니다.
|
|
164
|
+
|
|
165
|
+
**`check`가 서버에 연결할 수 없다고 합니다.**
|
|
166
|
+
`check`·`explore`는 떠 있는 `device-farm dev` 서버에 작업을 시킵니다. 다른 터미널에서 `dev`를 먼저 켜고, 포트를 바꿨다면 같은 `--port`를 넘기세요.
|
|
167
|
+
|
|
168
|
+
**LLM 키가 꼭 필요한가요?**
|
|
169
|
+
아니요. 점검·탐색·카오스·데이터 QA는 규칙으로만 판정합니다. PRD로 플로우 후보를 만들 때(`FLOW_LLM_*`)와 정책 판정에만 씁니다. MCP는 사용자의 Claude Code 계정으로 돕니다.
|
|
170
|
+
|
|
171
|
+
**결과 파일은 어디에 생기나요?**
|
|
172
|
+
작업 폴더의 `test-results/`. 커밋하지 마세요.
|
|
173
|
+
|
|
174
|
+
## 문서
|
|
175
|
+
|
|
176
|
+
- [운영: 호스트 맥과 기기 준비](https://github.com/Ahnseungc/device-farm/blob/main/docs/operations.md)
|
|
177
|
+
- [설계: 빌드 점검·기준 화면·플로우·화면 지도](https://github.com/Ahnseungc/device-farm/blob/main/docs/design-device-farm.md)
|
|
178
|
+
- [설계: 탐색 점검과 쓰기 요청 가드](https://github.com/Ahnseungc/device-farm/blob/main/docs/design-explore.md)
|
|
179
|
+
- [설계: 카오스 탐색](https://github.com/Ahnseungc/device-farm/blob/main/docs/design-chaos.md)
|
|
180
|
+
- [설계: 정책·피그마 대조](https://github.com/Ahnseungc/device-farm/blob/main/docs/design-policy-figma.md)
|
|
181
|
+
|
|
182
|
+
## 개발
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
git clone https://github.com/Ahnseungc/device-farm.git && cd device-farm
|
|
186
|
+
npm install
|
|
187
|
+
npm run dev # 화면 개발(vite dev). 저장소 루트 farm.config.ts → farm.config.example.ts
|
|
188
|
+
npm run typecheck && npm test
|
|
189
|
+
npm run build # dist/: bin·설정 타입(tsup), 화면(vite build), init 템플릿
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
npm workspaces 모노레포입니다. 의존 방향은 `core ← devices ← engine ← server ← apps`이고 `tools/boundaries.test.ts`가 지킵니다. 배포 패키지는 워크스페이스를 `dist/`에 묶어 냅니다.
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
|
|
196
|
+
[MIT](https://github.com/Ahnseungc/device-farm/blob/main/LICENSE)
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
defaultPort
|
|
4
|
+
} from "./chunk-4UOXXMXL.js";
|
|
5
|
+
|
|
6
|
+
// apps/cli/src/bin.ts
|
|
7
|
+
import { register } from "tsx/esm/api";
|
|
8
|
+
var commands = {
|
|
9
|
+
init: { usage: "init", summary: "farm.config.ts\xB7.env.example\uC744 \uB9CC\uB4E0\uB2E4(\uC788\uC73C\uBA74 \uAC74\uB108\uB700)", run: async () => (await import("./init-QFFEOJWY.js")).init() },
|
|
10
|
+
dev: { usage: "dev [--port 5173]", summary: "\uB514\uBC14\uC774\uC2A4\uD31C \uD654\uBA74\xB7API \uC11C\uBC84\uB97C \uC5F0\uB2E4(127.0.0.1)", run: runDev },
|
|
11
|
+
check: {
|
|
12
|
+
usage: "check --build dev|prod --version 2.1.5 [--devices all|id,id] [--port 5173]",
|
|
13
|
+
summary: "\uAE30\uC900 \uD654\uBA74 \uBE44\uAD50\xB7\uBC84\uD2BC \uB20C\uB7EC\uBCF4\uAE30 \uC810\uAC80(dev \uC11C\uBC84\uAC00 \uB5A0 \uC788\uC5B4\uC57C \uD55C\uB2E4)",
|
|
14
|
+
run: () => import("./device-run-XPNRYXEL.js")
|
|
15
|
+
},
|
|
16
|
+
explore: { usage: "explore [--devices all|id,id] [--build dev] [--port 5173]", summary: "\uD654\uBA74 \uD0D0\uC0C9(dev \uC11C\uBC84\uAC00 \uB5A0 \uC788\uC5B4\uC57C \uD55C\uB2E4)", run: () => import("./device-explore-PHS6SVEJ.js") },
|
|
17
|
+
chaos: {
|
|
18
|
+
usage: "chaos --device <Android serial> [--seed 42] [--episodes 3] [--steps 50] [--minutes 60] [--no-guard]",
|
|
19
|
+
summary: "\uCE74\uC624\uC2A4 \uD0D0\uC0C9. --no-guard\uB294 \uC4F0\uAE30 \uC694\uCCAD\uC744 \uB9C9\uC9C0 \uC54A\uB294\uB2E4(dev \uC11C\uBC84 \uC804\uC6A9)",
|
|
20
|
+
run: () => import("./chaos-CIB7G5X5.js")
|
|
21
|
+
},
|
|
22
|
+
record: { usage: 'record --device <Android serial> [--seconds 300] [--name "\uD50C\uB85C\uC6B0 \uC774\uB984"]', summary: "\uC0AC\uB78C \uC870\uC791\uC744 \uB179\uD654\uD574 \uD654\uBA74 \uC9C0\uB3C4\xB7\uD50C\uB85C\uC6B0 \uD6C4\uBCF4\uB85C", run: () => import("./record-EGHVQFR7.js") },
|
|
23
|
+
qa: { usage: "qa --device <id> [--path /home]", summary: "\uD654\uBA74 \uB370\uC774\uD130 QA(API \uC751\uB2F5 \u2194 \uD654\uBA74 \uAE00\uC790)", run: () => import("./qa-run-5LLQMLAM.js") },
|
|
24
|
+
"policy-watch": { usage: "policy-watch [--extract <pageId>] [--figma]", summary: "Confluence \uC815\uCC45 \uBC14\uB01C \uAC10\uC9C0\xB7\uADDC\uCE59 \uBF51\uAE30\xB7\uD53C\uADF8\uB9C8 \uCE90\uC2DC", run: () => import("./policy-watch-QPHI2P7Z.js") },
|
|
25
|
+
"policy-check": { usage: "policy-check --device <id> [--path /my/orders]", summary: "\uC815\uCC45\xB7\uD53C\uADF8\uB9C8 \uB300\uC870\uB97C \uAE30\uAE30 \uD55C \uB300\uC5D0\uC11C", run: () => import("./policy-check-AUWGFNHZ.js") },
|
|
26
|
+
"explore-allowlist": { usage: "explore-allowlist", summary: "FARM_OPENAPI_URL \uC2A4\uD399\uC758 \uC4F0\uAE30 API\uB97C explore/allowlist.json\uC5D0 deny\uB85C \uCD94\uAC00", run: () => import("./explore-allowlist-F44YQDVK.js") },
|
|
27
|
+
mcp: { usage: "mcp", summary: "Claude Code\uC6A9 MCP \uC11C\uBC84(stdio)", run: runMcp }
|
|
28
|
+
};
|
|
29
|
+
function usage() {
|
|
30
|
+
const width = Math.max(...Object.keys(commands).map((name) => name.length));
|
|
31
|
+
const lines = Object.entries(commands).map(([name, command]) => ` ${name.padEnd(width)} ${command.summary}`);
|
|
32
|
+
return `\uC0AC\uC6A9\uBC95: device-farm <\uBA85\uB839> [\uC635\uC158]
|
|
33
|
+
|
|
34
|
+
\uBA85\uB839
|
|
35
|
+
${lines.join("\n")}
|
|
36
|
+
|
|
37
|
+
\uBA85\uB839\uBCC4 \uC635\uC158: device-farm <\uBA85\uB839> --help
|
|
38
|
+
\uC571 \uC124\uC815: FARM_CONFIG \u2192 ./farm.config.ts \u2192 ./farm.config.example.ts, \uBE44\uBC00 \uAC12: ./.env`;
|
|
39
|
+
}
|
|
40
|
+
async function runDev() {
|
|
41
|
+
const index = process.argv.indexOf("--port");
|
|
42
|
+
const raw = index >= 0 ? process.argv[index + 1] : process.env.FARM_PORT;
|
|
43
|
+
const port = raw ? Number(raw) : defaultPort;
|
|
44
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error(`--port\uB294 1~65535 \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4: ${raw}`);
|
|
45
|
+
process.env.FARM_PORT = String(port);
|
|
46
|
+
await (await import("./serve-XT32MAEN.js")).serve(port);
|
|
47
|
+
}
|
|
48
|
+
async function runMcp() {
|
|
49
|
+
process.env.FARM_ROOT ??= process.cwd();
|
|
50
|
+
await import("./server-3NM7ODUG.js");
|
|
51
|
+
}
|
|
52
|
+
async function main() {
|
|
53
|
+
const [name, ...rest] = process.argv.slice(2);
|
|
54
|
+
if (!name || name === "--help" || name === "-h" || name === "help") {
|
|
55
|
+
console.log(usage());
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const command = commands[name];
|
|
59
|
+
if (!command) {
|
|
60
|
+
console.error(`\uC54C \uC218 \uC5C6\uB294 \uBA85\uB839\uC785\uB2C8\uB2E4: ${name}
|
|
61
|
+
|
|
62
|
+
${usage()}`);
|
|
63
|
+
process.exitCode = 1;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (rest.includes("--help") || rest.includes("-h")) {
|
|
67
|
+
console.log(`\uC0AC\uC6A9\uBC95: device-farm ${command.usage}
|
|
68
|
+
|
|
69
|
+
${command.summary}`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
process.argv.splice(2, 1);
|
|
73
|
+
register();
|
|
74
|
+
await command.run();
|
|
75
|
+
}
|
|
76
|
+
main().catch((error) => {
|
|
77
|
+
console.error(error instanceof Error ? error.message : error);
|
|
78
|
+
process.exitCode = 1;
|
|
79
|
+
});
|