unified-tvdevelopment-cli 1.0.1 → 1.0.2
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/cli.mjs +36 -19
- package/install.ps1 +197 -82
- package/install.sh +130 -60
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -41648,26 +41648,43 @@ function Devices({ focused, platform: platform3, onDeviceChange }) {
|
|
|
41648
41648
|
setMode(MODES.LIST);
|
|
41649
41649
|
return;
|
|
41650
41650
|
}
|
|
41651
|
-
|
|
41652
|
-
|
|
41653
|
-
|
|
41654
|
-
|
|
41655
|
-
|
|
41656
|
-
|
|
41657
|
-
|
|
41658
|
-
|
|
41659
|
-
|
|
41660
|
-
|
|
41661
|
-
|
|
41662
|
-
|
|
41663
|
-
|
|
41664
|
-
|
|
41665
|
-
|
|
41666
|
-
|
|
41667
|
-
|
|
41651
|
+
const goBack = key.upArrow || key.shift && key.tab;
|
|
41652
|
+
const goNext = !goBack && (key.tab || key.return && formField < fields.length - 1);
|
|
41653
|
+
const submit = !goBack && key.return && formField === fields.length - 1;
|
|
41654
|
+
if (goBack) {
|
|
41655
|
+
setFormField((f) => Math.max(0, f - 1));
|
|
41656
|
+
return;
|
|
41657
|
+
}
|
|
41658
|
+
if (goNext) {
|
|
41659
|
+
setFormField((f) => Math.min(fields.length - 1, f + 1));
|
|
41660
|
+
return;
|
|
41661
|
+
}
|
|
41662
|
+
if (submit) {
|
|
41663
|
+
const missingName = platform3 === "webos" && !form.name.trim();
|
|
41664
|
+
const missingHost = !form.host.trim();
|
|
41665
|
+
if (missingName) {
|
|
41666
|
+
setStatus({ ok: false, msg: "Name is required" });
|
|
41667
|
+
return;
|
|
41668
|
+
}
|
|
41669
|
+
if (missingHost) {
|
|
41670
|
+
setStatus({ ok: false, msg: "Host is required" });
|
|
41671
|
+
return;
|
|
41672
|
+
}
|
|
41673
|
+
setWorking(true);
|
|
41674
|
+
try {
|
|
41675
|
+
if (platform3 === "webos") {
|
|
41676
|
+
await utils.addDevice(form);
|
|
41677
|
+
setStatus({ ok: true, msg: `Device '${form.name}' added` });
|
|
41678
|
+
} else {
|
|
41679
|
+
const out = await utils.connectDevice({ host: form.host, port: parseInt(form.port) || void 0 });
|
|
41680
|
+
setStatus({ ok: true, msg: out || `Connected to ${form.host}:${form.port}` });
|
|
41668
41681
|
}
|
|
41669
|
-
|
|
41682
|
+
await load2();
|
|
41683
|
+
setMode(MODES.LIST);
|
|
41684
|
+
} catch (e) {
|
|
41685
|
+
setStatus({ ok: false, msg: e.message });
|
|
41670
41686
|
}
|
|
41687
|
+
setWorking(false);
|
|
41671
41688
|
}
|
|
41672
41689
|
}
|
|
41673
41690
|
if (mode === MODES.CONFIRM_REMOVE) {
|
|
@@ -41712,7 +41729,7 @@ function Devices({ focused, platform: platform3, onDeviceChange }) {
|
|
|
41712
41729
|
onChange: (v) => setForm((f) => ({ ...f, [field]: v })),
|
|
41713
41730
|
placeholder: field === "port" ? platform3 === "tizen" ? "26101" : "5555" : `Enter ${field}...`
|
|
41714
41731
|
}
|
|
41715
|
-
) : /* @__PURE__ */ import_react29.default.createElement(Text, { color: "white" }, form[field] || /* @__PURE__ */ import_react29.default.createElement(Text, { dimColor: true }, "(empty)")))), /* @__PURE__ */ import_react29.default.createElement(Text, { dimColor: true }, "Tab
|
|
41732
|
+
) : /* @__PURE__ */ import_react29.default.createElement(Text, { color: "white" }, form[field] || /* @__PURE__ */ import_react29.default.createElement(Text, { dimColor: true }, "(empty)")))), /* @__PURE__ */ import_react29.default.createElement(Text, { dimColor: true }, "Tab/\u2193 next \u2191 back Last field Enter submits Esc cancel")), mode === MODES.CONFIRM_REMOVE && devices[cursor] && /* @__PURE__ */ import_react29.default.createElement(Box_default, { borderStyle: "round", borderColor: "red", paddingX: 2, paddingY: 1 }, /* @__PURE__ */ import_react29.default.createElement(Text, { color: "red" }, removeLabel, " "), /* @__PURE__ */ import_react29.default.createElement(Text, { bold: true, color: "white" }, devices[cursor].name ?? devices[cursor].serial), /* @__PURE__ */ import_react29.default.createElement(Text, { color: "red" }, "? "), /* @__PURE__ */ import_react29.default.createElement(Text, { color: "yellow" }, "[y] Yes [n] No")));
|
|
41716
41733
|
}
|
|
41717
41734
|
|
|
41718
41735
|
// src/screens/Generate.js
|
package/install.ps1
CHANGED
|
@@ -1,29 +1,27 @@
|
|
|
1
|
-
#
|
|
1
|
+
# tvdev-cli — Windows installer
|
|
2
2
|
# Run:
|
|
3
3
|
# iwr -useb https://raw.githubusercontent.com/tvdev-cli/tvdev-cli/main/install.ps1 | iex
|
|
4
|
-
# iwr -useb https://raw.githubusercontent.com/tvdev-cli/tvdev-cli/main/install.ps1 | iex
|
|
5
|
-
|
|
4
|
+
# iwr -useb https://raw.githubusercontent.com/tvdev-cli/tvdev-cli/main/install.ps1 | iex -Beta
|
|
5
|
+
#
|
|
6
6
|
param(
|
|
7
|
-
[switch]$Beta
|
|
8
|
-
[string]$Tag = "latest"
|
|
7
|
+
[switch]$Beta
|
|
9
8
|
)
|
|
10
9
|
|
|
11
10
|
$ErrorActionPreference = 'Stop'
|
|
12
|
-
$Package = "unified-tvdevelopment-cli"
|
|
13
|
-
$Bin = "tvdev"
|
|
14
|
-
$RequiredNodeMajor = 18
|
|
15
11
|
|
|
16
|
-
|
|
12
|
+
$Repo = "tvdev-cli/tvdev-cli"
|
|
13
|
+
$Bin = "tvdev"
|
|
14
|
+
$Channel = if ($Beta) { "beta" } else { "stable" }
|
|
15
|
+
$RequiredNodeMajor = 18
|
|
16
|
+
$InstallDir = Join-Path $env:LOCALAPPDATA "tvdev\bin"
|
|
17
|
+
$BinPath = Join-Path $InstallDir "$Bin"
|
|
18
|
+
$VersionFile = Join-Path $InstallDir ".tvdev-version"
|
|
17
19
|
|
|
18
20
|
# ── Helpers ───────────────────────────────────────────────────────────────────
|
|
19
21
|
function Write-Banner {
|
|
20
22
|
Write-Host ""
|
|
21
23
|
Write-Host " [TV Dev Manager]" -ForegroundColor Cyan -NoNewline
|
|
22
|
-
if ($
|
|
23
|
-
Write-Host " tag: $Tag" -ForegroundColor Yellow
|
|
24
|
-
} else {
|
|
25
|
-
Write-Host ""
|
|
26
|
-
}
|
|
24
|
+
if ($Channel -eq "beta") { Write-Host " channel: beta" -ForegroundColor Yellow } else { Write-Host "" }
|
|
27
25
|
Write-Host " Universal Smart TV Development CLI" -ForegroundColor DarkGray
|
|
28
26
|
Write-Host " LG webOS · Samsung Tizen · Amazon Fire TV · Android TV" -ForegroundColor DarkGray
|
|
29
27
|
Write-Host ""
|
|
@@ -35,50 +33,81 @@ function Write-Info { param($msg) Write-Host " [..] $msg" -ForegroundColor Blue
|
|
|
35
33
|
function Write-Warn { param($msg) Write-Host " [!!] $msg" -ForegroundColor Yellow }
|
|
36
34
|
function Write-Fail { param($msg) Write-Host "`n [XX] $msg`n" -ForegroundColor Red; exit 1 }
|
|
37
35
|
|
|
38
|
-
function
|
|
39
|
-
param([string]$
|
|
40
|
-
$
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
function Compare-Version {
|
|
45
|
-
param([string]$installed, [string]$latest)
|
|
46
|
-
$a = Get-SemverParts $installed
|
|
47
|
-
$b = Get-SemverParts $latest
|
|
48
|
-
for ($i = 0; $i -lt [Math]::Max($a.Count, $b.Count); $i++) {
|
|
49
|
-
$av = if ($i -lt $a.Count) { $a[$i] } else { 0 }
|
|
50
|
-
$bv = if ($i -lt $b.Count) { $b[$i] } else { 0 }
|
|
51
|
-
if ($av -gt $bv) { return 1 }
|
|
52
|
-
if ($av -lt $bv) { return -1 }
|
|
36
|
+
function Invoke-GhApi {
|
|
37
|
+
param([string]$Url)
|
|
38
|
+
$headers = @{
|
|
39
|
+
'Accept' = 'application/vnd.github+json'
|
|
40
|
+
'X-GitHub-Api-Version' = '2022-11-28'
|
|
41
|
+
'User-Agent' = 'tvdev-cli-installer'
|
|
53
42
|
}
|
|
54
|
-
|
|
43
|
+
Invoke-RestMethod -Uri $Url -Headers $headers
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function Compare-Semver {
|
|
47
|
+
param([string]$A, [string]$B)
|
|
48
|
+
# returns $true if A >= B (strip pre-release suffix)
|
|
49
|
+
$av = [Version](($A -split '-')[0] -replace '^v','')
|
|
50
|
+
$bv = [Version](($B -split '-')[0] -replace '^v','')
|
|
51
|
+
return $av -ge $bv
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function Add-ToUserPath {
|
|
55
|
+
param([string]$Dir)
|
|
56
|
+
$current = [Environment]::GetEnvironmentVariable("PATH", "User")
|
|
57
|
+
if ($current -split ';' -contains $Dir) { return $false }
|
|
58
|
+
[Environment]::SetEnvironmentVariable("PATH", "$current;$Dir", "User")
|
|
59
|
+
$env:PATH = "$env:PATH;$Dir"
|
|
60
|
+
return $true
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function Test-Command {
|
|
64
|
+
param([string]$Name)
|
|
65
|
+
return [bool](Get-Command $Name -ErrorAction SilentlyContinue)
|
|
55
66
|
}
|
|
56
67
|
|
|
57
68
|
Write-Banner
|
|
58
69
|
|
|
59
|
-
# ──
|
|
60
|
-
Write-Step "
|
|
70
|
+
# ── Resolve latest release from GitHub ───────────────────────────────────────
|
|
71
|
+
Write-Step "Resolving latest $Channel release from GitHub"
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
if ($Channel -eq "beta") {
|
|
75
|
+
$releases = Invoke-GhApi "https://api.github.com/repos/$Repo/releases"
|
|
76
|
+
$release = $releases | Where-Object { $_.prerelease -eq $true } | Select-Object -First 1
|
|
77
|
+
} else {
|
|
78
|
+
$release = Invoke-GhApi "https://api.github.com/repos/$Repo/releases/latest"
|
|
79
|
+
}
|
|
61
80
|
|
|
62
|
-
$
|
|
63
|
-
|
|
81
|
+
if (-not $release -or -not $release.tag_name) {
|
|
82
|
+
Write-Fail "Could not resolve release from GitHub. Visit https://github.com/$Repo/releases"
|
|
83
|
+
}
|
|
64
84
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
85
|
+
$ReleaseTag = $release.tag_name
|
|
86
|
+
$ReleaseVersion = $ReleaseTag -replace '^v', ''
|
|
87
|
+
} catch {
|
|
88
|
+
Write-Fail "GitHub API request failed: $_"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
Write-Info "Latest release : $ReleaseTag"
|
|
92
|
+
|
|
93
|
+
# ── Idempotency check ─────────────────────────────────────────────────────────
|
|
94
|
+
Write-Step "Checking existing installation"
|
|
71
95
|
|
|
96
|
+
$InstalledVer = ""
|
|
97
|
+
if (Test-Path $VersionFile) {
|
|
98
|
+
$InstalledVer = (Get-Content $VersionFile -Raw).Trim().TrimStart('v')
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if ($InstalledVer -and (Test-Path $BinPath)) {
|
|
72
102
|
Write-Ok "$Bin already installed"
|
|
73
|
-
|
|
74
|
-
|
|
103
|
+
Write-Info "Installed : v$InstalledVer"
|
|
104
|
+
Write-Info "Latest : v$ReleaseVersion ($Channel)"
|
|
75
105
|
|
|
76
|
-
if (
|
|
106
|
+
if (Compare-Semver $InstalledVer $ReleaseVersion) {
|
|
77
107
|
Write-Ok "Already up to date — nothing to do"
|
|
78
108
|
Write-Host "`n Run: $Bin`n" -ForegroundColor Cyan
|
|
79
109
|
exit 0
|
|
80
110
|
}
|
|
81
|
-
|
|
82
111
|
Write-Info "Update available — reinstalling"
|
|
83
112
|
} else {
|
|
84
113
|
Write-Info "$Bin not yet installed — starting fresh install"
|
|
@@ -87,11 +116,23 @@ if (Get-Command $Bin -ErrorAction SilentlyContinue) {
|
|
|
87
116
|
# ── Node.js ───────────────────────────────────────────────────────────────────
|
|
88
117
|
Write-Step "Checking Node.js"
|
|
89
118
|
|
|
90
|
-
if (-not (
|
|
91
|
-
Write-Warn "Node.js not found
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
119
|
+
if (-not (Test-Command "node")) {
|
|
120
|
+
Write-Warn "Node.js not found — attempting install via winget"
|
|
121
|
+
try {
|
|
122
|
+
winget install --id OpenJS.NodeJS.LTS --accept-source-agreements --accept-package-agreements --silent
|
|
123
|
+
# reload PATH for current session
|
|
124
|
+
$env:PATH = [Environment]::GetEnvironmentVariable("PATH", "Machine") + ";" +
|
|
125
|
+
[Environment]::GetEnvironmentVariable("PATH", "User")
|
|
126
|
+
Write-Ok "Node.js installed via winget"
|
|
127
|
+
} catch {
|
|
128
|
+
Write-Host " Install Node.js manually from https://nodejs.org (LTS recommended)" -ForegroundColor DarkGray
|
|
129
|
+
Write-Host " Or via winget: winget install OpenJS.NodeJS.LTS" -ForegroundColor DarkGray
|
|
130
|
+
Write-Fail "Node.js $RequiredNodeMajor+ required."
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (-not (Test-Command "node")) {
|
|
135
|
+
Write-Fail "Node.js not found after install. Restart terminal and re-run this script."
|
|
95
136
|
}
|
|
96
137
|
|
|
97
138
|
$nodeVer = (node --version).TrimStart('v')
|
|
@@ -103,65 +144,127 @@ if ($nodeMajor -lt $RequiredNodeMajor) {
|
|
|
103
144
|
|
|
104
145
|
Write-Ok "Node.js v$nodeVer"
|
|
105
146
|
|
|
106
|
-
# ──
|
|
107
|
-
Write-Step "
|
|
147
|
+
# ── Download binary from GitHub release ───────────────────────────────────────
|
|
148
|
+
Write-Step "Downloading $Bin $ReleaseTag"
|
|
108
149
|
|
|
109
|
-
|
|
110
|
-
|
|
150
|
+
New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
|
|
151
|
+
|
|
152
|
+
# Find the cli.mjs asset in the release
|
|
153
|
+
$asset = $release.assets | Where-Object { $_.name -eq "cli.mjs" } | Select-Object -First 1
|
|
154
|
+
|
|
155
|
+
if ($asset) {
|
|
156
|
+
$DownloadUrl = $asset.browser_download_url
|
|
157
|
+
} else {
|
|
158
|
+
$DownloadUrl = "https://github.com/$Repo/releases/download/$ReleaseTag/cli.mjs"
|
|
111
159
|
}
|
|
112
160
|
|
|
113
|
-
Write-
|
|
161
|
+
Write-Info "Source : $DownloadUrl"
|
|
114
162
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
163
|
+
$downloaded = $false
|
|
164
|
+
try {
|
|
165
|
+
Invoke-WebRequest -Uri $DownloadUrl -OutFile $BinPath -UseBasicParsing
|
|
166
|
+
$downloaded = $true
|
|
167
|
+
Write-Ok "Binary downloaded to $BinPath"
|
|
168
|
+
} catch {
|
|
169
|
+
Write-Warn "GitHub download failed — falling back to npm"
|
|
170
|
+
}
|
|
119
171
|
|
|
120
|
-
|
|
172
|
+
if (-not $downloaded) {
|
|
173
|
+
if (-not (Test-Command "npm")) {
|
|
174
|
+
Write-Fail "npm not found and GitHub download failed. Install Node.js from https://nodejs.org"
|
|
175
|
+
}
|
|
176
|
+
$NpmTag = if ($Channel -eq "beta") { "beta" } else { "latest" }
|
|
177
|
+
Write-Info "Running: npm install -g unified-tvdevelopment-cli@$NpmTag"
|
|
178
|
+
npm install -g "unified-tvdevelopment-cli@$NpmTag" 2>&1 | Where-Object { $_ -notmatch "^npm warn" -and $_.Trim() -ne "" }
|
|
179
|
+
# use npm global bin path
|
|
180
|
+
$BinPath = Join-Path (npm prefix -g) "bin\$Bin"
|
|
181
|
+
}
|
|
121
182
|
|
|
122
|
-
|
|
123
|
-
|
|
183
|
+
# Create a wrapper .cmd so Windows can execute the .mjs without typing 'node'
|
|
184
|
+
$WrapperPath = Join-Path $InstallDir "$Bin.cmd"
|
|
185
|
+
$WrapperContent = "@echo off`r`nnode `"%~dp0$Bin`" %*"
|
|
186
|
+
Set-Content -Path $WrapperPath -Value $WrapperContent -Encoding ASCII
|
|
124
187
|
|
|
125
|
-
#
|
|
126
|
-
|
|
188
|
+
# Save installed version
|
|
189
|
+
Set-Content -Path $VersionFile -Value $ReleaseVersion -Encoding UTF8
|
|
190
|
+
Write-Ok "Installed to $InstallDir"
|
|
127
191
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
Write-
|
|
192
|
+
# ── PATH ──────────────────────────────────────────────────────────────────────
|
|
193
|
+
Write-Step "Setting up PATH"
|
|
194
|
+
|
|
195
|
+
if (Add-ToUserPath $InstallDir) {
|
|
196
|
+
Write-Ok "Added $InstallDir to user PATH"
|
|
197
|
+
Write-Warn "Restart your terminal for PATH to take effect in new sessions"
|
|
198
|
+
} else {
|
|
199
|
+
Write-Ok "PATH already contains $InstallDir"
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (Test-Command $Bin) {
|
|
203
|
+
Write-Ok "$Bin is available in PATH"
|
|
204
|
+
} else {
|
|
205
|
+
Write-Warn "$Bin not in PATH for this session — restart terminal to use it"
|
|
133
206
|
}
|
|
134
207
|
|
|
135
208
|
# ── Platform tools ────────────────────────────────────────────────────────────
|
|
136
|
-
Write-Step "Checking platform-specific tools"
|
|
209
|
+
Write-Step "Checking and installing platform-specific tools"
|
|
210
|
+
|
|
211
|
+
function Install-NpmPackage {
|
|
212
|
+
param([string]$Package, [string]$Label)
|
|
213
|
+
Write-Info "Installing $Label via npm..."
|
|
214
|
+
try {
|
|
215
|
+
npm install -g $Package 2>&1 | Where-Object { $_ -notmatch "^npm warn" -and $_.Trim() -ne "" }
|
|
216
|
+
Write-Ok "$Label installed"
|
|
217
|
+
} catch {
|
|
218
|
+
Write-Warn "$Label install failed — run manually: npm install -g $Package"
|
|
219
|
+
}
|
|
220
|
+
}
|
|
137
221
|
|
|
138
|
-
|
|
139
|
-
Write-
|
|
222
|
+
function Install-AdbViaWinget {
|
|
223
|
+
Write-Info "Installing adb via winget (Android Platform Tools)..."
|
|
224
|
+
try {
|
|
225
|
+
winget install --id Google.PlatformTools --accept-source-agreements --accept-package-agreements --silent
|
|
226
|
+
$env:PATH = [Environment]::GetEnvironmentVariable("PATH", "Machine") + ";" +
|
|
227
|
+
[Environment]::GetEnvironmentVariable("PATH", "User")
|
|
228
|
+
Write-Ok "adb (Fire TV/Android TV) installed"
|
|
229
|
+
} catch {
|
|
230
|
+
Write-Warn "winget adb install failed — install Android SDK Platform Tools: https://developer.android.com/studio/releases/platform-tools"
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
# ares-cli (LG webOS) — auto-install via npm
|
|
235
|
+
if (Test-Command "ares-setup-device") {
|
|
236
|
+
Write-Ok "ares-cli (LG webOS) — found"
|
|
140
237
|
} else {
|
|
141
|
-
Write-Warn "ares-cli not found
|
|
238
|
+
Write-Warn "ares-cli (LG webOS) not found — installing..."
|
|
239
|
+
Install-NpmPackage "@webosose/ares-cli" "ares-cli"
|
|
142
240
|
}
|
|
143
241
|
|
|
144
|
-
|
|
145
|
-
|
|
242
|
+
# sdb (Samsung Tizen) — requires Tizen Studio GUI installer, warn only
|
|
243
|
+
if (Test-Command "sdb") {
|
|
244
|
+
Write-Ok "sdb (Samsung Tizen) — found"
|
|
146
245
|
} else {
|
|
147
|
-
Write-Warn "sdb not found
|
|
246
|
+
Write-Warn "sdb (Samsung Tizen) not found — requires Tizen Studio: https://developer.samsung.com/smarttv"
|
|
148
247
|
}
|
|
149
248
|
|
|
150
|
-
|
|
151
|
-
|
|
249
|
+
# adb (Fire TV / Android TV) — auto-install via winget
|
|
250
|
+
if (Test-Command "adb") {
|
|
251
|
+
Write-Ok "adb (Fire TV/Android TV) — found"
|
|
152
252
|
} else {
|
|
153
|
-
Write-Warn "adb
|
|
253
|
+
Write-Warn "adb (Fire TV/Android TV) not found — installing..."
|
|
254
|
+
Install-AdbViaWinget
|
|
154
255
|
}
|
|
155
256
|
|
|
156
|
-
|
|
157
|
-
|
|
257
|
+
# inputd-cli (optional Fire TV input simulation) — auto-install via npm
|
|
258
|
+
if (Test-Command "inputd-cli") {
|
|
259
|
+
Write-Ok "inputd-cli (Fire TV input) — found"
|
|
158
260
|
} else {
|
|
159
|
-
Write-Warn "inputd-cli
|
|
261
|
+
Write-Warn "inputd-cli (Fire TV input) not found — installing..."
|
|
262
|
+
Install-NpmPackage "inputd-cli" "inputd-cli"
|
|
160
263
|
}
|
|
161
264
|
|
|
162
265
|
# ── Done ──────────────────────────────────────────────────────────────────────
|
|
163
266
|
Write-Host ""
|
|
164
|
-
Write-Host " Installation complete!" -ForegroundColor Green
|
|
267
|
+
Write-Host " Installation complete! ($ReleaseTag)" -ForegroundColor Green
|
|
165
268
|
Write-Host ""
|
|
166
269
|
Write-Host " Launch TV Dev Manager: " -NoNewline
|
|
167
270
|
Write-Host $Bin -ForegroundColor Cyan
|
|
@@ -169,3 +272,15 @@ Write-Host ""
|
|
|
169
272
|
Write-Host " GitHub : https://github.com/tvdev-cli/tvdev-cli" -ForegroundColor DarkGray
|
|
170
273
|
Write-Host " npm : https://npmjs.com/package/unified-tvdevelopment-cli" -ForegroundColor DarkGray
|
|
171
274
|
Write-Host ""
|
|
275
|
+
|
|
276
|
+
$currentPath = [Environment]::GetEnvironmentVariable("PATH", "User")
|
|
277
|
+
if (-not ($currentPath -split ';' -contains $InstallDir)) {
|
|
278
|
+
Write-Host " Tip: to make " -NoNewline -ForegroundColor Yellow
|
|
279
|
+
Write-Host "$Bin" -NoNewline -ForegroundColor Cyan
|
|
280
|
+
Write-Host " available in every new terminal, add this to your user PATH:" -ForegroundColor Yellow
|
|
281
|
+
Write-Host ""
|
|
282
|
+
Write-Host " [Environment]::SetEnvironmentVariable('PATH', `$env:PATH + ';$InstallDir', 'User')" -ForegroundColor DarkGray
|
|
283
|
+
Write-Host ""
|
|
284
|
+
Write-Host " Or restart your terminal — the installer already added it." -ForegroundColor DarkGray
|
|
285
|
+
Write-Host ""
|
|
286
|
+
}
|
package/install.sh
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# tvdev-cli — one-line installer
|
|
3
|
-
# Pulls the latest release binary directly from GitHub Releases.
|
|
2
|
+
# tvdev-cli — one-line installer (macOS / Linux)
|
|
4
3
|
#
|
|
5
4
|
# curl -fsSL https://raw.githubusercontent.com/tvdev-cli/tvdev-cli/main/install.sh | bash
|
|
6
5
|
# curl -fsSL https://raw.githubusercontent.com/tvdev-cli/tvdev-cli/main/install.sh | bash -s -- --beta
|
|
@@ -12,12 +11,15 @@ BIN="tvdev"
|
|
|
12
11
|
REQUIRED_NODE=18
|
|
13
12
|
NVM_VERSION="v0.39.7"
|
|
14
13
|
INSTALL_NODE_VERSION="20"
|
|
15
|
-
|
|
14
|
+
INSTALL_DIR="${HOME}/.local/bin"
|
|
15
|
+
VERSION_FILE="${INSTALL_DIR}/.tvdev-version"
|
|
16
|
+
CHANNEL="stable"
|
|
16
17
|
|
|
17
18
|
# ── Flags ─────────────────────────────────────────────────────────────────────
|
|
18
19
|
for arg in "${@:-}"; do
|
|
19
20
|
case "$arg" in
|
|
20
|
-
--beta)
|
|
21
|
+
--beta) CHANNEL="beta" ;;
|
|
22
|
+
--stable) CHANNEL="stable" ;;
|
|
21
23
|
esac
|
|
22
24
|
done
|
|
23
25
|
|
|
@@ -36,9 +38,7 @@ banner() {
|
|
|
36
38
|
echo -e " ${INDIGO}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
|
37
39
|
echo -e " ${DIM}Universal Smart TV Development CLI${RESET}"
|
|
38
40
|
echo -e " ${DIM}LG webOS · Samsung Tizen · Amazon Fire TV · Android TV${RESET}"
|
|
39
|
-
|
|
40
|
-
echo -e " ${YELLOW} channel: beta${RESET}"
|
|
41
|
-
fi
|
|
41
|
+
[ "$CHANNEL" = "beta" ] && echo -e " ${YELLOW} channel: beta${RESET}"
|
|
42
42
|
echo ""
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -58,57 +58,58 @@ semver_gte() {
|
|
|
58
58
|
detect_shell_rc() {
|
|
59
59
|
case "${SHELL:-}" in
|
|
60
60
|
*/zsh) echo "$HOME/.zshrc" ;;
|
|
61
|
-
*/bash)
|
|
62
|
-
[ -f "$HOME/.bash_profile" ] && echo "$HOME/.bash_profile" || echo "$HOME/.bashrc" ;;
|
|
61
|
+
*/bash) [ -f "$HOME/.bash_profile" ] && echo "$HOME/.bash_profile" || echo "$HOME/.bashrc" ;;
|
|
63
62
|
*/fish) echo "$HOME/.config/fish/config.fish" ;;
|
|
64
63
|
*) echo "$HOME/.profile" ;;
|
|
65
64
|
esac
|
|
66
65
|
}
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
gh_api() {
|
|
68
|
+
curl -fsSL \
|
|
69
|
+
-H "Accept: application/vnd.github+json" \
|
|
70
|
+
-H "X-GitHub-Api-Version: 2022-11-28" \
|
|
71
|
+
"$1"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# ── Resolve latest release tag from GitHub ────────────────────────────────────
|
|
75
|
+
resolve_release_tag() {
|
|
71
76
|
if [ "$CHANNEL" = "beta" ]; then
|
|
72
|
-
|
|
73
|
-
url="https://api.github.com/repos/${REPO}/releases"
|
|
74
|
-
curl -fsSL "$url" \
|
|
77
|
+
gh_api "https://api.github.com/repos/${REPO}/releases" \
|
|
75
78
|
| grep -E '"tag_name"|"prerelease"' \
|
|
76
79
|
| paste - - \
|
|
77
|
-
| awk -F'"' '/"prerelease": true/{print $4; exit}'
|
|
80
|
+
| awk -F'"' '$0 ~ /"prerelease": true/ { print $4; exit }'
|
|
78
81
|
else
|
|
79
|
-
|
|
80
|
-
curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" \
|
|
82
|
+
gh_api "https://api.github.com/repos/${REPO}/releases/latest" \
|
|
81
83
|
| grep '"tag_name"' \
|
|
82
84
|
| sed 's/.*"tag_name": *"\([^"]*\)".*/\1/'
|
|
83
85
|
fi
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
# ── Banner ────────────────────────────────────────────────────────────────────
|
|
87
88
|
banner
|
|
88
89
|
|
|
89
|
-
# ── Resolve release
|
|
90
|
+
# ── Resolve release ───────────────────────────────────────────────────────────
|
|
90
91
|
step "Resolving latest ${CHANNEL} release from GitHub"
|
|
91
92
|
|
|
92
|
-
RELEASE_TAG=$(
|
|
93
|
-
[ -z "$RELEASE_TAG" ] && fail "Could not resolve release
|
|
93
|
+
RELEASE_TAG=$(resolve_release_tag)
|
|
94
|
+
[ -z "$RELEASE_TAG" ] && fail "Could not resolve release from GitHub. Visit https://github.com/${REPO}/releases"
|
|
94
95
|
|
|
95
96
|
RELEASE_VERSION="${RELEASE_TAG#v}"
|
|
96
|
-
info "
|
|
97
|
+
info "Latest release : ${RELEASE_TAG}"
|
|
97
98
|
|
|
98
|
-
#
|
|
99
|
-
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${RELEASE_TAG}/cli.mjs"
|
|
100
|
-
|
|
101
|
-
# ── Idempotency check ─────────────────────────────────────────────────────────
|
|
99
|
+
# ── Idempotency ───────────────────────────────────────────────────────────────
|
|
102
100
|
step "Checking existing installation"
|
|
103
101
|
|
|
104
102
|
INSTALLED_VER=""
|
|
105
|
-
if
|
|
106
|
-
INSTALLED_VER=$("$
|
|
103
|
+
if [ -f "$VERSION_FILE" ]; then
|
|
104
|
+
INSTALLED_VER=$(cat "$VERSION_FILE" 2>/dev/null | tr -d '[:space:]v' || true)
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
if [ -n "$INSTALLED_VER" ] && command -v "$BIN" &>/dev/null; then
|
|
107
108
|
ok "${BIN} already installed"
|
|
108
|
-
|
|
109
|
-
info "Latest : ${RELEASE_VERSION} (${CHANNEL})"
|
|
109
|
+
info "Installed : v${INSTALLED_VER}"
|
|
110
|
+
info "Latest : v${RELEASE_VERSION} (${CHANNEL})"
|
|
110
111
|
|
|
111
|
-
if
|
|
112
|
+
if semver_gte "$INSTALLED_VER" "$RELEASE_VERSION"; then
|
|
112
113
|
ok "Already up to date — nothing to do"
|
|
113
114
|
echo ""
|
|
114
115
|
echo -e " ${BOLD}Run: ${INDIGO}${BIN}${RESET}"
|
|
@@ -138,9 +139,7 @@ if ! command -v node &>/dev/null; then
|
|
|
138
139
|
NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
|
|
139
140
|
if [ -s "$NVM_DIR/nvm.sh" ]; then
|
|
140
141
|
source "$NVM_DIR/nvm.sh"
|
|
141
|
-
|
|
142
|
-
nvm install "$INSTALL_NODE_VERSION" && nvm use "$INSTALL_NODE_VERSION"
|
|
143
|
-
fi
|
|
142
|
+
! command -v node &>/dev/null && nvm install "$INSTALL_NODE_VERSION" && nvm use "$INSTALL_NODE_VERSION"
|
|
144
143
|
else
|
|
145
144
|
install_node_via_nvm
|
|
146
145
|
fi
|
|
@@ -154,46 +153,49 @@ NODE_MAJOR=$(node --version | sed 's/v//' | cut -d. -f1)
|
|
|
154
153
|
|
|
155
154
|
ok "Node.js $(node --version)"
|
|
156
155
|
|
|
157
|
-
# ── Download
|
|
158
|
-
step "Downloading ${BIN} ${RELEASE_TAG}
|
|
159
|
-
info "Source: ${DOWNLOAD_URL}"
|
|
156
|
+
# ── Download binary from GitHub release ───────────────────────────────────────
|
|
157
|
+
step "Downloading ${BIN} ${RELEASE_TAG}"
|
|
160
158
|
|
|
161
|
-
INSTALL_DIR="${HOME}/.local/bin"
|
|
162
159
|
mkdir -p "$INSTALL_DIR"
|
|
163
160
|
BIN_PATH="${INSTALL_DIR}/${BIN}"
|
|
161
|
+
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${RELEASE_TAG}/cli.mjs"
|
|
162
|
+
|
|
163
|
+
info "Source : ${DOWNLOAD_URL}"
|
|
164
164
|
|
|
165
165
|
if curl -fsSL --output "$BIN_PATH" "$DOWNLOAD_URL"; then
|
|
166
166
|
chmod +x "$BIN_PATH"
|
|
167
|
+
echo "$RELEASE_VERSION" > "$VERSION_FILE"
|
|
167
168
|
ok "Installed to ${BIN_PATH}"
|
|
168
169
|
else
|
|
169
|
-
# fallback: try npm if GitHub download fails
|
|
170
170
|
warn "GitHub download failed — falling back to npm"
|
|
171
|
-
|
|
171
|
+
command -v npm &>/dev/null || fail "npm not found and GitHub download failed. Install Node.js from https://nodejs.org"
|
|
172
172
|
NPM_TAG="latest"
|
|
173
173
|
[ "$CHANNEL" = "beta" ] && NPM_TAG="beta"
|
|
174
174
|
npm install -g "unified-tvdevelopment-cli@${NPM_TAG}" 2>&1 | grep -v "^npm warn" | grep -v "^$" || true
|
|
175
|
+
# point BIN_PATH to npm global bin for the version file
|
|
176
|
+
BIN_PATH=$(npm prefix -g)/bin/${BIN}
|
|
177
|
+
echo "$RELEASE_VERSION" > "$VERSION_FILE"
|
|
175
178
|
fi
|
|
176
179
|
|
|
177
180
|
# ── PATH ──────────────────────────────────────────────────────────────────────
|
|
178
181
|
step "Setting up PATH"
|
|
179
182
|
|
|
180
183
|
rc_file=$(detect_shell_rc)
|
|
181
|
-
export_line="export PATH=\"\$PATH:${INSTALL_DIR}\""
|
|
182
184
|
|
|
183
185
|
if echo ":${PATH}:" | grep -q ":${INSTALL_DIR}:"; then
|
|
184
186
|
ok "PATH already contains ${INSTALL_DIR}"
|
|
185
|
-
elif [[ "${SHELL:-}" == */fish ]]; then
|
|
186
|
-
echo "set -gx PATH \$PATH ${INSTALL_DIR}" >> "$rc_file"
|
|
187
|
-
ok "Added PATH entry to ${rc_file}"
|
|
188
187
|
else
|
|
189
|
-
if
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
echo "$export_line" >> "$rc_file"
|
|
193
|
-
ok "Added PATH entry to ${rc_file}"
|
|
188
|
+
if [[ "${SHELL:-}" == */fish ]]; then
|
|
189
|
+
grep -qF "$INSTALL_DIR" "$rc_file" 2>/dev/null || \
|
|
190
|
+
echo "set -gx PATH \$PATH ${INSTALL_DIR}" >> "$rc_file"
|
|
194
191
|
else
|
|
195
|
-
|
|
192
|
+
if ! grep -qF "$INSTALL_DIR" "$rc_file" 2>/dev/null; then
|
|
193
|
+
echo "" >> "$rc_file"
|
|
194
|
+
echo "# added by tvdev-cli installer" >> "$rc_file"
|
|
195
|
+
echo "export PATH=\"\$PATH:${INSTALL_DIR}\"" >> "$rc_file"
|
|
196
|
+
fi
|
|
196
197
|
fi
|
|
198
|
+
ok "Added ${INSTALL_DIR} to PATH in ${rc_file}"
|
|
197
199
|
export PATH="${PATH}:${INSTALL_DIR}"
|
|
198
200
|
fi
|
|
199
201
|
|
|
@@ -202,35 +204,94 @@ hash -r 2>/dev/null || true
|
|
|
202
204
|
if command -v "$BIN" &>/dev/null; then
|
|
203
205
|
ok "${BIN} is in PATH → $(command -v ${BIN})"
|
|
204
206
|
else
|
|
205
|
-
warn "${BIN} not in PATH
|
|
207
|
+
warn "${BIN} not in PATH yet — restart terminal or run:"
|
|
206
208
|
echo -e "\n source $(detect_shell_rc)\n"
|
|
207
209
|
fi
|
|
208
210
|
|
|
209
211
|
# ── Platform tools ────────────────────────────────────────────────────────────
|
|
210
|
-
step "Checking platform-specific tools"
|
|
212
|
+
step "Checking and installing platform-specific tools"
|
|
211
213
|
|
|
214
|
+
install_ares_cli() {
|
|
215
|
+
info "Installing ares-cli via npm..."
|
|
216
|
+
if npm install -g @webosose/ares-cli 2>&1 | grep -v "^npm warn" | grep -v "^$"; then
|
|
217
|
+
ok "ares-cli (LG webOS) installed → $(command -v ares-setup-device 2>/dev/null || echo 'reload shell')"
|
|
218
|
+
else
|
|
219
|
+
warn "ares-cli install failed — run manually: npm install -g @webosose/ares-cli"
|
|
220
|
+
fi
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
install_adb() {
|
|
224
|
+
if [[ "$(uname)" == "Darwin" ]]; then
|
|
225
|
+
if command -v brew &>/dev/null; then
|
|
226
|
+
info "Installing adb via Homebrew..."
|
|
227
|
+
if brew install --quiet android-platform-tools; then
|
|
228
|
+
ok "adb (Fire TV/Android TV) installed → $(command -v adb)"
|
|
229
|
+
else
|
|
230
|
+
warn "Homebrew adb install failed — install Android Studio SDK manually"
|
|
231
|
+
fi
|
|
232
|
+
else
|
|
233
|
+
warn "adb not found — install Homebrew first, then: brew install android-platform-tools"
|
|
234
|
+
fi
|
|
235
|
+
else
|
|
236
|
+
# Linux
|
|
237
|
+
if command -v apt-get &>/dev/null; then
|
|
238
|
+
info "Installing adb via apt..."
|
|
239
|
+
if sudo apt-get install -y -qq adb 2>/dev/null; then
|
|
240
|
+
ok "adb (Fire TV/Android TV) installed → $(command -v adb)"
|
|
241
|
+
else
|
|
242
|
+
warn "apt adb install failed — install Android SDK Platform Tools manually"
|
|
243
|
+
fi
|
|
244
|
+
elif command -v dnf &>/dev/null; then
|
|
245
|
+
info "Installing adb via dnf..."
|
|
246
|
+
if sudo dnf install -y -q android-tools 2>/dev/null; then
|
|
247
|
+
ok "adb (Fire TV/Android TV) installed → $(command -v adb)"
|
|
248
|
+
else
|
|
249
|
+
warn "dnf adb install failed — install Android SDK Platform Tools manually"
|
|
250
|
+
fi
|
|
251
|
+
else
|
|
252
|
+
warn "adb not found — install Android SDK Platform Tools: https://developer.android.com/studio/releases/platform-tools"
|
|
253
|
+
fi
|
|
254
|
+
fi
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
install_inputd_cli() {
|
|
258
|
+
info "Installing inputd-cli via npm..."
|
|
259
|
+
if npm install -g inputd-cli 2>&1 | grep -v "^npm warn" | grep -v "^$"; then
|
|
260
|
+
ok "inputd-cli (Fire TV input) installed"
|
|
261
|
+
else
|
|
262
|
+
warn "inputd-cli install failed — optional tool, skip if not needed"
|
|
263
|
+
fi
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
# ares-cli (LG webOS) — auto-install via npm
|
|
212
267
|
if command -v ares-setup-device &>/dev/null; then
|
|
213
|
-
ok "ares-cli
|
|
268
|
+
ok "ares-cli (LG webOS) → $(command -v ares-setup-device)"
|
|
214
269
|
else
|
|
215
|
-
warn "ares-cli not found
|
|
270
|
+
warn "ares-cli (LG webOS) not found — installing..."
|
|
271
|
+
install_ares_cli
|
|
216
272
|
fi
|
|
217
273
|
|
|
274
|
+
# sdb (Samsung Tizen) — requires Tizen Studio GUI installer, warn only
|
|
218
275
|
if command -v sdb &>/dev/null; then
|
|
219
|
-
ok "sdb
|
|
276
|
+
ok "sdb (Samsung Tizen) → $(command -v sdb)"
|
|
220
277
|
else
|
|
221
|
-
warn "sdb not found
|
|
278
|
+
warn "sdb (Samsung Tizen) not found — requires Tizen Studio: https://developer.samsung.com/smarttv"
|
|
222
279
|
fi
|
|
223
280
|
|
|
281
|
+
# adb (Fire TV / Android TV) — auto-install
|
|
224
282
|
if command -v adb &>/dev/null; then
|
|
225
|
-
ok "adb
|
|
283
|
+
ok "adb (Fire TV/Android TV) → $(command -v adb)"
|
|
226
284
|
else
|
|
227
|
-
warn "adb not found
|
|
285
|
+
warn "adb (Fire TV/Android TV) not found — installing..."
|
|
286
|
+
install_adb
|
|
228
287
|
fi
|
|
229
288
|
|
|
289
|
+
# inputd-cli (optional Fire TV input simulation) — auto-install via npm
|
|
230
290
|
if command -v inputd-cli &>/dev/null; then
|
|
231
291
|
ok "inputd-cli (Fire TV input) → $(command -v inputd-cli)"
|
|
232
292
|
else
|
|
233
|
-
warn "inputd-cli
|
|
293
|
+
warn "inputd-cli (Fire TV input) not found — installing..."
|
|
294
|
+
install_inputd_cli
|
|
234
295
|
fi
|
|
235
296
|
|
|
236
297
|
# ── Done ──────────────────────────────────────────────────────────────────────
|
|
@@ -243,3 +304,12 @@ echo ""
|
|
|
243
304
|
echo -e " ${DIM}GitHub : https://github.com/tvdev-cli/tvdev-cli${RESET}"
|
|
244
305
|
echo -e " ${DIM}npm : https://npmjs.com/package/unified-tvdevelopment-cli${RESET}"
|
|
245
306
|
echo ""
|
|
307
|
+
rc_tip=$(detect_shell_rc)
|
|
308
|
+
if ! echo ":${PATH}:" | grep -q ":${INSTALL_DIR}:"; then
|
|
309
|
+
echo -e " ${YELLOW}Tip:${RESET} to make ${BOLD}${INDIGO}${BIN}${RESET} available in every new terminal, add this to ${rc_tip}:"
|
|
310
|
+
echo ""
|
|
311
|
+
echo -e " ${DIM}export PATH=\"\$PATH:${INSTALL_DIR}\"${RESET}"
|
|
312
|
+
echo ""
|
|
313
|
+
echo -e " Then reload: ${DIM}source ${rc_tip}${RESET}"
|
|
314
|
+
echo ""
|
|
315
|
+
fi
|