Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit46dced9

Browse files
authored
chore(scripts): add release autoversion to bump releases in docs (#13063)
This PR adds a command to bump versions in docs/markdown.This is still standalone and needs to be wired up.For now, I'm planning on putting this in `scripts/release.sh` (checkout main -> autoversion (this command) -> commit -> submit PR).It would be pretty neat to make it a GH actions that's triggered on release though, something for the future.Part of#12465
1 parentc933c75 commit46dced9

File tree

5 files changed

+317
-18
lines changed

5 files changed

+317
-18
lines changed

‎docs/install/kubernetes.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ locally in order to log in and manage templates.
128128

129129
For the**mainline** Coder release:
130130

131+
<!-- autoversion(mainline): "--version [version]"-->
132+
131133
```shell
132134
helm install coder coder-v2/coder \
133135
--namespace coder \
@@ -137,6 +139,8 @@ locally in order to log in and manage templates.
137139

138140
For the**stable** Coder release:
139141

142+
<!-- autoversion(stable): "--version [version]"-->
143+
140144
```shell
141145
helm install coder coder-v2/coder \
142146
--namespace coder \

‎scripts/release/main.go

Lines changed: 219 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,18 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"io/fs"
78
"os"
9+
"os/exec"
10+
"path/filepath"
11+
"regexp"
812
"slices"
913
"strings"
1014
"time"
1115

1216
"github.com/google/go-cmp/cmp"
1317
"github.com/google/go-github/v61/github"
18+
"github.com/spf13/afero"
1419
"golang.org/x/mod/semver"
1520
"golang.org/x/xerrors"
1621

@@ -26,42 +31,89 @@ const (
2631
)
2732

2833
funcmain() {
29-
logger:=slog.Make(sloghuman.Sink(os.Stderr)).Leveled(slog.LevelDebug)
34+
// Pre-flight checks.
35+
toplevel,err:=run("git","rev-parse","--show-toplevel")
36+
iferr!=nil {
37+
_,_=fmt.Fprintf(os.Stderr,"ERROR: %v\n",err)
38+
_,_=fmt.Fprintf(os.Stderr,"NOTE: This command must be run in the coder/coder repository.\n")
39+
os.Exit(1)
40+
}
41+
42+
iferr=checkCoderRepo(toplevel);err!=nil {
43+
_,_=fmt.Fprintf(os.Stderr,"ERROR: %v\n",err)
44+
_,_=fmt.Fprintf(os.Stderr,"NOTE: This command must be run in the coder/coder repository.\n")
45+
os.Exit(1)
46+
}
3047

31-
varghTokenstring
32-
vardryRunbool
48+
r:=&releaseCommand{
49+
fs:afero.NewBasePathFs(afero.NewOsFs(),toplevel),
50+
logger:slog.Make(sloghuman.Sink(os.Stderr)).Leveled(slog.LevelInfo),
51+
}
52+
53+
varchannelstring
3354

3455
cmd:= serpent.Command{
3556
Use:"release <subcommand>",
3657
Short:"Prepare, create and publish releases.",
3758
Options: serpent.OptionSet{
59+
{
60+
Flag:"debug",
61+
Description:"Enable debug logging.",
62+
Value:serpent.BoolOf(&r.debug),
63+
},
3864
{
3965
Flag:"gh-token",
4066
Description:"GitHub personal access token.",
4167
Env:"GH_TOKEN",
42-
Value:serpent.StringOf(&ghToken),
68+
Value:serpent.StringOf(&r.ghToken),
4369
},
4470
{
4571
Flag:"dry-run",
4672
FlagShorthand:"n",
4773
Description:"Do not make any changes, only print what would be done.",
48-
Value:serpent.BoolOf(&dryRun),
74+
Value:serpent.BoolOf(&r.dryRun),
4975
},
5076
},
5177
Children: []*serpent.Command{
5278
{
53-
Use:"promote <version>",
54-
Short:"Promote version to stable.",
79+
Use:"promote <version>",
80+
Short:"Promote version to stable.",
81+
Middleware:r.debugMiddleware,// Serpent doesn't support this on parent.
5582
Handler:func(inv*serpent.Invocation)error {
5683
ctx:=inv.Context()
5784
iflen(inv.Args)==0 {
5885
returnxerrors.New("version argument missing")
5986
}
60-
if!dryRun&&ghToken=="" {
87+
if!r.dryRun&&r.ghToken=="" {
6188
returnxerrors.New("GitHub personal access token is required, use --gh-token or GH_TOKEN")
6289
}
6390

64-
err:=promoteVersionToStable(ctx,inv,logger,ghToken,dryRun,inv.Args[0])
91+
err:=r.promoteVersionToStable(ctx,inv,inv.Args[0])
92+
iferr!=nil {
93+
returnerr
94+
}
95+
96+
returnnil
97+
},
98+
},
99+
{
100+
Use:"autoversion <version>",
101+
Short:"Automatically update the provided channel to version in markdown files.",
102+
Options: serpent.OptionSet{
103+
{
104+
Flag:"channel",
105+
Description:"Channel to update.",
106+
Value:serpent.EnumOf(&channel,"mainline","stable"),
107+
},
108+
},
109+
Middleware:r.debugMiddleware,// Serpent doesn't support this on parent.
110+
Handler:func(inv*serpent.Invocation)error {
111+
ctx:=inv.Context()
112+
iflen(inv.Args)==0 {
113+
returnxerrors.New("version argument missing")
114+
}
115+
116+
err:=r.autoversion(ctx,channel,inv.Args[0])
65117
iferr!=nil {
66118
returnerr
67119
}
@@ -72,24 +124,55 @@ func main() {
72124
},
73125
}
74126

75-
err:=cmd.Invoke().WithOS().Run()
127+
err=cmd.Invoke().WithOS().Run()
76128
iferr!=nil {
77129
iferrors.Is(err,cliui.Canceled) {
78130
os.Exit(1)
79131
}
80-
logger.Error(context.Background(),"release command failed","err",err)
132+
r.logger.Error(context.Background(),"release command failed","err",err)
81133
os.Exit(1)
82134
}
83135
}
84136

137+
funccheckCoderRepo(pathstring)error {
138+
remote,err:=run("git","-C",path,"remote","get-url","origin")
139+
iferr!=nil {
140+
returnxerrors.Errorf("get remote failed: %w",err)
141+
}
142+
if!strings.Contains(remote,"github.com")||!strings.Contains(remote,"coder/coder") {
143+
returnxerrors.Errorf("origin is not set to the coder/coder repository on github.com")
144+
}
145+
returnnil
146+
}
147+
148+
typereleaseCommandstruct {
149+
fs afero.Fs
150+
logger slog.Logger
151+
debugbool
152+
ghTokenstring
153+
dryRunbool
154+
}
155+
156+
func (r*releaseCommand)debugMiddleware(next serpent.HandlerFunc) serpent.HandlerFunc {
157+
returnfunc(inv*serpent.Invocation)error {
158+
ifr.debug {
159+
r.logger=r.logger.Leveled(slog.LevelDebug)
160+
}
161+
ifr.dryRun {
162+
r.logger=r.logger.With(slog.F("dry_run",true))
163+
}
164+
returnnext(inv)
165+
}
166+
}
167+
85168
//nolint:revive // Allow dryRun control flag.
86-
funcpromoteVersionToStable(ctx context.Context,inv*serpent.Invocation,logger slog.Logger,ghTokenstring,dryRunbool,versionstring)error {
169+
func(r*releaseCommand)promoteVersionToStable(ctx context.Context,inv*serpent.Invocation,versionstring)error {
87170
client:=github.NewClient(nil)
88-
ifghToken!="" {
89-
client=client.WithAuthToken(ghToken)
171+
ifr.ghToken!="" {
172+
client=client.WithAuthToken(r.ghToken)
90173
}
91174

92-
logger=logger.With(slog.F("dry_run",dryRun),slog.F("version",version))
175+
logger:=r.logger.With(slog.F("version",version))
93176

94177
logger.Info(ctx,"checking current stable release")
95178

@@ -161,7 +244,7 @@ func promoteVersionToStable(ctx context.Context, inv *serpent.Invocation, logger
161244
updatedNewStable.Body=github.String(updatedBody)
162245
updatedNewStable.Prerelease=github.Bool(false)
163246
updatedNewStable.Draft=github.Bool(false)
164-
if!dryRun {
247+
if!r.dryRun {
165248
_,_,err=client.Repositories.EditRelease(ctx,owner,repo,newStable.GetID(),newStable)
166249
iferr!=nil {
167250
returnxerrors.Errorf("edit release failed: %w",err)
@@ -221,3 +304,123 @@ func removeMainlineBlurb(body string) string {
221304

222305
returnstrings.Join(newBody,"\n")
223306
}
307+
308+
// autoversion automatically updates the provided channel to version in markdown
309+
// files.
310+
func (r*releaseCommand)autoversion(ctx context.Context,channel,versionstring)error {
311+
varfiles []string
312+
313+
// For now, scope this to docs, perhaps we include README.md in the future.
314+
iferr:=afero.Walk(r.fs,"docs",func(pathstring,_ fs.FileInfo,errerror)error {
315+
iferr!=nil {
316+
returnerr
317+
}
318+
ifstrings.EqualFold(filepath.Ext(path),".md") {
319+
files=append(files,path)
320+
}
321+
returnnil
322+
});err!=nil {
323+
returnxerrors.Errorf("walk failed: %w",err)
324+
}
325+
326+
for_,file:=rangefiles {
327+
err:=r.autoversionFile(ctx,file,channel,version)
328+
iferr!=nil {
329+
returnxerrors.Errorf("autoversion file failed: %w",err)
330+
}
331+
}
332+
333+
returnnil
334+
}
335+
336+
// autoversionMarkdownPragmaRe matches the autoversion pragma in markdown files.
337+
//
338+
// Example:
339+
//
340+
//<!-- autoversion(stable): "--version [version]" -->
341+
//
342+
// The channel is the first capture group and the match string is the second
343+
// capture group. The string "[version]" is replaced with the new version.
344+
varautoversionMarkdownPragmaRe=regexp.MustCompile(`<!-- ?autoversion\(([^)]+)\): ?"([^"]+)" ?-->`)
345+
346+
func (r*releaseCommand)autoversionFile(ctx context.Context,file,channel,versionstring)error {
347+
version=strings.TrimPrefix(version,"v")
348+
logger:=r.logger.With(slog.F("file",file),slog.F("channel",channel),slog.F("version",version))
349+
350+
logger.Debug(ctx,"checking file for autoversion pragma")
351+
352+
contents,err:=afero.ReadFile(r.fs,file)
353+
iferr!=nil {
354+
returnxerrors.Errorf("read file failed: %w",err)
355+
}
356+
357+
lines:=strings.Split(string(contents),"\n")
358+
varmatchRe*regexp.Regexp
359+
fori,line:=rangelines {
360+
ifautoversionMarkdownPragmaRe.MatchString(line) {
361+
matches:=autoversionMarkdownPragmaRe.FindStringSubmatch(line)
362+
matchChannel:=matches[1]
363+
match:=matches[2]
364+
365+
logger:=logger.With(slog.F("line_number",i+1),slog.F("match_channel",matchChannel),slog.F("match",match))
366+
367+
logger.Debug(ctx,"autoversion pragma detected")
368+
369+
ifmatchChannel!=channel {
370+
logger.Debug(ctx,"channel mismatch, skipping")
371+
continue
372+
}
373+
374+
logger.Info(ctx,"autoversion pragma found with channel match")
375+
376+
match=strings.Replace(match,"[version]",`(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,1)
377+
logger.Debug(ctx,"compiling match regexp","match",match)
378+
matchRe,err=regexp.Compile(match)
379+
iferr!=nil {
380+
returnxerrors.Errorf("regexp compile failed: %w",err)
381+
}
382+
}
383+
ifmatchRe!=nil {
384+
// Apply matchRe and find the group named "version", then replace it with the new version.
385+
// Utilize the index where the match was found to replace the correct part. The only
386+
// match group is the version.
387+
ifmatch:=matchRe.FindStringSubmatchIndex(line);match!=nil {
388+
logger.Info(ctx,"updating version number","line_number",i+1,"match",match)
389+
lines[i]=line[:match[2]]+version+line[match[3]:]
390+
matchRe=nil
391+
break
392+
}
393+
}
394+
}
395+
ifmatchRe!=nil {
396+
returnxerrors.Errorf("match not found in file")
397+
}
398+
399+
updated:=strings.Join(lines,"\n")
400+
401+
// Only update the file if there are changes.
402+
diff:=cmp.Diff(string(contents),updated)
403+
ifdiff=="" {
404+
returnnil
405+
}
406+
407+
if!r.dryRun {
408+
iferr:=afero.WriteFile(r.fs,file, []byte(updated),0o644);err!=nil {
409+
returnxerrors.Errorf("write file failed: %w",err)
410+
}
411+
logger.Info(ctx,"file autoversioned")
412+
}else {
413+
logger.Info(ctx,"dry-run: file not updated","uncommitted_changes",diff)
414+
}
415+
416+
returnnil
417+
}
418+
419+
funcrun(commandstring,args...string) (string,error) {
420+
cmd:=exec.Command(command,args...)
421+
out,err:=cmd.CombinedOutput()
422+
iferr!=nil {
423+
return"",xerrors.Errorf("command failed: %q: %w\n%s",fmt.Sprintf("%s %s",command,strings.Join(args," ")),err,out)
424+
}
425+
returnstrings.TrimSpace(string(out)),nil
426+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp