@savaryna/git-add-account 2.0.2 → 2.2.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.
Files changed (3) hide show
  1. package/README.md +25 -62
  2. package/bin/main.js +40 -38
  3. package/package.json +20 -21
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @savaryna/add-git-account
2
2
 
3
- 🔐 A small CLI app that allows you to easily add multiple GIT accounts on one machine. It switches between accounts automatically based on the workspace _(directory/subdirectory)_ you are in.
3
+ 🔐 A small CLI app that allows you to easily add multiple Git accounts on one machine. It switches between accounts automatically based on the workspace you are in.
4
4
 
5
- ## Usage
5
+ ## CLI usage
6
6
 
7
7
  Run the command direcly with
8
8
 
@@ -10,7 +10,7 @@ Run the command direcly with
10
10
  npx @savaryna/git-add-account
11
11
  ```
12
12
 
13
- or if you want, first install it globally
13
+ or if you installed it globally with
14
14
 
15
15
  ```shell
16
16
  npm i -g @savaryna/git-add-account
@@ -20,69 +20,32 @@ then you can run it using
20
20
 
21
21
  ```shell
22
22
  git-add-account
23
+ # or the shorter version
24
+ gaa
23
25
  ```
24
26
 
25
- or
27
+ For usage and command details run it with the `--help` option.
26
28
 
27
- ```shell
28
- gaa
29
- ```
29
+ After going through all the steps, you will be presented with your public SSH key so you can copy, and add it to your Git provider. For example GitHub[^1]:
30
+
31
+ 1. Copy the public SSH key.
32
+ 1. Go to the [SSH keys settings page](https://github.com/settings/keys).
33
+ 1. Click on `New SSH key`.
34
+ 1. Add the key as an `Authentication Key`.
35
+ 1. Click on `Add SSH key`.
36
+ 1. Add the same key again as a `Signing Key` if you chose to sign your work[^2].
37
+
38
+ Done! Any `git` command you run from the workspace you chose **(and its subdirectories)**, will now use this new account automatically.
39
+
40
+ ## How it works
41
+
42
+ A simple way to use multiple Git accounts on one machine is to use different SSH configs based on the directory you are in. The way [@savaryna/add-git-account](https://www.npmjs.com/package/@savaryna/git-add-account) works is, it asks you for some basic information and then it creates files under `.config/` in the workspace directory you specified.
30
43
 
31
- After going through all the steps, you will be presented with your public SSH key so you can copy, and add it to your GIT provider. For example GitHub[^1]:
32
-
33
- 1. Go to your account [settings / keys](https://github.com/settings/keys)
34
- 1. Click on `New SSH key`
35
- 1. Give it a title
36
- 1. Choose `Authentication Key` for key type
37
- 1. Paste in the public SSH key copied earlier in the key field
38
- 1. Click on `Add SSH key`
39
- 1. Repeat steps **2 through 6** to add a `Signing Key` key type, if you chose to sign your work (Commits, Tags, Pushes)[^2]
40
- 1. Done! Now, you can go to the workspace you chose for the account, ex: `cd /Users/john/code/work`, and all the `git`
41
- commands issued from this, **or any other subdirectory**, will automatically use the correct account/ssh keys.
42
-
43
- ## Example of how it works
44
-
45
- A simple way to use multiple git accounts on one machine is to use different SSH configs based on the directory you are in. The way [@savaryna/add-git-account](https://www.npmjs.com/package/@savaryna/git-add-account) works is, it asks you for some basic information and then it creates some files under `.config` in the workspace directory you specified. Ex:
46
-
47
- 1. It creates a _(private/public)_ SSH keypair using `ssh-keygen -t ed25519 -C "john@github.com" -f /Users/john/code/work/.config/id_ed25519_git_github_com`. [See code](https://github.com/savaryna/git-add-account/blob/main/src/index.ts#L24-L25).
48
- 1. It creates a `sshconfig` file. [See code](https://github.com/savaryna/git-add-account/blob/main/src/index.ts#L35-L49).
49
-
50
- ```ini
51
- # File at /Users/john/code/work/.config/sshconfig
52
- # Config for GIT account john@github.com
53
- Host github.com
54
- HostName github.com
55
- User git
56
- AddKeysToAgent yes
57
- UseKeychain yes
58
- IdentitiesOnly yes
59
- IdentityFile /Users/john/code/work/.config/id_ed25519_git_github_com
60
- ```
61
-
62
- 1. It creates a `gitconfig` file. [See code](https://github.com/savaryna/git-add-account/blob/main/src/index.ts#L51-L79).
63
-
64
- ```ini
65
- # File at /Users/john/code/work/.config/gitconfig
66
- # Config for GIT account john@github.com
67
- [user]
68
- name = John Doe
69
- email = john@github.com
70
- [core]
71
- sshCommand = ssh -F /Users/john/code/work/.config/sshconfig
72
- [gpg]
73
- format = ssh
74
- [commit]
75
- gpgsign = true
76
- [push]
77
- gpgsign = if-asked
78
- [tag]
79
- gpgsign = true
80
- [user]
81
- signingkey = /Users/john/code/work/.config/id_ed25519_git_github_com
82
- ```
83
-
84
- 1. It runs `git config --global includeIf.gitdir:/Users/john/code/work/.path /Users/john/code/work/.config/gitconfig`, this makes sure that as long as you are in the workspace created earlier, **or any other subdirectory**, git will use the config from step **3** automatically[^3]. [See code](https://github.com/savaryna/git-add-account/blob/main/src/index.ts#L81-L82).
85
- 1. And finally, it presents you with your public SSH key so you can copy it and add it to your GIT provider of choice.
44
+ 1. It creates a private/public `ed25519` SSH keypair using `ssh-keygen` ([see code](https://github.com/savaryna/git-add-account/blob/main/src/helpers/config.ts#L28-L29)).
45
+ 1. It creates a `sshconfig` file based on this [template](https://github.com/savaryna/git-add-account/blob/main/src/templates/sshconfig.mustache).
46
+ 1. It creates a `gitconfig` file based on this [template](https://github.com/savaryna/git-add-account/blob/main/src/templates/gitconfig.mustache).
47
+ 1. It appends a conditional include to your global Git config based on this [template](https://github.com/savaryna/git-add-account/blob/main/src/templates/gitconfig.global.mustache). This makes sure that any `git` command you run from the workspace you chose **(and its subdirectories)**, will now use this new account automatically[^3].
48
+ 1. Finally, it presents you with your public SSH key so you can copy, and add it to your Git provider.
86
49
 
87
50
  ## License
88
51
 
package/bin/main.js CHANGED
@@ -1,40 +1,42 @@
1
1
  #!/usr/bin/env node
2
- "use strict";var T=Object.create;var y=Object.defineProperty;var W=Object.getOwnPropertyDescriptor;var I=Object.getOwnPropertyNames;var _=Object.getPrototypeOf,H=Object.prototype.hasOwnProperty;var A=(t,e,s,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of I(e))!H.call(t,r)&&r!==s&&y(t,r,{get:()=>e[r],enumerable:!(o=W(e,r))||o.enumerable});return t};var F=(t,e,s)=>(s=t!=null?T(_(t)):{},A(e||!t||!t.__esModule?y(s,"default",{value:t,enumerable:!0}):s,t));var h=require("util"),d=require("child_process"),c=(0,h.promisify)(d.exec);var i=require("fs/promises"),n=require("os"),a=require("path"),u=(t,e)=>(0,i.writeFile)(t,e,"utf8"),v=t=>(0,i.rm)(t,{recursive:!0,force:!0}),w=t=>(0,i.access)(t,i.constants.R_OK|i.constants.W_OK).then(()=>!0,()=>!1);var g=require("zod"),L=t=>e=>{let{success:s,error:o}=t.safeParse(e);return s?!0:o.format()._errors[0]},l=L;var k=F(require("prompts")),O="2.34.0",S=()=>c("git --version").then(({stdout:t})=>t.split(" ")[2]),m=(t=0,e=null)=>{console.log(e?`
3
- ${e}
4
- `:""),console.log(`${t?"\u{1F635} Exited.":"\u2728 Done."} Thanks for using @savaryna/git-add-account!
5
- `),process.exit(t)},x=(t,e)=>(0,k.default)(t,{onCancel:()=>m(1),...e}),P=t=>x({type:"toggle",name:"overwrite",message:`Path ${t} already exists. Overwrite?`,initial:!1,active:"yes",inactive:"no"}),$=async()=>{let t=await S();return x([{type:"text",name:"name",message:"Your name:",validate:l(g.z.string()),format:e=>({value:e,camel:e.toLowerCase().replace(/[^\w]/g,"_")})},{type:"text",name:"email",message:"Email to use for this account:",validate:l(g.z.string().email())},{type:"text",name:"host",message:"Host to use for this account:",initial:e=>e.split("@")[1],validate:l(g.z.string().transform(e=>"https://"+e).refine(URL.canParse,{message:"Invalid host"})),format:e=>({value:e,camel:e.replace(/[^\w]/g,"_")})},{type:"text",name:"workspace",message:"Workspace to use for this account:",initial:e=>(0,a.resolve)((0,n.homedir)(),"code",e.camel),validate:l(g.z.string().refine(e=>!e.startsWith("~"),{message:'"~" is not supported'})),format:(e,{host:s})=>{let o=(0,a.resolve)((0,n.homedir)(),e),r=(0,a.resolve)(o,".config"),f=`id_ed25519_git_${s.camel}`;return{root:o,config:r,gitConfig:(0,a.resolve)(r,"gitconfig"),sshConfig:(0,a.resolve)(r,"sshconfig"),privateKey:(0,a.resolve)(r,f),publicKey:(0,a.resolve)(r,f+".pub")}}},{type:"toggle",name:"signYourWork",message:"Do you want to sign your work with SSH?",initial:!0,active:"yes",inactive:"no"},{type:e=>e&&t<O?"toggle":null,name:"signYourWork",initial:!0,message:`Your current git version (${t}) does not support SSH signing. Continue without?`,active:"yes",inactive:"no",format:e=>e?!e:m(1)}])};async function Y(){let{name:t,email:e,host:s,workspace:o,signYourWork:r}=await $();if(await w(o.config)){let{overwrite:p}=await P(o.config);p?await v(o.config):m(1)}await(0,i.mkdir)(o.config,{recursive:!0}),await c(`ssh-keygen -t ed25519 -C "${e}" -f ${o.privateKey}`);let f=await c(`ssh-keygen -y -P "" -f ${o.privateKey}`).then(()=>!1,()=>!0).then(p=>p&&(0,n.platform)()==="darwin"),C=`
6
- # Config for GIT account ${e}
7
- Host ${s.value}
8
- HostName ${s.value}
9
- User git
10
- AddKeysToAgent yes
11
- ${f?"UseKeychain yes":""}
12
- IdentitiesOnly yes
13
- IdentityFile ${o.privateKey}
14
- `.replace(/\n\s{4}/g,`
15
- `).concat(`
16
- `);await u(o.sshConfig,C);let K=`
17
- # Config for GIT account ${e}
18
- [user]
19
- name = ${t.value}
20
- email = ${e}
21
- [core]
22
- sshCommand = ssh -F ${o.sshConfig}
23
- ${r?`
24
- [gpg]
25
- format = ssh
26
- [commit]
27
- gpgsign = true
28
- [push]
29
- gpgsign = if-asked
30
- [tag]
31
- gpgsign = true
32
- [user]
33
- signingkey = ${o.privateKey}
34
- `:""}
35
- `.replace(/\n\s{4}/g,`
36
- `).concat(`
37
- `);await u(o.gitConfig,K),await c(`git config --global includeIf.gitdir:${o.root}/.path ${o.gitConfig}`);let b=await(0,i.readFile)(o.publicKey).then(p=>p.toString().trim());console.log(`
2
+ "use strict";var T=Object.create;var u=Object.defineProperty;var N=Object.getOwnPropertyDescriptor;var O=Object.getOwnPropertyNames;var I=Object.getPrototypeOf,Y=Object.prototype.hasOwnProperty;var j=(e,i,n,s)=>{if(i&&typeof i=="object"||typeof i=="function")for(let t of O(i))!Y.call(e,t)&&t!==n&&u(e,t,{get:()=>i[t],enumerable:!(s=N(i,t))||s.enumerable});return e};var d=(e,i,n)=>(n=e!=null?T(I(e)):{},j(i||!e||!e.__esModule?u(n,"default",{value:e,enumerable:!0}):n,e));var $=d(require("yargs")),S=require("yargs/helpers");var x=require("os"),A=require("path"),D=d(require("prompts"));var b=require("child_process"),w=require("fs"),a=require("fs/promises"),l=require("os"),r=require("path"),k=require("util"),m=d(require("mustache"));var h=`
3
+ [includeIf "gitdir:{{{paths.workspace}}}/"]
4
+ path = {{{paths.gitConfig}}}
5
+ `;var y=`# Config for GIT account {{{configDetails.email}}}
6
+ [user]
7
+ name = {{{configDetails.name}}}
8
+ email = {{{configDetails.email}}}
9
+ [core]
10
+ sshCommand = ssh -F {{{paths.sshConfig}}}
11
+ {{#configDetails.signYourWork}}
12
+ [gpg]
13
+ format = ssh
14
+ [commit]
15
+ gpgsign = true
16
+ [push]
17
+ gpgsign = if-asked
18
+ [tag]
19
+ gpgsign = true
20
+ [user]
21
+ signingkey = {{{paths.privateKey}}}
22
+ {{/configDetails.signYourWork}}
23
+ `;var C=`# Config for GIT account {{{configDetails.email}}}
24
+ Host {{{configDetails.host}}}
25
+ IgnoreUnknown AddKeysToAgent
26
+ HostName {{{configDetails.host}}}
27
+ User git
28
+ AddKeysToAgent yes
29
+ IdentitiesOnly yes
30
+ IdentityFile {{{paths.privateKey}}}
31
+ `;var c=require("zod"),E=(e,i)=>{let{success:n,error:s}=e.safeParse(i);return!n&&s.errors.map(({message:t})=>t).join(`
32
+ `)},g=E;var L=(0,k.promisify)(b.exec);async function R(e){let i=`id_ed25519_git_${e.host}`,n=i,s=`${i}.pub`,t=(0,r.resolve)((0,l.tmpdir)(),n),o=(0,r.resolve)((0,l.tmpdir)(),s);await(0,a.rm)(t,{force:!0}),await(0,a.rm)(o,{force:!0}),await L(`ssh-keygen -t ed25519 -C "${e.email}" -f ${t} -N ""`);let f=await(0,a.readFile)(t,"utf8"),F=await(0,a.readFile)(o,"utf8");return await(0,a.rm)(t,{force:!0}),await(0,a.rm)(o,{force:!0}),{privateKeyName:n,publicKeyName:s,privateKey:f,publicKey:F}}var p=c.z.object({name:c.z.string().min(1),email:c.z.string().email(),host:c.z.string().refine(e=>e.includes(".")&&URL.canParse(`https://${e}`),{message:"Invalid host, must be a valid domain (e.g., github.com)"}),workspace:c.z.string().min(1).refine(e=>!(0,w.existsSync)(e),"Path already exists"),signYourWork:c.z.boolean()});async function v(e){let i=g(p,e);if(i)throw new Error(i);let n=await R(e),s=(0,r.resolve)(e.workspace),t=(0,r.resolve)(s,".config"),o={workspace:s,config:t,gitConfigGlobal:(0,r.resolve)((0,l.homedir)(),".gitconfig"),gitConfig:(0,r.resolve)(t,"gitconfig"),sshConfig:(0,r.resolve)(t,"sshconfig"),privateKey:(0,r.resolve)(t,n.privateKeyName),publicKey:(0,r.resolve)(t,n.publicKeyName)};return{gitConfigGlobal:{path:o.gitConfigGlobal,content:m.default.render(h,{configDetails:e,paths:o})},gitConfig:{path:o.gitConfig,content:m.default.render(y,{configDetails:e,paths:o})},sshConfig:{path:o.sshConfig,content:m.default.render(C,{configDetails:e,paths:o})},privateKey:{path:o.privateKey,content:n.privateKey,secret:!0},publicKey:{path:o.publicKey,content:n.publicKey}}}async function K(e,i){let n=(0,r.resolve)(e.workspace,".config");await(0,a.mkdir)(n,{recursive:!0});let s=Object.values(i).map(({path:t,content:o,secret:f})=>(0,a.appendFile)(t,o,{encoding:"utf8",mode:f?384:420}));await Promise.all(s)}var G={command:["$0","add"],describe:"Add a new Git account",builder:e=>e.option("dry-run",{type:"boolean",describe:"Preview the generated config files without writing them to disk"}),handler:async e=>{console.log("Let's add a new Git account.");let s=await(0,D.default)([{type:"text",name:"name",message:"Full name for Git commits (e.g., John Doe):",validate:o=>g(p.shape.name,o)||!0},{type:"text",name:"email",message:"Email for this Git account:",validate:o=>g(p.shape.email,o)||!0},{type:"text",name:"host",message:"Git provider host (e.g., github.com, gitlab.com):",initial:o=>o.split("@")[1],validate:o=>g(p.shape.host,o)||!0},{type:"text",name:"workspace",message:"Absolute path to the workspace for this account:",initial:o=>(0,A.resolve)((0,x.homedir)(),"code",o),validate:o=>g(p.shape.workspace,o)||!0},{type:"toggle",name:"signYourWork",message:"Sign commits and tags with your SSH key?",initial:!0,active:"yes",inactive:"no"}],{onCancel:()=>{throw new Error("Operation cancelled by the user.")}}),t=await v(s);if(e!=null&&e.dryRun){console.log(`
33
+ Config files that would be generated/updated:`);for(let o of Object.values(t))console.log(`
34
+ Path:`,o.path),console.log(o.secret?"[Content hidden for security]":o.content)}else await K(s,t),console.log(`
38
35
  Your public SSH key is:
39
- `,b),console.log(`You can also find it here:
40
- `,o.publicKey),console.log("Add it to your favorite GIT provider and enjoy!")}Y().then(()=>m()).catch(t=>m(1,t.message));
36
+ ${t.publicKey.content}`),console.log(`You can also find it here:
37
+ ${t.publicKey.path}`),console.log(`
38
+ Next, add the key to ${s.host}:`),console.log("1. Copy the public SSH key above."),console.log(`2. Go to the SSH keys settings page on ${s.host}.`),console.log("3. Add the key as an 'Authentication Key'."),s.signYourWork&&console.log("4. Add the same key again as a 'Signing Key'."),console.log(`
39
+ Done! Any git command you run from '${s.workspace}',`),console.log("(and its subdirectories), will now use this new account.")}};var P=[G];(0,$.default)((0,S.hideBin)(process.argv)).scriptName("git-add-account").usage("$0 [cmd] <options>").command(P).demandCommand(0,1).help("h").alias("h","help").strict().fail((e,i,n)=>{e&&console.log(n.help()),console.log(`
40
+ ${e||(i==null?void 0:i.message)}
41
+ `),console.log(`\u{1F635} Exited! Thanks for using @savaryna/git-add-account!
42
+ `),process.exit(1)}).parse();
package/package.json CHANGED
@@ -1,27 +1,21 @@
1
1
  {
2
2
  "name": "@savaryna/git-add-account",
3
- "version": "2.0.2",
4
- "description": "🔐 A small CLI app that allows you to easily add multiple GIT accounts on one machine. It switches between accounts automatically based on the workspace (directory/subdirectory) you are in.",
3
+ "version": "2.2.0",
4
+ "description": "🔐 A small CLI app that allows you to easily add multiple Git accounts on one machine. It switches between accounts automatically based on the workspace you are in.",
5
5
  "homepage": "https://github.com/savaryna/git-add-account#readme",
6
6
  "keywords": [
7
- "add",
8
- "multiple",
9
7
  "git",
10
- "github",
11
- "account",
12
- "accounts",
13
- "users",
14
- "on",
15
- "one",
16
- "machine",
17
- "mac",
18
- "linux",
19
- "windows",
8
+ "multiple-accounts",
9
+ "account-switching",
10
+ "cli",
20
11
  "ssh",
21
- "verified",
22
- "commit",
23
- "auto",
24
- "switch"
12
+ "git-config",
13
+ "workspace",
14
+ "developer-tools",
15
+ "automation",
16
+ "productivity",
17
+ "github",
18
+ "gitlab"
25
19
  ],
26
20
  "author": "Alex Tofan",
27
21
  "license": "MIT",
@@ -43,19 +37,24 @@
43
37
  "scripts": {
44
38
  "link": "npm unlink -g && npm link",
45
39
  "dev": "tsup --watch",
40
+ "check": "biome check --write",
46
41
  "build": "tsup",
47
42
  "pretest": "npm run build",
48
43
  "test": "npm publish --dry-run"
49
44
  },
50
- "source": "src/index.ts",
51
45
  "main": "bin/main.js",
52
46
  "dependencies": {
47
+ "mustache": "^4.2.0",
53
48
  "prompts": "^2.4.2",
54
- "zod": "^3.20.6"
49
+ "yargs": "^18.0.0",
50
+ "zod": "^3.25.76"
55
51
  },
56
52
  "devDependencies": {
57
- "@types/node": "^18.14.0",
53
+ "@biomejs/biome": "2.1.2",
54
+ "@types/mustache": "^4.2.5",
55
+ "@types/node": "^22.16.5",
58
56
  "@types/prompts": "^2.4.2",
57
+ "@types/yargs": "^17.0.33",
59
58
  "tsup": "^8.2.4",
60
59
  "typescript": "^5.5.4"
61
60
  }