eish 0.1.0

Generate self-contained installation scripts (bash, fish, PowerShell) for GitHub release binaries
# eish

Generate self-contained installation scripts for binaries published as GitHub
release assets.

`eish` inspects a release, works out which asset belongs to which platform, and
renders an `install.sh`, `install.fish` or `install.ps1` with that mapping baked
in. The generated script needs nothing but `curl`/`wget`, `tar` and `unzip` — it
never calls the GitHub API, so it keeps working when the API is rate limited or
the machine is behind a flaky network.

```sh
cargo install --path .

eish quickjs-ng/quickjs --name qjs > install.sh
eish quickjs-ng/quickjs@v0.16.2 --shell powershell --name qjs > install.ps1
eish quickjs-ng/quickjs --shell fish --proxy xget --name qjs > install.fish
```

## Usage

```text
eish <SPEC> [OPTIONS]

  <SPEC>                  owner/repo, owner/repo@tag, or a full GitHub URL
  -s, --shell <SHELL>     bash | fish | powershell     [default: bash]
      --proxy <PROXY>     github | gh-proxy | xget |
                          jsdelivr | statically       [default: github]
      --tag <TAG>         tag to install, overrides `@tag`
  -b, --binary <BINARY>   executable name inside the archive
      --name <NAME>       install this program, when the release holds several
      --target <TARGET>   target triple the script defaults to
      --dir <DIR>         default install directory     [default: ~/.ei]
      --type <TYPE>       release | file                [default: release]
  -o, --output <PATH>     write to a file instead of stdout
      --asset <FILE>      bundle an asset explicitly (repeatable)
      --list              list the supported platforms and exit
  -v, --verbose           explain what eish is doing
  -q, --quiet             errors only
```

`eish --help` lists the rest (`--ref`, `--release-json`, `--api-base`,
`--min-disk-space`).

Everything except the script goes to stderr, so stdout stays clean to pipe:

```sh
eish ahaoboy/es5 | bash                # generate and install in one step
eish ahaoboy/es5 --list                # which platforms are covered
```

A plain run is silent. `-v` explains each step and `-vv` adds detail.

### Common cases

```sh
# Pin a version
eish owner/repo@v1.2.3 > install.sh

# Install from a file you already have, with no network access
curl -LO https://github.com/quickjs-ng/quickjs/releases/latest/download/qjs-linux-x86_64
eish quickjs-ng/quickjs --name qjs > install.sh
./install.sh --file ./qjs-linux-x86_64

# Generate without touching the network at all
eish owner/repo --asset tool-linux-amd64 --asset tool-macos-arm64 > install.sh
```

A release can hold several programs, and an installer can only install one. So
`eish` refuses rather than guessing, and the error lists the programs to choose
from — best-covered first, with how many platforms each one supports:

```sh
eish quickjs-ng/quickjs --name qjs  > install.sh
eish quickjs-ng/quickjs --name qjsc > install-qjsc.sh
```

A release with a single program needs no `--name`. The match is on the program a
file is *for*, not a prefix — `qjsc`'s assets start with `qjs` but belong only to
`qjsc`. A name that appears in no asset is rejected, and so is a `--file` whose
name belongs to a program you did not choose; both messages list the names that
would work.

## The generated script

It opens with the command that produced it, so a file found in someone's CI can
be traced back and regenerated:

```sh
#!/usr/bin/env bash
#
# Installer for quickjs-ng/quickjs
#
# This file was generated by eish <VERSION> <COMMIT> and is not meant to be
# edited: the next time it is regenerated, any change made here is lost.
#
# The complete command that produced this file:
#
#   eish quickjs-ng/quickjs --proxy xget --name qjs
```

The command is the real one, taken from the arguments `eish` was called with —
including things the script itself has no trace of, such as `--release-json`. It
is safe to commit: credentials come from the environment or a helper, never from
the command line.

The version line carries the commit too (as `0.2.0 a1b2c3d`), so a script can be
traced to the exact build that wrote it rather than just the release. A
`-modified` suffix means the build came from an uncommitted tree.

At install time it detects the platform (OS, architecture and libc), looks it up
in an embedded table, falls back to a compatible build if the exact one was not
published, then downloads, unpacks, installs into `~/.ei` and updates `PATH`.

Every option has a matching environment variable — `EI_DIR`, `EI_PROXY`,
`EI_TARGET`, `EI_FILE`, … — and `--help` documents them:

```sh
EI_DIR=/usr/local/bin ./install.sh
```

## Credentials

GitHub allows 60 anonymous API requests per hour per IP. There is no `--token`
flag — a secret on the command line ends up in your shell history and in `ps`
output. Instead `eish` uses credentials you already have:

1. `GITHUB_TOKEN` or `GH_TOKEN`
2. `gh auth token` (GitHub CLI)
3. `git credential fill` (Git Credential Manager)

So either run `gh auth login`, or set `GITHUB_TOKEN` in CI. `-v` reports which
source was used.

## Supported

Formats: `.tar.gz`, `.tgz`, `.tar.xz`, `.txz`, `.tar.bz2`, `.tbz2`, `.zip`,
`.gz`, `.exe`, and bare binaries such as `qjs-linux-x86_64`. Checksums, `.msi`,
`.deb` and similar are ignored.

Targets: Linux (glibc and musl), macOS, Windows, Android and the BSDs, across
x86_64, aarch64, armv7, arm, i686, riscv64gc, loongarch64, powerpc64le and
s390x. Asset names are matched by the
[`guess-target`](https://github.com/ahaoboy/guess-target) crate, so informal
names like `jq-linux-amd64` work as well as full Rust triples.

## Library

```rust
use eish::{Client, InstallSpec, Proxy, Shell};

let mut spec = InstallSpec::new("easy-install", "easy-install")
    .with_shell(Shell::Bash)
    .with_proxy(Proxy::Xget);

let release = Client::new().release("easy-install", "easy-install", None)?;
spec.apply_release_checked(&release)?;
std::fs::write("install.sh", spec.render()?)?;
```

The `cli` feature (on by default) builds the binary and pulls in `clap`. For the
library alone: `eish = { version = "0.1", default-features = false }`.

## Development

```sh
cargo test
cargo clippy --all-targets -- -D warnings
cargo fmt --all -- --check
```

## License

MIT OR Apache-2.0.