Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 163 additions & 0 deletions .github/actions/checkout-as-app/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
name: 'Checkout as GitHub App'
description: 'Authenticate as a GitHub App and checkout the repository with the app token baked into the remote URL, so subsequent git operations use the app credentials.'

branding:
icon: 'download'
color: 'blue'

inputs:
# ── GitHub App Auth inputs ──
app-id:
description: 'GitHub App ID'
required: true

private-key:
description: 'GitHub App private key'
required: true

owner:
description: 'Repository owner for the GitHub App token (defaults to current repository owner)'
required: false
default: ${{ github.repository_owner }}

# ── Checkout inputs (passthrough to actions/checkout) ──
repository:
description: 'Repository name with owner. For example, actions/checkout'
required: false
default: ${{ github.repository }}

ref:
description: 'The branch, tag or SHA to checkout. When checking out the repository that triggered a workflow, this defaults to the reference or SHA for that event. Otherwise, uses the default branch.'
required: false
default: ''

path:
description: 'Relative path under $GITHUB_WORKSPACE to place the repository'
required: false
default: ''

clean:
description: 'Whether to execute git clean -ffdx && git reset --hard HEAD before fetching'
required: false
default: 'true'

filter:
description: 'Partially clone against a given filter. Overrides sparse-checkout if set.'
required: false
default: ''

sparse-checkout:
description: 'Do a sparse checkout on given patterns. Each pattern should be separated with new lines.'
required: false
default: ''

sparse-checkout-cone-mode:
description: 'Specifies whether to use cone-mode when doing a sparse checkout.'
required: false
default: 'true'

fetch-depth:
description: 'Number of commits to fetch. 0 indicates all history for all branches and tags.'
required: false
default: '1'

fetch-tags:
description: 'Whether to fetch tags, even if fetch-depth > 0.'
required: false
default: 'false'

show-progress:
description: 'Whether to show progress status output when fetching.'
required: false
default: 'true'

lfs:
description: 'Whether to download Git-LFS files'
required: false
default: 'false'

submodules:
description: 'Whether to checkout submodules: true to checkout submodules or recursive to recursively checkout submodules.'
required: false
default: 'false'

set-safe-directory:
description: 'Add repository path as safe.directory for the checkout.'
required: false
default: 'true'

github-server-url:
description: 'The base URL for the GitHub instance that you are trying to clone from, it will use environment defaults to fetch from the same instance that the workflow is running from unless specified.'
required: false
default: ''

persist-credentials:
description: 'Whether to configure the token or SSH key with the local git config. Defaults to true so the app token persists for subsequent git operations.'
required: false
default: 'true'

outputs:
# ── Auth outputs (from github-app-auth) ──
token:
description: 'GitHub App token'
value: ${{ steps.auth.outputs.token }}

app-slug:
description: 'GitHub App slug name'
value: ${{ steps.auth.outputs.app-slug }}

user-id:
description: 'GitHub App bot user ID'
value: ${{ steps.auth.outputs.user-id }}

user-name:
description: 'GitHub App bot user name (app slug with [bot] suffix)'
value: ${{ steps.auth.outputs.user-name }}

user-email:
description: 'GitHub App bot email address'
value: ${{ steps.auth.outputs.user-email }}

# ── Checkout outputs ──
ref:
description: 'The branch, tag or SHA that was checked out'
value: ${{ steps.checkout.outputs.ref }}

commit:
description: 'The commit SHA that was checked out'
value: ${{ steps.checkout.outputs.commit }}

runs:
using: 'composite'
steps:
# ── Step 1: Authenticate as GitHub App (skip its built-in checkout) ──
- name: Authenticate as GitHub App
id: auth
uses: ./.github/actions/github-app-auth
with:
app-id: ${{ inputs.app-id }}
private-key: ${{ inputs.private-key }}
owner: ${{ inputs.owner }}
skip-checkout: 'true'

# ── Step 2: Checkout with app token baked into the remote URL ──
- name: Checkout repository as GitHub App
id: checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
with:
token: ${{ steps.auth.outputs.token }}
repository: ${{ inputs.repository }}
ref: ${{ inputs.ref }}
path: ${{ inputs.path }}
clean: ${{ inputs.clean }}
filter: ${{ inputs.filter }}
sparse-checkout: ${{ inputs.sparse-checkout }}
sparse-checkout-cone-mode: ${{ inputs.sparse-checkout-cone-mode }}
fetch-depth: ${{ inputs.fetch-depth }}
fetch-tags: ${{ inputs.fetch-tags }}
show-progress: ${{ inputs.show-progress }}
lfs: ${{ inputs.lfs }}
submodules: ${{ inputs.submodules }}
set-safe-directory: ${{ inputs.set-safe-directory }}
github-server-url: ${{ inputs.github-server-url }}
persist-credentials: ${{ inputs.persist-credentials }}
34 changes: 30 additions & 4 deletions .github/actions/github-app-auth/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ inputs:
required: false
default: ${{ github.repository_owner }}

skip-checkout:
description: 'Skip the checkout step (useful when called from checkout-as-app)'
required: false
default: 'false'

outputs:
token:
description: 'GitHub App token'
Expand All @@ -36,6 +41,10 @@ outputs:
description: 'GitHub App bot user name (app slug with [bot] suffix)'
value: ${{ steps.get-user-id.outputs.user-name }}

user-email:
description: 'GitHub App bot email address'
value: ${{ steps.get-user-id.outputs.user-email }}

runs:
using: 'composite'
steps:
Expand All @@ -51,8 +60,12 @@ runs:
id: get-user-id
shell: bash
run: |
echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
echo "user-name=${{ steps.app-token.outputs.app-slug }}[bot]" >> "$GITHUB_OUTPUT"
USER_ID=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)
USER_NAME="${{ steps.app-token.outputs.app-slug }}[bot]"
USER_EMAIL="${USER_ID}+${USER_NAME}@users.noreply.github.com"
echo "user-id=${USER_ID}" >> "$GITHUB_OUTPUT"
echo "user-name=${USER_NAME}" >> "$GITHUB_OUTPUT"
echo "user-email=${USER_EMAIL}" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}

Expand All @@ -63,13 +76,26 @@ runs:
run: |
gh auth setup-git
echo "GH_TOKEN=${GH_TOKEN}" >> $GITHUB_ENV
echo "Wrote GH_TOKEN to GITHUB_ENV"
echo "GITHUB_TOKEN=${GH_TOKEN}" >> $GITHUB_ENV
echo "Wrote GITHUB_TOKEN to GITHUB_ENV"
git config --global user.name '${{ steps.get-user-id.outputs.user-name }}'
git config user.name '${{ steps.get-user-id.outputs.user-name }}'
git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.get-user-id.outputs.user-name }}@users.noreply.github.com'
git config user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.get-user-id.outputs.user-name }}@users.noreply.github.com'
git config --global user.email '${{ steps.get-user-id.outputs.user-email }}'
git config user.email '${{ steps.get-user-id.outputs.user-email }}'

- name: Output GitHub App authentication info
shell: bash
run: |
echo "GitHub App Authentication Info:"
echo "-------------------------------"
echo "- User Name: ${{ steps.get-user-id.outputs.user-name }}"
echo "- User ID: ${{ steps.get-user-id.outputs.user-id }}"
echo "- User Email: ${{ steps.get-user-id.outputs.user-email }}"
echo "- App Slug: ${{ steps.app-token.outputs.app-slug }}"

- name: Configure repo checkout token
if: inputs.skip-checkout != 'true'
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
with:
token: ${{ steps.app-token.outputs.token }}
Expand Down
Loading