Switch from bot PAT to GitHub App token via Azure Key Vault (#63538)

This commit is contained in:
Jake Bailey
2026-06-08 12:58:53 -07:00
committed by GitHub
parent 7539c04d94
commit 345012d5a9
13 changed files with 1025 additions and 49 deletions

View File

@@ -5,6 +5,7 @@ on:
permissions:
contents: read
id-token: write
# Ensure scripts are run with pipefail. See:
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference
@@ -15,11 +16,14 @@ defaults:
jobs:
build:
runs-on: ubuntu-latest
environment:
name: azure
deployment: false
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
token: ${{ secrets.TS_BOT_GITHUB_TOKEN }}
persist-credentials: false
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 'lts/*'
@@ -37,4 +41,27 @@ jobs:
git add ./tests/baselines/reference
git diff --cached
git commit -m "Update Baselines, Applied Lint Fixes, and/or Formatted"
git push
- uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
- name: Create GitHub App token
id: app-token
uses: microsoft/create-github-app-token-via-key-vault@5ba0d436e9c3cac52feff4d1f2f66f9698ce4a2d # v1
with:
client-id: ${{ vars.TYPESCRIPT_AUTOMATION_GITHUB_APP_CLIENT_ID }}
key-id: ${{ vars.TYPESCRIPT_AUTOMATION_GITHUB_APP_KEY_ID }}
owner: microsoft
repositories: TypeScript
permission-contents: write
- name: Configure git for GitHub App token
shell: bash
env:
GITHUB_APP_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
set -euo pipefail
basic_auth="$(node -e 'process.stdout.write(Buffer.from("x-access-token:" + process.env.GITHUB_APP_TOKEN).toString("base64"))')"
echo "::add-mask::$basic_auth"
git config --local http.https://github.com/.extraheader "AUTHORIZATION: basic ${basic_auth}"
- run: git push

View File

@@ -7,6 +7,7 @@ on:
permissions:
contents: read
id-token: write
# Ensure scripts are run with pipefail. See:
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference
@@ -17,15 +18,36 @@ defaults:
jobs:
close-issues:
runs-on: ubuntu-latest
environment:
name: azure
deployment: false
if: github.repository == 'microsoft/TypeScript'
permissions:
contents: read # Apparently required to create issues
id-token: write
issues: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
- name: Create GitHub App token
id: app-token
uses: microsoft/create-github-app-token-via-key-vault@5ba0d436e9c3cac52feff4d1f2f66f9698ce4a2d # v1
with:
client-id: ${{ vars.TYPESCRIPT_AUTOMATION_GITHUB_APP_CLIENT_ID }}
key-id: ${{ vars.TYPESCRIPT_AUTOMATION_GITHUB_APP_KEY_ID }}
owner: microsoft
repositories: TypeScript
permission-issues: write
- name: Close issues
env:
GH_TOKEN: ${{ secrets.TS_BOT_GITHUB_TOKEN }}
GH_TOKEN: ${{ steps.app-token.outputs.token }}
REPO: ${{ github.repository }}
run: |
DATE=$(date --date='2 days ago' --iso-8601)

View File

@@ -34,6 +34,7 @@ run-name: ${{ github.workflow }}${{ inputs.distinct_id && format(' (bot run {0})
permissions:
contents: read
id-token: write
# Ensure scripts are run with pipefail. See:
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference
@@ -44,6 +45,9 @@ defaults:
jobs:
open-pr:
runs-on: ubuntu-latest
environment:
name: azure
deployment: false
if: github.repository == 'microsoft/TypeScript'
steps:
@@ -51,8 +55,32 @@ jobs:
with:
filter: blob:none # https://github.blog/2020-12-21-get-up-to-speed-with-partial-clone-and-shallow-clone/
fetch-depth: 0 # Default is 1; need to set to 0 to get the benefits of blob:none.
token: ${{ secrets.TS_BOT_GITHUB_TOKEN }}
persist-credentials: false
- uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
- name: Create GitHub App token
id: app-token
uses: microsoft/create-github-app-token-via-key-vault@5ba0d436e9c3cac52feff4d1f2f66f9698ce4a2d # v1
with:
client-id: ${{ vars.TYPESCRIPT_AUTOMATION_GITHUB_APP_CLIENT_ID }}
key-id: ${{ vars.TYPESCRIPT_AUTOMATION_GITHUB_APP_KEY_ID }}
owner: microsoft
repositories: TypeScript
permission-contents: write
permission-issues: write
permission-pull-requests: write
- name: Configure git for GitHub App token
shell: bash
env:
GITHUB_APP_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
set -euo pipefail
basic_auth="$(node -e 'process.stdout.write(Buffer.from("x-access-token:" + process.env.GITHUB_APP_TOKEN).toString("base64"))')"
echo "::add-mask::$basic_auth"
git config --local http.https://github.com/.extraheader "AUTHORIZATION: basic ${basic_auth}"
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
id: open-pr
env:
@@ -64,7 +92,7 @@ jobs:
STATUS_COMMENT: ${{ inputs.status_comment }}
with:
retries: 3
github-token: ${{ secrets.TS_BOT_GITHUB_TOKEN }}
github-token: ${{ steps.app-token.outputs.token }}
result-encoding: string
script: |
const {
@@ -182,7 +210,7 @@ jobs:
with:
success_comment: ${{ steps.open-pr.outputs.result }}
failure_comment: 'I was unable to cherry-pick this PR.'
github_token: ${{ secrets.TS_BOT_GITHUB_TOKEN }}
github_token: ${{ steps.app-token.outputs.token }}
distinct_id: ${{ inputs.distinct_id }}
source_issue: ${{ inputs.source_issue }}
requesting_user: ${{ inputs.requesting_user }}

View File

@@ -10,6 +10,7 @@ on:
permissions:
contents: read
id-token: write
# Ensure scripts are run with pipefail. See:
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference
@@ -20,6 +21,9 @@ defaults:
jobs:
build:
runs-on: ubuntu-latest
environment:
name: azure
deployment: false
steps:
- env:
BRANCH_NAME: ${{ inputs.branch_name }}
@@ -32,7 +36,7 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ inputs.branch_name }}
token: ${{ secrets.TS_BOT_GITHUB_TOKEN }}
persist-credentials: false
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 'lts/*'
@@ -48,4 +52,27 @@ jobs:
git config user.email "typescriptbot@microsoft.com"
git config user.name "TypeScript Bot"
git commit -m 'Update LKG'
git push
- uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
- name: Create GitHub App token
id: app-token
uses: microsoft/create-github-app-token-via-key-vault@5ba0d436e9c3cac52feff4d1f2f66f9698ce4a2d # v1
with:
client-id: ${{ vars.TYPESCRIPT_AUTOMATION_GITHUB_APP_CLIENT_ID }}
key-id: ${{ vars.TYPESCRIPT_AUTOMATION_GITHUB_APP_KEY_ID }}
owner: microsoft
repositories: TypeScript
permission-contents: write
- name: Configure git for GitHub App token
shell: bash
env:
GITHUB_APP_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
set -euo pipefail
basic_auth="$(node -e 'process.stdout.write(Buffer.from("x-access-token:" + process.env.GITHUB_APP_TOKEN).toString("base64"))')"
echo "::add-mask::$basic_auth"
git config --local http.https://github.com/.extraheader "AUTHORIZATION: basic ${basic_auth}"
- run: git push

View File

@@ -38,6 +38,7 @@ run-name: ${{ github.workflow }}${{ inputs.distinct_id && format(' (bot run {0})
permissions:
contents: read
id-token: write
# Ensure scripts are run with pipefail. See:
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference
@@ -48,13 +49,16 @@ defaults:
jobs:
build:
runs-on: ubuntu-latest
environment:
name: azure
deployment: false
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
filter: blob:none # https://github.blog/2020-12-21-get-up-to-speed-with-partial-clone-and-shallow-clone/
fetch-depth: 0 # Default is 1; need to set to 0 to get the benefits of blob:none.
token: ${{ secrets.TS_BOT_GITHUB_TOKEN }}
persist-credentials: false
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 'lts/*'
@@ -85,14 +89,39 @@ jobs:
git config user.email "typescriptbot@microsoft.com"
git config user.name "TypeScript Bot"
git commit -m "Bump version to $PACKAGE_VERSION and LKG"
git push --set-upstream origin "$BRANCH_NAME"
- uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
- name: Create GitHub App token
id: app-token
uses: microsoft/create-github-app-token-via-key-vault@5ba0d436e9c3cac52feff4d1f2f66f9698ce4a2d # v1
with:
client-id: ${{ vars.TYPESCRIPT_AUTOMATION_GITHUB_APP_CLIENT_ID }}
key-id: ${{ vars.TYPESCRIPT_AUTOMATION_GITHUB_APP_KEY_ID }}
owner: microsoft
repositories: TypeScript
permission-contents: write
- name: Configure git for GitHub App token
shell: bash
env:
GITHUB_APP_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
set -euo pipefail
basic_auth="$(node -e 'process.stdout.write(Buffer.from("x-access-token:" + process.env.GITHUB_APP_TOKEN).toString("base64"))')"
echo "::add-mask::$basic_auth"
git config --local http.https://github.com/.extraheader "AUTHORIZATION: basic ${basic_auth}"
- env:
BRANCH_NAME: ${{ inputs.branch_name }}
run: git push --set-upstream origin "$BRANCH_NAME"
- uses: microsoft/typescript-bot-test-triggerer/.github/actions/post-workflow-result@master
if: ${{ !cancelled() && inputs.distinct_id }}
with:
success_comment: "I've created ${{ inputs.branch_name }} with version ${{ inputs.package_version }} for you."
failure_comment: 'I was unable to create the new release branch.'
github_token: ${{ secrets.TS_BOT_GITHUB_TOKEN }}
github_token: ${{ steps.app-token.outputs.token }}
distinct_id: ${{ inputs.distinct_id }}
source_issue: ${{ inputs.source_issue }}
requesting_user: ${{ inputs.requesting_user }}

View File

@@ -17,6 +17,7 @@ concurrency:
permissions:
contents: read
id-token: write
# Ensure scripts are run with pipefail. See:
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference
@@ -27,17 +28,37 @@ defaults:
jobs:
manage-prs:
runs-on: ubuntu-latest
environment:
name: azure
deployment: false
if: github.repository == 'microsoft/TypeScript'
# No need to set explicit permissions; we are using typescript-bot's token, not github-actions' token.
# No need to set explicit permissions; we are using the GitHub App token, not github-actions' token.
env:
GH_TOKEN: ${{ secrets.TS_BOT_GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
REPO: ${{ github.repository }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
- name: Create GitHub App token
id: app-token
uses: microsoft/create-github-app-token-via-key-vault@5ba0d436e9c3cac52feff4d1f2f66f9698ce4a2d # v1
with:
client-id: ${{ vars.TYPESCRIPT_AUTOMATION_GITHUB_APP_CLIENT_ID }}
key-id: ${{ vars.TYPESCRIPT_AUTOMATION_GITHUB_APP_KEY_ID }}
owner: microsoft
repositories: TypeScript
permission-issues: write
permission-pull-requests: write
- name: Check if PR author is in pr_owners.txt
id: pr_owner
run: |
@@ -91,6 +112,8 @@ jobs:
- name: Generated DOM files
if: steps.pr_owner.outputs.pr_owner == 'false'
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
if ./is_changed.sh "src/lib/dom.generated.d.ts" \
"src/lib/dom.iterable.generated.d.ts" \
@@ -107,6 +130,8 @@ jobs:
fi
- name: Check if PR modifies protocol.ts
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
if ./is_changed.sh "src/server/protocol.ts"; then
MESSAGE="Thanks for the PR! It looks like you've changed the TSServer protocol in some way."
@@ -122,6 +147,8 @@ jobs:
fi
- name: Check for breaking changes
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
if ./is_changed.sh "tests/baselines/reference/api/typescript.d.ts"; then
MESSAGE="Looks like you're introducing a change to the public API surface area."

View File

@@ -38,6 +38,7 @@ run-name: ${{ github.workflow }}${{ inputs.distinct_id && format(' (bot run {0})
permissions:
contents: read
id-token: write
# Ensure scripts are run with pipefail. See:
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference
@@ -48,11 +49,14 @@ defaults:
jobs:
build:
runs-on: ubuntu-latest
environment:
name: azure
deployment: false
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ inputs.branch_name }}
token: ${{ secrets.TS_BOT_GITHUB_TOKEN }}
persist-credentials: false
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 'lts/*'
@@ -88,14 +92,37 @@ jobs:
git config user.email "typescriptbot@microsoft.com"
git config user.name "TypeScript Bot"
git commit -m "Bump version to $PACKAGE_VERSION and LKG"
git push
- uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
- name: Create GitHub App token
id: app-token
uses: microsoft/create-github-app-token-via-key-vault@5ba0d436e9c3cac52feff4d1f2f66f9698ce4a2d # v1
with:
client-id: ${{ vars.TYPESCRIPT_AUTOMATION_GITHUB_APP_CLIENT_ID }}
key-id: ${{ vars.TYPESCRIPT_AUTOMATION_GITHUB_APP_KEY_ID }}
owner: microsoft
repositories: TypeScript
permission-contents: write
- name: Configure git for GitHub App token
shell: bash
env:
GITHUB_APP_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
set -euo pipefail
basic_auth="$(node -e 'process.stdout.write(Buffer.from("x-access-token:" + process.env.GITHUB_APP_TOKEN).toString("base64"))')"
echo "::add-mask::$basic_auth"
git config --local http.https://github.com/.extraheader "AUTHORIZATION: basic ${basic_auth}"
- run: git push
- uses: microsoft/typescript-bot-test-triggerer/.github/actions/post-workflow-result@master
if: ${{ !cancelled() && inputs.distinct_id }}
with:
success_comment: "I've set the version of ${{ inputs.branch_name }} to ${{ inputs.package_version }} for you."
failure_comment: 'I was unable set the version.'
github_token: ${{ secrets.TS_BOT_GITHUB_TOKEN }}
github_token: ${{ steps.app-token.outputs.token }}
distinct_id: ${{ inputs.distinct_id }}
source_issue: ${{ inputs.source_issue }}
requesting_user: ${{ inputs.requesting_user }}

View File

@@ -30,6 +30,7 @@ run-name: ${{ github.workflow }}${{ inputs.distinct_id && format(' (bot run {0})
permissions:
contents: read
id-token: write
# Ensure scripts are run with pipefail. See:
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference
@@ -40,6 +41,9 @@ defaults:
jobs:
build:
runs-on: ubuntu-latest
environment:
name: azure
deployment: false
steps:
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
@@ -50,7 +54,7 @@ jobs:
ref: ${{ inputs.branch_name }}
filter: blob:none # https://github.blog/2020-12-21-get-up-to-speed-with-partial-clone-and-shallow-clone/
fetch-depth: 0 # Default is 1; need to set to 0 to get the benefits of blob:none.
token: ${{ secrets.TS_BOT_GITHUB_TOKEN }}
persist-credentials: false
# required client_payload members:
# branch_name - the target branch
- run: |
@@ -62,14 +66,37 @@ jobs:
npx hereby LKG
git add --force ./lib
git commit -m 'Update LKG'
git push
- uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
- name: Create GitHub App token
id: app-token
uses: microsoft/create-github-app-token-via-key-vault@5ba0d436e9c3cac52feff4d1f2f66f9698ce4a2d # v1
with:
client-id: ${{ vars.TYPESCRIPT_AUTOMATION_GITHUB_APP_CLIENT_ID }}
key-id: ${{ vars.TYPESCRIPT_AUTOMATION_GITHUB_APP_KEY_ID }}
owner: microsoft
repositories: TypeScript
permission-contents: write
- name: Configure git for GitHub App token
shell: bash
env:
GITHUB_APP_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
set -euo pipefail
basic_auth="$(node -e 'process.stdout.write(Buffer.from("x-access-token:" + process.env.GITHUB_APP_TOKEN).toString("base64"))')"
echo "::add-mask::$basic_auth"
git config --local http.https://github.com/.extraheader "AUTHORIZATION: basic ${basic_auth}"
- run: git push
- uses: microsoft/typescript-bot-test-triggerer/.github/actions/post-workflow-result@master
if: ${{ !cancelled() && inputs.distinct_id }}
with:
success_comment: "I've pulled main into ${{ inputs.branch_name }} for you."
failure_comment: 'I was unable merge main into ${{ inputs.branch_name }}.'
github_token: ${{ secrets.TS_BOT_GITHUB_TOKEN }}
github_token: ${{ steps.app-token.outputs.token }}
distinct_id: ${{ inputs.distinct_id }}
source_issue: ${{ inputs.source_issue }}
requesting_user: ${{ inputs.requesting_user }}

View File

@@ -4,6 +4,7 @@ on: [gollum]
permissions:
contents: read
id-token: write
# Ensure scripts are run with pipefail. See:
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference
@@ -14,17 +15,45 @@ defaults:
jobs:
sync:
runs-on: ubuntu-latest
environment:
name: azure
deployment: false
steps:
- name: Get repo name
run: R=${GITHUB_REPOSITORY%?wiki}; echo "BASENAME=${R##*/}" >> $GITHUB_ENV
- name: Checkout ${{ env.BASENAME }}-wiki
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: '${{ GITHUB.repository_owner }}/${{ env.BASENAME }}-wiki'
token: ${{ secrets.TS_BOT_GITHUB_TOKEN }}
repository: '${{ github.repository_owner }}/${{ env.BASENAME }}-wiki'
fetch-depth: 0
persist-credentials: false
- uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
- name: Create GitHub App token
id: app-token
uses: microsoft/create-github-app-token-via-key-vault@5ba0d436e9c3cac52feff4d1f2f66f9698ce4a2d # v1
with:
client-id: ${{ vars.TYPESCRIPT_AUTOMATION_GITHUB_APP_CLIENT_ID }}
key-id: ${{ vars.TYPESCRIPT_AUTOMATION_GITHUB_APP_KEY_ID }}
owner: microsoft
repositories: |
TypeScript
TypeScript-wiki
permission-contents: write
- name: Configure git for GitHub App token
shell: bash
env:
GITHUB_APP_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
set -euo pipefail
basic_auth="$(node -e 'process.stdout.write(Buffer.from("x-access-token:" + process.env.GITHUB_APP_TOKEN).toString("base64"))')"
echo "::add-mask::$basic_auth"
git config --local http.https://github.com/.extraheader "AUTHORIZATION: basic ${basic_auth}"
- name: Run sync
run: ./.github/workflows/sync
env:
PUSHER: typescript-bot <bot@typescriptlang.org>
AUTH: ${{ secrets.TS_BOT_GITHUB_TOKEN }}
AUTH: ${{ steps.app-token.outputs.token }}

View File

@@ -38,6 +38,7 @@ run-name: ${{ github.workflow }}${{ inputs.distinct_id && format(' (bot run {0})
permissions:
contents: read
id-token: write
# Ensure scripts are run with pipefail. See:
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference
@@ -49,19 +50,40 @@ jobs:
run:
if: ${{ github.repository == 'microsoft/TypeScript' }}
runs-on: ubuntu-latest
environment:
name: azure
deployment: false
steps:
- if: ${{ github.event.inputs.bisect }}
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
filter: blob:none # https://github.blog/2020-12-21-get-up-to-speed-with-partial-clone-and-shallow-clone/
fetch-depth: 0 # Default is 1; need to set to 0 to get the benefits of blob:none.
persist-credentials: false
- if: ${{ !github.event.inputs.bisect }}
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
- name: Create GitHub App token
id: app-token
uses: microsoft/create-github-app-token-via-key-vault@5ba0d436e9c3cac52feff4d1f2f66f9698ce4a2d # v1
with:
client-id: ${{ vars.TYPESCRIPT_AUTOMATION_GITHUB_APP_CLIENT_ID }}
key-id: ${{ vars.TYPESCRIPT_AUTOMATION_GITHUB_APP_KEY_ID }}
owner: microsoft
repositories: TypeScript
permission-contents: write
permission-issues: write
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 'lts/*'
- uses: microsoft/TypeScript-Twoslash-Repro-Action@master
with:
github-token: ${{ secrets.TS_BOT_GITHUB_TOKEN }}
github-token: ${{ steps.app-token.outputs.token }}
issue: ${{ github.event.inputs.issue }}
bisect: ${{ github.event.inputs.bisect }}

View File

@@ -9,6 +9,7 @@ on:
permissions:
contents: read
id-token: write
# Ensure scripts are run with pipefail. See:
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference
@@ -19,12 +20,15 @@ defaults:
jobs:
build:
runs-on: ubuntu-latest
environment:
name: azure
deployment: false
if: github.repository == 'microsoft/TypeScript'
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
token: ${{ secrets.TS_BOT_GITHUB_TOKEN }}
persist-credentials: false
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 'lts/*'
@@ -35,12 +39,14 @@ jobs:
npm --version
- name: Update package-lock.json and push
id: update
run: |
rm package-lock.json
npm install
if git diff --exit-code --name-only package-lock.json; then
echo "No change."
echo "changed=false" >> "$GITHUB_OUTPUT"
else
npm test
npx hereby LKG
@@ -48,5 +54,32 @@ jobs:
git config user.name "TypeScript Bot"
git add -f package-lock.json
git commit -m "Update package-lock.json"
git push
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
- uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0
if: steps.update.outputs.changed == 'true'
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
- name: Create GitHub App token
if: steps.update.outputs.changed == 'true'
id: app-token
uses: microsoft/create-github-app-token-via-key-vault@5ba0d436e9c3cac52feff4d1f2f66f9698ce4a2d # v1
with:
client-id: ${{ vars.TYPESCRIPT_AUTOMATION_GITHUB_APP_CLIENT_ID }}
key-id: ${{ vars.TYPESCRIPT_AUTOMATION_GITHUB_APP_KEY_ID }}
owner: microsoft
repositories: TypeScript
permission-contents: write
- name: Push
if: steps.update.outputs.changed == 'true'
shell: bash
env:
GITHUB_APP_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
set -euo pipefail
basic_auth="$(node -e 'process.stdout.write(Buffer.from("x-access-token:" + process.env.GITHUB_APP_TOKEN).toString("base64"))')"
echo "::add-mask::$basic_auth"
git config --local http.https://github.com/.extraheader "AUTHORIZATION: basic ${basic_auth}"
git push

View File

@@ -28,6 +28,10 @@ variables:
value: ${{ parameters.RELEASE_TITLE_NAME }}
- name: TAG_NAME
value: ${{ parameters.TAG_NAME }}
- name: TYPESCRIPT_AUTOMATION_GITHUB_APP_CLIENT_ID
value: 'Iv23li4GolzJSEp1mzHI'
- name: TYPESCRIPT_AUTOMATION_GITHUB_APP_KEY_ID
value: 'https://jststeam-passwords.vault.azure.net/keys/typescript-automation'
resources:
pipelines:
@@ -133,26 +137,86 @@ extends:
echo "##vso[task.setvariable variable=GIT_COMMIT_HASH]$GIT_COMMIT_HASH"
echo "Git commit hash: $GIT_COMMIT_HASH"
- task: GitHubRelease@1
displayName: GitHub release (create)
inputs:
# This must match the service connection name.
gitHubConnection: typescript-bot connection
repositoryName: microsoft/TypeScript
tagSource: userSpecifiedTag
tag: $(TAG_NAME)
title: TypeScript $(RELEASE_TITLE_NAME)
target: $(GIT_COMMIT_HASH)
releaseNotesSource: inline
releaseNotesInline: |
<!---
For release notes, check out the [release announcement]().
For new features, check out the [What's new in TypeScript $(TAG_NAME)]().
For the complete list of fixed issues, check out the
* [fixed issues query for TypeScript $(TAG_NAME)](https://github.com/microsoft/TypeScript/issues?utf8=%E2%9C%93&q=is%3Aissue+milestone%3A%22TypeScript+3.3%22+is%3Aclosed+).
Downloads are available on:
* [npm](https://www.npmjs.com/package/typescript)
-->
assets: $(Pipeline.Workspace)/tgz/**/typescript-*.tgz
isDraft: ${{ not(eq(parameters.PUBLISH_TAG, 'latest')) }}
addChangeLog: false
- template: scripts/create-github-app-token.yml
parameters:
repositories: TypeScript
permissions: contents:write
insertSteps:
- task: CmdLine@2
displayName: GitHub release (create)
inputs:
script: |
set -euo pipefail
TARBALL=$(find "$(Pipeline.Workspace)/tgz" -maxdepth 2 -name 'typescript-*.tgz' -type f | head -1)
if [ -z "$TARBALL" ] || [ ! -f "$TARBALL" ]; then
echo "ERROR: no typescript-*.tgz tarball found under $(Pipeline.Workspace)/tgz" >&2
exit 1
fi
TARBALL_NAME=$(basename "$TARBALL")
if [ "$PUBLISH_TAG" = "latest" ]; then
FINAL_DRAFT=false
else
FINAL_DRAFT=true
fi
RELEASE_BODY='<!---
For release notes, check out the [release announcement]().
For new features, check out the [What'\''s new in TypeScript '"$TAG_NAME"']().
For the complete list of fixed issues, check out the
* [fixed issues query for TypeScript '"$TAG_NAME"'](https://github.com/microsoft/TypeScript/issues?utf8=%E2%9C%93&q=is%3Aissue+milestone%3A%22TypeScript+3.3%22+is%3Aclosed+).
Downloads are available on:
* [npm](https://www.npmjs.com/package/typescript)
-->'
export RELEASE_BODY
echo "Creating draft release $TAG_NAME at $GIT_COMMIT_HASH"
# Create as draft first, then upload asset, then set final draft state.
# This avoids a half-created non-draft release if the upload fails.
RESPONSE=$(curl -fsS \
-X POST \
-H "Authorization: Bearer $GH_TOKEN" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/repos/microsoft/TypeScript/releases" \
-d "$(node -e "process.stdout.write(JSON.stringify({
tag_name: process.env.TAG_NAME,
target_commitish: process.env.GIT_COMMIT_HASH,
name: 'TypeScript ' + process.env.RELEASE_TITLE_NAME,
body: process.env.RELEASE_BODY,
draft: true
}))")")
RELEASE_ID=$(node -e "process.stdout.write(String(JSON.parse(process.argv[1]).id))" "$RESPONSE")
UPLOAD_URL=$(node -e "process.stdout.write(JSON.parse(process.argv[1]).upload_url.replace('{?name,label}',''))" "$RESPONSE")
echo "Release ID: $RELEASE_ID"
echo "Upload URL: $UPLOAD_URL"
echo "Uploading $TARBALL_NAME"
curl -fsS \
-X POST \
-H "Authorization: Bearer $GH_TOKEN" \
-H "Accept: application/vnd.github+json" \
-H "Content-Type: application/gzip" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"${UPLOAD_URL}?name=${TARBALL_NAME}" \
--data-binary "@${TARBALL}"
# Set the final draft state (may undraft the release).
curl -fsS \
-X PATCH \
-H "Authorization: Bearer $GH_TOKEN" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/repos/microsoft/TypeScript/releases/${RELEASE_ID}" \
-d "{\"draft\": ${FINAL_DRAFT}}"
echo "Release created (draft=$FINAL_DRAFT) and tarball uploaded."
env:
GH_TOKEN: $(GH_TOKEN)
TAG_NAME: $(TAG_NAME)
GIT_COMMIT_HASH: $(GIT_COMMIT_HASH)
PUBLISH_TAG: $(PUBLISH_TAG)
RELEASE_TITLE_NAME: $(RELEASE_TITLE_NAME)

View File

@@ -0,0 +1,614 @@
# Step template for creating a GitHub App token via Azure Key Vault.
# This template can be used in release jobs where checkout is not available.
# The CLI script from microsoft/create-github-app-token-via-key-vault is inlined below.
#
# The token is created and stored in an Azure Pipelines variable (default: GH_TOKEN).
# A revocation step runs at the end with condition: always().
# To use the token, add your steps between this template's create and revoke steps
# by setting insertSteps.
parameters:
- name: azureSubscription
type: string
default: 'TypeScript Public CI'
- name: owner
type: string
default: 'microsoft'
- name: repositories
type: string
- name: permissions
type: string
- name: tokenVariable
type: string
default: 'GH_TOKEN'
- name: insertSteps
type: stepList
default: []
steps:
- task: AzureCLI@2
displayName: Create GitHub App token
inputs:
azureSubscription: ${{ parameters.azureSubscription }}
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
cat << 'GITHUB_APP_TOKEN_CLI_EOF' > /tmp/create-github-app-token.cjs
"use strict";
// src/api.ts
var defaultRefreshWindowMs = 5 * 60 * 1e3;
var defaultGitHubApiUrl = "https://api.github.com";
var transientRetryCount = 3;
var GitHubRequestError = class extends Error {
status;
constructor(message, status) {
super(message);
this.status = status;
}
};
function assertValue(value, message) {
if (!value) {
throw new Error(message);
}
return value;
}
function base64url(value) {
return Buffer.from(value).toString("base64url");
}
async function sleep(ms) {
await new Promise((resolve) => setTimeout(resolve, ms));
}
function isRetryableError(error) {
return error instanceof GitHubRequestError ? error.status >= 500 : error instanceof TypeError;
}
async function retryTransient(operation) {
for (let attempt = 0; ; attempt++) {
try {
return await operation();
} catch (error) {
if (attempt >= transientRetryCount || !isRetryableError(error)) {
throw error;
}
await sleep(2 ** attempt * 1e3);
}
}
}
function splitRepositoryNames(repositories) {
if (Array.isArray(repositories)) {
return repositories.map((repo) => `${repo}`.trim()).filter(Boolean);
}
if (typeof repositories === "string") {
return repositories.split(/[,\n]/).map((repo) => repo.trim()).filter(Boolean);
}
return [];
}
function stableObject(value) {
if (!value || typeof value !== "object" || Array.isArray(value)) {
return value;
}
return Object.fromEntries(
Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, stableObject(entry)])
);
}
function githubHeaders(token, json = false) {
return {
"Accept": "application/vnd.github+json",
"Authorization": `Bearer ${token}`,
...json ? { "Content-Type": "application/json" } : {},
"X-GitHub-Api-Version": "2022-11-28"
};
}
function isRecord(value) {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function requiredIntegerProperty(value, property, failureMessage) {
const propertyValue = isRecord(value) ? value[property] : void 0;
if (typeof propertyValue !== "number" || !Number.isInteger(propertyValue)) {
throw new Error(failureMessage);
}
return propertyValue;
}
function requiredStringProperty(value, property, failureMessage) {
const propertyValue = isRecord(value) ? value[property] : void 0;
if (typeof propertyValue !== "string" || !propertyValue) {
throw new Error(failureMessage);
}
return propertyValue;
}
function validatePermissionName(key) {
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
throw new Error(`Invalid permission name: ${key}`);
}
}
function validatePermissionLevel(key, level) {
if (level !== "read" && level !== "write" && level !== "admin") {
throw new Error(`Invalid permission level for ${key}: ${level}`);
}
return level;
}
function validatePermissions(value) {
if (value === void 0) {
return void 0;
}
if (!isRecord(value)) {
throw new Error("permissions must be an object");
}
const permissions = {};
for (const [key, level] of Object.entries(value)) {
validatePermissionName(key);
permissions[key] = validatePermissionLevel(key, level);
}
return Object.keys(permissions).length === 0 ? void 0 : permissions;
}
async function requestJson(url, init, failureMessage) {
const response = await fetch(url, init);
const body = await response.text();
if (!response.ok) {
throw new GitHubRequestError(
`${failureMessage}: ${response.status} ${response.statusText}: ${body}`,
response.status
);
}
try {
return JSON.parse(body);
} catch {
throw new Error(`${failureMessage}: GitHub returned invalid JSON`);
}
}
async function requestNoContent(url, init, failureMessage) {
const response = await fetch(url, init);
if (!response.ok) {
const body = await response.text();
throw new GitHubRequestError(
`${failureMessage}: ${response.status} ${response.statusText}: ${body}`,
response.status
);
}
}
function parseRepositoryInput(input) {
const parts = input.split("/");
if (parts.length === 1 && parts[0]) {
return { input, name: parts[0] };
}
if (parts.length === 2 && parts[0] && parts[1]) {
return { input, owner: parts[0], name: parts[1] };
}
throw new Error(`Invalid repository '${input}'. Expected 'repository' or 'owner/repository'.`);
}
function normalizeRepositoryTarget(owner, repositories, defaultOwner) {
const parsedRepositories = repositories.map(parseRepositoryInput);
const repositoryOwner = parsedRepositories.find((repository) => repository.owner)?.owner;
const parsedOwner = owner || defaultOwner || repositoryOwner;
if (!parsedOwner) {
throw new Error("owner is required when repositories are provided");
}
const mismatchedRepository = parsedRepositories.find(
(repository) => repository.owner && repository.owner.toLowerCase() !== parsedOwner.toLowerCase()
);
if (mismatchedRepository) {
throw new Error(
`Repository '${mismatchedRepository.input}' includes owner '${mismatchedRepository.owner}', which does not match the resolved owner '${parsedOwner}'.`
);
}
return {
owner: parsedOwner,
repositories: parsedRepositories.map((repository) => repository.name)
};
}
function resolveInstallationTarget(options, defaultOwner) {
const repositories = splitRepositoryNames(options.repositories ?? options.repositoryNames);
if (options.enterprise) {
if (options.owner || repositories.length > 0) {
throw new Error("Cannot use 'enterprise' with 'owner' or 'repositories'");
}
return { type: "enterprise", enterprise: options.enterprise };
}
const owner = assertValue(options.owner ?? defaultOwner, "owner is required to discover installation ID");
if (repositories.length === 0) {
return { type: "owner", owner };
}
return { type: "repository", owner, repositories };
}
function createGitHubAppAuth(options) {
assertValue(options.appClientId, "appClientId is required");
assertValue(options.signer, "signer is required");
const appClientId = options.appClientId;
const signer = options.signer;
const defaultOwner = options.defaultOwner;
const refreshWindowMs = options.refreshWindowMs ?? defaultRefreshWindowMs;
const githubApiUrl = options.githubApiUrl ?? defaultGitHubApiUrl;
const installationCache = /* @__PURE__ */ new Map();
const tokenCache = /* @__PURE__ */ new Map();
async function createJwt() {
const now = Math.floor(Date.now() / 1e3);
const iat = now - 60;
const exp = now + 9 * 60;
const header = base64url(JSON.stringify({ typ: "JWT", alg: "RS256" }));
const payload = base64url(JSON.stringify({ iat, exp, iss: appClientId }));
const signingInput = `${header}.${payload}`;
const signature = await signer(signingInput);
return `${signingInput}.${signature}`;
}
async function discoverInstallation(target) {
const cacheKey = JSON.stringify(target);
const cached = installationCache.get(cacheKey);
if (cached !== void 0) {
return cached;
}
const jwt = await createJwt();
let installation;
switch (target.type) {
case "enterprise":
installation = await requestJson(
`${githubApiUrl}/enterprises/${target.enterprise}/installation`,
{ headers: githubHeaders(jwt) },
"Could not discover GitHub App installation ID"
);
break;
case "owner":
try {
installation = await requestJson(
`${githubApiUrl}/orgs/${target.owner}/installation`,
{ headers: githubHeaders(jwt) },
"Could not discover GitHub App installation ID"
);
} catch (error) {
if (!(error instanceof GitHubRequestError) || error.status !== 404) {
throw error;
}
installation = await requestJson(
`${githubApiUrl}/users/${target.owner}/installation`,
{ headers: githubHeaders(jwt) },
"Could not discover GitHub App installation ID"
);
}
break;
case "repository":
installation = await requestJson(
`${githubApiUrl}/repos/${target.owner}/${assertValue(target.repositories[0], "repository is required")}/installation`,
{ headers: githubHeaders(jwt) },
"Could not discover GitHub App installation ID"
);
break;
}
const result = {
id: requiredIntegerProperty(installation, "id", "GitHub did not return an installation ID"),
appSlug: requiredStringProperty(installation, "app_slug", "GitHub did not return an App slug")
};
installationCache.set(cacheKey, result);
return result;
}
async function getInstallationToken(options2) {
const target = resolveInstallationTarget(options2, defaultOwner);
const permissions = validatePermissions(options2.permissions);
return retryTransient(async () => {
const installation = await discoverInstallation(target);
const repositories = target.type === "repository" ? target.repositories : [];
const cacheKey = JSON.stringify({
installationId: installation.id,
repositories: [...repositories].sort(),
permissions: stableObject(permissions)
});
const cached = tokenCache.get(cacheKey);
if (cached && Date.now() < new Date(cached.expiresAt).getTime() - refreshWindowMs) {
return cached;
}
const jwt = await createJwt();
const body = {
...repositories.length > 0 ? { repositories } : {},
...permissions ? { permissions } : {}
};
const token = await requestJson(
`${githubApiUrl}/app/installations/${installation.id}/access_tokens`,
{
method: "POST",
headers: githubHeaders(jwt, true),
body: JSON.stringify(body)
},
"Could not create GitHub App installation token"
);
const result = {
token: requiredStringProperty(token, "token", "GitHub did not return an installation token"),
expiresAt: requiredStringProperty(
token,
"expires_at",
"GitHub did not return an installation token expiration"
),
installationId: installation.id,
appSlug: installation.appSlug,
repositories,
permissions: isRecord(token) && isRecord(token["permissions"]) ? token["permissions"] : permissions ?? {}
};
tokenCache.set(cacheKey, result);
return result;
});
}
async function getToken(options2) {
return (await getInstallationToken(options2)).token;
}
async function revokeToken(token) {
await requestNoContent(
`${githubApiUrl}/installation/token`,
{
method: "DELETE",
headers: githubHeaders(token)
},
"Could not revoke GitHub App installation token"
);
}
return {
getInstallationToken,
getToken,
revokeToken
};
}
// src/azureCliSigner.ts
var import_node_child_process = require("node:child_process");
var import_node_crypto = require("node:crypto");
var cachedAzCommand;
function isRecord2(value) {
return typeof value === "object" && value !== null;
}
function base64ToBase64url(value) {
return value.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
}
function commandExists(command) {
try {
(0, import_node_child_process.execFileSync)("where.exe", [command], {
encoding: "utf8",
stdio: ["ignore", "ignore", "ignore"]
});
return true;
} catch {
return false;
}
}
function azCommand() {
if (cachedAzCommand) {
return cachedAzCommand;
}
if (process.platform !== "win32") {
cachedAzCommand = { command: "az", argsPrefix: [] };
return cachedAzCommand;
}
for (const command of ["az.exe", "az.cmd", "az.bat", "az"]) {
if (commandExists(command)) {
cachedAzCommand = command.endsWith(".exe") ? { command, argsPrefix: [] } : { command: process.env["ComSpec"] || "cmd.exe", argsPrefix: ["/d", "/s", "/c", command] };
return cachedAzCommand;
}
}
throw new Error("Azure CLI (`az`) was not found on PATH");
}
function signDigest(keyId, digest) {
const { command, argsPrefix } = azCommand();
try {
return (0, import_node_child_process.execFileSync)(command, [
...argsPrefix,
"keyvault",
"key",
"sign",
"--id",
keyId,
"--algorithm",
"RS256",
"--digest",
digest,
"--query",
"signature",
"--output",
"tsv",
"--only-show-errors"
], {
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"]
}).trim();
} catch (error) {
if (isRecord2(error)) {
if (error["code"] === "ENOENT") {
throw new Error("Azure CLI (`az`) was not found on PATH");
}
const errorStderr = error["stderr"];
const stderr = typeof errorStderr === "string" ? errorStderr.trim() : "";
if (typeof error["status"] === "number") {
throw new Error(
`Azure Key Vault signing failed with exit code ${error["status"]}${stderr ? `: ${stderr}` : ""}`
);
}
}
throw new Error("Azure Key Vault signing failed");
}
}
function createAzureCliKeyVaultSigner(keyId) {
if (!keyId) {
throw new Error("keyId is required");
}
return async (signingInput) => {
const digest = (0, import_node_crypto.createHash)("sha256").update(signingInput).digest("base64");
const signature = signDigest(keyId, digest);
if (!signature) {
throw new Error("Azure Key Vault did not return a signature");
}
return base64ToBase64url(signature);
};
}
// src/proxy.ts
var proxyEnvironmentKeys = [
"https_proxy",
"HTTPS_PROXY",
"http_proxy",
"HTTP_PROXY"
];
function proxyEnvironmentConfigured() {
return proxyEnvironmentKeys.some((key) => process.env[key]);
}
function nativeProxySupportEnabled() {
return process.env["NODE_USE_ENV_PROXY"] === "1";
}
function ensureNativeProxySupport() {
if (!proxyEnvironmentConfigured() || nativeProxySupportEnabled()) {
return;
}
throw new Error(
"A proxy environment variable is set, but Node.js native proxy support is not enabled. Set NODE_USE_ENV_PROXY=1 before running this tool."
);
}
// src/cli.ts
function requiredEnv(name) {
const value = process.env[name];
if (!value) {
throw new Error(`${name} must be set`);
}
return value;
}
function getAppClientId() {
const appClientId = process.env["APP_CLIENT_ID"];
if (!appClientId) {
throw new Error("APP_CLIENT_ID must be set");
}
return appClientId;
}
function validatePermissionName2(key) {
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
throw new Error(`Invalid permission name: ${key}`);
}
}
function validatePermissionLevel2(key, level) {
if (level !== "read" && level !== "write" && level !== "admin") {
throw new Error(`Invalid permission level for ${key}: ${level}`);
}
return level;
}
function parsePermissions(value) {
if (!value) {
return void 0;
}
const permissions = {};
for (const entry of splitRepositoryNames(value)) {
const parts = entry.split(":");
if (parts.length !== 2) {
throw new Error(`Permission entry must include an explicit level: ${entry}`);
}
const key = parts[0]?.trim();
const rawLevel = parts[1]?.trim();
if (!key) {
throw new Error(`Permission entry must include a permission name: ${entry}`);
}
validatePermissionName2(key);
if (Object.hasOwn(permissions, key)) {
throw new Error(`Duplicate permission: ${key}`);
}
permissions[key] = validatePermissionLevel2(key, rawLevel);
}
return Object.keys(permissions).length === 0 ? void 0 : permissions;
}
function validateVariableName(name, envName) {
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
throw new Error(`${envName} must be an environment-style variable name`);
}
}
function parseOutputMode(value) {
const output = (value || "stdout").trim().toLowerCase();
if (output === "azure" || output === "azure-pipelines" || output === "stdout") {
return output;
}
throw new Error('OUTPUT must be "azure", "azure-pipelines", or "stdout"');
}
function getTokenOptions() {
const enterprise = process.env["ENTERPRISE"];
const owner = process.env["OWNER"];
const repositories = splitRepositoryNames(process.env["REPOSITORIES"]);
const permissions = parsePermissions(process.env["PERMISSIONS"]);
if (enterprise) {
if (owner || repositories.length > 0) {
throw new Error("Cannot use ENTERPRISE with OWNER or REPOSITORIES");
}
return { enterprise, permissions };
}
if (repositories.length > 0) {
return { ...normalizeRepositoryTarget(owner, repositories, void 0), permissions };
}
if (owner) {
return { owner, permissions };
}
throw new Error("OWNER, REPOSITORIES, or ENTERPRISE must be set");
}
function writeAzurePipelinesOutput(installationToken) {
const variableName = requiredEnv("AZURE_TOKEN_VARIABLE");
validateVariableName(variableName, "AZURE_TOKEN_VARIABLE");
process.stdout.write(`##vso[task.setvariable variable=${variableName};isSecret=true]${installationToken.token}
`);
}
function writeOutput(installationToken, output) {
switch (output) {
case "azure":
case "azure-pipelines":
writeAzurePipelinesOutput(installationToken);
break;
case "stdout":
process.stdout.write(`${installationToken.token}
`);
break;
}
}
function reportError(error) {
const message = error instanceof Error ? error.message : String(error);
console.error(`error: ${message}`);
}
async function main() {
ensureNativeProxySupport();
const githubApiUrl = process.env["GITHUB_API_URL"] || "https://api.github.com";
const revokeTokenValue = process.env["REVOKE_TOKEN"];
if (revokeTokenValue) {
const response = await fetch(`${githubApiUrl}/installation/token`, {
method: "DELETE",
headers: {
"Accept": "application/vnd.github+json",
"Authorization": `Bearer ${revokeTokenValue}`,
"X-GitHub-Api-Version": "2022-11-28"
}
});
if (!response.ok) {
const body = await response.text();
throw new Error(`Could not revoke token: ${response.status} ${response.statusText}: ${body}`);
}
return;
}
const githubAuth = createGitHubAppAuth({
appClientId: getAppClientId(),
signer: createAzureCliKeyVaultSigner(requiredEnv("KEY_ID")),
githubApiUrl
});
const installationToken = await githubAuth.getInstallationToken(getTokenOptions());
writeOutput(installationToken, parseOutputMode(process.env["OUTPUT"]));
}
void main().catch((error) => {
reportError(error);
process.exitCode = 1;
});
GITHUB_APP_TOKEN_CLI_EOF
node /tmp/create-github-app-token.cjs
env:
APP_CLIENT_ID: $(TYPESCRIPT_AUTOMATION_GITHUB_APP_CLIENT_ID)
KEY_ID: $(TYPESCRIPT_AUTOMATION_GITHUB_APP_KEY_ID)
OWNER: ${{ parameters.owner }}
REPOSITORIES: ${{ parameters.repositories }}
PERMISSIONS: ${{ parameters.permissions }}
OUTPUT: azure-pipelines
AZURE_TOKEN_VARIABLE: ${{ parameters.tokenVariable }}
- ${{ each step in parameters.insertSteps }}:
- ${{ step }}
- task: Bash@3
displayName: Revoke GitHub App token
condition: always()
continueOnError: true
inputs:
targetType: inline
script: node /tmp/create-github-app-token.cjs
env:
REVOKE_TOKEN: $(${{ parameters.tokenVariable }})