diff --git a/.vscode/settings.json b/.vscode/settings.json index d8037ad..f8fb691 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -59,6 +59,8 @@ "codeandstuff", "codegen", "codesnap", + "CODESPACE", + "CODESPACES", "codetour", "commitlint", "configit", @@ -105,22 +107,22 @@ ] }, "workbench.colorCustomizations": { - "activityBar.activeBackground": "#d75571", - "activityBar.background": "#d75571", + "activityBar.activeBackground": "#b42a4a", + "activityBar.background": "#b42a4a", "activityBar.foreground": "#e7e7e7", "activityBar.inactiveForeground": "#e7e7e799", - "activityBarBadge.background": "#68d44a", - "activityBarBadge.foreground": "#15202b", + "activityBarBadge.background": "#15340c", + "activityBarBadge.foreground": "#e7e7e7", "commandCenter.border": "#e7e7e799", - "sash.hoverBorder": "#d75571", - "statusBar.background": "#ca2f51", + "sash.hoverBorder": "#b42a4a", + "statusBar.background": "#8b2039", "statusBar.foreground": "#e7e7e7", - "statusBarItem.hoverBackground": "#d75571", - "statusBarItem.remoteBackground": "#ca2f51", + "statusBarItem.hoverBackground": "#b42a4a", + "statusBarItem.remoteBackground": "#8b2039", "statusBarItem.remoteForeground": "#e7e7e7", - "titleBar.activeBackground": "#ca2f51", + "titleBar.activeBackground": "#8b2039", "titleBar.activeForeground": "#e7e7e7", - "titleBar.inactiveBackground": "#ca2f5199", + "titleBar.inactiveBackground": "#8b203999", "titleBar.inactiveForeground": "#e7e7e799" }, "todohighlight.isEnable": true, diff --git a/package.json b/package.json index abc8f02..bfb311d 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,7 @@ "sdk:node": "swagger-codegen generate -i http://localhost:3003/api/docs-json -l typescript-axios -o ./sdks/node --additional-properties npmName=@kibibit/achievibit-server-node-sdk && npm run post:sdk:node", "sdk:angular": "swagger-codegen generate -i http://localhost:3003/api/docs-json -l typescript-angular -o ./sdks/angular -c ./angular-sdk-options.json", "sdk:angular:build": "ng-packagr -p ./sdks/angular/ng-package.json", - "create-github-oauth": "ts-node ./scripts/create-github-oauth.script.ts", - "create-github-app": "ts-node ./scripts/create-github-app.script.ts" + "create:github": "ts-node ./scripts/create-github.script.ts" }, "author": "thatkookooguy ", "license": "MIT", @@ -36,6 +35,7 @@ "@typescript-eslint/eslint-plugin": "^8.6.0", "@typescript-eslint/parser": "^8.6.0", "angular-eslint": "^18.3.1", + "colors": "^1.4.0", "eslint": "^8.57.1", "eslint-plugin-attribute-formatting": "link:plugins/eslint-plugin-attribute-formatting", "eslint-plugin-import": "^2.30.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2eed940..a2cac8d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,6 +50,9 @@ importers: angular-eslint: specifier: ^18.3.1 version: 18.3.1(@angular-devkit/core@17.3.10(chokidar@3.6.0))(@angular-devkit/schematics@17.3.10(chokidar@3.6.0))(@typescript-eslint/utils@8.8.1(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript-eslint@8.8.1(eslint@8.57.1)(typescript@5.6.3))(typescript@5.6.3) + colors: + specifier: ^1.4.0 + version: 1.4.0 eslint: specifier: ^8.57.1 version: 8.57.1 diff --git a/scripts/create-github-app.script.ts b/scripts/create-github-app.script.ts deleted file mode 100644 index 4875ead..0000000 --- a/scripts/create-github-app.script.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { resolve } from 'path'; - -import { ensureDirSync, writeFileSync } from 'fs-extra'; -import waitForUserInput from 'wait-for-user-input'; -import { chromium } from '@playwright/test'; - -(async () => { - const username = 'thatkookooguy'; - const baseUrl = 'http://localhost:10101'; - const homepageUrl = baseUrl; - const postInstallUrl = `${ baseUrl }/api/me/github/post-install`; - const webhookUrl = `${ baseUrl }/api/webhooks/github`; - // tell user that github will open and they need to login. - // after login, they need to press enter to continue in the terminal - console.log('After pressing enter, a browser window will open to github.com'); - console.log('Please login and press enter again to continue'); - - await waitForUserInput('press any key to continue'); - - // open browser - const browser = await chromium.launch({ - headless: false, - slowMo: 500, - // Required for running inside containers - args: [ '--no-sandbox', '--disable-setuid-sandbox' ] - }); - const page = await browser.newPage(); - page.setDefaultTimeout(240000); - - await page.goto('https://github.com/login'); - - await waitForUserInput('press any key to continue'); - - await page.goto('https://github.com/'); - await page.getByLabel('Open user navigation menu').click(); - await page.getByLabel('Settings').click(); - await page.getByRole('link', { name: 'Developer settings' }).click(); - await page.getByRole('link', { name: 'GitHub Apps' }).click(); - await page.getByRole('link', { name: 'New GitHub App' }).click(); - - await page.getByLabel('GitHub App name').fill(`${ username }-achievibit-dev-app`); - await page.getByLabel('Homepage URL').fill(homepageUrl); - await page.getByLabel('Setup URL (optional)').fill(postInstallUrl); - await page.getByLabel('Webhook URL').fill(webhookUrl); - - // permissions - await page.getByRole('button', { name: 'Repository permissions' }).click(); - await page.locator('li').filter({ hasText: 'Pull requests' }).getByRole('button').click(); - await page.getByRole('menuitemradio', { name: 'Read-only' }).click(); - - await page.getByRole('checkbox', { name: 'Installation target Info A' }).check(); - await page.getByRole('checkbox', { name: 'Meta Info When this App is' }).check(); - await page.getByLabel('Pull request Pull request').check(); - await page.getByLabel('Pull request review Pull').check(); - await page.getByLabel('Pull request review comment').check(); - await page.getByLabel('Pull request review thread A').check(); - await page.getByLabel('Any account').check(); - - // click on create github app - // GET client id from the page - const clientId = ''; - // click on generate new client secret - // GET client secret from the page - const clientSecret = ''; - // click on update application - // create a private key and download it - // save the private key to a file - const privateKey = 'test'; - - await browser.close(); - - console.log('clientId:', clientId); - console.log('clientSecret:', clientSecret); - console.log('postInstallUrl:', postInstallUrl); - console.log('webhookUrl:', webhookUrl); - // make sure the keys directory exists under the project root - ensureDirSync(resolve(__dirname, '..', 'keys')); - writeFileSync(resolve(__dirname, '..', 'keys', 'private-key.pem'), privateKey); - console.log('private key saved to private-key.pem'); - - process.exit(0); -})(); diff --git a/scripts/create-github-oauth.script.ts b/scripts/create-github-oauth.script.ts deleted file mode 100644 index 3b65d23..0000000 --- a/scripts/create-github-oauth.script.ts +++ /dev/null @@ -1,54 +0,0 @@ -import waitForUserInput from 'wait-for-user-input'; -import { chromium } from '@playwright/test'; - -(async () => { - const username = 'thatkookooguy'; - const baseUrl = 'http://localhost:10101'; - const homepageUrl = baseUrl; - const callbackUrl = `${ baseUrl }/api/auth/github/callback`; - // tell user that github will open and they need to login. - // after login, they need to press enter to continue in the terminal - console.log('After pressing enter, a browser window will open to github.com'); - console.log('Please login and press enter again to continue'); - - await waitForUserInput('press any key to continue'); - - // open browser - const browser = await chromium.launch({ - headless: false, - slowMo: 1000, - // Required for running inside containers - args: [ '--no-sandbox', '--disable-setuid-sandbox' ] - }); - const page = await browser.newPage(); - page.setDefaultTimeout(240000); - - await page.goto('https://github.com/login'); - - await waitForUserInput('press any key to continue'); - - await page.goto('https://github.com/'); - await page.getByLabel('Open user navigation menu').click(); - await page.getByLabel('Settings').click(); - await page.getByRole('link', { name: 'Developer settings' }).click(); - await page.getByRole('link', { name: 'OAuth Apps' }).click(); - await page.getByRole('link', { name: 'New OAuth App' }).click(); - await page.getByLabel('Application name').click(); - await page.getByLabel('Application name').fill(`${ username }-achievibit-dev-oauth`); - await page.getByLabel('Homepage URL').click(); - await page.getByLabel('Homepage URL').fill(homepageUrl); - await page.getByLabel('Authorization callback URL').fill(callbackUrl); - await page.getByRole('button', { name: 'Register application' }).click(); - const clientId = await page.locator('code').textContent(); - await page.getByRole('button', { name: 'Generate a new client secret' }).click(); - const clientSecret = await page.locator('code#new-oauth-token').textContent(); - await page.getByRole('button', { name: 'Update application' }).click(); - - await browser.close(); - - console.log('clientId:', clientId); - console.log('clientSecret:', clientSecret); - console.log('callbackUrl:', callbackUrl); - - process.exit(0); -})(); diff --git a/scripts/create-github.script.ts b/scripts/create-github.script.ts new file mode 100644 index 0000000..ce1720a --- /dev/null +++ b/scripts/create-github.script.ts @@ -0,0 +1,176 @@ +import { relative, resolve } from 'path'; + +import { green } from 'colors'; +import { ensureDirSync } from 'fs-extra'; +import inquirer from 'inquirer'; +import { chromium, Page } from '@playwright/test'; + +(async () => { + const questions: any = [ + { + type: 'input', + name: 'username', + message: 'Enter your github username' + }, + { + type: 'password', + name: 'password', + message: 'Enter your github password' + } + ]; + + const githubAnswers = await inquirer.prompt(questions); + + const baseUrl = 'http://localhost:10101'; + const homepageUrl = baseUrl; + const callbackUrl = `${ baseUrl }/api/auth/github/callback`; + const postInstallUrl = `${ baseUrl }/api/me/github/post-install`; + const webhookUrl = getWebhookUrl(); + // tell user that github will open and they need to login. + // after login, they need to press enter to continue in the terminal + // console.log('After pressing enter, a browser window will open to github.com'); + // console.log('Please login and press enter again to continue'); + + // await waitForUserInput('press any key to continue'); + + console.log('Opening browser to github.com'); + // open browser + const browser = await chromium.launch({ + headless: true, + // headless: false, + slowMo: 500, + // Required for running inside containers + args: [ '--no-sandbox', '--disable-setuid-sandbox' ] + }); + const page = await browser.newPage(); + page.setDefaultTimeout(120000); + + await page.goto('https://github.com/login'); + + await page.getByLabel('Username or email address').fill(githubAnswers.username); + await page.getByLabel('Password').fill((githubAnswers as any).password); + await page.getByRole('button', { name: 'Sign in', exact: true }).click(); + await page.getByRole('link', { name: 'Send a code via SMS' }).click(); + await page.getByRole('button', { name: 'Send SMS' }).click(); + + const answers = await inquirer.prompt({ + type: 'input', + name: 'smsCode', + message: 'Enter the SMS code sent to your phone' + }); + await page.getByPlaceholder('XXXXXX').fill(answers.smsCode); + + await page.waitForTimeout(1000); + + try { + await createGitHubOauthApp(page, githubAnswers, homepageUrl, callbackUrl); + + await createGitHubApp(page, githubAnswers, homepageUrl, postInstallUrl, webhookUrl); + } catch (error) { + // save screenshot for debugging + await page.screenshot({ path: 'error.png' }); + } + + await browser.close(); + process.exit(0); +})(); + +async function createGitHubOauthApp( + page: Page, + githubAnswers: { username: string }, + homepageUrl: string, + callbackUrl: string +) { + await page.goto('https://github.com/settings/developers'); + + await page.getByRole('link', { name: 'New OAuth App' }).click(); + await page.getByLabel('Application name').click(); + const oauthAppName = `${ githubAnswers.username }-achievibit-dev-oauth`; + await page.getByLabel('Application name').fill(oauthAppName); + await page.getByLabel('Homepage URL').click(); + await page.getByLabel('Homepage URL').fill(homepageUrl); + await page.getByLabel('Authorization callback URL').fill(callbackUrl); + await page.getByRole('button', { name: 'Register application' }).click(); + const clientId = await page.locator('code').textContent(); + await page.getByRole('button', { name: 'Generate a new client secret' }).click(); + const clientSecret = await page.locator('code#new-oauth-token').textContent(); + await page.getByRole('button', { name: 'Update application' }).click(); + + console.log(green('== GitHub OAuth App Details ==')); + console.log('oauthAppName:', oauthAppName); + console.log('clientId:', clientId); + console.log('clientSecret:', clientSecret); + console.log('callbackUrl:', callbackUrl); + console.log('Edit URL:', 'https://github.com/settings/developers'); +} + +async function createGitHubApp( + page: Page, + githubAnswers: { username: string }, + homepageUrl: string, + postInstallUrl: string, + webhookUrl: string +) { + await page.goto('https://github.com/settings/apps'); + await page.getByRole('link', { name: 'New GitHub App' }).click(); + + const githubAppName = `${ githubAnswers.username }-achievibit-dev-app`; + await page.getByLabel('GitHub App name').fill(githubAppName); + await page.getByLabel('Homepage URL').fill(homepageUrl); + await page.getByLabel('Setup URL (optional)').fill(postInstallUrl); + await page.getByLabel('Webhook URL').fill(webhookUrl); + + // permissions + await page.getByRole('button', { name: 'Repository permissions' }).click(); + await page.locator('li').filter({ hasText: 'Pull requests' }).getByRole('button').click(); + await page.getByRole('menuitemradio', { name: 'Read-only' }).click(); + + await page.getByRole('checkbox', { name: 'Installation target Info A' }).check(); + await page.getByRole('checkbox', { name: 'Meta Info When this App is' }).check(); + await page.getByLabel('Pull request Pull request').check(); + await page.getByLabel('Pull request review Pull').check(); + await page.getByLabel('Pull request review comment').check(); + await page.getByLabel('Pull request review thread A').check(); + await page.getByLabel('Any account').check(); + + await page.getByRole('button', { name: 'Create GitHub App' }).click(); + + const githubAppId = await page.locator('p').filter({ hasText: 'App ID:' }).textContent(); + const githubAppClientId = await page.locator('p').filter({ hasText: 'Client ID:' }).textContent(); + await page.getByRole('button', { name: 'Generate a new client secret' }).click(); + const githubAppClientSecret = await page.locator('code#new-oauth-token').textContent(); + + await page.getByRole('button', { name: 'Save changes' }).click(); + + const downloadPromise = page.waitForEvent('download'); + await page.getByRole('button', { name: 'Generate a private key' }).click(); + const download = await downloadPromise; + + console.log(green('== GitHub App Details ==')); + console.log('githubAppName:', githubAppName); + console.log('githubAppId:', githubAppId); + console.log('githubAppClientId:', githubAppClientId); + console.log('githubAppClientSecret:', githubAppClientSecret); + console.log('postInstallUrl:', postInstallUrl); + console.log('webhookUrl:', webhookUrl); + console.log('Edit URL:', `https://github.com/settings/apps/${ githubAppName }`); + // make sure the keys directory exists under the project root + const folderPath = resolve(__dirname, '..', 'server', 'keys'); + const filePath = resolve(folderPath, 'private-key.pem'); + ensureDirSync(folderPath); + await download.saveAs(filePath); + console.log(`private key saved to ${ relative(process.cwd(), filePath) }`); +} + +function getWebhookUrl() { + const isRunningInGitHubCodespace = process.env.CODESPACES === 'true'; + const codespaceName = process.env.CODESPACE_NAME; + const codespacePortForwardingDomain = process.env.GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN; + const port = '10101'; + + if (isRunningInGitHubCodespace) { + return `https://${ codespaceName }-${ port }.${ codespacePortForwardingDomain }`; + } + + throw new Error('Webhook URL is not supported outside of GitHub Codespaces'); +} diff --git a/server/src/systems/github/github.service.ts b/server/src/systems/github/github.service.ts index 14785b2..24b324c 100644 --- a/server/src/systems/github/github.service.ts +++ b/server/src/systems/github/github.service.ts @@ -27,7 +27,7 @@ export class GithubService { ) { // Load your private key from the .pem file this.privateKey = readFileSync( - join(configService.appRoot, 'achievibit-beta.private-key.pem'), + join(configService.appRoot, 'keys', 'private-key.pem'), 'utf8' ); }