mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-06-13 02:45:24 -05:00
Switch from bot PAT to GitHub App token via Azure Key Vault (#63538)
This commit is contained in:
@@ -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
|
||||
|
||||
24
.github/workflows/close-issues.yml
vendored
24
.github/workflows/close-issues.yml
vendored
@@ -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)
|
||||
|
||||
36
.github/workflows/create-cherry-pick-pr.yml
vendored
36
.github/workflows/create-cherry-pick-pr.yml
vendored
@@ -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 }}
|
||||
|
||||
31
.github/workflows/lkg.yml
vendored
31
.github/workflows/lkg.yml
vendored
@@ -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
|
||||
|
||||
35
.github/workflows/new-release-branch.yaml
vendored
35
.github/workflows/new-release-branch.yaml
vendored
@@ -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 }}
|
||||
|
||||
31
.github/workflows/pr-modified-files.yml
vendored
31
.github/workflows/pr-modified-files.yml
vendored
@@ -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."
|
||||
|
||||
33
.github/workflows/set-version.yaml
vendored
33
.github/workflows/set-version.yaml
vendored
@@ -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 }}
|
||||
|
||||
33
.github/workflows/sync-branch.yaml
vendored
33
.github/workflows/sync-branch.yaml
vendored
@@ -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 }}
|
||||
|
||||
35
.github/workflows/sync-wiki.yml
vendored
35
.github/workflows/sync-wiki.yml
vendored
@@ -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 }}
|
||||
|
||||
24
.github/workflows/twoslash-repros.yaml
vendored
24
.github/workflows/twoslash-repros.yaml
vendored
@@ -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 }}
|
||||
|
||||
37
.github/workflows/update-package-lock.yaml
vendored
37
.github/workflows/update-package-lock.yaml
vendored
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
614
scripts/create-github-app-token.yml
Normal file
614
scripts/create-github-app-token.yml
Normal 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 }})
|
||||
Reference in New Issue
Block a user