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