ghit 0.1.18 → 0.1.19
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 +26 -26
- package/package.json +2 -2
package/bin/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{createRequire as e}from"node:module";import{Logger as t}from"@h3ravel/shared";import{existsSync as n,mkdirSync as r,writeFileSync as i}from"node:fs";import{fileURLToPath as a}from"node:url";import o,{dirname as s,join as c}from"node:path";import l from"better-sqlite3";import{execSync as u}from"child_process";import d,{homedir as f,type as p}from"os";import m,{mkdirSync as h}from"fs";import g
|
|
2
|
+
import{createRequire as e}from"node:module";import{Logger as t}from"@h3ravel/shared";import{existsSync as n,mkdirSync as r,writeFileSync as i}from"node:fs";import{fileURLToPath as a}from"node:url";import o,{dirname as s,join as c}from"node:path";import l from"better-sqlite3";import{execSync as u}from"child_process";import d,{homedir as f,type as p}from"os";import m,{mkdirSync as h}from"fs";import g from"path";import{Octokit as _}from"@octokit/rest";import ee from"node:readline/promises";import{homedir as te}from"node:os";import{installPackage as ne}from"@antfu/install-pkg";import{Command as v,Kernel as re}from"@h3ravel/musket";import ie from"fast-diff";import ae from"cli-table3";import{createRequire as y}from"module";import oe from"dns/promises";import{createDeviceCode as se,exchangeDeviceCode as b}from"@octokit/oauth-methods";import x,{apps as S}from"open";import"dotenv/config";import C from"axios";String.prototype.toKebabCase=function(){return this.replace(/([a-z])([A-Z])/g,`$1-$2`).replace(/[\s_]+/g,`-`).toLowerCase()},String.prototype.toCamelCase=function(){return this.replace(/[-_ ]+([a-zA-Z0-9])/g,(e,t)=>t.toUpperCase()).replace(/^[A-Z]/,e=>e.toLowerCase())},String.prototype.toPascalCase=function(){return this.replace(/(^\w|[-_ ]+\w)/g,e=>e.replace(/[-_ ]+/,``).toUpperCase())},String.prototype.toSnakeCase=function(){return this.replace(/([a-z])([A-Z])/g,`$1_$2`).replace(/[\s-]+/g,`_`).toLowerCase()},String.prototype.toTitleCase=function(){return this.toLowerCase().replace(/(^|\s)\w/g,e=>e.toUpperCase())};function w(){let e=T();if(!e)return null;try{let t=u(`git credential fill`,{input:`url=${e.clone_url}\n\n`,encoding:`utf8`}),n=t.match(/^username=(.*)$/m),r=t.match(/^password=(.*)$/m);if(n&&r)return{username:n[1],password:r[1]}}catch{}return null}const T=()=>{try{let e=u(`git config --get remote.origin.url`,{encoding:`utf8`}).trim();if(!e)return null;let t=e.match(/[:/]([^/]+)\/([^/.]+)(?:\.git)?$/);if(!t)return null;let n=t[1],r=t[2],i=e.startsWith(`git@`)?e:`git@${e.replace(/^https?:\/\//,``)}.git`.replace(/\.git\.git$/,`.git`),a=e.startsWith(`http`)?e:`https://${e.replace(/^git@/,``).replace(`:`,`/`)}.git`.replace(/\.git\.git$/,`.git`);return{id:0,name:r,full_name:`${n}/${r}`,private:!1,ssh_url:i,clone_url:a}}catch{return null}};let E;function D(){return[()=>{if(!E)throw Error(`Commander instance has not been initialized`);return E},e=>{E=e}]}function O(){return[()=>R(`config`)||{debug:!1,apiBaseURL:`https://api.github.com`,timeoutDuration:3e3,useCurrentRepo:!0,ngrokAuthToken:void 0,skipLongCommandGeneration:!0},e=>(I(`config`,e),R(`config`))]}const k=new Set;function ce(){return[()=>Array.from(k).filter(e=>!!e),e=>e?k.has(e)?!1:(k.add(e),!0):(k.clear(),!1)]}const A=()=>{let e,[t]=O();if(t().useCurrentRepo){let t=w();t&&t.password&&(e=t.password)}if(e||=R(`token`),!e)throw Error(`No authentication token found. Please log in first.`);return new _({auth:e})};let j,M=g.join(f(),`.ghit`);h(M,{recursive:!0});const N=()=>[M,e=>{M=e}],[P,le]=[()=>j,e=>{j=new l(g.join(M,e));let[{journal_mode:t}]=j.pragma(`journal_mode`);t!==`wal`&&j.pragma(`journal_mode = WAL`)}];le(`app.db`);function F(){return P().exec(`
|
|
3
3
|
CREATE TABLE IF NOT EXISTS json_store (
|
|
4
4
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
5
5
|
key TEXT UNIQUE,
|
|
@@ -8,30 +8,30 @@ import{createRequire as e}from"node:module";import{Logger as t}from"@h3ravel/sha
|
|
|
8
8
|
`)}function I(e,t){let n=P();return typeof t==`boolean`&&(t=t?`1`:`0`),t instanceof Object&&(t=JSON.stringify(t)),n.prepare(`INSERT INTO json_store (key, value)
|
|
9
9
|
VALUES (?, ?)
|
|
10
10
|
ON CONFLICT(key) DO UPDATE SET value=excluded.value
|
|
11
|
-
`).run(e,t).lastInsertRowid}function L(e){return P().prepare(`DELETE FROM json_store WHERE key = ?`).run(e).lastInsertRowid}function R(e,t,n){let r=P();if(e===`default_repo`&&!n?.bypassCurrentRepo){let[e]=O(),t=e(),n=T();if(n&&t.useCurrentRepo)return n}try{let t=r.prepare(`SELECT * FROM json_store WHERE key = ?`).get(e);if(t)try{return JSON.parse(t.value)}catch{return t.value}}catch{}return t??null}const z=a(import.meta.url),B=o.dirname(z),V=e=>e.then(e=>[null,e]).catch(e=>[typeof e==`string`?e:e.message,null]);async function H(e,t,n){let r=
|
|
11
|
+
`).run(e,t).lastInsertRowid}function L(e){return P().prepare(`DELETE FROM json_store WHERE key = ?`).run(e).lastInsertRowid}function R(e,t,n){let r=P();if(e===`default_repo`&&!n?.bypassCurrentRepo){let[e]=O(),t=e(),n=T();if(n&&t.useCurrentRepo)return n}try{let t=r.prepare(`SELECT * FROM json_store WHERE key = ?`).get(e);if(t)try{return JSON.parse(t.value)}catch{return t.value}}catch{}return t??null}const z=a(import.meta.url),B=o.dirname(z),V=e=>e.then(e=>[null,e]).catch(e=>[typeof e==`string`?e:e.message,null]);async function H(e,t,n){let r=A(),{data:i,message:a}=await Reflect.apply(r[e][t.api],r[e],[n]);return!i||Array.isArray(i)&&i.length<1||i instanceof Object&&Object.keys(i).length<1?{data:null,message:a??`Request was successful but returned no data.`,status:!1}:{data:i,message:a??`Request Completed`,status:!0}}const U=(e,t)=>new Promise(n=>{setTimeout(()=>{t&&n(t()),n()},e)}),W=(e,n=[`green`,`italic`],r)=>t.log(e,n,r??!1),ue=e=>{t.log([[`Title:`,[`white`,`bold`]],[e.title,[`blue`]],[`
|
|
12
12
|
Type:`,[`white`,`bold`]],[typeof e.type==`string`?e.type:e.type?.name??`N/A`,[`blue`]],[`
|
|
13
13
|
Number:`,[`white`,`bold`]],[String(e.number),[`blue`]],[`
|
|
14
14
|
State:`,[`white`,`bold`]],[e.state,[`blue`]],[`
|
|
15
15
|
Labels:`,[`white`,`bold`]],[e.labels.map(e=>e.name??e).join(`, `),[`blue`]],[`
|
|
16
16
|
Assignees:`,[`white`,`bold`]],[e.assignees?.map(e=>e.login??e).join(`, `)||`N/A`,[`blue`]],[`
|
|
17
17
|
Created at:`,[`white`,`bold`]],[new Date(e.created_at).toLocaleString(),[`blue`]],[`
|
|
18
|
-
Updated at:`,[`white`,`bold`]],[new Date(e.updated_at).toLocaleString(),[`blue`]]],` `)},
|
|
18
|
+
Updated at:`,[`white`,`bold`]],[new Date(e.updated_at).toLocaleString(),[`blue`]]],` `)},de=(e=B)=>{let t=e;for(;;){let e=o.join(t,`package.json`);if(n(e))return e;let r=o.dirname(t);if(r===t)break;t=r}return null},fe=async e=>{let t=ee.createInterface({input:process.stdin,output:process.stdout});await t.question(``),e(),t.close()},pe=e=>e.split(`+`).map(e=>String.fromCharCode(Number(e)^73)).join(``),G=e=>{if(e){let t=e.match(/github\.com\/([^/]+)\/([^/]+)/);if(t)return[t[1],t[2]];let n=e.split(`/`);if(n.length===2)return[n[0],n[1]]}let t=R(`default_repo`);if(t?.full_name){let n=t.full_name.split(`/`);if(n.length===2)return[n[0],e??n[1]]}throw Error(`Invalid repository format. Please provide a full URL, "owner/repo", or just "repo" with a default repository set.`)},K=e=>({title:e.title,body:e.body??``,type:e.type?.name??`Feature`,labels:e.labels.map(e=>typeof e==`string`?e:e.name).filter(Boolean),assignees:[]}),me={issues:[{api:`create`,alias:void 0,endpoint:`/repos/{owner}/{repo}/issues`,description:`Create an issue`,params:[{parameter:`title`,required:!0,type:`String`,description:`The title of the issue`,paramType:`body`,flag:!0},{parameter:`body`,required:!1,type:`String`,description:`The contents of the issue`,paramType:`body`,flag:!0},{parameter:`owner`,required:!1,type:`String`,description:`The account owner of the repository`,paramType:`path`,arg:!0},{parameter:`repo`,required:!1,type:`String`,description:`The name of the repository`,paramType:`path`,arg:!0}]},{api:`listForRepo`,alias:`list`,endpoint:`/repos/{owner}/{repo}/issues`,description:`List repository issues`,params:[{parameter:`owner`,required:!1,type:`String`,description:`The account owner of the repository`,paramType:`path`},{parameter:`repo`,required:!1,type:`String`,description:`The name of the repository`,paramType:`path`},{parameter:`state`,required:!1,type:`String`,description:`Indicates the state of the issues to return. [open, closed]`,paramType:`query`}]},{api:`get`,alias:`get`,endpoint:`/repos/{owner}/{repo}/issues/{issue_number}`,description:`Get a single issue`,params:[{parameter:`issue_number`,required:!0,type:`Number`,description:`The number of the issue to get`,paramType:`path`},{parameter:`owner`,required:!1,type:`String`,description:`The account owner of the repository`,paramType:`path`},{parameter:`repo`,required:!1,type:`String`,description:`The name of the repository`,paramType:`path`}]}],orgs:[{api:`listForAuthenticatedUser`,alias:`list`,endpoint:`/user/orgs`,description:`List organizations for the authenticated user`,params:[{parameter:`page`,required:!1,type:`Number`,description:`Page number of the results to fetch`,paramType:`query`},{parameter:`per_page`,required:!1,type:`Number`,description:`Results per page (max 100)`,paramType:`query`}]}]};var q=class e{spec;config;openapi;skipApis=new Set([`issues:list`,`issues:update`,`issues:seed`,`issues:delete`]);skipParams=new Set([`s`]);PARAM_LOCATIONS=new Set([`path`,`query`,`header`]);constructor(e,t=`api.github.com.deref`){let[n]=O();if(this.openapi=e,this.spec=this.openapi.schemas[t],this.config=n(),!this.spec||!this.spec.paths)throw Error(`Could not find ${t} schema`)}static async installOctokitOpenapi(){let e=D()[0]().spinner(`Installing @octokit/openapi...`).start(),t=s(a(import.meta.url));return await ne(`@octokit/openapi`,{cwd:o.normalize(o.join(t,`../..`)),silent:!0,dev:!0}),e.succeed(`@octokit/openapi installed successfully.`),(await import(`@octokit/openapi`)).default}skipParam(e){return this.skipParams.has(e)||e.length>20||e.length<=2}skipApi(e,t){let n=(t?t+`:`:``)+e.toCamelCase();return this.skipApis.has(n)||this.skipApis.has(e.toCamelCase())||n.length>(this.config.skipLongCommandGeneration?23:1/0)}normalizeType(e){let t={integer:`Number`,number:`Number`,string:`String`,boolean:`Boolean`,array:`Array`,object:`Object`,enum:`String`,oneOf:`String`,anyOf:`String`,allOf:`String`},n=t[e?.type]||`any`;return Array.isArray(e?.type)&&(n=e.type.map(e=>t[e]||`any`).join(`|`)),n===`any`?(e||(n=`any`),Array.isArray(e.type)?e.type.join(`|`):(e.type&&(n=e.type),e.enum&&(n=`enum`),e.oneOf&&(n=`oneOf`),e.anyOf&&(n=`anyOf`),e.allOf&&(n=`allOf`),t[n]||`any`)):n}gatherParams(e){let t=[];for(let n of e.parameters??[]){let e=this.PARAM_LOCATIONS.has(n.in)?n.in:`query`;this.skipParam(n.name)||t.push({parameter:n.name,required:!!n.required,type:this.normalizeType(n.schema).toPascalCase(),description:n.description,paramType:e})}let n=e.requestBody?.content?.[`application/json`],r=n?.schema,i=r?.properties??{},a=r?.required??[];for(let[e,r]of Object.entries(i))this.skipParam(e)||t.push({parameter:e,required:a.includes(e)||!!n?.required,type:this.normalizeType(r).toPascalCase(),description:r.description,paramType:`body`});return t}buildTree(){let e={};for(let[t,n]of Object.entries(this.spec.paths))for(let[r,i]of Object.entries(n??{})){let n=i,r=n?.operationId;if(!r)continue;let[a,o]=r.split(`/`);if(!a||!o||this.skipApi(o,a))continue;let s=this.gatherParams(n);e[a.toCamelCase()]||(e[a.toCamelCase()]=[]),e[a.toCamelCase()].push({api:o.toCamelCase(),endpoint:t,description:n.summary??n.description??void 0,alias:n[`x-github`]?.alias??n[`x-octokit`]?.alias??void 0,params:s})}return e}static getOutputPath(e=`local`){return e===`global`?o.join(te(),`.ghit/apis.generated.js`):o.join(process.cwd(),`.ghit/apis.generated.js`)}static async run(){let t,n=R(`generated_commands_type`,`local`),[a]=D(),s=a();if(n=await s.choice(`Api Generation Type${n?` (${n.toTitleCase()})`:``}`,[{value:`global`,name:`Global`,description:`Will always be available.`},{value:`local`,name:`Local`,description:`Only for this project.`},{value:`cancel`,name:W(`Cancel`,`red`),description:`Cancel the operation.`}],0),n===`cancel`){s.newLine().info(`Operation cancelled.`).newLine();return}I(`generated_commands_type`,n);let c=s.spinner(`Checking if @octokit/openapi Installed...`).start();try{({default:t}=await import(`@octokit/openapi`)),c.succeed(`@octokit/openapi is already installed.`)}catch{c.fail(`@octokit/openapi is not installed.`),t=await e.installOctokitOpenapi()}c.start(`Generating Extended APIs...`);let l=new e(t,`api.github.com.deref`).buildTree(),u=e.getOutputPath(n),d=`// Auto-generated from @octokit/openapi. Do not edit directly.
|
|
19
19
|
|
|
20
|
-
export const APIs = ${JSON.stringify(l,null,2).replace(/"([A-Za-z_][\w$]*)":/g,`$1:`).replace(/:\s*"((?:[^"\\]|\\.)*)"/g,(e,t)=>`: '${t.replace(/\\"/g,`"`).replace(/'/g,`\\'`)}'`)}\n\nexport default APIs\n`;r(o.dirname(u),{recursive:!0}),i(u,d,`utf8`),c.succeed(`Generated Extended APIs to: `+W(u,[`gray`,`italic`]))}};const he=(e,t)=>{let[n,r]=
|
|
21
|
-
`),i=class extends
|
|
20
|
+
export const APIs = ${JSON.stringify(l,null,2).replace(/"([A-Za-z_][\w$]*)":/g,`$1:`).replace(/:\s*"((?:[^"\\]|\\.)*)"/g,(e,t)=>`: '${t.replace(/\\"/g,`"`).replace(/'/g,`\\'`)}'`)}\n\nexport default APIs\n`;r(o.dirname(u),{recursive:!0}),i(u,d,`utf8`),c.succeed(`Generated Extended APIs to: `+W(u,[`gray`,`italic`]))}};const he=(e,t)=>{let[n,r]=ce(),i=``;if((!e.required||e.default!==void 0||e.type===`Boolean`||e.options||e.flag===!0)&&e.paramType!==`path`&&e.arg!==!0){if(i+=`{--`,r(t+`:`+e.parameter.charAt(0).toLowerCase()))i+=`${e.parameter.charAt(0).toLowerCase()}|`;else{let n=e.parameter.split(/[_-\s]/);n.length>1&&r(t+`:`+n[1].charAt(0).toLowerCase())&&(i+=`${n[1].charAt(0).toLowerCase()}|`)}if(i+=`${e.parameter}`,e.type!==`Boolean`&&(i+=e.default?`=${e.default}`:`?`),e.description&&(i+=` : ${e.description}`),e.options){let t=e.options.join(`,`);i+=` : ${t}`}i+=`}`}else i+=`{${e.parameter}`,e.default&&(i+=`=${e.default}`),e.description&&(i+=` : ${e.description}`),i+=`}`;return i},ge=e=>{let n=(e,r=0)=>{let i=` `.repeat(r);for(let a in e){let o=e[a];if(typeof o==`object`&&o)console.log(`${i}${J(a)}:`),n(o,r+2);else{let e;switch(typeof o){case`string`:e=t.log(o,`green`,!1);break;case`number`:e=t.log(String(o),`yellow`,!1);break;case`boolean`:e=t.log(String(o),`blue`,!1);break;case`object`:e=o===null?t.log(`null`,`gray`,!1):t.log(JSON.stringify(o),`cyan`,!1);break;default:e=o}console.log(`${i}${J(a)}: ${e}`)}}};n(e)},J=e=>e.replace(/([a-z])([A-Z])/g,`$1 $2`).replace(/[_-]+/g,` `).replace(/\s+/g,` `).split(` `).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(` `).trim().replace(/^(\w{2})$/,(e,t)=>t.toUpperCase()),Y=(e,t)=>ie(t,e).map(e=>{let[t,n]=e;return t===0?n:t===-1?W(n,[`red`,`strikethrough`],!1):W(n,[`green`,`underline`],!1)}).join(``);var _e=()=>{let t=e(import.meta.url),r=[],i=me,a=process.argv.includes(`generate:apis`),o=q.getOutputPath(R(`generated_commands_type`,`local`));!a&&n(o)&&({APIs:i}=t(o));let s=Object.entries(i).reduce((e,[t,n])=>(n.forEach(n=>{let r=t===n.api?t:`${t}:${(n.alias??n.api).toKebabCase()}`;e[r]=n}),e),{});for(let[e,t]of Object.entries(s)){let n=t.params.map(t=>he(t,e)).join(`
|
|
21
|
+
`),i=class extends v{signature=`${e} \n${n}`;description=t.description||`No description available.`;handle=async()=>{let n=e.split(`:`).shift(),r={...this.arguments()??{},...this.options()??{}},[i,a]=D();if(a(this),!n)return void this.error(`Unknown command entry.`).newLine();for(let e of t.params)if(e.required&&!this.argument(e.parameter))return void this.newLine().error(`Missing required argument: ${e.parameter}`).newLine();let o=R(`default_repo`)??{},s=R(`token`),c=([r.owner,r.repo].filter(Boolean).join(`/`)||o.full_name).split(`/`)??[``,``],l=t.params.some(e=>[`repo`,`user`].includes(e.parameter));if(l&&(!c[0]||!c[1]))return void this.error(`ERROR: No repository set. Please set a default repository using the [set-repo] command or provide one using the --repo option.`).newLine();if(!s)return void this.error(`ERROR: You're not signed in, please run the [login] command before you begin`).newLine();this.newLine();let u=this.spinner(`Loading...
|
|
22
22
|
`).start();l&&(r.owner=c[0],r.repo=c[1]);let[d,f]=await V(H(n,t,r));if(d||!f)return void u.fail((d||`An error occurred`)+`
|
|
23
|
-
`);u.succeed(f.message),this.newLine(),ge(f.data),this.newLine()}};r.push(i)}return r};const
|
|
23
|
+
`);u.succeed(f.message),this.newLine(),ge(f.data),this.newLine()}};r.push(i)}return r};const ve=e=>[{name:`Debug Mode`,value:`debug`,description:`Enable or disable debug mode (${e.debug?`Enabled`:`Disabled`})`},{name:`API Base URL`,value:`apiBaseURL`,description:`Set the base URL for the API (${e.apiBaseURL})`},{name:`Timeout Duration`,value:`timeoutDuration`,description:`Set the timeout duration for API requests (${e.timeoutDuration} ms)`},{name:`Use Current Repo for Commands`,value:`useCurrentRepo`,description:`Enable or disable automatic detection of the current git repository for commands that support it (${e.useCurrentRepo?`Enabled`:`Disabled`})`},{name:`Skip Long Command Generation`,value:`skipLongCommandGeneration`,description:`Enable or disable skipping of long command generation when calling ${W(`generate:apis`,[`grey`,`italic`])} (${e.skipLongCommandGeneration?`Enabled`:`Disabled`})`},{name:`Ngrok Auth Token`,value:`ngrokAuthToken`,description:`Set the Ngrok Auth Token - will default to environment variable if not set (${e.ngrokAuthToken?`************`:`Not Set`})`},{name:`Reset Configuration`,value:`reset`,description:`Reset all configurations to default values`}],ye=async e=>{let[t,n]=O(),[r]=D(),i=t();if(e===`debug`){let e=await r().choice(`Enable debug mode? (${i.debug?`Enabled`:`Disabled`})`,[{name:`Enable`,value:`1`},{name:`Disable`,value:`0`}],i.debug?0:1);i.debug=e===`1`}else if(e===`apiBaseURL`){let e=await r().ask(`Enter API Base URL`,i.apiBaseURL);i.apiBaseURL=e}else if(e===`ngrokAuthToken`){let e=await r().ask(`Enter Ngrok Auth Token`,i.ngrokAuthToken||``);i.ngrokAuthToken=e}else if(e===`timeoutDuration`){let e=await r().ask(`Enter Timeout Duration (in ms)`,i.timeoutDuration.toString());i.timeoutDuration=parseInt(e)}else if(e===`useCurrentRepo`){let e=await r().choice(`Enable automatic detection of the current git repository for commands that support it? (${i.useCurrentRepo?`Enabled`:`Disabled`})`,[{name:`Enable`,value:`1`},{name:`Disable`,value:`0`}],i.useCurrentRepo?0:1);i.useCurrentRepo=e===`1`}else if(e===`skipLongCommandGeneration`){let e=await r().choice(`Enable skipping of long command generation? (${i.skipLongCommandGeneration?`Enabled`:`Disabled`})`,[{name:`Enable`,value:`1`},{name:`Disable`,value:`0`}],i.skipLongCommandGeneration?0:1);i.skipLongCommandGeneration=e===`1`}else e===`reset`&&(i={debug:!1,apiBaseURL:`https://api.github.com`,timeoutDuration:3e3,useCurrentRepo:!0,ngrokAuthToken:void 0,skipLongCommandGeneration:!0});n(i)};var be=class extends v{signature=`config`;description=`Configure Ghit`;async handle(){let[e,t]=D();t(this);let[n,r]=O(),i=n();i||(i={debug:!1,apiBaseURL:`https://api.github.com`,timeoutDuration:3e3,useCurrentRepo:!0,ngrokAuthToken:void 0,skipLongCommandGeneration:!0},r(i)),await ye(await this.choice(`Select configuration to set`,ve(i))),this.info(`Configuration updated successfully!`).newLine()}},xe=class extends v{signature=`#generate:
|
|
24
24
|
{apis : Generate extended API definitions from the GitHub OpenAPI spec.}
|
|
25
25
|
{reset : Remove all generated commands}
|
|
26
|
-
`;description=`Generate extended API definitions from the GitHub OpenAPI spec`;async handle(){let[e,t]=D();t(this);let{name:n}=this.dictionary;if(n===`apis`)
|
|
26
|
+
`;description=`Generate extended API definitions from the GitHub OpenAPI spec`;async handle(){let[e,t]=D();t(this);let{name:n}=this.dictionary;if(n===`apis`)q.run();else if(n===`reset`){if(!await this.confirm(`Are you sure you want to remove all generated APIs? This action cannot be undone.`))return void this.newLine().info(`Operation cancelled.`).newLine();let{existsSync:e,unlinkSync:t}=await import(`node:fs`),n=this.spinner(`Removing generated APIs...`).start(),r=0;for(let i of[`local`,`global`]){let a=q.getOutputPath(i);e(a)&&(t(a),n.succeed(`Removed generated ${i} APIs successfully.`),r++)}r>0?n.succeed(`Removed all generated APIs successfully.`):n.fail(`No generated APIs found to remove.`)}}},Se=class extends v{signature=`info`;description=`Display application runtime information.`;async handle(){let e={version:`unknown`,dependencies:{}},n=R(`user`),r=de(),i=y(import.meta.url),a=R(`default_repo`,void 0,{bypassCurrentRepo:!0})?.full_name||null,o=T(),[s,c]=D(),[l]=N();c(this),F();let u=this.spinner(`Gathering application information...
|
|
27
27
|
`).start();if(r)try{e=i(r)}catch{}U(500,()=>{u.succeed(`Application Information Loaded.
|
|
28
|
-
`);let r=new
|
|
29
|
-
Dependencies:`,`yellow`),t.log(Object.keys(e.dependencies).map(e=>`${e}`).join(`, `),`green`),this.newLine()})}},
|
|
28
|
+
`);let r=new ae;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
|
+
Dependencies:`,`yellow`),t.log(Object.keys(e.dependencies).map(e=>`${e}`).join(`, `),`green`),this.newLine()})}},Ce=class extends v{signature=`init`;description=`Initialize the application.`;async handle(){let[e,t]=D();t(this),F(),this.info(`Application initialized successfully.`).newLine()}},X=class{command;normalizeIssuePath(e){return e.replaceAll(`\\`,`/`).replace(/^\.\//,``)}constructor(){let[e]=D();this.command=e()}setFilePath(e,t){if(!t)return e;let n=this.normalizeIssuePath(t);return e=e.includes(`<!-- ghit#filepath:`)?e.replace(/<!--\s*ghit#filepath:\s*.+?\s*-->/i,`<!-- ghit#filepath: ${n} -->`):`<!-- ghit#filepath: ${n} -->\n\n`+e,e}getFilePath(e){let t=e.match(/<!--\s*ghit#filepath:\s*(.+?)\s*-->/i);if(t)return this.normalizeIssuePath(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
|
|
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 A().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 A().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:this.normalizeIssuePath(t),title:r.title||r.name||n,type:r.type,body:i,labels:a,assignees:o,fileName:n}}processIssueFile(e){let t=this.command.argument(`path`,`issues`),n=g.isAbsolute(t)?t:g.resolve(process.cwd(),t),r=m.readFileSync(e,`utf-8`),i=g.relative(n,e);g.isAbsolute(i)&&(i=g.relative(process.cwd(),e));let a=g.basename(e,`.md`);return this.prepareIssue(r,i,a)}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
|
-
`)}\n---`);t=t.replace(/labels:\s*\[(.*?)\]/g,(e,t)=>`labels: ${t.split(`,`).map(e=>e.replace(/['"]/g,``).trim()).filter(Boolean).join(`,`)}`);let i=g.
|
|
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
|
|
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.relative(process.cwd(),e),a=g.basename(e);n.push(this.prepareIssue(t.trimStart()+`
|
|
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 A().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:
|
|
35
35
|
1. No internet connection
|
|
36
36
|
2. DNS server issues
|
|
37
37
|
3. Firewall/proxy blocking DNS
|
|
@@ -57,7 +57,7 @@ Troubleshooting:
|
|
|
57
57
|
- If behind a corporate firewall, check proxy settings
|
|
58
58
|
- Try using a different DNS (e.g., 8.8.8.8)
|
|
59
59
|
|
|
60
|
-
Original error: ${t.message}`)}}async fetchExistingIssues(e,t,n){let r=[],i=1,a=!0,o=this.command.spinner(`Fetching existing open issues...`).start();for(;a;)try{let{data:s}=await
|
|
60
|
+
Original error: ${t.message}`)}}async fetchExistingIssues(e,t,n){let r=[],i=1,a=!0,o=this.command.spinner(`Fetching existing open issues...`).start();for(;a;)try{let{data:s}=await A().issues.listForRepo({owner:e,repo:t,state:n||`open`,per_page:100,page:i});r.push(...s.filter(e=>!e.pull_request)),o.stop(),a=r.length%100==0&&s.length===100,a?i++:a=!1}catch(e){a=!1,o.stop(),this.command.warn(`ERROR: Failed to fetch existing issues: ${e.message}`),this.command.warn(`INFO: Proceeding without duplicate check...`)}return o.succeed(`Found ${r.length} existing issues.`),r}requestError(e,n,r){let i=e.message||`GitHub API error`;return e.status===401?(i+=`
|
|
61
61
|
|
|
62
62
|
This is an authentication error. Check that:`,i+=`\n 1. You are logged in (make sure to run the ${t.log(`login`,[`grey`,`italic`],!1)}`,i+=`command first)`,i+=`
|
|
63
63
|
2. The app token has "repo" scope`,i+=`
|
|
@@ -69,14 +69,14 @@ Repository not found. Check that:`,n&&(i+=`\n 1. ${t.log(n,[`blue`,`bold`],!1)}
|
|
|
69
69
|
Validation failed. This usually means:`,i+=`
|
|
70
70
|
1. Issue data format is invalid`,i+=`
|
|
71
71
|
2. Labels don't exist in the repository`,i+=`
|
|
72
|
-
3. Assignees don't have access to the repository`),Error(i)}},
|
|
72
|
+
3. Assignees don't have access to the repository`),Error(i)}},we=class extends v{signature=`issues:clone
|
|
73
73
|
{from : The repository to clone issues from.}
|
|
74
74
|
{target? : The repository to clone issues to. If not provided, the default repository will be used.}
|
|
75
75
|
{--f|labeled? : Comma-separated list of labels to filter issues by. Only issues with at least one of these labels will be cloned.}
|
|
76
76
|
{--l|labels? : Comma-separated list of labels to add to cloned issues.}
|
|
77
77
|
{--dry-run : Simulate the cloning without actually cloning issues.}
|
|
78
|
-
`;description=`Clone issues from a specified repository to the default repository.`;async handle(){let[e,n]=D();n(this);let r=this.spinner(`Fetching issues...`).start(),i=this.option(`dryRun`,!1),[a,o]=G(this.argument(`from`));if(!o||!a){this.error(`The "from" repository is required.`);return}let{data:s}=await
|
|
79
|
-
`),r.succeed(`All ${s.length} issues cloned.`)}};const Z=async(e,t,n,r)=>{let i=
|
|
78
|
+
`;description=`Clone issues from a specified repository to the default repository.`;async handle(){let[e,n]=D();n(this);let r=this.spinner(`Fetching issues...`).start(),i=this.option(`dryRun`,!1),[a,o]=G(this.argument(`from`));if(!o||!a){this.error(`The "from" repository is required.`);return}let{data:s}=await A().issues.listForRepo({owner:a,repo:o,sort:`created`,direction:`asc`,labels:this.option(`labeled`)?this.option(`labeled`).split(`,`).map(e=>e.trim()):void 0});if(s.length<1){r.info(`No issues found to clone.`);return}r.succeed(`Issues fetched successfully.`);let c,l=[],u=R(`default_repo`);if(u?.full_name&&!this.argument(`target`)?c=G(u.full_name):this.argument(`target`)&&(c=G(this.argument(`target`))),!c){this.error(`The "target" repository is required (you can set a default repository using the set-repo command to skip this requirement).`);return}let d=new X;await d.validateAccess(...c);let f=await d.fetchExistingIssues(...c,`all`),p=new Set(f.map(e=>e.title));if(f.length>0?(this.newLine().info(`INFO: Issues to SKIP (already exist):`),s.forEach(e=>{p.has(e.title)?(W(` > ${e.title}`,`white`,!0),W(` Existing: #${e.number} (${e.state})`,`white`,!0)):l.push(K(e))})):l.push(...s.map(e=>K(e))),l.length<1){this.info(`No new issues to clone.`);return}if(t.log([[`⚠️ `,`white`],[` CONFIRM `,`bgYellow`],[`This will clone`,`yellow`],[l.length.toString(),`blue`],[`new issues from`,`yellow`],[`${a}/${o}`,`cyan`],[`to`,`yellow`],[`${c[0]}/${c[1]}.`,`cyan`]],` `),f.length>0&&this.info(`(Skipping ${f.length} existing issues)`),!await this.confirm(`Do you want to proceed?${i?` (Dry Run - No changes will be made)`:``}`))return void this.info(`Operation cancelled by user.`);r.start(`Cloning issues from ${a}/${o} to ${c[0]}/${c[1]}...`);let m=0,h=0;for(let e of l){if(this.option(`labels`)){let t=this.option(`labels`).split(`,`).map(e=>e.trim());e.labels.push(...t)}try{if(r.start(`Creating: ${e.title}...`),i)r.info(`Dry run: Issue ${W(e.title,[`cyan`,`italic`])} would be cloned.`);else{let t=await d.createIssue(e,...c);r.succeed(`Created #${t.number}: ${t.title}`),this.info(`URL: ${t.html_url}\n`)}m++,await U(1e3)}catch(t){this.error(`ERROR: Failed to clone issue: ${W(e.title,[`cyan`,`italic`])}`),this.error(`ERROR: ${t.message}\n`),h++}}t.log([[`=========================`,`white`],[`✔ Created: ${m}`,`white`],[`> Skipped: ${f.length}`,`white`],[`x Failed: ${h}`,`white`],[`☑ Total: ${s.length}`,`white`],[`========================`,`white`]],`
|
|
79
|
+
`),r.succeed(`All ${s.length} issues cloned.`)}};const Z=async(e,t,n,r)=>{let i=A(),a=r;a||({repository:{issue:{id:a}}}=await i.graphql(`
|
|
80
80
|
query ($owner: String!, $repo: String!, $issue_number: Int!) {
|
|
81
81
|
repository(owner: $owner, name: $repo) {
|
|
82
82
|
issue(number: $issue_number) {
|
|
@@ -90,17 +90,17 @@ Validation failed. This usually means:`,i+=`
|
|
|
90
90
|
clientMutationId
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
|
-
`,{issueId:a})};var
|
|
93
|
+
`,{issueId:a})};var Te=class extends v{signature=`issues
|
|
94
94
|
{ repo? : The full name of the repository (e.g., username/repo)}
|
|
95
|
-
`;description=`Manage issues in the default repository.`;async handle(){let[e,t]=D();t(this);let n=R(`default_repo`),r=this.argument(`repo`,n.full_name).split(`/`)??[``,``],i=this.spinner(`Fetching issues...`).start();try{let e=1,t=[];do{let n=await this.loadIssues(r,e);t.push(...n),i.succeed(`${t.length} issues fetched successfully.`);let a=await this.choice(`Select Issue`,t.map(e=>({name:`#${e.number}: ${e.state===`open`?`🟢`:`🔴`} ${e.title}`,value:String(e.number)})).concat(t.length===20?[{name:`Load more issues`,value:`>>`}]:[]),0);if(a===`>>`)e++;else{let e=t.find(e=>String(e.number)===a);this.info(`#${e.number}: ${e.title}`).newLine();let n=await this.choice(`Choose Action`,[{name:`View Details`,value:`view`},e.closed_at?null:{name:`Close Issue`,value:`close`},e.closed_at?{name:`Reopen Issue`,value:`reopen`}:null,{name:`Edit Issue`,value:`edit`},{name:W(`Delete Issue`,[`red`,`italic`]),value:`delete`},{name:`Exit`,value:`exit`}].filter(e=>!!e),0);if(n===`view`)
|
|
95
|
+
`;description=`Manage issues in the default repository.`;async handle(){let[e,t]=D();t(this);let n=R(`default_repo`),r=this.argument(`repo`,n.full_name).split(`/`)??[``,``],i=this.spinner(`Fetching issues...`).start();try{let e=1,t=[];do{let n=await this.loadIssues(r,e);t.push(...n),i.succeed(`${t.length} issues fetched successfully.`);let a=await this.choice(`Select Issue`,t.map(e=>({name:`#${e.number}: ${e.state===`open`?`🟢`:`🔴`} ${e.title}`,value:String(e.number)})).concat(t.length===20?[{name:`Load more issues`,value:`>>`}]:[]),0);if(a===`>>`)e++;else{let e=t.find(e=>String(e.number)===a);this.info(`#${e.number}: ${e.title}`).newLine();let n=await this.choice(`Choose Action`,[{name:`View Details`,value:`view`},e.closed_at?null:{name:`Close Issue`,value:`close`},e.closed_at?{name:`Reopen Issue`,value:`reopen`}:null,{name:`Edit Issue`,value:`edit`},{name:W(`Delete Issue`,[`red`,`italic`]),value:`delete`},{name:`Exit`,value:`exit`}].filter(e=>!!e),0);if(n===`view`)ue(e),this.newLine();else if(n===`close`)e.state===`closed`?this.warn(`Issue is already closed.`).newLine():(i.start(`Closing issue #${e.number}...`),await A().issues.update({owner:r[0],repo:r[1],issue_number:e.number,state:`closed`}),i.succeed(`Issue #${e.number} closed successfully.`));else if(n===`reopen`)e.state===`open`?this.warn(`Issue is already open.`).newLine():(i.start(`Reopening issue #${e.number}...`),await A().issues.update({owner:r[0],repo:r[1],issue_number:e.number,state:`open`}),i.succeed(`Issue #${e.number} reopened successfully.`));else if(n===`edit`){let t=await this.choice(`What do you want to edit?`,[{name:`Title`,value:`title`},{name:`Body`,value:`body`}],0);if(t===`exit`)return;let n={};if(t===`title`?n.title=await this.ask(`Enter new title:`,e.title):t===`body`&&(n.body=await this.editor(`Edit issue body:`,`.md`,e.body??``)),Object.keys(n).length>0){let t=new X;i.start(`Updating issue #${e.number}...`),await t.updateIssue(Object.assign({labels:e.labels,assignees:e.assignees},n),e,...r),i.succeed(`Issue #${e.number} updated successfully.`)}else this.info(`No changes made to the issue.`).newLine()}else if(n===`delete`)i.start(`Deleting issue #${e.number}...`),await Z(r[0],r[1],e.number,e.node_id),i.succeed(`Issue #${e.number} deleted successfully.`);else if(n===`exit`)return;return}}while(t.length===20)}catch(e){i.stop(),this.error(e.message);return}}async loadIssues(e,t=1){let n=[];return{data:n}=await A().issues.listForRepo({page:t,repo:e[1],owner:e[0],per_page:20,state:`all`}),n.filter(e=>!e.pull_request)}},Ee=class extends v{signature=`issues:delete
|
|
96
96
|
{ repo? : The full name of the repository (e.g., username/repo)}
|
|
97
97
|
{--dry-run : Simulate the deletion without actually deleting issues.}
|
|
98
|
-
`;description=`Delete issues from the specified repository.`;async handle(){let[e,t]=D();t(this);let n=R(`default_repo`),r=this.argument(`repo`,n.full_name).split(`/`)??[``,``],i=this.spinner(`Fetching issues...`).start(),a=this.option(`dryRun`,!1);try{let e=await this.loadIssues(r);i.succeed(`${e.length} issues fetched successfully.`);let t=await this.checkbox(`Select Issue${a?` (Dry Run)`:``}`,e.map(e=>({name:`#${e.number}: ${e.state===`open`?`🟢`:`🔴`} ${e.title}`,value:String(e.number)})),!0,void 0,20);if(!await this.confirm(`Are you sure you want to delete the selected ${t.length} issue(s)? ${a?`(Dry Run - No changes will be made)`:`This action cannot be undone`}.`)){this.info(`Operation cancelled.`);return}for(let n of e.filter(e=>t.includes(String(e.number))))i.start(`Deleting issue #${n.number}...`),a?i.info(`Dry run: Issue #${n.number} would be deleted.`):(await Z(r[0],r[1],n.number,n.node_id),i.succeed(`Issue #${n.number} deleted successfully.`));this.success(`${t.length} issue(s) deleted successfully.`)}catch(e){i.stop(),this.error(e.message);return}}async loadIssues(e){let t=[];return{data:t}=await
|
|
98
|
+
`;description=`Delete issues from the specified repository.`;async handle(){let[e,t]=D();t(this);let n=R(`default_repo`),r=this.argument(`repo`,n.full_name).split(`/`)??[``,``],i=this.spinner(`Fetching issues...`).start(),a=this.option(`dryRun`,!1);try{let e=await this.loadIssues(r);i.succeed(`${e.length} issues fetched successfully.`);let t=await this.checkbox(`Select Issue${a?` (Dry Run)`:``}`,e.map(e=>({name:`#${e.number}: ${e.state===`open`?`🟢`:`🔴`} ${e.title}`,value:String(e.number)})),!0,void 0,20);if(!await this.confirm(`Are you sure you want to delete the selected ${t.length} issue(s)? ${a?`(Dry Run - No changes will be made)`:`This action cannot be undone`}.`)){this.info(`Operation cancelled.`);return}for(let n of e.filter(e=>t.includes(String(e.number))))i.start(`Deleting issue #${n.number}...`),a?i.info(`Dry run: Issue #${n.number} would be deleted.`):(await Z(r[0],r[1],n.number,n.node_id),i.succeed(`Issue #${n.number} deleted successfully.`));this.success(`${t.length} issue(s) deleted successfully.`)}catch(e){i.stop(),this.error(e.message);return}}async loadIssues(e){let t=[];return{data:t}=await A().issues.listForRepo({repo:e[1],owner:e[0],per_page:20,state:`all`}),t.filter(e=>!e.pull_request)}},De=class extends v{signature=`issues:download
|
|
99
99
|
{from : The repository to download issues from.}
|
|
100
100
|
{target? : The directory to download issues into, relative to current directory. If not provided, a directory named after the repository will be created in the current working directory.}
|
|
101
101
|
{--f|labeled? : Comma-separated list of labels to filter issues by. Only issues with at least one of these labels will be downloaded.}
|
|
102
102
|
{--dry-run : Simulate the deletion without actually deleting issues.}
|
|
103
|
-
`;description=`Download issues from a specified repository.`;async handle(){let[e,n]=D();n(this);let a=this.spinner(`Fetching issues...`).start(),s=this.option(`dryRun`,!1),[c,l]=G(this.argument(`from`)),u=this.argument(`target`,`issues/${c}-${l}`);if(!l||!c){this.error(`The "from" repository is required.`);return}let{data:d}=await
|
|
103
|
+
`;description=`Download issues from a specified repository.`;async handle(){let[e,n]=D();n(this);let a=this.spinner(`Fetching issues...`).start(),s=this.option(`dryRun`,!1),[c,l]=G(this.argument(`from`)),u=this.argument(`target`,`issues/${c}-${l}`);if(!l||!c){this.error(`The "from" repository is required.`);return}let{data:d}=await A().issues.listForRepo({owner:c,repo:l,labels:this.option(`labeled`)?this.option(`labeled`).split(`,`).map(e=>e.trim()):void 0});if(d.length<1)return void a.info(`No issues found to clone.`);if(a.succeed(`Issues fetched successfully.`),s){t.log([[`⚠️ `,`white`],[` DRY RUN `,`bgYellow`],[`This will download`,`yellow`],[d.length.toString(),`blue`],[`issues from`,`yellow`],[`${c}/${l}`,`cyan`],[`to`,`yellow`],[u,`cyan`]],` `);return}a.start(`Downloading issues from ${c}/${l} to ${u}...`),r(o.resolve(process.cwd(),u),{recursive:!0}),d.map((e,t)=>{let n=`---
|
|
104
104
|
type: ${e.type?.name??`Feature`}
|
|
105
105
|
name: ${e.title}
|
|
106
106
|
title: ${e.title}
|
|
@@ -108,23 +108,23 @@ labels: ${e.labels.map(e=>typeof e==`string`?e:e.name).join(`, `)}
|
|
|
108
108
|
assignees: ${e.assignees?.map(e=>e.login).join(`, `)}
|
|
109
109
|
---
|
|
110
110
|
|
|
111
|
-
${e.body??``}`,r=`${t+1}-${e.title.replace(/[^a-z0-9]+/gi,`-`).replace(/^-+/,``).replace(/-+$/,``).toLowerCase()}.md`;i(o.resolve(process.cwd(),`${u}/${r}`),n,{encoding:`utf-8`})}),a.succeed(`Downloaded ${d.length} issues to ${u}.`)}},
|
|
111
|
+
${e.body??``}`,r=`${t+1}-${e.title.replace(/[^a-z0-9]+/gi,`-`).replace(/^-+/,``).replace(/-+$/,``).toLowerCase()}.md`;i(o.resolve(process.cwd(),`${u}/${r}`),n,{encoding:`utf-8`})}),a.succeed(`Downloaded ${d.length} issues to ${u}.`)}},Oe=class extends v{signature=`issues:seed
|
|
112
112
|
{path=issues : The directory containing issue files to seed from, or a markdown file to seed from.}
|
|
113
113
|
{--r|repo? : The repository to seed issues into. If not provided, the default repository will be used.}
|
|
114
114
|
{--dry-run : Simulate the deletion without actually deleting issues.}
|
|
115
115
|
`;description=`Seed the database with issues from a preset directory or markdown file.`;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=new Set,l=await s.fetchExistingIssues(...e,`all`);if(i.endsWith(`.md`))r=s.processMultiIssueMarkdown(i);else{let e=s.getIssueFiles(i);c=new Set(l.map(e=>s.getFilePath(e.body??``)??``).filter(Boolean)),r=e.map(s.processIssueFile.bind(s)).filter(Boolean)}let u=[],d=[];if(r.forEach(e=>{if(e.filePath&&c.has(e.filePath)||l.some(t=>t.title.toLowerCase()===e.title.toLowerCase())){let t=l.find(t=>t.title.toLowerCase()===e.title.toLowerCase());d.push({issue:e,existingIssue:t})}else u.push(e)}),d.length>0&&(this.newLine().info(`INFO: Issues to SKIP (already exist):`),d.forEach(({issue:e,existingIssue:t})=>{W(` > ${e.title}`,`white`,!0),W(` Existing: #${t.number} (${t.state})`,`white`,!0)})),u.length>0)this.newLine().info(`INFO: Issues to CREATE:`).newLine(),u.forEach((e,t)=>{W(`${t+1}. ${e.title}`,`white`,!0)}),this.newLine();else{this.newLine().success(`INFO: No new issues to create. All issues already exist`).newLine(),t.log([[`☑ Total files:`,`white`],[r.length.toString(),`blue`]],` `),t.log([[`> Skipped:`,`white`],[d.length.toString(),`blue`]],` `),t.log([[`± To create:`,`white`],[u.length.toString(),`blue`]],` `),this.newLine();return}if(t.log([[`⚠️ `,`white`],[` CONFIRM `,`bgYellow`],[`This will create`,`yellow`],[u.length.toString(),`blue`],[`new issues on the "${e.join(`/`)}" GitHub repository.`,`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(`Creating issues...`).start();for(let t of u)try{if(o.start(`Creating: ${t.title}...`),a)o.info(`Dry run: Issue ${W(t.title,[`cyan`,`italic`])} would be created.`);else{let n=await s.createIssue(t,...e);o.succeed(`Created #${n.number}: ${n.title}`),this.info(`URL: ${n.html_url}\n`)}n++,await U(1e3)}catch(e){this.error(`ERROR: Failed to create Issue: ${W(t.title,[`cyan`,`italic`])}`),this.error(`ERROR: ${e.message}\n`),i++}o.succeed(`All ${u.length} issues processed.`),t.log([[`=========================`,`white`],[`✔ Created: ${n}`,`white`],[`x Failed: ${i}`,`white`],[`> Skipped: ${d.length}`,`white`],[`☑ Total: ${r.length}`,`white`],[`========================`,`white`]],`
|
|
116
|
-
`),this.newLine()}}catch(e){this.error(e.message);return}}},
|
|
116
|
+
`),this.newLine()}}catch(e){this.error(e.message);return}}},ke=class extends v{signature=`issues:update
|
|
117
117
|
{path=issues : The directory containing issue files to seed from, or a markdown file to seed from.}
|
|
118
118
|
{--r|repo? : The repository to seed issues into. If not provided, the default repository will be used.}
|
|
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
|
-
`;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(` > ${
|
|
122
|
-
`),this.newLine()}}catch(e){this.error(e.message);return}}};const Q={CLIENT_ID:
|
|
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(` > ${Y(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:pe(``),CLIENT_TYPE:`oauth-app`,SCOPES:[`repo`,`read:user`,`user:email`]};async function Ae(){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 se({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 fe(async()=>{try{p()===`Windows_NT`?await x(a,{wait:!0,app:{name:S.browser}}):await x(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 b({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 je({authentication:e,user:t}){I(`user`,t),I(`token`,e.token),I(`scopes`,e.scopes),I(`clientId`,e.clientId),I(`clientType`,e.clientType)}function Me(){L(`token`),L(`scopes`),L(`clientId`),L(`clientType`)}var Ne=class extends v{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(Ae());t&&(je(t),r=R(`token`),i=R(`user`))}if(r&&i){let e=await A().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)}},Pe=class extends v{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,()=>Me()),n.succeed(`Logged out successfully`)}catch(e){n.fail(`Logout failed`),this.error(`An error occurred during logout: `+e.message)}this.newLine()}},Fe=class extends v{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
|
-
`;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
|
|
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 A().rest.repos.get({owner:e,repo:t}))}else if(this.option(`org`)){let e=this.spinner(`Fetching your organizations...`).start(),t=await A().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 A().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 A().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`}}),Ie=()=>{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 Le=`
|
|
126
126
|
▗▄▄▖▗▖ ▗▖▗▄▄▄▖▗▄▄▄▖
|
|
127
127
|
▐▌ ▐▌ ▐▌ █ █
|
|
128
128
|
▐▌▝▜▌▐▛▀▜▌ █ █
|
|
129
129
|
▝▚▄▞▘▐▌ ▐▌▗▄█▄▖ █
|
|
130
|
-
`,ze=class{};
|
|
130
|
+
`,Re=`0.1.19`,ze=class{};Ie(),re.init(new ze,{logo:Le,version:Re,exceptionHandler(e){let[t]=O(),n=t();console.error(n.debug?e:e.message)},baseCommands:[Se,Ce,Ne,Pe,be,Te,Fe,Oe,we,ke,Ee,xe,De,..._e()]});export{};
|
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.19",
|
|
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,
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"vue": "^3.5.13"
|
|
72
72
|
},
|
|
73
73
|
"scripts": {
|
|
74
|
-
"quick:test": "pnpm vitest",
|
|
74
|
+
"quick:test": "pnpm vitest --run",
|
|
75
75
|
"test": "pnpm run quick:test",
|
|
76
76
|
"lint": "eslint . --ext .ts --ext .js",
|
|
77
77
|
"cmd": "tsx --experimental-specifier-resolution=node src/cli.ts",
|