UNPKG

@pompeii-labs/cli

Version:

Magma CLI

213 lines (211 loc) 6.56 kB
import chalk from "chalk"; import http from "http"; import { exec } from "child_process"; import { exchangeCode, FRONTEND, getOrgs } from "../api.js"; import { storeAuthSession } from "../auth.js"; import os from "os"; import { select } from "@inquirer/prompts"; 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> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Inter', sans-serif; background-color: #100f0f; color: #f0f0f0; display: flex; flex-direction: column; gap: 1rem; align-items: center; justify-content: center; width: 100vw; height: 100vh; } </style> </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 state = Math.random().toString(36).substring(7); const authUrl = new URL(`${FRONTEND}/auth`); authUrl.searchParams.set("callback", `http://localhost:${PORT}`); authUrl.searchParams.set("state", state); 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 ); }); } async function login() { 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(); keyStdin.removeAllListeners(); }); }); 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 exchangeCode(code); console.log(chalk.dim("\u{1F511} Successfully received auth session")); await storeAuthSession(tokens); let selectedOrg = void 0; const orgs = await getOrgs(); if (!orgs || orgs.length === 0) { console.error( chalk.red( "No organizations found for this account. Please create an organization on magmadeploy.com and try again." ) ); process.exit(1); } if (orgs.length === 1) { selectedOrg = orgs[0]; } else { const orgId = await select({ message: "Select an organization to use:", choices: orgs.map((org) => ({ name: org.name, value: org.id })), pageSize: 10 }); selectedOrg = orgs.find((org) => org.id === orgId); } if (!selectedOrg) { console.error(chalk.red("Failed to select an organization. Please try again.")); process.exit(1); } await storeAuthSession({ ...tokens, org: selectedOrg }); console.log( chalk.green( `\u{1F30B} Successfully logged in as ${chalk.bold(tokens.email)} in ${chalk.bold( selectedOrg.name )} ` ) ); } function loginCommand(program) { program.command("login").description("Authenticate the CLI with your magmadeploy.com account").action(async () => { try { await login(); process.exit(0); } catch (error) { console.error(chalk.red(` \u274C Failed to login: ${error.message}`)); process.exit(1); } }); } export { loginCommand };