on:
  workflow_dispatch:
    inputs:
      version:
        description: "Version to deploy (format: vX.Y.Z)"
        required: true
      upload:
        description: "Upload final artifacts to R2"
        default: false

  push:
    tags:
      - "v[0-9]+.[0-9]+.[0-9]+"

name: Release Tag

# We must only run one release workflow at a time to prevent corrupting
# our release artifacts.
concurrency:
  group: ${{ github.workflow }}
  cancel-in-progress: false

jobs:
  setup:
    runs-on: namespace-profile-ghostty-sm
    outputs:
      version: ${{ steps.extract_version.outputs.version }}
      build: ${{ steps.extract_build_info.outputs.build }}
      commit: ${{ steps.extract_build_info.outputs.commit }}
      commit_long: ${{ steps.extract_build_info.outputs.commit_long }}
    steps:
      - name: Validate Version Input
        if: github.event_name == 'workflow_dispatch'
        run: |
          if [[ ! "${{ github.event.inputs.version }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
            echo "Error: Version must follow the format vX.Y.Z (e.g., v1.0.0)."
            exit 1
          fi

          echo "Version is valid: ${{ github.event.inputs.version }}"

      - name: Extract the Version
        id: extract_version
        run: |
          if [[ "${{ github.event_name }}" == "push" ]]; then
            # Remove the leading 'v' from the tag
            VERSION=${GITHUB_REF#refs/tags/v}
            echo "version=$VERSION" >> $GITHUB_OUTPUT
          elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
            VERSION=${{ github.event.inputs.version }}
            VERSION=${VERSION#v}
            echo "version=$VERSION" >> $GITHUB_OUTPUT
          else
            echo "Error: Unsupported event type."
            exit 1
          fi

      - name: Checkout code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          # Important so that build number generation works
          fetch-depth: 0

      - name: Extract build info
        id: extract_build_info
        run: |
          GHOSTTY_BUILD=$(git rev-list --count HEAD)
          GHOSTTY_COMMIT=$(git rev-parse --short HEAD)
          GHOSTTY_COMMIT_LONG=$(git rev-parse HEAD)
          echo "build=$GHOSTTY_BUILD" >> $GITHUB_OUTPUT
          echo "commit=$GHOSTTY_COMMIT" >> $GITHUB_OUTPUT
          echo "commit_long=$GHOSTTY_COMMIT_LONG" >> $GITHUB_OUTPUT
          cat $GITHUB_OUTPUT

  source-tarball:
    runs-on: namespace-profile-ghostty-md
    needs: [setup]
    env:
      GHOSTTY_VERSION: ${{ needs.setup.outputs.version }}
      ZIG_LOCAL_CACHE_DIR: /zig/local-cache
      ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Setup Cache
        uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9 # v1.4.2
        with:
          path: |
            /nix
            /zig

      - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0
        with:
          nix_path: nixpkgs=channel:nixos-unstable

      - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16
        with:
          name: ghostty
          authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"

      - name: Create Tarball
        run: |
          rm -rf zig-out/dist
          nix develop -c zig build distcheck
          cp zig-out/dist/ghostty-${GHOSTTY_VERSION}.tar.gz .
          cp zig-out/dist/ghostty-${GHOSTTY_VERSION}.tar.gz ghostty-source.tar.gz

      - name: Sign Tarball
        run: |
          echo -n "${{ secrets.MINISIGN_KEY }}" > minisign.key
          echo -n "${{ secrets.MINISIGN_PASSWORD }}" > minisign.password
          nix develop -c minisign -S -m "ghostty-${GHOSTTY_VERSION}.tar.gz" -s minisign.key < minisign.password
          nix develop -c minisign -S -m "ghostty-source.tar.gz" -s minisign.key < minisign.password

      - name: Upload artifact
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: source-tarball
          path: |-
            ghostty-${{ env.GHOSTTY_VERSION }}.tar.gz
            ghostty-${{ env.GHOSTTY_VERSION }}.tar.gz.minisig
            ghostty-source.tar.gz
            ghostty-source.tar.gz.minisig

  build-macos:
    needs: [setup]
    runs-on: namespace-profile-ghostty-macos-tahoe
    timeout-minutes: 90
    env:
      GHOSTTY_VERSION: ${{ needs.setup.outputs.version }}
      GHOSTTY_BUILD: ${{ needs.setup.outputs.build }}
      GHOSTTY_COMMIT: ${{ needs.setup.outputs.commit }}
      ZIG_LOCAL_CACHE_DIR: /Users/runner/zig/local-cache
      ZIG_GLOBAL_CACHE_DIR: /Users/runner/zig/global-cache
    steps:
      - name: Checkout code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Setup Cache
        uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9 # v1.4.2
        with:
          cache: |
            xcode
          path: |
            /Users/runner/zig

      - uses: DeterminateSystems/nix-installer-action@main
        with:
          determinate: true
      - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16
        with:
          name: ghostty
          authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"

      - name: XCode Select
        run: sudo xcode-select -s /Applications/Xcode_26.2.app

      - name: Xcode Version
        run: xcodebuild -version

      - name: Setup Sparkle
        env:
          SPARKLE_VERSION: 2.9.0
        run: |
          mkdir -p .action/sparkle
          cd .action/sparkle
          curl -L https://github.com/sparkle-project/Sparkle/releases/download/${SPARKLE_VERSION}/Sparkle-for-Swift-Package-Manager.zip > sparkle.zip
          unzip sparkle.zip
          echo "$(pwd)/bin" >> $GITHUB_PATH

      # GhosttyKit is the framework that is built from Zig for our native
      # Mac app to access. Build this in release mode.
      - name: Build GhosttyKit
        run: |
          nix develop -c \
            zig build \
            -Doptimize=ReleaseFast \
            -Demit-macos-app=false \
            -Dversion-string=${GHOSTTY_VERSION}

      # The native app is built with native XCode tooling. This also does
      # codesigning. IMPORTANT: this must NOT run in a Nix environment.
      # Nix breaks xcodebuild so this has to be run outside.
      - name: Build Ghostty.app
        run: |
          cd macos
          xcodebuild -target Ghostty -configuration Release \
            COMPILATION_CACHE_CAS_PATH=/Users/runner/Library/Developer/Xcode/DerivedData/CompilationCache.noindex \
            COMPILATION_CACHE_KEEP_CAS_DIRECTORY=YES

      # Add all our metadata to Info.plist so we can reference it later.
      - name: Update Info.plist
        env:
          SPARKLE_KEY_PUB: ${{ secrets.PROD_MACOS_SPARKLE_KEY_PUB }}
        run: |
          # Version Info
          /usr/libexec/PlistBuddy -c "Set :GhosttyCommit $GHOSTTY_COMMIT" "macos/build/Release/Ghostty.app/Contents/Info.plist"
          /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $GHOSTTY_BUILD" "macos/build/Release/Ghostty.app/Contents/Info.plist"
          /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $GHOSTTY_VERSION" "macos/build/Release/Ghostty.app/Contents/Info.plist"

          # Updater
          /usr/libexec/PlistBuddy -c "Set :SUPublicEDKey $SPARKLE_KEY_PUB" "macos/build/Release/Ghostty.app/Contents/Info.plist"
          /usr/libexec/PlistBuddy -c "Delete :SUEnableAutomaticChecks" "macos/build/Release/Ghostty.app/Contents/Info.plist"

      - name: Codesign app bundle
        env:
          MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}
          MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}
          MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
          MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
        run: |
          # Turn our base64-encoded certificate back to a regular .p12 file
          echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12

          # We need to create a new keychain, otherwise using the certificate will prompt
          # with a UI dialog asking for the certificate password, which we can't
          # use in a headless CI environment
          security create-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
          security default-keychain -s build.keychain
          security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
          security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign
          security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CI_KEYCHAIN_PWD" build.keychain

          # Codesign Sparkle. Some notes here:
          #   - The XPC services aren't used since we don't sandbox Ghostty,
          #     but since they're part of the build, they still need to be
          #     codesigned.
          #   - The binaries in the "Versions" folders need to NOT be symlinks.
          /usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework/Versions/B/XPCServices/Downloader.xpc"
          /usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework/Versions/B/XPCServices/Installer.xpc"
          /usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework/Versions/B/Autoupdate"
          /usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework/Versions/B/Updater.app"
          /usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework"
          /usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/PlugIns/DockTilePlugin.plugin"

          # Codesign the app bundle
          /usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime --entitlements "macos/Ghostty.entitlements" macos/build/Release/Ghostty.app

      - name: Create DMG
        env:
          MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
        run: |
          npm install --global create-dmg
          create-dmg \
            --identity="$MACOS_CERTIFICATE_NAME" \
            ./macos/build/Release/Ghostty.app \
            ./
          mv ./Ghostty*.dmg ./Ghostty.dmg

      - name: "Notarize DMG"
        env:
          APPLE_NOTARIZATION_ISSUER: ${{ secrets.APPLE_NOTARIZATION_ISSUER }}
          APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
          APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
        run: |
          # Store the notarization credentials so that we can prevent a UI password dialog
          # from blocking the CI
          echo "Create keychain profile"
          echo "$APPLE_NOTARIZATION_KEY" > notarization_key.p8
          xcrun notarytool store-credentials "notarytool-profile" --key notarization_key.p8 --key-id "$APPLE_NOTARIZATION_KEY_ID" --issuer "$APPLE_NOTARIZATION_ISSUER"
          rm notarization_key.p8

          # Here we send the notarization request to the Apple's Notarization service, waiting for the result.
          # This typically takes a few seconds inside a CI environment, but it might take more depending on the App
          # characteristics. Visit the Notarization docs for more information and strategies on how to optimize it if
          # you're curious
          echo "Notarize dmg"
          xcrun notarytool submit "Ghostty.dmg" --keychain-profile "notarytool-profile" --wait

          # Finally, we need to "attach the staple" to our executable, which will allow our app to be
          # validated by macOS even when an internet connection is not available. We do this to
          # both the app and the dmg
          echo "Attach staple"
          xcrun stapler staple "Ghostty.dmg"
          xcrun stapler staple "macos/build/Release/Ghostty.app"

      # Zip up the app and symbols
      - name: Zip App
        run: |
          cd macos/build/Release
          zip -9 -r --symlinks ../../../ghostty-macos-universal.zip Ghostty.app
          zip -9 -r --symlinks ../../../ghostty-macos-universal-dsym.zip Ghostty.app.dSYM/

      - name: Upload artifact
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: macos
          path: |-
            Ghostty.dmg
            ghostty-macos-universal.zip
            ghostty-macos-universal-dsym.zip

  sentry-dsym:
    runs-on: namespace-profile-ghostty-sm
    needs: [build-macos]
    steps:
      - name: Install sentry-cli
        run: |
          curl -sL https://sentry.io/get-cli/ | bash

      - name: Download macOS Artifacts
        uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
        with:
          name: macos

      - name: Upload dSYM to Sentry
        env:
          SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
        run: |
          sentry-cli dif upload --project ghostty --wait ghostty-macos-universal-dsym.zip

  appcast:
    needs: [setup, build-macos]
    runs-on: namespace-profile-ghostty-macos-tahoe
    env:
      GHOSTTY_VERSION: ${{ needs.setup.outputs.version }}
      GHOSTTY_BUILD: ${{ needs.setup.outputs.build }}
      GHOSTTY_COMMIT: ${{ needs.setup.outputs.commit }}
      GHOSTTY_COMMIT_LONG: ${{ needs.setup.outputs.commit_long }}
    steps:
      - name: Checkout code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Download macOS Artifacts
        uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
        with:
          name: macos

      - name: Setup Sparkle
        env:
          SPARKLE_VERSION: 2.9.0
        run: |
          mkdir -p .action/sparkle
          cd .action/sparkle
          curl -L https://github.com/sparkle-project/Sparkle/releases/download/${SPARKLE_VERSION}/Sparkle-for-Swift-Package-Manager.zip > sparkle.zip
          unzip sparkle.zip
          echo "$(pwd)/bin" >> $GITHUB_PATH

      - name: Generate Appcast
        env:
          SPARKLE_KEY: ${{ secrets.PROD_MACOS_SPARKLE_KEY }}
        run: |
          echo "GHOSTTY_VERSION=$GHOSTTY_VERSION"
          echo "GHOSTTY_BUILD=$GHOSTTY_BUILD"
          echo "GHOSTTY_COMMIT=$GHOSTTY_COMMIT"
          echo "GHOSTTY_COMMIT_LONG=$GHOSTTY_COMMIT_LONG"

          echo $SPARKLE_KEY > signing.key
          sign_update -f signing.key Ghostty.dmg > sign_update.txt
          curl -L https://release.files.ghostty.org/appcast.xml > appcast.xml
          python3 ./dist/macos/update_appcast_tag.py
          test -f appcast_new.xml
          mv appcast_new.xml appcast.xml

      - name: Upload artifact
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: sparkle
          path: |-
            appcast.xml

  upload:
    if: |-
      (github.event_name == 'workflow_dispatch' &&
        github.event.inputs.upload == 'true') ||
      github.event_name == 'push'
    needs: [setup, source-tarball, build-macos, appcast]
    runs-on: namespace-profile-ghostty-sm
    env:
      GHOSTTY_VERSION: ${{ needs.setup.outputs.version }}
    steps:
      - name: Download macOS Artifacts
        uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
        with:
          name: macos

      - name: Download Sparkle Artifacts
        uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
        with:
          name: sparkle

      - name: Download Source Tarball Artifacts
        uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
        with:
          name: source-tarball

      # Upload all of our files EXCEPT the appcast. The appcast triggers
      # updates in clients and we don't want to do that until we're
      # sure these are uploaded.
      - name: Prep Files
        run: |
          mkdir blob
          mkdir -p blob/${GHOSTTY_VERSION}
          mv "ghostty-${GHOSTTY_VERSION}.tar.gz" blob/${GHOSTTY_VERSION}/ghostty-${GHOSTTY_VERSION}.tar.gz
          mv "ghostty-${GHOSTTY_VERSION}.tar.gz.minisig" blob/${GHOSTTY_VERSION}/ghostty-${GHOSTTY_VERSION}.tar.gz.minisig
          mv ghostty-source.tar.gz blob/${GHOSTTY_VERSION}/ghostty-source.tar.gz
          mv ghostty-source.tar.gz.minisig blob/${GHOSTTY_VERSION}/ghostty-source.tar.gz.minisig
          mv ghostty-macos-universal.zip blob/${GHOSTTY_VERSION}/ghostty-macos-universal.zip
          mv ghostty-macos-universal-dsym.zip blob/${GHOSTTY_VERSION}/ghostty-macos-universal-dsym.zip
          mv Ghostty.dmg blob/${GHOSTTY_VERSION}/Ghostty.dmg
          mv appcast.xml blob/${GHOSTTY_VERSION}/appcast-staged.xml
      - name: Upload to R2
        uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1.4
        with:
          r2-account-id: ${{ secrets.CF_R2_RELEASE_ACCOUNT_ID }}
          r2-access-key-id: ${{ secrets.CF_R2_RELEASE_AWS_KEY }}
          r2-secret-access-key: ${{ secrets.CF_R2_RELEASE_SECRET_KEY }}
          r2-bucket: ghostty-release
          source-dir: blob
          destination-dir: ./
