@zsukim/ctv-run 1.0.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/README.md ADDED
@@ -0,0 +1,277 @@
1
+ # πŸ“Ί ctv-run
2
+
3
+ > React, Vue λ“± **μ›Ή 기술둜 λ§Œλ“  TV μ•±**을 Vizio, LG webOS, Fire TV, Samsung Tizen에 λ°°ν¬ν•˜κ³  μ‹€ν–‰ν•˜λŠ” CLI λ„κ΅¬μž…λ‹ˆλ‹€.
4
+
5
+ 슀마트 TV ν”Œλž«νΌλ§ˆλ‹€ λ‹€λ₯Έ 개발 ν™˜κ²½ ꡬ좕과 μ•± μ‹€ν–‰ 과정을 단일 λͺ…λ Ήμ–΄λ‘œ μžλ™ν™”ν•©λ‹ˆλ‹€.
6
+
7
+ ---
8
+
9
+ ## πŸ—‚ λͺ©μ°¨
10
+
11
+ 1. [μ‹€ν–‰ λͺ¨λ“œ κ°œμš”](#μ‹€ν–‰-λͺ¨λ“œ-κ°œμš”)
12
+ 2. [ctv.config.json](#ctvconfign)
13
+ 3. [ν”Œλž«νΌλ³„ 사전 μ€€λΉ„](#ν”Œλž«νΌλ³„-사전-μ€€λΉ„)
14
+ 4. [ν”Œλž«νΌλ³„ μ‚¬μš©λ²•](#ν”Œλž«νΌλ³„-μ‚¬μš©λ²•)
15
+ 5. [μ£Όμ˜μ‚¬ν•­](#μ£Όμ˜μ‚¬ν•­)
16
+
17
+ ---
18
+
19
+ ## μ‹€ν–‰ λͺ¨λ“œ κ°œμš”
20
+
21
+ λͺ¨λ“  ν”Œλž«νΌμ€ 두 κ°€μ§€ λͺ¨λ“œλ₯Ό μ§€μ›ν•©λ‹ˆλ‹€.
22
+
23
+ | λͺ¨λ“œ | μ˜΅μ…˜ | μ„€λͺ… |
24
+ |------|------|------|
25
+ | **Live Mode** | (κΈ°λ³Έκ°’) | 개발 μ„œλ²„ URL을 TV에 μ£Όμž…. HMR 지원 |
26
+ | **Static Mode** | `--target=dist` | λΉŒλ“œ 결과물을 TV에 직접 μ„€μΉ˜ |
27
+
28
+ > **Vizio**λŠ” ꡬ쑰상 항상 URL이 ν•„μš”ν•˜λ―€λ‘œ Live Mode만 μ§€μ›ν•©λ‹ˆλ‹€.
29
+
30
+ ---
31
+
32
+ ## ctv.config.json
33
+
34
+ ν”„λ‘œμ νŠΈ λ£¨νŠΈμ— μƒμ„±ν•˜λ©΄ CLI μ˜΅μ…˜μ„ μƒλž΅ν•  수 μžˆμŠ΅λ‹ˆλ‹€. CLI μ˜΅μ…˜μ΄ 항상 μš°μ„ ν•©λ‹ˆλ‹€.
35
+
36
+ ```json
37
+ {
38
+ "vizio": {
39
+ "ip": "192.168.11.27",
40
+ "url": "http://192.168.11.10:3300"
41
+ },
42
+ "fire": {
43
+ "ip": "192.168.11.20",
44
+ "package": "com.example.tvapp",
45
+ "url": "http://192.168.11.10:3300",
46
+ "adbPath": "/usr/local/bin/adb",
47
+ "androidAssetsPath": "/path/to/android/app/src/main/assets"
48
+ },
49
+ "tizen": {
50
+ "ip": "192.168.11.30",
51
+ "appId": "A123456789.MyApp"
52
+ },
53
+ "webos": {
54
+ "deviceName": "my-lg-tv",
55
+ "appId": "com.example.tvapp"
56
+ },
57
+ "common": {
58
+ "devServerPort": 3300
59
+ }
60
+ }
61
+ ```
62
+
63
+ ### Fields
64
+
65
+ **Vizio**
66
+
67
+ | Field | ν•„μˆ˜ | Description |
68
+ |-------|------|-------------|
69
+ | `ip` | βœ… | Vizio TV 둜컬 IP |
70
+ | `url` | | TVμ—μ„œ λ‘œλ“œν•  개발 μ„œλ²„ URL |
71
+
72
+ **Fire TV**
73
+
74
+ | Field | ν•„μˆ˜ | Description |
75
+ |-------|------|-------------|
76
+ | `ip` | βœ… | Fire TV 둜컬 IP |
77
+ | `package` | βœ… | Android μ•± νŒ¨ν‚€μ§€λͺ… |
78
+ | `url` | | Live Modeμ—μ„œ μ£Όμž…ν•  개발 μ„œλ²„ URL |
79
+ | `adbPath` | | adb μ‹€ν–‰ 경둜 (κΈ°λ³Έκ°’: `adb`) |
80
+ | `androidAssetsPath` | Static Mode μ‹œ βœ… | dist 파일이 볡사될 Android `assets` 폴더 μ ˆλŒ€ 경둜 |
81
+
82
+ **Tizen**
83
+
84
+ | Field | ν•„μˆ˜ | Description |
85
+ |-------|------|-------------|
86
+ | `ip` | βœ… | Samsung Tizen TV 둜컬 IP |
87
+ | `appId` | | Tizen μ•± 고유 ID (μ—†μœΌλ©΄ μžλ™ 생성) |
88
+
89
+ **WebOS**
90
+
91
+ | Field | ν•„μˆ˜ | Description |
92
+ |-------|------|-------------|
93
+ | `deviceName` | βœ… | ares-cli에 λ“±λ‘λœ κΈ°κΈ° 이름 |
94
+ | `appId` | | webOS μ•± ID (κΈ°λ³Έκ°’: `com.ctvrun.app`) |
95
+
96
+ **Common**
97
+
98
+ | Field | Description |
99
+ |-------|-------------|
100
+ | `devServerPort` | Live Modeμ—μ„œ μ‚¬μš©ν•  개발 μ„œλ²„ 포트 (κΈ°λ³Έκ°’: `3000`) |
101
+
102
+ > πŸ’‘ TV IPκ°€ κ°œλ°œμžλ§ˆλ‹€ λ‹€λ₯Ό 수 μžˆμœΌλ―€λ‘œ `ctv.config.json`은 `.gitignore`에 μΆ”κ°€ν•˜κ³ , `ctv.config.sample.json`을 μ €μž₯μ†Œμ— κ³΅μœ ν•˜λŠ” 것을 ꢌμž₯ν•©λ‹ˆλ‹€.
103
+
104
+ ---
105
+
106
+ ## ν”Œλž«νΌλ³„ 사전 μ€€λΉ„
107
+
108
+ ### Vizio
109
+
110
+ Playwright Chromium이 ν•„μš”ν•©λ‹ˆλ‹€.
111
+
112
+ ```bash
113
+ npx playwright install chromium
114
+ ```
115
+
116
+ ### Fire TV
117
+
118
+ ADBκ°€ μ„€μΉ˜λ˜μ–΄ μžˆμ–΄μ•Ό ν•˜λ©°, TVμ—μ„œ **개발자 μ˜΅μ…˜ β†’ ADB 디버깅**이 ν™œμ„±ν™”λ˜μ–΄ μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€.
119
+
120
+ ```bash
121
+ # μ—°κ²° 확인
122
+ adb devices
123
+ ```
124
+
125
+ Static Mode(`--target=dist`) μ‚¬μš© μ‹œ μ•± μ½”λ“œμ— λ‹€μŒ 섀정이 ν•„μš”ν•©λ‹ˆλ‹€.
126
+
127
+ **AndroidManifest.xml**
128
+
129
+ ```xml
130
+ <uses-permission android:name="android.permission.INTERNET" />
131
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
132
+
133
+ <application android:usesCleartextTraffic="true" ...>
134
+ ```
135
+
136
+ **MainActivity.kt** β€” ADB둜 μ£Όμž…λœ URL 처리
137
+
138
+ ```kotlin
139
+ val intentUrl = intent.getStringExtra("TARGET_URL")
140
+
141
+ if (!intentUrl.isNullOrEmpty()) {
142
+ webView.loadUrl(intentUrl) // Live Mode
143
+ } else {
144
+ webView.loadUrl("file:///android_asset/index.html") // Static Mode
145
+ }
146
+ ```
147
+
148
+ ### Tizen
149
+
150
+ [Tizen Studio](https://developer.tizen.org/development/tizen-studio/download) μ„€μΉ˜ ν›„ PATHλ₯Ό μ„€μ •ν•©λ‹ˆλ‹€.
151
+
152
+ ```bash
153
+ # ~/.zshrc λ˜λŠ” ~/.bash_profile
154
+ export TIZEN_STUDIO_HOME="$HOME/tizen-studio"
155
+ export PATH="$PATH:$TIZEN_STUDIO_HOME/tools/ide/bin:$TIZEN_STUDIO_HOME/tools"
156
+ ```
157
+
158
+ ```bash
159
+ source ~/.zshrc
160
+
161
+ # μ„€μΉ˜ 확인
162
+ tizen version
163
+ ```
164
+
165
+ μΈμ¦μ„œ ν”„λ‘œν•„μ΄ μ—†μœΌλ©΄ 첫 μ‹€ν–‰ μ‹œ μžλ™μœΌλ‘œ μƒμ„±ν•©λ‹ˆλ‹€.
166
+
167
+ ### WebOS
168
+
169
+ [LG webOS SDK](https://webostv.developer.lge.com) μ„€μΉ˜ ν›„ κΈ°κΈ°λ₯Ό λ“±λ‘ν•©λ‹ˆλ‹€.
170
+
171
+ ```bash
172
+ # κΈ°κΈ° 등둝 (졜초 1회)
173
+ ares-setup-device
174
+
175
+ # 등둝 확인
176
+ ares-setup-device --list
177
+ ```
178
+
179
+ ---
180
+
181
+ ## ν”Œλž«νΌλ³„ μ‚¬μš©λ²• μ˜ˆμ‹œ
182
+
183
+ ### Vizio
184
+
185
+ ```bash
186
+ # URL 직접 μ§€μ •
187
+ ctv-run vizio --ip=192.168.11.27 --url=http://192.168.11.10:3300
188
+
189
+ # 포트만 μ§€μ • (둜컬 IP μžλ™ 감지)
190
+ ctv-run vizio --ip=192.168.11.27 --port=3300
191
+ ```
192
+
193
+ **μž‘λ™ 원리**
194
+
195
+ Playwright둜 Vizio μ›Ή 런처λ₯Ό μžλ™ μ‘°μž‘ν•˜μ—¬ νŽ˜μ–΄λ§(PIN μž…λ ₯) ν›„ μ•± URL을 μ£Όμž…ν•©λ‹ˆλ‹€.
196
+
197
+ ---
198
+
199
+ ### Fire TV
200
+
201
+ ```bash
202
+ # Live Mode β€” 개발 μ„œλ²„ URL μ£Όμž…
203
+ ctv-run fire --url=http://192.168.11.10:3300
204
+
205
+ # Live Mode β€” 포트만 μ§€μ •
206
+ ctv-run fire --port=3300
207
+
208
+ # Static Mode β€” distλ₯Ό Android assets에 볡사 ν›„ 둜컬 μ‹€ν–‰
209
+ ctv-run fire --target=dist
210
+ ```
211
+
212
+ > Static ModeλŠ” `androidAssetsPath` 섀정이 ν•„μˆ˜μž…λ‹ˆλ‹€.
213
+
214
+ **μž‘λ™ 원리**
215
+
216
+ - **Live Mode**: ADB둜 앱을 μ‹€ν–‰ν•˜λ©° `TARGET_URL` extraλ₯Ό μ£Όμž…ν•©λ‹ˆλ‹€.
217
+ - **Static Mode**: dist νŒŒμΌμ„ Android `assets` 폴더에 λ³΅μ‚¬ν•œ λ’€ 앱을 μ‹€ν–‰ν•©λ‹ˆλ‹€. 앱은 `file:///android_asset/index.html`을 λ‘œλ“œν•©λ‹ˆλ‹€.
218
+
219
+ ---
220
+
221
+ ### Tizen
222
+
223
+ ```bash
224
+ # Live Mode β€” 개발 μ„œλ²„λ‘œ λ¦¬λ‹€μ΄λ ‰νŠΈν•˜λŠ” μ•± μ„€μΉ˜ ν›„ μ‹€ν–‰
225
+ ctv-run tizen --ip=192.168.11.30
226
+
227
+ # Static Mode β€” distλ₯Ό .wgt둜 νŒ¨ν‚€μ§•ν•˜μ—¬ TV에 μ„€μΉ˜
228
+ ctv-run tizen --ip=192.168.11.30 --target=dist
229
+ ```
230
+
231
+ **μž‘λ™ 원리**
232
+
233
+ - **Live Mode**: 개발 μ„œλ²„λ‘œ λ¦¬λ‹€μ΄λ ‰νŠΈν•˜λŠ” `index.html`을 ν¬ν•¨ν•œ `.wgt`λ₯Ό μ„€μΉ˜ν•©λ‹ˆλ‹€.
234
+ - **Static Mode**: dist 폴더λ₯Ό `.wgt`둜 νŒ¨ν‚€μ§•ν•˜μ—¬ TV에 μ„€μΉ˜ν•©λ‹ˆλ‹€.
235
+
236
+ `config.xml`이 μ—†μœΌλ©΄ μžλ™ μƒμ„±λ©λ‹ˆλ‹€. μ»€μŠ€ν„°λ§ˆμ΄μ§•μ΄ ν•„μš”ν•˜λ©΄ ν”„λ‘œμ νŠΈ λ£¨νŠΈμ— `config.xml`을 μΆ”κ°€ν•˜μ„Έμš”.
237
+
238
+ 디버깅은 Chromeμ—μ„œ `http://[TV_IP]:9222`둜 μ ‘μ†ν•©λ‹ˆλ‹€. (TV μ„€μ •μ—μ„œ 디버그 λͺ¨λ“œ ν™œμ„±ν™” ν•„μš”)
239
+
240
+ ---
241
+
242
+ ### WebOS
243
+
244
+ ```bash
245
+ # Live Mode β€” 개발 μ„œλ²„λ‘œ λ¦¬λ‹€μ΄λ ‰νŠΈν•˜λŠ” μ•± μ„€μΉ˜ ν›„ μ‹€ν–‰
246
+ ctv-run webos --device=my-lg-tv
247
+
248
+ # Static Mode β€” distλ₯Ό .ipk둜 νŒ¨ν‚€μ§•ν•˜μ—¬ TV에 μ„€μΉ˜
249
+ ctv-run webos --device=my-lg-tv --target=dist
250
+ ```
251
+
252
+ **μž‘λ™ 원리**
253
+
254
+ - **Live Mode**: 개발 μ„œλ²„λ‘œ λ¦¬λ‹€μ΄λ ‰νŠΈν•˜λŠ” `index.html`을 ν¬ν•¨ν•œ `.ipk`λ₯Ό μ„€μΉ˜ν•©λ‹ˆλ‹€.
255
+ - **Static Mode**: dist 폴더λ₯Ό `.ipk`둜 νŒ¨ν‚€μ§•ν•˜μ—¬ TV에 μ„€μΉ˜ν•©λ‹ˆλ‹€.
256
+
257
+ `appinfo.json`이 μ—†μœΌλ©΄ μžλ™ μƒμ„±λ©λ‹ˆλ‹€.
258
+
259
+ ---
260
+
261
+ ## μ£Όμ˜μ‚¬ν•­
262
+
263
+ ### λ„€νŠΈμ›Œν¬
264
+
265
+ 개발 PC와 TVλŠ” **λ™μΌν•œ Wi-Fi λ˜λŠ” μœ μ„  λ„€νŠΈμ›Œν¬**에 μ—°κ²°λ˜μ–΄ μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€.
266
+
267
+ ### Live Mode λ‘œλ”© 속도
268
+
269
+ Vite 개발 μ„œλ²„λŠ” HMR을 μœ„ν•΄ λͺ¨λ“ˆμ„ κ°œλ³„ μ „μ†‘ν•˜λ―€λ‘œ TV 사양에 따라 **초기 λ‘œλ”©μ— 10~30초 이상** 걸릴 수 μžˆμŠ΅λ‹ˆλ‹€. μ„±λŠ₯ 확인이 ν•„μš”ν•˜λ‹€λ©΄ Static Modeλ₯Ό μ‚¬μš©ν•˜μ„Έμš”.
270
+
271
+ ### Tizen 포트 κΆŒν•œ
272
+
273
+ `ctv-run tizen` μ‹€ν–‰ μ‹œ κΆŒν•œ 였λ₯˜κ°€ λ°œμƒν•˜λ©΄ `sudo`λ₯Ό λΆ™μ—¬ μ‹€ν–‰ν•˜μ„Έμš”.
274
+
275
+ ```bash
276
+ sudo ctv-run tizen --ip=192.168.11.30
277
+ ```
package/dist/config.js ADDED
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CONFIG = void 0;
7
+ const dotenv_1 = __importDefault(require("dotenv"));
8
+ dotenv_1.default.config();
9
+ // export function getTargetIp(platform: string) {
10
+ // const configPath = path.join(process.cwd(), 'ctv.config.json');
11
+ //
12
+ // // 1. μ„€μ • 파일이 있으면 μ½μ–΄μ˜€κΈ°
13
+ // if (fs.existsSync(configPath)) {
14
+ // const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
15
+ // if (config.platforms?.[platform]?.ip) {
16
+ // return config.platforms[platform].ip;
17
+ // }
18
+ // }
19
+ //
20
+ // // 2. μ„€μ • 파일이 μ—†μœΌλ©΄ ν™˜κ²½λ³€μˆ˜λ‚˜ κΈ°λ³Έκ°’ μ‚¬μš© (ν…ŒμŠ€νŠΈμš©)
21
+ // return process.env.VIZIO_TV_IP || '0.0.0.0';
22
+ // }
23
+ exports.CONFIG = {
24
+ DIST_PATH: './dist',
25
+ PORT: 3000,
26
+ // .env νŒŒμΌμ— TV_IP=192.168.x.x ν˜•νƒœλ‘œ μ €μž₯ν•˜μ„Έμš”
27
+ VIZIO_TV_IP: process.env.VIZIO_TV_IP || '0.0.0.0',
28
+ LAUNCHER_URL: 'https://vizio-pm.s3-us-west-1.amazonaws.com/conjure-launcher.html',
29
+ };
package/dist/firetv.js ADDED
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.runFireTv = runFireTv;
13
+ const child_process_1 = require("child_process");
14
+ const util_1 = require("./util");
15
+ function runFireTv() {
16
+ return __awaiter(this, void 0, void 0, function* () {
17
+ var _a, _b, _c;
18
+ const args = process.argv.slice(2);
19
+ const config = (0, util_1.loadConfig)();
20
+ const getArg = (name) => { var _a; return (_a = args.find(arg => arg.startsWith(`--${name}=`))) === null || _a === void 0 ? void 0 : _a.split('=')[1]; };
21
+ const tvIp = getArg('ip') || ((_a = config.firetv) === null || _a === void 0 ? void 0 : _a.ip);
22
+ const pkg = getArg('package') || ((_b = config.firetv) === null || _b === void 0 ? void 0 : _b.package);
23
+ const targetArg = getArg('target'); // 'dist' 등이 λ“€μ–΄μ˜€λ©΄ λ²ˆλ“€ λͺ¨λ“œλ‘œ κ°„μ£Ό
24
+ const activity = getArg('activity') || ((_c = config.firetv) === null || _c === void 0 ? void 0 : _c.activity) || '.MainActivity';
25
+ const appUrl = getArg('url') || `http://${(0, util_1.getLocalIp)()}:5173`;
26
+ if (!tvIp || !pkg) {
27
+ console.error(`\n❌ Fire TV ν•„μˆ˜ μ„€μ •(ip, package)이 λˆ„λ½λ˜μ—ˆμŠ΅λ‹ˆλ‹€!`);
28
+ return;
29
+ }
30
+ console.log(`-----------------------------------------`);
31
+ console.log(`πŸ“‘ Target TV IP : ${tvIp}`);
32
+ console.log(`πŸ“¦ Package Name : ${pkg}`);
33
+ if (targetArg) {
34
+ console.log(`πŸ“¦ Mode : 🏠 Bundle Mode (Local App)`);
35
+ }
36
+ else {
37
+ console.log(`πŸ“¦ Mode : 🌐 Live Mode (Remote URL)`);
38
+ console.log(`πŸš€ App URL : ${appUrl}`);
39
+ }
40
+ console.log(`-----------------------------------------`);
41
+ try {
42
+ // 1. ADB μ—°κ²°
43
+ console.log(`πŸ”— ADB Connecting to ${tvIp}...`);
44
+ (0, child_process_1.execSync)(`adb connect ${tvIp}:5555`, { stdio: 'pipe' });
45
+ // 2. κΈ°μ‘΄ μ•± μ’…λ£Œ
46
+ console.log('πŸ”„ Force stopping existing process...');
47
+ (0, child_process_1.execSync)(`adb -s ${tvIp}:5555 shell am force-stop ${pkg}`);
48
+ // 3. μ‹€ν–‰ μ»€λ§¨λ“œ 쑰립
49
+ const fullActivityPath = activity.startsWith('.') ? `${pkg}/${activity}` : `${pkg}/${pkg}${activity}`;
50
+ let launchCmd = `adb -s ${tvIp}:5555 shell am start -n ${fullActivityPath}`;
51
+ /**
52
+ * [핡심 둜직]
53
+ * target μΈμžκ°€ μ—†μœΌλ©΄(Live λͺ¨λ“œ), TARGET_URL μ—λ„ˆν…Œμ΄μ…˜μ„ μΆ”κ°€ν•΄μ„œ λ³΄λƒ…λ‹ˆλ‹€.
54
+ * target μΈμžκ°€ 있으면(Bundle λͺ¨λ“œ), κ·Έλƒ₯ μ•±λ§Œ μ‹€ν–‰ν•©λ‹ˆλ‹€.
55
+ */
56
+ if (!targetArg) {
57
+ launchCmd += ` --es "TARGET_URL" "${appUrl}"`;
58
+ }
59
+ console.log('πŸ“± Launching App...');
60
+ (0, child_process_1.execSync)(launchCmd, { stdio: 'inherit' });
61
+ console.log(`\nβœ… Success! Fire TV launch complete.`);
62
+ }
63
+ catch (err) {
64
+ console.error(`\n❌ Error: ${err.message}`);
65
+ }
66
+ });
67
+ }
package/dist/index.js ADDED
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
4
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
5
+ return new (P || (P = Promise))(function (resolve, reject) {
6
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
7
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
8
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
9
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
10
+ });
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ const firetv_1 = require("./platforms/firetv");
14
+ const tizen_1 = require("./platforms/tizen");
15
+ const vizio_1 = require("./platforms/vizio");
16
+ const webos_1 = require("./platforms/webos");
17
+ function main() {
18
+ return __awaiter(this, void 0, void 0, function* () {
19
+ const args = process.argv.slice(2);
20
+ const platform = args[0]; // ctv-run [vizio | webos | firetv | tizen]
21
+ if (!platform || platform === '--help' || platform === '-h') {
22
+ console.log('\nπŸ“Ί CTV-RUN: Smart TV Deployment Tool');
23
+ console.log('-----------------------------------------');
24
+ console.log('μ‚¬μš©λ²•: ctv-run [platform] [options]');
25
+ console.log('\n지원 ν”Œλž«νΌ:');
26
+ console.log(' vizio - Vizio SmartCast');
27
+ console.log(' webos - LG webOS TV');
28
+ console.log(' fire - Amazon Fire TV / Android TV');
29
+ console.log(' tizen - Samsung Tizen TV');
30
+ console.log('\nμ˜ˆμ‹œ:');
31
+ console.log(' ctv-run webos --device=lg-tv');
32
+ console.log(' ctv-run tizen --ip=192.168.0.10');
33
+ console.log('-----------------------------------------\n');
34
+ return;
35
+ }
36
+ try {
37
+ switch (platform.toLowerCase()) {
38
+ case 'vizio':
39
+ yield (0, vizio_1.runVizio)();
40
+ break;
41
+ case 'webos':
42
+ yield (0, webos_1.runWebOS)();
43
+ break;
44
+ case 'fire':
45
+ yield (0, firetv_1.runFireTv)();
46
+ break;
47
+ case 'tizen':
48
+ yield (0, tizen_1.runTizen)();
49
+ break;
50
+ default:
51
+ console.error(`❌ μ§€μ›ν•˜μ§€ μ•ŠλŠ” ν”Œλž«νΌμž…λ‹ˆλ‹€: ${platform}`);
52
+ process.exit(1);
53
+ }
54
+ }
55
+ catch (err) {
56
+ console.error(`\n❌ μ‹€ν–‰ 쀑 였λ₯˜ λ°œμƒ: ${err.message}`);
57
+ process.exit(1);
58
+ }
59
+ });
60
+ }
61
+ main();
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.runFireTv = runFireTv;
16
+ const child_process_1 = require("child_process");
17
+ const fs_1 = __importDefault(require("fs"));
18
+ const path_1 = __importDefault(require("path"));
19
+ const config_1 = require("../utils/config");
20
+ const ip_1 = require("../utils/ip");
21
+ const platform_1 = require("../utils/platform");
22
+ function runFireTv() {
23
+ return __awaiter(this, void 0, void 0, function* () {
24
+ var _a, _b, _c, _d, _e, _f, _g, _h;
25
+ const args = process.argv.slice(2);
26
+ const config = (0, config_1.loadConfig)();
27
+ const getArg = (0, platform_1.makeArgParser)(args);
28
+ // μ„€μ • 및 인자 νŒŒμ‹±
29
+ const adbBin = getArg('adbPath') || ((_a = config.fire) === null || _a === void 0 ? void 0 : _a.adbPath) || 'adb';
30
+ const tvIp = getArg('ip') || ((_b = config.fire) === null || _b === void 0 ? void 0 : _b.ip);
31
+ const pkg = getArg('package') || ((_c = config.fire) === null || _c === void 0 ? void 0 : _c.package);
32
+ const activity = getArg('activity') || ((_d = config.fire) === null || _d === void 0 ? void 0 : _d.activity) || '.MainActivity';
33
+ const targetArg = getArg('target');
34
+ const androidAssetsPath = getArg('assetsPath') || ((_e = config.fire) === null || _e === void 0 ? void 0 : _e.androidAssetsPath);
35
+ // μ•± URL κ²°μ • (Live Mode μ „μš©)
36
+ const userUrl = getArg('url') || ((_f = config.fire) === null || _f === void 0 ? void 0 : _f.url);
37
+ const port = getArg('port') || ((_g = config.fire) === null || _g === void 0 ? void 0 : _g.port) || ((_h = config.common) === null || _h === void 0 ? void 0 : _h.devServerPort) || 3000;
38
+ const appUrl = userUrl || `http://${(0, ip_1.getLocalIp)()}:${port}`;
39
+ if (!tvIp || !pkg) {
40
+ console.error(`\n❌ Fire TV ν•„μˆ˜ μ„€μ •(ip, package)이 λˆ„λ½λ˜μ—ˆμŠ΅λ‹ˆλ‹€!`);
41
+ return;
42
+ }
43
+ // Static Mode: distλ₯Ό Android assets에 볡사 (Tizen/WebOS와 λ™μΌν•œ κ°œλ…)
44
+ if (targetArg) {
45
+ if (!androidAssetsPath) {
46
+ console.error(`\n❌ ctv.config.json에 'androidAssetsPath'κ°€ μ„€μ •λ˜μ–΄ μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€.`);
47
+ return;
48
+ }
49
+ const distPath = path_1.default.resolve(process.cwd(), targetArg);
50
+ const finalAssetsPath = path_1.default.isAbsolute(androidAssetsPath)
51
+ ? androidAssetsPath
52
+ : path_1.default.resolve(process.cwd(), androidAssetsPath);
53
+ console.log(`-----------------------------------------`);
54
+ console.log(`πŸ“‘ Target TV IP : ${tvIp}`);
55
+ console.log(`πŸ“¦ Package Name : ${pkg}`);
56
+ console.log(`πŸ›  ADB Binary : ${adbBin}`);
57
+ console.log(`πŸ“¦ Mode : πŸ“‚ Static Mode (Assets Embedded)`);
58
+ console.log(`πŸ“‚ Path : ${finalAssetsPath}`);
59
+ console.log(`-----------------------------------------`);
60
+ try {
61
+ const parentPath = path_1.default.dirname(finalAssetsPath);
62
+ if (!fs_1.default.existsSync(parentPath)) {
63
+ console.error(`\n❌ 경둜 였λ₯˜: λΆ€λͺ¨ 폴더가 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. (${parentPath})`);
64
+ return;
65
+ }
66
+ if (fs_1.default.existsSync(finalAssetsPath)) {
67
+ (0, child_process_1.execSync)(`rm -rf "${finalAssetsPath}"/*`);
68
+ }
69
+ else {
70
+ (0, child_process_1.execSync)(`mkdir -p "${finalAssetsPath}"`);
71
+ }
72
+ (0, child_process_1.execSync)(`cp -r "${distPath}"/* "${finalAssetsPath}"`);
73
+ console.log(`βœ… Asset copy complete.`);
74
+ }
75
+ catch (err) {
76
+ console.error(`\n❌ 파일 볡사 쀑 μ—λŸ¬ λ°œμƒ: ${err.message}`);
77
+ return;
78
+ }
79
+ }
80
+ else {
81
+ // Live Mode
82
+ console.log(`-----------------------------------------`);
83
+ console.log(`πŸ“‘ Target TV IP : ${tvIp}`);
84
+ console.log(`πŸ“¦ Package Name : ${pkg}`);
85
+ console.log(`πŸ›  ADB Binary : ${adbBin}`);
86
+ console.log(`πŸ“¦ Mode : 🌐 Live Mode (Remote URL)`);
87
+ console.log(`πŸš€ App URL : ${appUrl}`);
88
+ console.log(`-----------------------------------------`);
89
+ }
90
+ try {
91
+ // ADB μ—°κ²°
92
+ console.log(`πŸ”— ADB Connecting to ${tvIp}...`);
93
+ (0, child_process_1.execSync)(`${adbBin} connect ${tvIp}:5555`, { stdio: 'pipe' });
94
+ // κΈ°μ‘΄ μ•± μ’…λ£Œ
95
+ console.log('πŸ”„ Force stopping existing process...');
96
+ (0, child_process_1.execSync)(`${adbBin} -s ${tvIp}:5555 shell am force-stop ${pkg}`);
97
+ // μ‹€ν–‰ μ»€λ§¨λ“œ μž‘μ„±
98
+ const fullActivityPath = activity.startsWith('.')
99
+ ? `${pkg}/${activity}`
100
+ : activity;
101
+ let launchCmd = `${adbBin} -s ${tvIp}:5555 shell am start -n ${fullActivityPath}`;
102
+ // Live Mode일 λ•Œλ§Œ URL μ£Όμž…
103
+ if (!targetArg) {
104
+ launchCmd += ` --es "TARGET_URL" "${appUrl}"`;
105
+ }
106
+ console.log('πŸ“± Launching App...');
107
+ (0, child_process_1.execSync)(launchCmd, { stdio: 'inherit' });
108
+ console.log(`\nβœ… Success! Fire TV launch complete.`);
109
+ }
110
+ catch (err) {
111
+ if (err.message.includes('ENOENT')) {
112
+ console.error(`\n❌ Error: '${adbBin}' λͺ…λ Ήμ–΄λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.`);
113
+ }
114
+ else {
115
+ console.error(`\n❌ Error: ${err.message}`);
116
+ }
117
+ }
118
+ });
119
+ }