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
113 changes: 113 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
name: Release Desktop

on:
push:
tags:
- 'v*'
workflow_dispatch:

jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- platform: macos-latest # Apple Silicon (arm64)
- platform: ubuntu-latest # .deb, .rpm, .AppImage
- platform: windows-latest # .msi, .exe

runs-on: ${{ matrix.platform }}

steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # need full history + tags for release-notes step

- name: Install Linux dependencies
if: matrix.platform == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
libappindicator3-dev \
librsvg2-dev \
patchelf \
libssl-dev

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Rust cache
uses: swatinem/rust-cache@v2
with:
workspaces: './frontend/src-tauri -> target'

- name: Set up Bun
uses: oven-sh/setup-bun@v2

- name: Install frontend dependencies
working-directory: frontend
run: bun install --frozen-lockfile

- name: Compile release notes from changelog
id: notes
shell: bash
run: |
set -euo pipefail

# Find the previous tag (the one before the one being released).
# Fallback: empty means "no previous release — include everything".
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
if [ -n "$PREV_TAG" ]; then
PREV_DATE=$(git log -1 --format=%cs "$PREV_TAG")
echo "Previous tag: $PREV_TAG (committed $PREV_DATE)"
else
PREV_DATE="0000-00-00"
echo "No previous tag — including all changelog entries"
fi

CHANGELOG_DIR="frontend/src/data/changelog"
{
echo 'body<<RELEASE_BODY_EOF'
echo "## What's new"
echo
FOUND=0
# Files are named YYYY-MM-DD.md — lexicographic sort == chronological
for f in $(ls "$CHANGELOG_DIR"/*.md 2>/dev/null | sort); do
DATE=$(basename "$f" .md)
if [[ "$DATE" > "$PREV_DATE" ]]; then
echo "### $DATE"
echo
cat "$f"
echo
FOUND=1
fi
done
if [ "$FOUND" -eq 0 ]; then
echo "_No new changelog entries since \`$PREV_TAG\`. Add notes manually before publishing._"
echo
fi
echo '---'
echo
echo '## Install'
echo
echo '**macOS** — the `.dmg` is unsigned. On first launch, right-click the app → Open, then confirm. One-time step.'
echo
echo '**Windows** — the `.msi` is unsigned. SmartScreen may warn — click "More info" → "Run anyway".'
echo
echo '**Linux** — `.AppImage` is portable (chmod +x and run). `.deb` / `.rpm` for system install.'
echo 'RELEASE_BODY_EOF'
} >> "$GITHUB_OUTPUT"

- name: Build and release
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
projectPath: frontend
tagName: ${{ github.ref_name }}
releaseName: 'Squill Desktop ${{ github.ref_name }}'
releaseBody: ${{ steps.notes.outputs.body }}
releaseDraft: true
prerelease: false
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,7 @@ dist-ssr
*.db
.ruff_cache
/*.jpeg

# Tauri
frontend/src-tauri/target/
frontend/src-tauri/gen/
55 changes: 55 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,61 @@ cd backend
python -m scripts.benchmarks.hex_remover
```

## Releasing the desktop app

Releases are built by [`.github/workflows/release.yml`](.github/workflows/release.yml) on GitHub Actions and published to [Releases](https://github.com/MaxHalford/squill/releases). Each tag produces macOS (arm64), Windows, and Linux bundles.

### Philosophy

The **web app is bleeding-edge** — deployed continuously from `main`, whatever's there is what users get. The **desktop app is versioned** — users on a tagged release know exactly what code they're running, and it doesn't change under them until they update.

### Version format

Standard [SemVer](https://semver.org/): `MAJOR.MINOR.PATCH`.

- **PATCH** — bug fixes, no behavioural change (`0.1.0 → 0.1.1`)
- **MINOR** — new features, backwards-compatible (`0.1.1 → 0.2.0`)
- **MAJOR** — breaking changes or milestone releases (`0.x → 1.0.0`)

Stay in `0.x` until we're confident the UX is stable enough to commit to it. `1.0.0` is a statement.

### Release steps

1. Bump the version in two places (they must match the tag):
- `frontend/src-tauri/tauri.conf.json` → `"version"`
- `frontend/src-tauri/Cargo.toml` → `version`

2. Commit, tag, push:

```bash
git add -A && git commit -m "Release 0.2.0"
git tag v0.2.0
git push origin main v0.2.0
```

3. The workflow runs (~15–25 min across the three platforms) and creates a **draft** release with all bundles attached. The release body is auto-compiled from every `frontend/src/data/changelog/YYYY-MM-DD.md` entry whose date is **after** the previous tag's commit date — so the web changelog doubles as desktop release notes, zero extra work.

4. Review the draft on GitHub Releases. Tweak the compiled notes if needed, then click **Publish**.

### Testing the workflow

Before your first real tag, trigger a throwaway run to catch config issues:

```bash
git tag v0.0.1-test
git push origin v0.0.1-test
# ... workflow runs, produces draft release
# delete the draft and the tag afterwards:
git push --delete origin v0.0.1-test
git tag -d v0.0.1-test
```

Or use the **Run workflow** button in the Actions tab (`workflow_dispatch`).

### Signing (not set up)

All bundles currently ship **unsigned**. Users see a Gatekeeper / SmartScreen warning on first launch. The release notes tell them how to bypass it. If we later pay for an Apple Developer account ($99/yr) or a Windows code-signing cert, secrets slot into the `tauri-action` step without changing the rest of the workflow.

## Useful links

- https://github.com/settings/developers
Expand Down
17 changes: 2 additions & 15 deletions backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,12 @@
from routers.connections import router as connections_router
from routers.hex_remover import router as hex_remover_router
from routers.spell_caster import router as spell_caster_router
from routers.postgres import router as postgres_router
from routers.snowflake import router as snowflake_router
from routers.clickhouse import router as clickhouse_router
from routers.mysql import router as mysql_router
from routers.snowflake import router as snowflake_router
from routers.user import router as user_router
from routers.wizard import router as wizard_router
from routers.ws import router as ws_router
from mcp_server import mcp
from services.postgres_pool import PostgresPoolManager
from services.snowflake_pool import SnowflakeConnectionManager
from services.clickhouse_pool import ClickHouseConnectionManager
from services.mysql_pool import MysqlConnectionManager

# Build MCP sub-app once at module level (routes + middleware are set up here)
_mcp_app = mcp.streamable_http_app()
Expand Down Expand Up @@ -64,11 +58,6 @@ async def lifespan(app: FastAPI):

# Shutdown
logger.info("Shutting down...")
await PostgresPoolManager.close_all()
await SnowflakeConnectionManager.close_all()
await ClickHouseConnectionManager.close_all()
await MysqlConnectionManager.close_all()
logger.info("All database connections closed")


app = FastAPI(lifespan=lifespan, default_response_class=ORJSONResponse)
Expand All @@ -90,10 +79,8 @@ async def lifespan(app: FastAPI):
app.include_router(hex_remover_router)
app.include_router(spell_caster_router)
app.include_router(connections_router)
app.include_router(postgres_router)
app.include_router(snowflake_router)
app.include_router(clickhouse_router)
app.include_router(mysql_router)
app.include_router(snowflake_router)
app.include_router(user_router)
app.include_router(wizard_router)
app.include_router(ws_router)
Expand Down
Loading
Loading