@cmtlyt/git-down
Version:
3 lines (2 loc) • 5.06 kB
JavaScript
;Object.defineProperty(exports,"__esModule",{value:!0});const node_path=require("node:path"),node_child_process=require("node:child_process"),node_fs=require("node:fs"),promises=require("node:fs/promises"),node_util=require("node:util"),exec=node_util.promisify(node_child_process.exec),b=new Map;async function createOutput(t){if(!node_fs.existsSync(t))return promises.mkdir(t,{recursive:!0})}async function rmdir(t){if(node_fs.existsSync(t))return promises.rm(t,{recursive:!0,force:!0}).catch(()=>{})}function buildOption(t){const r=t?.branch?.trim();return{output:t?.output??"./git-down",branch:r??""}}function buildCallback(t){return r=>{if(r){if(!t)throw r;t(r)}t&&t(null)}}function parseGitUrl(t){const r=l=>l.replace(/\.git$/,""),i=t.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)(?:\/(tree|blob)\/(.+))?/),n=t.match(/^git@github\.com:([^/]+)\/([^/]+)(?:\/(tree|blob)\/(.+))?/),e=i??n;if(!e)return{href:t,owner:"",project:r(t),isRepo:!0,sourceType:"dir",branch:"",pathname:""};const[,o,s,c,a=""]=e,h=r(s),u=a?a.split(/[?#]/)[0]:"";let p="",g="";if(u){const l=u.indexOf("/");l===-1?p=u:(p=u.slice(0,l),g=u.slice(l+1))}const w=!c,d=g.replace(/^\//,""),f=c==="blob"||d&&node_path.extname(d)?"file":"dir";return{href:t,owner:o,project:h,isRepo:w,sourceType:f,branch:p,pathname:d}}function z(t){return t.replace(/^\/+/,"")}function m(t){return t.replace(/^\/+|\/+$/g,"")}function reconcileGitInfoBranch(t,r){if(!r)return;const i=m(t.branch),n=m(r);t.branch=n;const e=z(t.pathname);if(!e)return;if(e===n){t.pathname="";return}if(e.startsWith(`${n}/`)){t.pathname=e.slice(n.length+1);return}if(!i||i===n||!n.startsWith(`${i}/`))return;const o=n.slice(i.length),s=m(o);if(s){if(e===s){t.pathname="";return}e.startsWith(`${s}/`)&&(t.pathname=e.slice(s.length+1))}}async function v(t,r){const i=`${t}/${r}`;if(b.has(i))return b.get(i);const n=exec(`git ls-remote --heads https://github.com/${t}/${r}`).then(({stdout:e})=>{const o=new Set,s=/^refs\/heads\/(.+)$/;return e.split(/\r?\n/).map(c=>c.trim()).filter(Boolean).forEach(c=>{const[,a]=c.split(/\s+/);if(!a)return;const h=a.match(s);h?.[1]&&o.add(h[1])}),o}).catch(()=>new Set);return b.set(i,n),n}async function resolveBranchFromRemote(t,r,i=v){const n=r?.trim();if(n)return n;const e=m(t.branch);if(!e||!t.owner||!t.project||!t.pathname)return t.branch;const o=t.pathname.split("/").filter(Boolean);if(!o.length)return t.branch;let s;try{s=await i(t.owner,t.project)}catch{return t.branch}if(!s.size)return t.branch;for(let c=o.length-1;c>=0;c--){const a=m([e,...o.slice(0,c)].join("/"));if(!a||!s.has(a))continue;const h=o.slice(c);if(h.length)return t.branch=a,t.pathname=h.join("/"),t.branch}return t.branch}async function moveOrCopyAndCleanup(t,r){return node_fs.existsSync(r)?promises.cp(t,r,{recursive:!0}):promises.rename(t,r)}function downloadMain(t){const{gitInfo:r,option:i,callback:n}=t,{branch:e,output:o}=i,{owner:s,project:c}=r,a={cwd:node_path.resolve(o)};return exec("git init --quiet",a).then(()=>exec(`git remote add origin https://github.com/${s}/${c}`,a),n).then(()=>exec(`git pull origin --quiet ${e} --depth 1`,a),n).then(()=>rmdir(node_path.resolve(o,".git")),n)}async function downloadPartial(t){const{gitInfo:r,callback:i,option:n}=t,{output:e}=n,{pathname:o,sourceType:s,branch:c,owner:a,project:h}=r,u=node_path.resolve(e,`./.git-down-temp-folder-${Date.now()}-${Math.random().toString(36).slice(2)}`);node_fs.existsSync(u)&&rmdir(u),await createOutput(u).catch(i);const p=node_path.resolve(u,".git/info/sparse-checkout"),g=s==="file"?o:`${o}/*`,w=`${node_fs.existsSync(p)?await promises.readFile(p,{encoding:"utf-8"}):""}
${g}`,d=node_path.resolve(e,o.split("/").pop()),f={cwd:u},l=`remote-${Date.now()}-${Math.random().toString(36).slice(2)}`;return exec("git init --quiet",f).then(()=>exec(`git remote add ${l} https://github.com/${a}/${h}`,f),i).then(async()=>{try{if((await exec("git config --get core.sparseCheckout",f)).stdout.trim()==="true")return}catch{}return exec("git config core.sparseCheckout true",f)},i).then(()=>promises.writeFile(p,w),i).then(()=>exec(`git pull ${l} --quiet ${c} --depth 1`,f),i).then(()=>{const $=node_path.resolve(u,o);if(node_fs.existsSync($))return moveOrCopyAndCleanup($,d);const y=node_path.resolve(u,o.split("/").pop());if(node_fs.existsSync(y))return moveOrCopyAndCleanup(y,d);throw new Error(`\u65E0\u6CD5\u5728\u4E0B\u8F7D\u76EE\u5F55\u4E2D\u627E\u5230\u76EE\u6807\u8D44\u6E90: ${o}`)},i).then(()=>rmdir(u),i)}function gitDownWithCallback(t,r,i){const n=buildOption(r),e=buildCallback(i),o=parseGitUrl(t);resolveBranchFromRemote(o,n.branch).then(s=>{let c=s||n.branch||o.branch;c||(c="main"),reconcileGitInfoBranch(o,c),n.branch=o.branch;const a={option:n,callback:e,gitInfo:o};return createOutput(n.output).then(()=>(o.isRepo?downloadMain:downloadPartial)(a),e).then(()=>e(null),e)},e).catch(e)}function gitDown(t,r){return new Promise((i,n)=>{gitDownWithCallback(t,r,e=>{e?n(e):i()})})}exports.default=gitDown,exports.gitDown=gitDown,exports.gitDownWithCallback=gitDownWithCallback,exports.parseGitUrl=parseGitUrl;