gitshift 2.1.1 → 2.3.0

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/README.md CHANGED
@@ -39,6 +39,48 @@ gitshift --help
39
39
  - `gitshift remove <profile>` - Delete a saved profile.
40
40
  - `gitshift scan` - Scan your `~/.ssh` folder and import existing SSH keys into new profiles.
41
41
  - `gitshift doctor` - Check whether Git, SSH, and GitHub CLI are installed.
42
+ - `gitshift backup [file]` - Export profiles, folder mappings, and current profile to a JSON backup file (default: `gitshift-backup.json`).
43
+ - `gitshift restore <file>` - Restore profiles and mappings from a previously created backup JSON file (prompts to confirm overwrite).
44
+ - `gitshift update` - Update GitShift to the latest version or manage automatic update checks.
45
+
46
+ - `gitshift link <folder>` - Link a local folder to a profile (prompts to select or create a profile).
47
+ - `gitshift unlink <folder>` - Remove an existing folder mapping.
48
+ - `gitshift links` - List folder → profile mappings.
49
+ - `gitshift auto` - Auto-switch profile based on the current working directory and configured folder mappings.
50
+
51
+ ### Add Command
52
+
53
+ - **Interactive prompts**: `Profile Name`, `GitHub Username`, `Email` (all required).
54
+ - **SSH key generation**: prompts `Generate SSH key automatically?` (default: **yes**). If accepted, an SSH key is generated and saved under `~/.ssh` with the pattern `gitshift-<profile-name>`.
55
+ - **Validation**: profile names must be unique; empty values for name, username, or email are rejected.
56
+ - **Cancelation**: pressing Ctrl+C during prompts exits gracefully and cancels creation.
57
+
58
+ ### Folder mappings
59
+
60
+ - `gitshift link <folder>`: associates a local folder path with a profile. If no profiles exist, you'll be prompted to create one; otherwise you can pick an existing profile or create a new one. Linking stores an absolute path mapping so GitShift can detect and switch profiles when you `cd` into that folder.
61
+ - `gitshift unlink <folder>`: removes the mapping for the given folder path.
62
+ - `gitshift links`: prints all configured folder mappings in the form `profile → /absolute/path`.
63
+
64
+ Example linking a folder
65
+
66
+ ```bash
67
+ $ gitshift link ~/projects/my-repo
68
+ Select Profile: personal
69
+ Linked /Users/akashs/projects/my-repo
70
+ Profile: personal
71
+ ```
72
+
73
+ ### Auto switching
74
+
75
+ - `gitshift auto` checks the current working directory against your configured folder mappings. If a matching mapping is found, GitShift will set the Git user (`user.name` and `user.email`) and mark the matched profile as current.
76
+
77
+ Run `gitshift auto` inside a linked folder (or any child path) to switch automatically:
78
+
79
+ ```bash
80
+ $ cd ~/projects/my-repo
81
+ $ gitshift auto
82
+ Switched to personal
83
+ ```
42
84
 
43
85
  ## Example Workflow
44
86
 
@@ -46,6 +88,7 @@ gitshift --help
46
88
  gitshift add
47
89
  gitshift list
48
90
  gitshift scan
91
+ gitshift backup
49
92
  gitshift use personal
50
93
  gitshift current
51
94
  gitshift doctor
@@ -55,6 +98,57 @@ When you create a profile and choose SSH generation, GitShift creates a key unde
55
98
 
56
99
  If you already have SSH keys on your machine, `gitshift scan` will list the available keys, let you pick one, and save it as a new imported profile.
57
100
 
101
+ Interactive `add` example
102
+
103
+ ```bash
104
+ $ gitshift add
105
+ Profile Name: personal
106
+ GitHub Username: akash
107
+ Email: akash@example.com
108
+ Generate SSH key automatically? (Y/n) [Y]
109
+ ```
110
+
111
+ ### Backup & Restore
112
+
113
+ - `gitshift backup [file]` writes a JSON file containing your saved profiles, folder mappings, and the currently selected profile. If no file is provided it defaults to `gitshift-backup.json` in your current directory.
114
+
115
+ Example backup:
116
+
117
+ ```bash
118
+ $ gitshift backup
119
+ Backup saved to /Users/akashs/gitshift-backup.json
120
+ ```
121
+
122
+ - `gitshift restore <file>` reads the JSON backup and restores profiles and mappings. It prompts to confirm overwriting existing data.
123
+
124
+ Example restore:
125
+
126
+ ```bash
127
+ $ gitshift restore gitshift-backup.json
128
+ This will overwrite current data. Continue? (y/N)
129
+ Backup restored
130
+ ```
131
+
132
+ ### Update Command
133
+
134
+ - `gitshift update` updates GitShift to the latest published version (runs `npm install -g gitshift@latest`).
135
+ - Options:
136
+ - `--enable-auto` — enable automatic update checks.
137
+ - `--disable-auto` — disable automatic update checks.
138
+
139
+ Examples:
140
+
141
+ ```bash
142
+ # Update GitShift to latest
143
+ $ gitshift update
144
+
145
+ # Enable automatic update checks
146
+ $ gitshift update --enable-auto
147
+
148
+ # Disable automatic update checks
149
+ $ gitshift update --disable-auto
150
+ ```
151
+
58
152
  ## How It Works
59
153
 
60
154
  Profiles are saved locally on your machine using the app's configuration store. Switching profiles updates your global Git identity with:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitshift",
3
- "version": "2.1.1",
3
+ "version": "2.3.0",
4
4
  "description": "GitHub Account Switcher CLI",
5
5
  "main": "server.js",
6
6
  "bin": {
@@ -0,0 +1,29 @@
1
+ import fs from "fs-extra";
2
+ import path from "path";
3
+ import { getCurrentProfile, getFolderMappings, getProfiles } from "../services/profile.js";
4
+ import { error, success } from "../utils/logger.js";
5
+
6
+ export async function backupCommand(fileName = "gitshift-backup.json") {
7
+ try {
8
+ const backup = {
9
+ profiles: getProfiles(),
10
+ folderMappings: getFolderMappings(),
11
+ currentProfile: getCurrentProfile(),
12
+ createdAt: new Date().toISOString(),
13
+ };
14
+
15
+ const filePath = path.resolve(fileName);
16
+
17
+ await fs.writeJson(
18
+ filePath,
19
+ backup,
20
+ {
21
+ spaces: 2,
22
+ }
23
+ );
24
+
25
+ success(`Backup saved to ${filePath}`);
26
+ } catch (err) {
27
+ error(err.message);
28
+ }
29
+ }
@@ -0,0 +1,33 @@
1
+ import { confirm } from "@inquirer/prompts";
2
+ import fs from "fs-extra";
3
+ import path from "path";
4
+ import { restoreData } from "../services/profile.js";
5
+ import { error, success } from "../utils/logger.js";
6
+
7
+ export async function restoreCommand(file) {
8
+ try {
9
+ const filePath = path.resolve(file);
10
+ const exists = await fs.pathExists(filePath);
11
+
12
+ if (!exists) {
13
+ error("Backup file not found");
14
+ return;
15
+ }
16
+
17
+ const overwrite = await confirm({
18
+ message: "This will overwrite current data. Continue?",
19
+ default: false,
20
+ });
21
+
22
+ if (!overwrite) {
23
+ return;
24
+ }
25
+
26
+ const backup = await fs.readJson(filePath);
27
+ restoreData(backup);
28
+
29
+ success("Backup restored");
30
+ } catch (err) {
31
+ error(err.message);
32
+ }
33
+ }
@@ -0,0 +1,40 @@
1
+ import { execa } from "execa";
2
+ import ora from "ora";
3
+ import { updateSettings } from "../services/profile.js";
4
+ import { error, success } from "../utils/logger.js";
5
+
6
+ export async function updateCommand(options) {
7
+ if (options.enableAuto) {
8
+ updateSettings({ autoUpdate: true, });
9
+ success("Auto update enabled");
10
+ return;
11
+ }
12
+
13
+ if (options.disableAuto) {
14
+ updateSettings({ autoUpdate: false });
15
+ success("Auto update disabled");
16
+ return;
17
+ }
18
+
19
+ const spinner = ora("Updating GitShift...").start();
20
+
21
+ try {
22
+ await execa(
23
+ "npm",
24
+ [
25
+ "install",
26
+ "-g",
27
+ "gitshift@latest",
28
+ ],
29
+ {
30
+ stdio:
31
+ "inherit",
32
+ }
33
+ );
34
+
35
+ spinner.succeed("GitShift updated");
36
+ } catch (err) {
37
+ spinner.fail("Update failed");
38
+ error(err.message);
39
+ }
40
+ }
package/src/server.js CHANGED
@@ -6,14 +6,17 @@ import { Command } from "commander";
6
6
  import { createRequire } from "node:module";
7
7
  import { addCommand } from "./commands/add.js";
8
8
  import { autoCommand } from "./commands/auto.js";
9
+ import { backupCommand } from "./commands/backup.js";
9
10
  import { currentCommand } from "./commands/current.js";
10
11
  import { doctorCommand } from "./commands/doctor.js";
11
12
  import { linkCommand } from "./commands/link.js";
12
13
  import { linksCommand } from "./commands/links.js";
13
14
  import { listCommand } from "./commands/list.js";
14
15
  import { removeCommand } from "./commands/remove.js";
16
+ import { restoreCommand } from "./commands/restore.js";
15
17
  import { scanCommand } from "./commands/scan.js";
16
18
  import { unlinkCommand } from "./commands/unlink.js";
19
+ import { updateCommand } from "./commands/update.js";
17
20
  import { useCommand } from "./commands/use.js";
18
21
 
19
22
  const require = createRequire(import.meta.url);
@@ -163,6 +166,36 @@ async function main() {
163
166
  )
164
167
  .action(doctorCommand);
165
168
 
169
+ program
170
+ .command("backup")
171
+ .description(
172
+ "Backup profiles and mappings"
173
+ )
174
+ .argument("[file]")
175
+ .action(backupCommand);
176
+
177
+ program
178
+ .command("restore")
179
+ .description(
180
+ "Restore backup"
181
+ )
182
+ .argument("<file>")
183
+ .action(restoreCommand);
184
+ program
185
+ .command("update")
186
+ .description(
187
+ "Update GitShift"
188
+ )
189
+ .option(
190
+ "--enable-auto",
191
+ "Enable automatic update checks"
192
+ )
193
+ .option(
194
+ "--disable-auto",
195
+ "Disable automatic update checks"
196
+ )
197
+ .action(updateCommand);
198
+
166
199
  program.exitOverride();
167
200
 
168
201
  try {
@@ -83,4 +83,30 @@ export function addFolderMapping(profile, folderPath) {
83
83
  export function removeFolderMapping(folderPath) {
84
84
  const mappings = getFolderMappings();
85
85
  config.set("folderMappings", mappings.filter((item) => item.path !== folderPath));
86
+ }
87
+
88
+ export function restoreData(data) {
89
+
90
+ config.set("profiles", data.profiles || []);
91
+ config.set("folderMappings", data.folderMappings || []);
92
+
93
+ if (data.currentProfile) {
94
+ config.set("current", data.currentProfile);
95
+ }
96
+ }
97
+
98
+ export function getSettings() {
99
+ return config.get("settings", {
100
+ autoUpdate: false,
101
+ lastUpdateCheck: null,
102
+ });
103
+ }
104
+
105
+ export function updateSettings(settings) {
106
+ const current = getSettings();
107
+
108
+ config.set("settings", {
109
+ ...current,
110
+ ...settings,
111
+ });
86
112
  }
@@ -0,0 +1,39 @@
1
+ import { execa } from "execa";
2
+ import { getSettings, updateSettings } from "./profile.js";
3
+
4
+ const DAY =
5
+ 24 *
6
+ 60 *
7
+ 60 *
8
+ 1000;
9
+
10
+ export async function checkForUpdates() {
11
+ const settings = getSettings();
12
+
13
+ if (!settings.autoUpdate) {
14
+ return;
15
+ }
16
+
17
+ const lastCheck = settings.lastUpdateCheck;
18
+
19
+ if (lastCheck && Date.now() - new Date(lastCheck).getTime() < DAY) {
20
+ return;
21
+ }
22
+
23
+ try {
24
+ const { stdout, } = await execa(
25
+ "npm",
26
+ [
27
+ "view",
28
+ "gitshift",
29
+ "version",
30
+ ]
31
+ );
32
+
33
+ updateSettings({ lastUpdateCheck: new Date().toISOString() });
34
+
35
+ return stdout.trim();
36
+ } catch {
37
+ return null;
38
+ }
39
+ }