UNPKG

@pompeii-labs/cli

Version:

Magma CLI

171 lines (170 loc) 5.21 kB
import chalk from "chalk"; import http from "http"; import { exec } from "child_process"; import os from "os"; import { endpoint, exchangeGitHubCode } from "../api.js"; async function findOpenPort() { const server = http.createServer(); return new Promise((resolve, reject) => { server.listen(0, () => { const port = server.address().port; server.close(() => { if (port < 3e3) { resolve(3e3); } else { resolve(port); } }); }); server.on("error", reject); }); } function getAuthCommand(url) { const plt = os.platform(); let command; switch (plt) { case "win32": command = `start "" "${url}"`; break; case "darwin": command = `open "${url}"`; break; case "linux": command = `xdg-open "${url}" || sensible-browser "${url}" || x-www-browser "${url}" || gnome-open "${url}" || kde-open "${url}"`; break; case "freebsd": case "openbsd": case "netbsd": command = `xdg-open "${url}"`; break; case "aix": case "sunos": case "android": console.log(chalk.yellow(`Please open this URL in your browser: ${url}`)); return; default: console.log( chalk.yellow( `Unsupported platform (${os}). Please open this URL in your browser: ${url}` ) ); return; } return command; } async function startAuthFlow() { const PORT = await findOpenPort(); if (PORT < 3e3) { throw new Error("Failed to find an open port"); } return new Promise((resolve, reject) => { const server = http.createServer(async (req, res) => { try { const url = new URL(req.url, `http://localhost:${PORT}`); const code = url.searchParams.get("code"); if (code) { res.writeHead(200, { "Content-Type": "text/html" }); res.end(` <html> <body> <h1>Successfully authenticated!</h1> <p>You can close this window and return to the CLI.</p> <script>window.close()</script> </body> </html> `); server.close(); resolve(code); } else { reject(new Error("Failed to receive grant code")); } } catch (error) { reject(error); server.close(); server.removeAllListeners(); } }); server.listen(PORT, async () => { const redirectUri = endpoint(`/auth/github/callback?callback=http://localhost:${PORT}`); const authUrl = getGitHubOAuthUrl(); authUrl.searchParams.set("redirect_uri", redirectUri); const command = getAuthCommand(authUrl.toString()); try { console.log(chalk.dim("\nWaiting for authentication in browser...")); exec(command); } catch (error) { console.log( chalk.yellow( `Failed to open browser automatically. Please open this URL: ${authUrl}` ) ); } }); setTimeout( () => { server.close(); reject(new Error("Authentication timed out")); }, 5 * 60 * 1e3 ); }); } function getGitHubOAuthUrl() { const state = Math.random().toString(36).substring(7); const clientId = "Ov23livwVBai6JNt3Dy1"; const authUrl = new URL("https://github.com/login/oauth/authorize"); authUrl.searchParams.set("client_id", clientId); authUrl.searchParams.set("scope", "repo admin:repo_hook"); authUrl.searchParams.set("response_type", "code"); authUrl.searchParams.set("state", state); return authUrl; } async function connectGitHub() { console.log(chalk.blue("Press any key to continue to browser, or q to quit...\n")); const keyStdin = process.stdin; keyStdin.setRawMode(true); keyStdin.resume(); keyStdin.setEncoding("utf8"); await new Promise((resolve) => { keyStdin.on("data", (event) => { if (event.toString() === "" || event.toString() === "q") { return process.exit(0); } resolve(); keyStdin.setRawMode(false); keyStdin.pause(); }); }); console.log(chalk.blue("\u{1F511} Opening browser for authentication...")); const code = await startAuthFlow(); console.log(chalk.dim("\u{1F511} Received grant code, exchanging for auth session...")); const tokens = await exchangeGitHubCode(code); console.log(chalk.green(`\u{1F30B} Successfully connected your GitHub account! `)); } function connectCommand(program) { program.command("connect").argument("<account>", "The account to connect").description( "Connect an account to Magma. This will allow automations like agent autodeployments when you make changes to your repo!" ).action(async (account) => { try { switch (account) { case "github": await connectGitHub(); break; default: throw new Error(`Unsupported account: ${account}`); } process.exit(0); } catch (error) { console.error(chalk.red(` \u274C Failed to login: ${error.message}`)); process.exit(1); } }); } export { connectCommand };