Compare commits

..

2 Commits

Author SHA1 Message Date
semantic-release-bot fb1c7fda2b build(release): 3.0.0-beta.1 [skip ci]
# [3.0.0-beta.1](https://github.com/actions/create-github-app-token/compare/v2.1.1...v3.0.0-beta.1) (2025-08-15)

* feat!: node 24 support ([#275](https://github.com/actions/create-github-app-token/issues/275)) ([6178938](https://github.com/actions/create-github-app-token/commit/61789386cb26150ab580cab449a9ae053bb9fd24))

### BREAKING CHANGES

* Requires [Actions Runner v2.327.1](https://github.com/actions/runner/releases/tag/v2.327.1) or later if you are using a self-hosted runner.
2025-08-15 19:55:36 +00:00
Salman Chishti 61789386cb feat!: node 24 support (#275)
BREAKING CHANGE: Requires [Actions Runner v2.327.1](https://github.com/actions/runner/releases/tag/v2.327.1) or later if you are using a self-hosted runner.

---------

Co-authored-by: Parker Brown <17183625+parkerbxyz@users.noreply.github.com>
2025-08-15 12:55:04 -07:00
24 changed files with 3029 additions and 5304 deletions
@@ -12,6 +12,6 @@ jobs:
id-token: write
packages: write
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- name: Publish Immutable Action
uses: actions/publish-immutable-action@v0.0.4
+3 -3
View File
@@ -18,14 +18,14 @@ jobs:
runs-on: ubuntu-latest
steps:
# build local version to create token
- uses: actions/checkout@v6
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: actions/setup-node@v6
- uses: actions/setup-node@v4
with:
node-version-file: package.json
cache: 'npm'
- run: npm ci
- run: npm run build
-34
View File
@@ -1,34 +0,0 @@
# This workflow warns and then closes issues that have had no activity for a specified amount of time.
# https://github.com/actions/stale
name: Stale
on:
workflow_dispatch:
schedule:
# 00:00 UTC on Mondays
- cron: '0 0 * * 1'
permissions:
issues: write
pull-requests: write
env:
DAYS_BEFORE_STALE: 180
DAYS_BEFORE_CLOSE: 60
STALE_LABEL: 'stale'
STALE_LABEL_URL: ${{github.server_url}}/${{github.repository}}/labels/stale
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v10
with:
operations-per-run: 100
days-before-stale: ${{ env.DAYS_BEFORE_STALE }}
days-before-close: ${{ env.DAYS_BEFORE_CLOSE }}
stale-issue-label: ${{ env.STALE_LABEL }}
stale-pr-label: ${{ env.STALE_LABEL }}
stale-issue-message: 'This issue has been marked ${{ env.STALE_LABEL_URL }} because it has been open for ${{ env.DAYS_BEFORE_STALE }} days with no activity. Please close this issue if it is no longer needed. If this issue is still relevant and you would like it to remain open, simply update it within the next ${{ env.DAYS_BEFORE_CLOSE }} days.'
stale-pr-message: 'This pull request has been marked ${{ env.STALE_LABEL_URL }} because it has been open for ${{ env.DAYS_BEFORE_STALE }} days with no activity. Please close this pull request if it is no longer needed. If this pull request is still relevant and you would like it to remain open, simply update it within the next ${{ env.DAYS_BEFORE_CLOSE }} days.'
+9 -8
View File
@@ -5,7 +5,6 @@ on:
branches:
- main
pull_request:
merge_group:
workflow_dispatch:
concurrency:
@@ -17,28 +16,30 @@ permissions:
jobs:
integration:
name: integration
name: Integration
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: actions/setup-node@v6
- uses: actions/setup-node@v4
with:
node-version-file: package.json
cache: 'npm'
- run: npm ci
- run: npm test
end-to-end:
name: end-to-end
name: End-to-End
runs-on: ubuntu-latest
# do not run from forks, as forks dont have access to repository secrets
if: github.event_name == 'merge_group' || github.event.pull_request.head.repo.owner.login == github.event.pull_request.base.repo.owner.login
if: github.event.pull_request.head.repo.owner.login == github.event.pull_request.base.repo.owner.login
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
- uses: actions/checkout@v5
- uses: actions/setup-node@v4
with:
node-version-file: package.json
cache: 'npm'
- run: npm ci
- run: npm run build
- uses: ./ # Uses the action in the root directory
+5 -14
View File
@@ -13,30 +13,21 @@ concurrency:
permissions:
contents: write
pull-requests: write
jobs:
update-permission-inputs:
runs-on: ubuntu-latest
env:
COMMIT_MESSAGE: 'feat: update permission inputs'
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
- uses: actions/checkout@v5
- uses: actions/setup-node@v4
with:
node-version-file: package.json
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run permission inputs update script
run: node scripts/update-permission-inputs.js
- name: Commit changes
id: auto-commit
uses: stefanzweifel/git-auto-commit-action@778341af668090896ca464160c2def5d1d1a3eb0 # v6.0.1
uses: stefanzweifel/git-auto-commit-action@b863ae1933cb653a53c021fe36dbb774e1fb9403 # v5.2.0
with:
commit_message: ${{ env.COMMIT_MESSAGE }}
- name: Update PR title
if: github.event_name == 'pull_request' && steps.auto-commit.outputs.changes_detected == 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh pr edit ${{ github.event.pull_request.number }} --title "${{ env.COMMIT_MESSAGE }}"
commit_message: 'feat: update permission inputs'
+12 -12
View File
@@ -28,7 +28,7 @@ jobs:
hello-world:
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@v2
- uses: actions/create-github-app-token@v3
id: app-token
with:
app-id: ${{ vars.APP_ID }}
@@ -47,13 +47,13 @@ jobs:
auto-format:
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@v2
- uses: actions/create-github-app-token@v3
id: app-token
with:
# required
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
token: ${{ steps.app-token.outputs.token }}
ref: ${{ github.head_ref }}
@@ -73,7 +73,7 @@ jobs:
auto-format:
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@v2
- uses: actions/create-github-app-token@v3
id: app-token
with:
# required
@@ -98,7 +98,7 @@ jobs:
auto-format:
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@v2
- uses: actions/create-github-app-token@v3
id: app-token
with:
# required
@@ -135,7 +135,7 @@ jobs:
hello-world:
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@v2
- uses: actions/create-github-app-token@v3
id: app-token
with:
app-id: ${{ vars.APP_ID }}
@@ -157,7 +157,7 @@ jobs:
hello-world:
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@v2
- uses: actions/create-github-app-token@v3
id: app-token
with:
app-id: ${{ vars.APP_ID }}
@@ -182,7 +182,7 @@ jobs:
hello-world:
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@v2
- uses: actions/create-github-app-token@v3
id: app-token
with:
app-id: ${{ vars.APP_ID }}
@@ -207,7 +207,7 @@ jobs:
hello-world:
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@v2
- uses: actions/create-github-app-token@v3
id: app-token
with:
app-id: ${{ vars.APP_ID }}
@@ -249,7 +249,7 @@ jobs:
owners-and-repos: ${{ fromJson(needs.set-matrix.outputs.matrix) }}
steps:
- uses: actions/create-github-app-token@v2
- uses: actions/create-github-app-token@v3
id: app-token
with:
app-id: ${{ vars.APP_ID }}
@@ -279,7 +279,7 @@ jobs:
steps:
- name: Create GitHub App token
id: create_token
uses: actions/create-github-app-token@v2
uses: actions/create-github-app-token@v3
with:
app-id: ${{ vars.GHES_APP_ID }}
private-key: ${{ secrets.GHES_APP_PRIVATE_KEY }}
@@ -318,7 +318,7 @@ steps:
echo "private-key=$private_key" >> "$GITHUB_OUTPUT"
- name: Generate GitHub App Token
id: app-token
uses: actions/create-github-app-token@v2
uses: actions/create-github-app-token@v3
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ steps.decode.outputs.private-key }}
+2 -6
View File
@@ -37,16 +37,12 @@ inputs:
description: "The level of permission to grant the access token to create, edit, delete, and list Codespaces. Can be set to 'read' or 'write'."
permission-contents:
description: "The level of permission to grant the access token for repository contents, commits, branches, downloads, releases, and merges. Can be set to 'read' or 'write'."
permission-custom-properties-for-organizations:
description: "The level of permission to grant the access token to view and edit custom properties for an organization, when allowed by the property. Can be set to 'read' or 'write'."
permission-dependabot-secrets:
description: "The level of permission to grant the access token to manage Dependabot secrets. Can be set to 'read' or 'write'."
permission-deployments:
description: "The level of permission to grant the access token for deployments and deployment statuses. Can be set to 'read' or 'write'."
permission-email-addresses:
description: "The level of permission to grant the access token to manage the email addresses belonging to a user. Can be set to 'read' or 'write'."
permission-enterprise-custom-properties-for-organizations:
description: "The level of permission to grant the access token for organization custom properties management at the enterprise level. Can be set to 'read', 'write', or 'admin'."
permission-environments:
description: "The level of permission to grant the access token for managing repository environments. Can be set to 'read' or 'write'."
permission-followers:
@@ -72,7 +68,7 @@ inputs:
permission-organization-custom-org-roles:
description: "The level of permission to grant the access token for custom organization roles management. Can be set to 'read' or 'write'."
permission-organization-custom-properties:
description: "The level of permission to grant the access token for repository custom properties management at the organization level. Can be set to 'read', 'write', or 'admin'."
description: "The level of permission to grant the access token for custom property management. Can be set to 'read', 'write', or 'admin'."
permission-organization-custom-roles:
description: "The level of permission to grant the access token for custom repository roles management. Can be set to 'read' or 'write'."
permission-organization-events:
@@ -136,6 +132,6 @@ outputs:
app-slug:
description: "GitHub App slug"
runs:
using: "node20"
using: "node24"
main: "dist/main.cjs"
post: "dist/post.cjs"
+1291 -2319
View File
File diff suppressed because one or more lines are too long
+912 -2076
View File
File diff suppressed because one or more lines are too long
+5 -5
View File
@@ -89,12 +89,12 @@ export async function main(
permissions
),
{
shouldRetry: ({ error }) => error.status >= 500,
onFailedAttempt: (context) => {
shouldRetry: (error) => error.status >= 500,
onFailedAttempt: (error) => {
core.info(
`Failed to create token for "${parsedRepositoryNames.join(
","
)}" (attempt ${context.attemptNumber}): ${context.error.message}`
)}" (attempt ${error.attemptNumber}): ${error.message}`
);
},
retries: 3,
@@ -105,9 +105,9 @@ export async function main(
({ authentication, installationId, appSlug } = await pRetry(
() => getTokenFromOwner(request, auth, parsedOwner, permissions),
{
onFailedAttempt: (context) => {
onFailedAttempt: (error) => {
core.info(
`Failed to create token for "${parsedOwner}" (attempt ${context.attemptNumber}): ${context.error.message}`
`Failed to create token for "${parsedOwner}" (attempt ${error.attemptNumber}): ${error.message}`
);
},
retries: 3,
-44
View File
@@ -1,44 +0,0 @@
// @ts-check
import { spawn } from "node:child_process";
/**
* Wraps a function to automatically enable Node.js proxy support when proxy
* environment variables are detected. If proxy env vars are set but
* `NODE_USE_ENV_PROXY` is not `"1"`, spawns a child process with
* `NODE_USE_ENV_PROXY=1` to enable native proxy support.
*
* @param {() => Promise<void>} run
* @returns {Promise<void>}
*
* @see https://github.com/nodejs/node/blob/4612c793cb9007a91cb3fd82afe518440473826e/lib/internal/process/pre_execution.js#L168-L187
*/
export async function runWithProxy(run) {
const httpProxyEnvVars = [
"https_proxy",
"HTTPS_PROXY",
"http_proxy",
"HTTP_PROXY",
];
const nodeHasProxySupportEnabled = process.env.NODE_USE_ENV_PROXY === "1";
const shouldUseProxy = httpProxyEnvVars.some((v) => process.env[v]);
if (!nodeHasProxySupportEnabled && shouldUseProxy) {
return new Promise((resolve, reject) => {
const child = spawn(process.execPath, process.argv.slice(1), {
env: { ...process.env, NODE_USE_ENV_PROXY: "1" },
stdio: "inherit",
});
child.on("exit", (code) => {
process.exitCode = code;
if (code !== 0) {
reject(new Error(`Child process exited with code ${code}`));
} else {
resolve();
}
});
});
}
return run();
}
+27 -30
View File
@@ -6,7 +6,6 @@ import { createAppAuth } from "@octokit/auth-app";
import { getPermissionsFromInputs } from "./lib/get-permissions-from-inputs.js";
import { main } from "./lib/main.js";
import request from "./lib/request.js";
import { runWithProxy } from "./lib/run-with-proxy.js";
if (!process.env.GITHUB_REPOSITORY) {
throw new Error("GITHUB_REPOSITORY missing, must be set to '<owner>/<repo>'");
@@ -16,34 +15,32 @@ if (!process.env.GITHUB_REPOSITORY_OWNER) {
throw new Error("GITHUB_REPOSITORY_OWNER missing, must be set to '<owner>'");
}
const appId = core.getInput("app-id");
const privateKey = core.getInput("private-key");
const owner = core.getInput("owner");
const repositories = core
.getInput("repositories")
.split(/[\n,]+/)
.map((s) => s.trim())
.filter((x) => x !== "");
const skipTokenRevoke = core.getBooleanInput("skip-token-revoke");
const permissions = getPermissionsFromInputs(process.env);
// Export promise for testing
export default runWithProxy(async () => {
const appId = core.getInput("app-id");
const privateKey = core.getInput("private-key");
const owner = core.getInput("owner");
const repositories = core
.getInput("repositories")
.split(/[\n,]+/)
.map((s) => s.trim())
.filter((x) => x !== "");
const skipTokenRevoke = core.getBooleanInput("skip-token-revoke");
const permissions = getPermissionsFromInputs(process.env);
return main(
appId,
privateKey,
owner,
repositories,
permissions,
core,
createAppAuth,
request,
skipTokenRevoke,
).catch((error) => {
/* c8 ignore next 3 */
console.error(error);
core.setFailed(error.message);
});
export default main(
appId,
privateKey,
owner,
repositories,
permissions,
core,
createAppAuth,
request,
skipTokenRevoke,
).catch((error) => {
/* c8 ignore next 3 */
console.error(error);
core.setFailed(error.message);
});
+745 -514
View File
File diff suppressed because it is too large Load Diff
+11 -12
View File
@@ -2,14 +2,13 @@
"name": "create-github-app-token",
"private": true,
"type": "module",
"version": "2.2.1",
"version": "3.0.0-beta.1",
"description": "GitHub Action for creating a GitHub App Installation Access Token",
"engines": {
"node": ">=20"
"node": ">=24.4.0"
},
"packageManager": "npm@10.9.4",
"scripts": {
"build": "esbuild main.js post.js --bundle --outdir=dist --out-extension:.js=.cjs --platform=node --target=node20.0.0 --packages=bundle",
"build": "esbuild main.js post.js --bundle --outdir=dist --out-extension:.js=.cjs --platform=node --packages=bundle",
"test": "c8 --100 ava tests/index.js",
"coverage": "c8 report --reporter html",
"postcoverage": "open-cli coverage/index.html"
@@ -17,18 +16,18 @@
"license": "MIT",
"dependencies": {
"@actions/core": "^1.11.1",
"@octokit/auth-app": "^8.1.2",
"@octokit/request": "^10.0.3",
"p-retry": "^7.1.0",
"undici": "^7.16.0"
"@octokit/auth-app": "^7.2.1",
"@octokit/request": "^9.2.2",
"p-retry": "^6.2.1",
"undici": "^7.8.0"
},
"devDependencies": {
"@octokit/openapi": "^21.0.0",
"@sinonjs/fake-timers": "^15.0.0",
"@octokit/openapi": "^19.1.0",
"@sinonjs/fake-timers": "^14.0.0",
"ava": "^6.4.1",
"c8": "^10.1.3",
"dotenv": "^17.2.3",
"esbuild": "^0.25.10",
"dotenv": "^17.2.1",
"esbuild": "^0.25.8",
"execa": "^9.6.0",
"open-cli": "^8.0.0",
"yaml": "^2.8.1"
+4 -8
View File
@@ -4,13 +4,9 @@ import core from "@actions/core";
import { post } from "./lib/post.js";
import request from "./lib/request.js";
import { runWithProxy } from "./lib/run-with-proxy.js";
// Export promise for testing
export default runWithProxy(async () => {
return post(core, request).catch((error) => {
/* c8 ignore next 3 */
console.error(error);
core.setFailed(error.message);
});
post(core, request).catch((error) => {
/* c8 ignore next 3 */
console.error(error);
core.setFailed(error.message);
});
+1 -18
View File
@@ -187,14 +187,6 @@
"write"
]
},
"custom_properties_for_organizations": {
"type": "string",
"description": "The level of permission to grant the access token to view and edit custom properties for an organization, when allowed by the property.",
"enum": [
"read",
"write"
]
},
"members": {
"type": "string",
"description": "The level of permission to grant the access token for organization teams and members.",
@@ -229,7 +221,7 @@
},
"organization_custom_properties": {
"type": "string",
"description": "The level of permission to grant the access token for repository custom properties management at the organization level.",
"description": "The level of permission to grant the access token for custom property management.",
"enum": [
"read",
"write",
@@ -392,15 +384,6 @@
"read",
"write"
]
},
"enterprise_custom_properties_for_organizations": {
"type": "string",
"description": "The level of permission to grant the access token for organization custom properties management at the enterprise level.",
"enum": [
"read",
"write",
"admin"
]
}
},
"example": {
+1 -9
View File
@@ -22,15 +22,7 @@ for (const file of testFiles) {
GITHUB_OUTPUT: undefined,
GITHUB_STATE: undefined,
};
const { stderr, stdout } = await execa(
"node",
[
"--experimental-test-module-mocks",
"--disable-warning=ExperimentalWarning",
`tests/${file}`,
],
{ env },
);
const { stderr, stdout } = await execa("node", [`tests/${file}`], { env });
t.snapshot(stderr, "stderr");
t.snapshot(stdout, "stdout");
});
-20
View File
@@ -1,20 +0,0 @@
// Verify that `runWithProxy()` calls the callback directly (no child process)
// when `NODE_USE_ENV_PROXY` is already set to `"1"`, even with proxy env vars set.
import assert from "node:assert";
import { runWithProxy } from "../lib/run-with-proxy.js";
process.env.https_proxy = "http://proxy.example.com";
process.env.NODE_USE_ENV_PROXY = "1";
let callbackCalled = false;
await runWithProxy(async () => {
callbackCalled = true;
});
assert(callbackCalled, "callback was called directly without spawning");
delete process.env.NODE_USE_ENV_PROXY;
delete process.env.https_proxy;
-30
View File
@@ -1,30 +0,0 @@
// Verify that `main.js` rejects when the child process exits with a non-zero code.
import assert from "node:assert";
import { mock } from "node:test";
mock.module("node:child_process", {
namedExports: {
spawn() {
return {
on(event, callback) {
if (event === "exit") callback(1);
},
};
},
},
});
process.env.GITHUB_REPOSITORY = "actions/create-github-app-token";
process.env.GITHUB_REPOSITORY_OWNER = "actions";
process.env.https_proxy = "http://proxy.example.com";
delete process.env.NODE_USE_ENV_PROXY;
const { default: runPromise } = await import("../main.js");
await assert.rejects(runPromise, {
message: "Child process exited with code 1",
});
assert.equal(process.exitCode, 1, "process exit code is 1");
// Reset for other tests
process.exitCode = 0;
-36
View File
@@ -1,36 +0,0 @@
// Verify that `main.js` spawns a child process when a proxy env var is set
// and `NODE_USE_ENV_PROXY` is not set.
import assert from "node:assert";
import { mock } from "node:test";
let spawnArgs;
mock.module("node:child_process", {
namedExports: {
spawn(command, args, options) {
spawnArgs = { command, args, options };
return {
on(event, callback) {
if (event === "exit") callback(0);
},
};
},
},
});
process.env.GITHUB_REPOSITORY = "actions/create-github-app-token";
process.env.GITHUB_REPOSITORY_OWNER = "actions";
process.env.https_proxy = "http://proxy.example.com";
delete process.env.NODE_USE_ENV_PROXY;
const { default: runPromise } = await import("../main.js");
await runPromise;
assert(spawnArgs, "spawn was called");
assert.equal(
spawnArgs.options.env.NODE_USE_ENV_PROXY,
"1",
"NODE_USE_ENV_PROXY is set to '1' in child env",
);
assert.equal(spawnArgs.options.stdio, "inherit", "stdio is inherited");
assert.equal(process.exitCode, 0, "process exit code is 0");
-21
View File
@@ -1,21 +0,0 @@
// Verify that `runWithProxy()` calls the callback directly (no child process)
// when `NODE_USE_ENV_PROXY` is already set to `"1"`, even with proxy env vars set.
// This ensures post.js would also follow the callback path.
import assert from "node:assert";
import { runWithProxy } from "../lib/run-with-proxy.js";
process.env.HTTP_PROXY = "http://proxy.example.com";
process.env.NODE_USE_ENV_PROXY = "1";
let callbackCalled = false;
await runWithProxy(async () => {
callbackCalled = true;
});
assert(callbackCalled, "callback was called directly without spawning");
delete process.env.NODE_USE_ENV_PROXY;
delete process.env.HTTP_PROXY;
-34
View File
@@ -1,34 +0,0 @@
// Verify that `post.js` spawns a child process when a proxy env var is set
// and `NODE_USE_ENV_PROXY` is not set.
import assert from "node:assert";
import { mock } from "node:test";
let spawnArgs;
mock.module("node:child_process", {
namedExports: {
spawn(command, args, options) {
spawnArgs = { command, args, options };
return {
on(event, callback) {
if (event === "exit") callback(0);
},
};
},
},
});
process.env.https_proxy = "http://proxy.example.com";
delete process.env.NODE_USE_ENV_PROXY;
const { default: runPromise } = await import("../post.js");
await runPromise;
assert(spawnArgs, "spawn was called");
assert.equal(
spawnArgs.options.env.NODE_USE_ENV_PROXY,
"1",
"NODE_USE_ENV_PROXY is set to '1' in child env",
);
assert.equal(spawnArgs.options.stdio, "inherit", "stdio is inherited");
assert.equal(process.exitCode, 0, "process exit code is 0");
-50
View File
@@ -82,36 +82,6 @@ Generated by [AVA](https://avajs.dev).
POST /app/installations/123456/access_tokens␊
{"repositories":["create-github-app-token"]}`
## main-proxy-already-enabled.test.js
> stderr
''
> stdout
''
## main-proxy-child-error.test.js
> stderr
''
> stdout
''
## main-proxy-spawns-child.test.js
> stderr
''
> stdout
''
## main-repo-skew.test.js
> stderr
@@ -363,26 +333,6 @@ Generated by [AVA](https://avajs.dev).
POST /app/installations/123456/access_tokens␊
{"repositories":["create-github-app-token"],"permissions":{"issues":"write","pull_requests":"read"}}`
## post-proxy-already-enabled.test.js
> stderr
''
> stdout
''
## post-proxy-spawns-child.test.js
> stderr
''
> stdout
''
## post-revoke-token-fail-response.test.js
> stderr
Binary file not shown.