ghit 0.1.17 → 0.1.18
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/bin/cli.mjs +2 -2
- package/package.json +1 -1
package/bin/cli.mjs
CHANGED
|
@@ -28,7 +28,7 @@ export const APIs = ${JSON.stringify(l,null,2).replace(/"([A-Za-z_][\w$]*)":/g,`
|
|
|
28
28
|
`);let r=new ie;r.push({"App Version":e.version},{Platform:`${d.platform()} ${d.arch()} (${d.release()})`},{CPUs:d.cpus().length},{Host:`${d.userInfo().username}@${d.hostname()}`},{Memory:`${(d.freemem()/1024**3).toFixed(2)} GB / ${(d.totalmem()/1024**3).toFixed(2)} GB`},{"Database Path":g.join(l,`app.db`)},{"Github User":n?`${n.login} (ID: ${n.id})`:`Not logged in`},{"Default Repo":(a||`Not set`)+`${a===o?.full_name?` (Current)`:``}`},{"Current Repo":o?`${o.full_name}`:`Not detected`},{"Current Directory":process.cwd().replace(d.homedir(),`~`)}),console.log(r.toString()),t.log(`
|
|
29
29
|
Dependencies:`,`yellow`),t.log(Object.keys(e.dependencies).map(e=>`${e}`).join(`, `),`green`),this.newLine()})}},we=class extends b{signature=`init`;description=`Initialize the application.`;async handle(){let[e,t]=D();t(this),F(),this.info(`Application initialized successfully.`).newLine()}},X=class{command;constructor(){let[e]=D();this.command=e()}setFilePath(e,t){return t&&(e=e.includes(`<!-- ghit#filepath:`)?e.replace(/<!--\s*ghit#filepath:\s*.+?\s*-->/i,`<!-- ghit#filepath: ${t} -->`):`<!-- ghit#filepath: ${t} -->\n\n`+e),e}getFilePath(e){let t=e.match(/<!--\s*ghit#filepath:\s*(.+?)\s*-->/i);if(t)return t[1].trim()}parseFrontmatter(e){let t=e.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);if(!t)return{metadata:{},body:e};let[,n,r]=t,i={},a=n.split(`
|
|
30
30
|
`),o=null;for(let e of a){let t=e.match(/^(\w+):\s*['"]?(.*?)['"]?$/);if(t){let[,e,n]=t;o=e,i[e]=n}else o&&e.trim()&&(i[o]+=`
|
|
31
|
-
`+e.trim())}return{metadata:i,body:r.trim()}}async updateIssue(e,t,n,r){try{let i=e.filePath?this.setFilePath(e.body??``,e.filePath):e.body??``,{data:a}=await j().issues.update({body:i,repo:r,owner:n,issue_number:t.number,title:e.title,labels:e.labels||[],assignees:e.assignees||[]});return a}catch(e){throw this.requestError(e,n,r)}}async createIssue(e,t,n){try{let r=e.filePath?this.setFilePath(e.body??``,e.filePath):e.body??``,{data:i}=await j().issues.create({body:r,repo:n,owner:t,type:e.type,title:e.title,labels:e.labels||[],assignees:e.assignees||[]});return i}catch(e){throw this.requestError(e,t,n)}}getIssueFiles(e){let t=[],n=this.command.spinner(`Reading issue files...`).start(),r=e=>{let n=m.readdirSync(e,{withFileTypes:!0});for(let i of n){let n=g.join(e,i.name);i.isDirectory()?r(n):i.isFile()&&i.name.endsWith(`.md`)&&t.push(n)}};r(e);let i=t.sort();return n.succeed(`Found ${i.length} issue files`),i.length===0&&(n.info(`No issue files found. Exiting.`),process.exit(0)),i}prepareIssue(e,t,n){let{metadata:r,body:i}=this.parseFrontmatter(e),a=[];r.labels&&(a=r.labels.split(`,`).map(e=>e.trim()).filter(e=>e));let o=[];return r.assignees&&r.assignees.trim()&&(o=r.assignees.split(`,`).map(e=>e.trim()).filter(e=>e)),{filePath:t,title:r.title||r.name||n,type:r.type,body:i,labels:a,assignees:o,fileName:n}}processIssueFile(e){let t=ee(process.cwd(),this.command.argument(`path`,`issues`)),n=m.readFileSync(e,`utf-8`),r=g.relative(t,e),i=g.basename(e,`.md`);return this.prepareIssue(n,r,i)}processMultiIssueMarkdown(e){try{let t=m.readFileSync(e,`utf-8`).split(/\n(\+{6}|={6})\n/)
|
|
31
|
+
`+e.trim())}return{metadata:i,body:r.trim()}}async updateIssue(e,t,n,r){try{let i=e.filePath?this.setFilePath(e.body??``,e.filePath):e.body??``,{data:a}=await j().issues.update({body:i,repo:r,owner:n,issue_number:t.number,title:e.title,labels:e.labels||[],assignees:e.assignees||[]});return a}catch(e){throw this.requestError(e,n,r)}}async createIssue(e,t,n){try{let r=e.filePath?this.setFilePath(e.body??``,e.filePath):e.body??``,{data:i}=await j().issues.create({body:r,repo:n,owner:t,type:e.type,title:e.title,labels:e.labels||[],assignees:e.assignees||[]});return i}catch(e){throw this.requestError(e,t,n)}}getIssueFiles(e){let t=[],n=this.command.spinner(`Reading issue files...`).start(),r=e=>{let n=m.readdirSync(e,{withFileTypes:!0});for(let i of n){let n=g.join(e,i.name);i.isDirectory()?r(n):i.isFile()&&i.name.endsWith(`.md`)&&t.push(n)}};r(e);let i=t.sort();return n.succeed(`Found ${i.length} issue files`),i.length===0&&(n.info(`No issue files found. Exiting.`),process.exit(0)),i}prepareIssue(e,t,n){let{metadata:r,body:i}=this.parseFrontmatter(e),a=[];r.labels&&(a=r.labels.split(`,`).map(e=>e.trim()).filter(e=>e));let o=[];return r.assignees&&r.assignees.trim()&&(o=r.assignees.split(`,`).map(e=>e.trim()).filter(e=>e)),{filePath:t,title:r.title||r.name||n,type:r.type,body:i,labels:a,assignees:o,fileName:n}}processIssueFile(e){let t=ee(process.cwd(),this.command.argument(`path`,`issues`)),n=m.readFileSync(e,`utf-8`),r=g.relative(t,e),i=g.basename(e,`.md`);return this.prepareIssue(n,r,i)}processMultiIssueMarkdown(e){try{let t=m.readFileSync(e,`utf-8`).split(/\n(\+{6}|={6})\n/),n=[];for(let r of t){if(!r||!r.trim()||!r.match(/title: (.+)/))continue;let t=r.replace(/^---\n([\s\S]*?)\n---/m,(e,t)=>`---\n${t.split(`
|
|
32
32
|
`).filter(e=>e.trim()!==``||e.includes(`:`)).join(`
|
|
33
33
|
`)}\n---`);t=t.replace(/labels:\s*\[(.*?)\]/g,(e,t)=>`labels: ${t.split(`,`).map(e=>e.replace(/['"]/g,``).trim()).filter(Boolean).join(`,`)}`);let i=g.dirname(e),a=g.basename(e);n.push(this.prepareIssue(t.trimStart()+`
|
|
34
34
|
`,i,a))}return n}catch(e){return this.command.error(`ERROR: Failed to read markdown file: ${e.message}`),[]}}async validateAccess(e,t){let n=this.command.spinner(`Checking GitHub access...`).start();try{let r=await j().repos.get({owner:e,repo:t});return n.succeed(`GitHub access validated successfully.`),r}catch(e){n.stop();let t=``;throw n.fail(`GitHub access validation failed: ${e.message}.`),e.status===404&&(t=`This usually means:
|
|
@@ -119,7 +119,7 @@ ${e.body??``}`,r=`${t+1}-${e.title.replace(/[^a-z0-9]+/gi,`-`).replace(/^-+/,``)
|
|
|
119
119
|
{--dry-run : Simulate the deletion without actually deleting issues.}
|
|
120
120
|
{--m|match=file : Matching strategy for existing issues. "title" matches issues by title, while "file" matches issues based on the file path derived from the issue body. : [title,file]}
|
|
121
121
|
`;description=`Seed the database with updated issues from a preset directory or markdown file. Issues will be matched based on the specified matching strategy and updated if they already exist.`;async handle(){let[e,r]=D();r(this);let i=c(process.cwd(),this.argument(`path`,`issues`)),a=this.option(`dryRun`,!1),o=R(`default_repo`);if(!o)return void this.error(`ERROR: No default repository set. Please set a default repository using the ${W(`set-repo`,[`grey`,`italic`])} command.`);let s=new X;try{let e=this.option(`repo`,o.full_name).split(`/`)??[``,``];if(await s.checkConnectivity(),await s.validateAccess(...e),!n(i)){this.error(`ERROR: Issues path not found: ${W(i,[`grey`,`italic`])}`);return}let r=[],c=await s.fetchExistingIssues(...e,`all`),l=this.option(`match`,`file`),u=new Set(c.map(e=>l===`file`?s.getFilePath(e.body??``):e.title));r=i.endsWith(`.md`)?s.processMultiIssueMarkdown(i):s.getIssueFiles(i).map(s.processIssueFile.bind(s)).filter(Boolean);let d=[],f=[];if(r.forEach(e=>{if(u.has(l===`file`?e.filePath:e.title)){let t=c.find(t=>l===`file`?s.getFilePath(t.body??``)===e.filePath:t.title===e.title);f.push({issue:e,existingIssue:t})}else d.push(e)}),d.length>0&&(this.newLine().info(`INFO: Issues to SKIP (not created):`),d.forEach((e,t)=>{W(`${t+1}. ${e.title}`,`white`,!0),W(` File: ${e.filePath} (${e.type})`,`white`,!0)})),f.length>0)this.newLine().info(`INFO: Issues to UPDATE:`).newLine(),f.forEach(({issue:e,existingIssue:t})=>{W(` > ${_e(e.title,t.title)}`,`white`,!0),W(` Existing: #${t.number} (${t.state})`,`white`,!0)}),this.newLine();else{this.newLine().success(`INFO: No issues to update. All issues are up to date`).newLine(),t.log([[`☑ Total files:`,`white`],[r.length.toString(),`blue`]],` `),t.log([[`> Skipped:`,`white`],[d.length.toString(),`blue`]],` `),t.log([[`± To update:`,`white`],[f.length.toString(),`blue`]],` `),this.newLine();return}if(t.log([[`⚠️ `,`white`],[` CONFIRM `,`bgYellow`],[`This will update`,`yellow`],[f.length.toString(),`blue`],[`existing issues on GitHub.`,`yellow`]],` `),d.length>0&&this.info(`(Skipping ${d.length} existing issues)`),await this.confirm(`Do you want to proceed?${a?` (Dry Run - No changes will be made)`:``}`)){this.newLine();let n=0,i=0,o=this.spinner(`Updating issues...`).start();for(let{issue:t,existingIssue:r}of f)try{if(o.start(`Updating: ${t.title}...`),a)o.info(`Dry run: Issue ${W(t.title,[`cyan`,`italic`])} would be updated.`);else{let n=await s.updateIssue(t,r,...e);o.succeed(`Updated #${n.number}: ${n.title}`),this.info(`URL: ${n.html_url}\n`)}n++,await U(1e3)}catch(e){this.error(`ERROR: Failed to update Issue: ${W(t.title,[`cyan`,`italic`])}`),this.error(`ERROR: ${e.message}\n`),i++}o.succeed(`All ${f.length} issues processed.`),t.log([[`=========================`,`white`],[`✔ Updated: ${n}`,`white`],[`x Failed: ${i}`,`white`],[`> Skipped: ${d.length}`,`white`],[`☑ Total: ${r.length}`,`white`],[`========================`,`white`]],`
|
|
122
|
-
`),this.newLine()}}catch(e){this.error(e.message);return}}};const Q={CLIENT_ID:me(
|
|
122
|
+
`),this.newLine()}}catch(e){this.error(e.message);return}}};const Q={CLIENT_ID:me(``),CLIENT_TYPE:`oauth-app`,SCOPES:[`repo`,`read:user`,`user:email`]};async function je(){let[e]=D(),n=e(),r=n.spinner(`Requesting device code...`).start();try{let{data:{device_code:e,user_code:i,verification_uri:a,interval:o}}=await x({clientType:Q.CLIENT_TYPE,clientId:Q.CLIENT_ID,scopes:Q.SCOPES});r.succeed(`Device code created`),t.log([[`Your authentication code is`,`white`],[`\n\t ${i} \n`,[`white`,`bgBlue`]]],` `),t.log([[`Please open the following URL in your browser to authenticate:`,`white`],[a,[`cyan`,`underline`]]],` `),t.log([[`Press Enter to open your browser, or `,`white`],[`Ctrl+C`,[`grey`,`italic`]],[` to cancel`,`white`]],` `),await pe(async()=>{try{p()===`Windows_NT`?await S(a,{wait:!0,app:{name:ce.browser}}):await S(a,{wait:!0})}catch(e){n.error(`Error opening browser:`+e.message),n.info(`Please manually open the following URL in your browser:`),n.info(a),await U(3e3)}});let s=o,c=150;for(r=n.spinner(`Waiting for authorization...`).start();;){if(--c,c<0)throw Error(`User took too long to respond`);try{let{authentication:t}=await se({clientType:`oauth-app`,clientId:Q.CLIENT_ID,code:e,scopes:Q.SCOPES}),{data:n}=await new _({auth:t.token}).request(`/user`);return r!==void 0&&r.succeed(`Authorization successful`),{authentication:t,user:n}}catch(e){if(e.status===400){let t=e.response.data.error;if([`authorization_pending`,`slow_down`].includes(t))await U(s*3e3);else if([`expired_token`,`incorrect_device_code`,`access_denied`].includes(t))throw Error(t);else throw Error(`An unexpected error occurred: ${e.message}`)}else throw Error(`An unexpected error occurred: ${e.message}`)}}}catch{return Q.CLIENT_ID?r.fail(`Failed to authenticate user`):r.fail(`GitHub Client ID not available.`),null}}function Me({authentication:e,user:t}){I(`user`,t),I(`token`,e.token),I(`scopes`,e.scopes),I(`clientId`,e.clientId),I(`clientType`,e.clientType)}function Ne(){L(`token`),L(`scopes`),L(`clientId`),L(`clientType`)}var Pe=class extends b{signature=`login`;description=`Log in to Ghit`;async handle(){let[e,n]=D();n(this);let r=R(`token`),i;if(r){this.info(`INFO: You're already logged in`).newLine();return}else{let[e,t]=await V(je());t&&(Me(t),r=R(`token`),i=R(`user`))}if(r&&i){let e=await j().rest.repos.listForAuthenticatedUser(),n=await this.choice(`Select default repository`,e.data.map(e=>({name:e.full_name,value:e.full_name})),0),r=e.data.find(e=>e.full_name===n);r?I(`default_repo`,{id:r.id,name:r.name,full_name:r.full_name,private:r.private}):I(`default_repo`,{}),this.info(`INFO: You have been logged in as ${t.log(i.name,`blue`,!1)}!`).newLine()}process.exit(0)}},Fe=class extends b{signature=`logout`;description=`Log out of Ghit CLI`;async handle(){let[e,t]=D();t(this);let n=this.spinner(`Logging out...`).start();try{await U(1e3,()=>Ne()),n.succeed(`Logged out successfully`)}catch(e){n.fail(`Logout failed`),this.error(`An error occurred during logout: `+e.message)}this.newLine()}},Ie=class extends b{signature=`set-repo
|
|
123
123
|
{ name? : The full name of the repository (e.g., username/repo)}
|
|
124
124
|
{--O|org? : Set repository from an organization}
|
|
125
125
|
`;description=`Set the default repository.`;async handle(){let[e,n]=D();n(this);let r=R(`token`),i;if(!r)return void this.error(`ERROR: You must be logged in to set a default repository.`);if(this.argument(`name`)){let[e,t]=G(this.argument(`name`));({data:i}=await j().rest.repos.get({owner:e,repo:t}))}else if(this.option(`org`)){let e=this.spinner(`Fetching your organizations...`).start(),t=await j().rest.orgs.listForAuthenticatedUser();e.succeed(`${t.data.length} organizations fetched successfully.`);let n=await this.choice(`Select organization`,t.data.map(e=>({name:e.login,value:e.login})),0),r=this.spinner(`Fetching repositories for organization ${n}...`).start(),a=await j().rest.repos.listForOrg({org:n});r.succeed(`${a.data.length} repositories fetched successfully.`);let o=await this.choice(`Select default repository (${R(`default_repo`)?.full_name??`none`})`,a.data.map(e=>({name:e.full_name,value:e.full_name})),0);i=a.data.find(e=>e.full_name===o)}else{let e=this.spinner(`Fetching your repositories...`).start(),t=await j().rest.repos.listForAuthenticatedUser();e.succeed(`${t.data.length} repositories fetched successfully.`);let n=await this.choice(`Select default repository (${R(`default_repo`)?.full_name??`none`})`,t.data.map(e=>({name:e.full_name,value:e.full_name})),0);i=t.data.find(e=>e.full_name===n)}i?(I(`default_repo`,{id:i.id,name:i.name,full_name:i.full_name,private:i.private,ssh_url:i.ssh_url,clone_url:i.clone_url}),this.info(`INFO: ${t.log(i.full_name,`blue`,!1)} has been set as the default repository.`).newLine()):(I(`default_repo`,R(`default_repo`)??{}),this.warn(`INFO: No repository selected. Default repository has been cleared.`).newLine())}};const $=C.create({baseURL:`https://api.github.com`,headers:{"Content-Type":`application/json`}}),Le=()=>{let[e]=O(),t=e();$.defaults.baseURL=t.apiBaseURL||`https://api.github.com`,$.defaults.timeout=t.timeoutDuration||3e3};$.interceptors.request.use(e=>{let[t]=O(),[n]=D(),r=t(),i=n().getVerbosity();return(r.debug||i>1)&&((r.debug||i>=2)&&(console.log(`Request URL:`,e.url),console.log(`Request Method:`,e.method)),(r.debug||i==3)&&(console.log(`Request Headers:`,e.headers),console.log(`Request Data:`,e.data)),console.log(`Error Response URL:`,C.getUri(e))),e},e=>Promise.reject(e)),$.interceptors.response.use(e=>{let[t]=O(),[n]=D(),r=t(),i=n().getVerbosity();if(r.debug||i>1){let{data:t,status:n,statusText:a,headers:o}=e;(r.debug||i>=2)&&(console.log(`Response Data:`,t),console.log(`Response Status:`,n)),(r.debug||i===3)&&(console.log(`Response Status Text:`,a),console.log(`Response Headers:`,o)),console.log(`Error Response URL:`,C.getUri(e.config))}return e},e=>{let[t]=O(),[n]=D(),r=t(),i=n().getVerbosity();if(r.debug||i>1)if(e.response){let{data:t,status:n,headers:a}=e.response;(r.debug||i>=2)&&(console.log(`Error Response Data:`,t),console.log(`Error Response Status:`,n)),(r.debug||i===3)&&console.log(`Error Response Headers:`,a),console.log(`Error Response URL:`,C.getUri(e.config))}else console.log(`Error Message:`,e.message);return Promise.reject(e)});var Re=`
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ghit",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.18",
|
|
5
5
|
"description": "A CLI tool for managing GitHub repositories, issues, and workflows from your terminal, combining custom workflows with full GitHub API coverage.",
|
|
6
6
|
"main": "bin/cli.mjs",
|
|
7
7
|
"private": false,
|