mirror of
https://github.com/0xrsydn/idx-cli.git
synced 2026-11-07 09:45:36 +00:00
feat: add curl | sh install script
- install.sh: POSIX sh installer that detects OS/arch (incl. Rosetta), resolves the newest vX.Y.Z release via the GitHub API (skipping ownership-snapshot-current), verifies SHA256SUMS, sanity-runs the binary, and installs atomically into ~/.local/bin without sudo. Supports --version/--dir and IDX_* env overrides; prints a PATH hint, a NixOS flake suggestion, and helper-tool install hints instead of calling package managers. - scripts/install-sh-test.sh: fixture tests against a local fake release server (latest, pinned, piped, checksum mismatch, missing release, unsupported OS, PATH hint, reinstall). - ci.yml: install-script job (ShellCheck, sh + dash on Linux, sh on macOS). - release.yml: attach install.sh to each release. - README + docs/INSTALL.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017a5Cv5Z1NzdYKgKnTiAF4E
This commit is contained in:
parent
52cf73beb9
commit
a142a3718f
6 changed files with 551 additions and 1 deletions
24
.github/workflows/ci.yml
vendored
24
.github/workflows/ci.yml
vendored
|
|
@ -56,3 +56,27 @@ jobs:
|
||||||
|
|
||||||
- name: Run default app
|
- name: Run default app
|
||||||
run: nix run .#default -- version
|
run: nix run .#default -- version
|
||||||
|
|
||||||
|
install-script:
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, macos-14]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: ShellCheck
|
||||||
|
if: runner.os == 'Linux'
|
||||||
|
run: |
|
||||||
|
shellcheck -s sh install.sh
|
||||||
|
shellcheck scripts/install-sh-test.sh
|
||||||
|
|
||||||
|
- name: Test install.sh with sh
|
||||||
|
run: scripts/install-sh-test.sh
|
||||||
|
|
||||||
|
- name: Test install.sh with dash
|
||||||
|
if: runner.os == 'Linux'
|
||||||
|
run: INSTALL_SH_SHELL=dash scripts/install-sh-test.sh
|
||||||
|
|
|
||||||
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
|
|
@ -16,6 +16,7 @@ on:
|
||||||
- "Cargo.lock"
|
- "Cargo.lock"
|
||||||
- "package.json"
|
- "package.json"
|
||||||
- "scripts/install.js"
|
- "scripts/install.js"
|
||||||
|
- "install.sh"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|
@ -88,6 +89,14 @@ jobs:
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
- name: Check out install script
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }}
|
||||||
|
sparse-checkout: install.sh
|
||||||
|
sparse-checkout-cone-mode: false
|
||||||
|
path: src
|
||||||
|
|
||||||
- name: Download target artifacts
|
- name: Download target artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
|
@ -107,3 +116,4 @@ jobs:
|
||||||
idx-darwin-arm64
|
idx-darwin-arm64
|
||||||
idx-darwin-x64
|
idx-darwin-x64
|
||||||
SHA256SUMS
|
SHA256SUMS
|
||||||
|
src/install.sh
|
||||||
|
|
|
||||||
13
README.md
13
README.md
|
|
@ -4,6 +4,17 @@ CLI tool for Indonesian stock market (IDX) analysis, built in Rust for humans an
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
### Install script (Linux, macOS)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/0xrsydn/idx-cli/main/install.sh | sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Downloads the prebuilt `idx` binary for your platform from GitHub Releases,
|
||||||
|
verifies it against the release `SHA256SUMS`, and installs it into
|
||||||
|
`~/.local/bin` without sudo. Pin a version or change the directory with
|
||||||
|
`sh -s -- --version v0.2.3 --dir ~/bin`. See [docs/INSTALL.md](docs/INSTALL.md).
|
||||||
|
|
||||||
### Cargo
|
### Cargo
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -12,7 +23,7 @@ cargo install idx-cli
|
||||||
|
|
||||||
This installs the `idx` binary. `idx-cli` currently requires Rust `1.85+`.
|
This installs the `idx` binary. `idx-cli` currently requires Rust `1.85+`.
|
||||||
|
|
||||||
If you use the Cargo install path directly, some commands also require helper tools at runtime:
|
If you use the install script or the Cargo install path directly, some commands also require helper tools at runtime:
|
||||||
|
|
||||||
- Yahoo-authenticated flows require a `curl_chrome*` binary from `curl-impersonate-chrome` in `PATH`, or `IDX_CURL_IMPERSONATE_BIN` pointing at that binary.
|
- Yahoo-authenticated flows require a `curl_chrome*` binary from `curl-impersonate-chrome` in `PATH`, or `IDX_CURL_IMPERSONATE_BIN` pointing at that binary.
|
||||||
- Ownership PDF import requires `mutool` from MuPDF in `PATH`.
|
- Ownership PDF import requires `mutool` from MuPDF in `PATH`.
|
||||||
|
|
|
||||||
74
docs/INSTALL.md
Normal file
74
docs/INSTALL.md
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
# Install script
|
||||||
|
|
||||||
|
`install.sh` installs a prebuilt `idx` binary from GitHub Releases. It is the
|
||||||
|
fastest path on Linux and macOS and needs no Rust toolchain, Node, or sudo.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/0xrsydn/idx-cli/main/install.sh | sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Each release also carries a copy of the script as a release asset, so a
|
||||||
|
versioned URL stays stable:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://github.com/0xrsydn/idx-cli/releases/download/v0.2.4/install.sh | sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
|
||||||
|
1. Detects the OS and CPU with `uname` (Rosetta shells on Apple Silicon get
|
||||||
|
the native arm64 build).
|
||||||
|
2. Resolves the newest `vX.Y.Z` release through the GitHub API. It does not use
|
||||||
|
`/releases/latest`, because this repo also publishes non-app releases such
|
||||||
|
as `ownership-snapshot-current`.
|
||||||
|
3. Downloads the binary and `SHA256SUMS`, and refuses to install on a checksum
|
||||||
|
mismatch.
|
||||||
|
4. Runs `idx version` from the download to confirm it executes here.
|
||||||
|
5. Moves the binary into the install directory in one step, replacing any
|
||||||
|
previous install.
|
||||||
|
6. Prints a PATH hint when needed, plus notes about optional helper tools.
|
||||||
|
|
||||||
|
It never edits shell profiles and never calls a system package manager. On
|
||||||
|
NixOS it suggests the flake instead, and when `mutool` or `curl_chrome*` are
|
||||||
|
missing it prints the matching package-manager command so you can install
|
||||||
|
them yourself.
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
| Flag | Environment | Default |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `--version <tag>` | `IDX_VERSION` | newest `vX.Y.Z` release |
|
||||||
|
| `--dir <path>` | `IDX_INSTALL_DIR` | `$HOME/.local/bin` |
|
||||||
|
| | `IDX_GITHUB_URL` | `https://github.com` (for mirrors) |
|
||||||
|
| | `IDX_GITHUB_API` | `https://api.github.com` (for mirrors) |
|
||||||
|
|
||||||
|
Pass flags through the pipe with `sh -s --`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/0xrsydn/idx-cli/main/install.sh | sh -s -- --version v0.2.3 --dir ~/bin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Supported platforms
|
||||||
|
|
||||||
|
Same assets as the npm package: `idx-linux-x64`, `idx-linux-arm64` (static
|
||||||
|
musl builds), `idx-darwin-arm64`, `idx-darwin-x64`. Windows users can run the
|
||||||
|
script inside WSL.
|
||||||
|
|
||||||
|
## Uninstall
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rm ~/.local/bin/idx
|
||||||
|
```
|
||||||
|
|
||||||
|
Config and cache live in `~/.config/idx/` and `~/.cache/idx/`.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
`scripts/install-sh-test.sh` runs the script against a local fake release
|
||||||
|
server, so it needs no network. CI runs it with `sh` and `dash` on Linux and
|
||||||
|
with `sh` on macOS, plus ShellCheck.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scripts/install-sh-test.sh
|
||||||
|
INSTALL_SH_SHELL=dash scripts/install-sh-test.sh
|
||||||
|
```
|
||||||
246
install.sh
Executable file
246
install.sh
Executable file
|
|
@ -0,0 +1,246 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# idx-cli installer: downloads a prebuilt `idx` binary from GitHub Releases,
|
||||||
|
# verifies it against the release SHA256SUMS, and installs it without sudo.
|
||||||
|
#
|
||||||
|
# curl -fsSL https://raw.githubusercontent.com/0xrsydn/idx-cli/main/install.sh | sh
|
||||||
|
# curl -fsSL https://raw.githubusercontent.com/0xrsydn/idx-cli/main/install.sh | sh -s -- --version v0.2.3
|
||||||
|
#
|
||||||
|
# Environment (flags take precedence):
|
||||||
|
# IDX_VERSION release tag to install (default: latest vX.Y.Z release)
|
||||||
|
# IDX_INSTALL_DIR install directory (default: $HOME/.local/bin)
|
||||||
|
# IDX_GITHUB_URL GitHub base URL, for mirrors (default: https://github.com)
|
||||||
|
# IDX_GITHUB_API GitHub API base URL, for mirrors (default: https://api.github.com)
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
REPO="0xrsydn/idx-cli"
|
||||||
|
BIN_NAME="idx"
|
||||||
|
|
||||||
|
say() {
|
||||||
|
printf 'idx-install: %s\n' "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
err() {
|
||||||
|
printf 'idx-install: error: %s\n' "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Install the idx CLI from GitHub Releases.
|
||||||
|
|
||||||
|
Usage: install.sh [--version <tag>] [--dir <path>]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--version <tag> Release tag to install, for example v0.2.3 (default: latest)
|
||||||
|
--dir <path> Install directory (default: $HOME/.local/bin)
|
||||||
|
-h, --help Show this help
|
||||||
|
|
||||||
|
Environment: IDX_VERSION, IDX_INSTALL_DIR, IDX_GITHUB_URL, IDX_GITHUB_API
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
has() {
|
||||||
|
command -v "$1" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# fetch <url> <output-file>
|
||||||
|
fetch() {
|
||||||
|
if has curl; then
|
||||||
|
curl --fail --silent --show-error --location --retry 3 --output "$2" "$1"
|
||||||
|
elif has wget; then
|
||||||
|
wget --quiet --tries=3 --output-document="$2" "$1"
|
||||||
|
else
|
||||||
|
err "need curl or wget to download files"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
sha256_of() {
|
||||||
|
if has sha256sum; then
|
||||||
|
sha256sum "$1" | cut -d ' ' -f 1
|
||||||
|
elif has shasum; then
|
||||||
|
shasum -a 256 "$1" | cut -d ' ' -f 1
|
||||||
|
else
|
||||||
|
err "need sha256sum or shasum to verify the download"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
detect_asset() {
|
||||||
|
os="$(uname -s)"
|
||||||
|
arch="$(uname -m)"
|
||||||
|
|
||||||
|
case "$os" in
|
||||||
|
Linux) os=linux ;;
|
||||||
|
Darwin) os=darwin ;;
|
||||||
|
*) err "unsupported OS: $os (supported: Linux, macOS; on Windows use WSL)" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "$arch" in
|
||||||
|
x86_64 | amd64) arch=x64 ;;
|
||||||
|
aarch64 | arm64) arch=arm64 ;;
|
||||||
|
*) err "unsupported CPU architecture: $arch (supported: x86_64, arm64)" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# An x64 shell under Rosetta on Apple Silicon should still get the native build.
|
||||||
|
if [ "$os" = darwin ] && [ "$arch" = x64 ] &&
|
||||||
|
[ "$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0)" = 1 ]; then
|
||||||
|
arch=arm64
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "idx-${os}-${arch}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Pick the newest vX.Y.Z tag. The repo also carries non-app releases such as
|
||||||
|
# ownership-snapshot-current, so /releases/latest is not reliable here.
|
||||||
|
resolve_latest_version() {
|
||||||
|
releases="$tmp_dir/releases.json"
|
||||||
|
fetch "$api_url/repos/$REPO/releases?per_page=30" "$releases" ||
|
||||||
|
err "could not list releases; pass --version <tag> to skip the lookup"
|
||||||
|
|
||||||
|
tag="$(
|
||||||
|
sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\(v[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\)".*/\1/p' "$releases" |
|
||||||
|
head -n 1
|
||||||
|
)"
|
||||||
|
[ -n "$tag" ] || err "no vX.Y.Z release found for $REPO"
|
||||||
|
echo "$tag"
|
||||||
|
}
|
||||||
|
|
||||||
|
os_release_id() {
|
||||||
|
if [ -r /etc/os-release ]; then
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
(. /etc/os-release && echo "${ID:-}")
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
mutool_hint() {
|
||||||
|
if [ "$(uname -s)" = Darwin ]; then
|
||||||
|
echo "brew install mupdf"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
case "$(os_release_id)" in
|
||||||
|
arch | manjaro | endeavouros) echo "sudo pacman -S mupdf-tools" ;;
|
||||||
|
debian | ubuntu | linuxmint | pop) echo "sudo apt install mupdf-tools" ;;
|
||||||
|
fedora) echo "sudo dnf install mupdf" ;;
|
||||||
|
alpine) echo "sudo apk add mupdf-tools" ;;
|
||||||
|
opensuse* | sles) echo "sudo zypper install mupdf" ;;
|
||||||
|
*) echo "install MuPDF's mutool with your package manager" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
print_notes() {
|
||||||
|
if [ -e /etc/NIXOS ] || [ "$(os_release_id)" = nixos ]; then
|
||||||
|
say "NixOS detected: the flake wraps idx with its helper tools and may suit you better:"
|
||||||
|
say " nix profile install github:$REPO#default"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! has mutool; then
|
||||||
|
say "optional: 'idx ownership import' needs mutool from MuPDF: $(mutool_hint)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
found_curl_impersonate=
|
||||||
|
for candidate in curl_chrome142 curl_chrome136 curl_chrome133a curl_chrome131 curl_chrome124 curl_chrome120 curl_chrome116; do
|
||||||
|
if has "$candidate"; then
|
||||||
|
found_curl_impersonate=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ -z "$found_curl_impersonate" ] && [ -z "${IDX_CURL_IMPERSONATE_BIN:-}" ]; then
|
||||||
|
say "optional: Yahoo-backed commands need a curl_chrome* binary from curl-impersonate"
|
||||||
|
say " (https://github.com/lexiforest/curl-impersonate) or IDX_CURL_IMPERSONATE_BIN"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
print_path_hint() {
|
||||||
|
case ":$PATH:" in
|
||||||
|
*":$install_dir:"*) return ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "${SHELL:-}" in
|
||||||
|
*/fish) line="fish_add_path $install_dir" ;;
|
||||||
|
*/zsh) line="echo 'export PATH=\"$install_dir:\$PATH\"' >> ~/.zshrc" ;;
|
||||||
|
*) line="echo 'export PATH=\"$install_dir:\$PATH\"' >> ~/.bashrc" ;;
|
||||||
|
esac
|
||||||
|
say "$install_dir is not on your PATH. Add it with:"
|
||||||
|
say " $line"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
version="${IDX_VERSION:-}"
|
||||||
|
install_dir="${IDX_INSTALL_DIR:-${HOME:?HOME is not set}/.local/bin}"
|
||||||
|
github_url="${IDX_GITHUB_URL:-https://github.com}"
|
||||||
|
api_url="${IDX_GITHUB_API:-https://api.github.com}"
|
||||||
|
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--version)
|
||||||
|
[ $# -ge 2 ] || err "--version needs a value"
|
||||||
|
version="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--dir)
|
||||||
|
[ $# -ge 2 ] || err "--dir needs a value"
|
||||||
|
install_dir="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h | --help)
|
||||||
|
usage
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
*) err "unknown argument: $1 (see --help)" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
asset="$(detect_asset)"
|
||||||
|
|
||||||
|
tmp_dir="$(mktemp -d 2>/dev/null || mktemp -d -t idx-install)"
|
||||||
|
trap 'rm -rf "$tmp_dir"' EXIT
|
||||||
|
trap 'exit 130' INT
|
||||||
|
trap 'exit 143' TERM
|
||||||
|
|
||||||
|
if [ -z "$version" ]; then
|
||||||
|
version="$(resolve_latest_version)"
|
||||||
|
fi
|
||||||
|
case "$version" in
|
||||||
|
v*) ;;
|
||||||
|
*) version="v$version" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
download_base="$github_url/$REPO/releases/download/$version"
|
||||||
|
say "installing idx $version ($asset) into $install_dir"
|
||||||
|
|
||||||
|
fetch "$download_base/$asset" "$tmp_dir/$asset" ||
|
||||||
|
err "download failed: $download_base/$asset (does release $version have binaries?)"
|
||||||
|
fetch "$download_base/SHA256SUMS" "$tmp_dir/SHA256SUMS" ||
|
||||||
|
err "download failed: $download_base/SHA256SUMS"
|
||||||
|
|
||||||
|
expected="$(awk -v name="$asset" '$2 == name || $2 == "*" name { print $1; exit }' "$tmp_dir/SHA256SUMS")"
|
||||||
|
[ -n "$expected" ] || err "SHA256SUMS for $version has no entry for $asset"
|
||||||
|
actual="$(sha256_of "$tmp_dir/$asset")"
|
||||||
|
[ "$actual" = "$expected" ] ||
|
||||||
|
err "checksum mismatch for $asset: expected $expected, got $actual"
|
||||||
|
|
||||||
|
chmod 0755 "$tmp_dir/$asset"
|
||||||
|
"$tmp_dir/$asset" version >/dev/null 2>&1 ||
|
||||||
|
err "downloaded binary does not run on this system"
|
||||||
|
|
||||||
|
mkdir -p "$install_dir" || err "cannot create $install_dir"
|
||||||
|
staged="$install_dir/.$BIN_NAME.tmp.$$"
|
||||||
|
if ! { cp "$tmp_dir/$asset" "$staged" && chmod 0755 "$staged" && mv -f "$staged" "$install_dir/$BIN_NAME"; }; then
|
||||||
|
rm -f "$staged"
|
||||||
|
err "cannot write $install_dir/$BIN_NAME"
|
||||||
|
fi
|
||||||
|
|
||||||
|
say "installed $install_dir/$BIN_NAME"
|
||||||
|
|
||||||
|
existing="$(command -v "$BIN_NAME" 2>/dev/null || true)"
|
||||||
|
if [ -n "$existing" ] && [ "$existing" != "$install_dir/$BIN_NAME" ]; then
|
||||||
|
say "warning: another idx at $existing comes first on your PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_path_hint
|
||||||
|
print_notes
|
||||||
|
say "done; run 'idx --help' to get started"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Everything runs from main so a truncated download of this script does nothing.
|
||||||
|
main "$@"
|
||||||
185
scripts/install-sh-test.sh
Executable file
185
scripts/install-sh-test.sh
Executable file
|
|
@ -0,0 +1,185 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Tests install.sh against a local fake GitHub release server (no live network).
|
||||||
|
# scripts/install-sh-test.sh # runs install.sh with sh
|
||||||
|
# INSTALL_SH_SHELL=dash scripts/install-sh-test.sh
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
installer="${repo_root}/install.sh"
|
||||||
|
test_shell="${INSTALL_SH_SHELL:-sh}"
|
||||||
|
work="$(mktemp -d "${TMPDIR:-/tmp}/idx-install-test.XXXXXX")"
|
||||||
|
server_pid=
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
if [[ -n "$server_pid" ]]; then
|
||||||
|
kill "$server_pid" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
rm -rf "$work"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
failures=0
|
||||||
|
pass() { echo "ok - $1"; }
|
||||||
|
fail() {
|
||||||
|
echo "FAIL - $1" >&2
|
||||||
|
failures=$((failures + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
sha256() {
|
||||||
|
if command -v sha256sum >/dev/null 2>&1; then
|
||||||
|
sha256sum "$1" | cut -d ' ' -f 1
|
||||||
|
else
|
||||||
|
shasum -a 256 "$1" | cut -d ' ' -f 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$(uname -s)" in
|
||||||
|
Linux) os=linux ;;
|
||||||
|
Darwin) os=darwin ;;
|
||||||
|
*) echo "unsupported test host" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$(uname -m)" in
|
||||||
|
x86_64 | amd64) arch=x64 ;;
|
||||||
|
*) arch=arm64 ;;
|
||||||
|
esac
|
||||||
|
if [[ "$os" == darwin && "$arch" == x64 && "$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0)" == 1 ]]; then
|
||||||
|
arch=arm64
|
||||||
|
fi
|
||||||
|
asset="idx-${os}-${arch}"
|
||||||
|
|
||||||
|
# Fake release layout served by python's http.server:
|
||||||
|
# /api/repos/0xrsydn/idx-cli/releases (query string is ignored)
|
||||||
|
# /gh/0xrsydn/idx-cli/releases/download/<tag>/...
|
||||||
|
root="${work}/srv"
|
||||||
|
api_dir="${root}/api/repos/0xrsydn/idx-cli"
|
||||||
|
mkdir -p "$api_dir"
|
||||||
|
cat >"${api_dir}/releases" <<'EOF'
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"tag_name": "ownership-snapshot-current",
|
||||||
|
"name": "Ownership Snapshot Current"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag_name": "v0.2.4",
|
||||||
|
"name": "v0.2.4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag_name": "v0.2.3",
|
||||||
|
"name": "v0.2.3"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
EOF
|
||||||
|
|
||||||
|
make_release() {
|
||||||
|
local tag="$1" dir="${root}/gh/0xrsydn/idx-cli/releases/download/$1"
|
||||||
|
mkdir -p "$dir"
|
||||||
|
printf '#!/bin/sh\necho "idx fake %s"\n' "$tag" >"${dir}/${asset}"
|
||||||
|
chmod +x "${dir}/${asset}"
|
||||||
|
(cd "$dir" && echo "$(sha256 "$asset") ${asset}" >SHA256SUMS)
|
||||||
|
}
|
||||||
|
make_release v0.2.3
|
||||||
|
make_release v0.2.4
|
||||||
|
|
||||||
|
# v0.0.9: checksum does not match the binary.
|
||||||
|
bad="${root}/gh/0xrsydn/idx-cli/releases/download/v0.0.9"
|
||||||
|
mkdir -p "$bad"
|
||||||
|
printf '#!/bin/sh\necho tampered\n' >"${bad}/${asset}"
|
||||||
|
printf '%064d %s\n' 0 "$asset" >"${bad}/SHA256SUMS"
|
||||||
|
|
||||||
|
port="$(python3 -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1", 0)); print(s.getsockname()[1])')"
|
||||||
|
python3 -m http.server "$port" --bind 127.0.0.1 --directory "$root" >/dev/null 2>&1 &
|
||||||
|
server_pid=$!
|
||||||
|
for _ in $(seq 50); do
|
||||||
|
curl -fs "http://127.0.0.1:${port}/api/repos/0xrsydn/idx-cli/releases" >/dev/null 2>&1 && break
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
|
||||||
|
run_installer() {
|
||||||
|
env IDX_GITHUB_URL="http://127.0.0.1:${port}/gh" \
|
||||||
|
IDX_GITHUB_API="http://127.0.0.1:${port}/api" \
|
||||||
|
HOME="${work}/home" \
|
||||||
|
"$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 1. latest resolves to the newest vX.Y.Z tag, skipping non-app releases.
|
||||||
|
dir="${work}/latest"
|
||||||
|
if run_installer "$test_shell" "$installer" --dir "$dir" >"${work}/out1" 2>&1 &&
|
||||||
|
[[ "$("${dir}/idx")" == "idx fake v0.2.4" ]]; then
|
||||||
|
pass "installs latest vX.Y.Z release"
|
||||||
|
else
|
||||||
|
fail "installs latest vX.Y.Z release"; cat "${work}/out1" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. pinned version via env, without the leading v.
|
||||||
|
dir="${work}/pinned"
|
||||||
|
if run_installer env IDX_VERSION=0.2.3 IDX_INSTALL_DIR="$dir" "$test_shell" "$installer" >"${work}/out2" 2>&1 &&
|
||||||
|
[[ "$("${dir}/idx")" == "idx fake v0.2.3" ]]; then
|
||||||
|
pass "installs pinned version from IDX_VERSION"
|
||||||
|
else
|
||||||
|
fail "installs pinned version from IDX_VERSION"; cat "${work}/out2" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. piped through stdin like `curl ... | sh -s -- --version v0.2.3`.
|
||||||
|
dir="${work}/piped"
|
||||||
|
if run_installer "$test_shell" -s -- --version v0.2.3 --dir "$dir" <"$installer" >"${work}/out3" 2>&1 &&
|
||||||
|
[[ "$("${dir}/idx")" == "idx fake v0.2.3" ]]; then
|
||||||
|
pass "works when piped to sh -s --"
|
||||||
|
else
|
||||||
|
fail "works when piped to sh -s --"; cat "${work}/out3" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. checksum mismatch is rejected and nothing is installed.
|
||||||
|
dir="${work}/bad"
|
||||||
|
if run_installer "$test_shell" "$installer" --version v0.0.9 --dir "$dir" >"${work}/out4" 2>&1; then
|
||||||
|
fail "rejects checksum mismatch (installer succeeded)"
|
||||||
|
elif grep -q "checksum mismatch" "${work}/out4" && [[ ! -e "${dir}/idx" ]]; then
|
||||||
|
pass "rejects checksum mismatch"
|
||||||
|
else
|
||||||
|
fail "rejects checksum mismatch"; cat "${work}/out4" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 5. missing release reports a clear download error.
|
||||||
|
if run_installer "$test_shell" "$installer" --version v9.9.9 --dir "${work}/missing" >"${work}/out5" 2>&1; then
|
||||||
|
fail "reports missing release (installer succeeded)"
|
||||||
|
elif grep -q "download failed" "${work}/out5"; then
|
||||||
|
pass "reports missing release"
|
||||||
|
else
|
||||||
|
fail "reports missing release"; cat "${work}/out5" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 6. unsupported platform, via a uname shim on PATH.
|
||||||
|
shim="${work}/shim"
|
||||||
|
mkdir -p "$shim"
|
||||||
|
# shellcheck disable=SC2016 # literal $1 belongs to the shim
|
||||||
|
printf '#!/bin/sh\ncase "$1" in -s) echo FreeBSD ;; *) echo amd64 ;; esac\n' >"${shim}/uname"
|
||||||
|
chmod +x "${shim}/uname"
|
||||||
|
if run_installer env PATH="${shim}:${PATH}" "$test_shell" "$installer" --dir "${work}/bsd" >"${work}/out6" 2>&1; then
|
||||||
|
fail "rejects unsupported OS (installer succeeded)"
|
||||||
|
elif grep -q "unsupported OS: FreeBSD" "${work}/out6"; then
|
||||||
|
pass "rejects unsupported OS"
|
||||||
|
else
|
||||||
|
fail "rejects unsupported OS"; cat "${work}/out6" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 7. PATH hint when the install dir is not on PATH.
|
||||||
|
if grep -q "is not on your PATH" "${work}/out1"; then
|
||||||
|
pass "prints PATH hint"
|
||||||
|
else
|
||||||
|
fail "prints PATH hint"; cat "${work}/out1" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 8. reinstall over an existing binary replaces it.
|
||||||
|
dir="${work}/latest"
|
||||||
|
if run_installer "$test_shell" "$installer" --version v0.2.3 --dir "$dir" >"${work}/out8" 2>&1 &&
|
||||||
|
[[ "$("${dir}/idx")" == "idx fake v0.2.3" ]] &&
|
||||||
|
[[ -z "$(find "$dir" -name '.idx.tmp.*')" ]]; then
|
||||||
|
pass "replaces an existing install cleanly"
|
||||||
|
else
|
||||||
|
fail "replaces an existing install cleanly"; cat "${work}/out8" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ((failures > 0)); then
|
||||||
|
echo "install.sh tests: ${failures} failure(s) with ${test_shell}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "install.sh tests: all passed with ${test_shell}"
|
||||||
Loading…
Add table
Add a link
Reference in a new issue