Skip to content

release: v0.24.0

release: v0.24.0 #66

Workflow file for this run

name: Build Plugin
on:
push:
tags: ["plugin-*-v*"]
workflow_dispatch:
inputs:
tags:
description: "Plugin tags, comma-separated (e.g., plugin-oracle-v1.0.1,plugin-sqlite-v1.0.1)"
required: true
type: string
permissions:
contents: write
env:
XCODE_PROJECT: TablePro.xcodeproj
jobs:
resolve-tags:
name: Resolve Plugin Tags
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.tags.outputs.matrix }}
steps:
- id: tags
run: |
if [ -n "${{ inputs.tags }}" ]; then
IFS=',' read -ra TAGS <<< "${{ inputs.tags }}"
else
TAGS=("${{ github.ref_name }}")
fi
JSON='{"include":['
FIRST=true
for TAG in "${TAGS[@]}"; do
TAG=$(echo "$TAG" | xargs)
if [ "$FIRST" = true ]; then FIRST=false; else JSON+=','; fi
JSON+="{\"tag\":\"$TAG\"}"
done
JSON+=']}'
echo "matrix=$JSON" >> "$GITHUB_OUTPUT"
echo "Matrix: $JSON"
build-plugin:
name: "Build ${{ matrix.tag }}"
needs: resolve-tags
runs-on: macos-15
timeout-minutes: 30
strategy:
matrix: ${{ fromJson(needs.resolve-tags.outputs.matrix) }}
fail-fast: false
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
lfs: true
- name: Pull LFS files
run: git lfs pull
- name: Select Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "26.2"
- name: Download static libraries
env:
GH_TOKEN: ${{ github.token }}
run: scripts/download-libs.sh
- name: Import signing certificate
env:
CERTIFICATES_P12: ${{ secrets.CERTIFICATES_P12 }}
CERTIFICATES_PASSWORD: ${{ secrets.CERTIFICATES_PASSWORD }}
run: |
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
security create-keychain -p "" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "" "$KEYCHAIN_PATH"
echo "$CERTIFICATES_P12" | base64 --decode > $RUNNER_TEMP/certificate.p12
security import $RUNNER_TEMP/certificate.p12 -P "$CERTIFICATES_PASSWORD" \
-A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
security set-key-partition-list -S apple-tool:,apple: -k "" "$KEYCHAIN_PATH"
security list-keychain -d user -s "$KEYCHAIN_PATH" login.keychain
- name: Configure notarization
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
NOTARY_PASSWORD: ${{ secrets.NOTARY_PASSWORD }}
run: |
xcrun notarytool store-credentials "TablePro" \
--apple-id "$APPLE_ID" \
--team-id "$APPLE_TEAM_ID" \
--password "$NOTARY_PASSWORD"
- name: Build and release plugin
env:
REGISTRY_DEPLOY_KEY: ${{ secrets.REGISTRY_DEPLOY_KEY }}
GH_TOKEN: ${{ github.token }}
run: |
TAG="${{ matrix.tag }}"
echo "Processing: $TAG"
# Get current app version for minAppVersion
MIN_APP_VERSION=$(sed -n 's/.*MARKETING_VERSION = \(.*\);/\1/p' \
TablePro.xcodeproj/project.pbxproj | head -1 | tr -d ' ')
resolve_plugin_info() {
local plugin_name=$1
case "$plugin_name" in
oracle)
TARGET="OracleDriver"; BUNDLE_ID="com.TablePro.OracleDriver"
DISPLAY_NAME="Oracle Driver"; SUMMARY="Oracle Database 12c+ driver via OracleNIO"
DB_TYPE_IDS='["Oracle"]'; ICON="server.rack"; BUNDLE_NAME="OracleDriver"
CATEGORY="database-driver"; HOMEPAGE="https://docs.tablepro.app/databases/oracle" ;;
clickhouse)
TARGET="ClickHouseDriver"; BUNDLE_ID="com.TablePro.ClickHouseDriver"
DISPLAY_NAME="ClickHouse Driver"; SUMMARY="ClickHouse OLAP database driver via HTTP interface"
DB_TYPE_IDS='["ClickHouse"]'; ICON="chart.bar.xaxis"; BUNDLE_NAME="ClickHouseDriver"
CATEGORY="database-driver"; HOMEPAGE="https://docs.tablepro.app/databases/clickhouse" ;;
sqlite)
TARGET="SQLiteDriver"; BUNDLE_ID="com.TablePro.SQLiteDriver"
DISPLAY_NAME="SQLite Driver"; SUMMARY="SQLite embedded database driver"
DB_TYPE_IDS='["SQLite"]'; ICON="internaldrive"; BUNDLE_NAME="SQLiteDriver"
CATEGORY="database-driver"; HOMEPAGE="https://docs.tablepro.app/databases/sqlite" ;;
duckdb)
TARGET="DuckDBDriver"; BUNDLE_ID="com.TablePro.DuckDBDriver"
DISPLAY_NAME="DuckDB Driver"; SUMMARY="DuckDB analytical database driver"
DB_TYPE_IDS='["DuckDB"]'; ICON="bird"; BUNDLE_NAME="DuckDBDriver"
CATEGORY="database-driver"; HOMEPAGE="https://docs.tablepro.app/databases/duckdb" ;;
cassandra)
TARGET="CassandraDriver"; BUNDLE_ID="com.TablePro.CassandraDriver"
DISPLAY_NAME="Cassandra Driver"; SUMMARY="Apache Cassandra and ScyllaDB driver via DataStax C driver"
DB_TYPE_IDS='["Cassandra", "ScyllaDB"]'; ICON="cassandra-icon"; BUNDLE_NAME="CassandraDriver"
CATEGORY="database-driver"; HOMEPAGE="https://docs.tablepro.app/databases/cassandra" ;;
etcd)
TARGET="EtcdDriverPlugin"; BUNDLE_ID="com.TablePro.EtcdDriverPlugin"
DISPLAY_NAME="etcd Driver"; SUMMARY="etcd v3 key-value store driver with prefix-tree browsing and lease management"
DB_TYPE_IDS='["etcd"]'; ICON="etcd-icon"; BUNDLE_NAME="EtcdDriverPlugin"
CATEGORY="database-driver"; HOMEPAGE="https://docs.tablepro.app/databases/etcd" ;;
mssql)
TARGET="MSSQLDriver"; BUNDLE_ID="com.TablePro.MSSQLDriver"
DISPLAY_NAME="MSSQL Driver"; SUMMARY="Microsoft SQL Server driver via FreeTDS"
DB_TYPE_IDS='["SQL Server"]'; ICON="mssql-icon"; BUNDLE_NAME="MSSQLDriver"
CATEGORY="database-driver"; HOMEPAGE="https://docs.tablepro.app/databases/mssql" ;;
mongodb)
TARGET="MongoDBDriver"; BUNDLE_ID="com.TablePro.MongoDBDriver"
DISPLAY_NAME="MongoDB Driver"; SUMMARY="MongoDB document database driver via libmongoc"
DB_TYPE_IDS='["MongoDB"]'; ICON="mongodb-icon"; BUNDLE_NAME="MongoDBDriver"
CATEGORY="database-driver"; HOMEPAGE="https://docs.tablepro.app/databases/mongodb" ;;
redis)
TARGET="RedisDriver"; BUNDLE_ID="com.TablePro.RedisDriver"
DISPLAY_NAME="Redis Driver"; SUMMARY="Redis in-memory data store driver via hiredis"
DB_TYPE_IDS='["Redis"]'; ICON="redis-icon"; BUNDLE_NAME="RedisDriver"
CATEGORY="database-driver"; HOMEPAGE="https://docs.tablepro.app/databases/redis" ;;
cloudflare-d1)
TARGET="CloudflareD1DriverPlugin"; BUNDLE_ID="com.TablePro.CloudflareD1DriverPlugin"
DISPLAY_NAME="Cloudflare D1 Driver"; SUMMARY="Cloudflare D1 serverless SQLite-compatible database driver via REST API"
DB_TYPE_IDS='["Cloudflare D1"]'; ICON="cloudflare-d1-icon"; BUNDLE_NAME="CloudflareD1DriverPlugin"
CATEGORY="database-driver"; HOMEPAGE="https://docs.tablepro.app/databases/cloudflare-d1" ;;
dynamodb)
TARGET="DynamoDBDriverPlugin"; BUNDLE_ID="com.TablePro.DynamoDBDriverPlugin"
DISPLAY_NAME="DynamoDB Driver"; SUMMARY="Amazon DynamoDB driver with PartiQL queries and AWS IAM/Profile/SSO authentication"
DB_TYPE_IDS='["DynamoDB"]'; ICON="dynamodb-icon"; BUNDLE_NAME="DynamoDBDriverPlugin"
CATEGORY="database-driver"; HOMEPAGE="https://docs.tablepro.app/databases/dynamodb" ;;
xlsx)
TARGET="XLSXExport"; BUNDLE_ID="com.TablePro.XLSXExportPlugin"
DISPLAY_NAME="XLSX Export"; SUMMARY="Export data to Microsoft Excel XLSX format"
DB_TYPE_IDS='null'; ICON="doc.richtext"; BUNDLE_NAME="XLSXExport"
CATEGORY="export-format"; HOMEPAGE="https://docs.tablepro.app/features/export" ;;
mql)
TARGET="MQLExport"; BUNDLE_ID="com.TablePro.MQLExportPlugin"
DISPLAY_NAME="MQL Export"; SUMMARY="Export MongoDB data as MQL statements"
DB_TYPE_IDS='null'; ICON="doc.text"; BUNDLE_NAME="MQLExport"
CATEGORY="export-format"; HOMEPAGE="https://docs.tablepro.app/features/export" ;;
sqlimport)
TARGET="SQLImport"; BUNDLE_ID="com.TablePro.SQLImportPlugin"
DISPLAY_NAME="SQL Import"; SUMMARY="Import data from SQL dump files"
DB_TYPE_IDS='null'; ICON="square.and.arrow.down"; BUNDLE_NAME="SQLImport"
CATEGORY="import-format"; HOMEPAGE="https://docs.tablepro.app/features/import" ;;
*) echo "Unknown plugin: $plugin_name"; return 1 ;;
esac
}
PLUGIN_NAME=$(echo "$TAG" | sed -E 's/^plugin-([a-z0-9-]+)-v([0-9].*)$/\1/')
VERSION=$(echo "$TAG" | sed -E 's/^plugin-([a-z0-9-]+)-v([0-9].*)$/\2/')
resolve_plugin_info "$PLUGIN_NAME"
echo "Building $TARGET v$VERSION"
# Build Cassandra dependencies if needed
if [ "$PLUGIN_NAME" = "cassandra" ]; then
./scripts/build-cassandra.sh both
fi
# Build both architectures
./scripts/build-plugin.sh "$TARGET" arm64
./scripts/build-plugin.sh "$TARGET" x86_64
# Capture SHA-256
ARM64_SHA=$(cat "build/Plugins/${BUNDLE_NAME}-arm64.zip.sha256")
X86_SHA=$(cat "build/Plugins/${BUNDLE_NAME}-x86_64.zip.sha256")
# Notarize if enabled
if [ "${NOTARIZE_PLUGINS:-}" = "true" ]; then
for zip in build/Plugins/${BUNDLE_NAME}-*.zip; do
xcrun notarytool submit "$zip" \
--keychain-profile "TablePro" \
--wait
done
fi
# Create GitHub Release
RELEASE_BODY="## $DISPLAY_NAME v$VERSION
Plugin release for TablePro.
### Installation
TablePro will prompt you to install this plugin automatically when you select the database type. You can also install manually via **Settings > Plugins > Browse**.
### SHA-256
- ARM64: \`$ARM64_SHA\`
- x86_64: \`$X86_SHA\`"
# Delete existing release if any, then create
gh release delete "$TAG" --yes 2>/dev/null || true
gh release create "$TAG" \
--title "$DISPLAY_NAME v$VERSION" \
--notes "$RELEASE_BODY" \
build/Plugins/${BUNDLE_NAME}-arm64.zip \
build/Plugins/${BUNDLE_NAME}-x86_64.zip
# Update plugin registry (with retry to handle parallel pushes)
if [ -n "${REGISTRY_DEPLOY_KEY:-}" ]; then
ARM64_URL="https://github.com/${{ github.repository }}/releases/download/${TAG}/${BUNDLE_NAME}-arm64.zip"
X86_64_URL="https://github.com/${{ github.repository }}/releases/download/${TAG}/${BUNDLE_NAME}-x86_64.zip"
WORK=$(mktemp -d)
eval "$(ssh-agent -s)"
echo "$REGISTRY_DEPLOY_KEY" | ssh-add -
git clone git@github.com:TableProApp/plugins.git "$WORK/registry"
cd "$WORK/registry"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Retry loop: pull latest, apply update, push — retry if another job pushed first
MAX_RETRIES=10
for attempt in $(seq 1 $MAX_RETRIES); do
echo "Registry update attempt $attempt/$MAX_RETRIES"
# Reset any previous commit and pull latest
git reset --hard origin/main
git pull --rebase origin main
python3 - \
"$BUNDLE_ID" "$DISPLAY_NAME" "$VERSION" "$SUMMARY" \
"$DB_TYPE_IDS" "$ARM64_URL" "$ARM64_SHA" \
"$X86_64_URL" "$X86_SHA" "$MIN_APP_VERSION" \
"$ICON" "$HOMEPAGE" "$CATEGORY" \
<<'PYTHON_SCRIPT'
import json, sys
bundle_id, name, version, summary = sys.argv[1:5]
db_type_ids = json.loads(sys.argv[5])
arm64_url, arm64_sha = sys.argv[6], sys.argv[7]
x86_64_url, x86_64_sha = sys.argv[8], sys.argv[9]
min_app_version, icon, homepage = sys.argv[10], sys.argv[11], sys.argv[12]
category = sys.argv[13] if len(sys.argv) > 13 else "database-driver"
with open("plugins.json", "r") as f:
manifest = json.load(f)
entry = {
"id": bundle_id, "name": name, "version": version,
"summary": summary,
"author": {"name": "TablePro", "url": "https://tablepro.app"},
"homepage": homepage, "category": category,
"databaseTypeIds": db_type_ids,
"downloadURL": arm64_url, "sha256": arm64_sha,
"binaries": [
{"architecture": "arm64", "downloadURL": arm64_url, "sha256": arm64_sha},
{"architecture": "x86_64", "downloadURL": x86_64_url, "sha256": x86_64_sha}
],
"minAppVersion": min_app_version,
"minPluginKitVersion": 2,
"iconName": icon, "isVerified": True
}
manifest["plugins"] = [p for p in manifest["plugins"] if p["id"] != bundle_id]
manifest["plugins"].append(entry)
with open("plugins.json", "w") as f:
json.dump(manifest, f, indent=2)
f.write("\n")
PYTHON_SCRIPT
git add plugins.json
git commit -m "Update $DISPLAY_NAME to v$VERSION"
if git push; then
echo "Registry updated successfully on attempt $attempt"
break
fi
if [ "$attempt" -eq "$MAX_RETRIES" ]; then
echo "::error::Failed to push registry update after $MAX_RETRIES attempts"
exit 1
fi
# Jittered backoff: 2-5s base + random to spread parallel retries
DELAY=$((2 + RANDOM % 4))
echo "Push rejected (concurrent update), retrying in ${DELAY}s..."
sleep "$DELAY"
done
ssh-add -D
eval "$(ssh-agent -k)"
cd -
rm -rf "$WORK"
fi
echo "$DISPLAY_NAME v$VERSION released"