mirror of
https://github.com/juanfont/headscale.git
synced 2026-09-26 18:24:54 +09:00
823db85509
`go get -u` takes the highest semver the proxy offers: a fork's stray tag sorting above its real branch, or a module that has moved and kept tagging under the old path. Resolving first refuses both, and names the compare link for every version that does move.
233 lines
5.7 KiB
Go
233 lines
5.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"golang.org/x/mod/semver"
|
|
)
|
|
|
|
const pypiURL = "https://pypi.org/pypi/%s/json"
|
|
|
|
var (
|
|
// preCommitRev matches the pinned revision of the upstream hook repository.
|
|
preCommitRev = regexp.MustCompile(`(?m)^(\s*- repo: https://github\.com/pre-commit/pre-commit-hooks\n\s*rev: )(\S+)`)
|
|
|
|
// pyRequirement splits "mkdocs-materialx[imaging]~=10.1" into its parts.
|
|
pyRequirement = regexp.MustCompile(`^([A-Za-z0-9._-]+)(\[[^\]]*\])?~=(\d+)\.(\d+)$`)
|
|
)
|
|
|
|
// applyOapiCodegen moves the generator pin in the Makefile. The generate area
|
|
// reads the pin back out of the Makefile, so the clients are regenerated with
|
|
// whatever this lands on.
|
|
func applyOapiCodegen(ctx context.Context, r *repo) (change, error) {
|
|
have, err := oapiVersion(r)
|
|
if err != nil {
|
|
return change{}, err
|
|
}
|
|
|
|
want, err := latestVersion(ctx, "github.com/oapi-codegen/oapi-codegen/v2", have)
|
|
if err != nil {
|
|
return change{}, err
|
|
}
|
|
|
|
if want == have {
|
|
return change{Empty: true}, nil
|
|
}
|
|
|
|
content, err := r.readFile("Makefile")
|
|
if err != nil {
|
|
return change{}, err
|
|
}
|
|
|
|
// Both invocations carry the pin; leaving one behind would generate the
|
|
// two clients with different tools.
|
|
updated := strings.ReplaceAll(content,
|
|
"oapi-codegen/v2/cmd/oapi-codegen@"+have,
|
|
"oapi-codegen/v2/cmd/oapi-codegen@"+want)
|
|
|
|
err = r.writeFile("Makefile", updated)
|
|
if err != nil {
|
|
return change{}, err
|
|
}
|
|
|
|
return change{
|
|
Summary: fmt.Sprintf("oapi-codegen %s to %s", have, want),
|
|
Detail: []string{fmt.Sprintf("Makefile oapi-codegen %s -> %s", have, want)},
|
|
}, nil
|
|
}
|
|
|
|
// gateOapiCodegen asserts no invocation kept the old pin.
|
|
func gateOapiCodegen(_ context.Context, r *repo) error {
|
|
content, err := r.readFile("Makefile")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
version, err := oapiVersion(r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
want := strings.Count(content, "oapi-codegen/v2/cmd/oapi-codegen@")
|
|
|
|
got := strings.Count(content, "oapi-codegen/v2/cmd/oapi-codegen@"+version)
|
|
if got != want {
|
|
return fmt.Errorf("%w: %d of %d oapi-codegen invocations use %s",
|
|
errNoMatchingTag, got, want, version)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// applyPreCommit moves the only external hook revision. Every other hook in the
|
|
// config is language: system and follows the devShell.
|
|
func applyPreCommit(ctx context.Context, r *repo) (change, error) {
|
|
content, err := r.readFile(".pre-commit-config.yaml")
|
|
if err != nil {
|
|
return change{}, err
|
|
}
|
|
|
|
m := preCommitRev.FindStringSubmatch(content)
|
|
if m == nil {
|
|
return change{}, fmt.Errorf("%w: pre-commit-hooks rev", errNoMatchingTag)
|
|
}
|
|
|
|
want, err := latestRelease(ctx, "pre-commit", "pre-commit-hooks")
|
|
if err != nil {
|
|
return change{}, err
|
|
}
|
|
|
|
if want == m[2] {
|
|
return change{Empty: true}, nil
|
|
}
|
|
|
|
err = r.writeFile(".pre-commit-config.yaml", preCommitRev.ReplaceAllString(content, "${1}"+want))
|
|
if err != nil {
|
|
return change{}, err
|
|
}
|
|
|
|
return change{
|
|
Summary: fmt.Sprintf("pre-commit-hooks %s to %s", m[2], want),
|
|
Detail: []string{fmt.Sprintf(".pre-commit-config.yaml rev %s -> %s", m[2], want)},
|
|
}, nil
|
|
}
|
|
|
|
// pypiVersion is the newest published version of a distribution.
|
|
func pypiVersion(ctx context.Context, name string) (string, error) {
|
|
body, err := fetch(ctx, fmt.Sprintf(pypiURL, name))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var meta struct {
|
|
Info struct {
|
|
Version string `json:"version"`
|
|
} `json:"info"`
|
|
}
|
|
|
|
if err := json.Unmarshal(body, &meta); err != nil { //nolint:noinlineerr
|
|
return "", fmt.Errorf("decoding PyPI metadata for %s: %w", name, err)
|
|
}
|
|
|
|
return meta.Info.Version, nil
|
|
}
|
|
|
|
// applyDocsRequirements raises the compatible-release floors in the docs
|
|
// requirements. "~=X.Y" already admits newer patch and minor releases, so this
|
|
// is about keeping the recorded floor honest rather than unblocking an upgrade.
|
|
func applyDocsRequirements(ctx context.Context, r *repo) (change, error) {
|
|
const file = "docs/requirements.txt"
|
|
|
|
content, err := r.readFile(file)
|
|
if err != nil {
|
|
return change{}, err
|
|
}
|
|
|
|
var (
|
|
out strings.Builder
|
|
details []string
|
|
)
|
|
|
|
for line := range strings.Lines(content) {
|
|
bumped, detail, err := bumpRequirement(ctx, line)
|
|
if err != nil {
|
|
return change{}, err
|
|
}
|
|
|
|
out.WriteString(bumped)
|
|
|
|
if detail != "" {
|
|
details = append(details, detail)
|
|
}
|
|
}
|
|
|
|
if len(details) == 0 {
|
|
return change{Empty: true}, nil
|
|
}
|
|
|
|
err = r.writeFile(file, out.String())
|
|
if err != nil {
|
|
return change{}, err
|
|
}
|
|
|
|
return change{
|
|
Summary: fmt.Sprintf("%d docs requirement floors", len(details)),
|
|
Detail: details,
|
|
}, nil
|
|
}
|
|
|
|
// bumpRequirement returns the line to write and, when it moved, a description.
|
|
func bumpRequirement(ctx context.Context, line string) (string, string, error) {
|
|
trimmed := strings.TrimRight(line, "\n")
|
|
|
|
m := pyRequirement.FindStringSubmatch(trimmed)
|
|
if m == nil {
|
|
return line, "", nil
|
|
}
|
|
|
|
name, extras, have := m[1], m[2], m[3]+"."+m[4]
|
|
|
|
latest, err := pypiVersion(ctx, name)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
parts := strings.SplitN(latest, ".", 3)
|
|
if len(parts) < 2 {
|
|
return line, "", nil
|
|
}
|
|
|
|
want := parts[0] + "." + parts[1]
|
|
if semver.Compare("v"+want, "v"+have) <= 0 {
|
|
return line, "", nil
|
|
}
|
|
|
|
return fmt.Sprintf("%s%s~=%s\n", name, extras, want),
|
|
fmt.Sprintf("%s ~=%s -> ~=%s", name, have, want), nil
|
|
}
|
|
|
|
func toolAreas() []area {
|
|
return []area{
|
|
{
|
|
Name: "tools:oapi-codegen",
|
|
Apply: applyOapiCodegen,
|
|
Gate: gateOapiCodegen,
|
|
Message: func(c change) string { return "Makefile: bump " + c.Summary },
|
|
},
|
|
{
|
|
Name: "tools:pre-commit",
|
|
Apply: applyPreCommit,
|
|
Message: func(c change) string { return "prek: bump " + c.Summary },
|
|
},
|
|
{
|
|
Name: "tools:docs",
|
|
Apply: applyDocsRequirements,
|
|
Message: func(c change) string { return "docs: raise " + c.Summary },
|
|
},
|
|
}
|
|
}
|