Use direct enterprise installation route

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Parker Brown
2026-03-13 18:14:05 -07:00
parent 77d42ce310
commit 4f9eeddfa7
6 changed files with 51 additions and 80 deletions
+20 -22
View File
@@ -208,37 +208,35 @@ async function getTokenFromRepository(
return { authentication, installationId, appSlug };
}
async function getTokenFromEnterprise(request, auth, enterpriseSlug, permissions) {
// Get all installations and find the enterprise one
// https://docs.github.com/rest/apps/apps#list-installations-for-the-authenticated-app
// Note: Currently we do not have a way to get the installation for an enterprise directly,
// so as a workaround we need to list all installations and filter for the enterprise one.
const response = await request("GET /app/installations", {
request: {
hook: auth.hook,
},
});
async function getTokenFromEnterprise(request, auth, enterpriseSlug, permissions) {
let response;
try {
response = await request("GET /enterprises/{enterprise}/installation", {
enterprise: enterpriseSlug,
request: {
hook: auth.hook,
},
});
} catch (error) {
/* c8 ignore next 8 */
if (error.status === 404) {
throw new Error(
`No enterprise installation found matching the name ${enterpriseSlug}.`
);
}
// Find the enterprise installation
const enterpriseInstallation = response.data.find(
installation => installation.target_type === "Enterprise" &&
installation.account?.slug === enterpriseSlug
);
/* c8 ignore next 3 */
if (!enterpriseInstallation) {
throw new Error(`No enterprise installation found matching the name ${enterpriseSlug}. Available installations: ${response.data.map(i => `${i.target_type}:${i.account?.login || 'N/A'}`).join(', ')}`);
throw error;
}
// Get token for the enterprise installation
const authentication = await auth({
type: "installation",
installationId: enterpriseInstallation.id,
installationId: response.data.id,
permissions,
});
const installationId = enterpriseInstallation.id;
const appSlug = enterpriseInstallation["app_slug"];
const installationId = response.data.id;
const appSlug = response.data["app_slug"];
return { authentication, installationId, appSlug };
}
+8 -8
View File
@@ -18,20 +18,20 @@ POST /api/v3/app/installations/123456/access_tokens
`;
exports[`main-enterprise-installation-not-found.test.js > stderr 1`] = `
Error: No enterprise installation found matching the name test-enterprise. Available installations: Organization:some-org, User:some-user
at getTokenFromEnterprise (file:///Users/parkerbxyz/.copilot/worktrees/create-github-app-token/pr-263/lib/main.js:230:11)
Error: No enterprise installation found matching the name test-enterprise.
at getTokenFromEnterprise (file:///Users/parkerbxyz/.copilot/worktrees/create-github-app-token/pr-263/lib/main.js:223:13)
 at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async pRetry (file:///Users/parkerbxyz/.copilot/worktrees/create-github-app-token/pr-263/node_modules/p-retry/index.js:197:19)
at async main (file:///Users/parkerbxyz/.copilot/worktrees/create-github-app-token/pr-263/lib/main.js:95:52)
at async test (file:///Users/parkerbxyz/.copilot/worktrees/create-github-app-token/pr-263/tests/main.js:111:3)
at async file:///Users/parkerbxyz/.copilot/worktrees/create-github-app-token/pr-263/tests/main-enterprise-installation-not-found.test.js:5:1
at async file:///Users/parkerbxyz/.copilot/worktrees/create-github-app-token/pr-263/tests/main-enterprise-installation-not-found.test.js:4:1
`;
exports[`main-enterprise-installation-not-found.test.js > stdout 1`] = `
Creating enterprise installation token for enterprise "test-enterprise".
Failed to create token for enterprise "test-enterprise" (attempt 1): No enterprise installation found matching the name test-enterprise. Available installations: Organization:some-org, User:some-user
Failed to create token for enterprise "test-enterprise" (attempt 1): No enterprise installation found matching the name test-enterprise.
--- REQUESTS ---
GET /app/installations
GET /enterprises/test-enterprise/installation
`;
exports[`main-enterprise-mutual-exclusivity-both.test.js > stderr 1`] = `
@@ -76,7 +76,7 @@ Creating enterprise installation token for enterprise "test-enterprise".
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::save-state name=expiresAt::2016-07-11T22:14:10Z
--- REQUESTS ---
GET /app/installations
GET /enterprises/test-enterprise/installation
POST /app/installations/123456/access_tokens
null
`;
@@ -93,7 +93,7 @@ Creating enterprise installation token for enterprise "test-enterprise".
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::save-state name=expiresAt::2016-07-11T22:14:10Z
--- REQUESTS ---
GET /app/installations
GET /enterprises/test-enterprise/installation
POST /app/installations/123456/access_tokens
null
`;
@@ -110,7 +110,7 @@ Creating enterprise installation token for enterprise "test-enterprise".
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::save-state name=expiresAt::2016-07-11T22:14:10Z
--- REQUESTS ---
GET /app/installations
GET /enterprises/test-enterprise/installation
POST /app/installations/123456/access_tokens
{"permissions":{"enterprise_organizations":"read","enterprise_people":"write"}}
`;
@@ -1,17 +1,15 @@
import { test } from "./main.js";
// Verify `main` handles when no enterprise installation is found.
await test((mockPool) => {
delete process.env.INPUT_OWNER;
delete process.env.INPUT_REPOSITORIES;
delete process.env.INPUT_REPOSITORIES;
process.env["INPUT_ENTERPRISE-SLUG"] = "test-enterprise";
// Mock the /app/installations endpoint to return only non-enterprise installations
// Mock the enterprise installation endpoint to return no matching installation
mockPool
.intercept({
path: "/app/installations",
path: "/enterprises/test-enterprise/installation",
method: "GET",
headers: {
accept: "application/vnd.github.v3+json",
@@ -20,21 +18,8 @@ await test((mockPool) => {
},
})
.reply(
200,
[
{
id: "111111",
app_slug: "github-actions",
target_type: "Organization",
account: { login: "some-org" }
},
{
id: "222222",
app_slug: "github-actions",
target_type: "User",
account: { login: "some-user" }
}
],
404,
{ message: "Not Found" },
{ headers: { "content-type": "application/json" } }
);
});
+6 -10
View File
@@ -6,12 +6,12 @@ await test((mockPool) => {
delete process.env.INPUT_OWNER;
delete process.env.INPUT_REPOSITORIES;
// Mock the /app/installations endpoint to return an enterprise installation
// Mock the enterprise installation endpoint
const mockInstallationId = "123456";
const mockAppSlug = "github-actions";
mockPool
.intercept({
path: "/app/installations",
path: "/enterprises/test-enterprise/installation",
method: "GET",
headers: {
accept: "application/vnd.github.v3+json",
@@ -21,14 +21,10 @@ await test((mockPool) => {
})
.reply(
200,
[
{
id: mockInstallationId,
app_slug: mockAppSlug,
target_type: "Enterprise",
account: { login: "test-enterprise", slug: "test-enterprise" }
}
],
{
id: mockInstallationId,
app_slug: mockAppSlug,
},
{ headers: { "content-type": "application/json" } }
);
});
+6 -10
View File
@@ -6,12 +6,12 @@ await test((mockPool) => {
delete process.env.INPUT_OWNER;
delete process.env.INPUT_REPOSITORIES;
// Mock the /app/installations endpoint to return an enterprise installation
// Mock the enterprise installation endpoint
const mockInstallationId = "123456";
const mockAppSlug = "github-actions";
mockPool
.intercept({
path: "/app/installations",
path: "/enterprises/test-enterprise/installation",
method: "GET",
headers: {
accept: "application/vnd.github.v3+json",
@@ -21,14 +21,10 @@ await test((mockPool) => {
})
.reply(
200,
[
{
id: mockInstallationId,
app_slug: mockAppSlug,
target_type: "Enterprise",
account: { login: "test-enterprise", slug: "test-enterprise" }
}
],
{
id: mockInstallationId,
app_slug: mockAppSlug,
},
{ headers: { "content-type": "application/json" } }
);
});
@@ -8,12 +8,12 @@ await test((mockPool) => {
process.env["INPUT_PERMISSION-ENTERPRISE-ORGANIZATIONS"] = "read";
process.env["INPUT_PERMISSION-ENTERPRISE-PEOPLE"] = "write";
// Mock the /app/installations endpoint to return an enterprise installation
// Mock the enterprise installation endpoint
const mockInstallationId = "123456";
const mockAppSlug = "github-actions";
mockPool
.intercept({
path: "/app/installations",
path: "/enterprises/test-enterprise/installation",
method: "GET",
headers: {
accept: "application/vnd.github.v3+json",
@@ -23,14 +23,10 @@ await test((mockPool) => {
})
.reply(
200,
[
{
id: mockInstallationId,
app_slug: mockAppSlug,
target_type: "Enterprise",
account: { login: "test-enterprise", slug: "test-enterprise" }
}
],
{
id: mockInstallationId,
app_slug: mockAppSlug,
},
{ headers: { "content-type": "application/json" } }
);
});