gitmt
Version:
CLI tool to manage multiple git accounts and SSH keys
262 lines (223 loc) • 7.2 kB
JavaScript
const { program } = require("commander");
const { existsSync, readFileSync, writeFileSync } = require("fs");
const { homedir } = require("os");
const path = require("path");
const simpleGit = require("simple-git");
const { confirm } = require("@inquirer/prompts");
const fs = require("fs-extra");
// Constants
const CONFIG_DIR = path.join(homedir(), ".gitmt");
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
const SSH_DIR = path.join(homedir(), ".ssh");
// Ensure config directory exists
if (!existsSync(CONFIG_DIR)) {
fs.mkdirSync(CONFIG_DIR, { recursive: true });
}
// Load or create config
let config = { users: [], activeUser: null };
if (existsSync(CONFIG_FILE)) {
config = JSON.parse(readFileSync(CONFIG_FILE, "utf8"));
}
// Helper to manage SSH config
const SSH_CONFIG_FILE = path.join(SSH_DIR, "config");
const updateSSHConfig = (alias, sshKeyPath, isRemove = false) => {
let sshConfig = "";
if (existsSync(SSH_CONFIG_FILE)) {
sshConfig = readFileSync(SSH_CONFIG_FILE, "utf8");
}
if (isRemove) {
// Remove existing config block
const regex = new RegExp(
`\\nHost github.com-${alias}[\\s\\S]*?(?=\\n\\w|$)`,
"g"
);
sshConfig = sshConfig.replace(regex, "");
} else {
// Add new config block
const newConfig = `
Host github.com-${alias}
HostName github.com
User git
IdentityFile ${sshKeyPath}
IdentitiesOnly yes
`;
sshConfig += newConfig;
}
writeFileSync(SSH_CONFIG_FILE, sshConfig.trim() + "\n");
};
// Save config
const saveConfig = () => {
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
};
// Helper to set git config
const setGitConfig = async (name, email) => {
const git = simpleGit();
await git.addConfig("user.name", name, false, "global");
await git.addConfig("user.email", email, false, "global");
};
// Commands
program
.name("gitmt")
.description("CLI tool to manage multiple git accounts")
.version("1.0.0");
program
.command("add")
.description("Add a new git user")
.requiredOption("-n, --name <name>", "Git user name")
.requiredOption("-e, --email <email>", "Git user email")
.requiredOption("-a, --alias <alias>", "GitHub alias (used for SSH config)")
.action(async (options) => {
const sshKeyPath = path.join(
SSH_DIR,
`id_rsa_gitmt_${options.alias.toLowerCase()}`
);
// Generate SSH key if it doesn't exist
if (!existsSync(sshKeyPath)) {
const generateKey = await confirm({
message: `Would you like to generate a new SSH key for ${options.name}?`,
default: true,
});
if (generateKey) {
const { execSync } = require("child_process");
execSync(
`ssh-keygen -t rsa -b 4096 -C "${options.email}" -f "${sshKeyPath}" -N ""`
);
console.log(`SSH key generated at: ${sshKeyPath}`);
updateSSHConfig(options.alias, sshKeyPath);
console.log(
`SSH config updated. You can now clone repositories using:`
);
console.log(
`git clone git@github.com-${options.alias}:username/repo.git`
);
}
}
const userId = config.users.length + 1;
config.users.push({
id: userId,
name: options.name,
email: options.email,
alias: options.alias,
sshKeyPath,
});
if (!config.activeUser) {
config.activeUser = userId;
await setGitConfig(options.name, options.email);
}
updateSSHConfig(options.name, sshKeyPath);
saveConfig();
console.log(`Added user ${options.name} (ID: ${userId})`);
});
program
.command("current")
.description("Show current active git user")
.action(() => {
if (!config.activeUser) {
console.log("No active git user");
return;
}
const user = config.users.find((u) => u.id === config.activeUser);
console.log(
`Current active user: ${user.name} <${user.email}> (ID: ${user.id})`
);
});
program
.command("remove")
.description("Remove a git user")
.argument("<id>", "User ID to remove")
.action(async (id) => {
const userId = parseInt(id);
const userIndex = config.users.findIndex((u) => u.id === userId);
if (userIndex === -1) {
console.log(`No user found with ID ${userId}`);
return;
}
const user = config.users[userIndex];
const sshKeyExists =
existsSync(user.sshKeyPath) || existsSync(`${user.sshKeyPath}.pub`);
if (sshKeyExists) {
const removeSSH = await confirm({
message: `Do you want to remove the SSH keys for ${user.name}?`,
default: false,
});
if (removeSSH) {
try {
if (existsSync(user.sshKeyPath)) {
fs.unlinkSync(user.sshKeyPath);
}
if (existsSync(`${user.sshKeyPath}.pub`)) {
fs.unlinkSync(`${user.sshKeyPath}.pub`);
}
updateSSHConfig(user.alias, "", true); // Remove from SSH config
console.log(`SSH keys and config removed for ${user.name}`);
} catch (error) {
console.error(`Error removing SSH keys: ${error.message}`);
}
}
}
config.users.splice(userIndex, 1);
if (config.activeUser === userId) {
config.activeUser = null;
}
updateSSHConfig(user.name, user.sshKeyPath, true);
saveConfig();
console.log(`Removed user ${user.name} (ID: ${userId})`);
});
program
.command("change")
.description("Switch to a different git user")
.argument("<id>", "User ID to switch to")
.action(async (id, options) => {
const userId = parseInt(id);
const user = config.users.find((u) => u.id === userId);
if (!user) {
console.log(`No user found with ID ${userId}`);
return;
}
config.activeUser = userId;
await setGitConfig(user.name, user.email);
saveConfig();
console.log(`Switched to user: ${user.name} <${user.email}>`);
});
program
.command("list")
.description("List all git users")
.action(() => {
if (config.users.length === 0) {
console.log("No users configured");
return;
}
console.log("Configured git users:");
config.users.forEach((user) => {
const activeMarker = user.id === config.activeUser ? "(active)" : "";
console.log(
`${user.id}. ${user.name} <${user.email}> [${user.alias}] ${activeMarker}`
);
console.log(
` Clone URL format: git clone git@github.com-${user.alias}:username/repo.git`
);
});
});
program
.command("key")
.description("Show public SSH key for a user")
.argument("<id>", "User ID to show key for")
.action((id) => {
const userId = parseInt(id);
const user = config.users.find((u) => u.id === userId);
// TODO copy the public key to clipboard
if (!user) {
console.log(`No user found with ID ${userId}`);
return;
}
const publicKeyPath = `${user.sshKeyPath}.pub`;
if (!existsSync(publicKeyPath)) {
console.log(`No SSH key found for user ${user.name}`);
return;
}
const publicKey = readFileSync(publicKeyPath, "utf8");
console.log(`Public SSH key for ${user.name}:`);
console.log(publicKey);
});
program.parse();