83d91c5630
- src/move.ts entry point with CLI parsing, --help, --version - src/cli.ts: parseCliArgs, printHelp, ParsedCliArgs, CliError, VERSION - src/config.ts: Config type, DEFAULT_CONFIG, resolveConfig - src/keeper.ts: synthetic-activity sweep + idle-watch loop - package.json bin entry + shebang for 'bun link' global install - install.sh: contributor bootstrap (will be repurposed; see DISTRIBUTION-PLAN.md for the end-user installer design) - DISTRIBUTION-PLAN.md captures the tabled end-user distribution work
223 lines
8.5 KiB
Markdown
223 lines
8.5 KiB
Markdown
# Distribution Plan for `move`
|
|
|
|
**Status:** Tabled 2026-06-15. Revisit after the initial Gitea push lands
|
|
and the repo is publicly reachable at <https://gitea.cahlen.com/nokeo08/Move>.
|
|
|
|
This document captures the analysis and decisions for distributing the
|
|
`move` CLI as an end-user-friendly tool, so we can resume without redoing
|
|
the discussion.
|
|
|
|
---
|
|
|
|
## Decisions locked in
|
|
|
|
| Decision | Choice |
|
|
| -------- | ------ |
|
|
| Primary hosting | Self-hosted Gitea (`https://gitea.cahlen.com/nokeo08/Move`) |
|
|
| Default branch | `master` |
|
|
| Audience | Anyone who finds the repo (broader public-ish) |
|
|
| Effort budget for now | Smallest viable curl-able `install.sh` |
|
|
| Bun handling | Fail with a clear message when missing (no auto-install) |
|
|
| Reinstall behavior | Idempotent via a version-marker file; `MOVE_FORCE=1` overrides |
|
|
| Install location | `~/.local/bin` + `~/.local/share/move` (XDG, no sudo), env-overridable |
|
|
| `move` shim | Bash wrapper script, not symlink |
|
|
| PATH rc files | Never modified; print instructions if PATH isn't right |
|
|
| Uninstaller | In scope; same `curl ... \| sh` pattern |
|
|
| npm publishing | Out of scope |
|
|
| Standalone binaries (`bun build --compile`) | Future work; depends on nut.js native-bundling testing |
|
|
| Homebrew tap | Future work; cheap once releases exist |
|
|
|
|
---
|
|
|
|
## Distribution-option survey (from the discussion)
|
|
|
|
Four options were weighed:
|
|
|
|
1. **Compiled standalone binary** (`bun build --compile`). Best UX (single
|
|
binary, no Bun needed) but needs per-platform builds, a release
|
|
pipeline, code signing on macOS, and depends on nut.js's prebuilt native
|
|
`.node` files bundling correctly under `--compile` — **unverified**.
|
|
2. **Curl-piped installer** — **chosen**. Single command, no clone,
|
|
source-and-Bun model. Conventions used by bun, deno, rustup, nvm.
|
|
Slight cold-start cost vs. compiled binary; tradeoff accepted.
|
|
3. **Package-manager distribution** (`bun add -g`, brew). `move` is taken
|
|
on npm; brew is macOS-centric; both add publishing overhead. Out of
|
|
scope for first cut.
|
|
4. **Status quo** (clone + `./install.sh` + `bun link`). Developer flow
|
|
only; will be renamed to `dev-setup.sh`.
|
|
|
|
---
|
|
|
|
## Target end-user UX
|
|
|
|
```sh
|
|
curl -fsSL https://gitea.cahlen.com/nokeo08/Move/raw/branch/master/install.sh | sh
|
|
move --help
|
|
```
|
|
|
|
One command. No clone. No `bun link`. No `chmod`. After this the user has
|
|
a global `move` on their PATH.
|
|
|
|
---
|
|
|
|
## `install.sh` design (end-user, curl-able)
|
|
|
|
- **Shebang:** `#!/usr/bin/env sh` (POSIX).
|
|
- **Strict mode:** `set -eu`. Trap on errors with a clear
|
|
"install failed at <step>" message.
|
|
|
|
### Top-to-bottom behavior
|
|
|
|
1. **Platform detection:**
|
|
- `uname -s` → `Darwin` / `Linux` / else → "unsupported OS" + exit 1.
|
|
- `uname -m` → `arm64` / `x86_64` (mapped to `x64`) / else →
|
|
"unsupported arch" + exit 1.
|
|
- nut.js supports darwin-arm64/x64, linux-x64, win32-x64. WSL users
|
|
fall under Linux.
|
|
2. **Bun check:**
|
|
- `command -v bun` present → continue.
|
|
- Absent → print
|
|
`Error: bun is not installed. Install from https://bun.sh (e.g. 'curl -fsSL https://bun.sh/install | bash'), then re-run.`
|
|
→ exit 1.
|
|
3. **Resolve paths:**
|
|
- `INSTALL_DIR=${MOVE_PREFIX:-$HOME/.local/share/move}`
|
|
- `BIN_DIR=${MOVE_BIN_DIR:-$HOME/.local/bin}`
|
|
- `mkdir -p` both.
|
|
4. **Resolve version:**
|
|
- `VERSION=${MOVE_VERSION:-master}`.
|
|
5. **Idempotence check:**
|
|
- If `$INSTALL_DIR/.installed-version` exists AND its content equals
|
|
`$VERSION` AND `${MOVE_FORCE:-0}` is not `1`:
|
|
- Print `move <version> already installed at $BIN_DIR/move. Set MOVE_FORCE=1 to reinstall, or pass a different MOVE_VERSION.`
|
|
- Exit 0.
|
|
6. **Clean install dir:** `rm -rf "$INSTALL_DIR"/*` (preserving the dir
|
|
itself so user-set permissions on it are kept).
|
|
7. **Download source:**
|
|
- `TARBALL="https://gitea.cahlen.com/nokeo08/Move/archive/${VERSION}.tar.gz"`
|
|
- `curl -fsSL "$TARBALL" -o "$TMP"` (mktemp) → on failure, fail
|
|
loudly with the offending URL.
|
|
- `tar -xzf "$TMP" -C "$INSTALL_DIR" --strip-components=1`
|
|
- `rm -f "$TMP"`
|
|
8. **Install deps:** `cd "$INSTALL_DIR" && bun install --production`.
|
|
`--production` skips devDependencies (TypeScript, @types/bun); saves
|
|
tens of MB and seconds per install.
|
|
9. **Drop the wrapper:**
|
|
```sh
|
|
cat > "$BIN_DIR/move" <<EOF
|
|
#!/usr/bin/env sh
|
|
exec bun "$INSTALL_DIR/src/move.ts" "\$@"
|
|
EOF
|
|
chmod +x "$BIN_DIR/move"
|
|
```
|
|
Wrapper, not a symlink, so the path is absolute and stable regardless
|
|
of how the user's shell resolves things.
|
|
10. **Write version marker:** `printf '%s\n' "$VERSION" > "$INSTALL_DIR/.installed-version"`.
|
|
11. **PATH sanity check:**
|
|
- If `$BIN_DIR` is not on `$PATH`, print:
|
|
`Warning: $BIN_DIR is not on your PATH. Add this to your shell rc: export PATH="$BIN_DIR:$PATH"`
|
|
- Do **not** mutate the user's rc files — that breaks the "clean
|
|
uninstall" contract.
|
|
12. **macOS Accessibility hint** (carried over from current `install.sh`).
|
|
13. **Final message:** `Installed move $VERSION at $BIN_DIR/move. Run 'move --help' to get started.`
|
|
|
|
### Env vars
|
|
|
|
| Var | Default | Purpose |
|
|
| --- | ------- | ------- |
|
|
| `MOVE_VERSION` | `master` | Branch or tag to fetch from Gitea. |
|
|
| `MOVE_PREFIX` | `$HOME/.local/share/move` | Source install location. |
|
|
| `MOVE_BIN_DIR` | `$HOME/.local/bin` | Wrapper install location. |
|
|
| `MOVE_FORCE` | unset | Force reinstall even if version marker matches. |
|
|
|
|
---
|
|
|
|
## `uninstall.sh` design
|
|
|
|
```sh
|
|
curl -fsSL https://gitea.cahlen.com/nokeo08/Move/raw/branch/master/uninstall.sh | sh
|
|
```
|
|
|
|
**Behavior:**
|
|
|
|
1. `set -eu`.
|
|
2. Respect `MOVE_PREFIX` and `MOVE_BIN_DIR` env vars (same defaults as
|
|
install).
|
|
3. `rm -f "$BIN_DIR/move"`.
|
|
4. `rm -rf "$INSTALL_DIR"`.
|
|
5. Print `Uninstalled. (Bun was not touched.)`.
|
|
|
|
Does **not** uninstall Bun — that's the user's runtime, not ours.
|
|
|
|
---
|
|
|
|
## `dev-setup.sh` design (renamed from current `install.sh`)
|
|
|
|
The current `install.sh` is appropriate for contributors but inappropriate
|
|
as the end-user installer. Rename to `dev-setup.sh`. Content stays nearly
|
|
identical:
|
|
|
|
- Verify Bun is installed (hard-error if missing).
|
|
- `bun install`.
|
|
- macOS Accessibility hint.
|
|
- Trailing message updated to:
|
|
`Done. Dev workflow: 'bun run start' to run, or 'bun link' to install the global 'move' command. End-user installer is install.sh.`
|
|
|
|
---
|
|
|
|
## README changes (when we resume)
|
|
|
|
- `## Install` section becomes the curl one-liner targeting `master`, plus
|
|
a short note about env-var overrides and a pointer to uninstall.
|
|
- `## Run` section collapses to `move` / `move --help` for the global
|
|
install path.
|
|
- New `## Uninstall` section with the curl-pipe uninstall command.
|
|
- New `## For contributors` section absorbing the existing
|
|
`bun run start` / `bun run src/move.ts` / `bun link` examples; points
|
|
at `dev-setup.sh`.
|
|
- `## Files` table updates: add `install.sh` (end-user, curl-able),
|
|
`uninstall.sh` (end-user, curl-able), `dev-setup.sh` (contributor
|
|
bootstrap).
|
|
|
|
---
|
|
|
|
## Verification plan (when we resume)
|
|
|
|
1. `sh -n install.sh` and `sh -n uninstall.sh` — POSIX syntax check.
|
|
2. Local end-to-end with overridden paths:
|
|
```sh
|
|
MOVE_PREFIX=/tmp/movetest MOVE_BIN_DIR=/tmp/movetest/bin ./install.sh
|
|
/tmp/movetest/bin/move --help
|
|
./install.sh # second run: already-installed message
|
|
MOVE_FORCE=1 ./install.sh # forced reinstall
|
|
```
|
|
3. Uninstall against the test prefix; confirm nothing left under it.
|
|
4. Re-run existing acceptance tests (`tsc`, `--help`, `--version`,
|
|
`--bogus`) — should be unaffected; no source changes in this milestone.
|
|
5. After committing/pushing, end-to-end against the actual Gitea URL.
|
|
|
|
---
|
|
|
|
## Caveat: Gitea archive URL shape
|
|
|
|
The plan assumes
|
|
`https://gitea.cahlen.com/nokeo08/Move/archive/<ref>.tar.gz` returns a
|
|
tarball whose top-level directory contains the project files (peeled by
|
|
`--strip-components=1`). This is the standard Gitea archive format, but
|
|
if your Gitea version produces a different layout the installer will need
|
|
a small tweak. The installer should error loudly with the offending URL
|
|
if extraction fails.
|
|
|
|
---
|
|
|
|
## Open future work (out of scope for the resume session)
|
|
|
|
- Switch `MOVE_VERSION` default from `master` to "latest tag" via the
|
|
Gitea API (`/api/v1/repos/nokeo08/Move/tags`) once tagged releases are
|
|
routine.
|
|
- Investigate `bun build --compile` + nut.js native-binary bundling for
|
|
true zero-Bun installs. If it works, layer prebuilt binaries onto the
|
|
curl installer so users without Bun also get one-command installs.
|
|
- Homebrew tap for macOS users (cheap once a release pipeline exists).
|
|
- Possible: PowerShell installer (`install.ps1`) for native Windows users
|
|
without WSL.
|