fix(tools): resolve external MySQL/PG client tools correctly (#44)

Addresses #44 (closed after the v0.8.3 release builds are green).

Three independent defects in one shared tool-resolution path: bundled tools were
looked up at the wrong path (so bundled fallback never worked, for PostgreSQL
too), macOS GUI apps inherit launchd's minimal PATH, and only mariadb-dump/mariadb
were probed — never mysqldump/mysql, which is what brew install mysql-client
installs. Both engines now resolve through one headless, cached, timed resolver,
plus a non-blocking client/server version warning.

Also fixes passing MySQL-only CLI options to the bundled MariaDB client, and
carries the tool-page UI polish and the requested #40 icon revert.
This commit is contained in:
2026-09-30 20:04:16 +08:00
committed by GitHub
parent 092b3b2831
commit daa38d163e
45 changed files with 2603 additions and 1268 deletions
+2 -22
View File
@@ -161,30 +161,10 @@ bun run --filter gridline-desktop build # Desktop production build
bun run dev:www # Website dev server (Astro)
bun run build:www # Website static build (www/dist/)
bun run test # Desktop frontend tests (vitest)
bun run icons # Regenerate desktop/src-tauri/icons/* from icon.svg
bun run icons:verify # Check the macOS icon isn't rendered on a legacy plate
cargo build # Rust backend only (from desktop/src-tauri/)
cargo test # Rust tests (from desktop/src-tauri/)
```
### App icon
`desktop/src-tauri/icons/icon.svg` is the source of truth; every PNG/ICNS/ICO beside it is generated output.
Regenerate **only** through the script — never with a bare `tauri icon`:
```bash
bun run icons # tauri icon -> temp dir, copy back the desktop set, then fix the ICNS
bun run icons:verify # asserts AppKit renders the icon cleanly at 16px..512px
```
Two reasons the wrapper is mandatory:
1. `tauri icon` takes one output directory for all platforms, so running it in place would drop generated `android/` and `ios/` trees into the desktop-only icons directory. The script generates into a temp dir and copies back just the desktop files.
2. `tauri icon` emits legacy `is32`/`s8mk`/`il32`/`l8mk` (+ `ic12`) representations. **macOS 26+ then draws a grey "legacy plate" behind the icon for every render size below ~72px** — a white border around the app icon in the Dock and in any launcher that resolves icons via `NSWorkspace.icon(forFile:)` (Spotlight, Vicinae, Raycast). `scripts/fix-icns.mjs` strips those chunks, which makes the icon render cleanly at every size.
`scripts/verify-app-icon.mjs` is the regression check for that: it builds a throwaway `.app` around the committed `icon.icns`, asks AppKit for the icon (the same call launchers make) and fails if any size comes back plated. Run it after touching icons, and after bumping Tauri (a newer `tauri icon` may change what it emits).
### Branch & PR workflow
**GitHub is the only remote that matters.** The canonical repo is `github` → [AdrianBonpin/gridline](https://github.com/AdrianBonpin/gridline) (default branch `prod`, issues, PRs, Actions, releases). The `origin` remote (`git.ranio.xyz/adrianbonpin/gridline`) is a **just-in-case mirror** — do not push branches, open PRs, or publish releases/tags there. If you must mirror something manually, push to `github` first.
@@ -404,9 +384,9 @@ Planned work is prioritized in the [Project Roadmap](./ROADMAP.md) (source of tr
| Restore UI | ✅ | In-page view: file browse, format, clean toggle, destructive confirmation checkbox, progress bar. **Clean toggle is disabled for plain format** (psql can't DROP-before-CREATE) with a hint to use Custom Archive |
| DB-to-DB sync | ✅ | In-page view: source/target connection pickers, schema dropdown, pipe-based pg_dump → pg_restore. **pg_restore side passes `--clean --if-exists`**, so sync works into a non-empty target (UI already requires destructive-overwrite confirmation). Core logic in headless-testable `run_db_sync` |
| Unified Tools view | ✅ | Backup / Restore / DB Sync merged into a single **Tools** nav item; operation-switcher dropdown in the view toolbar, existing forms rendered below |
| Bundled PostgreSQL client tools | ✅ | Static pg_dump/pg_restore/psql shipped as Tauri resources; system-first, bundled-fallback resolution via resource_dir (v0.7.5). `resources/mysql_tools` (mariadb-dump / mariadb) is **still bundled and still required** — for MySQL **backup** and **DB Sync** only; MySQL **restore** is native since v0.8.2 so no MySQL client is needed for it |
| Bundled PostgreSQL client tools | ✅ | Static pg_dump/pg_restore/psql shipped as Tauri resources; system-first, bundled-fallback resolution (v0.7.5). **Both engines now resolve through one headless `db/tool_resolver.rs`** (v0.8.3): trusted absolute dirs (Homebrew, MacPorts, Postgres.app, `/usr/local`) → inherited `PATH` → bundled, with the bundled tools read from the nested `resources/pg_tools` / `resources/mysql_tools` path Tauri actually produces (the pre-0.8.3 join dropped the `resources/` component, so bundled fallback never worked). Candidates must be regular files that run `--version` (1 s per probe, 3 s aggregate). `resources/mysql_tools` (mariadb-dump / mariadb) is required for MySQL **backup** and **DB Sync** only; MySQL restore is native since v0.8.2 |
| SQLite .dump (backup/restore) | ✅ | Backup a SQLite database to a portable SQL dump and restore it back, matching the pg_dump UX (v0.7.8) |
| Backup / Restore / Sync for MySQL & SQLite | ✅ | MySQL **backup** via bundled/system `mariadb-dump` (`mysqldump`-compatible, system-first); DB-to-DB sync extended beyond PostgreSQL (v0.7.8). **MySQL restore runs in-process over the sqlx driver** (v0.8.2) — `run_mysql_restore_on` splits the script (`DELIMITER`-aware) and executes it on one connection, **no `mariadb` client and no subprocess**, so a MySQL 8 user authenticating with `caching_sha2_password` (the case the bundled client cannot reach) restores. Stop-at-first-error, cancellable, 100 MB / 1M-statement caps; MySQL **backup and DB Sync still shell out to the bundled MariaDB client** |
| Backup / Restore / Sync for MySQL & SQLite | ✅ | MySQL **backup** via the resolved dump client — system `mariadb-dump`, then system `mysqldump` (this is what Homebrew `mysql-client` installs), then the bundled `mariadb-dump`; DB-to-DB sync extended beyond PostgreSQL (v0.7.8). A **non-blocking** client/server version warning is emitted at job start when the resolved dump client's major version is below the server's (MariaDB's legacy `5.5.5-<real>-MariaDB` prefix is unwrapped). **MySQL restore runs in-process over the sqlx driver** (v0.8.2) — `run_mysql_restore_on` splits the script (`DELIMITER`-aware) and executes it on one connection, **no `mariadb` client and no subprocess**. MySQL backup and DB Sync still shell out to the resolved dump client |
| MySQL `clean` restore semantics | ✅ | `Clean` drops **only the objects the dump recreates** (`CREATE TABLE`/`VIEW`/`PROCEDURE`/`FUNCTION`/`TRIGGER`/`EVENT`), matching `pg_restore --clean` — an unrelated table in the target survives (`db/mysql_clean.rs`, v0.8.2). Deliberately not `DROP DATABASE`. The drops run with `FOREIGN_KEY_CHECKS = 0` (restored right after) so ordering cannot fail the restore. **MySQL DDL auto-commits and cannot be rolled back**, so a failure stops at the failing statement (reported with its position) and the Restore form warns that a failed restore "may leave this database partially applied" |
| `.sql` file association (open with Gridline) | ✅ | `bundle.fileAssociations` in `tauri.conf.json` registers `.sql` as **SQL Script** (`role: Editor`, **`rank: Alternate`** — Gridline never hijacks the default handler, it only appears under *Open With*, v0.8.2). The OS path is validated (absolute, `.sql` extension, real file, ≤5 MB — `MAX_SQL_FILE_BYTES`, symlinks not followed) and queued in Rust, then drained by the **argument-free** `take_pending_sql_files` command, so the webview can never name a file to read. The text lands in a query tab named after the file and is **never executed** — Run still goes through the destructive-query guard. Re-opening the same file focuses the existing tab without discarding edits. With no connection open the file is stashed and Home shows a banner prompting for one (macOS `RunEvent::Opened`; Windows/Linux argv + single-instance forward) |
| Schema diff / compare | ✅ | Tools → Schema Diff; same-engine snapshot diff; safe items stage into the changes queue (v0.8.0) |