Files
beekeeper-studio/.github/workflows/studio-publish.yml
2026-03-12 12:34:15 -06:00

525 lines
20 KiB
YAML

name: Studio - Build & Publish
permissions:
contents: read
actions: write
on:
push:
tags:
- "v*"
paths-ignore:
- "apps/sqltools/**"
jobs:
create_draft_release:
runs-on: "ubuntu-22.04"
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
assets_url: ${{ steps.create_release.outputs.assets_url }}
id: ${{ steps.create_release.outputs.id }}
json: ${{ steps.create_release.outputs.json }}
azure_client_id: ${{ steps.azure_creds.outputs.client_id }}
azure_tenant_id: ${{ steps.azure_creds.outputs.tenant_id }}
azure_keyvault_url: ${{ steps.azure_creds.outputs.keyvault_url }}
steps:
- name: Check out Git repository
uses: actions/checkout@v1
# Requires us to use a github token with more power (to create drafts on another repo)
- name: Create or check draft release
id: create_release
uses: actions/github-script@v7
env:
TAG_NAME: ${{ github.ref_name }}
OWNER: 'beekeeper-studio'
REPO: 'beekeeper-studio'
with:
github-token: ${{ secrets.GH_DEPLOY_TOKEN }}
script: |
const script = require('./.github/scripts/create_draft_release.js')
await script({github, context, core}, process.env.OWNER, process.env.REPO, process.env.TAG_NAME)
- name: Extract Azure credentials from existing secrets
id: azure_creds
env:
KEYVAULT_AUTH: "${{secrets.keyvault_auth}}"
run: |
CLIENT_ID=$(echo "$KEYVAULT_AUTH" | jq -r '.id')
TENANT_ID=$(echo "$KEYVAULT_AUTH" | jq -r '.tenant')
KEYVAULT_URL=$(echo "$KEYVAULT_AUTH" | jq -r '.url')
echo "client_id=$CLIENT_ID" >> $GITHUB_OUTPUT
echo "tenant_id=$TENANT_ID" >> $GITHUB_OUTPUT
echo "keyvault_url=$KEYVAULT_URL" >> $GITHUB_OUTPUT
# electron-builder comes built in with channels -- latest, beta, alpha.
# To support these for deb, rpm, and snap, we need to extract the channel from the package version
# outputs: latest, beta, alpha
# deb_codename: stable, beta
identify_channel:
runs-on: "ubuntu-22.04"
outputs:
channel: ${{steps.extract_channel.outputs.channel}}
deb_codename: ${{steps.extract_channel.outputs.deb_codename}}
steps:
- name: Check out Git repository
uses: actions/checkout@v1
- name: Extract Channel from package.json
id: extract_channel
run: bash ./.github/scripts/extract_channel.sh
release:
runs-on: ${{ matrix.os.name }}
needs: [create_draft_release, identify_channel]
outputs:
mac_x64_yml: ${{ steps.set_yaml.outputs.mac_x64_yml }}
mac_arm64_yml: ${{ steps.set_yaml.outputs.mac_arm64_yml }}
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.snapcraft_token }}
strategy:
fail-fast: false
matrix:
os:
- name: ubuntu-22.04
arch: x64
type: linux
setup_python: true
setup_ruby: true
setup_flatpak: true
- name: windows-2022
arch: x64
type: windows
setup_python: true
- name: ubuntu-arm64
arch: arm64
type: linux
setup_python: true
clean_zfs: true
- name: macos-14-large
arch: x64
type: macos
- name: macos-14
arch: arm64
type: macos
steps:
- name: Check out Git repository
uses: actions/checkout@v1
# Oh the hacks I put in place for the snap build
# Why does it fill up zfs? I don't understand what it does at all
- name: Free Up ZFS Space
run: |
# Force unmount any snapcraft-related ZFS datasets that are busy
sudo zfs list -H -o name | grep "snapcraft" | sort -r | xargs -I{} sudo zfs unmount -f {} 2>/dev/null || true
# Destroy datasets (clones) first, deepest children first
sudo zfs list -H -o name | grep "snapcraft" | sort -r | xargs -I{} sudo zfs destroy -rR {} 2>/dev/null || true
# Then destroy any remaining snapshots
sudo zfs list -H -o name -t snapshot | grep "snapcraft" | xargs -I{} sudo zfs destroy -rR {} 2>/dev/null || true
if: matrix.os.clean_zfs
- name: Install flatpak tools
if: matrix.os.type == 'linux' && matrix.os.arch != 'arm64'
run: bash ./.github/scripts/install-build-deps.sh
- name: Install azuresigntool
run: 'dotnet tool install --global AzureSignTool --version 7.0.1'
if: matrix.os.type == 'windows'
- name: Azure Login
if: matrix.os.type == 'windows'
uses: azure/login@v2
with:
creds: '{"clientId":"${{ needs.create_draft_release.outputs.azure_client_id }}","clientSecret":"${{ secrets.keyvault_auth_secret }}","subscriptionId":"${{ secrets.azure_subscription_id }}","tenantId":"${{ needs.create_draft_release.outputs.azure_tenant_id }}"}'
- uses: ruby/setup-ruby@v1
with:
ruby-version: 3.0.2
if: matrix.os.setup_ruby
- name: Install fpm
run: sudo gem install fpm -v 1.17.0
if: matrix.os.setup_ruby
- name: Update repository
env:
OWNER: 'beekeeper-studio'
REPO: 'beekeeper-studio'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs')
const path = `apps/studio/package.json`
const githubUrl = `git@github.com/${process.env.OWNER}/${process.env.REPO}.git`
const packageJson = JSON.parse(fs.readFileSync(path, 'utf8'));
// Update the 'repository' field
packageJson.repository = githubUrl;
// Write the updated package.json back to the file
fs.writeFileSync(path, JSON.stringify(packageJson, null, 2) + '\n');
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.1
with:
vs-version: '16.11.14'
if: matrix.os.type == 'windows'
- name: "Install python 3.11 (NOT Mac)"
uses: actions/setup-python@v5
with:
python-version: 3.11
if: matrix.os.setup_python
- name: Install python 3.11 (mac arm64)
if: matrix.os.type == 'macos'
id: mac_python
env:
ARCH: ${{ matrix.os.arch }}
run: |
rm -f /usr/local/bin/2to3-3.11
rm -f /usr/local/bin/idle3.11
rm -f /usr/local/bin/pydoc3.11
rm -f /usr/local/bin/python3.11
rm -f /usr/local/bin/python3.11-config
brew install python@3.11
if [ "$ARCH" = "arm64" ]; then
# Add Python 3.11 to PATH for arm64
echo "/opt/homebrew/opt/python@3.11/libexec/bin" >> $GITHUB_PATH
export PATH="/opt/homebrew/opt/python@3.11/libexec/bin:$PATH"
export PYTHON_PATH="/opt/homebrew/opt/python@3.11/libexec/bin/python3"
echo "python_path=$PYTHON_PATH" >> $GITHUB_OUTPUT
else
# Add Python 3.11 to PATH for other architectures
echo "/usr/local/opt/python@3.11/libexec/bin" >> $GITHUB_PATH
export PATH="/usr/local/opt/python@3.11/libexec/bin:$PATH"
export PYTHON_PATH="/usr/local/opt/python@3.11/libexec/bin/python3"
echo "python_path=$PYTHON_PATH" >> $GITHUB_OUTPUT
fi
- name: Verify Python Version
if: matrix.os.type == 'windows'
run: |
$PYTHON_VERSION = python --version | Out-String
Write-Output "Installed Python version: $PYTHON_VERSION"
if (-not $PYTHON_VERSION.StartsWith("Python 3.11")) {
Write-Output "Error: Python version does not start with 3.11"
exit 1
}
shell: powershell
# Verify on everything except windows
- name: Verify Python Version
if: matrix.os.type != 'windows'
run: |
PYTHON_VERSION=$(python3 --version | cut -d " " -f 2)
echo "Installed Python version: $PYTHON_VERSION"
if [[ ! $PYTHON_VERSION == 3.11* ]]; then
echo "Error: Python version does not start with 3.11"
exit 1
fi
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
- name: Cache node_modules
id: cache-nm
uses: actions/cache@v4
with:
path: |
node_modules
apps/studio/node_modules
apps/ui-kit/node_modules
apps/sqltools/node_modules
key: node-modules-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.nvmrc') }}-${{ hashFiles('yarn.lock') }}
- name: Install Snapcraft
uses: samuelmeuli/action-snapcraft@v3
if: matrix.os.type == 'linux' && matrix.os.arch != 'arm64'
- name: Clean cache
if: steps.cache-nm.outputs.cache-hit != 'true'
run: yarn cache clean --all
# FIXME (matthew) Windows needs retries. It sometimes fails to build
# the native oracledb package.
# But only sometimes. I cannot figure out why.
# Someone should at some point.
- name: yarn install (with retry)
if: steps.cache-nm.outputs.cache-hit != 'true'
uses: nick-fields/retry@v2
with:
timeout_minutes: 20
max_attempts: 3
command: "yarn install --frozen-lockfile --network-timeout 100000"
env:
npm_config_node_gyp: ${{ github.workspace }}${{ runner.os == 'Windows' && '\node_modules\node-gyp\bin\node-gyp.js' || '/node_modules/node-gyp/bin/node-gyp.js' }}
- name: Remove dist directory
if: matrix.os.type != 'windows'
run: rm -rf ./apps/studio/dist_electron
- name: Remove dist directory windows
if: matrix.os.type == 'windows'
run: Remove-Item dist_electron -Recurse -ErrorAction Ignore
- name: Prepare for app notarization
if: matrix.os.type == 'macos'
# Import Apple API key for app notarization on macOS
run: |
mkdir -p ~/private_keys/
echo '${{ secrets.apple_key }}' > ~/private_keys/AuthKey_${{ secrets.apple_key_id }}.p8
- name: Decode macOS signing certificate
if: matrix.os.type == 'macos'
env:
DATA: ${{ secrets.mac_dev }}
run: |
echo "$DATA" | base64 --decode > ~/mac-certificate.p12
# Oh hello future Matthew, why do we split out windows build?
# well CSC_LINK gets picked up EVEN THOUGH YOU HAVE
# A CUSTOM sign.js. Obviously a bug, but this is a workaround
# Bug report: https://github.com/electron-userland/electron-builder/issues/8731
- name: Build & Publish (NT)
if: matrix.os.type == 'windows'
env:
KEYVAULT_URL: "${{ needs.create_draft_release.outputs.azure_keyvault_url }}"
KV_WIN_CERTIFICATE: "${{secrets.kv_win_certificate}}"
PYTHON_PATH: "${{'$PYTHON_PATH' }}"
PYTHONPATH: "${{ '$PYTHONPATH' }}"
GH_TOKEN: ${{ secrets.GH_DEPLOY_TOKEN }}
USE_SYSTEM_FPM: true
SNAPCRAFT_BUILD_ENVIRONMENT: 'lxd'
run: yarn run electron:build --publish always
- name: Build & Publish (*NIX)
if: matrix.os.type != 'windows'
env:
APPLE_ID: ${{ secrets.apple_id }}
APPLE_ID_PASSWORD: ${{ secrets.apple_id_password }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.apple_id_password }}
APPLE_TEAM_ID: "7KK583U8H2"
CSC_LINK: "~/mac-certificate.p12"
CSC_KEY_PASSWORD: ${{ secrets.mac_dev_pw }}
PYTHON_PATH: "${{ matrix.os.type == 'macos' && steps.mac_python.outputs.python_path || '$PYTHON_PATH' }}"
PYTHONPATH: "${{ matrix.os.type == 'macos' && steps.mac_python.outputs.python_path || '$PYTHONPATH' }}"
GH_TOKEN: ${{ secrets.GH_DEPLOY_TOKEN }}
USE_SYSTEM_FPM: true
SNAPCRAFT_BUILD_ENVIRONMENT: 'lxd'
run: yarn run electron:build --publish always
- name: Delete latest-mac.yml
if: matrix.os.type == 'macos'
uses: actions/github-script@v7
env:
RELEASE_ID: ${{needs.create_draft_release.outputs.id}}
with:
github-token: ${{ secrets.GH_DEPLOY_TOKEN }}
script: |
const script = require('./.github/scripts/delete_latest_yml.js')
const assetsUrl = '${{ needs.create_draft_release.outputs.assets_url }}'
const channel = '${{needs.identify_channel.outputs.channel}}'
await script({ github, core, context }, assetsUrl, channel)
- name: Invalidate cloudfront cache
if: matrix.os.type == 'linux' && needs.identify_channel.outputs.channel == 'latest'
run: |
aws cloudfront create-invalidation \
--distribution "$DISTRIBUTION" \
--paths "/*"
env:
AWS_ACCESS_KEY_ID: "${{secrets.aws_access_key_id}}"
AWS_SECRET_ACCESS_KEY: "${{secrets.aws_secret_access_key}}"
DISTRIBUTION: ${{secrets.cloudfront_distribution}}
AWS_DEFAULT_REGION: "us-east-1"
- name: Set yaml files
id: set_yaml
if: matrix.os.type == 'macos'
uses: actions/github-script@v7
env:
ARCH: ${{ matrix.os.arch }}
CHANNEL: ${{needs.identify_channel.outputs.channel}}
with:
github-token: ${{ secrets.GH_DEPLOY_TOKEN }}
script: |
const fs = require('fs')
const content = fs.readFileSync(`apps/studio/dist_electron/latest-mac.yml`, 'utf8')
core.setOutput(`mac_${process.env.ARCH}_yml`, content)
- name: Upload DEB/RPM artifacts
uses: actions/upload-artifact@v4
if: matrix.os.type == 'linux'
with:
name: "${{matrix.os.type}}-${{matrix.os.arch}}"
path: |
apps/studio/dist_electron/*.deb
apps/studio/dist_electron/*.rpm
- name: Cleanup artifacts
if: ${{!startsWith(matrix.os.name, 'windows')}}
run: npx rimraf "apps/studio/dist_electron/!(*.exe|*.deb|*.rpm|*.AppImage|*.dmg|*.snap|*.yml|*.flatpak|*.aur)"
- name: Cleanup artifacts Win
if: startsWith(matrix.os.name, 'windows')
shell: powershell
run: Get-ChildItem apps/studio/dist_electron -File | Where-Object { $_.Extension -notin '.exe','.yml' } | Remove-Item -Force
finalize_mac_yml:
needs: [release, create_draft_release, identify_channel]
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v3
- run: npm install js-yaml
- name: Merge yml files
uses: actions/github-script@v7
env:
INTEL_YML: ${{ needs.release.outputs.mac_x64_yml }}
ARM_YML: ${{ needs.release.outputs.mac_arm64_yml }}
with:
github-token: ${{ secrets.GH_DEPLOY_TOKEN }}
script: |
const fs = require('fs');
const yaml = require('js-yaml');
const intelContent = process.env.INTEL_YML
const armContent = process.env.ARM_YML
const mergeFiles = () => {
const intelObject = yaml.load(intelContent);
const armObject = yaml.load(armContent);
const mergedObject = {
...intelObject,
};
mergedObject.files = [
...intelObject.files,
...armObject.files,
];
const dumpOptions = {
// avoids moving the sha512 checksum into its own line
lineWidth: -1,
};
const mergedString = yaml.dump(mergedObject, dumpOptions);
return mergedString;
};
const merge = mergeFiles();
fs.writeFileSync('mac.yml', merge, 'utf8');
- name: Upload fixed mac yml
uses: actions/github-script@v7
env:
RELEASE_ID: ${{ needs.create_draft_release.outputs.id }}
with:
github-token: ${{ secrets.GH_DEPLOY_TOKEN }}
script: |
const fs = require('fs');
const releaseId = process.env.RELEASE_ID;
// Read the merged yml file
const ymlContent = fs.readFileSync('mac.yml', 'utf8');
// Upload the asset using the release ID
await github.rest.repos.uploadReleaseAsset({
owner: 'beekeeper-studio',
repo: 'beekeeper-studio',
release_id: releaseId,
name: 'latest-mac.yml',
data: ymlContent,
headers: {
'content-type': 'application/octet-stream'
}
});
publish_snapcraft:
# The release pushes to edge. We need to promote the edge release if we have a stable release
needs: [release, identify_channel]
runs-on: ubuntu-latest
if: needs.identify_channel.outputs.channel == 'latest'
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.snapcraft_token }}
steps:
- name: Install Snapcraft
uses: samuelmeuli/action-snapcraft@v3
- name: Move release snap from edge to stable
continue-on-error: true
run: |
snapcraft promote beekeeper-studio --from-channel "latest/edge" --to-channel "latest/stable" --yes
publish_repositories:
needs: [release, create_draft_release, identify_channel]
runs-on: ubuntu-24.04
steps:
- name: Check out Git repository
uses: actions/checkout@v1
- uses: ruby/setup-ruby@v1
with:
ruby-version: 3.0.2
- name: Install dependencies for rpm deployment
run: sudo apt-get update && sudo apt-get install -y createrepo-c
- name: Install deb-s3 from GitHub
run: |
curl -sL https://github.com/deb-s3/deb-s3/releases/download/0.11.8/deb-s3-0.11.8.gem -o ./deb-s3.gem
gem install -N ./deb-s3.gem
- name: Import GPG key
id: import_gpg
uses: crazy-max/ghaction-import-gpg@v3
with:
gpg-private-key: ${{ secrets.gpg_key }}
- run: "rm -rf ./artifacts; mkdir artifacts"
- name: Download artifacts
uses: actions/download-artifact@v4
with:
path: ./artifacts
# Only publishing the DEB for the stable channel (for now)
- name: Publish DEB to R2
if: needs.identify_channel.outputs.channel == 'latest'
run: |
deb-s3 upload $(find ./artifacts -type f -name "*.deb") \
--bucket=beekeeper-deb-repo \
--codename=${{needs.identify_channel.outputs.deb_codename}} \
--endpoint=${{secrets.cloudflare_endpoint}} \
--lock \
--sign=${{steps.import_gpg.outputs.keyid}} \
--preserve-versions
env:
AWS_ACCESS_KEY_ID: "${{secrets.cloudflare_key_id}}"
AWS_SECRET_ACCESS_KEY: "${{secrets.cloudflare_secret_access_key}}"
AWS_DEFAULT_REGION: "us-east-1"
# 2025-01-15: AWS made changes to their clis that broke compatibility
# for third party services.
# This works around that.
# https://github.com/aws/aws-sdk-ruby/issues/3166
AWS_REQUEST_CHECKSUM_CALCULATION: WHEN_REQUIRED
AWS_RESPONSE_CHECKSUM_VALIDATION: WHEN_REQUIRED
- name: Publish RPM to R2
if: needs.identify_channel.outputs.channel == 'latest'
run: |
.github/scripts/publish_rpm.sh $(find ./artifacts -type f -name "*.rpm")
env:
GPG_KEY_ID: "${{steps.import_gpg.outputs.keyid}}"
R2_BUCKET: "beekeeper-rpm-repo/repo"
R2_ENDPOINT: ${{secrets.cloudflare_endpoint}}
AWS_ACCESS_KEY_ID: "${{secrets.cloudflare_key_id}}"
AWS_SECRET_ACCESS_KEY: "${{secrets.cloudflare_secret_access_key}}"
# 2025-01-15: AWS made changes to their clis that broke compatibility
# for third party services.
# This works around that.
# https://github.com/aws/aws-sdk-ruby/issues/3166
AWS_REQUEST_CHECKSUM_CALCULATION: WHEN_REQUIRED
AWS_RESPONSE_CHECKSUM_VALIDATION: WHEN_REQUIRED