refactor(ui): update styling and logic for components #108
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
| # Build Windows installer with Electron Forge on CI, publish GitHub Release, notify /api/releases. | |
| name: Electron Forge EXE Release | |
| on: | |
| push: | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| jobs: | |
| publish: | |
| runs-on: windows-latest | |
| defaults: | |
| run: | |
| shell: bash | |
| env: | |
| CSC_IDENTITY_AUTO_DISCOVERY: false | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| - name: Setup Node | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| cache-dependency-path: package-lock.json | |
| - name: Install dependencies | |
| # Avoid packages whose postinstall downloads GitHub release binaries (e.g. @railway/cli); | |
| # those are invoked via npx only when deploying the TDLib gateway. | |
| run: npm ci --no-fund --no-audit | |
| - name: Resolve next app version from GitHub releases | |
| id: version | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: node windows/resolve-next-app-version.cjs --write | |
| - name: Set release metadata | |
| id: meta | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| rid="${{ steps.version.outputs.release_tag }}_forge" | |
| echo "release_id=$rid" >> "$GITHUB_OUTPUT" | |
| echo "RELEASE_BUILD_ID=$rid" >> "$GITHUB_ENV" | |
| echo "app_version=${{ steps.version.outputs.app_version }}" >> "$GITHUB_OUTPUT" | |
| - name: Build Forge Windows artifacts | |
| env: | |
| ELECTRON_PUBLISH: never | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: npm run make:win:forge | |
| # forge-cleanup writes releases/forge/<build_id>_forge/ (installer at root; zip + yml in dev/). | |
| # Stage flat copies under releases/electron/<build_id>_forge/ for GitHub upload. | |
| - name: Locate Forge artifacts and stage under releases/electron | |
| id: artifact | |
| run: | | |
| set -euo pipefail | |
| rid="${{ steps.meta.outputs.release_id }}" | |
| src_dir="releases/forge/$rid" | |
| dev_dir="$src_dir/dev" | |
| stage_dir="releases/electron/$rid" | |
| mkdir -p "$stage_dir" | |
| exe="$(ls -1 "$src_dir"/HyperlinksSpaceProgramInstaller_*.exe 2>/dev/null | head -n 1 || true)" | |
| if [[ -z "$exe" || ! -f "$exe" ]]; then | |
| echo "Expected timestamped installer in $src_dir (forge-cleanup; set RELEASE_BUILD_ID=$rid)." | |
| find releases -maxdepth 6 -type f 2>/dev/null || true | |
| exit 1 | |
| fi | |
| zip="$(ls -1 "$dev_dir"/HyperlinksSpaceProgram_*.zip 2>/dev/null | head -n 1 || true)" | |
| if [[ -z "$zip" || ! -f "$zip" ]]; then | |
| echo "Portable zip required in $dev_dir (Forge zip maker + forge-cleanup)." | |
| find "$dev_dir" -maxdepth 1 -type f 2>/dev/null || true | |
| exit 1 | |
| fi | |
| yml="$dev_dir/latest.yml" | |
| zip_yml="$dev_dir/zip-latest.yml" | |
| if [[ ! -f "$yml" ]]; then | |
| echo "latest.yml missing next to installer — electron-updater needs it on the GitHub Release." | |
| exit 1 | |
| fi | |
| exe_name="$(basename "$exe")" | |
| zip_name="$(basename "$zip")" | |
| exe_out="$stage_dir/$exe_name" | |
| zip_out="$stage_dir/$zip_name" | |
| yml_out="$stage_dir/latest.yml" | |
| zip_yml_out="$stage_dir/zip-latest.yml" | |
| cp "$exe" "$exe_out" | |
| cp "$zip" "$zip_out" | |
| cp "$yml" "$yml_out" | |
| if [[ -f "$zip_yml" ]]; then | |
| cp "$zip_yml" "$zip_yml_out" | |
| fi | |
| echo "exe_path=$exe_out" >> "$GITHUB_OUTPUT" | |
| echo "exe_name=$exe_name" >> "$GITHUB_OUTPUT" | |
| echo "zip_path=$zip_out" >> "$GITHUB_OUTPUT" | |
| echo "zip_name=$zip_name" >> "$GITHUB_OUTPUT" | |
| echo "yml_path=$yml_out" >> "$GITHUB_OUTPUT" | |
| echo "zip_yml_path=$zip_yml_out" >> "$GITHUB_OUTPUT" | |
| echo "release_id=$rid" >> "$GITHUB_OUTPUT" | |
| - name: Check if GitHub release already exists | |
| id: exists | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| working-directory: ${{ github.workspace }} | |
| run: | | |
| set -euo pipefail | |
| rid="${{ steps.artifact.outputs.release_id }}" | |
| if gh release view "$rid" --repo "${{ github.repository }}" >/dev/null 2>&1; then | |
| echo "exists=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "exists=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Preflight checks and artifact summary | |
| if: steps.exists.outputs.exists != 'true' | |
| id: preflight | |
| run: | | |
| set -euo pipefail | |
| rid="${{ steps.artifact.outputs.release_id }}" | |
| exe="${{ steps.artifact.outputs.exe_path }}" | |
| zip="${{ steps.artifact.outputs.zip_path }}" | |
| yml="${{ steps.artifact.outputs.yml_path }}" | |
| zip_yml="${{ steps.artifact.outputs.zip_yml_path }}" | |
| stage_dir="releases/electron/$rid" | |
| [[ -d "$stage_dir" ]] || { echo "Stage dir missing: $stage_dir"; exit 1; } | |
| [[ -f "$exe" ]] || { echo "Installer missing: $exe"; exit 1; } | |
| [[ -f "$zip" ]] || { echo "Portable zip missing: $zip"; exit 1; } | |
| [[ -f "$yml" ]] || { echo "latest.yml missing: $yml"; exit 1; } | |
| exe_name="$(basename "$exe")" | |
| zip_name="$(basename "$zip")" | |
| yml_name="$(basename "$yml")" | |
| [[ "$exe_name" == HyperlinksSpaceProgramInstaller_*.exe ]] || { | |
| echo "Unexpected installer name: $exe_name"; exit 1; | |
| } | |
| [[ "$zip_name" == HyperlinksSpaceProgram_*.zip ]] || { | |
| echo "Unexpected zip name: $zip_name"; exit 1; | |
| } | |
| [[ "$yml_name" == "latest.yml" ]] || { | |
| echo "Unexpected yml name: $yml_name"; exit 1; | |
| } | |
| app_version="${{ steps.meta.outputs.app_version }}" | |
| zip_version="$(echo "$zip_name" | sed -E 's/^HyperlinksSpaceProgram_(.+)\.zip$/\1/')" | |
| if [[ -z "$app_version" ]]; then | |
| app_version="$zip_version" | |
| fi | |
| if [[ -n "$zip_version" && "$zip_version" != "$zip_name" && "$zip_version" != "$app_version" ]]; then | |
| echo "Zip version ($zip_version) does not match resolved app version ($app_version)" | |
| exit 1 | |
| fi | |
| echo "app_version=$app_version" >> "$GITHUB_OUTPUT" | |
| echo "Preflight OK (Forge):" | |
| echo " release_id: $rid" | |
| echo " app_version: $app_version" | |
| echo " stage_dir: $stage_dir" | |
| echo " installer: $exe" | |
| echo " portable_zip: $zip" | |
| echo " latest_yml: $yml" | |
| if [[ -n "$zip_yml" && -f "$zip_yml" ]]; then | |
| echo " zip_latest_yml: $zip_yml" | |
| else | |
| echo " zip_latest_yml: (missing, optional)" | |
| fi | |
| - name: Create GitHub release and upload Forge artifacts | |
| if: steps.exists.outputs.exists != 'true' | |
| id: create | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| rid="${{ steps.artifact.outputs.release_id }}" | |
| exe="${{ steps.artifact.outputs.exe_path }}" | |
| zip="${{ steps.artifact.outputs.zip_path }}" | |
| yml="${{ steps.artifact.outputs.yml_path }}" | |
| zip_yml="${{ steps.artifact.outputs.zip_yml_path }}" | |
| upload=( "$exe" "$yml" "$zip" ) | |
| if [[ -f "$zip_yml" ]]; then | |
| upload+=( "$zip_yml" ) | |
| else | |
| echo "Warning: zip-latest.yml missing (forge-cleanup should generate it when zip exists)." | |
| fi | |
| echo "Publishing release $rid (app_version=${{ steps.preflight.outputs.app_version }})" | |
| printf 'Assets:\n- %s\n' "${upload[@]}" | |
| gh release create "$rid" "${upload[@]}" \ | |
| --repo "${{ github.repository }}" \ | |
| --title "Hyperlinks Space Program ${{ steps.preflight.outputs.app_version }} (Forge)" \ | |
| --notes "Windows Forge build on GitHub Actions (workflow run ${{ github.run_id }}). Same asset names as electron-builder; tag ends with _forge." | |
| release_url="https://github.com/${{ github.repository }}/releases/tag/$rid" | |
| echo "release_url=$release_url" >> "$GITHUB_OUTPUT" | |
| - name: Notify app webhook | |
| if: steps.exists.outputs.exists != 'true' | |
| env: | |
| RELEASE_WEBHOOK_URL: ${{ secrets.RELEASE_WEBHOOK_URL }} | |
| VERCEL_PROJECT_PRODUCTION_URL: ${{ vars.VERCEL_PROJECT_PRODUCTION_URL }} | |
| VERCEL_PROJECT_PRODUCTION_URL_SECRET: ${{ secrets.VERCEL_PROJECT_PRODUCTION_URL }} | |
| WEBHOOK_TOKEN: ${{ secrets.RELEASE_WEBHOOK_TOKEN }} | |
| RELEASE_ID: ${{ steps.artifact.outputs.release_id }} | |
| RELEASE_URL: ${{ steps.create.outputs.release_url }} | |
| EXE_NAME: ${{ steps.artifact.outputs.exe_name }} | |
| working-directory: ${{ github.workspace }} | |
| run: | | |
| set -euo pipefail | |
| webhook_url="${RELEASE_WEBHOOK_URL:-}" | |
| if [[ -z "$webhook_url" ]]; then | |
| if [[ -n "${VERCEL_PROJECT_PRODUCTION_URL:-}" ]]; then | |
| webhook_url="https://${VERCEL_PROJECT_PRODUCTION_URL}/api/releases" | |
| elif [[ -n "${VERCEL_PROJECT_PRODUCTION_URL_SECRET:-}" ]]; then | |
| webhook_url="https://${VERCEL_PROJECT_PRODUCTION_URL_SECRET}/api/releases" | |
| fi | |
| fi | |
| if [[ -z "$webhook_url" ]]; then | |
| echo "No webhook URL configured; skipping." | |
| exit 0 | |
| fi | |
| published_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)" | |
| export PUBLISHED_AT="$published_at" | |
| asset_url="${RELEASE_URL/\/tag\//\/download\/}/${EXE_NAME}" | |
| export ASSET_URL="$asset_url" | |
| payload="$(python3 -c "import json, os; print(json.dumps({'release_id': os.environ['RELEASE_ID'], 'published_at': os.environ['PUBLISHED_AT'], 'platform': 'windows', 'assets': [{'name': os.environ['EXE_NAME'], 'url': os.environ['ASSET_URL']}], 'github_release_url': os.environ['RELEASE_URL']}))")" | |
| if [[ -n "${WEBHOOK_TOKEN:-}" ]]; then | |
| curl --fail --show-error --silent \ | |
| -X POST "$webhook_url" \ | |
| -H "Content-Type: application/json" \ | |
| -H "x-release-token: $WEBHOOK_TOKEN" \ | |
| --data "$payload" | |
| else | |
| curl --fail --show-error --silent \ | |
| -X POST "$webhook_url" \ | |
| -H "Content-Type: application/json" \ | |
| --data "$payload" | |
| fi | |
| - name: Summary | |
| working-directory: ${{ github.workspace }} | |
| run: | | |
| if [[ "${{ steps.exists.outputs.exists }}" == "true" ]]; then | |
| echo "Skipped: release already exists for ${{ steps.artifact.outputs.release_id }}." | |
| else | |
| echo "Published ${{ steps.artifact.outputs.release_id }} (${{ steps.artifact.outputs.exe_path }})" | |
| fi |