Compare commits
88 Commits
v1.10.2
...
v3.0.0-beta.3
| Author | SHA1 | Date | |
|---|---|---|---|
| d28ad69b67 | |||
| 54e58b612c | |||
| bf559f8544 | |||
| cda91bf2b9 | |||
| 2ae58da528 | |||
| fb1c7fda2b | |||
| 61789386cb | |||
| 8ab05a8a84 | |||
| d00315e88c | |||
| fcc6c288e5 | |||
| 49bb2edce3 | |||
| a8d6161485 | |||
| 5204204e81 | |||
| 0f859bf9e6 | |||
| a1cbe0fa3c | |||
| d7ee281215 | |||
| 93c1f04d6f | |||
| dff4b11d10 | |||
| 6d44c9fd24 | |||
| df432ceedc | |||
| 333678481b | |||
| db3cdf4098 | |||
| d64d7d7355 | |||
| 1b6f53e48e | |||
| 061a84d5f5 | |||
| c8f34a61a8 | |||
| 4821f52fa7 | |||
| 2950cbc446 | |||
| 30bf6253fa | |||
| c3c17c79cc | |||
| 9ba274d954 | |||
| a3c826a204 | |||
| 3ff1caaa28 | |||
| eaef29498f | |||
| 86e24964d6 | |||
| 2411bfc792 | |||
| f17d09a7b5 | |||
| e250d17c7a | |||
| ed258b491a | |||
| 5c652ca715 | |||
| 60ee75db78 | |||
| 064492a9a1 | |||
| 5cc811bc40 | |||
| 23b44b2c8e | |||
| 6f3f59186f | |||
| d72941d797 | |||
| 0e0aa99a86 | |||
| f577941506 | |||
| af35edadc0 | |||
| a24b46a462 | |||
| 21cfef2b49 | |||
| 1ff1dea6a9 | |||
| 0d564482f0 | |||
| 8cedd97af1 | |||
| 415f6a523d | |||
| c14f92a8f9 | |||
| d30def842e | |||
| a5be4722a6 | |||
| 67e27a7eb7 | |||
| 8e85a3cf14 | |||
| 136412a57a | |||
| b4192a5b36 | |||
| 29aa0514a7 | |||
| a5f8600f58 | |||
| 0edddd70c8 | |||
| bb3ca765af | |||
| c1a285145b | |||
| fa6118ca85 | |||
| ae140fab7b | |||
| c84b152776 | |||
| 26a5f3652e | |||
| 6f9957685a | |||
| 25cc3bdc27 | |||
| a2c2dfabb4 | |||
| 349e62c51a | |||
| 5d869da34e | |||
| 796b88dc58 | |||
| 3378cda945 | |||
| e177c20e0f | |||
| 961c2284dc | |||
| 15db0371da | |||
| 9ccc6dbd71 | |||
| 000e2a0d29 | |||
| d0ac2addd1 | |||
| 040c2598aa | |||
| 31c86eb3b3 | |||
| cc82279e84 | |||
| 74cd7f68cb |
+1
-1
@@ -1 +1 @@
|
||||
* @gr2m @parkerbxyz @actions/create-github-app-token-maintainers
|
||||
* @actions/create-github-app-token-maintainers
|
||||
|
||||
+22
-11
@@ -1,19 +1,30 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
- package-ecosystem: 'npm'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: 'monthly'
|
||||
groups:
|
||||
production-dependencies:
|
||||
dependency-type: "production"
|
||||
dependency-type: 'production'
|
||||
update-types:
|
||||
- minor
|
||||
- patch
|
||||
development-dependencies:
|
||||
dependency-type: "development"
|
||||
dependency-type: 'development'
|
||||
update-types:
|
||||
- minor
|
||||
- patch
|
||||
commit-message:
|
||||
prefix: "fix"
|
||||
prefix-development: "build"
|
||||
include: "scope"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
prefix: 'fix'
|
||||
prefix-development: 'build'
|
||||
include: 'scope'
|
||||
- package-ecosystem: 'github-actions'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: 'monthly'
|
||||
groups:
|
||||
github-actions:
|
||||
update-types:
|
||||
- minor
|
||||
- patch
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
name: 'Publish Immutable Action'
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Publish Immutable Action
|
||||
uses: actions/publish-immutable-action@v0.0.4
|
||||
@@ -3,7 +3,9 @@ name: release
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "*.x"
|
||||
- main
|
||||
- beta
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -16,13 +18,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# build local version to create token
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
node-version-file: package.json
|
||||
cache: 'npm'
|
||||
|
||||
- run: npm ci
|
||||
|
||||
@@ -4,22 +4,27 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- beta
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
integration:
|
||||
name: Integration
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
node-version-file: package.json
|
||||
cache: 'npm'
|
||||
|
||||
- run: npm ci
|
||||
@@ -29,13 +34,13 @@ jobs:
|
||||
name: End-to-End
|
||||
runs-on: ubuntu-latest
|
||||
# do not run from forks, as forks don’t have access to repository secrets
|
||||
if: github.event.pull_request.head.repo.owner.login == github.event.pull_request.base.repo.owner.login
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.owner.login == github.event.pull_request.base.repo.owner.login
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "npm"
|
||||
node-version-file: package.json
|
||||
cache: 'npm'
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
- uses: ./ # Uses the action in the root directory
|
||||
@@ -50,3 +55,28 @@ jobs:
|
||||
with:
|
||||
route: GET /installation/repositories
|
||||
- run: echo '${{ steps.get-repository.outputs.data }}'
|
||||
|
||||
end-to-end-proxy:
|
||||
name: End-to-End with unreachable proxy
|
||||
runs-on: ubuntu-latest
|
||||
# do not run from forks, as forks don’t have access to repository secrets
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.owner.login == github.event.pull_request.base.repo.owner.login
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: package.json
|
||||
cache: 'npm'
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
- uses: ./ # Uses the action in the root directory
|
||||
continue-on-error: true
|
||||
id: test
|
||||
env:
|
||||
NODE_USE_ENV_PROXY: "1"
|
||||
https_proxy: http://127.0.0.1:9
|
||||
with:
|
||||
app-id: ${{ vars.TEST_APP_ID }}
|
||||
private-key: ${{ secrets.TEST_APP_PRIVATE_KEY }}
|
||||
- name: Assert action failed through unreachable proxy
|
||||
run: test "${{ steps.test.outcome }}" = "failure"
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
name: Update Permission Inputs
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
update-permission-inputs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: package.json
|
||||
cache: 'npm'
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Run permission inputs update script
|
||||
run: node scripts/update-permission-inputs.js
|
||||
- name: Commit changes
|
||||
uses: stefanzweifel/git-auto-commit-action@b863ae1933cb653a53c021fe36dbb774e1fb9403 # v5.2.0
|
||||
with:
|
||||
commit_message: 'feat: update permission inputs'
|
||||
@@ -1 +0,0 @@
|
||||
20.9.0
|
||||
@@ -0,0 +1,15 @@
|
||||
# Contributing
|
||||
|
||||
Initial setup
|
||||
|
||||
```console
|
||||
npm install
|
||||
```
|
||||
|
||||
Run tests locally
|
||||
|
||||
```console
|
||||
npm test
|
||||
```
|
||||
|
||||
Learn more about how the tests work in [tests/README.md](tests/README.md).
|
||||
@@ -8,9 +8,9 @@ GitHub Action for creating a GitHub App installation access token.
|
||||
|
||||
In order to use this action, you need to:
|
||||
|
||||
1. [Register new GitHub App](https://docs.github.com/apps/creating-github-apps/setting-up-a-github-app/creating-a-github-app)
|
||||
2. [Store the App's ID in your repository environment variables](https://docs.github.com/actions/learn-github-actions/variables#defining-configuration-variables-for-multiple-workflows) (example: `APP_ID`)
|
||||
3. [Store the App's private key in your repository secrets](https://docs.github.com/actions/security-guides/encrypted-secrets?tool=webui#creating-encrypted-secrets-for-a-repository) (example: `PRIVATE_KEY`)
|
||||
1. [Register new GitHub App](https://docs.github.com/apps/creating-github-apps/setting-up-a-github-app/creating-a-github-app).
|
||||
2. [Store the App's ID or Client ID in your repository environment variables](https://docs.github.com/actions/learn-github-actions/variables#defining-configuration-variables-for-multiple-workflows) (example: `APP_ID`).
|
||||
3. [Store the App's private key in your repository secrets](https://docs.github.com/actions/security-guides/encrypted-secrets?tool=webui#creating-encrypted-secrets-for-a-repository) (example: `PRIVATE_KEY`).
|
||||
|
||||
> [!IMPORTANT]
|
||||
> An installation access token expires after 1 hour. Please [see this comment](https://github.com/actions/create-github-app-token/issues/121#issuecomment-2043214796) for alternative approaches if you have long-running processes.
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
hello-world:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v1
|
||||
- uses: actions/create-github-app-token@v3
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.APP_ID }}
|
||||
@@ -47,13 +47,13 @@ jobs:
|
||||
auto-format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v1
|
||||
- uses: actions/create-github-app-token@v3
|
||||
id: app-token
|
||||
with:
|
||||
# required
|
||||
app-id: ${{ vars.APP_ID }}
|
||||
private-key: ${{ secrets.PRIVATE_KEY }}
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
ref: ${{ github.head_ref }}
|
||||
@@ -73,17 +73,59 @@ jobs:
|
||||
auto-format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v1
|
||||
- uses: actions/create-github-app-token@v3
|
||||
id: app-token
|
||||
with:
|
||||
# required
|
||||
app-id: ${{ vars.APP_ID }}
|
||||
private-key: ${{ secrets.PRIVATE_KEY }}
|
||||
- name: Get GitHub App User ID
|
||||
id: get-user-id
|
||||
run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
- id: committer
|
||||
run: echo "string=${{steps.app-auth.outputs.app-slug}}[bot] <${{ steps.app-auth.outputs.installation-id }}+${{ steps.app-auth.outputs.app-slug }}[bot]@users.noreply.github.com>" >> "$GITHUB_OUTPUT"
|
||||
- run: echo "committer string is ${{steps.committer.outputs.string}}"
|
||||
run: echo "string=${{ steps.app-token.outputs.app-slug }}[bot] <${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>" >> "$GITHUB_OUTPUT"
|
||||
- run: echo "committer string is ${{ steps.committer.outputs.string }}"
|
||||
```
|
||||
|
||||
### Configure git CLI for an app's bot user
|
||||
|
||||
```yaml
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
auto-format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v3
|
||||
id: app-token
|
||||
with:
|
||||
# required
|
||||
app-id: ${{ vars.APP_ID }}
|
||||
private-key: ${{ secrets.PRIVATE_KEY }}
|
||||
- name: Get GitHub App User ID
|
||||
id: get-user-id
|
||||
run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
- run: |
|
||||
git config --global user.name '${{ steps.app-token.outputs.app-slug }}[bot]'
|
||||
git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com'
|
||||
# git commands like commit work using the bot user
|
||||
- run: |
|
||||
git add .
|
||||
git commit -m "Auto-generated changes"
|
||||
git push
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> The `<BOT USER ID>` is the numeric user ID of the app's bot user, which can be found under `https://api.github.com/users/<app-slug>%5Bbot%5D`.
|
||||
>
|
||||
> For example, we can check at `https://api.github.com/users/dependabot[bot]` to see the user ID of Dependabot is 49699333.
|
||||
>
|
||||
> Alternatively, you can use the [octokit/request-action](https://github.com/octokit/request-action) to get the ID.
|
||||
|
||||
### Create a token for all repositories in the current owner's installation
|
||||
|
||||
```yaml
|
||||
@@ -93,7 +135,7 @@ jobs:
|
||||
hello-world:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v1
|
||||
- uses: actions/create-github-app-token@v3
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.APP_ID }}
|
||||
@@ -115,13 +157,15 @@ jobs:
|
||||
hello-world:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v1
|
||||
- uses: actions/create-github-app-token@v3
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.APP_ID }}
|
||||
private-key: ${{ secrets.PRIVATE_KEY }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
repositories: "repo1,repo2"
|
||||
repositories: |
|
||||
repo1
|
||||
repo2
|
||||
- uses: peter-evans/create-or-update-comment@v3
|
||||
with:
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
@@ -138,7 +182,7 @@ jobs:
|
||||
hello-world:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v1
|
||||
- uses: actions/create-github-app-token@v3
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.APP_ID }}
|
||||
@@ -151,6 +195,32 @@ jobs:
|
||||
body: "Hello, World!"
|
||||
```
|
||||
|
||||
### Create a token with specific permissions
|
||||
|
||||
> [!NOTE]
|
||||
> Selected permissions must be granted to the installation of the specified app and repository owner. Setting a permission that the installation does not have will result in an error.
|
||||
|
||||
```yaml
|
||||
on: [issues]
|
||||
|
||||
jobs:
|
||||
hello-world:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v3
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.APP_ID }}
|
||||
private-key: ${{ secrets.PRIVATE_KEY }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
permission-issues: write
|
||||
- uses: peter-evans/create-or-update-comment@v3
|
||||
with:
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
body: "Hello, World!"
|
||||
```
|
||||
|
||||
### Create tokens for multiple user or organization accounts
|
||||
|
||||
You can use a matrix strategy to create tokens for multiple user or organization accounts.
|
||||
@@ -165,7 +235,7 @@ jobs:
|
||||
set-matrix:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{steps.set.outputs.matrix }}
|
||||
matrix: ${{ steps.set.outputs.matrix }}
|
||||
steps:
|
||||
- id: set
|
||||
run: echo 'matrix=[{"owner":"owner1"},{"owner":"owner2","repos":["repo1"]}]' >>"$GITHUB_OUTPUT"
|
||||
@@ -179,7 +249,7 @@ jobs:
|
||||
owners-and-repos: ${{ fromJson(needs.set-matrix.outputs.matrix) }}
|
||||
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v1
|
||||
- uses: actions/create-github-app-token@v3
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.APP_ID }}
|
||||
@@ -207,23 +277,41 @@ jobs:
|
||||
runs-on: self-hosted
|
||||
|
||||
steps:
|
||||
- name: Create GitHub App token
|
||||
id: create_token
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: ${{ vars.GHES_APP_ID }}
|
||||
private-key: ${{ secrets.GHES_APP_PRIVATE_KEY }}
|
||||
owner: ${{ vars.GHES_INSTALLATION_ORG }}
|
||||
github-api-url: ${{ vars.GITHUB_API_URL }}
|
||||
- name: Create GitHub App token
|
||||
id: create_token
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
app-id: ${{ vars.GHES_APP_ID }}
|
||||
private-key: ${{ secrets.GHES_APP_PRIVATE_KEY }}
|
||||
owner: ${{ vars.GHES_INSTALLATION_ORG }}
|
||||
github-api-url: ${{ vars.GITHUB_API_URL }}
|
||||
|
||||
- name: Create issue
|
||||
uses: octokit/request-action@v2.x
|
||||
with:
|
||||
route: POST /repos/${{ github.repository }}/issues
|
||||
title: "New issue from workflow"
|
||||
body: "This is a new issue created from a GitHub Action workflow."
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.create_token.outputs.token }}
|
||||
- name: Create issue
|
||||
uses: octokit/request-action@v2.x
|
||||
with:
|
||||
route: POST /repos/${{ github.repository }}/issues
|
||||
title: "New issue from workflow"
|
||||
body: "This is a new issue created from a GitHub Action workflow."
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.create_token.outputs.token }}
|
||||
```
|
||||
|
||||
### Proxy support
|
||||
|
||||
This action relies on Node.js native proxy support.
|
||||
|
||||
If you set `HTTP_PROXY` or `HTTPS_PROXY`, also set `NODE_USE_ENV_PROXY: "1"` on the action step so Node.js honors those variables. If you need proxy bypass rules, set `NO_PROXY` alongside them.
|
||||
|
||||
```yaml
|
||||
- uses: actions/create-github-app-token@v3
|
||||
id: app-token
|
||||
env:
|
||||
HTTPS_PROXY: http://proxy.example.com:8080
|
||||
NO_PROXY: github.example.com
|
||||
NODE_USE_ENV_PROXY: "1"
|
||||
with:
|
||||
app-id: ${{ vars.APP_ID }}
|
||||
private-key: ${{ secrets.PRIVATE_KEY }}
|
||||
```
|
||||
|
||||
## Inputs
|
||||
@@ -236,20 +324,44 @@ jobs:
|
||||
|
||||
**Required:** GitHub App private key. Escaped newlines (`\\n`) will be automatically replaced with actual newlines.
|
||||
|
||||
Some other actions may require the private key to be Base64 encoded. To avoid recreating a new secret, it can be decoded on the fly, but it needs to be managed securely. Here is an example of how this can be achieved:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- name: Decode the GitHub App Private Key
|
||||
id: decode
|
||||
run: |
|
||||
private_key=$(echo "${{ secrets.PRIVATE_KEY }}" | base64 -d | awk 'BEGIN {ORS="\\n"} {print}' | head -c -2) &> /dev/null
|
||||
echo "::add-mask::$private_key"
|
||||
echo "private-key=$private_key" >> "$GITHUB_OUTPUT"
|
||||
- name: Generate GitHub App Token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
app-id: ${{ vars.APP_ID }}
|
||||
private-key: ${{ steps.decode.outputs.private-key }}
|
||||
```
|
||||
|
||||
### `owner`
|
||||
|
||||
**Optional:** The owner of the GitHub App installation. If empty, defaults to the current repository owner.
|
||||
|
||||
### `repositories`
|
||||
|
||||
**Optional:** Comma-separated list of repositories to grant access to.
|
||||
**Optional:** Comma or newline-separated list of repositories to grant access to.
|
||||
|
||||
> [!NOTE]
|
||||
> If `owner` is set and `repositories` is empty, access will be scoped to all repositories in the provided repository owner's installation. If `owner` and `repositories` are empty, access will be scoped to only the current repository.
|
||||
|
||||
### `permission-<permission name>`
|
||||
|
||||
**Optional:** The permissions to grant to the token. By default, the token inherits all of the installation's permissions. We recommend to explicitly list the permissions that are required for a use case. This follows GitHub's own recommendation to [control permissions of `GITHUB_TOKEN` in workflows](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/controlling-permissions-for-github_token). The documentation also lists all available permissions, just prefix the permission key with `permission-` (e.g., `pull-requests` → `permission-pull-requests`).
|
||||
|
||||
The reason we define one `permision-<permission name>` input per permission is to benefit from type intelligence and input validation built into GitHub's action runner.
|
||||
|
||||
### `skip-token-revoke`
|
||||
|
||||
**Optional:** If truthy, the token will not be revoked when the current job is complete.
|
||||
**Optional:** If true, the token will not be revoked when the current job is complete.
|
||||
|
||||
### `github-api-url`
|
||||
|
||||
@@ -276,12 +388,16 @@ The action creates an installation access token using [the `POST /app/installati
|
||||
1. The token is scoped to the current repository or `repositories` if set.
|
||||
2. The token inherits all the installation's permissions.
|
||||
3. The token is set as output `token` which can be used in subsequent steps.
|
||||
4. Unless the `skip-token-revoke` input is set to a truthy value, the token is revoked in the `post` step of the action, which means it cannot be passed to another job.
|
||||
4. Unless the `skip-token-revoke` input is set to true, the token is revoked in the `post` step of the action, which means it cannot be passed to another job.
|
||||
5. The token is masked, it cannot be logged accidentally.
|
||||
|
||||
> [!NOTE]
|
||||
> Installation permissions can differ from the app's permissions they belong to. Installation permissions are set when an app is installed on an account. When the app adds more permissions after the installation, an account administrator will have to approve the new permissions before they are set on the installation.
|
||||
|
||||
## Contributing
|
||||
|
||||
[CONTRIBUTING.md](CONTRIBUTING.md)
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
|
||||
+104
-17
@@ -7,36 +7,123 @@ branding:
|
||||
inputs:
|
||||
app-id:
|
||||
description: "GitHub App ID"
|
||||
required: false # TODO: When 'app_id' is removed, make 'app-id' required
|
||||
app_id:
|
||||
description: "GitHub App ID"
|
||||
required: false
|
||||
deprecationMessage: "'app_id' is deprecated and will be removed in a future version. Use 'app-id' instead."
|
||||
required: true
|
||||
private-key:
|
||||
description: "GitHub App private key"
|
||||
required: false # TODO: When 'private_key' is removed, make 'private-key' required
|
||||
private_key:
|
||||
description: "GitHub App private key"
|
||||
required: false
|
||||
deprecationMessage: "'private_key' is deprecated and will be removed in a future version. Use 'private-key' instead."
|
||||
required: true
|
||||
owner:
|
||||
description: "The owner of the GitHub App installation (defaults to current repository owner)"
|
||||
required: false
|
||||
repositories:
|
||||
description: "Repositories to install the GitHub App on (defaults to current repository if owner is unset)"
|
||||
description: "Comma or newline-separated list of repositories to install the GitHub App on (defaults to current repository if owner is unset)"
|
||||
required: false
|
||||
skip-token-revoke:
|
||||
description: "If truthy, the token will not be revoked when the current job is complete"
|
||||
description: "If true, the token will not be revoked when the current job is complete"
|
||||
required: false
|
||||
skip_token_revoke:
|
||||
description: "If truthy, the token will not be revoked when the current job is complete"
|
||||
required: false
|
||||
deprecationMessage: "'skip_token_revoke' is deprecated and will be removed in a future version. Use 'skip-token-revoke' instead."
|
||||
default: "false"
|
||||
# Make GitHub API configurable to support non-GitHub Cloud use cases
|
||||
# see https://github.com/actions/create-github-app-token/issues/77
|
||||
github-api-url:
|
||||
description: The URL of the GitHub REST API.
|
||||
default: ${{ github.api_url }}
|
||||
# <START GENERATED PERMISSIONS INPUTS>
|
||||
permission-actions:
|
||||
description: "The level of permission to grant the access token for GitHub Actions workflows, workflow runs, and artifacts. Can be set to 'read' or 'write'."
|
||||
permission-administration:
|
||||
description: "The level of permission to grant the access token for repository creation, deletion, settings, teams, and collaborators creation. Can be set to 'read' or 'write'."
|
||||
permission-checks:
|
||||
description: "The level of permission to grant the access token for checks on code. Can be set to 'read' or 'write'."
|
||||
permission-codespaces:
|
||||
description: "The level of permission to grant the access token to create, edit, delete, and list Codespaces. Can be set to 'read' or 'write'."
|
||||
permission-contents:
|
||||
description: "The level of permission to grant the access token for repository contents, commits, branches, downloads, releases, and merges. Can be set to 'read' or 'write'."
|
||||
permission-dependabot-secrets:
|
||||
description: "The level of permission to grant the access token to manage Dependabot secrets. Can be set to 'read' or 'write'."
|
||||
permission-deployments:
|
||||
description: "The level of permission to grant the access token for deployments and deployment statuses. Can be set to 'read' or 'write'."
|
||||
permission-email-addresses:
|
||||
description: "The level of permission to grant the access token to manage the email addresses belonging to a user. Can be set to 'read' or 'write'."
|
||||
permission-environments:
|
||||
description: "The level of permission to grant the access token for managing repository environments. Can be set to 'read' or 'write'."
|
||||
permission-followers:
|
||||
description: "The level of permission to grant the access token to manage the followers belonging to a user. Can be set to 'read' or 'write'."
|
||||
permission-git-ssh-keys:
|
||||
description: "The level of permission to grant the access token to manage git SSH keys. Can be set to 'read' or 'write'."
|
||||
permission-gpg-keys:
|
||||
description: "The level of permission to grant the access token to view and manage GPG keys belonging to a user. Can be set to 'read' or 'write'."
|
||||
permission-interaction-limits:
|
||||
description: "The level of permission to grant the access token to view and manage interaction limits on a repository. Can be set to 'read' or 'write'."
|
||||
permission-issues:
|
||||
description: "The level of permission to grant the access token for issues and related comments, assignees, labels, and milestones. Can be set to 'read' or 'write'."
|
||||
permission-members:
|
||||
description: "The level of permission to grant the access token for organization teams and members. Can be set to 'read' or 'write'."
|
||||
permission-metadata:
|
||||
description: "The level of permission to grant the access token to search repositories, list collaborators, and access repository metadata. Can be set to 'read' or 'write'."
|
||||
permission-organization-administration:
|
||||
description: "The level of permission to grant the access token to manage access to an organization. Can be set to 'read' or 'write'."
|
||||
permission-organization-announcement-banners:
|
||||
description: "The level of permission to grant the access token to view and manage announcement banners for an organization. Can be set to 'read' or 'write'."
|
||||
permission-organization-copilot-seat-management:
|
||||
description: "The level of permission to grant the access token for managing access to GitHub Copilot for members of an organization with a Copilot Business subscription. This property is in public preview and is subject to change. Can be set to 'write'."
|
||||
permission-organization-custom-org-roles:
|
||||
description: "The level of permission to grant the access token for custom organization roles management. Can be set to 'read' or 'write'."
|
||||
permission-organization-custom-properties:
|
||||
description: "The level of permission to grant the access token for custom property management. Can be set to 'read', 'write', or 'admin'."
|
||||
permission-organization-custom-roles:
|
||||
description: "The level of permission to grant the access token for custom repository roles management. Can be set to 'read' or 'write'."
|
||||
permission-organization-events:
|
||||
description: "The level of permission to grant the access token to view events triggered by an activity in an organization. Can be set to 'read'."
|
||||
permission-organization-hooks:
|
||||
description: "The level of permission to grant the access token to manage the post-receive hooks for an organization. Can be set to 'read' or 'write'."
|
||||
permission-organization-packages:
|
||||
description: "The level of permission to grant the access token for organization packages published to GitHub Packages. Can be set to 'read' or 'write'."
|
||||
permission-organization-personal-access-token-requests:
|
||||
description: "The level of permission to grant the access token for viewing and managing fine-grained personal access tokens that have been approved by an organization. Can be set to 'read' or 'write'."
|
||||
permission-organization-personal-access-tokens:
|
||||
description: "The level of permission to grant the access token for viewing and managing fine-grained personal access token requests to an organization. Can be set to 'read' or 'write'."
|
||||
permission-organization-plan:
|
||||
description: "The level of permission to grant the access token for viewing an organization's plan. Can be set to 'read'."
|
||||
permission-organization-projects:
|
||||
description: "The level of permission to grant the access token to manage organization projects and projects public preview (where available). Can be set to 'read', 'write', or 'admin'."
|
||||
permission-organization-secrets:
|
||||
description: "The level of permission to grant the access token to manage organization secrets. Can be set to 'read' or 'write'."
|
||||
permission-organization-self-hosted-runners:
|
||||
description: "The level of permission to grant the access token to view and manage GitHub Actions self-hosted runners available to an organization. Can be set to 'read' or 'write'."
|
||||
permission-organization-user-blocking:
|
||||
description: "The level of permission to grant the access token to view and manage users blocked by the organization. Can be set to 'read' or 'write'."
|
||||
permission-packages:
|
||||
description: "The level of permission to grant the access token for packages published to GitHub Packages. Can be set to 'read' or 'write'."
|
||||
permission-pages:
|
||||
description: "The level of permission to grant the access token to retrieve Pages statuses, configuration, and builds, as well as create new builds. Can be set to 'read' or 'write'."
|
||||
permission-profile:
|
||||
description: "The level of permission to grant the access token to manage the profile settings belonging to a user. Can be set to 'write'."
|
||||
permission-pull-requests:
|
||||
description: "The level of permission to grant the access token for pull requests and related comments, assignees, labels, milestones, and merges. Can be set to 'read' or 'write'."
|
||||
permission-repository-custom-properties:
|
||||
description: "The level of permission to grant the access token to view and edit custom properties for a repository, when allowed by the property. Can be set to 'read' or 'write'."
|
||||
permission-repository-hooks:
|
||||
description: "The level of permission to grant the access token to manage the post-receive hooks for a repository. Can be set to 'read' or 'write'."
|
||||
permission-repository-projects:
|
||||
description: "The level of permission to grant the access token to manage repository projects, columns, and cards. Can be set to 'read', 'write', or 'admin'."
|
||||
permission-secret-scanning-alerts:
|
||||
description: "The level of permission to grant the access token to view and manage secret scanning alerts. Can be set to 'read' or 'write'."
|
||||
permission-secrets:
|
||||
description: "The level of permission to grant the access token to manage repository secrets. Can be set to 'read' or 'write'."
|
||||
permission-security-events:
|
||||
description: "The level of permission to grant the access token to view and manage security events like code scanning alerts. Can be set to 'read' or 'write'."
|
||||
permission-single-file:
|
||||
description: "The level of permission to grant the access token to manage just a single file. Can be set to 'read' or 'write'."
|
||||
permission-starring:
|
||||
description: "The level of permission to grant the access token to list and manage repositories a user is starring. Can be set to 'read' or 'write'."
|
||||
permission-statuses:
|
||||
description: "The level of permission to grant the access token for commit statuses. Can be set to 'read' or 'write'."
|
||||
permission-team-discussions:
|
||||
description: "The level of permission to grant the access token to manage team discussions and related comments. Can be set to 'read' or 'write'."
|
||||
permission-vulnerability-alerts:
|
||||
description: "The level of permission to grant the access token to manage Dependabot alerts. Can be set to 'read' or 'write'."
|
||||
permission-workflows:
|
||||
description: "The level of permission to grant the access token to update GitHub Actions workflow files. Can be set to 'write'."
|
||||
# <END GENERATED PERMISSIONS INPUTS>
|
||||
outputs:
|
||||
token:
|
||||
description: "GitHub installation access token"
|
||||
@@ -45,6 +132,6 @@ outputs:
|
||||
app-slug:
|
||||
description: "GitHub App slug"
|
||||
runs:
|
||||
using: "node20"
|
||||
using: "node24"
|
||||
main: "dist/main.cjs"
|
||||
post: "dist/post.cjs"
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="106"
|
||||
height="20" role="img" aria-label="Coverage: 100%">
|
||||
<title>Coverage: 100%</title>
|
||||
<linearGradient id="s" x2="0" y2="100%">
|
||||
<stop offset="0" stop-color="#bbb" stop-opacity=".1" />
|
||||
<stop offset="1" stop-opacity=".1" />
|
||||
</linearGradient>
|
||||
<clipPath id="r">
|
||||
<rect width="106" height="20" rx="3" fill="#fff" />
|
||||
</clipPath>
|
||||
<g clip-path="url(#r)">
|
||||
<rect width="63" height="20" fill="#555" />
|
||||
<rect x="63" width="43" height="20" fill="#4c1" />
|
||||
<rect width="106" height="20" fill="url(#s)" />
|
||||
</g>
|
||||
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif"
|
||||
text-rendering="geometricPrecision" font-size="110">
|
||||
<text aria-hidden="true" x="325" y="150" fill="#010101" fill-opacity=".3"
|
||||
transform="scale(.1)" textLength="530">Coverage</text>
|
||||
<text x="325" y="140" transform="scale(.1)" fill="#fff" textLength="530">Coverage</text>
|
||||
<text aria-hidden="true" x="835" y="150" fill="#010101" fill-opacity=".3"
|
||||
transform="scale(.1)" textLength="330">100%</text>
|
||||
<text x="835" y="140" transform="scale(.1)" fill="#fff" textLength="330">100%</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
Vendored
+1987
-19552
File diff suppressed because one or more lines are too long
Vendored
+1598
-18083
File diff suppressed because one or more lines are too long
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Finds all permissions passed via `permision-*` inputs and turns them into an object.
|
||||
*
|
||||
* @see https://docs.github.com/en/actions/sharing-automations/creating-actions/metadata-syntax-for-github-actions#inputs
|
||||
* @param {NodeJS.ProcessEnv} env
|
||||
* @returns {undefined | Record<string, string>}
|
||||
*/
|
||||
export function getPermissionsFromInputs(env) {
|
||||
return Object.entries(env).reduce((permissions, [key, value]) => {
|
||||
if (!key.startsWith("INPUT_PERMISSION-")) return permissions;
|
||||
if (!value) return permissions;
|
||||
|
||||
const permission = key.slice("INPUT_PERMISSION-".length).toLowerCase()
|
||||
.replaceAll(/-/g, "_");
|
||||
|
||||
// Inherit app permissions if no permissions inputs are set
|
||||
if (permissions === undefined) {
|
||||
return { [permission]: value };
|
||||
}
|
||||
|
||||
return {
|
||||
// @ts-expect-error - needs to be typed correctly
|
||||
...permissions,
|
||||
[permission]: value,
|
||||
};
|
||||
}, undefined);
|
||||
}
|
||||
+70
-51
@@ -5,7 +5,8 @@ import pRetry from "p-retry";
|
||||
* @param {string} appId
|
||||
* @param {string} privateKey
|
||||
* @param {string} owner
|
||||
* @param {string} repositories
|
||||
* @param {string[]} repositories
|
||||
* @param {undefined | Record<string, string>} permissions
|
||||
* @param {import("@actions/core")} core
|
||||
* @param {import("@octokit/auth-app").createAppAuth} createAppAuth
|
||||
* @param {import("@octokit/request").request} request
|
||||
@@ -16,51 +17,55 @@ export async function main(
|
||||
privateKey,
|
||||
owner,
|
||||
repositories,
|
||||
permissions,
|
||||
core,
|
||||
createAppAuth,
|
||||
request,
|
||||
skipTokenRevoke
|
||||
) {
|
||||
let parsedOwner = "";
|
||||
let parsedRepositoryNames = "";
|
||||
let parsedRepositoryNames = [];
|
||||
|
||||
// If neither owner nor repositories are set, default to current repository
|
||||
if (!owner && !repositories) {
|
||||
[parsedOwner, parsedRepositoryNames] = String(
|
||||
process.env.GITHUB_REPOSITORY
|
||||
).split("/");
|
||||
if (!owner && repositories.length === 0) {
|
||||
const [owner, repo] = String(process.env.GITHUB_REPOSITORY).split("/");
|
||||
parsedOwner = owner;
|
||||
parsedRepositoryNames = [repo];
|
||||
|
||||
core.info(
|
||||
`owner and repositories not set, creating token for the current repository ("${parsedRepositoryNames}")`
|
||||
`Inputs 'owner' and 'repositories' are not set. Creating token for this repository (${owner}/${repo}).`
|
||||
);
|
||||
}
|
||||
|
||||
// If only an owner is set, default to all repositories from that owner
|
||||
if (owner && !repositories) {
|
||||
if (owner && repositories.length === 0) {
|
||||
parsedOwner = owner;
|
||||
|
||||
core.info(
|
||||
`repositories not set, creating token for all repositories for given owner "${owner}"`
|
||||
`Input 'repositories' is not set. Creating token for all repositories owned by ${owner}.`
|
||||
);
|
||||
}
|
||||
|
||||
// If repositories are set, but no owner, default to `GITHUB_REPOSITORY_OWNER`
|
||||
if (!owner && repositories) {
|
||||
if (!owner && repositories.length > 0) {
|
||||
parsedOwner = String(process.env.GITHUB_REPOSITORY_OWNER);
|
||||
parsedRepositoryNames = repositories;
|
||||
|
||||
core.info(
|
||||
`owner not set, creating owner for given repositories "${repositories}" in current owner ("${parsedOwner}")`
|
||||
`No 'owner' input provided. Using default owner '${parsedOwner}' to create token for the following repositories:${repositories
|
||||
.map((repo) => `\n- ${parsedOwner}/${repo}`)
|
||||
.join("")}`
|
||||
);
|
||||
}
|
||||
|
||||
// If both owner and repositories are set, use those values
|
||||
if (owner && repositories) {
|
||||
if (owner && repositories.length > 0) {
|
||||
parsedOwner = owner;
|
||||
parsedRepositoryNames = repositories;
|
||||
|
||||
core.info(
|
||||
`owner and repositories set, creating token for repositories "${repositories}" owned by "${owner}"`
|
||||
`Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:
|
||||
${repositories.map((repo) => `\n- ${parsedOwner}/${repo}`).join("")}`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -73,25 +78,41 @@ export async function main(
|
||||
let authentication, installationId, appSlug;
|
||||
// If at least one repository is set, get installation ID from that repository
|
||||
|
||||
if (parsedRepositoryNames) {
|
||||
({ authentication, installationId, appSlug } = await pRetry(() => getTokenFromRepository(request, auth, parsedOwner, parsedRepositoryNames), {
|
||||
onFailedAttempt: (error) => {
|
||||
core.info(
|
||||
`Failed to create token for "${parsedRepositoryNames}" (attempt ${error.attemptNumber}): ${error.message}`
|
||||
);
|
||||
},
|
||||
retries: 3,
|
||||
}));
|
||||
if (parsedRepositoryNames.length > 0) {
|
||||
({ authentication, installationId, appSlug } = await pRetry(
|
||||
() =>
|
||||
getTokenFromRepository(
|
||||
request,
|
||||
auth,
|
||||
parsedOwner,
|
||||
parsedRepositoryNames,
|
||||
permissions
|
||||
),
|
||||
{
|
||||
shouldRetry: (error) => error.status >= 500,
|
||||
onFailedAttempt: (error) => {
|
||||
core.info(
|
||||
`Failed to create token for "${parsedRepositoryNames.join(
|
||||
","
|
||||
)}" (attempt ${error.attemptNumber}): ${error.message}`
|
||||
);
|
||||
},
|
||||
retries: 3,
|
||||
}
|
||||
));
|
||||
} else {
|
||||
// Otherwise get the installation for the owner, which can either be an organization or a user account
|
||||
({ authentication, installationId, appSlug } = await pRetry(() => getTokenFromOwner(request, auth, parsedOwner), {
|
||||
onFailedAttempt: (error) => {
|
||||
core.info(
|
||||
`Failed to create token for "${parsedOwner}" (attempt ${error.attemptNumber}): ${error.message}`
|
||||
);
|
||||
},
|
||||
retries: 3,
|
||||
}));
|
||||
({ authentication, installationId, appSlug } = await pRetry(
|
||||
() => getTokenFromOwner(request, auth, parsedOwner, permissions),
|
||||
{
|
||||
onFailedAttempt: (error) => {
|
||||
core.info(
|
||||
`Failed to create token for "${parsedOwner}" (attempt ${error.attemptNumber}): ${error.message}`
|
||||
);
|
||||
},
|
||||
retries: 3,
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
// Register the token with the runner as a secret to ensure it is masked in logs
|
||||
@@ -108,43 +129,40 @@ export async function main(
|
||||
}
|
||||
}
|
||||
|
||||
async function getTokenFromOwner(request, auth, parsedOwner) {
|
||||
// https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#get-an-organization-installation-for-the-authenticated-app
|
||||
const response = await request("GET /orgs/{org}/installation", {
|
||||
org: parsedOwner,
|
||||
async function getTokenFromOwner(request, auth, parsedOwner, permissions) {
|
||||
// https://docs.github.com/rest/apps/apps?apiVersion=2022-11-28#get-a-user-installation-for-the-authenticated-app
|
||||
// This endpoint works for both users and organizations
|
||||
const response = await request("GET /users/{username}/installation", {
|
||||
username: parsedOwner,
|
||||
request: {
|
||||
hook: auth.hook,
|
||||
},
|
||||
}).catch((error) => {
|
||||
/* c8 ignore next */
|
||||
if (error.status !== 404) throw error;
|
||||
|
||||
// https://docs.github.com/rest/apps/apps?apiVersion=2022-11-28#get-a-user-installation-for-the-authenticated-app
|
||||
return request("GET /users/{username}/installation", {
|
||||
username: parsedOwner,
|
||||
request: {
|
||||
hook: auth.hook,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// Get token for for all repositories of the given installation
|
||||
const authentication = await auth({
|
||||
type: "installation",
|
||||
installationId: response.data.id,
|
||||
permissions,
|
||||
});
|
||||
|
||||
const installationId = response.data.id;
|
||||
const appSlug = response.data['app_slug'];
|
||||
const appSlug = response.data["app_slug"];
|
||||
|
||||
return { authentication, installationId, appSlug };
|
||||
}
|
||||
|
||||
async function getTokenFromRepository(request, auth, parsedOwner, parsedRepositoryNames) {
|
||||
async function getTokenFromRepository(
|
||||
request,
|
||||
auth,
|
||||
parsedOwner,
|
||||
parsedRepositoryNames,
|
||||
permissions
|
||||
) {
|
||||
// https://docs.github.com/rest/apps/apps?apiVersion=2022-11-28#get-a-repository-installation-for-the-authenticated-app
|
||||
const response = await request("GET /repos/{owner}/{repo}/installation", {
|
||||
owner: parsedOwner,
|
||||
repo: parsedRepositoryNames.split(",")[0],
|
||||
repo: parsedRepositoryNames[0],
|
||||
request: {
|
||||
hook: auth.hook,
|
||||
},
|
||||
@@ -154,11 +172,12 @@ async function getTokenFromRepository(request, auth, parsedOwner, parsedReposito
|
||||
const authentication = await auth({
|
||||
type: "installation",
|
||||
installationId: response.data.id,
|
||||
repositoryNames: parsedRepositoryNames.split(","),
|
||||
repositoryNames: parsedRepositoryNames,
|
||||
permissions,
|
||||
});
|
||||
|
||||
const installationId = response.data.id;
|
||||
const appSlug = response.data['app_slug'];
|
||||
const appSlug = response.data["app_slug"];
|
||||
|
||||
return { authentication, installationId, appSlug };
|
||||
}
|
||||
}
|
||||
|
||||
+2
-5
@@ -5,9 +5,7 @@
|
||||
* @param {import("@octokit/request").request} request
|
||||
*/
|
||||
export async function post(core, request) {
|
||||
const skipTokenRevoke = Boolean(
|
||||
core.getInput("skip-token-revoke") || core.getInput("skip_token_revoke")
|
||||
);
|
||||
const skipTokenRevoke = core.getBooleanInput("skip-token-revoke");
|
||||
|
||||
if (skipTokenRevoke) {
|
||||
core.info("Token revocation was skipped");
|
||||
@@ -35,8 +33,7 @@ export async function post(core, request) {
|
||||
});
|
||||
core.info("Token revoked");
|
||||
} catch (error) {
|
||||
core.warning(
|
||||
`Token revocation failed: ${error.message}`)
|
||||
core.warning(`Token revocation failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+23
-28
@@ -1,41 +1,36 @@
|
||||
import core from "@actions/core";
|
||||
import { request } from "@octokit/request";
|
||||
import { ProxyAgent, fetch as undiciFetch } from "undici";
|
||||
|
||||
// Get the GitHub API URL from the action input and remove any trailing slash
|
||||
const baseUrl = core.getInput("github-api-url").replace(/\/$/, "");
|
||||
|
||||
// https://docs.github.com/actions/hosting-your-own-runners/managing-self-hosted-runners/using-a-proxy-server-with-self-hosted-runners
|
||||
const proxyUrl =
|
||||
process.env.https_proxy ||
|
||||
process.env.HTTPS_PROXY ||
|
||||
process.env.http_proxy ||
|
||||
process.env.HTTP_PROXY;
|
||||
const proxyEnvironmentKeys = [
|
||||
"https_proxy",
|
||||
"HTTPS_PROXY",
|
||||
"http_proxy",
|
||||
"HTTP_PROXY",
|
||||
];
|
||||
|
||||
/* c8 ignore start */
|
||||
// Native support for proxies in Undici is under consideration: https://github.com/nodejs/undici/issues/1650
|
||||
// Until then, we need to use a custom fetch function to add proxy support.
|
||||
const proxyFetch = (url, options) => {
|
||||
const urlHost = new URL(url).hostname;
|
||||
const noProxy = (process.env.no_proxy || process.env.NO_PROXY || "").split(
|
||||
","
|
||||
);
|
||||
function proxyEnvironmentConfigured() {
|
||||
return proxyEnvironmentKeys.some((key) => process.env[key]);
|
||||
}
|
||||
|
||||
if (!noProxy.includes(urlHost)) {
|
||||
options = {
|
||||
...options,
|
||||
dispatcher: new ProxyAgent(String(proxyUrl)),
|
||||
};
|
||||
function nativeProxySupportEnabled() {
|
||||
return process.env.NODE_USE_ENV_PROXY === "1";
|
||||
}
|
||||
|
||||
export function ensureNativeProxySupport() {
|
||||
if (!proxyEnvironmentConfigured() || nativeProxySupportEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
return undiciFetch(url, options);
|
||||
};
|
||||
/* c8 ignore stop */
|
||||
throw new Error(
|
||||
"A proxy environment variable is set, but Node.js native proxy support is not enabled. Set NODE_USE_ENV_PROXY=1 for this action step.",
|
||||
);
|
||||
}
|
||||
|
||||
// Configure the default settings for GitHub API requests
|
||||
export default request.defaults({
|
||||
headers: {
|
||||
"user-agent": "actions/create-github-app-token",
|
||||
},
|
||||
headers: { "user-agent": "actions/create-github-app-token" },
|
||||
baseUrl,
|
||||
/* c8 ignore next */
|
||||
request: proxyUrl ? { fetch: proxyFetch } : {},
|
||||
});
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
import core from "@actions/core";
|
||||
import { createAppAuth } from "@octokit/auth-app";
|
||||
|
||||
import { getPermissionsFromInputs } from "./lib/get-permissions-from-inputs.js";
|
||||
import { main } from "./lib/main.js";
|
||||
import request from "./lib/request.js";
|
||||
import request, { ensureNativeProxySupport } from "./lib/request.js";
|
||||
|
||||
if (!process.env.GITHUB_REPOSITORY) {
|
||||
throw new Error("GITHUB_REPOSITORY missing, must be set to '<owner>/<repo>'");
|
||||
@@ -14,33 +15,37 @@ if (!process.env.GITHUB_REPOSITORY_OWNER) {
|
||||
throw new Error("GITHUB_REPOSITORY_OWNER missing, must be set to '<owner>'");
|
||||
}
|
||||
|
||||
const appId = core.getInput("app-id") || core.getInput("app_id");
|
||||
if (!appId) {
|
||||
// The 'app_id' input was previously required, but it and 'app-id' are both optional now, until the former is removed. Still, we want to ensure that at least one of them is set.
|
||||
throw new Error("Input required and not supplied: app-id");
|
||||
}
|
||||
const privateKey = core.getInput("private-key") || core.getInput("private_key");
|
||||
if (!privateKey) {
|
||||
// The 'private_key' input was previously required, but it and 'private-key' are both optional now, until the former is removed. Still, we want to ensure that at least one of them is set.
|
||||
throw new Error("Input required and not supplied: private-key");
|
||||
}
|
||||
const owner = core.getInput("owner");
|
||||
const repositories = core.getInput("repositories");
|
||||
async function run() {
|
||||
ensureNativeProxySupport();
|
||||
|
||||
const skipTokenRevoke = Boolean(
|
||||
core.getInput("skip-token-revoke") || core.getInput("skip_token_revoke")
|
||||
);
|
||||
const appId = core.getInput("app-id");
|
||||
const privateKey = core.getInput("private-key");
|
||||
const owner = core.getInput("owner");
|
||||
const repositories = core
|
||||
.getInput("repositories")
|
||||
.split(/[\n,]+/)
|
||||
.map((s) => s.trim())
|
||||
.filter((x) => x !== "");
|
||||
|
||||
main(
|
||||
appId,
|
||||
privateKey,
|
||||
owner,
|
||||
repositories,
|
||||
core,
|
||||
createAppAuth,
|
||||
request,
|
||||
skipTokenRevoke
|
||||
).catch((error) => {
|
||||
const skipTokenRevoke = core.getBooleanInput("skip-token-revoke");
|
||||
|
||||
const permissions = getPermissionsFromInputs(process.env);
|
||||
|
||||
return main(
|
||||
appId,
|
||||
privateKey,
|
||||
owner,
|
||||
repositories,
|
||||
permissions,
|
||||
core,
|
||||
createAppAuth,
|
||||
request,
|
||||
skipTokenRevoke,
|
||||
);
|
||||
}
|
||||
|
||||
// Export promise for testing
|
||||
export default run().catch((error) => {
|
||||
/* c8 ignore next 3 */
|
||||
console.error(error);
|
||||
core.setFailed(error.message);
|
||||
|
||||
Generated
+1417
-1000
File diff suppressed because it is too large
Load Diff
+24
-16
@@ -2,48 +2,56 @@
|
||||
"name": "create-github-app-token",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "1.10.2",
|
||||
"version": "3.0.0-beta.3",
|
||||
"description": "GitHub Action for creating a GitHub App Installation Access Token",
|
||||
"engines": {
|
||||
"node": ">=24.4.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "esbuild main.js post.js --bundle --outdir=dist --out-extension:.js=.cjs --platform=node --target=node20.0.0",
|
||||
"build": "esbuild main.js post.js --bundle --outdir=dist --out-extension:.js=.cjs --platform=node --packages=bundle",
|
||||
"test": "c8 --100 ava tests/index.js",
|
||||
"coverage": "c8 report --reporter html",
|
||||
"postcoverage": "open-cli coverage/index.html"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.1",
|
||||
"@octokit/auth-app": "^7.1.0",
|
||||
"@octokit/request": "^9.0.1",
|
||||
"p-retry": "^6.2.0",
|
||||
"undici": "^6.18.2"
|
||||
"@actions/core": "^1.11.1",
|
||||
"@octokit/auth-app": "^7.2.1",
|
||||
"@octokit/request": "^9.2.2",
|
||||
"p-retry": "^6.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sinonjs/fake-timers": "^11.2.2",
|
||||
"ava": "^6.1.3",
|
||||
"c8": "^9.1.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"esbuild": "^0.21.4",
|
||||
"execa": "^9.1.0",
|
||||
"@octokit/openapi": "^19.1.0",
|
||||
"@sinonjs/fake-timers": "^14.0.0",
|
||||
"ava": "^6.4.1",
|
||||
"c8": "^10.1.3",
|
||||
"dotenv": "^17.2.1",
|
||||
"esbuild": "^0.25.8",
|
||||
"execa": "^9.6.0",
|
||||
"open-cli": "^8.0.0",
|
||||
"yaml": "^2.4.2"
|
||||
"undici": "^7.13.0",
|
||||
"yaml": "^2.8.1"
|
||||
},
|
||||
"release": {
|
||||
"branches": [
|
||||
"+([0-9]).x",
|
||||
"main"
|
||||
"main",
|
||||
{
|
||||
"name": "beta",
|
||||
"prerelease": true
|
||||
}
|
||||
],
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer",
|
||||
"@semantic-release/release-notes-generator",
|
||||
"@semantic-release/github",
|
||||
"@semantic-release/npm",
|
||||
"semantic-release-plugin-github-breaking-version-tag",
|
||||
[
|
||||
"@semantic-release/git",
|
||||
{
|
||||
"assets": [
|
||||
"package.json",
|
||||
"package-lock.json",
|
||||
"dist/*"
|
||||
],
|
||||
"message": "build(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
|
||||
|
||||
@@ -3,9 +3,15 @@
|
||||
import core from "@actions/core";
|
||||
|
||||
import { post } from "./lib/post.js";
|
||||
import request from "./lib/request.js";
|
||||
import request, { ensureNativeProxySupport } from "./lib/request.js";
|
||||
|
||||
post(core, request).catch((error) => {
|
||||
async function run() {
|
||||
ensureNativeProxySupport();
|
||||
|
||||
return post(core, request);
|
||||
}
|
||||
|
||||
run().catch((error) => {
|
||||
/* c8 ignore next 3 */
|
||||
console.error(error);
|
||||
core.setFailed(error.message);
|
||||
|
||||
@@ -0,0 +1,395 @@
|
||||
{
|
||||
"title": "App Permissions",
|
||||
"type": "object",
|
||||
"description": "The permissions granted to the user access token.",
|
||||
"properties": {
|
||||
"actions": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token for GitHub Actions workflows, workflow runs, and artifacts.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"administration": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token for repository creation, deletion, settings, teams, and collaborators creation.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"checks": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token for checks on code.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"codespaces": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to create, edit, delete, and list Codespaces.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"contents": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token for repository contents, commits, branches, downloads, releases, and merges.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"dependabot_secrets": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to manage Dependabot secrets.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"deployments": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token for deployments and deployment statuses.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"environments": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token for managing repository environments.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"issues": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token for issues and related comments, assignees, labels, and milestones.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to search repositories, list collaborators, and access repository metadata.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"packages": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token for packages published to GitHub Packages.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"pages": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to retrieve Pages statuses, configuration, and builds, as well as create new builds.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"pull_requests": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token for pull requests and related comments, assignees, labels, milestones, and merges.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"repository_custom_properties": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to view and edit custom properties for a repository, when allowed by the property.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"repository_hooks": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to manage the post-receive hooks for a repository.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"repository_projects": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to manage repository projects, columns, and cards.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write",
|
||||
"admin"
|
||||
]
|
||||
},
|
||||
"secret_scanning_alerts": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to view and manage secret scanning alerts.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"secrets": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to manage repository secrets.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"security_events": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to view and manage security events like code scanning alerts.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"single_file": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to manage just a single file.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"statuses": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token for commit statuses.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"vulnerability_alerts": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to manage Dependabot alerts.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"workflows": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to update GitHub Actions workflow files.",
|
||||
"enum": [
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"members": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token for organization teams and members.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"organization_administration": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to manage access to an organization.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"organization_custom_roles": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token for custom repository roles management.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"organization_custom_org_roles": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token for custom organization roles management.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"organization_custom_properties": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token for custom property management.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write",
|
||||
"admin"
|
||||
]
|
||||
},
|
||||
"organization_copilot_seat_management": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token for managing access to GitHub Copilot for members of an organization with a Copilot Business subscription. This property is in public preview and is subject to change.",
|
||||
"enum": [
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"organization_announcement_banners": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to view and manage announcement banners for an organization.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"organization_events": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to view events triggered by an activity in an organization.",
|
||||
"enum": [
|
||||
"read"
|
||||
]
|
||||
},
|
||||
"organization_hooks": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to manage the post-receive hooks for an organization.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"organization_personal_access_tokens": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token for viewing and managing fine-grained personal access token requests to an organization.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"organization_personal_access_token_requests": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token for viewing and managing fine-grained personal access tokens that have been approved by an organization.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"organization_plan": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token for viewing an organization's plan.",
|
||||
"enum": [
|
||||
"read"
|
||||
]
|
||||
},
|
||||
"organization_projects": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to manage organization projects and projects public preview (where available).",
|
||||
"enum": [
|
||||
"read",
|
||||
"write",
|
||||
"admin"
|
||||
]
|
||||
},
|
||||
"organization_packages": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token for organization packages published to GitHub Packages.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"organization_secrets": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to manage organization secrets.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"organization_self_hosted_runners": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to view and manage GitHub Actions self-hosted runners available to an organization.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"organization_user_blocking": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to view and manage users blocked by the organization.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"team_discussions": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to manage team discussions and related comments.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"email_addresses": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to manage the email addresses belonging to a user.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"followers": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to manage the followers belonging to a user.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"git_ssh_keys": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to manage git SSH keys.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"gpg_keys": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to view and manage GPG keys belonging to a user.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"interaction_limits": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to view and manage interaction limits on a repository.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"profile": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to manage the profile settings belonging to a user.",
|
||||
"enum": [
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"starring": {
|
||||
"type": "string",
|
||||
"description": "The level of permission to grant the access token to list and manage repositories a user is starring.",
|
||||
"enum": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"contents": "read",
|
||||
"issues": "read",
|
||||
"deployments": "write",
|
||||
"single_file": "read"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
|
||||
import OctokitOpenapi from "@octokit/openapi";
|
||||
|
||||
const appPermissionsSchema =
|
||||
OctokitOpenapi.schemas["api.github.com"].components.schemas[
|
||||
"app-permissions"
|
||||
];
|
||||
|
||||
await writeFile(
|
||||
`scripts/generated/app-permissions.json`,
|
||||
JSON.stringify(appPermissionsSchema, null, 2) + "\n",
|
||||
"utf8"
|
||||
);
|
||||
|
||||
const permissionsInputs = Object.entries(appPermissionsSchema.properties)
|
||||
.sort((a, b) => a[0].localeCompare(b[0]))
|
||||
.reduce((result, [key, value]) => {
|
||||
const formatter = new Intl.ListFormat("en", {
|
||||
style: "long",
|
||||
type: "disjunction",
|
||||
});
|
||||
const permissionAccessValues = formatter.format(
|
||||
value.enum.map((p) => `'${p}'`)
|
||||
);
|
||||
|
||||
const description = `${value.description} Can be set to ${permissionAccessValues}.`;
|
||||
return `${result}
|
||||
permission-${key.replace(/_/g, "-")}:
|
||||
description: "${description}"`;
|
||||
}, "");
|
||||
|
||||
const actionsYamlContent = await readFile("action.yml", "utf8");
|
||||
|
||||
// In the action.yml file, replace the content between the `<START GENERATED PERMISSIONS INPUTS>` and `<END GENERATED PERMISSIONS INPUTS>` comments with the new content
|
||||
const updatedActionsYamlContent = actionsYamlContent.replace(
|
||||
/(?<=# <START GENERATED PERMISSIONS INPUTS>)(.|\n)*(?=# <END GENERATED PERMISSIONS INPUTS>)/,
|
||||
permissionsInputs + "\n "
|
||||
);
|
||||
|
||||
await writeFile("action.yml", updatedActionsYamlContent, "utf8");
|
||||
console.log("Updated action.yml with new permissions inputs");
|
||||
@@ -17,3 +17,14 @@ or with npm
|
||||
```
|
||||
npm test
|
||||
```
|
||||
|
||||
## How the tests work
|
||||
|
||||
The output from the tests is captured into a snapshot ([tests/snapshots/index.js.md](snapshots/index.js.md)). It includes all requests sent by our scripts to verify it's working correctly and to prevent regressions.
|
||||
|
||||
## How to add a new test
|
||||
|
||||
We have tests both for the `main.js` and `post.js` scripts.
|
||||
|
||||
- If you do not expect an error, take [main-token-permissions-set.test.js](tests/main-token-permissions-set.test.js) as a starting point.
|
||||
- If your test has an expected error, take [main-missing-app-id.test.js](tests/main-missing-app-id.test.js) as a starting point.
|
||||
|
||||
+21
-3
@@ -1,16 +1,34 @@
|
||||
import { readdirSync } from "node:fs";
|
||||
|
||||
import { execa } from "execa";
|
||||
import test from "ava";
|
||||
import { execa } from "execa";
|
||||
|
||||
const tests = readdirSync("tests").filter((file) => file.endsWith(".test.js"));
|
||||
// Get all files in tests directory
|
||||
const files = readdirSync("tests");
|
||||
|
||||
for (const file of tests) {
|
||||
// Files to ignore
|
||||
const ignore = ["index.js", "main.js", "README.md", "snapshots"];
|
||||
|
||||
const testFiles = files.filter((file) => !ignore.includes(file));
|
||||
|
||||
// Throw an error if there is a file that does not end with test.js in the tests directory
|
||||
for (const file of testFiles) {
|
||||
if (!file.endsWith(".test.js")) {
|
||||
throw new Error(`File ${file} does not end with .test.js`);
|
||||
}
|
||||
test(file, async (t) => {
|
||||
// Override Actions environment variables that change `core`’s behavior
|
||||
const env = {
|
||||
GITHUB_OUTPUT: undefined,
|
||||
GITHUB_STATE: undefined,
|
||||
HTTP_PROXY: undefined,
|
||||
HTTPS_PROXY: undefined,
|
||||
http_proxy: undefined,
|
||||
https_proxy: undefined,
|
||||
NO_PROXY: undefined,
|
||||
no_proxy: undefined,
|
||||
NODE_OPTIONS: undefined,
|
||||
NODE_USE_ENV_PROXY: undefined,
|
||||
};
|
||||
const { stderr, stdout } = await execa("node", [`tests/${file}`], { env });
|
||||
t.snapshot(stderr, "stderr");
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { test, DEFAULT_ENV } from "./main.js";
|
||||
import { DEFAULT_ENV, test } from "./main.js";
|
||||
|
||||
// Verify that main works with a custom GitHub API URL passed as `github-api-url` input
|
||||
await test(
|
||||
() => {
|
||||
process.env.INPUT_OWNER = process.env.GITHUB_REPOSITORY_OWNER;
|
||||
process.env.INPUT_REPOSITORIES = process.env.GITHUB_REPOSITORY;
|
||||
const currentRepoName = process.env.GITHUB_REPOSITORY.split("/")[1];
|
||||
process.env.INPUT_REPOSITORIES = currentRepoName;
|
||||
},
|
||||
{
|
||||
...DEFAULT_ENV,
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
process.env.GITHUB_REPOSITORY_OWNER = "actions";
|
||||
process.env.GITHUB_REPOSITORY = "actions/create-github-app-token";
|
||||
|
||||
// Verify `main` exits with an error when neither the `app-id` nor `app_id` input is set.
|
||||
try {
|
||||
await import("../main.js");
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
process.env.GITHUB_REPOSITORY_OWNER = "actions";
|
||||
process.env.GITHUB_REPOSITORY = "actions/create-github-app-token";
|
||||
process.env["INPUT_APP-ID"] = "123456";
|
||||
|
||||
// Verify `main` exits with an error when neither the `private-key` nor `private_key` input is set.
|
||||
try {
|
||||
await import("../main.js");
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import { test, DEFAULT_ENV } from "./main.js";
|
||||
|
||||
// Verify `main` works correctly when `private-key` input has escaped newlines
|
||||
await test(() => {
|
||||
process.env['INPUT_PRIVATE-KEY'] = DEFAULT_ENV.PRIVATE_KEY.replace(/\n/g, '\\n')
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import { DEFAULT_ENV, test } from "./main.js";
|
||||
|
||||
// Verify `main` works correctly when `private-key` input has escaped newlines
|
||||
await test(() => {
|
||||
process.env["INPUT_PRIVATE-KEY"] = DEFAULT_ENV["INPUT_PRIVATE-KEY"].replace(
|
||||
/\n/g,
|
||||
"\\n"
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
process.env.GITHUB_REPOSITORY = "actions/create-github-app-token";
|
||||
process.env.GITHUB_REPOSITORY_OWNER = "actions";
|
||||
process.env.HTTPS_PROXY = "http://127.0.0.1:3128";
|
||||
|
||||
const originalConsoleError = console.error;
|
||||
console.error = (...args) => {
|
||||
originalConsoleError(
|
||||
...args.map((arg) => (arg instanceof Error ? arg.message : arg)),
|
||||
);
|
||||
};
|
||||
|
||||
await import("../main.js");
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
process.exitCode = 0;
|
||||
@@ -4,10 +4,10 @@ import { install } from "@sinonjs/fake-timers";
|
||||
|
||||
// Verify `main` retry when the clock has drifted.
|
||||
await test((mockPool) => {
|
||||
process.env.INPUT_OWNER = 'actions'
|
||||
process.env.INPUT_REPOSITORIES = 'failed-repo';
|
||||
const owner = process.env.INPUT_OWNER
|
||||
const repo = process.env.INPUT_REPOSITORIES
|
||||
process.env.INPUT_OWNER = "actions";
|
||||
process.env.INPUT_REPOSITORIES = "failed-repo";
|
||||
const owner = process.env.INPUT_OWNER;
|
||||
const repo = process.env.INPUT_REPOSITORIES;
|
||||
const mockInstallationId = "123456";
|
||||
const mockAppSlug = "github-actions";
|
||||
|
||||
@@ -25,20 +25,23 @@ await test((mockPool) => {
|
||||
})
|
||||
.reply(({ headers }) => {
|
||||
const [_, jwt] = (headers.authorization || "").split(" ");
|
||||
const payload = JSON.parse(Buffer.from(jwt.split(".")[1], "base64").toString());
|
||||
const payload = JSON.parse(
|
||||
Buffer.from(jwt.split(".")[1], "base64").toString(),
|
||||
);
|
||||
|
||||
if (payload.iat < 0) {
|
||||
return {
|
||||
statusCode: 401,
|
||||
data: {
|
||||
message: "'Issued at' claim ('iat') must be an Integer representing the time that the assertion was issued."
|
||||
message:
|
||||
"'Issued at' claim ('iat') must be an Integer representing the time that the assertion was issued.",
|
||||
},
|
||||
responseOptions: {
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"date": new Date(Date.now() + 30000).toUTCString()
|
||||
}
|
||||
}
|
||||
date: new Date(Date.now() + 30000).toUTCString(),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -46,13 +49,14 @@ await test((mockPool) => {
|
||||
statusCode: 200,
|
||||
data: {
|
||||
id: mockInstallationId,
|
||||
"app_slug": mockAppSlug
|
||||
app_slug: mockAppSlug,
|
||||
},
|
||||
responseOptions: {
|
||||
headers: {
|
||||
"content-type": "application/json"
|
||||
}
|
||||
}
|
||||
"content-type": "application/json",
|
||||
},
|
||||
},
|
||||
};
|
||||
}).times(2);
|
||||
})
|
||||
.times(2);
|
||||
});
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
import { test } from "./main.js";
|
||||
|
||||
// Verify `main` successfully obtains a token when the `owner` input is set (to a user), but the `repositories` input isn’t set.
|
||||
// Verify retries work when getting a token for a user or organization fails on the first attempt.
|
||||
await test((mockPool) => {
|
||||
process.env.INPUT_OWNER = "smockle";
|
||||
delete process.env.INPUT_REPOSITORIES;
|
||||
@@ -10,7 +10,7 @@ await test((mockPool) => {
|
||||
const mockAppSlug = "github-actions";
|
||||
mockPool
|
||||
.intercept({
|
||||
path: `/orgs/${process.env.INPUT_OWNER}/installation`,
|
||||
path: `/users/smockle/installation`,
|
||||
method: "GET",
|
||||
headers: {
|
||||
accept: "application/vnd.github.v3+json",
|
||||
@@ -21,7 +21,7 @@ await test((mockPool) => {
|
||||
.reply(500, "GitHub API not available");
|
||||
mockPool
|
||||
.intercept({
|
||||
path: `/orgs/${process.env.INPUT_OWNER}/installation`,
|
||||
path: `/users/smockle/installation`,
|
||||
method: "GET",
|
||||
headers: {
|
||||
accept: "application/vnd.github.v3+json",
|
||||
@@ -31,7 +31,7 @@ await test((mockPool) => {
|
||||
})
|
||||
.reply(
|
||||
200,
|
||||
{ id: mockInstallationId, "app_slug": mockAppSlug },
|
||||
{ headers: { "content-type": "application/json" } }
|
||||
{ id: mockInstallationId, app_slug: mockAppSlug },
|
||||
{ headers: { "content-type": "application/json" } },
|
||||
);
|
||||
});
|
||||
@@ -33,7 +33,7 @@ await test((mockPool) => {
|
||||
})
|
||||
.reply(
|
||||
200,
|
||||
{ id: mockInstallationId, "app_slug": mockAppSlug },
|
||||
{ headers: { "content-type": "application/json" } }
|
||||
{ id: mockInstallationId, app_slug: mockAppSlug },
|
||||
{ headers: { "content-type": "application/json" } },
|
||||
);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { test } from "./main.js";
|
||||
|
||||
// Verify `main` successfully obtains a token when the `owner` and `repositories` inputs are set (and the latter is a list of repos).
|
||||
await test(() => {
|
||||
process.env.INPUT_OWNER = process.env.GITHUB_REPOSITORY_OWNER;
|
||||
const currentRepoName = process.env.GITHUB_REPOSITORY.split("/")[1];
|
||||
// Intentional unnecessary whitespace to test parsing to array
|
||||
process.env.INPUT_REPOSITORIES = `\n ${currentRepoName}\ntoolkit \n\n checkout \n`;
|
||||
});
|
||||
@@ -3,5 +3,7 @@ import { test } from "./main.js";
|
||||
// Verify `main` successfully obtains a token when the `owner` and `repositories` inputs are set (and the latter is a list of repos).
|
||||
await test(() => {
|
||||
process.env.INPUT_OWNER = process.env.GITHUB_REPOSITORY_OWNER;
|
||||
process.env.INPUT_REPOSITORIES = `${process.env.GITHUB_REPOSITORY},actions/toolkit`;
|
||||
const currentRepoName = process.env.GITHUB_REPOSITORY.split("/")[1];
|
||||
// Intentional unnecessary whitespace to test parsing to array
|
||||
process.env.INPUT_REPOSITORIES = ` ${currentRepoName}, toolkit ,checkout`;
|
||||
});
|
||||
|
||||
@@ -3,5 +3,6 @@ import { test } from "./main.js";
|
||||
// Verify `main` successfully obtains a token when the `owner` and `repositories` inputs are set (and the latter is a single repo).
|
||||
await test(() => {
|
||||
process.env.INPUT_OWNER = process.env.GITHUB_REPOSITORY_OWNER;
|
||||
process.env.INPUT_REPOSITORIES = process.env.GITHUB_REPOSITORY;
|
||||
const currentRepoName = process.env.GITHUB_REPOSITORY.split("/")[1];
|
||||
process.env.INPUT_REPOSITORIES = currentRepoName;
|
||||
});
|
||||
|
||||
+5
-5
@@ -1,16 +1,16 @@
|
||||
import { test } from "./main.js";
|
||||
|
||||
// Verify `main` successfully obtains a token when the `owner` input is set (to an org), but the `repositories` input isn’t set.
|
||||
// Verify `main` successfully obtains a token when the `owner` input is set, and the `repositories` input isn’t set.
|
||||
await test((mockPool) => {
|
||||
process.env.INPUT_OWNER = process.env.GITHUB_REPOSITORY_OWNER;
|
||||
delete process.env.INPUT_REPOSITORIES;
|
||||
|
||||
// Mock installation id and app slug request
|
||||
// Mock installation ID and app slug request
|
||||
const mockInstallationId = "123456";
|
||||
const mockAppSlug = "github-actions";
|
||||
mockPool
|
||||
.intercept({
|
||||
path: `/orgs/${process.env.INPUT_OWNER}/installation`,
|
||||
path: `/users/${process.env.INPUT_OWNER}/installation`,
|
||||
method: "GET",
|
||||
headers: {
|
||||
accept: "application/vnd.github.v3+json",
|
||||
@@ -20,7 +20,7 @@ await test((mockPool) => {
|
||||
})
|
||||
.reply(
|
||||
200,
|
||||
{ id: mockInstallationId, "app_slug": mockAppSlug },
|
||||
{ headers: { "content-type": "application/json" } }
|
||||
{ id: mockInstallationId, app_slug: mockAppSlug },
|
||||
{ headers: { "content-type": "application/json" } },
|
||||
);
|
||||
});
|
||||
@@ -1,37 +0,0 @@
|
||||
import { test } from "./main.js";
|
||||
|
||||
// Verify `main` successfully obtains a token when the `owner` input is set (to a user), but the `repositories` input isn’t set.
|
||||
await test((mockPool) => {
|
||||
process.env.INPUT_OWNER = "smockle";
|
||||
delete process.env.INPUT_REPOSITORIES;
|
||||
|
||||
// Mock installation ID and app slug request
|
||||
const mockInstallationId = "123456";
|
||||
const mockAppSlug = "github-actions";
|
||||
mockPool
|
||||
.intercept({
|
||||
path: `/orgs/${process.env.INPUT_OWNER}/installation`,
|
||||
method: "GET",
|
||||
headers: {
|
||||
accept: "application/vnd.github.v3+json",
|
||||
"user-agent": "actions/create-github-app-token",
|
||||
// Intentionally omitting the `authorization` header, since JWT creation is not idempotent.
|
||||
},
|
||||
})
|
||||
.reply(404);
|
||||
mockPool
|
||||
.intercept({
|
||||
path: `/users/${process.env.INPUT_OWNER}/installation`,
|
||||
method: "GET",
|
||||
headers: {
|
||||
accept: "application/vnd.github.v3+json",
|
||||
"user-agent": "actions/create-github-app-token",
|
||||
// Intentionally omitting the `authorization` header, since JWT creation is not idempotent.
|
||||
},
|
||||
})
|
||||
.reply(
|
||||
200,
|
||||
{ id: mockInstallationId, "app_slug": mockAppSlug },
|
||||
{ headers: { "content-type": "application/json" } }
|
||||
);
|
||||
});
|
||||
@@ -3,5 +3,6 @@ import { test } from "./main.js";
|
||||
// Verify `main` successfully obtains a token when the `owner` input is not set, but the `repositories` input is set.
|
||||
await test(() => {
|
||||
delete process.env.INPUT_OWNER;
|
||||
process.env.INPUT_REPOSITORIES = process.env.GITHUB_REPOSITORY;
|
||||
const currentRepoName = process.env.GITHUB_REPOSITORY.split("/")[1];
|
||||
process.env.INPUT_REPOSITORIES = currentRepoName;
|
||||
});
|
||||
|
||||
@@ -20,7 +20,7 @@ await test((mockPool) => {
|
||||
})
|
||||
.reply(
|
||||
200,
|
||||
{ id: mockInstallationId, "app_slug": mockAppSlug },
|
||||
{ headers: { "content-type": "application/json" } }
|
||||
{ id: mockInstallationId, app_slug: mockAppSlug },
|
||||
{ headers: { "content-type": "application/json" } },
|
||||
);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { test } from "./main.js";
|
||||
|
||||
// Verify `main` successfully sets permissions
|
||||
await test(() => {
|
||||
process.env["INPUT_PERMISSION-ISSUES"] = `write`;
|
||||
process.env["INPUT_PERMISSION-PULL-REQUESTS"] = `read`;
|
||||
});
|
||||
+25
-5
@@ -8,6 +8,7 @@ export const DEFAULT_ENV = {
|
||||
// inputs are set as environment variables with the prefix INPUT_
|
||||
// https://docs.github.com/actions/creating-actions/metadata-syntax-for-github-actions#example-specifying-inputs
|
||||
"INPUT_GITHUB-API-URL": "https://api.github.com",
|
||||
"INPUT_SKIP-TOKEN-REVOKE": "false",
|
||||
"INPUT_APP-ID": "123456",
|
||||
// This key is invalidated. It’s from https://github.com/octokit/auth-app.js/issues/465#issuecomment-1564998327.
|
||||
"INPUT_PRIVATE-KEY": `-----BEGIN RSA PRIVATE KEY-----
|
||||
@@ -37,6 +38,8 @@ so0tiQKBgGQXZaxaXhYUcxYHuCkQ3V4Vsj3ezlM92xXlP32SGFm3KgFhYy9kATxw
|
||||
Cax1ytZzvlrKLQyQFVK1COs2rHt7W4cJ7op7C8zXfsigXCiejnS664oAuX8sQZID
|
||||
x3WQZRiXlWejSMUAHuMwXrhGlltF3lw83+xAjnqsVp75kGS6OH61
|
||||
-----END RSA PRIVATE KEY-----`,
|
||||
// The Actions runner sets all inputs to empty strings if not set.
|
||||
"INPUT_PERMISSION-ADMINISTRATION": "",
|
||||
};
|
||||
|
||||
export async function test(cb = (_mockPool) => {}, env = DEFAULT_ENV) {
|
||||
@@ -46,8 +49,8 @@ export async function test(cb = (_mockPool) => {}, env = DEFAULT_ENV) {
|
||||
|
||||
// Set up mocking
|
||||
const baseUrl = new URL(env["INPUT_GITHUB-API-URL"]);
|
||||
const basePath = baseUrl.pathname === '/' ? '' : baseUrl.pathname;
|
||||
const mockAgent = new MockAgent();
|
||||
const basePath = baseUrl.pathname === "/" ? "" : baseUrl.pathname;
|
||||
const mockAgent = new MockAgent({ enableCallHistory: true });
|
||||
mockAgent.disableNetConnect();
|
||||
setGlobalDispatcher(mockAgent);
|
||||
const mockPool = mockAgent.get(baseUrl.origin);
|
||||
@@ -58,9 +61,11 @@ export async function test(cb = (_mockPool) => {}, env = DEFAULT_ENV) {
|
||||
const mockInstallationId = "123456";
|
||||
const mockAppSlug = "github-actions";
|
||||
const owner = env.INPUT_OWNER ?? env.GITHUB_REPOSITORY_OWNER;
|
||||
const currentRepoName = env.GITHUB_REPOSITORY.split("/")[1];
|
||||
const repo = encodeURIComponent(
|
||||
(env.INPUT_REPOSITORIES ?? env.GITHUB_REPOSITORY).split(",")[0]
|
||||
(env.INPUT_REPOSITORIES ?? currentRepoName).split(",")[0]
|
||||
);
|
||||
|
||||
mockPool
|
||||
.intercept({
|
||||
path: `${basePath}/repos/${owner}/${repo}/installation`,
|
||||
@@ -73,7 +78,7 @@ export async function test(cb = (_mockPool) => {}, env = DEFAULT_ENV) {
|
||||
})
|
||||
.reply(
|
||||
200,
|
||||
{ id: mockInstallationId, "app_slug": mockAppSlug },
|
||||
{ id: mockInstallationId, app_slug: mockAppSlug },
|
||||
{ headers: { "content-type": "application/json" } }
|
||||
);
|
||||
|
||||
@@ -81,6 +86,7 @@ export async function test(cb = (_mockPool) => {}, env = DEFAULT_ENV) {
|
||||
const mockInstallationAccessToken =
|
||||
"ghs_16C7e42F292c6912E7710c838347Ae178B4a"; // This token is invalidated. It’s from https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#create-an-installation-access-token-for-an-app.
|
||||
const mockExpiresAt = "2016-07-11T22:14:10Z";
|
||||
|
||||
mockPool
|
||||
.intercept({
|
||||
path: `${basePath}/app/installations/${mockInstallationId}/access_tokens`,
|
||||
@@ -101,5 +107,19 @@ export async function test(cb = (_mockPool) => {}, env = DEFAULT_ENV) {
|
||||
cb(mockPool);
|
||||
|
||||
// Run the main script
|
||||
await import("../main.js");
|
||||
const { default: promise } = await import("../main.js");
|
||||
await promise;
|
||||
|
||||
console.log("--- REQUESTS ---");
|
||||
const calls = mockAgent
|
||||
.getCallHistory()
|
||||
.calls()
|
||||
.map((call) => {
|
||||
const route = `${call.method} ${call.path}`;
|
||||
if (call.method === "GET") return route;
|
||||
|
||||
return `${route}\n${call.body}`;
|
||||
});
|
||||
|
||||
console.log(calls.join("\n"));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
process.env["INPUT_GITHUB-API-URL"] = "https://api.github.com";
|
||||
process.env.HTTPS_PROXY = "http://127.0.0.1:3128";
|
||||
|
||||
const originalConsoleError = console.error;
|
||||
console.error = (...args) => {
|
||||
originalConsoleError(
|
||||
...args.map((arg) => (arg instanceof Error ? arg.message : arg)),
|
||||
);
|
||||
};
|
||||
|
||||
await import("../post.js");
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
process.exitCode = 0;
|
||||
@@ -7,6 +7,7 @@ process.env.STATE_token = "secret123";
|
||||
// inputs are set as environment variables with the prefix INPUT_
|
||||
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#example-specifying-inputs
|
||||
process.env["INPUT_GITHUB-API-URL"] = "https://api.github.com";
|
||||
process.env["INPUT_SKIP-TOKEN-REVOKE"] = "false";
|
||||
|
||||
// 1 hour in the future, not expired
|
||||
process.env.STATE_expiresAt = new Date(
|
||||
|
||||
@@ -7,6 +7,10 @@ process.env.STATE_token = "secret123";
|
||||
// 1 hour in the past, expired
|
||||
process.env.STATE_expiresAt = new Date(Date.now() - 1000 * 60 * 60).toISOString();
|
||||
|
||||
// inputs are set as environment variables with the prefix INPUT_
|
||||
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#example-specifying-inputs
|
||||
process.env["INPUT_SKIP-TOKEN-REVOKE"] = "false";
|
||||
|
||||
const mockAgent = new MockAgent();
|
||||
|
||||
setGlobalDispatcher(mockAgent);
|
||||
|
||||
@@ -7,6 +7,7 @@ process.env.STATE_token = "secret123";
|
||||
// inputs are set as environment variables with the prefix INPUT_
|
||||
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#example-specifying-inputs
|
||||
process.env["INPUT_GITHUB-API-URL"] = "https://api.github.com";
|
||||
process.env["INPUT_SKIP-TOKEN-REVOKE"] = "false";
|
||||
|
||||
// 1 hour in the future, not expired
|
||||
process.env.STATE_expiresAt = new Date(Date.now() + 1000 * 60 * 60).toISOString();
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#sending-values-to-the-pre-and-post-actions
|
||||
delete process.env.STATE_token;
|
||||
|
||||
// inputs are set as environment variables with the prefix INPUT_
|
||||
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#example-specifying-inputs
|
||||
process.env["INPUT_SKIP-TOKEN-REVOKE"] = "false";
|
||||
|
||||
await import("../post.js");
|
||||
|
||||
+205
-81
@@ -12,9 +12,7 @@ Generated by [AVA](https://avajs.dev).
|
||||
|
||||
> stdout
|
||||
|
||||
`app_id — 'app_id' is deprecated and will be removed in a future version. Use 'app-id' instead.␊
|
||||
private_key — 'private_key' is deprecated and will be removed in a future version. Use 'private-key' instead.␊
|
||||
skip_token_revoke — 'skip_token_revoke' is deprecated and will be removed in a future version. Use 'skip-token-revoke' instead.`
|
||||
''
|
||||
|
||||
## main-custom-github-api-url.test.js
|
||||
|
||||
@@ -24,7 +22,9 @@ Generated by [AVA](https://avajs.dev).
|
||||
|
||||
> stdout
|
||||
|
||||
`owner and repositories set, creating token for repositories "actions/create-github-app-token" owned by "actions"␊
|
||||
`Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:␊
|
||||
␊
|
||||
- actions/create-github-app-token␊
|
||||
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
␊
|
||||
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
@@ -33,17 +33,11 @@ Generated by [AVA](https://avajs.dev).
|
||||
␊
|
||||
::set-output name=app-slug::github-actions␊
|
||||
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
::save-state name=expiresAt::2016-07-11T22:14:10Z`
|
||||
|
||||
## main-missing-app-id.test.js
|
||||
|
||||
> stderr
|
||||
|
||||
'Input required and not supplied: app-id'
|
||||
|
||||
> stdout
|
||||
|
||||
''
|
||||
::save-state name=expiresAt::2016-07-11T22:14:10Z␊
|
||||
--- REQUESTS ---␊
|
||||
GET /api/v3/repos/actions/create-github-app-token/installation␊
|
||||
POST /api/v3/app/installations/123456/access_tokens␊
|
||||
{"repositories":["create-github-app-token"]}`
|
||||
|
||||
## main-missing-owner.test.js
|
||||
|
||||
@@ -55,16 +49,6 @@ Generated by [AVA](https://avajs.dev).
|
||||
|
||||
''
|
||||
|
||||
## main-missing-private-key.test.js
|
||||
|
||||
> stderr
|
||||
|
||||
'Input required and not supplied: private-key'
|
||||
|
||||
> stdout
|
||||
|
||||
''
|
||||
|
||||
## main-missing-repository.test.js
|
||||
|
||||
> stderr
|
||||
@@ -75,7 +59,7 @@ Generated by [AVA](https://avajs.dev).
|
||||
|
||||
''
|
||||
|
||||
## main-token-get-owner-set-repo-fail-response.test.js
|
||||
## main-private-key-with-escaped-newlines.test.js
|
||||
|
||||
> stderr
|
||||
|
||||
@@ -83,8 +67,7 @@ Generated by [AVA](https://avajs.dev).
|
||||
|
||||
> stdout
|
||||
|
||||
`owner and repositories set, creating token for repositories "failed-repo" owned by "actions"␊
|
||||
Failed to create token for "failed-repo" (attempt 1): GitHub API not available␊
|
||||
`Inputs 'owner' and 'repositories' are not set. Creating token for this repository (actions/create-github-app-token).␊
|
||||
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
␊
|
||||
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
@@ -93,17 +76,34 @@ Generated by [AVA](https://avajs.dev).
|
||||
␊
|
||||
::set-output name=app-slug::github-actions␊
|
||||
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
::save-state name=expiresAt::2016-07-11T22:14:10Z`
|
||||
::save-state name=expiresAt::2016-07-11T22:14:10Z␊
|
||||
--- REQUESTS ---␊
|
||||
GET /repos/actions/create-github-app-token/installation␊
|
||||
POST /app/installations/123456/access_tokens␊
|
||||
{"repositories":["create-github-app-token"]}`
|
||||
|
||||
## main-token-get-owner-set-repo-set-to-many.test.js
|
||||
## main-proxy-requires-native-support.test.js
|
||||
|
||||
> stderr
|
||||
|
||||
''
|
||||
'A proxy environment variable is set, but Node.js native proxy support is not enabled. Set NODE_USE_ENV_PROXY=1 for this action step.'
|
||||
|
||||
> stdout
|
||||
|
||||
`owner and repositories set, creating token for repositories "actions/create-github-app-token,actions/toolkit" owned by "actions"␊
|
||||
'::error::A proxy environment variable is set, but Node.js native proxy support is not enabled. Set NODE_USE_ENV_PROXY=1 for this action step.'
|
||||
|
||||
## main-repo-skew.test.js
|
||||
|
||||
> stderr
|
||||
|
||||
`'Issued at' claim ('iat') must be an Integer representing the time that the assertion was issued.␊
|
||||
[@octokit/auth-app] GitHub API time and system time are different by 30 seconds. Retrying request with the difference accounted for.`
|
||||
|
||||
> stdout
|
||||
|
||||
`Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:␊
|
||||
␊
|
||||
- actions/failed-repo␊
|
||||
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
␊
|
||||
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
@@ -112,9 +112,14 @@ Generated by [AVA](https://avajs.dev).
|
||||
␊
|
||||
::set-output name=app-slug::github-actions␊
|
||||
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
::save-state name=expiresAt::2016-07-11T22:14:10Z`
|
||||
::save-state name=expiresAt::2016-07-11T22:14:10Z␊
|
||||
--- REQUESTS ---␊
|
||||
GET /repos/actions/failed-repo/installation␊
|
||||
GET /repos/actions/failed-repo/installation␊
|
||||
POST /app/installations/123456/access_tokens␊
|
||||
{"repositories":["failed-repo"]}`
|
||||
|
||||
## main-token-get-owner-set-repo-set-to-one.test.js
|
||||
## main-token-get-owner-set-fail-response.test.js
|
||||
|
||||
> stderr
|
||||
|
||||
@@ -122,45 +127,7 @@ Generated by [AVA](https://avajs.dev).
|
||||
|
||||
> stdout
|
||||
|
||||
`owner and repositories set, creating token for repositories "actions/create-github-app-token" owned by "actions"␊
|
||||
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
␊
|
||||
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
␊
|
||||
::set-output name=installation-id::123456␊
|
||||
␊
|
||||
::set-output name=app-slug::github-actions␊
|
||||
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
::save-state name=expiresAt::2016-07-11T22:14:10Z`
|
||||
|
||||
## main-token-get-owner-set-to-org-repo-unset.test.js
|
||||
|
||||
> stderr
|
||||
|
||||
''
|
||||
|
||||
> stdout
|
||||
|
||||
`repositories not set, creating token for all repositories for given owner "actions"␊
|
||||
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
␊
|
||||
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
␊
|
||||
::set-output name=installation-id::123456␊
|
||||
␊
|
||||
::set-output name=app-slug::github-actions␊
|
||||
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
::save-state name=expiresAt::2016-07-11T22:14:10Z`
|
||||
|
||||
## main-token-get-owner-set-to-user-fail-response.test.js
|
||||
|
||||
> stderr
|
||||
|
||||
''
|
||||
|
||||
> stdout
|
||||
|
||||
`repositories not set, creating token for all repositories for given owner "smockle"␊
|
||||
`Input 'repositories' is not set. Creating token for all repositories owned by smockle.␊
|
||||
Failed to create token for "smockle" (attempt 1): GitHub API not available␊
|
||||
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
␊
|
||||
@@ -170,9 +137,14 @@ Generated by [AVA](https://avajs.dev).
|
||||
␊
|
||||
::set-output name=app-slug::github-actions␊
|
||||
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
::save-state name=expiresAt::2016-07-11T22:14:10Z`
|
||||
::save-state name=expiresAt::2016-07-11T22:14:10Z␊
|
||||
--- REQUESTS ---␊
|
||||
GET /users/smockle/installation␊
|
||||
GET /users/smockle/installation␊
|
||||
POST /app/installations/123456/access_tokens␊
|
||||
null`
|
||||
|
||||
## main-token-get-owner-set-to-user-repo-unset.test.js
|
||||
## main-token-get-owner-set-repo-fail-response.test.js
|
||||
|
||||
> stderr
|
||||
|
||||
@@ -180,7 +152,10 @@ Generated by [AVA](https://avajs.dev).
|
||||
|
||||
> stdout
|
||||
|
||||
`repositories not set, creating token for all repositories for given owner "smockle"␊
|
||||
`Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:␊
|
||||
␊
|
||||
- actions/failed-repo␊
|
||||
Failed to create token for "failed-repo" (attempt 1): GitHub API not available␊
|
||||
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
␊
|
||||
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
@@ -189,7 +164,114 @@ Generated by [AVA](https://avajs.dev).
|
||||
␊
|
||||
::set-output name=app-slug::github-actions␊
|
||||
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
::save-state name=expiresAt::2016-07-11T22:14:10Z`
|
||||
::save-state name=expiresAt::2016-07-11T22:14:10Z␊
|
||||
--- REQUESTS ---␊
|
||||
GET /repos/actions/failed-repo/installation␊
|
||||
GET /repos/actions/failed-repo/installation␊
|
||||
POST /app/installations/123456/access_tokens␊
|
||||
{"repositories":["failed-repo"]}`
|
||||
|
||||
## main-token-get-owner-set-repo-set-to-many-newline.test.js
|
||||
|
||||
> stderr
|
||||
|
||||
''
|
||||
|
||||
> stdout
|
||||
|
||||
`Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:␊
|
||||
␊
|
||||
- actions/create-github-app-token␊
|
||||
- actions/toolkit␊
|
||||
- actions/checkout␊
|
||||
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
␊
|
||||
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
␊
|
||||
::set-output name=installation-id::123456␊
|
||||
␊
|
||||
::set-output name=app-slug::github-actions␊
|
||||
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
::save-state name=expiresAt::2016-07-11T22:14:10Z␊
|
||||
--- REQUESTS ---␊
|
||||
GET /repos/actions/create-github-app-token/installation␊
|
||||
POST /app/installations/123456/access_tokens␊
|
||||
{"repositories":["create-github-app-token","toolkit","checkout"]}`
|
||||
|
||||
## main-token-get-owner-set-repo-set-to-many.test.js
|
||||
|
||||
> stderr
|
||||
|
||||
''
|
||||
|
||||
> stdout
|
||||
|
||||
`Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:␊
|
||||
␊
|
||||
- actions/create-github-app-token␊
|
||||
- actions/toolkit␊
|
||||
- actions/checkout␊
|
||||
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
␊
|
||||
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
␊
|
||||
::set-output name=installation-id::123456␊
|
||||
␊
|
||||
::set-output name=app-slug::github-actions␊
|
||||
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
::save-state name=expiresAt::2016-07-11T22:14:10Z␊
|
||||
--- REQUESTS ---␊
|
||||
GET /repos/actions/create-github-app-token/installation␊
|
||||
POST /app/installations/123456/access_tokens␊
|
||||
{"repositories":["create-github-app-token","toolkit","checkout"]}`
|
||||
|
||||
## main-token-get-owner-set-repo-set-to-one.test.js
|
||||
|
||||
> stderr
|
||||
|
||||
''
|
||||
|
||||
> stdout
|
||||
|
||||
`Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:␊
|
||||
␊
|
||||
- actions/create-github-app-token␊
|
||||
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
␊
|
||||
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
␊
|
||||
::set-output name=installation-id::123456␊
|
||||
␊
|
||||
::set-output name=app-slug::github-actions␊
|
||||
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
::save-state name=expiresAt::2016-07-11T22:14:10Z␊
|
||||
--- REQUESTS ---␊
|
||||
GET /repos/actions/create-github-app-token/installation␊
|
||||
POST /app/installations/123456/access_tokens␊
|
||||
{"repositories":["create-github-app-token"]}`
|
||||
|
||||
## main-token-get-owner-set-repo-unset.test.js
|
||||
|
||||
> stderr
|
||||
|
||||
''
|
||||
|
||||
> stdout
|
||||
|
||||
`Input 'repositories' is not set. Creating token for all repositories owned by actions.␊
|
||||
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
␊
|
||||
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
␊
|
||||
::set-output name=installation-id::123456␊
|
||||
␊
|
||||
::set-output name=app-slug::github-actions␊
|
||||
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
::save-state name=expiresAt::2016-07-11T22:14:10Z␊
|
||||
--- REQUESTS ---␊
|
||||
GET /users/actions/installation␊
|
||||
POST /app/installations/123456/access_tokens␊
|
||||
null`
|
||||
|
||||
## main-token-get-owner-unset-repo-set.test.js
|
||||
|
||||
@@ -199,7 +281,8 @@ Generated by [AVA](https://avajs.dev).
|
||||
|
||||
> stdout
|
||||
|
||||
`owner not set, creating owner for given repositories "actions/create-github-app-token" in current owner ("actions")␊
|
||||
`No 'owner' input provided. Using default owner 'actions' to create token for the following repositories:␊
|
||||
- actions/create-github-app-token␊
|
||||
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
␊
|
||||
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
@@ -208,7 +291,11 @@ Generated by [AVA](https://avajs.dev).
|
||||
␊
|
||||
::set-output name=app-slug::github-actions␊
|
||||
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
::save-state name=expiresAt::2016-07-11T22:14:10Z`
|
||||
::save-state name=expiresAt::2016-07-11T22:14:10Z␊
|
||||
--- REQUESTS ---␊
|
||||
GET /repos/actions/create-github-app-token/installation␊
|
||||
POST /app/installations/123456/access_tokens␊
|
||||
{"repositories":["create-github-app-token"]}`
|
||||
|
||||
## main-token-get-owner-unset-repo-unset.test.js
|
||||
|
||||
@@ -218,7 +305,7 @@ Generated by [AVA](https://avajs.dev).
|
||||
|
||||
> stdout
|
||||
|
||||
`owner and repositories not set, creating token for the current repository ("create-github-app-token")␊
|
||||
`Inputs 'owner' and 'repositories' are not set. Creating token for this repository (actions/create-github-app-token).␊
|
||||
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
␊
|
||||
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
@@ -227,7 +314,44 @@ Generated by [AVA](https://avajs.dev).
|
||||
␊
|
||||
::set-output name=app-slug::github-actions␊
|
||||
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
::save-state name=expiresAt::2016-07-11T22:14:10Z`
|
||||
::save-state name=expiresAt::2016-07-11T22:14:10Z␊
|
||||
--- REQUESTS ---␊
|
||||
GET /repos/actions/create-github-app-token/installation␊
|
||||
POST /app/installations/123456/access_tokens␊
|
||||
{"repositories":["create-github-app-token"]}`
|
||||
|
||||
## main-token-permissions-set.test.js
|
||||
|
||||
> stderr
|
||||
|
||||
''
|
||||
|
||||
> stdout
|
||||
|
||||
`Inputs 'owner' and 'repositories' are not set. Creating token for this repository (actions/create-github-app-token).␊
|
||||
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
␊
|
||||
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
␊
|
||||
::set-output name=installation-id::123456␊
|
||||
␊
|
||||
::set-output name=app-slug::github-actions␊
|
||||
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
::save-state name=expiresAt::2016-07-11T22:14:10Z␊
|
||||
--- REQUESTS ---␊
|
||||
GET /repos/actions/create-github-app-token/installation␊
|
||||
POST /app/installations/123456/access_tokens␊
|
||||
{"repositories":["create-github-app-token"],"permissions":{"issues":"write","pull_requests":"read"}}`
|
||||
|
||||
## post-proxy-requires-native-support.test.js
|
||||
|
||||
> stderr
|
||||
|
||||
'A proxy environment variable is set, but Node.js native proxy support is not enabled. Set NODE_USE_ENV_PROXY=1 for this action step.'
|
||||
|
||||
> stdout
|
||||
|
||||
'::error::A proxy environment variable is set, but Node.js native proxy support is not enabled. Set NODE_USE_ENV_PROXY=1 for this action step.'
|
||||
|
||||
## post-revoke-token-fail-response.test.js
|
||||
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user