From 580a7f49c21eacc5d7af0a3be5ffa0c6a3abec06 Mon Sep 17 00:00:00 2001 From: Gregor Martynus <39992+gr2m@users.noreply.github.com> Date: Thu, 5 Sep 2024 16:09:13 -0700 Subject: [PATCH 01/11] WIP script to update `action.yml` with permissions based on https://github.com/octokit/app-permissions/blob/main/generated/api.github.com.json Co-Authored-By: Parker Brown <17183625+parkerbxyz@users.noreply.github.com> --- action.yml | 42 +++++++++++++++++++++++++++ scripts/update-permission-inputs.js | 44 +++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 scripts/update-permission-inputs.js diff --git a/action.yml b/action.yml index 09cc8fa..a0a99c8 100644 --- a/action.yml +++ b/action.yml @@ -37,6 +37,48 @@ inputs: github-api-url: description: The URL of the GitHub REST API. default: ${{ github.api_url }} + # + permission-metadata: + description: "Can be set to 'read'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#metadata" + permission-actions: + description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#actions" + permission-administration: + description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#administration" + permission-organization-user-blocking: + description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#organization-user-blocking" + permission-checks: + description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#checks" + permission-security-events: + description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#code-scanning-alerts" + permission-statuses: + description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#commit-statuses" + permission-contents: + description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#contents" + permission-vulnerability-alerts: + description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#dependabot-alerts" + permission-deployments: + description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#deployments" + permission-issues: + description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#issues" + permission-members: + description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#members" + permission-organization-administration: + description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#organization-administration" + permission-organization-projects: + description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#organization-projects" + permission-pages: + description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#pages" + permission-pull-requests: + description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#pull-requests" + permission-repository-projects: + description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#repository-projects" + permission-secrets: + description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#secrets" + permission-single-file: + description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#single-file" + permission-team-discussions: + description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#team-discussions" + # outputs: token: description: "GitHub installation access token" diff --git a/scripts/update-permission-inputs.js b/scripts/update-permission-inputs.js new file mode 100644 index 0000000..5e33dab --- /dev/null +++ b/scripts/update-permission-inputs.js @@ -0,0 +1,44 @@ +import { readFile, writeFile } from "node:fs/promises"; + +import { request } from "@octokit/request"; + +const { data: permissionsSchemaString } = await request( + "GET /repos/{owner}/{repo}/contents/{path}", + { + owner: "octokit", + repo: "app-permissions", + path: "generated/api.github.com.json", + mediaType: { + format: "raw", + }, + headers: { + authorization: `token ${process.env.GITHUB_TOKEN}`, + }, + }, +); + +const permissionsSchema = JSON.parse(permissionsSchemaString); + +const permissionsInputs = Object.entries(permissionsSchema.permissions).reduce( + (result, [key, value]) => { + const supportsWrite = value.write.length > 0; + const description = supportsWrite + ? `Can be set to 'read' or 'write'. Learn more at ${value.url}` + : `Can be set to 'read'. Learn more at ${value.url}`; + 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 `` and `` comments with the new content +const updatedActionsYamlContent = actionsYamlContent.replace( + /(?<=# )(.|\n)*(?=# )/, + permissionsInputs + "\n ", +); + +await writeFile("action.yml", updatedActionsYamlContent, "utf8"); +console.log("Updated action.yml with new permissions inputs"); From 165aac03ab564a94315d9e4df4a833d6fe902b52 Mon Sep 17 00:00:00 2001 From: Gregor Martynus <39992+gr2m@users.noreply.github.com> Date: Thu, 13 Mar 2025 12:44:08 -0700 Subject: [PATCH 02/11] build(deps): `@octokit/openapi` --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index c9e1006..05a2155 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "undici": "^6.19.2" }, "devDependencies": { + "@octokit/openapi": "^18.0.0", "@sinonjs/fake-timers": "^11.2.2", "ava": "^6.1.3", "c8": "^10.1.2", From 5275c2e87e407167c87efaeccc91a2408101d8ae Mon Sep 17 00:00:00 2001 From: Gregor Martynus <39992+gr2m@users.noreply.github.com> Date: Thu, 13 Mar 2025 12:44:13 -0700 Subject: [PATCH 03/11] build(package): lock file --- package-lock.json | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index a318de7..3f3b48a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "create-github-app-token", - "version": "1.10.2", + "version": "1.10.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "create-github-app-token", - "version": "1.10.2", + "version": "1.10.3", "license": "MIT", "dependencies": { "@actions/core": "^1.10.1", @@ -16,6 +16,7 @@ "undici": "^6.19.2" }, "devDependencies": { + "@octokit/openapi": "^18.0.0", "@sinonjs/fake-timers": "^11.2.2", "ava": "^6.1.3", "c8": "^10.1.2", @@ -695,6 +696,16 @@ "node": ">= 18" } }, + "node_modules/@octokit/openapi": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi/-/openapi-18.0.0.tgz", + "integrity": "sha512-N1khK+uLrWkyJ6J/kjYfhD4NnTsgU+xf1av6Ui9an5Z7Now5ZzUvUkKgymbmfGb+yjPHM/jQG2Ql4RWKw/AkpA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@octokit/openapi-types": { "version": "22.2.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", From f48303c5db7b9c46afc16536286dc00752046f6e Mon Sep 17 00:00:00 2001 From: Gregor Martynus <39992+gr2m@users.noreply.github.com> Date: Thu, 13 Mar 2025 13:01:32 -0700 Subject: [PATCH 04/11] build: update script to utilize `@octokit/openapi` as source of app permissions --- scripts/generated/app-permissions.json | 395 +++++++++++++++++++++++++ scripts/update-permission-inputs.js | 42 ++- 2 files changed, 412 insertions(+), 25 deletions(-) create mode 100644 scripts/generated/app-permissions.json diff --git a/scripts/generated/app-permissions.json b/scripts/generated/app-permissions.json new file mode 100644 index 0000000..c3e6a86 --- /dev/null +++ b/scripts/generated/app-permissions.json @@ -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 leve 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" + } +} \ No newline at end of file diff --git a/scripts/update-permission-inputs.js b/scripts/update-permission-inputs.js index 5e33dab..0adce5a 100644 --- a/scripts/update-permission-inputs.js +++ b/scripts/update-permission-inputs.js @@ -1,36 +1,28 @@ import { readFile, writeFile } from "node:fs/promises"; -import { request } from "@octokit/request"; +import OctokitOpenapi from "@octokit/openapi"; -const { data: permissionsSchemaString } = await request( - "GET /repos/{owner}/{repo}/contents/{path}", - { - owner: "octokit", - repo: "app-permissions", - path: "generated/api.github.com.json", - mediaType: { - format: "raw", - }, - headers: { - authorization: `token ${process.env.GITHUB_TOKEN}`, - }, - }, -); +const appPermissionsSchema = + OctokitOpenapi.schemas["api.github.com"].components.schemas[ + "app-permissions" + ]; -const permissionsSchema = JSON.parse(permissionsSchemaString); +await writeFile( + `scripts/generated/app-permissions.json`, + JSON.stringify(appPermissionsSchema, null, 2), + "utf8", +); -const permissionsInputs = Object.entries(permissionsSchema.permissions).reduce( - (result, [key, value]) => { - const supportsWrite = value.write.length > 0; - const description = supportsWrite - ? `Can be set to 'read' or 'write'. Learn more at ${value.url}` - : `Can be set to 'read'. Learn more at ${value.url}`; +const permissionsInputs = Object.entries(appPermissionsSchema.properties) + .sort((a, b) => a[0].localeCompare(b[0])) + .reduce((result, [key, value]) => { + const description = `Can be set to: ${value.enum + .map((permission) => `'${permission}'`) + .join(", ")}. ${value.description}`; return `${result} permission-${key.replace(/_/g, "-")}: description: "${description}"`; - }, - "", -); + }, ""); const actionsYamlContent = await readFile("action.yml", "utf8"); From 766bb55a4654d84065bfdb810fa2a57439f4d1c4 Mon Sep 17 00:00:00 2001 From: Gregor Martynus <39992+gr2m@users.noreply.github.com> Date: Thu, 13 Mar 2025 13:01:42 -0700 Subject: [PATCH 05/11] build: update `action.yml` by running script --- action.yml | 106 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 81 insertions(+), 25 deletions(-) diff --git a/action.yml b/action.yml index a0a99c8..a13e0ee 100644 --- a/action.yml +++ b/action.yml @@ -38,46 +38,102 @@ inputs: description: The URL of the GitHub REST API. default: ${{ github.api_url }} # - permission-metadata: - description: "Can be set to 'read'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#metadata" permission-actions: - description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#actions" + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for GitHub Actions workflows, workflow runs, and artifacts." permission-administration: - description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#administration" - permission-organization-user-blocking: - description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#organization-user-blocking" + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for repository creation, deletion, settings, teams, and collaborators creation." permission-checks: - description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#checks" - permission-security-events: - description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#code-scanning-alerts" - permission-statuses: - description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#commit-statuses" + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for checks on code." + permission-codespaces: + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to create, edit, delete, and list Codespaces." permission-contents: - description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#contents" - permission-vulnerability-alerts: - description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#dependabot-alerts" + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for repository contents, commits, branches, downloads, releases, and merges." + permission-dependabot-secrets: + description: "Can be set to: 'read', 'write'. The leve of permission to grant the access token to manage Dependabot secrets." permission-deployments: - description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#deployments" + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for deployments and deployment statuses." + permission-email-addresses: + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to manage the email addresses belonging to a user." + permission-environments: + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for managing repository environments." + permission-followers: + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to manage the followers belonging to a user." + permission-git-ssh-keys: + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to manage git SSH keys." + permission-gpg-keys: + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to view and manage GPG keys belonging to a user." + permission-interaction-limits: + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to view and manage interaction limits on a repository." permission-issues: - description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#issues" + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for issues and related comments, assignees, labels, and milestones." permission-members: - description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#members" + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for organization teams and members." + permission-metadata: + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to search repositories, list collaborators, and access repository metadata." permission-organization-administration: - description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#organization-administration" + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to manage access to an organization." + permission-organization-announcement-banners: + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to view and manage announcement banners for an organization." + permission-organization-copilot-seat-management: + description: "Can be set to: 'write'. 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." + permission-organization-custom-org-roles: + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for custom organization roles management." + permission-organization-custom-properties: + description: "Can be set to: 'read', 'write', 'admin'. The level of permission to grant the access token for custom property management." + permission-organization-custom-roles: + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for custom repository roles management." + permission-organization-events: + description: "Can be set to: 'read'. The level of permission to grant the access token to view events triggered by an activity in an organization." + permission-organization-hooks: + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to manage the post-receive hooks for an organization." + permission-organization-packages: + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for organization packages published to GitHub Packages." + permission-organization-personal-access-token-requests: + description: "Can be set to: 'read', 'write'. 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." + permission-organization-personal-access-tokens: + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for viewing and managing fine-grained personal access token requests to an organization." + permission-organization-plan: + description: "Can be set to: 'read'. The level of permission to grant the access token for viewing an organization's plan." permission-organization-projects: - description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#organization-projects" + description: "Can be set to: 'read', 'write', 'admin'. The level of permission to grant the access token to manage organization projects and projects public preview (where available)." + permission-organization-secrets: + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to manage organization secrets." + permission-organization-self-hosted-runners: + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to view and manage GitHub Actions self-hosted runners available to an organization." + permission-organization-user-blocking: + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to view and manage users blocked by the organization." + permission-packages: + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for packages published to GitHub Packages." permission-pages: - description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#pages" + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to retrieve Pages statuses, configuration, and builds, as well as create new builds." + permission-profile: + description: "Can be set to: 'write'. The level of permission to grant the access token to manage the profile settings belonging to a user." permission-pull-requests: - description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#pull-requests" + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for pull requests and related comments, assignees, labels, milestones, and merges." + permission-repository-custom-properties: + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to view and edit custom properties for a repository, when allowed by the property." + permission-repository-hooks: + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to manage the post-receive hooks for a repository." permission-repository-projects: - description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#repository-projects" + description: "Can be set to: 'read', 'write', 'admin'. The level of permission to grant the access token to manage repository projects, columns, and cards." + permission-secret-scanning-alerts: + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to view and manage secret scanning alerts." permission-secrets: - description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#secrets" + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to manage repository secrets." + permission-security-events: + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to view and manage security events like code scanning alerts." permission-single-file: - description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#single-file" + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to manage just a single file." + permission-starring: + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to list and manage repositories a user is starring." + permission-statuses: + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for commit statuses." permission-team-discussions: - description: "Can be set to 'read' or 'write'. Learn more at https://docs.github.com/en/free-pro-team@latest/rest/reference/permissions-required-for-github-apps/#team-discussions" + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to manage team discussions and related comments." + permission-vulnerability-alerts: + description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to manage Dependabot alerts." + permission-workflows: + description: "Can be set to: 'write'. The level of permission to grant the access token to update GitHub Actions workflow files." # outputs: token: From be692b834e84a4b9c071de1bafdaf050690b0f1a Mon Sep 17 00:00:00 2001 From: Parker Brown <17183625+parkerbxyz@users.noreply.github.com> Date: Thu, 13 Mar 2025 13:11:30 -0700 Subject: [PATCH 06/11] build: end generated file with newline --- scripts/generated/app-permissions.json | 2 +- scripts/update-permission-inputs.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/generated/app-permissions.json b/scripts/generated/app-permissions.json index c3e6a86..ae7fa8b 100644 --- a/scripts/generated/app-permissions.json +++ b/scripts/generated/app-permissions.json @@ -392,4 +392,4 @@ "deployments": "write", "single_file": "read" } -} \ No newline at end of file +} diff --git a/scripts/update-permission-inputs.js b/scripts/update-permission-inputs.js index 0adce5a..cf6339c 100644 --- a/scripts/update-permission-inputs.js +++ b/scripts/update-permission-inputs.js @@ -9,7 +9,7 @@ const appPermissionsSchema = await writeFile( `scripts/generated/app-permissions.json`, - JSON.stringify(appPermissionsSchema, null, 2), + JSON.stringify(appPermissionsSchema, null, 2) + "\n", "utf8", ); From f4d40fc9664e839bf29bc478ba55b1f6b868f37d Mon Sep 17 00:00:00 2001 From: Parker Brown <17183625+parkerbxyz@users.noreply.github.com> Date: Thu, 13 Mar 2025 13:26:04 -0700 Subject: [PATCH 07/11] build: use `Intl.ListFormat` to format permission access values --- action.yml | 96 ++++++++++++++--------------- scripts/update-permission-inputs.js | 7 ++- 2 files changed, 52 insertions(+), 51 deletions(-) diff --git a/action.yml b/action.yml index a13e0ee..c35854d 100644 --- a/action.yml +++ b/action.yml @@ -39,101 +39,101 @@ inputs: default: ${{ github.api_url }} # permission-actions: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for GitHub Actions workflows, workflow runs, and artifacts." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token for GitHub Actions workflows, workflow runs, and artifacts." permission-administration: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for repository creation, deletion, settings, teams, and collaborators creation." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token for repository creation, deletion, settings, teams, and collaborators creation." permission-checks: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for checks on code." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token for checks on code." permission-codespaces: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to create, edit, delete, and list Codespaces." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token to create, edit, delete, and list Codespaces." permission-contents: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for repository contents, commits, branches, downloads, releases, and merges." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token for repository contents, commits, branches, downloads, releases, and merges." permission-dependabot-secrets: - description: "Can be set to: 'read', 'write'. The leve of permission to grant the access token to manage Dependabot secrets." + description: "Can be set to 'read' or 'write'. The leve of permission to grant the access token to manage Dependabot secrets." permission-deployments: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for deployments and deployment statuses." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token for deployments and deployment statuses." permission-email-addresses: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to manage the email addresses belonging to a user." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token to manage the email addresses belonging to a user." permission-environments: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for managing repository environments." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token for managing repository environments." permission-followers: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to manage the followers belonging to a user." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token to manage the followers belonging to a user." permission-git-ssh-keys: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to manage git SSH keys." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token to manage git SSH keys." permission-gpg-keys: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to view and manage GPG keys belonging to a user." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token to view and manage GPG keys belonging to a user." permission-interaction-limits: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to view and manage interaction limits on a repository." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token to view and manage interaction limits on a repository." permission-issues: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for issues and related comments, assignees, labels, and milestones." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token for issues and related comments, assignees, labels, and milestones." permission-members: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for organization teams and members." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token for organization teams and members." permission-metadata: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to search repositories, list collaborators, and access repository metadata." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token to search repositories, list collaborators, and access repository metadata." permission-organization-administration: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to manage access to an organization." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token to manage access to an organization." permission-organization-announcement-banners: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to view and manage announcement banners for an organization." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token to view and manage announcement banners for an organization." permission-organization-copilot-seat-management: - description: "Can be set to: 'write'. 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." + description: "Can be set to 'write'. 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." permission-organization-custom-org-roles: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for custom organization roles management." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token for custom organization roles management." permission-organization-custom-properties: - description: "Can be set to: 'read', 'write', 'admin'. The level of permission to grant the access token for custom property management." + description: "Can be set to 'read', 'write', or 'admin'. The level of permission to grant the access token for custom property management." permission-organization-custom-roles: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for custom repository roles management." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token for custom repository roles management." permission-organization-events: - description: "Can be set to: 'read'. The level of permission to grant the access token to view events triggered by an activity in an organization." + description: "Can be set to 'read'. The level of permission to grant the access token to view events triggered by an activity in an organization." permission-organization-hooks: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to manage the post-receive hooks for an organization." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token to manage the post-receive hooks for an organization." permission-organization-packages: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for organization packages published to GitHub Packages." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token for organization packages published to GitHub Packages." permission-organization-personal-access-token-requests: - description: "Can be set to: 'read', 'write'. 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." + description: "Can be set to 'read' or 'write'. 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." permission-organization-personal-access-tokens: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for viewing and managing fine-grained personal access token requests to an organization." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token for viewing and managing fine-grained personal access token requests to an organization." permission-organization-plan: - description: "Can be set to: 'read'. The level of permission to grant the access token for viewing an organization's plan." + description: "Can be set to 'read'. The level of permission to grant the access token for viewing an organization's plan." permission-organization-projects: - description: "Can be set to: 'read', 'write', 'admin'. The level of permission to grant the access token to manage organization projects and projects public preview (where available)." + description: "Can be set to 'read', 'write', or 'admin'. The level of permission to grant the access token to manage organization projects and projects public preview (where available)." permission-organization-secrets: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to manage organization secrets." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token to manage organization secrets." permission-organization-self-hosted-runners: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to view and manage GitHub Actions self-hosted runners available to an organization." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token to view and manage GitHub Actions self-hosted runners available to an organization." permission-organization-user-blocking: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to view and manage users blocked by the organization." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token to view and manage users blocked by the organization." permission-packages: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for packages published to GitHub Packages." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token for packages published to GitHub Packages." permission-pages: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to retrieve Pages statuses, configuration, and builds, as well as create new builds." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token to retrieve Pages statuses, configuration, and builds, as well as create new builds." permission-profile: - description: "Can be set to: 'write'. The level of permission to grant the access token to manage the profile settings belonging to a user." + description: "Can be set to 'write'. The level of permission to grant the access token to manage the profile settings belonging to a user." permission-pull-requests: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for pull requests and related comments, assignees, labels, milestones, and merges." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token for pull requests and related comments, assignees, labels, milestones, and merges." permission-repository-custom-properties: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to view and edit custom properties for a repository, when allowed by the property." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token to view and edit custom properties for a repository, when allowed by the property." permission-repository-hooks: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to manage the post-receive hooks for a repository." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token to manage the post-receive hooks for a repository." permission-repository-projects: - description: "Can be set to: 'read', 'write', 'admin'. The level of permission to grant the access token to manage repository projects, columns, and cards." + description: "Can be set to 'read', 'write', or 'admin'. The level of permission to grant the access token to manage repository projects, columns, and cards." permission-secret-scanning-alerts: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to view and manage secret scanning alerts." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token to view and manage secret scanning alerts." permission-secrets: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to manage repository secrets." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token to manage repository secrets." permission-security-events: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to view and manage security events like code scanning alerts." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token to view and manage security events like code scanning alerts." permission-single-file: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to manage just a single file." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token to manage just a single file." permission-starring: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to list and manage repositories a user is starring." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token to list and manage repositories a user is starring." permission-statuses: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token for commit statuses." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token for commit statuses." permission-team-discussions: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to manage team discussions and related comments." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token to manage team discussions and related comments." permission-vulnerability-alerts: - description: "Can be set to: 'read', 'write'. The level of permission to grant the access token to manage Dependabot alerts." + description: "Can be set to 'read' or 'write'. The level of permission to grant the access token to manage Dependabot alerts." permission-workflows: - description: "Can be set to: 'write'. The level of permission to grant the access token to update GitHub Actions workflow files." + description: "Can be set to 'write'. The level of permission to grant the access token to update GitHub Actions workflow files." # outputs: token: diff --git a/scripts/update-permission-inputs.js b/scripts/update-permission-inputs.js index cf6339c..b977ffb 100644 --- a/scripts/update-permission-inputs.js +++ b/scripts/update-permission-inputs.js @@ -16,9 +16,10 @@ await writeFile( const permissionsInputs = Object.entries(appPermissionsSchema.properties) .sort((a, b) => a[0].localeCompare(b[0])) .reduce((result, [key, value]) => { - const description = `Can be set to: ${value.enum - .map((permission) => `'${permission}'`) - .join(", ")}. ${value.description}`; + const formatter = new Intl.ListFormat('en', { style: 'long', type: 'disjunction' }); + const permissionAccessValues = formatter.format(value.enum.map(p => `'${p}'`)); + + const description = `Can be set to ${permissionAccessValues}. ${value.description}`; return `${result} permission-${key.replace(/_/g, "-")}: description: "${description}"`; From b17ed311dc0137e0a22e66a8db40e2ec73267d49 Mon Sep 17 00:00:00 2001 From: Parker Brown <17183625+parkerbxyz@users.noreply.github.com> Date: Thu, 13 Mar 2025 13:36:40 -0700 Subject: [PATCH 08/11] style: format with Prettier --- scripts/update-permission-inputs.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/scripts/update-permission-inputs.js b/scripts/update-permission-inputs.js index b977ffb..31f02c1 100644 --- a/scripts/update-permission-inputs.js +++ b/scripts/update-permission-inputs.js @@ -10,14 +10,19 @@ const appPermissionsSchema = await writeFile( `scripts/generated/app-permissions.json`, JSON.stringify(appPermissionsSchema, null, 2) + "\n", - "utf8", + "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 formatter = new Intl.ListFormat("en", { + style: "long", + type: "disjunction", + }); + const permissionAccessValues = formatter.format( + value.enum.map((p) => `'${p}'`) + ); const description = `Can be set to ${permissionAccessValues}. ${value.description}`; return `${result} @@ -30,7 +35,7 @@ const actionsYamlContent = await readFile("action.yml", "utf8"); // In the action.yml file, replace the content between the `` and `` comments with the new content const updatedActionsYamlContent = actionsYamlContent.replace( /(?<=# )(.|\n)*(?=# )/, - permissionsInputs + "\n ", + permissionsInputs + "\n " ); await writeFile("action.yml", updatedActionsYamlContent, "utf8"); From 367412411b31579320d306d1e13c13a5fd942394 Mon Sep 17 00:00:00 2001 From: Parker Brown <17183625+parkerbxyz@users.noreply.github.com> Date: Thu, 13 Mar 2025 13:54:56 -0700 Subject: [PATCH 09/11] build: swap order of description and valid values in `action.yml` --- action.yml | 96 ++++++++++++++--------------- scripts/update-permission-inputs.js | 2 +- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/action.yml b/action.yml index 4d029d0..aab57bc 100644 --- a/action.yml +++ b/action.yml @@ -39,101 +39,101 @@ inputs: default: ${{ github.api_url }} # permission-actions: - description: "Can be set to 'read' or 'write'. The level of permission to grant the access token for GitHub Actions workflows, workflow runs, and artifacts." + 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token for repository creation, deletion, settings, teams, and collaborators creation." + 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token for checks on code." + description: "The level of permission to grant the access token for checks on code. Can be set to 'read' or 'write'." permission-codespaces: - description: "Can be set to 'read' or 'write'. The level of permission to grant the access token to create, edit, delete, and list 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token for repository contents, commits, branches, downloads, releases, and merges." + 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: "Can be set to 'read' or 'write'. The leve of permission to grant the access token to manage Dependabot secrets." + description: "The leve of permission to grant the access token to manage Dependabot secrets. Can be set to 'read' or 'write'." permission-deployments: - description: "Can be set to 'read' or 'write'. The level of permission to grant the access token for deployments and deployment statuses." + 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token to manage the email addresses belonging to a user." + 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token for managing repository 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token to manage the followers belonging to a user." + 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token to manage 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token to view and manage GPG keys belonging to a user." + 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token to view and manage interaction limits on a repository." + 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token for issues and related comments, assignees, labels, and milestones." + 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token for organization teams and 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token to search repositories, list collaborators, and access repository 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token to manage access to an organization." + 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token to view and manage announcement banners for an organization." + 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: "Can be set to 'write'. 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." + 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token for custom organization roles management." + 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: "Can be set to 'read', 'write', or 'admin'. The level of permission to grant the access token for custom property management." + 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token for custom repository roles management." + 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: "Can be set to 'read'. The level of permission to grant the access token to view events triggered by an activity in an organization." + 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token to manage the post-receive hooks for an organization." + 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token for organization packages published to GitHub 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: "Can be set to 'read' or 'write'. 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." + 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token for viewing and managing fine-grained personal access token requests to an organization." + 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: "Can be set to 'read'. The level of permission to grant the access token for viewing an organization's 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: "Can be set to 'read', 'write', or 'admin'. The level of permission to grant the access token to manage organization projects and projects public preview (where available)." + 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token to manage 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token to view and manage GitHub Actions self-hosted runners available to an organization." + 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token to view and manage users blocked by the organization." + 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token for packages published to GitHub 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token to retrieve Pages statuses, configuration, and builds, as well as create new builds." + 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: "Can be set to 'write'. The level of permission to grant the access token to manage the profile settings belonging to a user." + 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token for pull requests and related comments, assignees, labels, milestones, and merges." + 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token to view and edit custom properties for a repository, when allowed by the property." + 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token to manage the post-receive hooks for a repository." + 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: "Can be set to 'read', 'write', or 'admin'. The level of permission to grant the access token to manage repository projects, columns, and cards." + 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token to view and manage 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token to manage repository 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token to view and manage security events like code scanning alerts." + 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token to manage just a 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token to list and manage repositories a user is 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token for commit 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token to manage team discussions and related comments." + 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: "Can be set to 'read' or 'write'. The level of permission to grant the access token to manage Dependabot 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: "Can be set to 'write'. The level of permission to grant the access token to update GitHub Actions workflow files." + description: "The level of permission to grant the access token to update GitHub Actions workflow files. Can be set to 'write'." # outputs: token: diff --git a/scripts/update-permission-inputs.js b/scripts/update-permission-inputs.js index 31f02c1..8f17994 100644 --- a/scripts/update-permission-inputs.js +++ b/scripts/update-permission-inputs.js @@ -24,7 +24,7 @@ const permissionsInputs = Object.entries(appPermissionsSchema.properties) value.enum.map((p) => `'${p}'`) ); - const description = `Can be set to ${permissionAccessValues}. ${value.description}`; + const description = `${value.description} Can be set to ${permissionAccessValues}.`; return `${result} permission-${key.replace(/_/g, "-")}: description: "${description}"`; From 7bc649b05806042eb47d114afa377b44558b88f5 Mon Sep 17 00:00:00 2001 From: Gregor Martynus <39992+gr2m@users.noreply.github.com> Date: Tue, 25 Mar 2025 10:39:24 -0700 Subject: [PATCH 10/11] add implementation of permissions inputs (#217) --- CONTRIBUTING.md | 15 +++ README.md | 72 ++++++++++---- lib/get-permissions-from-inputs.js | 23 +++++ lib/main.js | 38 +++++--- lib/request.js | 2 +- main.js | 17 ++-- package-lock.json | 12 +-- package.json | 2 +- tests/README.md | 11 +++ tests/main-repo-skew.test.js | 32 +++--- ...-token-get-owner-set-fail-response.test.js | 6 +- ...n-get-owner-set-repo-fail-response.test.js | 4 +- ...ain-token-get-owner-set-repo-unset.test.js | 2 +- ...n-token-get-owner-unset-repo-unset.test.js | 4 +- tests/main-token-permissions-set.test.js | 7 ++ tests/main.js | 26 ++++- tests/snapshots/index.js.md | 92 +++++++++++++++--- tests/snapshots/index.js.snap | Bin 1318 -> 1511 bytes 18 files changed, 279 insertions(+), 86 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 lib/get-permissions-from-inputs.js create mode 100644 tests/main-token-permissions-set.test.js 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 061ef5b8b6372f1cb16b1cea0dac1646c8574b67..2291b3afed0274a2edb7b728e95da7287556f135 100644 GIT binary patch literal 1511 zcmVR~0A_R(r1n&?-aI%onK`v?1u3fEx*ihG1h;_6b zbQ@E+*iVup&YijEoUEEAA@&Ur;ysi227Dr3@C|suJGir(*lOH%bvq>Hyh@z&bN_qp z_kYgz`PXhIklp})c?O#603H$MX)XozAynWo4L;=}j5O9Ypss(190d!pFE#%7%=ejQ zU%WE++MM~!zdHBU90aTbK^L_TO2rNC&9iiqg`Ig8hHdVXKYsX`RO9be!jVk-Buw~Z z%!7b*fG8Ns5%`1)!bmsLkpeOTgpkjTfs;UAnx_Z;bMK8aMXsQlW>WGY1(C^59iVuGA^hWp_W71cd1))T)rQ(1DLDz_kXeuKeMw$pVgpX|x#X^|8vmjvBE8KV8W@~xn%Id79W=t4FJ=Z-PWIP_z zl#PI*W*W{W>}Ujd6mkW4UAt~;skus*)@ZZ2*J`=V6}P!`zd|V`I~!l#-PqmRC6v<2 z=Efdru#h)K%MCj-jYM7}XJ#7dp;zu~?@rHWVVLNOkx zlj_-|l8n?;S<4sfW(3zx%w#I)*)+bN(Kw;z7>Tzs65S&?Ju#b&Yo?pCCbez_r^Zdk zT=yxU$3pO_fN{VD6j@6b?q{m+=XurF&O}ZH)_oveq!b7}J?Vff=KAn*S$Z$3^cJYO zFQnLxEYT(g#Q}`-o%dIU%RlF;TZoY<3%W|Yfbk(&ta7HSmnY6+f@}#5J+RjV3J^pL zY9EN^Lm;}(wEe{pL7734F+;*_;Cki3r_$4Mz;%N~x^G7EkZf{&E9#KzcedgX#us>i z8ivVl1;ppwE?D?G2V{APAb3*vSSLH6)qzo9Y8s#>#=KU*;VB*v=6Ny_=Hjg@Reh#K zA(M))id`Ue84tj>Gf-X>{ddkxO+(g=&(pS`bJHl~TpGm-P<)TjGdVZtQImahGF5<~ z6bMBYAk5J1FPwVWVL^~(+Z^oi5eO1H#KEET1_3ygn|7pTU5=|&D$1l0S!7znFw~@Z z*-abYXka781J((k^v6Z{BSKKvNM>V(Cyx35xqKj^AehPeaxUw3xjHxCf6oB_+r^gi zy4$r$ftRL?f^F>QKWa8lPR;7qk!LwYaZZge(%7P?7YEkfV78T!6V3hGhp;^R}oPFYF0m~08e z5f&#fff^&LMzTe8cd+~wmj+qi% z^jvq(GHyEcY*I-4tkNa3U!t)RV{P&5<-gr?vZ~dLs>N7J61GOnD|O-2hEs_%_rg=c z3YClfOBqpbO!G4U|HPbF&4qIEVW#_ck4%r*WlwsUspjpJnt~Sq`|oA6ycuiZlS-NC N{~wy7L-4LD002Tg_Xz+1 literal 1318 zcmV+>1=;#RRzV)BR?qoc?83^~}yq{2SD(xF86^UIo#=z`sHd9>t4C!B^FtnJzM&gmkhCOXrZx zbgJL`)_b4#s=E67e$Nf;5&z~f2qs*3M5rxT;2REv5!h6KV=zCA1#bx8qVYLr+@~QQ z27-U}*m0a8-}vsa{<(8EQb*rqn+*u zriqu#g<`H!Lq=lg*dHIUuq%QQ_}vJ`_&4!-;&L(H(ww2p88dUKf_d13SRCiF>QUw! zcFaZK83QJUanGP3GvdfClFeVVZ2nXUCO$-JDiMSMXCjCgaKb^KXV6HBmV3kO#4(UxvH6fB$mo1Arj^R;0vaG?7cg>9t+OXcb+uCZ`8(U^;r`a5+R%cjKpLBcy15oQ1>cV6z3NZJAdH8Ech5AN0he(En zOen|YUK(yJKYTy1F(yphq%jhVk^GG8GO-u;$o9QXVnRa4r?{Sb>37@!IIQ0XMHa4v zYfWU(4t$3<$N`9GDiz3vf%7D?GL(ShTWqvo+d=HhjRtbsSjhhqEfn7sTg>~ExmcI- zV!41ny#+jJlgY+#AjQiZ8gd#kA(##xqFgA_CjZee`J-~~=7=%YA(0mcY>fX-P^)qe zA3$8FF5gx3$kiihoEa;#L1cvrERawTbJfaLkkmlOXo2=zC_%d#(AIK6Q%(2MX!}p2 z?eD8?N~d9`-CLsf4AI*<9XRIdb}+x0^si&fWKbJBW_&;fr~qWx;+GYgi|e- z%%_Kwd2XCaj7p4hF>0O*qo7zbD$m=VFKFI59hw+7PofAfKWMysdzpLn5{+kph9Y9t zQHr4Pn})`dw3VD>BDp?K|2caI^JfQPiG`IJG3A-p6(M7sg-qdoQXY{$N4V55_#%CM zm{M~|kL^b-?Pb9Y%bL*0M-D9OP=O$w+A4@7d9Jp|;vsGcM@va~M0MWI6&Hw-a From d64ecbbda822637ed6981f76e1687c1d765cc05a Mon Sep 17 00:00:00 2001 From: Parker Brown <17183625+parkerbxyz@users.noreply.github.com> Date: Thu, 27 Mar 2025 11:55:00 -0700 Subject: [PATCH 11/11] Add comment to explain why we are exporting main --- main.js | 1 + 1 file changed, 1 insertion(+) diff --git a/main.js b/main.js index d96203d..81b7767 100644 --- a/main.js +++ b/main.js @@ -38,6 +38,7 @@ const skipTokenRevoke = Boolean( const permissions = getPermissionsFromInputs(process.env); +// Export promise for testing export default main( appId, privateKey,