gitshift 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitshift",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "GitHub Account Switcher CLI",
5
5
  "main": "server.js",
6
6
  "bin": {
@@ -16,6 +16,7 @@
16
16
  "type": "module",
17
17
  "dependencies": {
18
18
  "@inquirer/prompts": "^8.5.1",
19
+ "axios": "^1.16.1",
19
20
  "chalk": "^5.6.2",
20
21
  "commander": "^15.0.0",
21
22
  "conf": "^15.1.0",
@@ -23,4 +24,4 @@
23
24
  "fs-extra": "^11.3.5",
24
25
  "ora": "^9.4.0"
25
26
  }
26
- }
27
+ }
@@ -0,0 +1,73 @@
1
+ import {
2
+ confirm,
3
+ input,
4
+ } from "@inquirer/prompts";
5
+
6
+ import ora from "ora";
7
+
8
+ import {
9
+ getProfile,
10
+ saveProfile,
11
+ } from "../services/profile.js";
12
+
13
+ import {
14
+ generateSSHKey,
15
+ } from "../services/ssh.js";
16
+
17
+ import {
18
+ success,
19
+ } from "../utils/logger.js";
20
+
21
+ export async function addCommand() {
22
+ const name = await input({
23
+ message: "Profile Name",
24
+ });
25
+
26
+ if (getProfile(name)) {
27
+ throw new Error(
28
+ `Profile "${name}" already exists`
29
+ );
30
+ }
31
+
32
+ const username = await input({
33
+ message: "GitHub Username",
34
+ });
35
+
36
+ const email = await input({
37
+ message: "Email",
38
+ });
39
+
40
+ const createSSH = await confirm({
41
+ message:
42
+ "Generate SSH key automatically?",
43
+ default: true,
44
+ });
45
+
46
+ let sshKey = null;
47
+
48
+ if (createSSH) {
49
+ const spinner = ora(
50
+ "Generating SSH key..."
51
+ ).start();
52
+
53
+ sshKey = await generateSSHKey(
54
+ name,
55
+ email
56
+ );
57
+
58
+ spinner.succeed(
59
+ "SSH key generated"
60
+ );
61
+ }
62
+
63
+ saveProfile({
64
+ name,
65
+ username,
66
+ email,
67
+ sshKey,
68
+ });
69
+
70
+ success(
71
+ `Profile "${name}" saved locally`
72
+ );
73
+ }
@@ -0,0 +1,45 @@
1
+ import {
2
+ getCurrentProfile,
3
+ getProfile,
4
+ } from "../services/profile.js";
5
+
6
+ import { info } from "../utils/logger.js";
7
+
8
+ export async function currentCommand() {
9
+ const current =
10
+ getCurrentProfile();
11
+
12
+ if (!current) {
13
+ info("No active profile");
14
+ return;
15
+ }
16
+
17
+ const profile =
18
+ getProfile(current);
19
+
20
+ if (!profile) {
21
+ info("Profile not found");
22
+ return;
23
+ }
24
+
25
+ console.log();
26
+
27
+ console.log(
28
+ `Profile : ${profile.name}`
29
+ );
30
+
31
+ console.log(
32
+ `Username: ${profile.username}`
33
+ );
34
+
35
+ console.log(
36
+ `Email : ${profile.email}`
37
+ );
38
+
39
+ console.log(
40
+ `SSH Key : ${profile.sshKey || "Not configured"
41
+ }`
42
+ );
43
+
44
+ console.log();
45
+ }
@@ -0,0 +1,44 @@
1
+ import { execa } from "execa";
2
+
3
+ import {
4
+ error,
5
+ success,
6
+ } from "../utils/logger.js";
7
+
8
+ async function check(
9
+ name,
10
+ command,
11
+ args = []
12
+ ) {
13
+ try {
14
+ await execa(command, args);
15
+
16
+ success(`${name} installed`);
17
+
18
+ return true;
19
+ } catch {
20
+ error(`${name} missing`);
21
+
22
+ return false;
23
+ }
24
+ }
25
+
26
+ export async function doctorCommand() {
27
+ console.log();
28
+
29
+ await check("Git", "git", [
30
+ "--version",
31
+ ]);
32
+
33
+ await check("SSH", "ssh", [
34
+ "-V",
35
+ ]);
36
+
37
+ await check(
38
+ "GitHub CLI",
39
+ "gh",
40
+ ["--version"]
41
+ );
42
+
43
+ console.log();
44
+ }
@@ -0,0 +1,21 @@
1
+ import { getProfiles } from "../services/profile.js";
2
+ import { info } from "../utils/logger.js";
3
+
4
+ export async function listCommand() {
5
+ const profiles = getProfiles();
6
+
7
+ if (!profiles.length) {
8
+ info("No profiles found");
9
+ return;
10
+ }
11
+
12
+ console.log();
13
+
14
+ profiles.forEach((profile) => {
15
+ console.log(
16
+ `• ${profile.name} (${profile.username})`
17
+ );
18
+ });
19
+
20
+ console.log();
21
+ }
@@ -0,0 +1,37 @@
1
+ import {
2
+ getCurrentProfile,
3
+ getProfile,
4
+ removeProfile,
5
+ setCurrentProfile,
6
+ } from "../services/profile.js";
7
+
8
+ import {
9
+ error,
10
+ success,
11
+ } from "../utils/logger.js";
12
+
13
+ export async function removeCommand(
14
+ profileName
15
+ ) {
16
+ const profile =
17
+ getProfile(profileName);
18
+
19
+ if (!profile) {
20
+ error("Profile not found");
21
+
22
+ return;
23
+ }
24
+
25
+ removeProfile(profileName);
26
+
27
+ if (
28
+ getCurrentProfile() ===
29
+ profileName
30
+ ) {
31
+ setCurrentProfile(null);
32
+ }
33
+
34
+ success(
35
+ `Profile "${profileName}" removed`
36
+ );
37
+ }
@@ -0,0 +1,57 @@
1
+ import ora from "ora";
2
+
3
+ import {
4
+ getProfile,
5
+ setCurrentProfile,
6
+ } from "../services/profile.js";
7
+
8
+ import {
9
+ setGitUser,
10
+ } from "../services/git.js";
11
+
12
+ import {
13
+ error,
14
+ success,
15
+ } from "../utils/logger.js";
16
+
17
+ export async function useCommand(
18
+ profileName
19
+ ) {
20
+ const profile =
21
+ getProfile(profileName);
22
+
23
+ if (!profile) {
24
+ error(
25
+ `Profile "${profileName}" not found`
26
+ );
27
+
28
+ return;
29
+ }
30
+
31
+ const spinner = ora(
32
+ "Switching profile..."
33
+ ).start();
34
+
35
+ try {
36
+ await setGitUser(
37
+ profile.username,
38
+ profile.email
39
+ );
40
+
41
+ setCurrentProfile(profile.name);
42
+
43
+ spinner.succeed(
44
+ "Profile switched"
45
+ );
46
+
47
+ success(
48
+ `Current profile: ${profile.name}`
49
+ );
50
+ } catch (err) {
51
+ spinner.fail(
52
+ "Unable to switch profile"
53
+ );
54
+
55
+ error(err.message);
56
+ }
57
+ }
package/src/server.js CHANGED
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env node
2
+
3
+ import axios from "axios";
4
+ import chalk from "chalk";
5
+ import { Command } from "commander";
6
+ import packageJson from "../package.json" with { type: "json" };
7
+ import { addCommand } from "./commands/add.js";
8
+ import { currentCommand } from "./commands/current.js";
9
+ import { doctorCommand } from "./commands/doctor.js";
10
+ import { listCommand } from "./commands/list.js";
11
+ import { removeCommand } from "./commands/remove.js";
12
+ import { useCommand } from "./commands/use.js";
13
+
14
+ const { name, version } = packageJson;
15
+
16
+ function printBanner() {
17
+ const logo = String.raw`
18
+ ██████╗ ██╗████████╗███████╗██╗ ██╗██╗███████╗████████╗
19
+ ██╔════╝ ██║╚══██╔══╝██╔════╝██║ ██║██║██╔════╝╚══██╔══╝
20
+ ██║ ███╗██║ ██║ ███████╗███████║██║█████╗ ██║
21
+ ██║ ██║██║ ██║ ╚════██║██╔══██║██║██╔══╝ ██║
22
+ ╚██████╔╝██║ ██║ ███████║██║ ██║██║██║ ██║
23
+ ╚═════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ╚═╝
24
+ `;
25
+ console.log(chalk.cyanBright(logo));
26
+ console.log(chalk.bold("GitShift CLI"));
27
+ console.log(
28
+ chalk.dim("Manage and switch GitHub accounts effortlessly\n")
29
+ );
30
+ }
31
+
32
+ function compareVersions(currentVersion, latestVersion) {
33
+ const currentParts = currentVersion.split(".").map(Number);
34
+ const latestParts = latestVersion.split(".").map(Number);
35
+
36
+ for (let index = 0; index < 3; index += 1) {
37
+ const currentPart = currentParts[index] || 0;
38
+ const latestPart = latestParts[index] || 0;
39
+
40
+ if (currentPart > latestPart) return 1;
41
+ if (currentPart < latestPart) return -1;
42
+ }
43
+
44
+ return 0;
45
+ }
46
+
47
+ async function checkForUpdates() {
48
+ try {
49
+ const response = await axios.get(
50
+ `https://registry.npmjs.org/${name}/latest`,
51
+ {
52
+ timeout: 3000,
53
+ },
54
+ );
55
+
56
+ const latestVersion = response.data.version;
57
+
58
+ if (latestVersion && compareVersions(version, latestVersion) < 0) {
59
+ console.log(
60
+ `A new version of ${name} is available: ${version} -> ${latestVersion}`,
61
+ );
62
+ console.log(`Run: npm install -g ${name}`);
63
+ }
64
+ } catch (error) {
65
+ // Ignore version check failures so the CLI still starts offline.
66
+ }
67
+ }
68
+
69
+ async function main() {
70
+ printBanner();
71
+ await checkForUpdates();
72
+
73
+ const program = new Command();
74
+
75
+ program
76
+ .name("gitshift")
77
+ .description(
78
+ "GitHub Account Switcher"
79
+ )
80
+ .version(version);
81
+
82
+ program
83
+ .command("add")
84
+ .description(
85
+ "Create local profile"
86
+ )
87
+ .action(addCommand);
88
+
89
+ program
90
+ .command("list")
91
+ .description(
92
+ "List profiles"
93
+ )
94
+ .action(listCommand);
95
+
96
+ program
97
+ .command("current")
98
+ .description(
99
+ "Show active profile"
100
+ )
101
+ .action(currentCommand);
102
+
103
+ program
104
+ .command("use <profile>")
105
+ .description(
106
+ "Switch profile and update git config"
107
+ )
108
+ .action(useCommand);
109
+
110
+ program
111
+ .command("remove <profile>")
112
+ .description(
113
+ "Delete profile"
114
+ )
115
+ .action(removeCommand);
116
+
117
+ program
118
+ .command("doctor")
119
+ .description(
120
+ "System health check"
121
+ )
122
+ .action(doctorCommand);
123
+
124
+ await program.parseAsync(process.argv);
125
+ }
126
+
127
+ main().catch((error) => {
128
+ console.error(error);
129
+ process.exit(1);
130
+ });
@@ -0,0 +1,20 @@
1
+ import { execa } from "execa";
2
+
3
+ export async function setGitUser(
4
+ name,
5
+ email
6
+ ) {
7
+ await execa("git", [
8
+ "config",
9
+ "--global",
10
+ "user.name",
11
+ name,
12
+ ]);
13
+
14
+ await execa("git", [
15
+ "config",
16
+ "--global",
17
+ "user.email",
18
+ email,
19
+ ]);
20
+ }
@@ -0,0 +1,60 @@
1
+ import Conf from "conf";
2
+
3
+ const config = new Conf({
4
+ projectName: "ghswitch",
5
+ });
6
+
7
+ export function getProfiles() {
8
+ return config.get("profiles", []);
9
+ }
10
+
11
+ export function saveProfile(profile) {
12
+ const profiles = getProfiles();
13
+
14
+ const exists = profiles.find(
15
+ (item) => item.name === profile.name
16
+ );
17
+
18
+ if (exists) {
19
+ throw new Error(
20
+ `Profile "${profile.name}" already exists`
21
+ );
22
+ }
23
+
24
+ profiles.push(profile);
25
+
26
+ config.set("profiles", profiles);
27
+ }
28
+
29
+ export function getProfile(name) {
30
+ const profiles = getProfiles();
31
+
32
+ return profiles.find(
33
+ (profile) => profile.name === name
34
+ );
35
+ }
36
+
37
+ export function removeProfile(name) {
38
+ const profiles = getProfiles();
39
+
40
+ const filtered = profiles.filter(
41
+ (profile) => profile.name !== name
42
+ );
43
+
44
+ config.set("profiles", filtered);
45
+ }
46
+
47
+ export function setCurrentProfile(
48
+ name
49
+ ) {
50
+ if (!name) {
51
+ config.delete("current");
52
+ return;
53
+ }
54
+
55
+ config.set("current", name);
56
+ }
57
+
58
+ export function getCurrentProfile() {
59
+ return config.get("current", null);
60
+ }
@@ -0,0 +1,36 @@
1
+ import { execa } from "execa";
2
+ import fs from "fs-extra";
3
+ import os from "os";
4
+ import path from "path";
5
+
6
+ export async function generateSSHKey(
7
+ profileName,
8
+ email
9
+ ) {
10
+ const keyPath = path.join(
11
+ os.homedir(),
12
+ ".ssh",
13
+ `ghswitch-${profileName}`
14
+ );
15
+
16
+ const exists = await fs.pathExists(
17
+ keyPath
18
+ );
19
+
20
+ if (exists) {
21
+ return keyPath;
22
+ }
23
+
24
+ await execa("ssh-keygen", [
25
+ "-t",
26
+ "ed25519",
27
+ "-C",
28
+ email,
29
+ "-f",
30
+ keyPath,
31
+ "-N",
32
+ "",
33
+ ]);
34
+
35
+ return keyPath;
36
+ }
@@ -0,0 +1,10 @@
1
+ import chalk from "chalk";
2
+
3
+ export const success = (msg) =>
4
+ console.log(chalk.green(`✓ ${msg}`));
5
+
6
+ export const error = (msg) =>
7
+ console.log(chalk.red(`✗ ${msg}`));
8
+
9
+ export const info = (msg) =>
10
+ console.log(chalk.cyan(msg));