@@ -90,6 +90,25 @@ export const parseGitHubUrl = (url: string): GitHubRepo | null => {
9090 } ;
9191} ;
9292
93+ const getRepositoryFromRemoteUrl = async (
94+ directoryPath : string ,
95+ ) : Promise < string > => {
96+ const remoteUrl = await getRemoteUrl ( directoryPath ) ;
97+ if ( ! remoteUrl ) {
98+ throw new Error ( "No remote URL found" ) ;
99+ }
100+
101+ // Parse repo from URL (handles both HTTPS and SSH formats)
102+ const repoMatch = remoteUrl . match (
103+ / g i t h u b \. c o m [: / ] ( [ ^ / ] + \/ [ ^ / ] + ?) (?: \. g i t ) ? $ / ,
104+ ) ;
105+ if ( ! repoMatch ) {
106+ throw new Error ( `Cannot parse repository from URL: ${ remoteUrl } ` ) ;
107+ }
108+
109+ return repoMatch [ 1 ] ;
110+ } ;
111+
93112export const isGitRepository = async (
94113 directoryPath : string ,
95114) : Promise < boolean > => {
@@ -555,20 +574,7 @@ const getPullRequestReviewComments = async (
555574 }
556575
557576 try {
558- // Extract repo from remote URL (format: owner/repo)
559- const remoteUrl = await getRemoteUrl ( directoryPath ) ;
560- if ( ! remoteUrl ) {
561- throw new Error ( "No remote URL found" ) ;
562- }
563-
564- // Parse repo from URL (handles both HTTPS and SSH formats)
565- const repoMatch = remoteUrl . match (
566- / g i t h u b \. c o m [: / ] ( [ ^ / ] + \/ [ ^ / ] + ?) (?: \. g i t ) ? $ / ,
567- ) ;
568- if ( ! repoMatch ) {
569- throw new Error ( `Cannot parse repository from URL: ${ remoteUrl } ` ) ;
570- }
571- const repo = repoMatch [ 1 ] ;
577+ const repo = await getRepositoryFromRemoteUrl ( directoryPath ) ;
572578
573579 // TODO: Paginate if many comments
574580 const { stdout } = await execAsync (
@@ -581,6 +587,56 @@ const getPullRequestReviewComments = async (
581587 }
582588} ;
583589
590+ interface AddPullRequestCommentOptions {
591+ body : string ;
592+ commitId : string ;
593+ path : string ;
594+ line : number ;
595+ side ?: "LEFT" | "RIGHT" ;
596+ }
597+
598+ const addPullRequestComment = async (
599+ directoryPath : string ,
600+ prNumber : number ,
601+ options : AddPullRequestCommentOptions ,
602+ ) : Promise < any > => {
603+ // Validate prNumber: must be a positive integer
604+ if (
605+ typeof prNumber !== "number" ||
606+ ! Number . isInteger ( prNumber ) ||
607+ prNumber < 1
608+ ) {
609+ throw new Error ( `Invalid pull request number: ${ prNumber } ` ) ;
610+ }
611+
612+ // Validate required options
613+ if ( ! options . body || ! options . commitId || ! options . path ) {
614+ throw new Error ( "body, commitId, and path are required" ) ;
615+ }
616+
617+ if ( typeof options . line !== "number" || options . line < 1 ) {
618+ throw new Error ( "line must be a positive number" ) ;
619+ }
620+
621+ try {
622+ const repo = await getRepositoryFromRemoteUrl ( directoryPath ) ;
623+ const side = options . side || "RIGHT" ;
624+
625+ const { stdout } = await execAsync (
626+ `gh api repos/${ repo } /pulls/${ prNumber } /comments ` +
627+ `-f body="${ options . body . replace ( / " / g, '\\"' ) } " ` +
628+ `-f commit_id="${ options . commitId } " ` +
629+ `-f path="${ options . path } " ` +
630+ `-f line=${ options . line } ` +
631+ `-f side="${ side } "` ,
632+ { cwd : directoryPath } ,
633+ ) ;
634+ return JSON . parse ( stdout ) ;
635+ } catch ( error ) {
636+ throw new Error ( `Failed to add PR comment: ${ error } ` ) ;
637+ }
638+ } ;
639+
584640export function registerGitIpc (
585641 getMainWindow : ( ) => BrowserWindow | null ,
586642) : void {
@@ -882,4 +938,16 @@ export function registerGitIpc(
882938 return getPullRequestReviewComments ( directoryPath , prNumber ) ;
883939 } ,
884940 ) ;
941+
942+ ipcMain . handle (
943+ "add-pr-comment" ,
944+ async (
945+ _event : IpcMainInvokeEvent ,
946+ directoryPath : string ,
947+ prNumber : number ,
948+ options : AddPullRequestCommentOptions ,
949+ ) : Promise < any > => {
950+ return addPullRequestComment ( directoryPath , prNumber , options ) ;
951+ } ,
952+ ) ;
885953}
0 commit comments