|
| 1 | +# installation Specification |
| 2 | + |
| 3 | +## Purpose |
| 4 | +TBD - created by archiving change installable-cli. Update Purpose after archive. |
| 5 | +## Requirements |
| 6 | +### Requirement: A one-line installer SHALL drop a working `diting` onto a user's macOS without requiring Python, uv, or Xcode |
| 7 | +A first-time end-user SHALL be able to install diting by running a |
| 8 | +single shell command: |
| 9 | + |
| 10 | +```bash |
| 11 | +curl -fsSL https://raw.githubusercontent.com/chenchaoyi/diting/main/install.sh | bash |
| 12 | +``` |
| 13 | + |
| 14 | +The installer MUST NOT require the user to have Python, `uv`, |
| 15 | +`brew`, or Xcode Command Line Tools installed beforehand. It MUST |
| 16 | +NOT require sudo. On completion, `diting` SHALL be on the user's |
| 17 | +PATH (directly, or via a hint the script prints to stderr) and the |
| 18 | +Swift helper bundle SHALL be in place at |
| 19 | +`~/Library/Application Support/diting/diting-tianer.app`. |
| 20 | + |
| 21 | +#### Scenario: First-time install on Apple Silicon |
| 22 | +- **WHEN** a user on a macOS 14+ arm64 Mac runs the curl-bash one-liner with no diting toolchain previously installed |
| 23 | +- **THEN** `~/.local/bin/diting` exists and is executable; `~/Library/Application Support/diting/diting-tianer.app` exists; `diting --help` exits 0 after the user adds `~/.local/bin` to PATH (or follows the printed hint to do so) |
| 24 | + |
| 25 | +#### Scenario: First-time install on Intel |
| 26 | +- **WHEN** a user on a macOS 13+ x86_64 Mac runs the same one-liner |
| 27 | +- **THEN** the installer downloads the `darwin-x86_64` tarball variant (not arm64), and the rest of the flow proceeds identically |
| 28 | + |
| 29 | +#### Scenario: Repeat install / upgrade |
| 30 | +- **WHEN** the user runs the installer a second time on the same machine |
| 31 | +- **THEN** the installer downloads the current latest release, replaces `~/.local/share/diting/`, refreshes the helper at `~/Library/Application Support/diting/`, and prints "upgraded from <old> to <new>" |
| 32 | + |
| 33 | +### Requirement: The installer SHALL refuse to run on non-macOS hosts with a clear error |
| 34 | +The installer MUST detect non-Darwin / non-arm64-or-x86_64 hosts |
| 35 | +and exit with a non-zero status and a one-line error message |
| 36 | +naming the unsupported OS / arch. It MUST NOT partially install, |
| 37 | +leave files behind, or attempt to "best-effort" the install. |
| 38 | + |
| 39 | +#### Scenario: Run on Linux |
| 40 | +- **WHEN** the user pipes the install.sh through bash on a Linux machine |
| 41 | +- **THEN** the script exits non-zero with stderr containing "diting is macOS-only" and no files are written outside `/tmp` |
| 42 | + |
| 43 | +#### Scenario: Run on Apple Silicon iPad-on-Mac emulation or other unsupported uname |
| 44 | +- **WHEN** `uname -s` is not `Darwin` |
| 45 | +- **THEN** the script exits non-zero with a clear error and writes no files |
| 46 | + |
| 47 | +### Requirement: The installer SHALL fetch the matching release tarball from GitHub Releases and verify its SHA256 |
| 48 | +The installer SHALL download the platform-matching tarball from the project's GitHub Releases and SHALL refuse to proceed when the SHA256 does not match the published hash. For each supported arch (`darwin-arm64`, `darwin-x86_64`) a GitHub Release SHALL provide: |
| 49 | + |
| 50 | +- `diting-X.Y.Z-darwin-{arch}.tar.gz` — the platform tarball |
| 51 | +- `SHASUMS256.txt` — a sibling asset listing SHA256 hashes for |
| 52 | + each tarball, one per line, in `sha256sum`-compatible format |
| 53 | + |
| 54 | +The installer SHALL fetch the latest release via the GitHub API |
| 55 | +(`api.github.com/repos/chenchaoyi/diting/releases/latest`), |
| 56 | +download the right tarball, compute its SHA256, and abort if the |
| 57 | +hash does not match the `SHASUMS256.txt` entry. |
| 58 | + |
| 59 | +#### Scenario: Tarball matches SHASUMS256.txt |
| 60 | +- **WHEN** the download completes and the computed SHA256 matches the entry in SHASUMS256.txt |
| 61 | +- **THEN** the installer proceeds to extract and install |
| 62 | + |
| 63 | +#### Scenario: Tarball SHA256 mismatch |
| 64 | +- **WHEN** the computed SHA256 does NOT match the recorded entry |
| 65 | +- **THEN** the installer aborts with a non-zero exit and stderr message naming the expected vs actual hash; no files are extracted |
| 66 | + |
| 67 | +#### Scenario: Tag-locked install via DITING_VERSION env var |
| 68 | +- **WHEN** the user runs `DITING_VERSION=v0.10.0 curl … | bash` |
| 69 | +- **THEN** the installer fetches `v0.10.0` specifically rather than the latest tag |
| 70 | + |
| 71 | +### Requirement: The installer SHALL extract the tarball under `~/.local/share/diting/` and symlink the binary at `~/.local/bin/diting` |
| 72 | +The tarball layout MUST be: |
| 73 | + |
| 74 | +``` |
| 75 | +diting-X.Y.Z/ |
| 76 | +├── bin/diting |
| 77 | +└── share/diting-tianer.app/ |
| 78 | + └── Contents/ |
| 79 | + ├── Info.plist |
| 80 | + └── MacOS/diting-tianer |
| 81 | +``` |
| 82 | + |
| 83 | +The installer SHALL extract into `~/.local/share/diting/` (creating |
| 84 | +parents as needed), atomically replace any prior install, and |
| 85 | +symlink `~/.local/bin/diting` to `~/.local/share/diting/bin/diting`. |
| 86 | +The installer MUST NOT use sudo. |
| 87 | + |
| 88 | +#### Scenario: Fresh install with no prior `~/.local/share/diting/` |
| 89 | +- **WHEN** the user has no prior diting install |
| 90 | +- **THEN** the tarball extracts to `~/.local/share/diting/`, `~/.local/bin/diting` is a symlink to `bin/diting`, and `diting --help` succeeds from a shell where `~/.local/bin` is on PATH |
| 91 | + |
| 92 | +#### Scenario: Upgrade replacing an older install |
| 93 | +- **WHEN** an older `~/.local/share/diting/` already exists from a previous install |
| 94 | +- **THEN** the installer moves the existing directory aside (rename to `~/.local/share/diting.old/`), extracts the new tarball, removes `.old` only after the new symlink target verifies as executable; on failure mid-flight the old directory is restored |
| 95 | + |
| 96 | +### Requirement: The installer SHALL place the Swift helper bundle under `~/Library/Application Support/diting/` and prime it for TCC |
| 97 | +After extracting the tarball, the installer SHALL copy |
| 98 | +`share/diting-tianer.app/` to |
| 99 | +`~/Library/Application Support/diting/diting-tianer.app`, strip |
| 100 | +the quarantine xattr so Gatekeeper does not block first launch, |
| 101 | +and `open` the bundle once so macOS surfaces the Location Services |
| 102 | +and Bluetooth TCC prompts to the user. |
| 103 | + |
| 104 | +#### Scenario: First install primes TCC |
| 105 | +- **WHEN** the helper bundle did not exist at the target path before this install |
| 106 | +- **THEN** after the installer runs, the bundle exists at the target path, has no `com.apple.quarantine` xattr, and a brief background `open` invocation has fired so the user sees TCC prompts on their next interaction |
| 107 | + |
| 108 | +#### Scenario: Subsequent installs preserve granted permissions |
| 109 | +- **WHEN** the user has already granted Location Services and Bluetooth in a prior install and re-runs the installer with a same-cdhash helper binary |
| 110 | +- **THEN** the new copy lands at the same path; TCC keys by cdhash so the grants persist with no re-prompt |
| 111 | + |
| 112 | +### Requirement: The installer SHALL print a PATH-update hint when `~/.local/bin` is not on PATH |
| 113 | +After installing, the installer SHALL check whether |
| 114 | +`~/.local/bin` is on the user's `PATH`. If not, it SHALL print a |
| 115 | +single-line hint suggesting the appropriate `export PATH` addition |
| 116 | +for the user's detected shell (`zsh`, `bash`, or `fish`). |
| 117 | + |
| 118 | +#### Scenario: PATH already includes `~/.local/bin` |
| 119 | +- **WHEN** the user's shell already has `~/.local/bin` on PATH |
| 120 | +- **THEN** the installer prints "diting is on your PATH — run `diting`" and no hint |
| 121 | + |
| 122 | +#### Scenario: PATH does NOT include `~/.local/bin` on zsh |
| 123 | +- **WHEN** the detected shell is zsh and the PATH check fails |
| 124 | +- **THEN** the installer prints exactly: `Add to ~/.zshrc: export PATH="$HOME/.local/bin:$PATH"` |
| 125 | + |
| 126 | +### Requirement: The frozen-binary install path SHALL coexist with the `uv run diting` developer workflow |
| 127 | +Installing via the curl-bash one-liner MUST NOT break the |
| 128 | +contributor flow (`git clone` + `uv sync` + `make helper` + |
| 129 | +`uv run diting`). A developer can have both installed at once — |
| 130 | +the frozen binary on PATH and a working repo checkout — without |
| 131 | +the two paths interfering. In particular, `make helper` in the |
| 132 | +repo MUST keep working, and `find_helper()` MUST keep finding the |
| 133 | +in-repo dev build when invoked through `uv run`. |
| 134 | + |
| 135 | +#### Scenario: Developer has both paths installed |
| 136 | +- **WHEN** a contributor has run the curl-bash installer AND has a repo checkout where they run `uv run diting` |
| 137 | +- **THEN** `uv run diting` picks up the in-repo helper (priority in `find_helper()` ordering), and standalone `diting` picks up the Application Support helper — neither shadows the other |
| 138 | + |
| 139 | +#### Scenario: Developer uninstalls the frozen binary |
| 140 | +- **WHEN** the contributor deletes `~/.local/bin/diting` and `~/.local/share/diting/` |
| 141 | +- **THEN** `uv run diting` in the repo continues to work; the in-repo helper bundle is unaffected |
| 142 | + |
0 commit comments