mirror of
https://github.com/therootcompany/golib.git
synced 2026-03-02 23:57:59 +00:00
fix(monorel): guard .goreleaser.yaml overwrite in release; loosen compat check
release subcommand:
- Replace yamlLooksCorrect with yamlIsCompatible: file is considered OK if
{{ .ProjectName }} is absent AND at least one binary's VERSION string is
present. Extra hand-edited binaries (like fixtures) no longer trigger a
rewrite.
- Before overwriting an existing file, prompt the user [Y/n]. --yes does
not skip this prompt; --force does. If stdin is not a terminal and
--force is not set, the command errors rather than silently clobbering.
init subcommand: unchanged — still uses the strict yamlLooksCorrect check
(all binaries must be present, ldflags must include main.version).
This commit is contained in:
parent
c14bc239f5
commit
1289f3e5b6
@ -112,11 +112,12 @@ func usage() {
|
|||||||
|
|
||||||
func runRelease(args []string) {
|
func runRelease(args []string) {
|
||||||
fs := flag.NewFlagSet("monorel release", flag.ExitOnError)
|
fs := flag.NewFlagSet("monorel release", flag.ExitOnError)
|
||||||
var recursive, all, dryRun, yes, draft, prerelease bool
|
var recursive, all, dryRun, yes, force, draft, prerelease bool
|
||||||
fs.BoolVar(&recursive, "recursive", false, "find all main packages recursively under each path")
|
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(&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(&dryRun, "dry-run", false, "show each step without running it")
|
||||||
fs.BoolVar(&yes, "yes", false, "run all steps without prompting")
|
fs.BoolVar(&yes, "yes", false, "run all steps without prompting")
|
||||||
|
fs.BoolVar(&force, "force", false, "overwrite .goreleaser.yaml without prompting even if it has been modified")
|
||||||
fs.BoolVar(&draft, "draft", false, "keep the GitHub release in draft state after uploading (default: publish)")
|
fs.BoolVar(&draft, "draft", false, "keep the GitHub release in draft state after uploading (default: publish)")
|
||||||
fs.BoolVar(&prerelease, "prerelease", false, "keep the GitHub release marked as pre-release even for clean tags (default: promote clean tags to stable)")
|
fs.BoolVar(&prerelease, "prerelease", false, "keep the GitHub release marked as pre-release even for clean tags (default: promote clean tags to stable)")
|
||||||
fs.Usage = func() {
|
fs.Usage = func() {
|
||||||
@ -161,7 +162,7 @@ func runRelease(args []string) {
|
|||||||
printGroupHeader(cwd, group)
|
printGroupHeader(cwd, group)
|
||||||
relPath, _ := filepath.Rel(cwd, group.root)
|
relPath, _ := filepath.Rel(cwd, group.root)
|
||||||
relPath = filepath.ToSlash(relPath)
|
relPath = filepath.ToSlash(relPath)
|
||||||
processModule(group, relPath, dryRun, yes, draft, prerelease)
|
processModule(group, relPath, dryRun, yes, force, draft, prerelease)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -952,7 +953,7 @@ func printGroupHeader(cwd string, group *moduleGroup) {
|
|||||||
// for one module group. relPath is the path from the caller's CWD to the
|
// 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
|
// 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.
|
// be run from the directory where monorel was invoked.
|
||||||
func processModule(group *moduleGroup, relPath string, dryRun, yes, draft, prerelease bool) {
|
func processModule(group *moduleGroup, relPath string, dryRun, yes, force, draft, prerelease bool) {
|
||||||
modRoot := group.root
|
modRoot := group.root
|
||||||
bins := group.bins
|
bins := group.bins
|
||||||
|
|
||||||
@ -971,18 +972,18 @@ func processModule(group *moduleGroup, relPath string, dryRun, yes, draft, prere
|
|||||||
rawURL := mustRunIn(modRoot, "git", "remote", "get-url", "origin")
|
rawURL := mustRunIn(modRoot, "git", "remote", "get-url", "origin")
|
||||||
repoPath := normalizeGitURL(rawURL)
|
repoPath := normalizeGitURL(rawURL)
|
||||||
|
|
||||||
// 1. Write .goreleaser.yaml (always regenerate).
|
// 1. Write .goreleaser.yaml when necessary.
|
||||||
// Track whether this is a first-time creation: auto-commit and auto-tag
|
// For release, the file is considered compatible if it has no stock
|
||||||
// only apply when the file is new. If it already exists, just update it
|
// {{ .ProjectName }} template and at least one binary uses the VERSION env
|
||||||
// on disk and leave committing to the user.
|
// var — local edits that add extra binaries etc. are preserved.
|
||||||
|
// Auto-commit and auto-tag only apply when the file is brand new.
|
||||||
yamlContent := goreleaserYAML(projectName, bins)
|
yamlContent := goreleaserYAML(projectName, bins)
|
||||||
yamlPath := filepath.Join(modRoot, ".goreleaser.yaml")
|
yamlPath := filepath.Join(modRoot, ".goreleaser.yaml")
|
||||||
existing, readErr := os.ReadFile(yamlPath)
|
existing, readErr := os.ReadFile(yamlPath)
|
||||||
isNewFile := readErr != nil
|
isNewFile := readErr != nil
|
||||||
isChanged := isNewFile || !yamlLooksCorrect(string(existing), bins)
|
isChanged := isNewFile || !yamlIsCompatible(string(existing), bins)
|
||||||
if !isNewFile && isChanged {
|
if !isNewFile && isChanged {
|
||||||
// Warn if a stock {{ .ProjectName }} template is in use (one of the
|
// Warn if a stock {{ .ProjectName }} template is in use.
|
||||||
// reasons yamlLooksCorrect may have returned false).
|
|
||||||
hasProjectName := strings.Contains(string(existing), "{{ .ProjectName }}") ||
|
hasProjectName := strings.Contains(string(existing), "{{ .ProjectName }}") ||
|
||||||
strings.Contains(string(existing), "{{.ProjectName}}")
|
strings.Contains(string(existing), "{{.ProjectName}}")
|
||||||
gitInfo, gitErr := os.Stat(filepath.Join(modRoot, ".git"))
|
gitInfo, gitErr := os.Stat(filepath.Join(modRoot, ".git"))
|
||||||
@ -991,6 +992,23 @@ func processModule(group *moduleGroup, relPath string, dryRun, yes, draft, prere
|
|||||||
fmt.Fprintf(os.Stderr, "warning: %s: contains {{ .ProjectName }} but module is a monorepo subdirectory;\n", yamlPath)
|
fmt.Fprintf(os.Stderr, "warning: %s: contains {{ .ProjectName }} but module is a monorepo subdirectory;\n", yamlPath)
|
||||||
fmt.Fprintf(os.Stderr, " replacing stock goreleaser config with monorel-generated config.\n")
|
fmt.Fprintf(os.Stderr, " replacing stock goreleaser config with monorel-generated config.\n")
|
||||||
}
|
}
|
||||||
|
// Prompt before overwriting a modified file. --yes does not apply;
|
||||||
|
// use --force to skip the prompt. If stdin is not a terminal and
|
||||||
|
// --force is not set, refuse rather than silently clobber.
|
||||||
|
if !force {
|
||||||
|
fi, statErr := os.Stdin.Stat()
|
||||||
|
isTTY := statErr == nil && fi.Mode()&os.ModeCharDevice != 0
|
||||||
|
if !isTTY {
|
||||||
|
fatalf("%s needs updating but stdin is not a terminal; use --force to overwrite", cwdRelPath(yamlPath))
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "%s needs updating; overwrite? [Y/n] ", cwdRelPath(yamlPath))
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
line, _ := reader.ReadString('\n')
|
||||||
|
if resp := strings.ToLower(strings.TrimSpace(line)); resp == "n" || resp == "no" {
|
||||||
|
fmt.Fprintf(os.Stderr, "skipped %s\n", cwdRelPath(yamlPath))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if isChanged {
|
if isChanged {
|
||||||
if err := os.WriteFile(yamlPath, []byte(yamlContent), 0o644); err != nil {
|
if err := os.WriteFile(yamlPath, []byte(yamlContent), 0o644); err != nil {
|
||||||
@ -1273,6 +1291,24 @@ func goreleaserYAML(projectName string, bins []binary) string {
|
|||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// yamlIsCompatible is the looser check used by the release subcommand.
|
||||||
|
// It returns true when the file has no stock {{ .ProjectName }} template and
|
||||||
|
// at least one binary already uses the VERSION env var in its archive name.
|
||||||
|
// This preserves hand-edited files that add extra binaries or tweak settings,
|
||||||
|
// where yamlLooksCorrect would demand every declared binary be present.
|
||||||
|
func yamlIsCompatible(content string, bins []binary) bool {
|
||||||
|
if strings.Contains(content, "{{ .ProjectName }}") ||
|
||||||
|
strings.Contains(content, "{{.ProjectName}}") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, bin := range bins {
|
||||||
|
if strings.Contains(content, bin.name+"_{{ .Env.VERSION }}_") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// yamlLooksCorrect returns true when content appears to be a valid monorel-
|
// yamlLooksCorrect returns true when content appears to be a valid monorel-
|
||||||
// generated (or compatible) .goreleaser.yaml for the given binaries:
|
// generated (or compatible) .goreleaser.yaml for the given binaries:
|
||||||
//
|
//
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user