@zsukim/ctv-run 1.0.5 → 1.0.10
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 +19 -11
- package/dist/index.js +0 -0
- package/dist/platforms/tizen.js +17 -15
- package/package.json +7 -3
- package/dist/config.js +0 -29
- package/dist/firetv.js +0 -67
- package/dist/tizen.js +0 -94
- package/dist/util.js +0 -35
- package/dist/vizio.js +0 -161
- package/dist/webos.js +0 -87
package/README.md
CHANGED
|
@@ -86,6 +86,8 @@
|
|
|
86
86
|
| `url` | | TV가 로드할 URL |
|
|
87
87
|
| `port` | | TV가 로드할 URL의 포트 |
|
|
88
88
|
| `appId` | | Tizen 앱 고유 ID (없으면 자동 생성) |
|
|
89
|
+
| `tizenPath` | | tizen CLI 실행 경로 (기본값: `tizen`) |
|
|
90
|
+
| `sdbPath` | | sdb 실행 경로 (기본값: `sdb`) |
|
|
89
91
|
|
|
90
92
|
**WebOS**
|
|
91
93
|
|
|
@@ -153,7 +155,11 @@ if (!intentUrl.isNullOrEmpty()) {
|
|
|
153
155
|
|
|
154
156
|
### Tizen
|
|
155
157
|
|
|
156
|
-
[Tizen Studio](https://
|
|
158
|
+
[Tizen Studio](https://docs.tizen.org/application/tizen-studio/setup/install-sdk/) 설치 후 PATH를 설정하거나, `ctv.config.json`에 경로를 직접 지정합니다.
|
|
159
|
+
|
|
160
|
+
> `tizen` CLI와 `sdb`는 Tizen Studio에 포함되어 있습니다. 별도 설치는 필요하지 않습니다.
|
|
161
|
+
|
|
162
|
+
**방법 1 — PATH 설정**
|
|
157
163
|
|
|
158
164
|
```bash
|
|
159
165
|
# ~/.zshrc 또는 ~/.bash_profile
|
|
@@ -163,24 +169,26 @@ export PATH="$PATH:$TIZEN_STUDIO_HOME/tools/ide/bin:$TIZEN_STUDIO_HOME/tools"
|
|
|
163
169
|
|
|
164
170
|
```bash
|
|
165
171
|
source ~/.zshrc
|
|
172
|
+
```
|
|
166
173
|
|
|
167
|
-
|
|
168
|
-
|
|
174
|
+
**방법 2 — ctv.config.json에 경로 지정**
|
|
175
|
+
|
|
176
|
+
```json
|
|
177
|
+
{
|
|
178
|
+
"tizen": {
|
|
179
|
+
"tizenPath": "/home/user/tizen-studio/tools/ide/bin/tizen",
|
|
180
|
+
"sdbPath": "/home/user/tizen-studio/tools/sdb"
|
|
181
|
+
}
|
|
182
|
+
}
|
|
169
183
|
```
|
|
170
184
|
|
|
171
185
|
인증서 프로필이 없으면 첫 실행 시 자동으로 생성합니다.
|
|
172
186
|
|
|
173
187
|
### WebOS
|
|
174
188
|
|
|
175
|
-
[LG webOS
|
|
189
|
+
[LG webOS CLI](https://webostv.developer.lge.com/develop/tools/cli-installation) 설치 후 `ares` 명령어를 사용할 수 있습니다.
|
|
176
190
|
|
|
177
|
-
|
|
178
|
-
# 기기 등록 (최초 1회)
|
|
179
|
-
ares-setup-device
|
|
180
|
-
|
|
181
|
-
# 등록 확인
|
|
182
|
-
ares-setup-device --list
|
|
183
|
-
```
|
|
191
|
+
기기 등록은 `ctv-run webos` 첫 실행 시 자동으로 안내됩니다. 등록된 기기가 없으면 `ares-setup-device`를 대신 실행해줍니다.
|
|
184
192
|
|
|
185
193
|
---
|
|
186
194
|
|
package/dist/index.js
CHANGED
|
File without changes
|
package/dist/platforms/tizen.js
CHANGED
|
@@ -21,7 +21,7 @@ const ip_1 = require("../utils/ip");
|
|
|
21
21
|
const platform_1 = require("../utils/platform");
|
|
22
22
|
function runTizen() {
|
|
23
23
|
return __awaiter(this, void 0, void 0, function* () {
|
|
24
|
-
var _a, _b, _c, _d, _e;
|
|
24
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
25
25
|
const config = (0, config_1.loadConfig)();
|
|
26
26
|
const args = process.argv.slice(2);
|
|
27
27
|
const getArg = (0, platform_1.makeArgParser)(args);
|
|
@@ -30,11 +30,13 @@ function runTizen() {
|
|
|
30
30
|
console.error('\n❌ Tizen TV IP 주소가 필요합니다! (예: --ip=192.168.x.x)');
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
|
+
const tizenBin = getArg('tizenPath') || ((_b = config.tizen) === null || _b === void 0 ? void 0 : _b.tizenPath) || 'tizen';
|
|
34
|
+
const sdbBin = getArg('sdbPath') || ((_c = config.tizen) === null || _c === void 0 ? void 0 : _c.sdbPath) || 'sdb';
|
|
33
35
|
const targetArg = getArg('target');
|
|
34
36
|
const isLiveMode = !targetArg;
|
|
35
|
-
const port = getArg('port') || ((
|
|
37
|
+
const port = getArg('port') || ((_d = config.tizen) === null || _d === void 0 ? void 0 : _d.port) || ((_e = config.common) === null || _e === void 0 ? void 0 : _e.devServerPort) || 3000;
|
|
36
38
|
const appUrl = getArg('url') ||
|
|
37
|
-
((
|
|
39
|
+
((_f = config.tizen) === null || _f === void 0 ? void 0 : _f.url) ||
|
|
38
40
|
`http://${(0, ip_1.getLocalIp)()}:${port}`;
|
|
39
41
|
const tempAppDir = path_1.default.resolve(process.cwd(), '.ctv-run-tizen');
|
|
40
42
|
if (fs_extra_1.default.existsSync(tempAppDir))
|
|
@@ -68,7 +70,7 @@ function runTizen() {
|
|
|
68
70
|
xmlInfo = getInfoFromXml(process.cwd());
|
|
69
71
|
const appId = getArg('appId') ||
|
|
70
72
|
(xmlInfo === null || xmlInfo === void 0 ? void 0 : xmlInfo.appId) ||
|
|
71
|
-
((
|
|
73
|
+
((_g = config.tizen) === null || _g === void 0 ? void 0 : _g.appId) ||
|
|
72
74
|
'A123456789.CtvRunApp';
|
|
73
75
|
const safeAppName = ((xmlInfo === null || xmlInfo === void 0 ? void 0 : xmlInfo.appName) || 'CtvRunApp').replace(/\s+/g, '');
|
|
74
76
|
// config.xml 없으면 생성
|
|
@@ -77,7 +79,7 @@ function runTizen() {
|
|
|
77
79
|
console.log('\n🎫 Tizen 인증서 프로필 확인 중...');
|
|
78
80
|
const getActive = () => {
|
|
79
81
|
try {
|
|
80
|
-
const list = (0, child_process_1.execSync)(
|
|
82
|
+
const list = (0, child_process_1.execSync)(`${tizenBin} security-profiles list`, {
|
|
81
83
|
encoding: 'utf8',
|
|
82
84
|
});
|
|
83
85
|
const match = list.match(/Active Profile\s*:\s*(\S+)/);
|
|
@@ -108,23 +110,23 @@ function runTizen() {
|
|
|
108
110
|
}
|
|
109
111
|
else {
|
|
110
112
|
p12Pwd = askQuestion('🔑 새로 만들 인증서 비밀번호(4자 이상): ');
|
|
111
|
-
(0, child_process_1.execSync)(
|
|
113
|
+
(0, child_process_1.execSync)(`${tizenBin} certificate -a "${certFileName}" -p "${p12Pwd}" -f "${certFileName}"`, { stdio: 'inherit' });
|
|
112
114
|
}
|
|
113
115
|
try {
|
|
114
|
-
(0, child_process_1.execSync)(
|
|
116
|
+
(0, child_process_1.execSync)(`${tizenBin} security-profiles remove -n "${profileName}"`, {
|
|
115
117
|
stdio: 'ignore',
|
|
116
118
|
});
|
|
117
119
|
}
|
|
118
120
|
catch (e) { }
|
|
119
|
-
(0, child_process_1.execSync)(
|
|
120
|
-
(0, child_process_1.execSync)(
|
|
121
|
+
(0, child_process_1.execSync)(`${tizenBin} security-profiles add -n "${profileName}" -a "${p12Path}" -p "${p12Pwd}"`, { stdio: 'inherit' });
|
|
122
|
+
(0, child_process_1.execSync)(`${tizenBin} security-profiles set-active -n "${profileName}"`, {
|
|
121
123
|
stdio: 'inherit',
|
|
122
124
|
});
|
|
123
125
|
activeProfile = getActive();
|
|
124
126
|
}
|
|
125
127
|
// 패키징
|
|
126
128
|
console.log(`\n🔨 [${activeProfile}] 프로필로 패키징 중...`);
|
|
127
|
-
(0, child_process_1.execSync)(
|
|
129
|
+
(0, child_process_1.execSync)(`${tizenBin} package -t wgt -s "${activeProfile}" -o "${tempAppDir}" -- "${tempAppDir}"`, { stdio: 'inherit' });
|
|
128
130
|
const files = fs_extra_1.default.readdirSync(tempAppDir);
|
|
129
131
|
const generatedWgt = files.find((f) => f.endsWith('.wgt'));
|
|
130
132
|
if (!generatedWgt)
|
|
@@ -133,12 +135,12 @@ function runTizen() {
|
|
|
133
135
|
// TV 연결 및 설치
|
|
134
136
|
console.log(`\n📡 TV 연결 시도: ${tvIp}`);
|
|
135
137
|
try {
|
|
136
|
-
(0, child_process_1.execSync)(
|
|
138
|
+
(0, child_process_1.execSync)(`${sdbBin} connect ${tvIp}`, { stdio: 'ignore' });
|
|
137
139
|
}
|
|
138
140
|
catch (e) { }
|
|
139
141
|
console.log(`📦 앱 설치 중: ${generatedWgt}`);
|
|
140
142
|
try {
|
|
141
|
-
(0, child_process_1.execSync)(
|
|
143
|
+
(0, child_process_1.execSync)(`${tizenBin} install -n "${wgtPath}"`, { stdio: 'inherit' });
|
|
142
144
|
}
|
|
143
145
|
catch (err) {
|
|
144
146
|
console.log('\n⚠️ 설치 실패! (인증서 불일치 가능성이 있습니다.)');
|
|
@@ -151,9 +153,9 @@ function runTizen() {
|
|
|
151
153
|
const answer = askQuestion('❓ 기존 앱을 삭제하고 다시 설치할까요? (Y/n): ');
|
|
152
154
|
if (answer === 'Y') {
|
|
153
155
|
console.log(`🗑️ 기존 앱 삭제 중: ${appId}`);
|
|
154
|
-
(0, child_process_1.execSync)(
|
|
156
|
+
(0, child_process_1.execSync)(`${tizenBin} uninstall -p ${appId}`, { stdio: 'inherit' });
|
|
155
157
|
console.log('✅ 삭제 완료. 재설치 시도...');
|
|
156
|
-
(0, child_process_1.execSync)(
|
|
158
|
+
(0, child_process_1.execSync)(`${tizenBin} install -n "${wgtPath}"`, { stdio: 'inherit' });
|
|
157
159
|
}
|
|
158
160
|
else {
|
|
159
161
|
console.log('🚫 설치 중단.');
|
|
@@ -162,7 +164,7 @@ function runTizen() {
|
|
|
162
164
|
}
|
|
163
165
|
// 앱 실행
|
|
164
166
|
console.log(`🚀 앱 실행 중: ${appId}`);
|
|
165
|
-
(0, child_process_1.execSync)(
|
|
167
|
+
(0, child_process_1.execSync)(`${tizenBin} run -p ${appId}`, { stdio: 'inherit' });
|
|
166
168
|
console.log(`\n✅ 모든 과정 완료!`);
|
|
167
169
|
}
|
|
168
170
|
catch (err) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zsukim/ctv-run",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "Smart TV deployment CLI for Vizio, LG webOS, Fire TV, and Samsung Tizen",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ctv",
|
|
@@ -21,13 +21,17 @@
|
|
|
21
21
|
"dist",
|
|
22
22
|
"README.md"
|
|
23
23
|
],
|
|
24
|
+
"packageManager": "pnpm@10.2.0",
|
|
24
25
|
"engines": {
|
|
25
|
-
"node": ">=
|
|
26
|
+
"node": ">=22"
|
|
26
27
|
},
|
|
27
28
|
"scripts": {
|
|
28
29
|
"build": "tsc",
|
|
29
30
|
"watch": "tsc -w",
|
|
30
|
-
"prepublishOnly": "tsc"
|
|
31
|
+
"prepublishOnly": "tsc",
|
|
32
|
+
"release:patch": "npm version patch && git push && git push --tags",
|
|
33
|
+
"release:minor": "npm version minor && git push && git push --tags",
|
|
34
|
+
"release:major": "npm version major && git push && git push --tags"
|
|
31
35
|
},
|
|
32
36
|
"dependencies": {
|
|
33
37
|
"fs-extra": "^11.3.4",
|
package/dist/config.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
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/tizen.js
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
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.runTizen = runTizen;
|
|
16
|
-
const wits_1 = require("@tizentv/wits");
|
|
17
|
-
const util_1 = require("./util");
|
|
18
|
-
const child_process_1 = require("child_process");
|
|
19
|
-
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
20
|
-
const path_1 = __importDefault(require("path"));
|
|
21
|
-
function runTizen() {
|
|
22
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
23
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
24
|
-
const config = (0, util_1.loadConfig)();
|
|
25
|
-
const args = process.argv.slice(2);
|
|
26
|
-
const getArg = (name) => { var _a; return (_a = args.find(arg => arg.startsWith(`--${name}=`))) === null || _a === void 0 ? void 0 : _a.split('=')[1]; };
|
|
27
|
-
const tvIp = getArg('ip') || ((_a = config.tizen) === null || _a === void 0 ? void 0 : _a.ip);
|
|
28
|
-
const appId = getArg('appId') || ((_b = config.tizen) === null || _b === void 0 ? void 0 : _b.appId);
|
|
29
|
-
const profileName = getArg('profile') || ((_c = config.tizen) === null || _c === void 0 ? void 0 : _c.profileName);
|
|
30
|
-
const witsConfigPath = path_1.default.join(process.cwd(), '.witsconfig.json');
|
|
31
|
-
// [수정 포인트 1] 기존 설정을 먼저 읽어와야 함
|
|
32
|
-
let existingWitsConfig = {};
|
|
33
|
-
if (fs_extra_1.default.existsSync(witsConfigPath)) {
|
|
34
|
-
try {
|
|
35
|
-
existingWitsConfig = fs_extra_1.default.readJsonSync(witsConfigPath);
|
|
36
|
-
}
|
|
37
|
-
catch (e) {
|
|
38
|
-
existingWitsConfig = {};
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
// 1. 인증서가 없는 경우 (인자, config, 기존 파일 모두 뒤져도 없을 때)
|
|
42
|
-
const finalProfileName = profileName || ((_d = existingWitsConfig.profileInfo) === null || _d === void 0 ? void 0 : _d.name);
|
|
43
|
-
if (!finalProfileName && !fs_extra_1.default.existsSync(witsConfigPath)) {
|
|
44
|
-
console.log("🎫 Tizen 인증서 설정이 없습니다. 'wits -c'를 실행합니다...");
|
|
45
|
-
try {
|
|
46
|
-
(0, child_process_1.execSync)('npx wits -c', { stdio: 'inherit' });
|
|
47
|
-
console.log("\n✅ 인증서 생성이 완료되었습니다. 다시 실행해주세요.");
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
catch (e) {
|
|
51
|
-
console.error("❌ 인증서 생성 실패");
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
// 2. 필수 값 체크
|
|
56
|
-
if (!tvIp || !appId) {
|
|
57
|
-
console.error("\n❌ Tizen 필수 설정이 누락되었습니다!");
|
|
58
|
-
console.log(`\n[해결 방법]`);
|
|
59
|
-
console.log(`1. ctv.config.json에 'tizen.ip'와 'tizen.appId'를 설정하세요.`);
|
|
60
|
-
console.log(`2. 또는 실행 시 인자를 추가하세요: ctv-run tizen --ip=192.168.x.x --appId=YOUR_APP_ID`);
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
// [수정 포인트 2] 조립 시 existingWitsConfig를 참조하도록 수정
|
|
64
|
-
const finalConfig = {
|
|
65
|
-
connectionInfo: {
|
|
66
|
-
deviceIp: tvIp,
|
|
67
|
-
hostIp: (0, util_1.getLocalIp)(),
|
|
68
|
-
width: ((_e = existingWitsConfig.connectionInfo) === null || _e === void 0 ? void 0 : _e.width) || "1920"
|
|
69
|
-
},
|
|
70
|
-
profileInfo: {
|
|
71
|
-
name: finalProfileName,
|
|
72
|
-
path: ((_f = config.tizen) === null || _f === void 0 ? void 0 : _f.profilePath) || ((_g = existingWitsConfig.profileInfo) === null || _g === void 0 ? void 0 : _g.path) || ""
|
|
73
|
-
},
|
|
74
|
-
appInfo: {
|
|
75
|
-
appId: appId,
|
|
76
|
-
baseAppPath: "index.html",
|
|
77
|
-
appWorkspace: process.cwd()
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
try {
|
|
81
|
-
console.log("🛠️ Updating .witsconfig.json...");
|
|
82
|
-
fs_extra_1.default.writeFileSync(witsConfigPath, JSON.stringify(finalConfig, null, 2));
|
|
83
|
-
const wits = new wits_1.Wits();
|
|
84
|
-
console.log("🚀 Starting Wits (Tizen Live Development)...");
|
|
85
|
-
yield wits.init();
|
|
86
|
-
yield wits.start();
|
|
87
|
-
yield wits.watch();
|
|
88
|
-
console.log("✅ Tizen App is running!");
|
|
89
|
-
}
|
|
90
|
-
catch (err) {
|
|
91
|
-
console.error(`❌ Tizen Error: ${err.message}`);
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
}
|
package/dist/util.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
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.loadConfig = loadConfig;
|
|
7
|
-
exports.getLocalIp = getLocalIp;
|
|
8
|
-
const path_1 = __importDefault(require("path"));
|
|
9
|
-
const fs_1 = __importDefault(require("fs"));
|
|
10
|
-
const os_1 = require("os");
|
|
11
|
-
/**
|
|
12
|
-
* 설정 파일을 로드하는 헬퍼 함수
|
|
13
|
-
*/
|
|
14
|
-
function loadConfig() {
|
|
15
|
-
const configPath = path_1.default.resolve(process.cwd(), 'ctv.config.json');
|
|
16
|
-
if (fs_1.default.existsSync(configPath)) {
|
|
17
|
-
try {
|
|
18
|
-
return JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
|
|
19
|
-
}
|
|
20
|
-
catch (e) {
|
|
21
|
-
console.warn('⚠️ ctv.config.json 형식이 올바르지 않습니다. 기본값을 사용합니다.');
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
return {};
|
|
25
|
-
}
|
|
26
|
-
function getLocalIp() {
|
|
27
|
-
const nets = (0, os_1.networkInterfaces)();
|
|
28
|
-
for (const name of Object.keys(nets)) {
|
|
29
|
-
for (const net of nets[name]) {
|
|
30
|
-
if (net.family === 'IPv4' && !net.internal)
|
|
31
|
-
return net.address;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return 'localhost';
|
|
35
|
-
}
|
package/dist/vizio.js
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
-
});
|
|
43
|
-
};
|
|
44
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
45
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
46
|
-
};
|
|
47
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
-
exports.runVizio = runVizio;
|
|
49
|
-
const playwright_1 = require("playwright");
|
|
50
|
-
const readline = __importStar(require("readline/promises"));
|
|
51
|
-
const path_1 = __importDefault(require("path"));
|
|
52
|
-
const http_1 = __importDefault(require("http"));
|
|
53
|
-
const util_1 = require("./util");
|
|
54
|
-
const serveHandler = require('serve-handler');
|
|
55
|
-
function runVizio() {
|
|
56
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
57
|
-
var _a, _b;
|
|
58
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
59
|
-
const args = process.argv.slice(2);
|
|
60
|
-
const config = (0, util_1.loadConfig)();
|
|
61
|
-
const getArg = (name) => { var _a; return (_a = args.find(arg => arg.startsWith(`--${name}=`))) === null || _a === void 0 ? void 0 : _a.split('=')[1]; };
|
|
62
|
-
const tvIp = getArg('ip') || ((_a = config.vizio) === null || _a === void 0 ? void 0 : _a.ip);
|
|
63
|
-
if (!tvIp) {
|
|
64
|
-
console.error(`\n❌ Vizio TV의 IP 주소가 없습니다!`);
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
const defaultPort = Number(getArg('port')) || ((_b = config.vizio) === null || _b === void 0 ? void 0 : _b.port) || 3300;
|
|
68
|
-
const launcherUrl = 'https://vizio-pm.s3-us-west-1.amazonaws.com/conjure-launcher.html';
|
|
69
|
-
const targetArg = getArg('target');
|
|
70
|
-
const urlArg = getArg('url');
|
|
71
|
-
let appUrl = '';
|
|
72
|
-
if (urlArg) {
|
|
73
|
-
appUrl = urlArg;
|
|
74
|
-
}
|
|
75
|
-
else if (targetArg) {
|
|
76
|
-
const distPath = path_1.default.resolve(process.cwd(), targetArg);
|
|
77
|
-
const server = http_1.default.createServer((req, res) => serveHandler(req, res, { public: distPath }));
|
|
78
|
-
server.listen(3000, '0.0.0.0');
|
|
79
|
-
appUrl = `http://${(0, util_1.getLocalIp)()}:3000`;
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
appUrl = `http://${(0, util_1.getLocalIp)()}.nip.io:${defaultPort}`;
|
|
83
|
-
}
|
|
84
|
-
console.log(`-----------------------------------------`);
|
|
85
|
-
console.log(`📡 Vizio TV IP : ${tvIp}`);
|
|
86
|
-
console.log(`🚀 App URL : ${appUrl}`);
|
|
87
|
-
console.log(`-----------------------------------------`);
|
|
88
|
-
// [개선] 로컬 네트워크 권한 팝업을 차단하기 위한 브라우저 실행 인자 설정
|
|
89
|
-
const browser = yield playwright_1.chromium.launch({
|
|
90
|
-
headless: false,
|
|
91
|
-
args: [
|
|
92
|
-
'--disable-web-security',
|
|
93
|
-
'--disable-features=BlockInsecurePrivateNetworkRequests'
|
|
94
|
-
]
|
|
95
|
-
});
|
|
96
|
-
const context = yield browser.newContext({ ignoreHTTPSErrors: true });
|
|
97
|
-
const page = yield context.newPage();
|
|
98
|
-
try {
|
|
99
|
-
console.log(`🌐 런처 접속 중: ${launcherUrl}`);
|
|
100
|
-
yield page.goto(launcherUrl);
|
|
101
|
-
console.log(`⌨️ TV IP 입력 중: ${tvIp}`);
|
|
102
|
-
yield page.waitForSelector('input[placeholder*="IP"]');
|
|
103
|
-
yield page.fill('input[placeholder*="IP"]', tvIp);
|
|
104
|
-
// 스크린샷 10.34.07 반영: Start Pairing 버튼 클릭
|
|
105
|
-
console.log('🔘 Start Pairing 클릭');
|
|
106
|
-
yield page.locator('input[value="Start Pairing"]').click();
|
|
107
|
-
// SSL 인증 자동화 (스크린샷 10.25.20, 10.25.29 대응)
|
|
108
|
-
const sslLink = page.locator('a', { hasText: 'Start SSL process' });
|
|
109
|
-
if (yield sslLink.isVisible({ timeout: 5000 })) {
|
|
110
|
-
console.log('⏳ SSL 인증 페이지 자동 돌파 시도...');
|
|
111
|
-
const [sslPage] = yield Promise.all([
|
|
112
|
-
context.waitForEvent('page'),
|
|
113
|
-
sslLink.click()
|
|
114
|
-
]);
|
|
115
|
-
if (sslPage) {
|
|
116
|
-
yield sslPage.waitForLoadState('domcontentloaded');
|
|
117
|
-
try {
|
|
118
|
-
yield sslPage.locator('#details-button').click({ timeout: 5000 });
|
|
119
|
-
yield sslPage.locator('#proceed-link').click({ timeout: 5000 });
|
|
120
|
-
console.log('✅ SSL 인증 수락 완료.');
|
|
121
|
-
yield sslPage.waitForTimeout(1000);
|
|
122
|
-
yield sslPage.close();
|
|
123
|
-
}
|
|
124
|
-
catch (e) {
|
|
125
|
-
console.log('ℹ️ SSL 경고창이 이미 승인되었거나 뜨지 않았습니다.');
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
// Continue 클릭 (이미지 10.25.20의 3단계)
|
|
130
|
-
const continueBtn = page.locator('a', { hasText: 'Continue' });
|
|
131
|
-
if (yield continueBtn.isVisible()) {
|
|
132
|
-
yield continueBtn.click();
|
|
133
|
-
}
|
|
134
|
-
console.log('----------------------------------------------------');
|
|
135
|
-
console.log('📺 TV 화면의 PIN 4자리를 입력하세요.');
|
|
136
|
-
const pin = yield rl.question('PIN: ');
|
|
137
|
-
console.log('----------------------------------------------------');
|
|
138
|
-
// PIN 입력창 대기 및 입력
|
|
139
|
-
yield page.waitForSelector('input[placeholder*="PIN"]');
|
|
140
|
-
yield page.fill('input[placeholder*="PIN"]', pin);
|
|
141
|
-
// [스크린샷 10.39.39 반영] Pair 버튼 클릭 (value="Pair")
|
|
142
|
-
console.log('🔘 Pair 버튼 클릭');
|
|
143
|
-
yield page.locator('input[value="Pair"]').click();
|
|
144
|
-
console.log(`🔗 App URL 주입 중: ${appUrl}`);
|
|
145
|
-
// 페어링 완료 후 App URL 입력 필드가 나타날 때까지 대기
|
|
146
|
-
const urlInput = page.locator('#app-url');
|
|
147
|
-
yield urlInput.waitFor({ state: 'visible', timeout: 30000 });
|
|
148
|
-
// 기존 텍스트가 있을 수 있으므로 fill 대신 clear 후 fill 권장
|
|
149
|
-
yield urlInput.click(); // 스크린샷의 onclick="this.select();" 대응
|
|
150
|
-
yield urlInput.fill(appUrl);
|
|
151
|
-
console.log(`✅ URL 주입 완료: ${appUrl}`);
|
|
152
|
-
// [스크린샷 반영] Launch 버튼 클릭 (id="launch-btn")
|
|
153
|
-
console.log('🚀 Launch 버튼 클릭!');
|
|
154
|
-
yield page.locator('#launch-btn').click();
|
|
155
|
-
console.log('✅ 모든 명령이 성공적으로 전달되었습니다!');
|
|
156
|
-
}
|
|
157
|
-
catch (err) {
|
|
158
|
-
console.error('❌ 실행 중 에러 발생:', err);
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
}
|
package/dist/webos.js
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
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.runWebOS = runWebOS;
|
|
16
|
-
const child_process_1 = require("child_process");
|
|
17
|
-
const util_1 = require("./util");
|
|
18
|
-
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
19
|
-
const path_1 = __importDefault(require("path"));
|
|
20
|
-
function runWebOS() {
|
|
21
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
22
|
-
var _a, _b;
|
|
23
|
-
const args = process.argv.slice(2);
|
|
24
|
-
const config = (0, util_1.loadConfig)();
|
|
25
|
-
const getArg = (name) => { var _a; return (_a = args.find(arg => arg.startsWith(`--${name}=`))) === null || _a === void 0 ? void 0 : _a.split('=')[1]; };
|
|
26
|
-
// 1. 필수 값 추출 (기본값 제거)
|
|
27
|
-
const deviceName = getArg('device') || ((_a = config.webos) === null || _a === void 0 ? void 0 : _a.deviceName);
|
|
28
|
-
const appId = getArg('appId') || ((_b = config.webos) === null || _b === void 0 ? void 0 : _b.appId);
|
|
29
|
-
const targetArg = getArg('target');
|
|
30
|
-
const appUrl = getArg('url') || `http://${(0, util_1.getLocalIp)()}:5173`;
|
|
31
|
-
// 2. 필수 값 체크 (appId와 deviceName이 없으면 실행 중단)
|
|
32
|
-
if (!deviceName || !appId) {
|
|
33
|
-
console.error(`\n❌ 필수 설정이 누락되었습니다!`);
|
|
34
|
-
console.log(`\n[해결 방법]`);
|
|
35
|
-
console.log(`1. ctv.config.json에 'webos.deviceName'과 'webos.appId'를 설정하세요.`);
|
|
36
|
-
console.log(`2. 또는 실행 시 인자를 추가하세요: ctv-run webos --device=lg-tv --appId=com.my.app`);
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
const tempAppDir = path_1.default.resolve(process.cwd(), '.ctv-temp-webos');
|
|
40
|
-
try {
|
|
41
|
-
if (fs_extra_1.default.existsSync(tempAppDir))
|
|
42
|
-
fs_extra_1.default.removeSync(tempAppDir);
|
|
43
|
-
console.log(`🛠️ Generating webOS app structure...`);
|
|
44
|
-
// appId를 동적으로 받아 생성
|
|
45
|
-
(0, child_process_1.execSync)(`ares-generate -t basic -p "id=${appId},title=CTV-App" ${tempAppDir}`, { stdio: 'inherit' });
|
|
46
|
-
if (targetArg) {
|
|
47
|
-
// [이사 모드] 빌드된 파일 복사
|
|
48
|
-
const distPath = path_1.default.resolve(process.cwd(), targetArg);
|
|
49
|
-
if (!fs_extra_1.default.existsSync(distPath))
|
|
50
|
-
throw new Error(`❌ 폴더를 찾을 수 없습니다: ${distPath}`);
|
|
51
|
-
console.log(`📦 [Static Mode] Copying from ${distPath}...`);
|
|
52
|
-
fs_extra_1.default.copySync(distPath, tempAppDir, { overwrite: true });
|
|
53
|
-
}
|
|
54
|
-
else {
|
|
55
|
-
// [창문 모드] 리다이렉트 HTML 생성
|
|
56
|
-
console.log(`🌐 [Live Mode] Redirecting TV to ${appUrl}...`);
|
|
57
|
-
const redirectHtml = `
|
|
58
|
-
<!DOCTYPE html>
|
|
59
|
-
<html>
|
|
60
|
-
<head>
|
|
61
|
-
<meta charset="utf-8">
|
|
62
|
-
<script type="text/javascript">
|
|
63
|
-
window.onload = function() { window.location.href = "${appUrl}"; };
|
|
64
|
-
</script>
|
|
65
|
-
</head>
|
|
66
|
-
<body style="background:#000;color:#fff;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;">
|
|
67
|
-
<h2>🚀 Connecting to Dev Server...</h2>
|
|
68
|
-
</body>
|
|
69
|
-
</html>
|
|
70
|
-
`;
|
|
71
|
-
fs_extra_1.default.writeFileSync(path_1.default.join(tempAppDir, 'index.html'), redirectHtml);
|
|
72
|
-
}
|
|
73
|
-
console.log(`🔨 Packaging & Installing [${appId}]...`);
|
|
74
|
-
(0, child_process_1.execSync)(`ares-package ${tempAppDir}`, { stdio: 'inherit' });
|
|
75
|
-
const ipkFile = fs_extra_1.default.readdirSync(process.cwd()).find(f => f.startsWith(appId) && f.endsWith('.ipk'));
|
|
76
|
-
if (ipkFile) {
|
|
77
|
-
(0, child_process_1.execSync)(`ares-install -d ${deviceName} ${ipkFile}`, { stdio: 'inherit' });
|
|
78
|
-
(0, child_process_1.execSync)(`ares-launch -d ${deviceName} ${appId}`, { stdio: 'inherit' });
|
|
79
|
-
fs_extra_1.default.removeSync(path_1.default.resolve(process.cwd(), ipkFile));
|
|
80
|
-
console.log(`\n✅ webOS Launch Success!`);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
catch (err) {
|
|
84
|
-
console.error(`\n❌ Error: ${err.message}`);
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
}
|