diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..566a8fe --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,15 @@ +# Contributing + +Initial setup + +```console +npm install +``` + +Run tests locally + +```console +npm test +``` + +Learn more about how the tests work in [test/README.md](test/README.md). diff --git a/README.md b/README.md index a0518e7..91efed1 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ jobs: > [!TIP] > The `` is the numeric user ID of the app's bot user, which can be found under `https://api.github.com/users/%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. @@ -195,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@v1 + 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. @@ -251,23 +277,23 @@ 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 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 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 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 }} ``` ## Inputs @@ -309,6 +335,12 @@ steps: > [!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-` + +**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-` 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. @@ -344,6 +376,10 @@ The action creates an installation access token using [the `POST /app/installati > [!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) diff --git a/lib/get-permissions-from-inputs.js b/lib/get-permissions-from-inputs.js new file mode 100644 index 0000000..7458155 --- /dev/null +++ b/lib/get-permissions-from-inputs.js @@ -0,0 +1,23 @@ +/** + * 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} + */ +export function getPermissionsFromInputs(env) { + return Object.entries(env).reduce((permissions, [key, value]) => { + if (!key.startsWith("INPUT_PERMISSION_")) return permissions; + + const permission = key.slice("INPUT_PERMISSION_".length).toLowerCase(); + if (permissions === undefined) { + return { [permission]: value }; + } + + return { + // @ts-expect-error - needs to be typed correctly + ...permissions, + [permission]: value, + }; + }, undefined); +} diff --git a/lib/main.js b/lib/main.js index 0f3d07b..3440d9a 100644 --- a/lib/main.js +++ b/lib/main.js @@ -6,6 +6,7 @@ import pRetry from "p-retry"; * @param {string} privateKey * @param {string} owner * @param {string[]} repositories + * @param {undefined | Record} permissions * @param {import("@actions/core")} core * @param {import("@octokit/auth-app").createAppAuth} createAppAuth * @param {import("@octokit/request").request} request @@ -16,10 +17,11 @@ export async function main( privateKey, owner, repositories, + permissions, core, createAppAuth, request, - skipTokenRevoke + skipTokenRevoke, ) { let parsedOwner = ""; let parsedRepositoryNames = []; @@ -31,7 +33,7 @@ export async function main( parsedRepositoryNames = [repo]; core.info( - `owner and repositories not set, creating token for the current repository ("${repo}")` + `owner and repositories not set, creating token for the current repository ("${repo}")`, ); } @@ -40,7 +42,7 @@ export async function main( parsedOwner = owner; core.info( - `repositories not set, creating token for all repositories for given owner "${owner}"` + `repositories not set, creating token for all repositories for given owner "${owner}"`, ); } @@ -51,8 +53,8 @@ export async function main( core.info( `owner not set, creating owner for given repositories "${repositories.join( - "," - )}" in current owner ("${parsedOwner}")` + ",", + )}" in current owner ("${parsedOwner}")`, ); } @@ -63,8 +65,8 @@ export async function main( core.info( `owner and repositories set, creating token for repositories "${repositories.join( - "," - )}" owned by "${owner}"` + ",", + )}" owned by "${owner}"`, ); } @@ -84,31 +86,32 @@ export async function main( request, auth, parsedOwner, - parsedRepositoryNames + parsedRepositoryNames, + permissions, ), { onFailedAttempt: (error) => { core.info( `Failed to create token for "${parsedRepositoryNames.join( - "," - )}" (attempt ${error.attemptNumber}): ${error.message}` + ",", + )}" (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), + () => getTokenFromOwner(request, auth, parsedOwner, permissions), { onFailedAttempt: (error) => { core.info( - `Failed to create token for "${parsedOwner}" (attempt ${error.attemptNumber}): ${error.message}` + `Failed to create token for "${parsedOwner}" (attempt ${error.attemptNumber}): ${error.message}`, ); }, retries: 3, - } + }, )); } @@ -126,7 +129,7 @@ export async function main( } } -async function getTokenFromOwner(request, auth, 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", { @@ -140,6 +143,7 @@ async function getTokenFromOwner(request, auth, parsedOwner) { const authentication = await auth({ type: "installation", installationId: response.data.id, + permissions, }); const installationId = response.data.id; @@ -152,7 +156,8 @@ async function getTokenFromRepository( request, auth, parsedOwner, - parsedRepositoryNames + 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", { @@ -168,6 +173,7 @@ async function getTokenFromRepository( type: "installation", installationId: response.data.id, repositoryNames: parsedRepositoryNames, + permissions, }); const installationId = response.data.id; diff --git a/lib/request.js b/lib/request.js index 1bc8332..7593fb7 100644 --- a/lib/request.js +++ b/lib/request.js @@ -17,7 +17,7 @@ const proxyUrl = const proxyFetch = (url, options) => { const urlHost = new URL(url).hostname; const noProxy = (process.env.no_proxy || process.env.NO_PROXY || "").split( - "," + ",", ); if (!noProxy.includes(urlHost)) { diff --git a/main.js b/main.js index 8011b51..d96203d 100644 --- a/main.js +++ b/main.js @@ -5,6 +5,7 @@ import { createAppAuth } from "@octokit/auth-app"; import { main } from "./lib/main.js"; import request from "./lib/request.js"; +import { getPermissionsFromInputs } from "./lib/get-permissions-from-inputs.js"; if (!process.env.GITHUB_REPOSITORY) { throw new Error("GITHUB_REPOSITORY missing, must be set to '/'"); @@ -25,24 +26,28 @@ if (!privateKey) { throw new Error("Input required and not supplied: private-key"); } const owner = core.getInput("owner"); -const repositories = core.getInput("repositories") +const repositories = core + .getInput("repositories") .split(/[\n,]+/) - .map(s => s.trim()) - .filter(x => x !== ''); + .map((s) => s.trim()) + .filter((x) => x !== ""); const skipTokenRevoke = Boolean( - core.getInput("skip-token-revoke") || core.getInput("skip_token_revoke") + core.getInput("skip-token-revoke") || core.getInput("skip_token_revoke"), ); -main( +const permissions = getPermissionsFromInputs(process.env); + +export default main( appId, privateKey, owner, repositories, + permissions, core, createAppAuth, request, - skipTokenRevoke + skipTokenRevoke, ).catch((error) => { /* c8 ignore next 3 */ console.error(error); diff --git a/package-lock.json b/package-lock.json index cae4ed5..6b79dc6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,19 @@ { "name": "create-github-app-token", - "version": "1.11.5", + "version": "1.11.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "create-github-app-token", - "version": "1.11.5", + "version": "1.11.6", "license": "MIT", "dependencies": { "@actions/core": "^1.11.1", "@octokit/auth-app": "^7.1.5", "@octokit/request": "^9.2.2", "p-retry": "^6.2.1", - "undici": "^7.4.0" + "undici": "^7.5.0" }, "devDependencies": { "@octokit/openapi": "^18.0.0", @@ -3650,9 +3650,9 @@ } }, "node_modules/undici": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.4.0.tgz", - "integrity": "sha512-PUQM3/es3noM24oUn10u3kNNap0AbxESOmnssmW+dOi9yGwlUSi5nTNYl3bNbTkWOF8YZDkx2tCmj9OtQ3iGGw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.5.0.tgz", + "integrity": "sha512-NFQG741e8mJ0fLQk90xKxFdaSM7z4+IQpAgsFI36bCDY9Z2+aXXZjVy2uUksMouWfMI9+w5ejOq5zYYTBCQJDQ==", "license": "MIT", "engines": { "node": ">=20.18.1" diff --git a/package.json b/package.json index 33e103f..eb074a0 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "@octokit/auth-app": "^7.1.5", "@octokit/request": "^9.2.2", "p-retry": "^6.2.1", - "undici": "^7.4.0" + "undici": "^7.5.0" }, "devDependencies": { "@octokit/openapi": "^18.0.0", diff --git a/tests/README.md b/tests/README.md index b50533b..9e61bca 100644 --- a/tests/README.md +++ b/tests/README.md @@ -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. diff --git a/tests/main-repo-skew.test.js b/tests/main-repo-skew.test.js index e35a531..5905558 100644 --- a/tests/main-repo-skew.test.js +++ b/tests/main-repo-skew.test.js @@ -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); }); diff --git a/tests/main-token-get-owner-set-fail-response.test.js b/tests/main-token-get-owner-set-fail-response.test.js index 90408ba..35f8908 100644 --- a/tests/main-token-get-owner-set-fail-response.test.js +++ b/tests/main-token-get-owner-set-fail-response.test.js @@ -10,7 +10,7 @@ await test((mockPool) => { const mockAppSlug = "github-actions"; mockPool .intercept({ - path: `/users/${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: `/users/${process.env.INPUT_OWNER}/installation`, + path: `/users/smockle/installation`, method: "GET", headers: { accept: "application/vnd.github.v3+json", @@ -32,6 +32,6 @@ await test((mockPool) => { .reply( 200, { id: mockInstallationId, app_slug: mockAppSlug }, - { headers: { "content-type": "application/json" } } + { headers: { "content-type": "application/json" } }, ); }); diff --git a/tests/main-token-get-owner-set-repo-fail-response.test.js b/tests/main-token-get-owner-set-repo-fail-response.test.js index f97cf26..d54d1ab 100644 --- a/tests/main-token-get-owner-set-repo-fail-response.test.js +++ b/tests/main-token-get-owner-set-repo-fail-response.test.js @@ -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" } }, ); }); diff --git a/tests/main-token-get-owner-set-repo-unset.test.js b/tests/main-token-get-owner-set-repo-unset.test.js index 1c06512..0ce2b18 100644 --- a/tests/main-token-get-owner-set-repo-unset.test.js +++ b/tests/main-token-get-owner-set-repo-unset.test.js @@ -21,6 +21,6 @@ await test((mockPool) => { .reply( 200, { id: mockInstallationId, app_slug: mockAppSlug }, - { headers: { "content-type": "application/json" } } + { headers: { "content-type": "application/json" } }, ); }); diff --git a/tests/main-token-get-owner-unset-repo-unset.test.js b/tests/main-token-get-owner-unset-repo-unset.test.js index e284aae..697f193 100644 --- a/tests/main-token-get-owner-unset-repo-unset.test.js +++ b/tests/main-token-get-owner-unset-repo-unset.test.js @@ -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" } }, ); }); diff --git a/tests/main-token-permissions-set.test.js b/tests/main-token-permissions-set.test.js new file mode 100644 index 0000000..b3f6386 --- /dev/null +++ b/tests/main-token-permissions-set.test.js @@ -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`; +}); diff --git a/tests/main.js b/tests/main.js index 245b6e6..2172752 100644 --- a/tests/main.js +++ b/tests/main.js @@ -47,7 +47,7 @@ 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 mockAgent = new MockAgent({ enableCallHistory: true }); mockAgent.disableNetConnect(); setGlobalDispatcher(mockAgent); const mockPool = mockAgent.get(baseUrl.origin); @@ -60,8 +60,9 @@ export async function test(cb = (_mockPool) => {}, env = DEFAULT_ENV) { const owner = env.INPUT_OWNER ?? env.GITHUB_REPOSITORY_OWNER; const currentRepoName = env.GITHUB_REPOSITORY.split("/")[1]; const repo = encodeURIComponent( - (env.INPUT_REPOSITORIES ?? currentRepoName).split(",")[0] + (env.INPUT_REPOSITORIES ?? currentRepoName).split(",")[0], ); + mockPool .intercept({ path: `${basePath}/repos/${owner}/${repo}/installation`, @@ -75,13 +76,14 @@ export async function test(cb = (_mockPool) => {}, env = DEFAULT_ENV) { .reply( 200, { id: mockInstallationId, app_slug: mockAppSlug }, - { headers: { "content-type": "application/json" } } + { headers: { "content-type": "application/json" } }, ); // Mock installation access token request 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`, @@ -95,12 +97,26 @@ export async function test(cb = (_mockPool) => {}, env = DEFAULT_ENV) { .reply( 201, { token: mockInstallationAccessToken, expires_at: mockExpiresAt }, - { headers: { "content-type": "application/json" } } + { headers: { "content-type": "application/json" } }, ); // Run the callback 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")); } diff --git a/tests/snapshots/index.js.md b/tests/snapshots/index.js.md index 73a4c6a..f085f87 100644 --- a/tests/snapshots/index.js.md +++ b/tests/snapshots/index.js.md @@ -33,7 +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` + ::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-app-id.test.js @@ -92,7 +96,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-repo-skew.test.js @@ -112,7 +120,12 @@ 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-fail-response.test.js @@ -132,7 +145,12 @@ 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-repo-fail-response.test.js @@ -152,7 +170,12 @@ 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 @@ -171,7 +194,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","toolkit","checkout"]}` ## main-token-get-owner-set-repo-set-to-many.test.js @@ -190,7 +217,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","toolkit","checkout"]}` ## main-token-get-owner-set-repo-set-to-one.test.js @@ -209,7 +240,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-set-repo-unset.test.js @@ -228,7 +263,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 /users/actions/installation␊ + POST /app/installations/123456/access_tokens␊ + null` ## main-token-get-owner-unset-repo-set.test.js @@ -247,7 +286,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 @@ -266,7 +309,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-permissions-set.test.js + +> stderr + + '' + +> stdout + + `owner and repositories not set, creating token for the current repository ("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-revoke-token-fail-response.test.js diff --git a/tests/snapshots/index.js.snap b/tests/snapshots/index.js.snap index 061ef5b..2291b3a 100644 Binary files a/tests/snapshots/index.js.snap and b/tests/snapshots/index.js.snap differ