@zsukim/ctv-run 1.0.6 → 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/dist/index.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zsukim/ctv-run",
3
- "version": "1.0.6",
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": ">=18"
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
- }