Build standalone binary with bunfig autoload disabled
Set autoloadBunfig: false in build.ts so the compiled runtime ignores any bunfig.toml in the launching directory, preventing startup failures from a CWD preload the standalone cannot resolve. Update release.yml, Makefile, bunfig.toml, CONTRIBUTING.md, and README.md to match.
This commit is contained in:
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
@@ -66,12 +66,14 @@ jobs:
|
||||
env:
|
||||
DIST_TAR: podtui-${{ matrix.plat }}-${{ matrix.arch }}.tar.gz
|
||||
run: |
|
||||
# The embedded runtime reads the launching process's CWD bunfig.toml.
|
||||
# This repo's bunfig lists a preload the standalone can't resolve
|
||||
# ("preload not found"), so kicking the binary from the workspace root
|
||||
# would falsely fail every build. cd into a clean dir first.
|
||||
# The binary is compiled with bunfig autoload disabled
|
||||
# (autoloadBunfig: false in build.ts), so it must boot even from a
|
||||
# directory holding a bunfig.toml with a top-level preload the
|
||||
# standalone can't resolve. Plant one to make this a real regression
|
||||
# test for "preload not found".
|
||||
SMOKE_DIR=$(mktemp -d)
|
||||
tar -xzf "dist/$DIST_TAR" -C "$SMOKE_DIR"
|
||||
printf 'preload = ["./definitely-missing.ts"]\n' > "$SMOKE_DIR/bunfig.toml"
|
||||
cd "$SMOKE_DIR"
|
||||
./podtui-*/podtui --version
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ The app is a TUI — it expects a real terminal (Ghostty, kitty, iTerm2,
|
||||
| `bun run lint` | Type-check |
|
||||
| `bun run build` | Bundle JS into `dist/` + copy native libs (the `podtui` npm script path) |
|
||||
| `make dist` | Compile the standalone binary + make the current platform's tarball |
|
||||
| `make dist-mac` / `make dist-linux` | Aliases for `dist` on their platform (CI runs these) |
|
||||
| `make clean` | Remove `dist/` |
|
||||
|
||||
## Repository layout
|
||||
@@ -90,19 +91,21 @@ Cavacore smoke test: `bun tests/cavacore-smoke.ts`
|
||||
|
||||
## Gotchas (read before touching anything)
|
||||
|
||||
1. **Never add a top-level `preload` to `bunfig.toml`.**
|
||||
A compiled PodTui binary's embedded runtime reads the *launching process's*
|
||||
CWD `bunfig.toml`, and a `preload` entry points at a module the standalone
|
||||
can't resolve (`@opentui/solid/preload`) → the binary dies at startup with
|
||||
`preload not found`. This is why `bunfig.toml` has **no** top-level
|
||||
`preload`; dev-mode preloading happens via explicit `--preload` flags in
|
||||
`package.json`. The `[test]` section *does* keep a preload — that only
|
||||
affects `bun test`.
|
||||
1. **The compiled binary must keep bunfig autoload disabled.**
|
||||
`build.ts` compiles the standalone with `autoloadBunfig: false`, so its
|
||||
embedded runtime *never* reads the launching CWD's `bunfig.toml`. Without
|
||||
that flag, a top-level `preload` in the CWD bunfig (common in Bun project
|
||||
dirs) resolves against the CWD rather than the binary and kills startup
|
||||
with `preload not found`. Don't remove the flag. Preloads for dev/test
|
||||
belong in the explicit `--preload` flags in `package.json` and the
|
||||
`[test]` section of `bunfig.toml` — not as a top-level entry.
|
||||
|
||||
2. **Smoke-test the compiled binary from a bunfig-free dir.**
|
||||
Because of (1), `./dist/podtui --version` run from the repo root launched
|
||||
inside CI would fail. CI always unpacks the tarball into a `mktemp` dir
|
||||
before booting. Do the same when testing a release build locally.
|
||||
2. **Smoke-test the binary from a dir with a poisoned bunfig.**
|
||||
The CI smoke test unpacks the tarball into a `mktemp` dir, drops a
|
||||
`bunfig.toml` containing an unresolvable top-level `preload` next to it,
|
||||
and boots the binary — proving bunfig autoload stayed disabled. `./dist/
|
||||
podtui --version` must work from any directory, including the repo root;
|
||||
do the same check when testing a release build locally.
|
||||
|
||||
3. **Homebrew's dylib-repair warning is benign.**
|
||||
`brew install` may print “load commands do not fit in the header … needs
|
||||
@@ -166,7 +169,7 @@ Releases are built and published from **tags**
|
||||
test: `brew install mikefreno/tap/podtui`.
|
||||
5. **AUR packaging** (`packaging/aur/PKGBUILD`): the `podtui-bin` package is
|
||||
staged, not yet published (AUR account registrations are closed; see the
|
||||
README note in section 3). On each release, keep the AUR sources in sync
|
||||
README's Installation section). On each release, keep the AUR sources in sync
|
||||
with the new tag: bump `pkgver`, recompute the two tarball `sha256sums`
|
||||
entries, keep the `LICENSE` asset source (the workflow above uploads
|
||||
`LICENSE` to every release), and regenerate `packaging/aur/.SRCINFO` with
|
||||
@@ -190,13 +193,36 @@ make dist # builds the binary + tarball for THIS machine only
|
||||
|
||||
Bun cannot cross-compile — the other platforms come from CI.
|
||||
|
||||
## Distribution & packaging
|
||||
|
||||
A release tarball is three files sitting side by side: the `podtui` binary
|
||||
plus its two FFI libraries (`libopentui.<dylib|so>`,
|
||||
`libcavacore.<dylib|so>`). The sibling rule above is why they ship together.
|
||||
|
||||
PodTui deliberately ships **no** `.deb`, `.rpm`, Flatpak, or Snap packages:
|
||||
for a terminal app that's overwhelmingly installed through repositories or
|
||||
archives, those formats add desktop-sandboxing overhead and a packaging tax
|
||||
with little benefit. Instead:
|
||||
|
||||
- **GitHub Release tarballs** are the universal path — one upload per
|
||||
OS/arch, works on any distro with `curl` + `tar`.
|
||||
- **AUR (`podtui-bin`)** covers Arch/Manjaro with the same binary through the
|
||||
native package manager.
|
||||
- **Nix / cross-distro** users build from source (or a Nix flake can be added
|
||||
later).
|
||||
|
||||
This keeps maintenance to a single build per OS/arch while still reaching the
|
||||
vast majority of desktop Linux users. The AUR PKGBUILD lives in
|
||||
`packaging/aur/` and can be built locally to test before publication:
|
||||
|
||||
```bash
|
||||
cd packaging/aur && makepkg -si
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Open items / things to sort out
|
||||
|
||||
- **LICENSE**: `README.md` says "TBD — choose and document a license before
|
||||
the first release". Pick one (MIT/BSD-3) and add `LICENSE` + update the
|
||||
README footer.
|
||||
- **Native libs in `dist/` still need committing?** No — they're built from
|
||||
sources kept in the repo (`cava/`, `node_modules/@opentui/core-*`). Only
|
||||
`src/native/libcavacore.dylib` is a committed binary artifact; macOS arm64
|
||||
|
||||
5
Makefile
5
Makefile
@@ -47,9 +47,8 @@ native:
|
||||
scripts/build-cavacore.sh
|
||||
|
||||
## Standalone binary + native-libs tarball for the current platform.
|
||||
## Unaffected by bunfig.toml at build time. Note: the compiled runtime reads
|
||||
## the launching process's CWD bunfig.toml, so smoke tests must run the binary
|
||||
## from a bunfig-free dir (see release.yml).
|
||||
## Built with bunfig autoload disabled (build.ts sets autoloadBunfig: false),
|
||||
## so the embedded runtime ignores any bunfig.toml in the launching directory.
|
||||
dist:
|
||||
bun run build.ts --compile
|
||||
|
||||
|
||||
208
README.md
208
README.md
@@ -1,7 +1,6 @@
|
||||
# PodTui
|
||||
|
||||
A keyboard-first, yazi-style terminal podcast client written in TypeScript and
|
||||
built on [OpenTUI](https://github.com/opentui/opentui). Subscribe to RSS feeds,
|
||||
A keyboard-first, terminal podcast client built on [OpenTUI](https://github.com/opentui/opentui). Subscribe to RSS feeds,
|
||||
browse episodes in a three-pane file-manager layout, and play audio through an
|
||||
external player with full transport control — all from your terminal.
|
||||
|
||||
@@ -11,8 +10,7 @@ external player with full transport control — all from your terminal.
|
||||
`Enter` to open, `1–6` / `[` `]` to switch tabs. The tab list is the app root:
|
||||
at launch it fills the current pane, and drilling into a tab's contents slides
|
||||
it into the parent pane.
|
||||
- **Three-pane view** — parent / current / preview (Up | Current | Preview),
|
||||
mirroring yazi's pane model.
|
||||
- **Three-pane view** — parent / current / preview (Up | Current | Preview).
|
||||
- **Podcast feeds** — add feeds, browse episodes, and manage your library
|
||||
(My Shows, Discover, Feed tabs).
|
||||
- **Search** across your subscribed shows.
|
||||
@@ -22,13 +20,28 @@ external player with full transport control — all from your terminal.
|
||||
- Ships as a **standalone compiled binary** — no runtime or install step beyond
|
||||
a system audio player.
|
||||
|
||||
## Quick start
|
||||
|
||||
1. Install PodTui ([Installation](#installation)) and make sure **mpv** is in
|
||||
your `PATH`.
|
||||
2. Run `podtui` in your terminal.
|
||||
3. Press `3` to open **Discover** (or `4` to open **Search**, then `s`), drill
|
||||
in with `Enter`, and press `Enter` on a show to subscribe.
|
||||
4. Press `1` (**Feed**) or `2` (**My Shows**), open an episode with `Enter`,
|
||||
and use `P` to play/pause, `N`/`B` for next/previous, and `shift-.` /
|
||||
`shift-,` to seek.
|
||||
|
||||
Press `~` any time for in-app help. All keys are remappable — see
|
||||
[Keybindings](#keybindings).
|
||||
|
||||
## Requirements
|
||||
|
||||
- A terminal with UTF-8 and modern color support (kitty, iTerm2, WezTerm,
|
||||
tmux, GNOME Terminal, etc.).
|
||||
Ghostty, tmux etc.).
|
||||
- **mpv** on `PATH` for audio playback. PodTui drives mpv over JSON IPC, so
|
||||
seek, speed, and position tracking all work. Without `mpv` on `PATH`,
|
||||
playback is a silent no-op (the `none` backend).
|
||||
playback is a silent no-op (the `none` backend) — see
|
||||
[Troubleshooting](#troubleshooting).
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -41,10 +54,6 @@ Linux (arm64/x64). Pick whichever fits your platform.
|
||||
brew install mikefreno/tap/podtui
|
||||
```
|
||||
|
||||
> The formula installs the standalone binary plus its two native libraries
|
||||
> side by side (see [Packaging model](#packaging-model)). It does **not**
|
||||
> depend on Bun.
|
||||
|
||||
### 2. Standalone tarball (all platforms)
|
||||
|
||||
Grab `podtui-<platform>-<arch>.tar.gz` from the latest
|
||||
@@ -62,68 +71,29 @@ sudo ln -sf /opt/podtui/podtui /usr/local/bin/podtui
|
||||
> The tarball contains `podtui` plus `libopentui.<ext>` and
|
||||
> `libcavacore.<ext>` **beside it** — keep them together (don't move just the
|
||||
> binary alone), or the native FFI libraries won't load.
|
||||
>
|
||||
> One caveat: the embedded runtime reads a `bunfig.toml` from the directory
|
||||
> you launch from. If that file has a `preload` entry (as Bun project
|
||||
> directories often do), startup fails with `preload not found`. Launching
|
||||
> from a normal directory (home, `~/bin`, …) works fine.
|
||||
|
||||
### 3. Arch Linux (AUR)
|
||||
|
||||
```bash
|
||||
# Status: PKGBUILD ready, not yet on the AUR (see note below)
|
||||
yay -S podtui-bin # once published
|
||||
```
|
||||
|
||||
Requires an AUR helper ([paru](https://github.com/morgan/paru)). The AUR
|
||||
package (PKGBUILD lives in `packaging/aur/`) installs the released binary and
|
||||
its two FFI sibling libraries into `/usr/lib/podtui/` with a `/usr/bin/podtui`
|
||||
symlink, and pulls in `mpv` (the sole audio backend) as a dependency.
|
||||
Requires an AUR helper ([paru](https://github.com/morgan/paru)); the package
|
||||
pulls in `mpv` as a dependency.
|
||||
|
||||
> **Not yet on the AUR.** The `podtui-bin` PKGBUILD and `.SRCINFO` are ready
|
||||
> in `packaging/aur/` and can be built locally today:
|
||||
>
|
||||
> ```bash
|
||||
> cd packaging/aur && makepkg -si
|
||||
> ```
|
||||
>
|
||||
> Publishing is on hold until [AUR account registrations](https://aur.archlinux.org)
|
||||
> reopen (suspended while the AUR team works on suspicious-package
|
||||
> moderation).
|
||||
> **Not yet on the AUR.** The `podtui-bin` package is staged and awaiting
|
||||
> publication (AUR account registrations are currently suspended). Until it
|
||||
> lands, use the standalone tarball above.
|
||||
|
||||
### 4. From source
|
||||
|
||||
Requires [Bun](https://bun.sh) ≥ 1.2.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/mikefreno/podtui.git
|
||||
cd podtui
|
||||
bun install
|
||||
bun run build:native # build the cavacore FFI lib from C source
|
||||
bun run dev # run with hot reload, or: bun start
|
||||
```
|
||||
|
||||
## Linux distribution notes
|
||||
|
||||
PodTUI deliberately does **not** ship `.deb`, `.rpm`, Flatpak, or Snap
|
||||
packages. For a terminal application that's overwhelmingly installed through
|
||||
repositories or archives, those formats add desktop-sandboxing overhead and a
|
||||
packaging tax with little benefit. Instead:
|
||||
|
||||
- **GitHub Release tarballs** are the universal path — one upload, works on
|
||||
any distro with `curl` + `tar`.
|
||||
- **AUR (`podtui-bin`)** covers Arch. Anyone on Arch/Manjaro gets the same
|
||||
binary through their native package manager.
|
||||
- **Nix / cross-distro** users can build from source (or a Nix flake can be
|
||||
added later).
|
||||
|
||||
This keeps maintenance to a single build per OS/arch and still reaches the
|
||||
vast majority of desktop Linux users through their preferred path.
|
||||
PodTui is written in TypeScript and runs on [Bun](https://bun.sh). To build
|
||||
from source (development, distro packaging, unreleased versions), see
|
||||
[CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
|
||||
## Usage
|
||||
|
||||
Launch `podtui` (or `bun src/index.tsx` from the source tree). Press `~`
|
||||
for the in-app help.
|
||||
Launch `podtui`. Press `~` for the in-app help.
|
||||
|
||||
### Command-line flags
|
||||
|
||||
@@ -135,30 +105,70 @@ for the in-app help.
|
||||
|
||||
### Keybindings
|
||||
|
||||
All keys are remappable — edit `~/.config/podtui/keybinds.jsonc`.
|
||||
All keys are remappable — edit `keybinds.jsonc` in your config directory
|
||||
(see [Configuration](#configuration)).
|
||||
|
||||
**Movement**
|
||||
|
||||
| Keys | Action |
|
||||
|------|--------|
|
||||
| `j` / `k` | Move cursor down / up |
|
||||
| `J` / `K` | Jump 5 lines |
|
||||
| `j` / `k` (or `down` / `up`) | Move down / up |
|
||||
| `J` / `K` | Jump 5 lines down / up |
|
||||
| `ctrl-d` / `ctrl-u` | Page down / up |
|
||||
| `ctrl-f` / `ctrl-b` | Full page down / up |
|
||||
| `gg` / `G` | Go to top / bottom |
|
||||
| `h` / `l` | Swipe to parent pane / preview pane |
|
||||
|
||||
**Panes**
|
||||
|
||||
| Keys | Action |
|
||||
|------|--------|
|
||||
| `h` / `l` (or `left` / `right`) | Focus parent pane / preview pane |
|
||||
| `Enter` | Open the item under the cursor (a tab, episode, show…) |
|
||||
| `Space` | Select / toggle selection |
|
||||
| `shift-enter` | Open with the interactive variant |
|
||||
|
||||
**Selection**
|
||||
|
||||
| Keys | Action |
|
||||
|------|--------|
|
||||
| `Space` | Toggle selection |
|
||||
| `v` | Visual mode (multi-select) |
|
||||
| `ctrl-a` | Select / deselect all |
|
||||
| `ctrl-r` | Invert selection |
|
||||
| `Esc` | Cancel / escape |
|
||||
|
||||
**Tabs**
|
||||
|
||||
| Keys | Action |
|
||||
|------|--------|
|
||||
| `1`–`6` | Jump to tab 1–6 (Feed, My Shows, Discover, Search, Player, Settings) |
|
||||
| `[` / `]` | Previous / next tab |
|
||||
| `P` (shift) | Play / pause |
|
||||
|
||||
**Commands, help, quit**
|
||||
|
||||
| Keys | Action |
|
||||
|------|--------|
|
||||
| `:` or `q` | Open the command palette (type `q` + `Enter` there to quit) |
|
||||
| `Q` or `ctrl-c` | Quit |
|
||||
| `~` or `f1` | In-app help |
|
||||
|
||||
**Lists**
|
||||
|
||||
| Keys | Action |
|
||||
|------|--------|
|
||||
| `s` | Search |
|
||||
| `f` | Filter |
|
||||
| `,` | Sort |
|
||||
| `.` | Toggle hidden |
|
||||
| `r` | Refresh |
|
||||
| `x` | Unsubscribe the focused show (My Shows) |
|
||||
|
||||
**Audio**
|
||||
|
||||
| Keys | Action |
|
||||
|------|--------|
|
||||
| `P` | Play / pause |
|
||||
| `N` / `B` | Next / previous episode |
|
||||
| `shift-.` / `shift-,` | Seek forward / backward |
|
||||
| `s` | Search (in a list) |
|
||||
| `f` | Filter |
|
||||
| `r` | Refresh |
|
||||
| `:` | Command bar |
|
||||
| `~`, `f1` | Help |
|
||||
| `q`, `ctrl-c` | Quit |
|
||||
| `Esc` | Escape / cancel |
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -175,50 +185,32 @@ default (`$XDG_CONFIG_HOME/podtui` if set).
|
||||
Legacy `feeds.json`, `sources.json`, and `app-state.json` are auto-migrated
|
||||
into `config.json` on first run.
|
||||
|
||||
Env overrides: `PODTUI_AUDIO_BACKEND`, `XDG_CONFIG_HOME`. Startup also reads
|
||||
the same OpenTUI environment variables.
|
||||
Env overrides: `PODTUI_AUDIO_BACKEND`, `XDG_CONFIG_HOME`.
|
||||
|
||||
## Development
|
||||
## Troubleshooting
|
||||
|
||||
```bash
|
||||
bun install # install dependencies
|
||||
bun run dev # run with hot reload
|
||||
bun test # run the test suite
|
||||
bun run build # bundle JS + copy native libs into dist/
|
||||
make native # rebuild cavacore from C source
|
||||
make lint # type-check (tsc)
|
||||
```
|
||||
**`preload not found` at startup** — this used to happen when the binary was
|
||||
launched from a Bun project directory whose `bunfig.toml` had a `preload`
|
||||
entry. Releases are compiled with bunfig autoload disabled
|
||||
(`autoloadBunfig: false`), so current binaries ignore the CWD's `bunfig.toml`
|
||||
entirely. If you still hit it, you're on an old release — upgrade.
|
||||
|
||||
### Releasing
|
||||
**No audio — playback is a silent no-op** — PodTui needs **mpv** on your
|
||||
`PATH`. Install it (`brew install mpv`, `pacman -S mpv`, …) and relaunch.
|
||||
|
||||
Tag a release (e.g. `v0.1.0`); CI builds and uploads the per-platform tarballs
|
||||
to your GitHub Release automatically:
|
||||
**Homebrew prints a dylib warning** — “load commands do not fit in the header
|
||||
… needs `-headerpad`” is benign: the app loads its libraries by path, the
|
||||
install completes, and the app boots normally.
|
||||
|
||||
```bash
|
||||
make dist # build the standalone binary + tarball for THIS platform
|
||||
make dist-mac # (run on macOS) → podtui-darwin-<arch>.tar.gz
|
||||
make dist-linux # (run on Linux) → podtui-linux-<arch>.tar.gz
|
||||
```
|
||||
**The app won't start / no spectrum after moving files** — `podtui` loads its
|
||||
two native libraries relative to the binary, so keep `podtui`,
|
||||
`libopentui.*`, and `libcavacore.*` together in the same directory (the
|
||||
tarball unpacks them side by side).
|
||||
|
||||
`make dist` emits a config-independent binary: Bun does not bake bunfig
|
||||
settings into `--compile` output, and the solid JSX transform is registered in
|
||||
`build.ts` itself. The binary then embeds the `preload`-free runtime, so launch
|
||||
it from any normal directory.
|
||||
## Building from source / contributing
|
||||
|
||||
## Packaging model
|
||||
|
||||
A release tarball is three files sitting side by side:
|
||||
|
||||
```
|
||||
podtui # standalone compiled binary (embeds the Bun runtime)
|
||||
libopentui.<dylib|so> # OpenTUI native renderer FFI library
|
||||
libcavacore.<dylib|so> # cavacore spectrum FFI library (built from C)
|
||||
```
|
||||
|
||||
PodTui loads its native libraries relative to the binary, so **keep them in
|
||||
the same directory**. The compiled binary embeds the Bun runtime, so it runs
|
||||
with no Bun installed. Each release builds one tarball per OS/arch in CI; there
|
||||
is no cross-compilation.
|
||||
Development setup, the test suite, packaging, and the release process are
|
||||
documented in [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
6
build.ts
6
build.ts
@@ -82,6 +82,12 @@ if (COMPILE) {
|
||||
plugins: [solidPlugin],
|
||||
compile: {
|
||||
outfile,
|
||||
// Don't let the embedded runtime autoload the launching CWD's
|
||||
// bunfig.toml. A top-level `preload` there (common in Bun project
|
||||
// dirs) resolves against the CWD, not the binary, so startup dies
|
||||
// with "preload not found". With autoload disabled, the binary is
|
||||
// config-independent and boots from any directory.
|
||||
autoloadBunfig: false,
|
||||
},
|
||||
});
|
||||
console.log(`Compiled standalone binary: ${outfile}`);
|
||||
|
||||
11
bunfig.toml
11
bunfig.toml
@@ -1,9 +1,8 @@
|
||||
# NO top-level `preload` here — intentional. A compiled PodTUI binary's
|
||||
# embedded Bun runtime reads the launching process's CWD bunfig.toml, and a
|
||||
# top-level `preload` entry (e.g. "@opentui/solid/preload", which the
|
||||
# standalone cannot resolve) makes the binary die at startup with
|
||||
# "preload not found". Dev/test still get the solid transform via explicit
|
||||
# `--preload` flags in package.json and the [test] section below.
|
||||
# No top-level `preload` here — dev/test get the solid JSX transform via the
|
||||
# explicit `--preload` flags in package.json and the [test] section below.
|
||||
# Releases don't read this file at all: build.ts compiles the standalone with
|
||||
# `autoloadBunfig: false`, so its embedded runtime ignores any bunfig.toml in
|
||||
# the launching directory — no more "preload not found" from CWD bunfigs.
|
||||
|
||||
[test]
|
||||
preload = "@opentui/solid/preload"
|
||||
|
||||
Reference in New Issue
Block a user