Build Plugin #41
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" ;; | |
| 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-z]+)-v.*$/\1/') | |
| VERSION=$(echo "$TAG" | sed -E 's/^plugin-[a-z]+-v(.*)$/\1/') | |
| 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 | |
| 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:datlechin/tablepro-plugins.git "$WORK/registry" | |
| cd "$WORK/registry" | |
| 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": 1, | |
| "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 config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add plugins.json | |
| git commit -m "Update $DISPLAY_NAME to v$VERSION" | |
| git push | |
| ssh-add -D | |
| eval "$(ssh-agent -k)" | |
| cd - | |
| rm -rf "$WORK" | |
| fi | |
| echo "$DISPLAY_NAME v$VERSION released" |