fix: route Nightly Google OAuth callbacks correctly (#5537) #351
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: Desktop App Release | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - ".github/workflows/desktop-release.yml" | |
| - "packages/desktop-app/**" | |
| - "packages/shared-app-config/**" | |
| workflow_dispatch: | |
| inputs: | |
| channel: | |
| description: "Release channel" | |
| required: true | |
| default: production | |
| type: choice | |
| options: | |
| - production | |
| - nightly | |
| version: | |
| description: "Stable version to release (e.g. 0.2.0). Required for production; Nightly may use the package.json version." | |
| required: false | |
| type: string | |
| source_ref: | |
| description: "Exact source commit to build for a coordinated release" | |
| required: false | |
| type: string | |
| permissions: | |
| contents: write | |
| concurrency: | |
| group: desktop-release-${{ github.event_name == 'push' && 'nightly' || inputs.channel || 'production' }} | |
| cancel-in-progress: false | |
| env: | |
| # Pushes to main are the employee-facing Nightly train. Production is only | |
| # released deliberately through workflow_dispatch. | |
| RELEASE_CHANNEL: ${{ github.event_name == 'push' && 'nightly' || inputs.channel || 'production' }} | |
| AGENT_NATIVE_DESKTOP_RELEASE_CHANNEL: ${{ github.event_name == 'push' && 'nightly' || inputs.channel || 'production' }} | |
| EP_PRE_RELEASE: ${{ (github.event_name == 'push' && 'nightly' || inputs.channel || 'production') == 'nightly' }} | |
| ELECTRON_APP_ID: ${{ (github.event_name == 'push' && 'nightly' || inputs.channel || 'production') == 'nightly' && 'com.agentnative.desktop.nightly' || 'com.agentnative.desktop' }} | |
| ELECTRON_PRODUCT_NAME: ${{ (github.event_name == 'push' && 'nightly' || inputs.channel || 'production') == 'nightly' && 'Agent-Native Nightly' || 'Agent-Native' }} | |
| # Deterministic version for auto-triggered builds: base version from | |
| # package.json + run number ensures uniqueness without committing back. | |
| AUTO_VERSION_SUFFIX: ${{ github.run_number }} | |
| jobs: | |
| resolve-version: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.v.outputs.version }} | |
| source_ref: ${{ steps.v.outputs.source_ref }} | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| with: | |
| ref: ${{ inputs.source_ref || github.sha }} | |
| - name: Resolve version | |
| id: v | |
| shell: bash | |
| env: | |
| RELEASE_VERSION_INPUT: ${{ inputs.version }} | |
| RELEASE_SOURCE_REF_INPUT: ${{ inputs.source_ref }} | |
| run: | | |
| if [ -n "$RELEASE_SOURCE_REF_INPUT" ] && [[ ! "$RELEASE_SOURCE_REF_INPUT" =~ ^[0-9a-fA-F]{40}$ ]]; then | |
| echo "::error::source_ref must be a full 40-character commit SHA." | |
| exit 1 | |
| fi | |
| SOURCE_REF="${RELEASE_SOURCE_REF_INPUT:-$GITHUB_SHA}" | |
| echo "source_ref=${SOURCE_REF,,}" >> "$GITHUB_OUTPUT" | |
| if [ -n "$RELEASE_VERSION_INPUT" ]; then | |
| if [ "$RELEASE_CHANNEL" = "nightly" ]; then | |
| if [[ "$RELEASE_VERSION_INPUT" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| echo "version=${RELEASE_VERSION_INPUT}-nightly.${AUTO_VERSION_SUFFIX}" >> "$GITHUB_OUTPUT" | |
| elif [[ "$RELEASE_VERSION_INPUT" =~ ^[0-9]+\.[0-9]+\.[0-9]+-nightly\.[0-9]+$ ]]; then | |
| echo "version=$RELEASE_VERSION_INPUT" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "::error::Nightly versions must be semver or include the -nightly prerelease marker." | |
| exit 1 | |
| fi | |
| else | |
| if [[ ! "$RELEASE_VERSION_INPUT" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| echo "::error::Production versions must be an exact stable major.minor.patch version." | |
| exit 1 | |
| fi | |
| echo "version=$RELEASE_VERSION_INPUT" >> "$GITHUB_OUTPUT" | |
| fi | |
| else | |
| if [ "$RELEASE_CHANNEL" = "nightly" ]; then | |
| BASE=$(node -p "require('./packages/desktop-app/package.json').version") | |
| # Use a prerelease tag rather than build metadata: npm version | |
| # silently strips +metadata, which would make release assets | |
| # collide across automatic runs. | |
| echo "version=${BASE}-nightly.${AUTO_VERSION_SUFFIX}" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "::error::Production releases require an explicit stable version input." | |
| exit 1 | |
| fi | |
| fi | |
| prepare-release: | |
| needs: resolve-version | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Create draft release before parallel uploads | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| shell: bash | |
| run: | | |
| TAG="v${{ needs.resolve-version.outputs.version }}" | |
| get_tag_sha() { | |
| local ref_json object_type object_sha | |
| if ! ref_json=$(gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/$1"); then | |
| return 1 | |
| fi | |
| object_type=$(printf '%s' "$ref_json" | jq -r '.object.type') | |
| object_sha=$(printf '%s' "$ref_json" | jq -r '.object.sha') | |
| if [ "$object_type" = "tag" ]; then | |
| gh api "repos/${GITHUB_REPOSITORY}/git/tags/$object_sha" --jq '.object.sha' | |
| else | |
| printf '%s\n' "$object_sha" | |
| fi | |
| } | |
| RELEASE_ERROR=$(mktemp) | |
| if RELEASE_JSON=$(gh api "repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}" 2>"$RELEASE_ERROR"); then | |
| IS_DRAFT=$(printf '%s' "$RELEASE_JSON" | jq -r '.draft') | |
| if [ "$IS_DRAFT" != "true" ]; then | |
| echo "::error::Release $TAG is already published. Choose a new version instead of rebuilding it." | |
| rm -f "$RELEASE_ERROR" | |
| exit 1 | |
| fi | |
| EXISTING_TAG_SHA=$(get_tag_sha "$TAG") | |
| if [ "$EXISTING_TAG_SHA" != "${{ needs.resolve-version.outputs.source_ref }}" ]; then | |
| echo "::error::Existing release $TAG points to $EXISTING_TAG_SHA, expected ${{ needs.resolve-version.outputs.source_ref }}." | |
| rm -f "$RELEASE_ERROR" | |
| exit 1 | |
| fi | |
| echo "Draft release $TAG already points to the coordinated source; build jobs will upload to it." | |
| elif grep -q 'HTTP 404' "$RELEASE_ERROR"; then | |
| TAG_ERROR=$(mktemp) | |
| if TAG_SHA=$(get_tag_sha "$TAG" 2>"$TAG_ERROR"); then | |
| if [ "$TAG_SHA" != "${{ needs.resolve-version.outputs.source_ref }}" ]; then | |
| echo "::error::Existing tag $TAG points to $TAG_SHA, expected ${{ needs.resolve-version.outputs.source_ref }}." | |
| rm -f "$RELEASE_ERROR" "$TAG_ERROR" | |
| exit 1 | |
| fi | |
| echo "Existing tag $TAG already points to the coordinated source; creating its release." | |
| elif grep -q 'HTTP 404' "$TAG_ERROR"; then | |
| : | |
| else | |
| cat "$TAG_ERROR" | |
| rm -f "$RELEASE_ERROR" "$TAG_ERROR" | |
| exit 1 | |
| fi | |
| rm -f "$TAG_ERROR" | |
| if [ "$RELEASE_CHANNEL" = "nightly" ]; then | |
| gh release create "$TAG" \ | |
| --draft \ | |
| --prerelease \ | |
| --latest=false \ | |
| --target "${{ needs.resolve-version.outputs.source_ref }}" \ | |
| --title "Agent-Native Nightly v${{ needs.resolve-version.outputs.version }}" \ | |
| --notes "Auto-updating Nightly build of the Agent-Native desktop app." \ | |
| -R "${{ github.repository }}" | |
| else | |
| gh release create "$TAG" \ | |
| --draft \ | |
| --latest=false \ | |
| --target "${{ needs.resolve-version.outputs.source_ref }}" \ | |
| --title "Agent-Native v${{ needs.resolve-version.outputs.version }}" \ | |
| --notes "Stable release of the Agent-Native desktop app." \ | |
| -R "${{ github.repository }}" | |
| fi | |
| else | |
| cat "$RELEASE_ERROR" | |
| rm -f "$RELEASE_ERROR" | |
| exit 1 | |
| fi | |
| rm -f "$RELEASE_ERROR" | |
| build-mac: | |
| needs: [resolve-version, prepare-release] | |
| runs-on: macos-latest | |
| env: | |
| NODE_OPTIONS: --max-old-space-size=8192 | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| with: | |
| ref: ${{ needs.resolve-version.outputs.source_ref }} | |
| - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0 | |
| - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 | |
| with: | |
| node-version: 22 | |
| cache: pnpm | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Configure Nightly channel | |
| if: env.RELEASE_CHANNEL == 'nightly' | |
| working-directory: packages/desktop-app | |
| shell: bash | |
| run: | | |
| node <<'NODE' | |
| const fs = require("node:fs"); | |
| const builderPath = "electron-builder.yml"; | |
| let builder = fs.readFileSync(builderPath, "utf8"); | |
| let nightlySchemeConfigured = false; | |
| builder = builder.replace( | |
| /^([ \t]*- )(agentnative(?:-nightly)?)([ \t]*\r?\n)$/gm, | |
| (_match, prefix, _scheme, suffix) => { | |
| if (nightlySchemeConfigured) return ""; | |
| nightlySchemeConfigured = true; | |
| return `${prefix}agentnative-nightly${suffix}`; | |
| }, | |
| ); | |
| builder = builder.replace( | |
| /^([ \t]*- name: )Agent-Native([ \t]*\r?\n)/m, | |
| "$1Agent-Native Nightly$2", | |
| ); | |
| if (!nightlySchemeConfigured) { | |
| throw new Error("Could not configure the Nightly deep-link scheme."); | |
| } | |
| fs.writeFileSync(builderPath, builder); | |
| const updateConfigPath = "desktop-update-config.yml"; | |
| let updateConfig = fs.readFileSync(updateConfigPath, "utf8"); | |
| updateConfig = updateConfig.replace( | |
| 'updaterCacheDirName: "@agent-nativedesktop-app-updater"', | |
| 'updaterCacheDirName: "@agent-nativedesktop-app-updater-nightly"', | |
| ); | |
| if (!updateConfig.includes("@agent-nativedesktop-app-updater-nightly")) { | |
| throw new Error("Could not configure the Nightly updater cache."); | |
| } | |
| fs.writeFileSync(updateConfigPath, updateConfig); | |
| NODE | |
| - name: Set version | |
| working-directory: packages/desktop-app | |
| run: npm version "${{ needs.resolve-version.outputs.version }}" --no-git-tag-version --allow-same-version | |
| - name: Import signing certificate | |
| env: | |
| APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} | |
| APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| run: | | |
| echo "$APPLE_CERTIFICATE" | base64 --decode > certificate.p12 | |
| security create-keychain -p "" build.keychain | |
| security default-keychain -s build.keychain | |
| security unlock-keychain -p "" build.keychain | |
| security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign | |
| security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "" build.keychain | |
| rm certificate.p12 | |
| - name: Build and publish | |
| working-directory: packages/desktop-app | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| APPLE_ID: ${{ secrets.APPLE_ID }} | |
| APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} | |
| APPLE_TEAM_ID: W3PMF2T3MW | |
| SENTRY_DESKTOP_DSN: ${{ secrets.SENTRY_DESKTOP_DSN || secrets.SENTRY_ELECTRON_DSN || secrets.SENTRY_CLIENT_DSN || secrets.SENTRY_DSN }} | |
| SENTRY_DESKTOP_CLIENT_KEY: ${{ secrets.SENTRY_DESKTOP_CLIENT_KEY || secrets.SENTRY_CLIENT_KEY }} | |
| SENTRY_DESKTOP_PROJECT_ID: ${{ secrets.SENTRY_DESKTOP_PROJECT_ID || secrets.SENTRY_PROJECT_ID }} | |
| SENTRY_DESKTOP_INGEST_HOST: ${{ secrets.SENTRY_DESKTOP_INGEST_HOST || secrets.SENTRY_INGEST_HOST }} | |
| SENTRY_DESKTOP_ENVIRONMENT: ${{ env.RELEASE_CHANNEL }} | |
| run: | | |
| pnpm build:mac-assets && pnpm build | |
| npx electron-builder --mac --config --publish always \ | |
| "-c.appId=$ELECTRON_APP_ID" \ | |
| "-c.productName=$ELECTRON_PRODUCT_NAME" | |
| build-windows: | |
| needs: [resolve-version, prepare-release] | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| with: | |
| ref: ${{ needs.resolve-version.outputs.source_ref }} | |
| - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0 | |
| - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 | |
| with: | |
| node-version: 22 | |
| cache: pnpm | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Configure Nightly channel | |
| if: env.RELEASE_CHANNEL == 'nightly' | |
| working-directory: packages/desktop-app | |
| shell: bash | |
| run: | | |
| node <<'NODE' | |
| const fs = require("node:fs"); | |
| const builderPath = "electron-builder.yml"; | |
| let builder = fs.readFileSync(builderPath, "utf8"); | |
| let nightlySchemeConfigured = false; | |
| builder = builder.replace( | |
| /^([ \t]*- )(agentnative(?:-nightly)?)([ \t]*\r?\n)$/gm, | |
| (_match, prefix, _scheme, suffix) => { | |
| if (nightlySchemeConfigured) return ""; | |
| nightlySchemeConfigured = true; | |
| return `${prefix}agentnative-nightly${suffix}`; | |
| }, | |
| ); | |
| builder = builder.replace( | |
| /^([ \t]*- name: )Agent-Native([ \t]*\r?\n)/m, | |
| "$1Agent-Native Nightly$2", | |
| ); | |
| if (!nightlySchemeConfigured) { | |
| throw new Error("Could not configure the Nightly deep-link scheme."); | |
| } | |
| fs.writeFileSync(builderPath, builder); | |
| const updateConfigPath = "desktop-update-config.yml"; | |
| let updateConfig = fs.readFileSync(updateConfigPath, "utf8"); | |
| updateConfig = updateConfig.replace( | |
| 'updaterCacheDirName: "@agent-nativedesktop-app-updater"', | |
| 'updaterCacheDirName: "@agent-nativedesktop-app-updater-nightly"', | |
| ); | |
| if (!updateConfig.includes("@agent-nativedesktop-app-updater-nightly")) { | |
| throw new Error("Could not configure the Nightly updater cache."); | |
| } | |
| fs.writeFileSync(updateConfigPath, updateConfig); | |
| NODE | |
| - name: Set version | |
| working-directory: packages/desktop-app | |
| run: npm version "${{ needs.resolve-version.outputs.version }}" --no-git-tag-version --allow-same-version | |
| - name: Build and publish | |
| working-directory: packages/desktop-app | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| SENTRY_DESKTOP_DSN: ${{ secrets.SENTRY_DESKTOP_DSN || secrets.SENTRY_ELECTRON_DSN || secrets.SENTRY_CLIENT_DSN || secrets.SENTRY_DSN }} | |
| SENTRY_DESKTOP_CLIENT_KEY: ${{ secrets.SENTRY_DESKTOP_CLIENT_KEY || secrets.SENTRY_CLIENT_KEY }} | |
| SENTRY_DESKTOP_PROJECT_ID: ${{ secrets.SENTRY_DESKTOP_PROJECT_ID || secrets.SENTRY_PROJECT_ID }} | |
| SENTRY_DESKTOP_INGEST_HOST: ${{ secrets.SENTRY_DESKTOP_INGEST_HOST || secrets.SENTRY_INGEST_HOST }} | |
| SENTRY_DESKTOP_ENVIRONMENT: ${{ env.RELEASE_CHANNEL }} | |
| run: | | |
| pnpm build:chrome-extension && pnpm build | |
| npx electron-builder --win --config --publish always \ | |
| "-c.appId=$ELECTRON_APP_ID" \ | |
| "-c.productName=$ELECTRON_PRODUCT_NAME" | |
| build-linux: | |
| needs: [resolve-version, prepare-release] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| with: | |
| ref: ${{ needs.resolve-version.outputs.source_ref }} | |
| - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0 | |
| - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 | |
| with: | |
| node-version: 22 | |
| cache: pnpm | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Configure Nightly channel | |
| if: env.RELEASE_CHANNEL == 'nightly' | |
| working-directory: packages/desktop-app | |
| shell: bash | |
| run: | | |
| node <<'NODE' | |
| const fs = require("node:fs"); | |
| const builderPath = "electron-builder.yml"; | |
| let builder = fs.readFileSync(builderPath, "utf8"); | |
| let nightlySchemeConfigured = false; | |
| builder = builder.replace( | |
| /^([ \t]*- )(agentnative(?:-nightly)?)([ \t]*\r?\n)$/gm, | |
| (_match, prefix, _scheme, suffix) => { | |
| if (nightlySchemeConfigured) return ""; | |
| nightlySchemeConfigured = true; | |
| return `${prefix}agentnative-nightly${suffix}`; | |
| }, | |
| ); | |
| builder = builder.replace( | |
| /^([ \t]*- name: )Agent-Native([ \t]*\r?\n)/m, | |
| "$1Agent-Native Nightly$2", | |
| ); | |
| if (!nightlySchemeConfigured) { | |
| throw new Error("Could not configure the Nightly deep-link scheme."); | |
| } | |
| fs.writeFileSync(builderPath, builder); | |
| const updateConfigPath = "desktop-update-config.yml"; | |
| let updateConfig = fs.readFileSync(updateConfigPath, "utf8"); | |
| updateConfig = updateConfig.replace( | |
| 'updaterCacheDirName: "@agent-nativedesktop-app-updater"', | |
| 'updaterCacheDirName: "@agent-nativedesktop-app-updater-nightly"', | |
| ); | |
| if (!updateConfig.includes("@agent-nativedesktop-app-updater-nightly")) { | |
| throw new Error("Could not configure the Nightly updater cache."); | |
| } | |
| fs.writeFileSync(updateConfigPath, updateConfig); | |
| NODE | |
| - name: Set version | |
| working-directory: packages/desktop-app | |
| run: npm version "${{ needs.resolve-version.outputs.version }}" --no-git-tag-version --allow-same-version | |
| - name: Build and publish | |
| working-directory: packages/desktop-app | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| SENTRY_DESKTOP_DSN: ${{ secrets.SENTRY_DESKTOP_DSN || secrets.SENTRY_ELECTRON_DSN || secrets.SENTRY_CLIENT_DSN || secrets.SENTRY_DSN }} | |
| SENTRY_DESKTOP_CLIENT_KEY: ${{ secrets.SENTRY_DESKTOP_CLIENT_KEY || secrets.SENTRY_CLIENT_KEY }} | |
| SENTRY_DESKTOP_PROJECT_ID: ${{ secrets.SENTRY_DESKTOP_PROJECT_ID || secrets.SENTRY_PROJECT_ID }} | |
| SENTRY_DESKTOP_INGEST_HOST: ${{ secrets.SENTRY_DESKTOP_INGEST_HOST || secrets.SENTRY_INGEST_HOST }} | |
| SENTRY_DESKTOP_ENVIRONMENT: ${{ env.RELEASE_CHANNEL }} | |
| run: | | |
| pnpm build:chrome-extension && pnpm build | |
| LINUX_EXECUTABLE_NAME="agent-native" | |
| if [ "$RELEASE_CHANNEL" = "nightly" ]; then | |
| LINUX_EXECUTABLE_NAME="agent-native-nightly" | |
| fi | |
| npx electron-builder --linux --config --publish always \ | |
| "-c.linux.executableName=$LINUX_EXECUTABLE_NAME" \ | |
| "-c.appId=$ELECTRON_APP_ID" \ | |
| "-c.productName=$ELECTRON_PRODUCT_NAME" | |
| publish-release: | |
| needs: [resolve-version, build-mac, build-windows, build-linux] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Publish draft release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| shell: bash | |
| run: | | |
| TAG="v${{ needs.resolve-version.outputs.version }}" | |
| if [ "$RELEASE_CHANNEL" = "nightly" ]; then | |
| gh release edit "$TAG" \ | |
| --draft=false \ | |
| --prerelease \ | |
| --latest=false \ | |
| --title "Agent-Native Nightly v${{ needs.resolve-version.outputs.version }}" \ | |
| -R "${{ github.repository }}" | |
| else | |
| gh release edit "$TAG" \ | |
| --draft=false \ | |
| --latest \ | |
| --title "Agent-Native v${{ needs.resolve-version.outputs.version }}" \ | |
| -R "${{ github.repository }}" | |
| fi |