folder-downloader
Version:
Download specific folders from GitHub repositories without cloning the entire repository
17 lines (13 loc) • 4.76 kB
JavaScript
;var T=Object.create;var v=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var G=Object.getOwnPropertyNames;var L=Object.getPrototypeOf,M=Object.prototype.hasOwnProperty;var V=(e,r,o,t)=>{if(r&&typeof r=="object"||typeof r=="function")for(let n of G(r))!M.call(e,n)&&n!==o&&v(e,n,{get:()=>r[n],enumerable:!(t=z(r,n))||t.enumerable});return e};var f=(e,r,o)=>(o=e!=null?T(L(e)):{},V(r||!e||!e.__esModule?v(o,"default",{value:e,enumerable:!0}):o,e));var A=()=>typeof document>"u"?new URL("file:"+__filename).href:document.currentScript&&document.currentScript.src||new URL("main.js",document.baseURI).href,R=A();var i=f(require("fs-extra")),p=f(require("path")),U=f(require("os")),S=f(require("node-fetch")),D=f(require("unzipper")),a=f(require("chalk")),F=f(require("ora"));var I=require("url"),j=require("path"),q=(0,I.fileURLToPath)(R),X=(0,j.dirname)(q);function B(){return"1.0.33"}function P(){console.log(`
${a.default.bold("GitHub Folder Downloader")}
Usage:
${a.default.cyan("github-folder-downloader")} <repo> <subfolder> [target-folder]
Examples:
${a.default.cyan("github-folder-downloader")} metallicjs/templates saas-lite
${a.default.cyan("github-folder-downloader")} metallicjs/templates#dev src/components ./my-folder
${a.default.cyan("github-folder-downloader")} https://github.com/user/repo/tree/main/src/lib ./lib-copy
Options:
--help Show help
--version Show version
`)}function C(e){let r=/^([\w.-]+)\/([\w.-]+)(?:#(.+))?$/,o=/github\.com\/(.+?)\/(.+?)(?:\.git)?(?:#(.+))?$/,t=/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/(.+)$/,n=e.match(t);if(n){let[,h,u,g,w]=n;return{owner:h,repo:u,branch:g,subfolder:w}}let s=e.match(r);if(s)return{owner:s[1],repo:s[2],branch:s[3]};let l=e.match(o);if(l)return{owner:l[1],repo:l[2],branch:l[3]};throw new Error("Invalid GitHub repo format. Use owner/repo[#branch] or full GitHub folder URL.")}async function W(e,r){let o=await(0,S.default)(`https://api.github.com/repos/${e}/${r}`);if(!o.ok)throw new Error(`Failed to fetch repo metadata: ${o.statusText}`);return(await o.json()).default_branch}async function Z({repoUrl:e,subfolder:r,target:o}){let t=(0,F.default)("Preparing download...").start(),n=null;try{let{owner:s,repo:l,branch:h}=C(e),u=h||await W(s,l),g=`https://github.com/${s}/${l}/archive/refs/heads/${u}.zip`;n=p.default.join(U.default.tmpdir(),`gh-folder-${Date.now()}`);let w=`${l}-${u}/`;t.text=`Fetching ZIP from ${a.default.cyan(h?`${h}`:"default branch")}`;let m=await(0,S.default)(g);if(!m.ok)throw new Error(`Failed to download ZIP: ${m.statusText}`);if(!m.body)throw new Error("Error fetching data");let N=p.default.basename(r.replace(/\/$/,""))||"downloaded-folder",d=p.default.resolve(process.cwd(),o||N);try{if((await i.default.stat(d)).isFile()){t.fail(`Target ${a.default.red(d)} exists as a file. Please remove it or choose another name.`),process.exitCode=1;return}if((await i.default.readdir(d)).length>0){t.fail(`Directory ${a.default.red(d)} already exists and is not empty.`),process.exitCode=1;return}}catch(c){if(c.code!=="ENOENT"){t.fail(`Error checking target path: ${c.message}`),process.exitCode=1;return}}await i.default.ensureDir(n),await i.default.ensureDir(d);let O=m.body.pipe(D.default.Parse({forceStream:!0})),b=0,E=!1,$=r.replace(/\/$/,"");for await(let c of O)try{let x=c.path.replace(w,"");if(x===$||x.startsWith($+"/")){E=!0;let _=p.default.join(d,x.replace($+"/",""));c.type==="Directory"?await i.default.ensureDir(_):(await i.default.ensureDir(p.default.dirname(_)),await new Promise((k,H)=>{c.pipe(i.default.createWriteStream(_)).on("finish",k).on("error",H)}),b++,t.text=`Extracted ${b} files...`)}else c.autodrain()}catch(y){t.warn(`Failed to extract ${c.path}: ${y.message}`)}if(!E){t.fail(`Subfolder "${r}" not found in repo.`),await i.default.remove(n).catch(()=>{}),process.exitCode=1;return}t.succeed(`Successfully downloaded ${a.default.cyan(r)} to ${a.default.green(d)} (${b} files)`)}catch(s){t.fail(s.message),n&&await i.default.remove(n).catch(()=>{}),process.exitCode=1;return}}async function J(){let[,,...e]=process.argv;e.includes("--version")&&(console.log(`github-folder-downloader v${B()}`),process.exit(0)),(e.includes("--help")||e.length===0)&&(P(),process.exit(0));try{let r=e[0],o=C(r),t=o.subfolder,n;if(!o.subfolder&&e.length<2){console.error(a.default.red("Subfolder path is required.")),P(),process.exitCode=1;return}if(t?n=e[1]:(t=e[1],n=e[2]),!t||typeof t!="string"){console.error(a.default.red("Subfolder path is required.")),P(),process.exitCode=1;return}await Z({repoUrl:`${o.owner}/${o.repo}${o.branch?`#${o.branch}`:""}`,subfolder:t,target:n})}catch(r){console.error(a.default.red("Error:"),r.message),process.exitCode=1;return}}J();