Skip to content

Commit dd76de9

Browse files
committed
feat: add register-github-app to dev/scripts; add its deps to devDeps
1 parent ccac544 commit dd76de9

4 files changed

Lines changed: 525 additions & 51 deletions

File tree

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { request } from "@octokit/request";
2+
import { components } from "@octokit/openapi-types";
3+
4+
export default function registerGitHubApp(
5+
manifest?: Manifest,
6+
metaOptions?: MetaOptions,
7+
): Promise<AppCredentials>;
8+
9+
export type Manifest = {
10+
/** Organization login. If not set, the app will be registered on the user's account */
11+
org?: string;
12+
/** The name of the GitHub App. */
13+
name?: string;
14+
/** A description of the GitHub App. */
15+
description?: string;
16+
/** The homepage of your GitHub App. If `org` is set, `homepageUrl` will default to `https://github.com/{org}`, otherwise to `https://github.com` */
17+
url: string;
18+
/** The configuration of the GitHub App's webhook. */
19+
hook_attributes?: {
20+
/*
21+
* __Required.__ The URL of the server that will receive the webhook POST requests.
22+
*/
23+
url: string;
24+
/*
25+
* Deliver event details when this hook is triggered, defaults to true.
26+
*/
27+
active?: boolean;
28+
};
29+
/** The full URL to redirect to after a user initiates the registration of a GitHub App from a manifest. */
30+
redirect_url?: string;
31+
/** A full URL to redirect to after someone authorizes an installation. You can provide up to 10 callback URLs. */
32+
callback_urls?: string[];
33+
/** A full URL to redirect users to after they install your GitHub App if additional setup is required. */
34+
setup_url?: string;
35+
/** Set to `true` when your GitHub App is available to the public or `false` when it is only accessible to the owner of the app. */
36+
public?: boolean;
37+
/** The list of events the GitHub App subscribes to. */
38+
default_events?: string[];
39+
/** The set of permissions needed by the GitHub App. The format of the object uses the permission name for the key (for example, `issues`) and the access type for the value (for example, `write`). */
40+
default_permissions?: Record<string, string>;
41+
/** Set to `true` to request the user to authorize the GitHub App, after the GitHub App is installed. */
42+
request_oauth_on_install?: boolean;
43+
/** Set to `true` to redirect users to the setup_url after they update your GitHub App installation. */
44+
setup_on_update?: boolean;
45+
};
46+
47+
export type MetaOptions = {
48+
/** Port number for local server. Defaults to a random available port number */
49+
port?: number;
50+
51+
/** GitHub website URL. Defaults to `https://github.com` */
52+
githubUrl?: string;
53+
54+
/** GitHub API base URL. Defaults to `https://api.github.com` */
55+
githubApiUrl?: string;
56+
57+
/** message to be used for logging. Defaults to `console.log` */
58+
log?: Console["log"];
59+
60+
/** custom `octokit.request` method */
61+
request?: typeof request;
62+
};
63+
64+
export type AppCredentials = components["schemas"]["integration"];
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/**
2+
* Register a GitHub App using the manifest flow
3+
* @see https://github.com/gr2m/register-github-app
4+
*
5+
* @ts-check
6+
*/
7+
8+
import { createServer } from "node:http";
9+
10+
import { request as octokitRequest } from "@octokit/request";
11+
12+
const DEFAULT_MANIFEST = {
13+
url: "https://github.com",
14+
};
15+
const DEFAULT_META_OPTIONS = {
16+
githubUrl: "https://github.com",
17+
githubApiUrl: "https://api.github.com",
18+
log: console.log.bind(console),
19+
};
20+
21+
/**
22+
* @param {import('./index').Manifest} manifest
23+
* @param {import('./index').MetaOptions} metaOptions
24+
* @returns {Promise<import('./index').AppCredentials>}
25+
*/
26+
export default async function registerGitHubApp(
27+
{ org, ...manifest } = DEFAULT_MANIFEST,
28+
metaOptions = DEFAULT_META_OPTIONS,
29+
) {
30+
// defaults
31+
manifest.url ||= org ? `https://github.com/${org}` : "https://github.com";
32+
manifest.public ||= false;
33+
manifest.setup_on_update ||= false;
34+
manifest.request_oauth_on_install ||= false;
35+
36+
metaOptions.githubUrl ||= DEFAULT_META_OPTIONS.githubUrl;
37+
metaOptions.githubApiUrl ||= DEFAULT_META_OPTIONS.githubApiUrl;
38+
39+
const log = metaOptions.log || DEFAULT_META_OPTIONS.log;
40+
41+
const manifestRequest = (metaOptions.request || octokitRequest).defaults({
42+
baseUrl: metaOptions.githubApiUrl,
43+
method: "POST",
44+
url: "/app-manifests/{code}/conversions",
45+
});
46+
47+
return new Promise((resolve, reject) => {
48+
// start the server at an available port
49+
const server = createServer();
50+
51+
server.listen(metaOptions.port || 0);
52+
53+
const port =
54+
metaOptions.port ||
55+
// @ts-expect-error - I have yet to see a usecase when `server.address()` can be a string
56+
server.address().port;
57+
58+
log(`Open http://localhost:${port}`);
59+
60+
server.on("error", (error) => {
61+
reject(new Error("A server error occured", { cause: error }));
62+
server.close();
63+
});
64+
65+
// Listen to the request event
66+
server.on("request", async (request, response) => {
67+
const url = new URL(
68+
request.url,
69+
`http://localhost:${port}`,
70+
);
71+
72+
const code = url.searchParams.get("code");
73+
if (code) {
74+
const { data: appCredentials } = await manifestRequest({
75+
code,
76+
});
77+
78+
console.log(appCredentials);
79+
80+
response.writeHead(200, { "Content-Type": "text/html" });
81+
response.end(`
82+
<meta charset="utf-8">
83+
<h1>GitHub App registered successfully</h1>
84+
<p>
85+
Now follow this steps below..
86+
<ul>
87+
<li>
88+
Create a new github repository with name "jargons.dev-test" at <a target="_blank" href="https://github.com/new">https://github.com/new</a>
89+
</li>
90+
<li>
91+
Copy and paste the repo name in full as value to the "PUBLIC_PROJECT_REPO" in the newly created .env;
92+
<br>
93+
Example: (assuming you chose the suggested name)
94+
<br>
95+
<code>
96+
PUBLIC_PROJECT_REPO="${appCredentials.owner.login}/jargons.dev-test"
97+
</code>
98+
</li>
99+
<li>
100+
Then follow this link to install the app on the repo <a href="${appCredentials.html_url}">${appCredentials.html_url}</a>.
101+
</li>
102+
</ul>
103+
</p>
104+
`);
105+
106+
resolve(appCredentials);
107+
108+
server.close();
109+
return;
110+
}
111+
112+
const registerUrl = org
113+
? `${metaOptions.githubUrl}/organizations/${org}/settings/apps/new`
114+
: `${metaOptions.githubUrl}/settings/apps/new`;
115+
const manifestJson = JSON.stringify({
116+
redirect_url: `http://localhost:${port}`,
117+
...manifest,
118+
});
119+
120+
response.writeHead(200, { "Content-Type": "text/html" });
121+
response.end(`
122+
<meta charset="utf-8">
123+
<h1>Registering GitHub App</h1>
124+
<form action="${registerUrl}" method="post">
125+
<input type="hidden" name="manifest" id="manifest">
126+
<input type="submit" value="Submit" id="submit">
127+
</form>
128+
129+
<p>
130+
You will be redirected automatically …
131+
</p>
132+
133+
<script>
134+
const input = document.getElementById("manifest")
135+
input.value = \`${manifestJson}\`
136+
137+
document.getElementById("submit").click()
138+
</script>
139+
`);
140+
});
141+
});
142+
}

0 commit comments

Comments
 (0)