UNPKG

git-account-switch-ssh

Version:
37 lines (18 loc) 12.6 kB
#!/usr/bin/env node 'use strict';var prompts=require('@clack/prompts'),de=require('gradient-string'),fs=require('fs'),Ct=require('path'),G=require('chalk'),os=require('os'),child_process=require('child_process'),se=require('ssh-config'),util=require('util'),promises=require('fs/promises');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var de__default=/*#__PURE__*/_interopDefault(de);var Ct__default=/*#__PURE__*/_interopDefault(Ct);var G__default=/*#__PURE__*/_interopDefault(G);var se__default=/*#__PURE__*/_interopDefault(se);var B=Buffer.from("ICAgX19fIF8gXyAgICAgICBfICAgICAgICAgICAgICAgICAgICAgIF8gICAgIF9fXyAgICAgICAgXyBfICAgICAgXyAgICAgIF9fXyBfX18gXyAgXyAKICAvIF9fKF8pIHxfICAgIC9fXCAgX18gX18gX19fIF8gIF8gXyBffCB8XyAgLyBfX3xfIF9fIF8oXykgfF8gX198IHxfICAgLyBfXy8gX198IHx8IHwKIHwgKF8gfCB8ICBffCAgLyBfIFwvIF8vIF8vIF8gXCB8fCB8ICcgXCAgX3wgXF9fIFwgViAgViAvIHwgIF8vIF98ICcgXCAgXF9fIFxfXyBcIF9fIHwKICBcX19ffF98XF9ffCAvXy8gXF9cX19cX19cX19fL1xfLF98X3x8X1xfX3wgfF9fXy9cXy9cXy98X3xcX19cX198X3x8X3wgfF9fXy9fX18vX3x8X3wK","base64").toString(),_={blue:"#ADD7FF",cyan:"#89DDFF",green:"#5DE4C7",magenta:"#FAE4FC",red:"#D0679D",yellow:"#FFFAC2"};var U=async()=>{try{let e=os.homedir();fs.accessSync(`${e}/.ssh/config_backup`,fs.constants.R_OK|fs.constants.W_OK);let t=fs.readFileSync(`${e}/.ssh/config_backup`,{encoding:"utf-8"}),s=se.parse(t),o=[];for(let c of s){let r=c.value;if(r){let n=s.compute(r);n.Host&&o.push(n);}}return o}catch{return []}};var L=async e=>{try{let t=await U(),s=[],o=[],c=t.reduce((r,n)=>{let i=r;return n.IdentityFile?.length&&(i=[...i,...n.IdentityFile]),i},o);for(let r of e){let n=r.IdentityFile?.[0],i=n?!c.includes(n):!1;if(n&&i){let l=os.homedir(),h=n.split("/").pop(),m=`${l}/.ssh/${h}`,p=`${m}.pub`;fs.existsSync(m)&&fs.existsSync(p)&&(s.push(m),s.push(p),child_process.execSync(`ssh-add -d ${p}`,{stdio:[]}),fs.unlinkSync(m),fs.unlinkSync(p));}}return s}catch{return []}};var P=async()=>{try{let e=os.homedir();return child_process.execSync(`mv ${e}/.ssh/config_backup ${e}/.ssh/config`),fs.readFileSync(`${e}/.ssh/config`,{encoding:"utf-8"})}catch{}};var R=async()=>{try{let t=`${os.homedir()}/.gascache.json`;fs.existsSync(t)&&fs.unlinkSync(t);}catch{}};var Y=async e=>{try{let t=await L(e),s=await P();await R(),prompts.log.info(`Removing keys: ${t.join(` `)}`),prompts.log.info(`Restoring SSH config: ${s}`);}catch{}};var A=Symbol("NEW_SSH_USER"),g=Symbol("GITHUB"),u=Symbol("GITLAB"),E=Symbol("KEYCHAIN_YES"),J=Symbol("KEYCHAIN_NO");var a={[g]:{site:"github.com",keys:"https://github.com/settings/keys",cta:"New SSH key",usage:"Set the Key type (Authentication Key)"},[u]:{site:"gitlab.com",keys:"https://gitlab.com/-/user_settings/ssh_keys",cta:"Add new key",usage:"Set the Usage type (Authentication & Signing)"}};var S=async()=>{try{let e=os.homedir();fs.accessSync(`${e}/.gascache.json`,fs.constants.R_OK|fs.constants.W_OK);let t=fs.readFileSync(`${e}/.gascache.json`,{encoding:"utf-8"});return JSON.parse(t)}catch{return []}};var v=async e=>{let t=os.homedir();try{fs.accessSync(`${t}/.gascache.json`,fs.constants.R_OK|fs.constants.W_OK);let s=fs.readFileSync(`${t}/.gascache.json`,{encoding:"utf-8"}),o=JSON.parse(s);o.push(e),fs.writeFileSync(`${t}/.gascache.json`,JSON.stringify(o),{encoding:"utf-8"});}catch{let o=[];o.push(e),fs.writeFileSync(`${t}/.gascache.json`,JSON.stringify(o),{encoding:"utf-8"});}};var w=async()=>{try{let e=os.homedir();fs.accessSync(`${e}/.ssh/config`,fs.constants.R_OK|fs.constants.W_OK);let t=fs.readFileSync(`${e}/.ssh/config`,{encoding:"utf-8"}),s=se.parse(t),o=[];for(let c of s){let r=c.value;if(r){let n=s.compute(r);n.Host&&o.push(n);}}return o}catch{return []}};var C=async(e,t=".")=>{try{let s=await S(),o=await w(),c=s.find(n=>n.username===e),r=o.find(n=>n?.User===e);c&&(child_process.execSync(`cd ${t} && git config user.name "${c.name}"`,{stdio:[]}),child_process.execSync(`cd ${t} && git config user.email "${c.email}"`,{stdio:[]})),r?.IdentityFile?.[0]&&child_process.execSync(`cd ${t} && git config core.sshCommand "ssh -o IdentitiesOnly=yes -i ${r.IdentityFile[0]} -F /dev/null"`);}catch{}};var Q=async e=>{try{let s=`${os.homedir()}/.ssh/known_hosts`;fs.existsSync(s)&&child_process.execSync(`ssh-keyscan ${a[e].site} >> ${s}`,{stdio:[]});}catch{}};var ee=async({host:e,username:t,name:s,email:o,passphrase:c})=>{try{let r=`~/.ssh/id_ed25519_${t}`,n=`pbcopy < ${r}.pub`;if(child_process.execSync(`ssh-keygen -t ed25519 -C "${o}" -f ${r} -P "${c}"`),prompts.note(` Open a new shell and run this command to copy your public key: ${G__default.default.inverse(n)} Now head over to ${G__default.default.blue.underline(a[e].keys)} Click "${a[e].cta}" Give your key a Title (e.g. id_ed25519_${t}) ${a[e].usage} Paste in your public key and save `,"Instructions"),await prompts.confirm({message:"Confirm once you've copied and saved your key",initialValue:!0}))await Q(e);else return prompts.cancel("Operation cancelled"),process.exit(0);return r}catch{}};var Me=util.promisify(child_process.exec),oe=async(e,t=false)=>{try{let s=t?"--apple-use-keychain ":"";await Me("ssh-add -l").then(()=>!0).catch(()=>!1)||child_process.execSync('eval "$(ssh-agent -s)"',{stdio:[]}),child_process.execSync(`ssh-add ${s}${e}`,{stdio:[]});}catch{}};var O=async(e,t)=>{try{let s=os.homedir(),o=new se__default.default;e.forEach(c=>{o.append({...c});}),t&&o.append(t),fs.writeFileSync(`${s}/.ssh/config`,se__default.default.stringify(o),{encoding:"utf-8"});}catch{}};var K=async({project:e,users:t,gitconfig:s})=>{let o,c=t.map(f=>{let k=`${f.IdentityFile?.[0]&&f.IdentityFile[0]===s.local.key?G__default.default.hex(_.green).bold(f.User):f.User}${f?.HostName?" ("+f.HostName+")":""}`;return {value:f.User,label:k}}),r=await prompts.select({message:`Which git account do you want linked to ${G__default.default.hex(_.red).bold(e)}`,options:[...c,{value:A,label:"Setup SSH for another git account"}]});if(prompts.isCancel(r))return prompts.cancel("Operation cancelled"),process.exit(0);if(r!==A){o=r;let y=(await S()).find(I=>I.username===o),d=s.local.name??s.global.name,k=s.local.email??s.global.email;if(!y){let I=await prompts.group({host:()=>prompts.select({message:`Where is ${G__default.default.hex(_.green).bold(o)} hosted?`,options:[{value:g,label:a[g].site},{value:u,label:a[u].site}]}),name:()=>prompts.text({message:"What is your name?",placeholder:d?`${d}`:""}),email:()=>prompts.text({message:"What is your primary email for this git account?",placeholder:k?`${k}`:""})},{onCancel:()=>{prompts.cancel("Operation cancelled"),process.exit(0);}});await v({host:a[I.host].site,username:o,name:I.name,email:I.email});}return await O(t),await C(o),o}let n=await prompts.group({host:()=>prompts.select({message:"Where is your git account hosted?",options:[{value:g,label:a[g].site},{value:u,label:a[u].site}]}),username:()=>prompts.text({message:"What's your username?",placeholder:"username"}),name:()=>prompts.text({message:"What's your full name?",placeholder:"Firstname Lastname"}),email:()=>prompts.text({message:"What is your primary email for this git account?",placeholder:"example@email.com"}),passphrase:()=>prompts.password({message:"Enter a passphrase to protect your SSH keys",mask:"*"})},{onCancel:()=>{prompts.cancel("Operation cancelled"),process.exit(0);}}),i={host:a[n.host].site,username:n.username,name:n.name,email:n.email};if(prompts.log.info(`Host: ${i.host}`),prompts.log.info(`Username: ${i.username}`),prompts.log.info(`Name: ${i.name}`),prompts.log.info(`Email: ${i.email}`),!await prompts.confirm({message:"Does this information look correct?",initialValue:true}))return prompts.cancel("Operation cancelled"),process.exit(0);if((await S()).find(f=>f.username===n.username&&f.host===a[n.host].site))return prompts.cancel("It looks like you've already setup this git account"),process.exit(0);let p=await ee(n);if(p){let f={Host:`${a[n.host].site}-${n.username}`,AddKeysToAgent:"yes",UseKeychain:"yes",IdentityFile:[p],User:n.username,HostName:a[n.host].site},y=await prompts.select({message:"Do you want to save your passphrase in Apple Keychain?",options:[{value:E,label:"Yes",hint:"recommended"},{value:J,label:"No"}]});if(prompts.isCancel(y))return prompts.cancel("Operation cancelled"),process.exit(0);await oe(p,y===E),await v(i),await O(t,f),await C(n.username);}return n.username};var ce=async({repository:e,project:t,username:s})=>{try{let c=(await w()).find(r=>r?.User===s);c?.IdentityFile?.[0]&&(child_process.execSync(`GIT_SSH_COMMAND="ssh -o IdentitiesOnly=yes -i ${c.IdentityFile[0]} -F /dev/null" git clone ${e}`,{stdio:[]}),await C(s,t),prompts.note(` Repository cloned ${G__default.default.inverse(`cd ${t}`)} `,"Success"));}catch{}};var rt=util.promisify(child_process.exec),ie=async()=>{try{return await rt("uname -s").then(({stdout:t})=>t.includes("Darwin")).catch(()=>!1)}catch{return false}};var ae=async()=>{try{let e=os.homedir(),t=/^(id_ed25519|id_ecdsa|id_rsa|id_dsa).+$/,s=[];for(let o of fs.readdirSync(`${e}/.ssh`))t.test(o)&&s.push(o);return s}catch{return []}};var me=async()=>{let e=".";try{return e=child_process.execSync("git rev-parse --show-toplevel",{stdio:[]}).toString().trim(),e}catch{return e}};var le=async e=>{let s=`${os.homedir()}/.gitconfig`,o=`${e}/.git/config`,c=false,r=false,n={global:{email:void 0,name:void 0},local:{email:void 0,name:void 0}};try{fs.accessSync(s,fs.constants.R_OK|fs.constants.W_OK);}catch(m){m.message.includes(s)&&(c=true);}try{fs.accessSync(o,fs.constants.R_OK|fs.constants.W_OK);}catch(m){m.message.includes(o)&&(r=true);}let i=/email\s=\s(.*?)$/ms,l=/name\s=\s(.*?)$/ms,h=/sshCommand\s=\sssh\s-o\sIdentitiesOnly=yes\s-i\s(.*?)\s-F\s\/dev\/null$/ms;if(!c){let m=fs.readFileSync(s,{encoding:"utf-8"}),p=m.match(i),f=m.match(l),y=p?p[1]:void 0,d=f?f[1]:void 0;n.global={email:y,name:d};}if(!r){let m=fs.readFileSync(o,{encoding:"utf-8"}),p=m.match(i),f=m.match(l),y=m.match(h),d=p?p[1]:void 0,k=f?f[1]:void 0,I=y?y[1]:void 0;n.local={email:d,name:k,key:I};}return n};var ge=async()=>{try{let e=os.homedir();if(fs.accessSync(`${e}/.ssh/config`,fs.constants.R_OK|fs.constants.W_OK),!await promises.stat(`${e}/.ssh/config_backup`).then(()=>!0).catch(()=>!1)){let s=fs.readFileSync(`${e}/.ssh/config`,{encoding:"utf-8"});fs.writeFileSync(`${e}/.ssh/config_backup`,s);}}catch{}};var dt=util.promisify(child_process.exec),ue=async e=>{let t=[];for(let s of e){let o=s;try{let{stdout:c}=await dt(`ssh -T git@${s.Host}`);if(c){let r=/@(.*?)\!/ms,n=c.match(r),i=n?n[1]:void 0;if(i){let l=o.Host.replace(`${a[u].site}-`,"");o.User=i,o.HostName=a[u].site,(o.Host===a[u].site||l===i)&&t.push(o);}}}catch(c){if(c){let r=c,n=/Hi\s(.*?)\!/ms,i=r.stderr?.match(n),l=i?i[1]:void 0;if(l){let h=o.Host.replace(`${a[g].site}-`,"");o.User=l,o.HostName=a[g].site,(o.Host===a[g].site||h===l)&&t.push(o);}}}}return t};var{version:$t,homepage:bt}=JSON.parse(fs.readFileSync(Ct__default.default.resolve(__dirname+"./../package.json"),"utf-8")),Ft=async()=>{let e=de__default.default(Object.values(_));console.log(e.multiline(B)+` `),console.log(" "+de__default.default.cristal.multiline($t)+` `);},Ht=async()=>{if(!await ie())return console.log(`MacOS support only. \u{1F64F} See ${G__default.default.blue.underline(bt)} to contribute.`),process.exit(0);let t=await w(),s=await ae(),o=await me(),c=await le(o);return await ge(),{accounts:t,keys:s,gitrepo:o,gitconfig:c}},At=async({accounts:e,keys:t,gitrepo:s,gitconfig:o})=>{let c,r="",n=process.argv?.[2]==="restore",i=s.length>1,l=prompts.spinner();prompts.intro(n?"Restoring SSH configurations":i?"Link SSH to this repository":"Clone a new repository and link SSH"),l.start("Checking for git accounts with SSH access");let h=await ue(e);if(l.stop(h.length?`Found ${h.length} account${h.length>1?"s":""}!`:"No accounts have SSH access. Let's set one up!"),n)return await Y(h),prompts.outro("Restore complete!"),process.exit(0);if(i)r=s.split("/").pop()??"",c=await K({project:r,users:h,gitconfig:o});else {let m=await prompts.text({message:"Clone which repository?",placeholder:"git@git{hub|lab}.com:organization/repository.git"});if(prompts.isCancel(m))return prompts.cancel("Operation cancelled"),process.exit(0);r=m.toString().split("/").pop()?.replace(".git","")??"",c=await K({project:r,users:h,gitconfig:o}),await ce({repository:m.toString(),project:r,username:c});}prompts.outro(`Account ${G__default.default.hex(_.green).bold(c)} is all setup for repository ${G__default.default.hex(_.red).bold(r)}`);};Ft().then(()=>Ht().then(e=>At(e).catch(console.error)));