release: v0.24.0 #66
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" |