Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .github/assets/preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 6 additions & 3 deletions .github/workflows/auto-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@ jobs:
- uses: actions/checkout@v5
with:
fetch-depth: 0
# If master has branch protection that blocks the default bot token,
# replace with: token: ${{ secrets.RELEASE_TOKEN }}
token: ${{ secrets.GITHUB_TOKEN }}
# Push the version-bump commit + tag with the RELEASES_TOKEN PAT, not
# the default GITHUB_TOKEN: tags pushed by GITHUB_TOKEN never trigger
# other workflows (GitHub Actions rule), which is why release.yml had
# to be dispatched manually after every auto-release. With the PAT,
# the tag push starts the Release build automatically.
token: ${{ secrets.RELEASES_TOKEN }}

- uses: actions/setup-node@v5
with:
Expand Down
51 changes: 34 additions & 17 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Build Stroke for Linux, Windows, and macOS and publish to GitHub Releases.
#
# The source repo is PRIVATE, so releases are published to a separate PUBLIC
# repo (RELEASES_REPO below) that anonymous users, the in-app updater, Scoop,
# and Homebrew can all reach. Publishing cross-repo requires a fine-grained
# PAT with "Contents: read/write" on RELEASES_REPO, stored as the
# RELEASES_TOKEN secret in this repo (the default GITHUB_TOKEN only works
# within this repo).
#
# Create a release:
# git tag v0.1.0
# git push origin v0.1.0
Expand All @@ -24,6 +31,10 @@ permissions:

env:
RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref_name }}
# Public repo that hosts release assets, latest.json, and the Scoop bucket.
# Keeping the old "stroke" name means updater endpoints and package-manager
# URLs baked into already-shipped versions continue to resolve.
RELEASES_REPO: broisnischal/stroke

# ── 1. Create the release before any build starts ────────────────────────────
jobs:
Expand All @@ -34,7 +45,7 @@ jobs:

- name: Create GitHub release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.RELEASES_TOKEN }}
TAG: ${{ env.RELEASE_TAG }}
SHA: ${{ github.sha }}
run: |
Expand Down Expand Up @@ -89,9 +100,12 @@ jobs:
Built from \`${SHA}\`.
EOF

# No --target: the source SHA lives in the private repo, not in
# RELEASES_REPO. The tag there just points at its default branch;
# the real source commit is recorded in the notes ("Built from …").
gh release create "$TAG" \
--repo "$RELEASES_REPO" \
--title "Stroke $TAG" \
--target "$SHA" \
--notes-file /tmp/notes.md \
$PRERELEASE

Expand Down Expand Up @@ -171,7 +185,7 @@ jobs:
if: matrix.name == 'macos-aarch64'
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.RELEASES_TOKEN }}
TAG: ${{ env.RELEASE_TAG }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
Expand All @@ -187,7 +201,7 @@ jobs:
cp "$DIR/macos/Stroke.app.tar.gz" stroke_aarch64.app.tar.gz
cp "$DIR/macos/Stroke.app.tar.gz.sig" updater.sig

gh release upload "$TAG" \
gh release upload "$TAG" --repo "$RELEASES_REPO" \
"$DIR/dmg/stroke_${VERSION}_aarch64.dmg" \
stroke_aarch64.app.tar.gz \
--clobber
Expand All @@ -197,7 +211,7 @@ jobs:
if: matrix.name == 'macos-x64'
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.RELEASES_TOKEN }}
TAG: ${{ env.RELEASE_TAG }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
Expand All @@ -213,7 +227,7 @@ jobs:
cp "$DIR/macos/Stroke.app.tar.gz" stroke_x64.app.tar.gz
cp "$DIR/macos/Stroke.app.tar.gz.sig" updater.sig

gh release upload "$TAG" \
gh release upload "$TAG" --repo "$RELEASES_REPO" \
"$DIR/dmg/stroke_${VERSION}_x64.dmg" \
stroke_x64.app.tar.gz \
--clobber
Expand All @@ -223,7 +237,7 @@ jobs:
if: matrix.name == 'linux'
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.RELEASES_TOKEN }}
TAG: ${{ env.RELEASE_TAG }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
Expand All @@ -249,7 +263,7 @@ jobs:
cp "stroke_${VERSION}_amd64.AppImage.tar.gz" stroke_amd64.AppImage.tar.gz
cp "stroke_${VERSION}_amd64.AppImage.tar.gz.sig" updater.sig

gh release upload "$TAG" \
gh release upload "$TAG" --repo "$RELEASES_REPO" \
"stroke_${VERSION}_amd64.deb" \
"stroke-${VERSION}-1.x86_64.rpm" \
"stroke_${VERSION}_amd64.AppImage" \
Expand All @@ -261,7 +275,7 @@ jobs:
if: matrix.name == 'windows'
shell: pwsh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.RELEASES_TOKEN }}
TAG: ${{ env.RELEASE_TAG }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
Expand Down Expand Up @@ -304,7 +318,7 @@ jobs:
Copy-Item $zip "stroke_x64-setup.nsis.zip"
Copy-Item "$zip.sig" "updater.sig"

gh release upload "$env:TAG" `
gh release upload "$env:TAG" --repo "$env:RELEASES_REPO" `
"$dir/msi/stroke_${version}_x64_en-US.msi" `
"$dir/nsis/stroke_${version}_x64-setup.exe" `
"stroke_x64-setup.nsis.zip" `
Expand Down Expand Up @@ -334,7 +348,7 @@ jobs:
TAG: ${{ env.RELEASE_TAG }}
run: |
export VERSION=$(node -p "require('./src-tauri/tauri.conf.json').version")
export BASE="https://github.com/broisnischal/stroke/releases/download/${TAG}"
export BASE="https://github.com/${RELEASES_REPO}/releases/download/${TAG}"

node - <<'JSEOF'
const fs = require('fs')
Expand Down Expand Up @@ -387,22 +401,25 @@ jobs:

- name: Upload latest.json
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.RELEASES_TOKEN }}
TAG: ${{ env.RELEASE_TAG }}
run: gh release upload "$TAG" latest.json --clobber
run: gh release upload "$TAG" --repo "$RELEASES_REPO" latest.json --clobber

# ── 4. Refresh the Scoop manifest in this repo (Windows clean-install path) ───
# ── 4. Refresh the Scoop manifest in the public releases repo ────────────────
# Scoop clones the bucket repo anonymously, so the manifest must live in
# RELEASES_REPO, not in this private source repo.
update-scoop:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
ref: master
repository: broisnischal/stroke
token: ${{ secrets.RELEASES_TOKEN }}

- name: Update bucket/stroke.json
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.RELEASES_TOKEN }}
TAG: ${{ env.RELEASE_TAG }}
run: |
VERSION="${TAG#v}"
Expand Down Expand Up @@ -448,7 +465,7 @@ jobs:
echo "Scoop manifest already current."
else
git commit -m "chore(scoop): stroke v${VERSION}"
git push origin master
git push origin HEAD
fi

# Optional instant Homebrew tap refresh. Without a PAT this is skipped and
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,11 +266,14 @@ Connections are saved locally. Stroke reopens your last connection on launch.

## Build from source

The source repo is private — `github.com/broisnischal/stroke` is the public
releases/bucket repo. Collaborators clone the source repo:

Requires [Node.js](https://nodejs.org) 18+ and the [Rust toolchain](https://rustup.rs).

```bash
git clone https://github.com/broisnischal/stroke
cd stroke
git clone https://github.com/broisnischal/stroke-app
cd stroke-app
npm install
npm run tauri # dev
npm run tauri:build # release binary
Expand Down
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
const parsed = raw ? JSON.parse(raw) : null
const valid = [
'light', 'parchment', 'ice', 'github-light',
'dark', 'midnight', 'vitesse', 'slate', 'forest', 'mono',
'dark', 'graphite', 'midnight', 'vitesse', 'slate', 'forest', 'mono',
'rose-pine', 'catppuccin', 'solarized',
]
const lightThemes = ['light', 'parchment', 'ice', 'github-light']
Expand Down
30 changes: 30 additions & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,33 @@ objc2-web-kit = { version = "0.3", features = ["WKWebView"] }
strip = true
lto = true
codegen-units = 1

# ── Dev-profile crypto optimization ───────────────────────────────────────────
# Connect-time auth runs heavy crypto: Postgres SCRAM-SHA-256 (PBKDF2, 4096
# HMAC-SHA-256 rounds via sha2/hmac), MySQL caching_sha2_password (RSA via
# rsa/num-bigint-dig), and rustls/ring TLS handshakes. Unoptimized, these are
# 50-200x slower — debug-build connects took seconds while release was instant.
# Optimizing only these leaf crates keeps incremental dev builds fast; they are
# compiled once and never change with app code.
[profile.dev.package.sha2]
opt-level = 3
[profile.dev.package.sha1]
opt-level = 3
[profile.dev.package.hmac]
opt-level = 3
[profile.dev.package.digest]
opt-level = 3
[profile.dev.package.md-5]
opt-level = 3
[profile.dev.package.rsa]
opt-level = 3
[profile.dev.package.num-bigint-dig]
opt-level = 3
[profile.dev.package.ring]
opt-level = 3
[profile.dev.package.rustls]
opt-level = 3
[profile.dev.package.curve25519-dalek]
opt-level = 3
[profile.dev.package.block-buffer]
opt-level = 3
15 changes: 13 additions & 2 deletions src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,11 @@ pub fn toggle_devtools(window: tauri::WebviewWindow) {
use crate::db::{
connect, connect_clickhouse, connect_d1, connect_duckdb, connect_libsql, connect_mssql, connect_mysql, connect_sqlite, disconnect,
delete_table_row, delete_table_rows, execute_ddl, execute_sql, execute_sql_multi, get_table_rows, insert_table_row,
list_schemas, list_tables, list_indexes, list_enums, list_functions, list_triggers, list_sequences, ping_connection,
list_schemas, list_tables, list_indexes, list_enums, list_functions, list_triggers, list_sequences, ping_connection, table_row_counts,
truncate_table, drop_table, get_table_column_structure, get_incoming_foreign_keys, get_table_ddl as db_get_table_ddl,
test_clickhouse_connection, test_connection, test_d1_connection, test_duckdb_connection, test_libsql_connection, test_mssql_connection, test_mysql_connection, test_sqlite_connection,
update_table_cell, ConnectionConfig, D1Config, DbState, EnumInfo, FunctionInfo, ExplainResult, IndexInfo, LibSqlConfig,
SqlResult, SqliteConfig, TableInfo, TableRows, TriggerInfo, SequenceInfo,
SqlResult, SqliteConfig, TableInfo, TableRowCount, TableRows, TriggerInfo, SequenceInfo,
ColumnStructureRow, IncomingForeignKey, InsertRowResult, TunnelState,
explain_pg, explain_mysql, explain_sqlite,
};
Expand Down Expand Up @@ -310,6 +310,17 @@ pub async fn pg_list_tables(
list_tables(state, schema).await
}

/// Background pass: exact COUNT(*) for tables `pg_list_tables` returned with an
/// unknown (-1) row count. See `db::schema::table_row_counts`.
#[tauri::command]
pub async fn pg_table_row_counts(
state: State<'_, DbState>,
schema: String,
tables: Vec<String>,
) -> Result<Vec<TableRowCount>, String> {
table_row_counts(state, schema, tables).await
}

#[tauri::command]
pub async fn pg_list_indexes(
state: State<'_, DbState>,
Expand Down
Loading
Loading