From ec16bccd044f892f7fd1997aac977c77aa14376d Mon Sep 17 00:00:00 2001 From: Tom Riglar Date: Wed, 24 Jun 2026 12:52:40 +0100 Subject: [PATCH] fix(installer): make beta opt-in, add stable/beta channels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The install scripts resolved the version from /latest.json, which (until a stable release exists) synthesized the newest prerelease — so the default `curl … | sh` was silently installing betas. Pair the proxy's new channel support (get.devicecloud.dev now serves stable on /latest.json and prereleases on ?channel=beta) with explicit opt-ins: - DCD_BETA — request the beta channel (latest prerelease). - DCD_VERSION — already pins an exact version; documented for rollback. - Default (no opt-in) installs the latest *stable* only. When no stable release exists yet, the installer errors with guidance pointing at DCD_BETA / DCD_VERSION instead of falling back to a beta. The manifest fetch is separated from parsing so a transient network/proxy failure (curl -f non-zero) is reported differently from a channel that has no release yet (HTTP 200 with "version": null). Co-Authored-By: Claude Opus 4.8 (1M context) --- install.ps1 | 35 +++++++++++++++++++++++++++++++---- install.sh | 45 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 71 insertions(+), 9 deletions(-) diff --git a/install.ps1 b/install.ps1 index f96cbb4..07a70b7 100644 --- a/install.ps1 +++ b/install.ps1 @@ -4,7 +4,8 @@ # irm https://get.devicecloud.dev/install.ps1 | iex # # Env vars: -# DCD_VERSION Pin a specific version (default: latest) +# DCD_VERSION Pin a specific version, e.g. for rollback (default: latest stable) +# DCD_BETA Set to any value to install the latest beta/prerelease (opt-in) # DCD_INSTALL_DIR Override install location (default: $env:USERPROFILE\.dcd\bin) # DCD_DOWNLOAD_BASE Override the download host (default: https://get.devicecloud.dev) @@ -25,13 +26,39 @@ if ([Environment]::Is64BitOperatingSystem -ne $true) { $asset = 'dcd-windows-x64.exe' # --- resolve version --- +# Precedence: explicit DCD_VERSION pin > DCD_BETA opt-in > latest stable. if ($env:DCD_VERSION) { $version = $env:DCD_VERSION } else { - Write-Host 'Resolving latest version...' - $manifest = Invoke-RestMethod -Uri "$DownloadBase/latest.json" + if ($env:DCD_BETA) { + Write-Host 'Resolving latest beta version...' + $manifestUrl = "$DownloadBase/latest.json?channel=beta" + $channel = 'beta' + } else { + Write-Host 'Resolving latest version...' + $manifestUrl = "$DownloadBase/latest.json" + $channel = 'stable' + } + try { + $manifest = Invoke-RestMethod -Uri $manifestUrl + } catch { + throw "Could not reach $manifestUrl" + } + # A null version means the channel has no release yet (HTTP 200), as opposed + # to a transient failure (which throws above). Stable is the default and beta + # is strictly opt-in, so refuse to silently fall back to a prerelease. $version = $manifest.version - if (-not $version) { throw "Could not resolve latest version from $DownloadBase/latest.json" } + if (-not $version) { + if ($channel -eq 'stable') { + throw @" +No stable dcd release is available yet. + Install the latest beta: `$env:DCD_BETA=1; irm '$DownloadBase/install.ps1' | iex + Or pin a version: `$env:DCD_VERSION='5.0.0-beta.1'; irm '$DownloadBase/install.ps1' | iex +"@ + } else { + throw "No beta release is available yet from $manifestUrl" + } + } } $url = "$DownloadBase/download/$version/$asset" diff --git a/install.sh b/install.sh index ac94e43..4d25dc8 100755 --- a/install.sh +++ b/install.sh @@ -5,7 +5,8 @@ # curl -fsSL https://get.devicecloud.dev/install.sh | sh # # Env vars: -# DCD_VERSION Pin a specific version (default: latest) +# DCD_VERSION Pin a specific version, e.g. for rollback (default: latest stable) +# DCD_BETA Set to any value to install the latest beta/prerelease (opt-in) # DCD_INSTALL_DIR Override install location (default: $HOME/.dcd/bin) # DCD_DOWNLOAD_BASE Override the download host (default: https://get.devicecloud.dev) # @@ -23,6 +24,17 @@ info() { printf '%s\n' "$1" } +# Stable is the default channel and beta is strictly opt-in, so when no stable +# release exists yet (only prereleases published) we refuse to silently install a +# beta and instead point the user at the two explicit opt-ins. $DOWNLOAD_BASE is +# echoed so a custom host shows the right command. +no_stable_release_err() { + printf 'error: No stable dcd release is available yet.\n' >&2 + printf ' Install the latest beta: curl -fsSL %s/install.sh | DCD_BETA=1 sh\n' "$DOWNLOAD_BASE" >&2 + printf ' Or pin a version: curl -fsSL %s/install.sh | DCD_VERSION=5.0.0-beta.1 sh\n' "$DOWNLOAD_BASE" >&2 + exit 1 +} + # Find a dcd on PATH other than the one we just installed — usually a leftover # `npm install -g @devicecloud.dev/dcd` that can shadow this binary. Runs in a # subshell so the temporary IFS change never leaks back to the caller. @@ -121,17 +133,40 @@ main() { asset="dcd-${os_id}-${arch_id}" # --- resolve version --- + # Precedence: explicit DCD_VERSION pin > DCD_BETA opt-in > latest stable. if [ -n "${DCD_VERSION:-}" ]; then version="$DCD_VERSION" else - info "Resolving latest version..." - # /latest.json returns { "version": "5.1.0", ... } + if [ -n "${DCD_BETA:-}" ]; then + channel=beta + manifest_url="$DOWNLOAD_BASE/latest.json?channel=beta" + info "Resolving latest beta version..." + else + channel=stable + manifest_url="$DOWNLOAD_BASE/latest.json" + info "Resolving latest version..." + fi + + # Fetch the manifest separately from parsing so we can tell a transient + # network/proxy failure (curl -f returns non-zero → empty $manifest) apart + # from a channel that simply has no release yet (HTTP 200 with + # "version": null → $manifest non-empty but $version empty). + manifest=$(curl -fsSL "$manifest_url") || manifest="" + [ -z "$manifest" ] && err "Could not reach $manifest_url" + # /latest.json returns { "version": "5.1.0", ... }; a null version is unquoted + # and so won't match this quoted-string pattern. version=$( - curl -fsSL "$DOWNLOAD_BASE/latest.json" \ + printf '%s' "$manifest" \ | sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \ | head -n1 ) - [ -z "$version" ] && err "Could not resolve latest version from $DOWNLOAD_BASE/latest.json" + if [ -z "$version" ]; then + if [ "$channel" = stable ]; then + no_stable_release_err + else + err "No beta release is available yet from $manifest_url" + fi + fi fi url="$DOWNLOAD_BASE/download/${version}/${asset}"