Skip to content

Commit e6cb5ea

Browse files
committed
Add support for creating GitHub draft releases
1 parent 6714723 commit e6cb5ea

File tree

4 files changed

+173
-0
lines changed

4 files changed

+173
-0
lines changed

cmd/release.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
Copyright © 2023 Chandler <chandler@chand1012.dev>
3+
*/
4+
package cmd
5+
6+
import (
7+
"fmt"
8+
"os"
9+
"strings"
10+
11+
"github.com/chand1012/ottodocs/pkg/ai"
12+
"github.com/chand1012/ottodocs/pkg/config"
13+
"github.com/chand1012/ottodocs/pkg/constants"
14+
"github.com/chand1012/ottodocs/pkg/gh"
15+
"github.com/chand1012/ottodocs/pkg/git"
16+
"github.com/chand1012/ottodocs/pkg/utils"
17+
l "github.com/charmbracelet/log"
18+
"github.com/spf13/cobra"
19+
)
20+
21+
// releaseCmd represents the release command
22+
var releaseCmd = &cobra.Command{
23+
Use: "release",
24+
Short: "Generate GitHub release notes from git commit logs",
25+
Long: `This command generates GitHub release notes from git commit logs.
26+
It will create a new release given a tag and post it to GitHub as a draft.`,
27+
Aliases: []string{"r"},
28+
PreRun: func(cmd *cobra.Command, args []string) {
29+
if verbose {
30+
log.SetLevel(l.DebugLevel)
31+
}
32+
},
33+
Run: func(cmd *cobra.Command, args []string) {
34+
c, err := config.Load()
35+
if err != nil {
36+
log.Errorf("Error loading config: %s", err)
37+
os.Exit(1)
38+
}
39+
40+
if previousTag == "" {
41+
previousTag, err = utils.Input("Previous tag: ")
42+
if err != nil {
43+
log.Errorf("Error getting previous tag: %s", err)
44+
os.Exit(1)
45+
}
46+
}
47+
48+
if currentTag == "" {
49+
currentTag, err = utils.Input("Current tag: ")
50+
if err != nil {
51+
log.Errorf("Error getting current tag: %s", err)
52+
os.Exit(1)
53+
}
54+
}
55+
56+
fmt.Print("Release notes: ")
57+
58+
// get the log between the tags
59+
gitLog, err := git.LogBetween(previousTag, currentTag)
60+
if err != nil {
61+
log.Errorf("Error getting log between tags: %s", err)
62+
os.Exit(1)
63+
}
64+
65+
prompt := constants.RELEASE_PROMPT + gitLog
66+
67+
stream, err := ai.SimpleStreamRequest(prompt, c)
68+
if err != nil {
69+
log.Errorf("Error getting response: %s", err)
70+
os.Exit(1)
71+
}
72+
73+
releaseNotes, err := utils.PrintChatCompletionStream(stream)
74+
if err != nil {
75+
log.Errorf("Error printing completion stream: %s", err)
76+
os.Exit(1)
77+
}
78+
79+
if !force {
80+
confirm, err := utils.Input("Create release? (y/n): ")
81+
if err != nil {
82+
log.Errorf("Error getting confirmation: %s", err)
83+
os.Exit(1)
84+
}
85+
confirm = strings.ToLower(confirm)
86+
if confirm != "y" {
87+
os.Exit(0)
88+
}
89+
}
90+
91+
origin, err := git.GetRemote("origin")
92+
if err != nil {
93+
log.Errorf("Error getting remote: %s", err)
94+
os.Exit(1)
95+
}
96+
97+
owner, repo, err := git.ExtractOriginInfo(origin)
98+
if err != nil {
99+
log.Errorf("Error extracting origin info: %s", err)
100+
os.Exit(1)
101+
}
102+
103+
err = gh.CreateDraftRelease(owner, repo, currentTag, releaseNotes, currentTag, c)
104+
if err != nil {
105+
log.Errorf("Error creating release: %s", err)
106+
os.Exit(1)
107+
}
108+
109+
fmt.Println("Release created successfully!")
110+
},
111+
}
112+
113+
func init() {
114+
RootCmd.AddCommand(releaseCmd)
115+
116+
releaseCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Verbose output")
117+
releaseCmd.Flags().BoolVarP(&force, "force", "f", false, "Force overwrite of existing file")
118+
releaseCmd.Flags().StringVarP(&previousTag, "prev-tag", "p", "", "Previous tag")
119+
releaseCmd.Flags().StringVarP(&currentTag, "tag", "t", "", "Current tag")
120+
}

cmd/vars.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ var startLine int
4444
var endLine int
4545
var appendFile bool
4646

47+
var previousTag string
48+
var currentTag string
49+
4750
var log = l.NewWithOptions(os.Stderr, l.Options{
4851
Level: l.InfoLevel,
4952
ReportTimestamp: false,

pkg/constants/prompts.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,12 @@ var PR_BODY_PROMPT string = "You are a helpful assistant who writes pull request
4141
var COMPRESS_DIFF_PROMPT string = "You are a helpful assistant who describes git diff changes. You will be given a Git diff and you should use it to create a description of the changes. The description should be no longer than 75 characters long and should describe the changes in the diff. Do not include the file names in the description."
4242

4343
var EDIT_CODE_PROMPT string = `You are a helpful assistant who edits code. You will be given a file what you are trying to accomplish in the edit and you should edit it to the best of your abilities. Only edit the code you are told. All code to edit will be preceded by "EDIT:". If "EDIT:" is omitted, assume you must edit the entire file. The goal of the edit will be preceded by "GOAL:". The entire file will be preceded by "FILE:". If there is not content after "FILE:", assume you are writing new code. Make sure to use the language specified in the task. The output code should be unformatted. Use no markdown.\n`
44+
45+
var RELEASE_PROMPT string = `You are a helpful assistant who creates GitHub release notes from git commit logs. You will be given a series of git commit messages and your task is to summarize these messages into release notes. The release notes should:
46+
- Be concise and informative about the changes made in the release.
47+
- Be written in plain English.
48+
- Group related changes together under appropriate headings.
49+
- Exclude unnecessary details, such as the specific file names that were changed.
50+
51+
Commit log:
52+
`

pkg/gh/issues.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,44 @@ type Comment struct {
111111
// The username of the user who made the comment. This is optional.
112112
Username string `json:"username,omitempty"`
113113
}
114+
115+
func CreateDraftRelease(owner, repo, title, body, tag string, conf *config.Config) error {
116+
if conf.GHToken == "" {
117+
return fmt.Errorf("no GitHub token found")
118+
}
119+
120+
releaseURL := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases", owner, repo)
121+
data := map[string]interface{}{
122+
"title": title,
123+
"body": body,
124+
"tag_name": tag,
125+
"draft": true,
126+
"prerelease": false,
127+
}
128+
129+
payload, err := json.Marshal(data)
130+
if err != nil {
131+
return err
132+
}
133+
134+
releaseReq, err := http.NewRequest("POST", releaseURL, strings.NewReader(string(payload)))
135+
if err != nil {
136+
return err
137+
}
138+
139+
releaseReq.Header.Set("Authorization", fmt.Sprintf("Bearer %s", conf.GHToken))
140+
releaseReq.Header.Set("Content-Type", "application/json")
141+
142+
client := &http.Client{}
143+
releaseResp, err := client.Do(releaseReq)
144+
if err != nil {
145+
return err
146+
}
147+
defer releaseResp.Body.Close()
148+
149+
if releaseResp.StatusCode != http.StatusCreated {
150+
return fmt.Errorf("failed to create release draft: %s", releaseResp.Status)
151+
}
152+
153+
return nil
154+
}

0 commit comments

Comments
 (0)