UNPKG

@cmtlyt/git-down

Version:
4 lines (3 loc) 7.52 kB
#!/usr/bin/env node import{defineCommand as x,runMain as G}from"citty";import{existsSync as h,rmSync as q}from"node:fs";import{extname as U,resolve as f}from"node:path";import{exit as w,stdin as L,stdout as I}from"node:process";import{createInterface as M}from"node:readline";import{exec as P}from"node:child_process";import{mkdir as H,rm as O,cp as W,rename as T,readFile as z,writeFile as _}from"node:fs/promises";import{promisify as J}from"node:util";const K="0.3.1",p=J(P),B=new Map;async function A(u){if(!h(u))return H(u,{recursive:!0})}async function $(u){if(h(u))return O(u,{recursive:!0,force:!0}).catch(()=>{})}function N(u){const t=u?.branch?.trim();return{output:u?.output??"./git-down",branch:t??""}}function Q(u){return t=>{if(t){if(!u)throw t;u(t)}u&&u(null)}}function j(u){const t=F=>F.replace(/\.git$/,""),n=u.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)(?:\/(tree|blob)\/(.+))?/),e=u.match(/^git@github\.com:([^/]+)\/([^/]+)(?:\/(tree|blob)\/(.+))?/),o=n??e;if(!o)return{href:u,owner:"",project:t(u),isRepo:!0,sourceType:"dir",branch:"",pathname:""};const[,r,c,i,a=""]=o,l=t(c),s=a?a.split(/[?#]/)[0]:"";let E="",d="";if(s){const F=s.indexOf("/");F===-1?E=s:(E=s.slice(0,F),d=s.slice(F+1))}const D=!i,g=d.replace(/^\//,""),m=i==="blob"||g&&U(g)?"file":"dir";return{href:u,owner:r,project:l,isRepo:D,sourceType:m,branch:E,pathname:g}}function V(u){return u.replace(/^\/+/,"")}function b(u){return u.replace(/^\/+|\/+$/g,"")}function v(u,t){if(!t)return;const n=b(u.branch),e=b(t);u.branch=e;const o=V(u.pathname);if(!o)return;if(o===e){u.pathname="";return}if(o.startsWith(`${e}/`)){u.pathname=o.slice(e.length+1);return}if(!n||n===e||!e.startsWith(`${n}/`))return;const r=e.slice(n.length),c=b(r);if(c){if(o===c){u.pathname="";return}o.startsWith(`${c}/`)&&(u.pathname=o.slice(c.length+1))}}async function X(u,t){const n=`${u}/${t}`;if(B.has(n))return B.get(n);const e=p(`git ls-remote --heads https://github.com/${u}/${t}`).then(({stdout:o})=>{const r=new Set,c=/^refs\/heads\/(.+)$/;return o.split(/\r?\n/).map(i=>i.trim()).filter(Boolean).forEach(i=>{const[,a]=i.split(/\s+/);if(!a)return;const l=a.match(c);l?.[1]&&r.add(l[1])}),r}).catch(()=>new Set);return B.set(n,e),e}async function Y(u,t,n=X){const e=t?.trim();if(e)return e;const o=b(u.branch);if(!o||!u.owner||!u.project||!u.pathname)return u.branch;const r=u.pathname.split("/").filter(Boolean);if(!r.length)return u.branch;let c;try{c=await n(u.owner,u.project)}catch{return u.branch}if(!c.size)return u.branch;for(let i=r.length-1;i>=0;i--){const a=b([o,...r.slice(0,i)].join("/"));if(!a||!c.has(a))continue;const l=r.slice(i);if(l.length)return u.branch=a,u.pathname=l.join("/"),u.branch}return u.branch}async function k(u,t){return h(t)?W(u,t,{recursive:!0}):T(u,t)}function Z(u){const{gitInfo:t,option:n,callback:e}=u,{branch:o,output:r}=n,{owner:c,project:i}=t,a={cwd:f(r)};return p("git init --quiet",a).then(()=>p(`git remote add origin https://github.com/${c}/${i}`,a),e).then(()=>p(`git pull origin --quiet ${o} --depth 1`,a),e).then(()=>$(f(r,".git")),e)}async function uu(u){const{gitInfo:t,callback:n,option:e}=u,{output:o}=e,{pathname:r,sourceType:c,branch:i,owner:a,project:l}=t,s=f(o,`./.git-down-temp-folder-${Date.now()}-${Math.random().toString(36).slice(2)}`);h(s)&&$(s),await A(s).catch(n);const E=f(s,".git/info/sparse-checkout"),d=c==="file"?r:`${r}/*`,D=`${h(E)?await z(E,{encoding:"utf-8"}):""} ${d}`,g=f(o,r.split("/").pop()),m={cwd:s},F=`remote-${Date.now()}-${Math.random().toString(36).slice(2)}`;return p("git init --quiet",m).then(()=>p(`git remote add ${F} https://github.com/${a}/${l}`,m),n).then(async()=>{try{if((await p("git config --get core.sparseCheckout",m)).stdout.trim()==="true")return}catch{}return p("git config core.sparseCheckout true",m)},n).then(()=>_(E,D),n).then(()=>p(`git pull ${F} --quiet ${i} --depth 1`,m),n).then(()=>{const C=f(s,r);if(h(C))return k(C,g);const y=f(s,r.split("/").pop());if(h(y))return k(y,g);throw new Error(`\u65E0\u6CD5\u5728\u4E0B\u8F7D\u76EE\u5F55\u4E2D\u627E\u5230\u76EE\u6807\u8D44\u6E90: ${r}`)},n).then(()=>$(s),n)}function tu(u,t,n){const e=N(t),o=Q(n),r=j(u);Y(r,e.branch).then(c=>{let i=c||e.branch||r.branch;i||(i="main"),v(r,i),e.branch=r.branch;const a={option:e,callback:o,gitInfo:r};return A(e.output).then(()=>(r.isRepo?Z:uu)(a),o).then(()=>o(null),o)},o).catch(o)}function nu(u,t){return new Promise((n,e)=>{tu(u,t,o=>{o?e(o):n()})})}function eu(u){return!u||typeof u!="string"?!1:/^https?:\/\/github\.com\/[\w.-]+\/[\w.-]+/.test(u)}function R(u,t){return typeof u=="string"&&u.trim()||t}function ou(u){const t=u.url||u._[0];return t||(console.error("\u274C \u9519\u8BEF: \u5FC5\u987B\u63D0\u4F9BURL\u53C2\u6570"),console.log("\u4F7F\u7528 --help \u67E5\u770B\u4F7F\u7528\u8BF4\u660E"),w(1)),eu(t)||(console.error("\u274C \u9519\u8BEF: GitHub URL\u683C\u5F0F\u65E0\u6548"),console.log("\u8BF7\u63D0\u4F9B\u6709\u6548\u7684GitHub\u4ED3\u5E93\u3001\u6587\u4EF6\u6216\u76EE\u5F55URL"),w(1)),t}async function ru(u){const t=M({input:L,output:I});return new Promise(n=>{t.question(`\u76EE\u5F55 "${u}" \u5DF2\u5B58\u5728\u3002\u662F\u5426\u5220\u9664? (y/n): `,e=>{t.close(),n(e.toLowerCase()==="y")})})}async function iu(u,t){const n=R(u.output,`./${u.name??t.project}`);return h(n)&&(console.log(`\u26A0\uFE0F \u8B66\u544A: \u8F93\u51FA\u76EE\u5F55\u5DF2\u5B58\u5728: ${n}`),await ru(n)||(console.log("\u274C \u64CD\u4F5C\u53D6\u6D88"),w(1)),S(n)),n}function cu(u,t){const n=u.owner?`${u.owner}/${u.project}`:u.project,e=t||"main";console.log(`\u{1F4E6} \u4ED3\u5E93: ${n}`),console.log(`\u{1F33F} \u5206\u652F: ${e}`),u.pathname&&console.log(`\u{1F5C2}\uFE0F \u76EE\u6807: ${u.pathname}`)}function S(u){if(!u)return;const t=f(u);if(h(t))try{q(t,{recursive:!0}),console.log(`\u{1F5D1}\uFE0F \u5DF2\u6E05\u7406\u4E0D\u5B8C\u6574\u7684\u4E0B\u8F7D\u76EE\u5F55: ${u}`)}catch(n){console.error(`\u26A0\uFE0F \u6E05\u7406\u76EE\u5F55\u5931\u8D25: ${String(n)}`)}}async function au(u,t){console.log(`\u{1F680} \u5F00\u59CB\u4ECE\u4EE5\u4E0B\u5730\u5740\u4E0B\u8F7D: ${u}`),console.log(`\u{1F4C1} \u8F93\u51FA\u76EE\u5F55: ${t.output}`),await nu(u,t),console.log("\u2705 \u4E0B\u8F7D\u6210\u529F\u5B8C\u6210!")}async function su(u){let t="";try{const n=ou(u),e=j(n),o=R(u.branch,e.branch||"main");v(e,o),t=await iu(u,e);const r={output:t,branch:o};cu(e,r.branch||""),await au(n,r)}catch(n){console.error("\u274C \u4E0B\u8F7D\u5931\u8D25:",n instanceof Error?n.message:String(n)),S(t),w(1)}}const lu=x({meta:{name:"gd",description:"\u4E0B\u8F7D GitHub \u4ED3\u5E93\u6587\u4EF6\u6216\u76EE\u5F55",version:K},args:{url:{type:"positional",description:"GitHub \u4ED3\u5E93URL\u6216\u6587\u4EF6/\u76EE\u5F55URL",required:!0},output:{type:"string",description:"\u6307\u5B9A\u5B8C\u6574\u7684\u8F93\u51FA\u8DEF\u5F84\uFF0C\u652F\u6301\u76F8\u5BF9\u6216\u7EDD\u5BF9\u8DEF\u5F84 (\u4F8B\u5982: ./path \u6216 /path)",alias:["o","out"]},branch:{type:"string",description:"\u6307\u5B9A\u8981\u4E0B\u8F7D\u7684\u5206\u652F\u540D\u79F0",alias:["b"]},help:{type:"boolean",description:"\u663E\u793A\u5E2E\u52A9\u4FE1\u606F",alias:["h"],default:!1},name:{type:"positional",description:"\u7B80\u4FBF\u65B9\u5F0F\uFF1A\u6307\u5B9A\u8F93\u51FA\u6587\u4EF6\u5939\u540D\u79F0 (\u7C7B\u4F3C git clone \u7684\u7528\u6CD5)",required:!1}},async run({args:u}){console.warn("\u4E3B\u5206\u652F\u9ED8\u8BA4\u662F main\uFF0C\u5982\u679C\u9700\u8981\u4E0B\u8F7D\u5176\u4ED6\u5206\u652F\uFF0C\u8BF7\u4F7F\u7528 -b \u53C2\u6570\u6307\u5B9A\u5206\u652F\u540D\u79F0"),await su(u)}});G(lu);