@pompeii-labs/cli
Version:
Magma CLI
213 lines (211 loc) • 6.56 kB
JavaScript
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
};