gitpick 4.20.0-canary.5 → 4.20.0-canary.7

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.
package/README.md CHANGED
@@ -15,6 +15,8 @@
15
15
 
16
16
  Unlike other tools that force you to tweak URLs or follow strict formats to clone files, folders, branches or commits GitPick works seamlessly with any URL.
17
17
 
18
+ > šŸ”„ **New in v5.0** — [Interactive mode](#-interactive-mode): browse any repo's file tree in your terminal and cherry-pick exactly what you need with `gitpick owner/repo -i`
19
+
18
20
  <img width="400" alt="GitPick Meme" src="https://github.com/user-attachments/assets/180c3e5b-320c-48d7-aaf9-a7402e74c882" />
19
21
 
20
22
  ---
@@ -24,8 +26,8 @@ Unlike other tools that force you to tweak URLs or follow strict formats to clon
24
26
  - [Some Examples](#-some-examples)
25
27
  - [Features](#-features)
26
28
  - [Quick Usage](#-quick-usage)
27
- - [Interactive Mode](#-interactive-mode)
28
29
  - [Options](#-options)
30
+ - [Interactive Mode](#-interactive-mode)
29
31
  - [Private Repos](#-private-repos)
30
32
  - [Config File](#-config-file)
31
33
  - [Install Globally](#-install-globally-optional)
@@ -126,6 +128,25 @@ npx gitpick https://bitbucket.org/owner/repo # Bitbucket
126
128
 
127
129
  ---
128
130
 
131
+ ## šŸ”§ Options
132
+
133
+ ```
134
+ -b, --branch Branch/SHA to clone
135
+ -i, --interactive Browse and pick files/folders interactively
136
+ -n, --dry-run Show what would be cloned without cloning
137
+ -o, --overwrite Skip overwrite prompt
138
+ -r, --recursive Clone submodules
139
+ -w, --watch [time] Watch the repository and sync every [time]
140
+ (e.g. 1h, 30m, 15s)
141
+ --tree List copied files as a tree
142
+ -q, --quiet Suppress all output except errors
143
+ --verbose Show detailed clone information
144
+ -h, --help display help for command
145
+ -v, --version display the version number
146
+ ```
147
+
148
+ ---
149
+
129
150
  ## šŸ”„ Interactive Mode
130
151
 
131
152
  > **New in v5.0** — Browse any repository's file tree in your terminal and cherry-pick exactly the files and folders you want.
@@ -177,25 +198,6 @@ Works with GitHub, GitLab, Bitbucket, public and private repos.
177
198
 
178
199
  ---
179
200
 
180
- ## šŸ”§ Options
181
-
182
- ```
183
- -b, --branch Branch/SHA to clone
184
- -i, --interactive Browse and pick files/folders interactively
185
- -n, --dry-run Show what would be cloned without cloning
186
- -o, --overwrite Skip overwrite prompt
187
- -r, --recursive Clone submodules
188
- -w, --watch [time] Watch the repository and sync every [time]
189
- (e.g. 1h, 30m, 15s)
190
- --tree List copied files as a tree
191
- -q, --quiet Suppress all output except errors
192
- --verbose Show detailed clone information
193
- -h, --help display help for command
194
- -v, --version display the version number
195
- ```
196
-
197
- ---
198
-
199
201
  ## šŸ” Private Repos
200
202
 
201
203
  Use a personal access token with read-only contents permission. Works with GitHub, GitLab and Bitbucket:
package/dist/index.mjs CHANGED
@@ -10,7 +10,7 @@ import e from"node:fs";import t from"node:os";import n from"node:path";import{pa
10
10
  `;let C=E(`↑↓:navigate enter:expand space:select c:confirm q:quit`);m+=` ${C}\n`,s.write(m)}function m(e){let t=ie(r),i=t.length+1,o=e.toString();if(o===``||o===`q`||o===`Q`){u(),process.removeListener(`exit`,d),process.removeListener(`SIGINT`,f),n([]);return}if(o===`c`||o===`C`){u(),process.removeListener(`exit`,d),process.removeListener(`SIGINT`,f),n(ae(r));return}if((o===`\x1B[A`||o===`k`)&&a>0&&a--,(o===`\x1B[B`||o===`j`)&&a<i-1&&a++,a===0&&(o===` `||o===`\r`)){let e=r.every(e=>e.selected);for(let t of r)z(t,!e)}if(o===` `&&a>0){let e=t[a-1];e&&z(e.node,!e.node.selected)}if(o===`\r`&&a>0){let e=t[a-1];e&&e.node.type===`tree`&&(e.node.expanded=!e.node.expanded)}if((o===`\x1B[C`||o===`l`)&&a>0){let e=t[a-1];e&&e.node.type===`tree`&&(e.node.expanded=!0)}if((o===`\x1B[D`||o===`h`)&&a>0){let e=t[a-1];e&&e.node.type===`tree`&&(e.node.expanded=!1)}p()}c.on(`data`,m),p()})}function B(e){if(typeof e==`number`||/^\d+$/.test(e))return typeof e==`number`?e:parseInt(e,10);let t=/(\d+)([hms])/g,n=0,r;for(;(r=t.exec(e))!==null;){let e=parseInt(r[1],10);switch(r[2]){case`h`:n+=e*36e5;break;case`m`:n+=e*6e4;break;case`s`:n+=e*1e3;break}}return n}const V=async e=>{let t=(await S(`git`,[`ls-remote`,e])).stdout,n=t.match(/(.+)\s+HEAD/)?.[1],r=t.match(RegExp(`${n}\\s+refs/heads/(.+)`))?.[1];if(!r)throw Error(`Could not determine default branch!`);return r},H=[{prefix:`git@github.com:`,host:`github.com`},{prefix:`https://github.com/`,host:`github.com`},{prefix:`https://raw.githubusercontent.com/`,host:`github.com`},{prefix:`git@gitlab.com:`,host:`gitlab.com`},{prefix:`https://gitlab.com/`,host:`gitlab.com`},{prefix:`git@bitbucket.org:`,host:`bitbucket.org`},{prefix:`https://bitbucket.org/`,host:`bitbucket.org`}];async function le(e,{branch:t,target:n}){let r=e.match(/^https:\/\/([^@]+)@(github\.com|gitlab\.com|bitbucket\.org)/),i=``;if(r)i=r[1],e=e.replace(`${r[1]}@`,``);else{let t={"github.com":process.env.GITHUB_TOKEN||process.env.GH_TOKEN||``,"gitlab.com":process.env.GITLAB_TOKEN||``,"bitbucket.org":process.env.BITBUCKET_TOKEN||``};for(let{prefix:n,host:r}of H)if(e.startsWith(n)){i=t[r];break}!i&&!e.startsWith(`https://`)&&!e.startsWith(`git@`)&&(i=t[`github.com`])}let a=`github.com`;for(let{prefix:t,host:n}of H)if(e.startsWith(t)){a=n,e=e.replace(t,``);break}let o=e.split(`/`),s=o[0],c=o[1]?.endsWith(`.git`)?o[1].slice(0,-4):o[1],l=`https://${i&&i+`@`}${a}/${s}/${c}`,u,d,f;a===`github.com`?o[2]===`refs`&&[`heads`,`tags`].includes(o[3])?(u=`raw`,d=t||o[4],f=o.slice(5).join(`/`)):o[2]===`refs`&&o[3]===`remotes`?(u=`raw`,d=t||`${o[4]}/${o[5]}`,f=o.slice(6).join(`/`)):o[2]===`blob`?(u=`blob`,d=t||o[3],f=o.slice(4).join(`/`)):o[2]===`tree`?(u=`tree`,d=t||o[3],f=o.slice(4).join(`/`)):o[2]===`commit`?(u=`repository`,d=t||o[3],f=``):(u=`repository`,d=t||await V(l),f=``):a===`gitlab.com`?o[2]===`-`&&o[3]===`blob`?(u=`blob`,d=t||o[4],f=o.slice(5).join(`/`)):o[2]===`-`&&o[3]===`tree`?(u=`tree`,d=t||o[4],f=o.slice(5).join(`/`)):(u=`repository`,d=t||await V(l),f=``):o[2]===`src`?(u=`tree`,d=t||o[3],f=o.slice(4).join(`/`)):(u=`repository`,d=t||await V(l),f=``);let p=n||(u===`blob`?`.`:f.split(`/`).pop()||c);return{token:i,host:a,owner:s,repository:c,type:u,branch:d,path:f,target:p}}const U=n.join(t.homedir(),`.cache`,`gitpick`),W=n.join(U,`update-check.json`);function G(){try{return JSON.parse(e.readFileSync(W,`utf-8`))}catch{return null}}function ue(t){try{e.mkdirSync(U,{recursive:!0}),e.writeFileSync(W,JSON.stringify(t))}catch{}}function de(){return new Promise(e=>{let t=d.get(`https://registry.npmjs.org/gitpick/latest`,{headers:{Accept:`application/json`},timeout:3e3},t=>{if(t.statusCode!==200)return t.resume(),e(null);let n=``;t.on(`data`,e=>n+=e),t.on(`end`,()=>{try{e(JSON.parse(n).version||null)}catch{e(null)}})});t.on(`error`,()=>e(null)),t.on(`timeout`,()=>{t.destroy(),e(null)})})}function fe(e,t){let n=e.split(`.`).map(Number),r=t.split(`.`).map(Number);for(let e=0;e<3;e++){if((n[e]||0)>(r[e]||0))return!0;if((n[e]||0)<(r[e]||0))return!1}return!1}function K(e,t){if(t)return;let n=G();n&&fe(n.latestVersion,e)&&console.log(E(`\n Update available: ${k(e)} → ${A(T(n.latestVersion))}\n Run ${A(`npm i -g gitpick`)} to update\n`))}function pe(){let e=G();e&&Date.now()-e.lastCheck<864e5||setTimeout(async()=>{let e=await de();e&&ue({lastCheck:Date.now(),latestVersion:e})},0)}const q=Symbol(`singleComment`),J=Symbol(`multiComment`),Y=(e,t,n)=>e.slice(t,n).replace(/[^ \t\r\n]/g,` `),me=(e,t)=>{let n=t-1,r=0;for(;e[n]===`\\`;)--n,r+=1;return!!(r%2)};function he(e){if(typeof e!=`string`)throw TypeError(`Expected argument \`jsonString\` to be a \`string\`, got \`${typeof e}\``);let t=!1,n=!1,r=0,i=``,a=``,o=-1;for(let s=0;s<e.length;s++){let c=e[s],l=e[s+1];if(!n&&c===`"`&&(me(e,s)||(t=!t)),!t)if(!n&&c+l===`//`)i+=e.slice(r,s),r=s,n=q,s++;else if(n===q&&c+l===`\r
11
11
  `){s++,n=!1,i+=Y(e,r,s),r=s;continue}else if(n===q&&c===`
12
12
  `)n=!1,i+=Y(e,r,s),r=s;else if(!n&&c+l===`/*`){i+=e.slice(r,s),r=s,n=J,s++;continue}else if(n===J&&c+l===`*/`){s++,n=!1,i+=Y(e,r,s+1),r=s+1;continue}else n||(o===-1?c===`,`&&(a+=i+e.slice(r,s),i=``,r=s,o=s):c===`}`||c===`]`?(i+=e.slice(r,s),a+=Y(i,0,1)+i.slice(1),i=``,r=s,o=-1):c!==` `&&c!==` `&&c!==`\r`&&c!==`
13
- `&&(i+=e.slice(r,s),r=s,o=-1))}let s=n===q?Y(e,r):e.slice(r);return a+i+s}const ge=[`.gitpick.json`,`.gitpick.jsonc`],_e=async()=>{let t;for(let r of ge){let i=n.resolve(r);if(e.existsSync(i)){t=i;break}}if(!t)return!1;let r=await e.promises.readFile(t,`utf-8`),i=JSON.parse(he(r));if(!Array.isArray(i)||!i.every(e=>typeof e==`string`))throw Error(`${n.basename(t)} must be an array of strings`);for(let e of i)await S(process.argv[0],[...process.argv.slice(1),...e.split(/\s+/),`-o`],{stdio:`inherit`});return!0};var ve=`gitpick`,X=`4.20.0-canary.5`;const Z=(e,t)=>`\x1b]8;;${t}\x07${e}\x1b]8;;\x07`,ye=`
13
+ `&&(i+=e.slice(r,s),r=s,o=-1))}let s=n===q?Y(e,r):e.slice(r);return a+i+s}const ge=[`.gitpick.json`,`.gitpick.jsonc`],_e=async()=>{let t;for(let r of ge){let i=n.resolve(r);if(e.existsSync(i)){t=i;break}}if(!t)return!1;let r=await e.promises.readFile(t,`utf-8`),i=JSON.parse(he(r));if(!Array.isArray(i)||!i.every(e=>typeof e==`string`))throw Error(`${n.basename(t)} must be an array of strings`);for(let e of i)await S(process.argv[0],[...process.argv.slice(1),...e.split(/\s+/),`-o`],{stdio:`inherit`});return!0};var ve=`gitpick`,X=`4.20.0-canary.7`;const Z=(e,t)=>`\x1b]8;;${t}\x07${e}\x1b]8;;\x07`,ye=`
14
14
  With ${T(`${Z(`GitPick`,`https://github.com/nrjdalal/gitpick`)}`)} clone specific directories or files from GitHub, GitLab and Bitbucket!
15
15
 
16
16
  $ gitpick ${k(`<url>`)} ${O(`[target]`)} ${A(`[options]`)}
@@ -48,8 +48,8 @@ ${T(`Examples:`)}
48
48
  $ gitpick https://bitbucket.org/owner/repo
49
49
 
50
50
  šŸš€ More awesome tools at ${A(`https://github.com/nrjdalal`)}`,Q=e=>{let r=process.cwd(),i=t.homedir(),a=n.sep;return e===r?`.`:e.startsWith(r+a)?`./`+n.relative(r,e).replaceAll(a,`/`):e.startsWith(i+a)?`~/`+n.relative(i,e).replaceAll(a,`/`):e},$=async(t,r=``)=>{let i=(await e.promises.readdir(t,{withFileTypes:!0})).filter(e=>e.name!==`.git`).sort((e,t)=>e.name.localeCompare(t.name,void 0,{sensitivity:`base`}));for(let a=0;a<i.length;a++){let o=i[a],s=a===i.length-1,c=s?`└── `:`ā”œā”€ā”€ `,l=n.join(t,o.name);if(o.isSymbolicLink()){let t=await e.promises.readlink(l),n=!1;try{n=e.statSync(l).isDirectory()}catch{}process.stdout.write(`${r}${c}${k(o.name)} -> ${n?A(t):t}\n`)}else o.isDirectory()?(process.stdout.write(`${r}${c}${A(o.name)}\n`),await $(l,`${r}${s?` `:`│ `}`)):process.stdout.write(`${r}${c}${o.name}\n`)}},be=e=>{try{return r(e)}catch(e){throw Error(`Error parsing arguments: ${e.message}`)}};(async()=>{pe();try{let{positionals:r,values:i}=be({allowPositionals:!0,options:{branch:{type:`string`,short:`b`},"dry-run":{type:`boolean`,short:`n`},force:{type:`boolean`,short:`f`},help:{type:`boolean`,short:`h`},interactive:{type:`boolean`,short:`i`},quiet:{type:`boolean`,short:`q`},tree:{type:`boolean`},verbose:{type:`boolean`},overwrite:{type:`boolean`,short:`o`},recursive:{type:`boolean`,short:`r`},version:{type:`boolean`,short:`v`},watch:{type:`string`,short:`w`}}});r.length||(i.version&&(console.log(`\n${ve}@${X}`),process.exit(0)),await _e()&&process.exit(0),console.log(ye),process.exit(0)),r[0]===`clone`&&r.shift();let[a,o]=r,s={branch:i.branch,dryRun:i[`dry-run`],force:i.force,interactive:i.interactive,quiet:i.quiet,tree:i.tree,verbose:i.verbose,overwrite:i.overwrite,recursive:i.recursive,watch:i.watch},c=s.tree||s.quiet;c||console.log(`\nWith ${T(`${Z(`GitPick`,`https://github.com/nrjdalal/gitpick`)}`)} clone specific files, folders, branches,\ncommits and much more from GitHub, GitLab and Bitbucket!`);let l=await le(a,{branch:s.branch,target:o});if(l.type===`blob`){let e=l.target.split(/[/\\]/).filter(e=>e!==``),t=e[e.length-1];t!==`.`&&t!==`..`&&t.includes(`.`)?e.pop():t=l.path.split(`/`).pop()||t,l.target=[...e,t].join(`/`)}c||console.info(`\n${O(`āœ”`)} ${l.owner}/${l.repository} ${A(l.type+`:`+l.branch)} ${l.type===`repository`?`> ${O(l.target)}`:`${l.path.length?k(l.path)+` >`:`>`} ${O(l.target)}`}`);let u=n.resolve(l.target);if(s.interactive){if(!process.stdout.isTTY)throw Error(`Interactive mode requires a TTY`);let r=n.resolve(t.tmpdir(),`gitpick-interactive-${Date.now()}${Math.random().toString(16).slice(2,6)}`),i=`https://${l.token?l.token+`@`:``}${l.host}/${l.owner}/${l.repository}.git`,a=N();a.start(`Fetching ${l.owner}/${l.repository}...`);try{await S(`git`,[`clone`,i,r,`--branch`,l.branch,`--depth`,`1`,`--single-branch`,...s.recursive?[`--recursive`]:[]])}catch{await S(`git`,[`clone`,i,r,...s.recursive?[`--recursive`]:[]]),await S(`git`,[`checkout`,l.branch],{cwd:r})}let o=[];async function c(t,r){let i=await e.promises.readdir(t,{withFileTypes:!0});for(let a of i){if(a.name===`.git`)continue;let i=r?`${r}/${a.name}`:a.name,s=n.join(t,a.name);if(a.isDirectory())o.push({path:i,type:`tree`}),await c(s,i);else{let t=await e.promises.stat(s);o.push({path:i,type:`blob`,size:t.size})}}}await c(r,``),a.success(`Fetched ${l.owner}/${l.repository} (${o.length} entries)`),o.length||(await e.promises.rm(r,{recursive:!0,force:!0}),console.log(k(`
51
- Repository has no files.`)),process.exit(0));let d=await ce(o,`${l.owner}/${l.repository} ${A(`repository:`+l.branch)} > ${O(l.target)}`);d.length||(await e.promises.rm(r,{recursive:!0,force:!0}),console.log(`
52
- No files selected.`),process.exit(0)),console.log(`\n${O(`āœ”`)} Picking ${d.length} selected path${d.length===1?``:`s`}...`),e.existsSync(u)&&!s.overwrite&&(await e.promises.readdir(u)).length&&(await e.promises.rm(r,{recursive:!0,force:!0}),console.log(`${k(`\nWarning: The target directory exists at ${O(l.target)} and is not empty. Use ${A(`-f`)} or ${A(`-o`)} to overwrite.`)}`),process.exit(1)),await e.promises.mkdir(u,{recursive:!0});let f=0;for(let t of d){let i=n.join(r,t),a=n.join(u,t),o=await e.promises.stat(i).catch(()=>null);if(o)if(o.isDirectory()){await e.promises.mkdir(a,{recursive:!0});let t=await P(i,a);f+=t.length}else await e.promises.mkdir(n.dirname(a),{recursive:!0}),await e.promises.copyFile(i,a),f++}await e.promises.rm(r,{recursive:!0,force:!0}),console.log(O(`āœ” Copied ${f} file${f===1?``:`s`} to ${Q(u)}`)),s.tree&&(process.stdout.write(`\n${T(A(Q(u)))}\n`),await $(u),process.stdout.write(`
51
+ Repository has no files.`)),process.exit(0));let d=await ce(o,`${l.owner}/${l.repository} ${A(`repository:`+l.branch)} > ${O(l.target)}`);if(d.length||(await e.promises.rm(r,{recursive:!0,force:!0}),console.log(`
52
+ No files selected.`),process.exit(0)),s.dryRun){console.log(`\n${O(`āœ”`)} Would pick ${d.length} path${d.length===1?``:`s`}:`);for(let e of d)console.log(` ${e}`);await e.promises.rm(r,{recursive:!0,force:!0}),console.log(),K(X,!1),process.exit(0)}console.log(`\n${O(`āœ”`)} Picking ${d.length} selected path${d.length===1?``:`s`}...`),e.existsSync(u)&&!s.overwrite&&(await e.promises.readdir(u)).length&&(await e.promises.rm(r,{recursive:!0,force:!0}),console.log(`${k(`\nWarning: The target directory exists at ${O(l.target)} and is not empty. Use ${A(`-f`)} or ${A(`-o`)} to overwrite.`)}`),process.exit(1)),await e.promises.mkdir(u,{recursive:!0});let f=0;for(let t of d){let i=n.join(r,t),a=n.join(u,t),o=await e.promises.stat(i).catch(()=>null);if(o)if(o.isDirectory()){await e.promises.mkdir(a,{recursive:!0});let t=await P(i,a);f+=t.length}else await e.promises.mkdir(n.dirname(a),{recursive:!0}),await e.promises.copyFile(i,a),f++}await e.promises.rm(r,{recursive:!0,force:!0}),console.log(O(`āœ” Copied ${f} file${f===1?``:`s`} to ${Q(u)}`)),s.tree&&(process.stdout.write(`\n${T(A(Q(u)))}\n`),await $(u),process.stdout.write(`
53
53
  `)),K(X,!1),process.exit(0)}let d=async t=>{e.statSync(t).isDirectory()?(process.stdout.write(`${T(A(Q(u)))}\n`),await $(t)):(process.stdout.write(`${T(A(Q(n.dirname(u))))}\n`),process.stdout.write(`└── ${n.basename(u)}\n`)),process.stdout.write(`
54
54
  `)};if(s.dryRun){if(s.tree){let r=n.resolve(t.tmpdir(),`gitpick-dry-${Date.now()}${Math.random().toString(16).slice(2,6)}`);try{await L(l,s,r),await d(r)}finally{await e.promises.rm(r,{recursive:!0,force:!0})}}c||console.log(),K(X,c),process.exit(0)}if(s.overwrite=s.overwrite||s.force,s.watch&&(s.overwrite=!0),e.existsSync(u)&&!s.overwrite&&(l.type===`blob`&&(console.log(`${k(`\nWarning: The target file exists at ${O(l.target)}. Use ${A(`-f`)} or ${A(`-o`)} to overwrite.`)}`),process.exit(1)),(await e.promises.readdir(u)).length&&(console.log(`${k(`\nWarning: The target directory exists at ${O(l.target)} and is not empty. Use ${A(`-f`)} or ${A(`-o`)} to overwrite.`)}`),process.exit(1))),s.watch){c||console.log(`\nšŸ‘€ Watching every ${B(s.watch)/1e3+`s`}\n`),await L(l,s,u),s.tree&&await d(u);let e=B(s.watch);setInterval(async()=>{await L(l,s,u),s.tree&&await d(u)},e)}else await L(l,s,u),s.tree&&await d(u),K(X,c),process.exit(0)}catch(e){e instanceof Error?console.log(T(`\n${D(`Error: `)}`)+e.message):console.log(T(`${D(`
55
55
  Unexpected Error: `)}`)+JSON.stringify(e,null,2)),process.exit(1)}})();export{};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitpick",
3
- "version": "4.20.0-canary.5",
3
+ "version": "4.20.0-canary.7",
4
4
  "description": "Clone exactly what you need aka straightforward project scaffolding!",
5
5
  "keywords": [
6
6
  "clone",