From f87bfeb438e066d05ec8a89255f1092e58efad34 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Sun, 1 Mar 2026 01:00:25 -0700 Subject: [PATCH] feat(monorel): add --draft and --prerelease flags to release subcommand The gh release create flags are now explicit and always emitted: --draft / --draft=false --prerelease / --prerelease=false Publish step (gh release edit --draft=false) logic: --draft --prerelease : NO (stays as draft pre-release) --draft only : YES (draft needs publishing) --prerelease only : NO (published immediately as pre-release) neither : NO (published immediately) The step prompt adapts: "create draft pre-release / draft / pre-release / GitHub release " depending on the flag combination. --- tools/monorel/main.go | 85 +++++++++++++++++++++++++++---------------- 1 file changed, 53 insertions(+), 32 deletions(-) diff --git a/tools/monorel/main.go b/tools/monorel/main.go index 6af6867..b66a6b7 100644 --- a/tools/monorel/main.go +++ b/tools/monorel/main.go @@ -112,11 +112,13 @@ func usage() { func runRelease(args []string) { fs := flag.NewFlagSet("monorel release", flag.ExitOnError) - var recursive, all, dryRun, yes bool + var recursive, all, dryRun, yes, draft, prerelease bool fs.BoolVar(&recursive, "recursive", false, "find all main packages recursively under each path") fs.BoolVar(&all, "A", false, "include dot/underscore-prefixed directories; warn rather than error on failures") fs.BoolVar(&dryRun, "dry-run", false, "show each step without running it") fs.BoolVar(&yes, "yes", false, "run all steps without prompting") + fs.BoolVar(&draft, "draft", false, "create GitHub release as a draft") + fs.BoolVar(&prerelease, "prerelease", false, "mark GitHub release as a pre-release") fs.Usage = func() { fmt.Fprintln(os.Stderr, "usage: monorel release [options] ...") fmt.Fprintln(os.Stderr, "") @@ -159,7 +161,7 @@ func runRelease(args []string) { printGroupHeader(cwd, group) relPath, _ := filepath.Rel(cwd, group.root) relPath = filepath.ToSlash(relPath) - processModule(group, relPath, dryRun, yes) + processModule(group, relPath, dryRun, yes, draft, prerelease) } } @@ -950,7 +952,7 @@ func printGroupHeader(cwd string, group *moduleGroup) { // for one module group. relPath is the path from the caller's CWD to the // module root; it is used in the script for all paths so that the script can // be run from the directory where monorel was invoked. -func processModule(group *moduleGroup, relPath string, dryRun, yes bool) { +func processModule(group *moduleGroup, relPath string, dryRun, yes, draft, prerelease bool) { modRoot := group.root bins := group.bins @@ -1091,7 +1093,7 @@ func processModule(group *moduleGroup, relPath string, dryRun, yes bool) { steps := buildModuleSteps( modRoot, relPath, projectName, bins, version, currentTag, repoPath, headSHA, - releaseNotes, isPreRelease, needsNewTag, + releaseNotes, needsNewTag, draft, prerelease, ) if err := runSteps(steps, dryRun, yes); err != nil { fmt.Fprintf(os.Stderr, "monorel: %v\n", err) @@ -1351,7 +1353,7 @@ func buildModuleSteps( modRoot, relPath, projectName string, bins []binary, version, currentTag, repoPath, headSHA string, releaseNotes string, - isPreRelease, needsNewTag bool, + needsNewTag, draft, prerelease bool, ) []releaseStep { distDir := filepath.Join(modRoot, "dist") var distRelDir string @@ -1415,31 +1417,47 @@ func buildModuleSteps( }, }) - // Step: Create draft GitHub release. - var ghCreateDisplay []string - ghCreateDisplay = append(ghCreateDisplay, fmt.Sprintf("gh release create %q \\", currentTag)) - ghCreateDisplay = append(ghCreateDisplay, fmt.Sprintf(" --title %q \\", title)) - ghCreateDisplay = append(ghCreateDisplay, fmt.Sprintf(" --notes %s \\", shellSingleQuote(releaseNotes))) - if isPreRelease { - ghCreateDisplay = append(ghCreateDisplay, " --prerelease \\") + // Step: Create GitHub release. + // --draft and --prerelease are always explicit so the intent is unambiguous. + draftFlag := "--draft=false" + if draft { + draftFlag = "--draft" + } + prereleaseFlag := "--prerelease=false" + if prerelease { + prereleaseFlag = "--prerelease" + } + var stepTitle string + switch { + case draft && prerelease: + stepTitle = fmt.Sprintf("create draft pre-release %s", currentTag) + case draft: + stepTitle = fmt.Sprintf("create draft GitHub release %s", currentTag) + case prerelease: + stepTitle = fmt.Sprintf("create pre-release %s", currentTag) + default: + stepTitle = fmt.Sprintf("create GitHub release %s", currentTag) + } + ghCreateDisplay := []string{ + fmt.Sprintf("gh release create %q \\", currentTag), + fmt.Sprintf(" --title %q \\", title), + fmt.Sprintf(" --notes %s \\", shellSingleQuote(releaseNotes)), + fmt.Sprintf(" %s \\", draftFlag), + fmt.Sprintf(" %s \\", prereleaseFlag), + fmt.Sprintf(" --target %q", headSHA), } - ghCreateDisplay = append(ghCreateDisplay, " --draft \\") - ghCreateDisplay = append(ghCreateDisplay, fmt.Sprintf(" --target %q", headSHA)) steps = append(steps, releaseStep{ - title: "Create draft GitHub release", - prompt: fmt.Sprintf("create draft GitHub release %s", currentTag), + title: "Create GitHub release", + prompt: stepTitle, display: ghCreateDisplay, run: func() error { - ghArgs := []string{"release", "create", currentTag, + return execIn(modRoot, "gh", "release", "create", currentTag, "--title", title, "--notes", releaseNotes, - "--draft", + draftFlag, + prereleaseFlag, "--target", headSHA, - } - if isPreRelease { - ghArgs = append(ghArgs, "--prerelease") - } - return execIn(modRoot, "gh", ghArgs...) + ) }, }) @@ -1479,15 +1497,18 @@ func buildModuleSteps( }, }) - // Step: Publish release. - steps = append(steps, releaseStep{ - title: "Publish release (remove draft)", - prompt: fmt.Sprintf("publish release %s", currentTag), - display: []string{fmt.Sprintf("gh release edit %q --draft=false", currentTag)}, - run: func() error { - return execIn(modRoot, "gh", "release", "edit", currentTag, "--draft=false") - }, - }) + // Step: Publish release — only needed when creating as draft without prerelease. + // draft+prerelease: stays as draft; neither: published immediately; prerelease-only: published immediately. + if draft && !prerelease { + steps = append(steps, releaseStep{ + title: "Publish release (remove draft)", + prompt: fmt.Sprintf("publish release %s", currentTag), + display: []string{fmt.Sprintf("gh release edit %q --draft=false", currentTag)}, + run: func() error { + return execIn(modRoot, "gh", "release", "edit", currentTag, "--draft=false") + }, + }) + } return steps }