gitshift 1.0.1 → 1.0.3

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.
@@ -0,0 +1,41 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+
9
+ jobs:
10
+ test:
11
+ name: ${{ matrix.os }} / Node ${{ matrix.node-version }}
12
+ runs-on: ${{ matrix.os }}
13
+
14
+ strategy:
15
+ fail-fast: false
16
+ matrix:
17
+ os:
18
+ - ubuntu-latest
19
+ - macos-latest
20
+ - windows-latest
21
+ node-version:
22
+ - "20"
23
+ - "22"
24
+
25
+ steps:
26
+ - name: Check out repository
27
+ uses: actions/checkout@v4
28
+
29
+ - name: Set up Node.js
30
+ uses: actions/setup-node@v4
31
+ with:
32
+ node-version: ${{ matrix.node-version }}
33
+ cache: npm
34
+
35
+ - name: Install dependencies
36
+ run: npm ci
37
+
38
+ - name: CLI smoke test
39
+ run: |
40
+ node src/server.js --version
41
+ node src/server.js --help
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitshift",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
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",
@@ -6,6 +6,7 @@ import {
6
6
  import ora from "ora";
7
7
 
8
8
  import {
9
+ getProfile,
9
10
  saveProfile,
10
11
  } from "../services/profile.js";
11
12
 
@@ -14,6 +15,7 @@ import {
14
15
  } from "../services/ssh.js";
15
16
 
16
17
  import {
18
+ error,
17
19
  success,
18
20
  } from "../utils/logger.js";
19
21
 
@@ -22,6 +24,15 @@ export async function addCommand() {
22
24
  message: "Profile Name",
23
25
  });
24
26
 
27
+ if (getProfile(name)) {
28
+ error(
29
+ `Profile "${name}" already exists`
30
+ );
31
+
32
+ process.exitCode = 1;
33
+ return;
34
+ }
35
+
25
36
  const username = await input({
26
37
  message: "GitHub Username",
27
38
  });
@@ -43,14 +54,36 @@ export async function addCommand() {
43
54
  "Generating SSH key..."
44
55
  ).start();
45
56
 
46
- sshKey = await generateSSHKey(
47
- name,
48
- email
49
- );
57
+ try {
58
+ sshKey = await generateSSHKey(
59
+ name,
60
+ email
61
+ );
50
62
 
51
- spinner.succeed(
52
- "SSH key generated"
53
- );
63
+ spinner.succeed(
64
+ "SSH key generated"
65
+ );
66
+ } catch (err) {
67
+ spinner.fail(
68
+ "Unable to generate SSH key"
69
+ );
70
+
71
+ error(
72
+ "Could not create SSH key. Ensure OpenSSH is installed and available."
73
+ );
74
+
75
+ if (
76
+ err &&
77
+ typeof err === "object" &&
78
+ "shortMessage" in err &&
79
+ err.shortMessage
80
+ ) {
81
+ error(String(err.shortMessage));
82
+ }
83
+
84
+ process.exitCode = 1;
85
+ return;
86
+ }
54
87
  }
55
88
 
56
89
  saveProfile({
@@ -61,6 +94,6 @@ export async function addCommand() {
61
94
  });
62
95
 
63
96
  success(
64
- `Profile "${name}" created`
97
+ `Profile "${name}" saved locally`
65
98
  );
66
99
  }
package/src/server.js CHANGED
@@ -1,69 +1,131 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import axios from "axios";
4
+ import chalk from "chalk";
3
5
  import { Command } from "commander";
4
-
6
+ import { createRequire } from "node:module";
5
7
  import { addCommand } from "./commands/add.js";
6
-
7
- import { listCommand } from "./commands/list.js";
8
-
9
8
  import { currentCommand } from "./commands/current.js";
10
-
11
- import { useCommand } from "./commands/use.js";
12
-
13
- import { removeCommand } from "./commands/remove.js";
14
-
15
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";
16
13
 
17
- const program =
18
- new Command();
19
-
20
- program
21
- .name("gitshift")
22
- .description(
23
- "GitHub Account Switcher"
24
- )
25
- .version("1.0.0");
26
-
27
- program
28
- .command("add")
29
- .description(
30
- "Create profile"
31
- )
32
- .action(addCommand);
33
-
34
- program
35
- .command("list")
36
- .description(
37
- "List profiles"
38
- )
39
- .action(listCommand);
40
-
41
- program
42
- .command("current")
43
- .description(
44
- "Show active profile"
45
- )
46
- .action(currentCommand);
47
-
48
- program
49
- .command("use <profile>")
50
- .description(
51
- "Switch profile"
52
- )
53
- .action(useCommand);
54
-
55
- program
56
- .command("remove <profile>")
57
- .description(
58
- "Delete profile"
59
- )
60
- .action(removeCommand);
61
-
62
- program
63
- .command("doctor")
64
- .description(
65
- "System health check"
66
- )
67
- .action(doctorCommand);
68
-
69
- program.parse();
14
+ const require = createRequire(import.meta.url);
15
+ const { name, version } = require("../package.json");
16
+
17
+ function printBanner() {
18
+ const logo = String.raw`
19
+ ██████╗ ██╗████████╗███████╗██╗ ██╗██╗███████╗████████╗
20
+ ██╔════╝ ██║╚══██╔══╝██╔════╝██║ ██║██║██╔════╝╚══██╔══╝
21
+ ██║ ███╗██║ ██║ ███████╗███████║██║█████╗ ██║
22
+ ██║ ██║██║ ██║ ╚════██║██╔══██║██║██╔══╝ ██║
23
+ ╚██████╔╝██║ ██║ ███████║██║ ██║██║██║ ██║
24
+ ╚═════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ╚═╝
25
+ `;
26
+ console.log(chalk.cyanBright(logo));
27
+ console.log(chalk.bold("GitShift CLI"));
28
+ console.log(
29
+ chalk.dim("Manage and switch GitHub accounts effortlessly\n")
30
+ );
31
+ }
32
+
33
+ function compareVersions(currentVersion, latestVersion) {
34
+ const currentParts = currentVersion.split(".").map(Number);
35
+ const latestParts = latestVersion.split(".").map(Number);
36
+
37
+ for (let index = 0; index < 3; index += 1) {
38
+ const currentPart = currentParts[index] || 0;
39
+ const latestPart = latestParts[index] || 0;
40
+
41
+ if (currentPart > latestPart) return 1;
42
+ if (currentPart < latestPart) return -1;
43
+ }
44
+
45
+ return 0;
46
+ }
47
+
48
+ async function checkForUpdates() {
49
+ try {
50
+ const response = await axios.get(
51
+ `https://registry.npmjs.org/${name}/latest`,
52
+ {
53
+ timeout: 3000,
54
+ },
55
+ );
56
+
57
+ const latestVersion = response.data.version;
58
+
59
+ if (latestVersion && compareVersions(version, latestVersion) < 0) {
60
+ console.log(
61
+ `A new version of ${name} is available: ${version} -> ${latestVersion}`,
62
+ );
63
+ console.log(`Run: npm install -g ${name}`);
64
+ }
65
+ } catch (error) {
66
+ // Ignore version check failures so the CLI still starts offline.
67
+ }
68
+ }
69
+
70
+ async function main() {
71
+ printBanner();
72
+ await checkForUpdates();
73
+
74
+ const program = new Command();
75
+
76
+ program
77
+ .name("gitshift")
78
+ .description(
79
+ "GitHub Account Switcher"
80
+ )
81
+ .version(version);
82
+
83
+ program
84
+ .command("add")
85
+ .description(
86
+ "Create local profile"
87
+ )
88
+ .action(addCommand);
89
+
90
+ program
91
+ .command("list")
92
+ .description(
93
+ "List profiles"
94
+ )
95
+ .action(listCommand);
96
+
97
+ program
98
+ .command("current")
99
+ .description(
100
+ "Show active profile"
101
+ )
102
+ .action(currentCommand);
103
+
104
+ program
105
+ .command("use <profile>")
106
+ .description(
107
+ "Switch profile and update git config"
108
+ )
109
+ .action(useCommand);
110
+
111
+ program
112
+ .command("remove <profile>")
113
+ .description(
114
+ "Delete profile"
115
+ )
116
+ .action(removeCommand);
117
+
118
+ program
119
+ .command("doctor")
120
+ .description(
121
+ "System health check"
122
+ )
123
+ .action(doctorCommand);
124
+
125
+ await program.parseAsync(process.argv);
126
+ }
127
+
128
+ main().catch((error) => {
129
+ console.error(error);
130
+ process.exit(1);
131
+ });
@@ -3,14 +3,28 @@ import fs from "fs-extra";
3
3
  import os from "os";
4
4
  import path from "path";
5
5
 
6
+ function toSafeKeyName(profileName) {
7
+ const normalized = profileName
8
+ .trim()
9
+ .replace(/[^a-zA-Z0-9._-]+/g, "-")
10
+ .replace(/-+/g, "-")
11
+ .replace(/^-|-$/g, "");
12
+
13
+ return normalized || "profile";
14
+ }
15
+
6
16
  export async function generateSSHKey(
7
17
  profileName,
8
18
  email
9
19
  ) {
20
+ const safeProfileName = toSafeKeyName(
21
+ profileName
22
+ );
23
+
10
24
  const keyPath = path.join(
11
25
  os.homedir(),
12
26
  ".ssh",
13
- `ghswitch-${profileName}`
27
+ `ghswitch-${safeProfileName}`
14
28
  );
15
29
 
16
30
  const exists = await fs.pathExists(
@@ -21,6 +35,10 @@ export async function generateSSHKey(
21
35
  return keyPath;
22
36
  }
23
37
 
38
+ await fs.ensureDir(
39
+ path.dirname(keyPath)
40
+ );
41
+
24
42
  await execa("ssh-keygen", [
25
43
  "-t",
26
44
  "ed25519",