feat: add verify command (#4527)
This commit is contained in:
parent
6709c974a4
commit
eaafdf3623
1
.github/workflows/pr.yml
vendored
1
.github/workflows/pr.yml
vendored
@ -166,6 +166,7 @@ jobs:
|
||||
|
||||
- run: ./golangci-lint config
|
||||
- run: ./golangci-lint config path
|
||||
- run: ./golangci-lint config verify --schema jsonschema/golangci.jsonschema.json
|
||||
|
||||
- run: ./golangci-lint help
|
||||
- run: ./golangci-lint help linters
|
||||
|
@ -163,6 +163,8 @@ issues:
|
||||
text: "SA1019: c.cfg.Run.ShowStats is deprecated: use Output.ShowStats instead."
|
||||
- path: pkg/golinters/govet.go
|
||||
text: "SA1019: cfg.CheckShadowing is deprecated: the linter should be enabled inside `Enable`."
|
||||
- path: pkg/commands/config.go
|
||||
text: "SA1019: cfg.Run.UseDefaultSkipDirs is deprecated: use Issues.UseDefaultExcludeDirs instead."
|
||||
|
||||
- path: pkg/golinters
|
||||
linters:
|
||||
|
2
Makefile
2
Makefile
@ -89,7 +89,7 @@ go.mod: FORCE
|
||||
go.sum: go.mod
|
||||
|
||||
website_copy_jsonschema:
|
||||
cp -r ./jsonschema ./docs/static
|
||||
go run ./scripts/website/copy_jsonschema/
|
||||
.PHONY: website_copy_jsonschema
|
||||
|
||||
website_expand_templates:
|
||||
|
2
go.mod
2
go.mod
@ -80,6 +80,7 @@ require (
|
||||
github.com/nishanths/exhaustive v0.12.0
|
||||
github.com/nishanths/predeclared v0.2.2
|
||||
github.com/nunnatsa/ginkgolinter v0.16.1
|
||||
github.com/pelletier/go-toml/v2 v2.1.1
|
||||
github.com/polyfloyd/go-errorlint v1.4.8
|
||||
github.com/quasilyte/go-ruleguard/dsl v0.3.22
|
||||
github.com/ryancurrah/gomodguard v1.3.1
|
||||
@ -161,7 +162,6 @@ require (
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/prometheus/client_golang v1.12.1 // indirect
|
||||
|
4
go.sum
generated
4
go.sum
generated
@ -410,8 +410,8 @@ github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT9
|
||||
github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
|
||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
|
||||
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
|
||||
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
|
||||
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
@ -17,13 +18,19 @@ type configCommand struct {
|
||||
viper *viper.Viper
|
||||
cmd *cobra.Command
|
||||
|
||||
opts config.LoaderOptions
|
||||
verifyOpts verifyOptions
|
||||
|
||||
buildInfo BuildInfo
|
||||
|
||||
log logutils.Log
|
||||
}
|
||||
|
||||
func newConfigCommand(log logutils.Log) *configCommand {
|
||||
func newConfigCommand(log logutils.Log, info BuildInfo) *configCommand {
|
||||
c := &configCommand{
|
||||
viper: viper.New(),
|
||||
log: log,
|
||||
viper: viper.New(),
|
||||
log: log,
|
||||
buildInfo: info,
|
||||
}
|
||||
|
||||
configCmd := &cobra.Command{
|
||||
@ -33,6 +40,15 @@ func newConfigCommand(log logutils.Log) *configCommand {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
return cmd.Help()
|
||||
},
|
||||
PersistentPreRunE: c.preRunE,
|
||||
}
|
||||
|
||||
verifyCommand := &cobra.Command{
|
||||
Use: "verify",
|
||||
Short: "Verify configuration against JSON schema",
|
||||
Args: cobra.NoArgs,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
RunE: c.executeVerify,
|
||||
}
|
||||
|
||||
configCmd.AddCommand(
|
||||
@ -41,11 +57,21 @@ func newConfigCommand(log logutils.Log) *configCommand {
|
||||
Short: "Print used config path",
|
||||
Args: cobra.NoArgs,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
Run: c.execute,
|
||||
PreRunE: c.preRunE,
|
||||
Run: c.executePath,
|
||||
},
|
||||
verifyCommand,
|
||||
)
|
||||
|
||||
flagSet := configCmd.PersistentFlags()
|
||||
flagSet.SortFlags = false // sort them as they are defined here
|
||||
|
||||
setupConfigFileFlagSet(flagSet, &c.opts)
|
||||
|
||||
// ex: --schema jsonschema/golangci.next.jsonschema.json
|
||||
verifyFlagSet := verifyCommand.Flags()
|
||||
verifyFlagSet.StringVar(&c.verifyOpts.schemaURL, "schema", "", color.GreenString("JSON schema URL"))
|
||||
_ = verifyFlagSet.MarkHidden("schema")
|
||||
|
||||
c.cmd = configCmd
|
||||
|
||||
return c
|
||||
@ -54,7 +80,16 @@ func newConfigCommand(log logutils.Log) *configCommand {
|
||||
func (c *configCommand) preRunE(cmd *cobra.Command, _ []string) error {
|
||||
// The command doesn't depend on the real configuration.
|
||||
// It only needs to know the path of the configuration file.
|
||||
loader := config.NewLoader(c.log.Child(logutils.DebugKeyConfigReader), c.viper, cmd.Flags(), config.LoaderOptions{}, config.NewDefault())
|
||||
cfg := config.NewDefault()
|
||||
|
||||
// Hack to hide deprecation messages related to `--skip-dirs-use-default`:
|
||||
// Flags are not bound then the default values, defined only through flags, are not applied.
|
||||
// In this command, file path and file information are the only requirements, i.e. it don't need flag values.
|
||||
//
|
||||
// TODO(ldez) add an option (check deprecation) to `Loader.Load()` but this require a dedicated PR.
|
||||
cfg.Run.UseDefaultSkipDirs = true
|
||||
|
||||
loader := config.NewLoader(c.log.Child(logutils.DebugKeyConfigReader), c.viper, cmd.Flags(), c.opts, cfg)
|
||||
|
||||
if err := loader.Load(); err != nil {
|
||||
return fmt.Errorf("can't load config: %w", err)
|
||||
@ -63,14 +98,14 @@ func (c *configCommand) preRunE(cmd *cobra.Command, _ []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *configCommand) execute(_ *cobra.Command, _ []string) {
|
||||
func (c *configCommand) executePath(cmd *cobra.Command, _ []string) {
|
||||
usedConfigFile := c.getUsedConfig()
|
||||
if usedConfigFile == "" {
|
||||
c.log.Warnf("No config file detected")
|
||||
os.Exit(exitcodes.NoConfigFileDetected)
|
||||
}
|
||||
|
||||
fmt.Println(usedConfigFile)
|
||||
cmd.Println(usedConfigFile)
|
||||
}
|
||||
|
||||
// getUsedConfig returns the resolved path to the golangci config file,
|
||||
|
176
pkg/commands/config_verify.go
Normal file
176
pkg/commands/config_verify.go
Normal file
@ -0,0 +1,176 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
hcversion "github.com/hashicorp/go-version"
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"github.com/santhosh-tekuri/jsonschema/v5"
|
||||
_ "github.com/santhosh-tekuri/jsonschema/v5/httploader"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/exitcodes"
|
||||
)
|
||||
|
||||
type verifyOptions struct {
|
||||
schemaURL string // For debugging purpose only (Flag only).
|
||||
}
|
||||
|
||||
func (c *configCommand) executeVerify(cmd *cobra.Command, _ []string) error {
|
||||
usedConfigFile := c.getUsedConfig()
|
||||
if usedConfigFile == "" {
|
||||
c.log.Warnf("No config file detected")
|
||||
os.Exit(exitcodes.NoConfigFileDetected)
|
||||
}
|
||||
|
||||
schemaURL, err := createSchemaURL(cmd.Flags(), c.buildInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get JSON schema: %w", err)
|
||||
}
|
||||
|
||||
err = validateConfiguration(schemaURL, usedConfigFile)
|
||||
if err != nil {
|
||||
var v *jsonschema.ValidationError
|
||||
if !errors.As(err, &v) {
|
||||
return fmt.Errorf("[%s] validate: %w", usedConfigFile, err)
|
||||
}
|
||||
|
||||
detail := v.DetailedOutput()
|
||||
|
||||
printValidationDetail(cmd, &detail)
|
||||
|
||||
return fmt.Errorf("the configuration contains invalid elements")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createSchemaURL(flags *pflag.FlagSet, buildInfo BuildInfo) (string, error) {
|
||||
schemaURL, err := flags.GetString("schema")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get schema flag: %w", err)
|
||||
}
|
||||
|
||||
if schemaURL != "" {
|
||||
return schemaURL, nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case buildInfo.Version != "" && buildInfo.Version != "(devel)":
|
||||
version, err := hcversion.NewVersion(buildInfo.Version)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("parse version: %w", err)
|
||||
}
|
||||
|
||||
schemaURL = fmt.Sprintf("https://golangci-lint.run/jsonschema/golangci.v%d.%d.jsonschema.json",
|
||||
version.Segments()[0], version.Segments()[1])
|
||||
|
||||
case buildInfo.Commit != "" && buildInfo.Commit != "?":
|
||||
if buildInfo.Commit == "unknown" {
|
||||
return "", errors.New("unknown commit information")
|
||||
}
|
||||
|
||||
commit := buildInfo.Commit
|
||||
|
||||
if strings.HasPrefix(commit, "(") {
|
||||
c, _, ok := strings.Cut(strings.TrimPrefix(commit, "("), ",")
|
||||
if !ok {
|
||||
return "", errors.New("commit information not found")
|
||||
}
|
||||
|
||||
commit = c
|
||||
}
|
||||
|
||||
schemaURL = fmt.Sprintf("https://raw.githubusercontent.com/golangci/golangci-lint/%s/jsonschema/golangci.next.jsonschema.json",
|
||||
commit)
|
||||
|
||||
default:
|
||||
return "", errors.New("version not found")
|
||||
}
|
||||
|
||||
return schemaURL, nil
|
||||
}
|
||||
|
||||
func validateConfiguration(schemaPath, targetFile string) error {
|
||||
compiler := jsonschema.NewCompiler()
|
||||
compiler.Draft = jsonschema.Draft7
|
||||
|
||||
schema, err := compiler.Compile(schemaPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("compile schema: %w", err)
|
||||
}
|
||||
|
||||
var m any
|
||||
|
||||
switch strings.ToLower(filepath.Ext(targetFile)) {
|
||||
case ".yaml", ".yml", ".json":
|
||||
m, err = decodeYamlFile(targetFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case ".toml":
|
||||
m, err = decodeTomlFile(targetFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
default:
|
||||
// unsupported
|
||||
return errors.New("unsupported configuration format")
|
||||
}
|
||||
|
||||
return schema.Validate(m)
|
||||
}
|
||||
|
||||
func printValidationDetail(cmd *cobra.Command, detail *jsonschema.Detailed) {
|
||||
if detail.Error != "" {
|
||||
cmd.PrintErrf("jsonschema: %q does not validate with %q: %s\n",
|
||||
strings.ReplaceAll(strings.TrimPrefix(detail.InstanceLocation, "/"), "/", "."), detail.KeywordLocation, detail.Error)
|
||||
}
|
||||
|
||||
for _, d := range detail.Errors {
|
||||
d := d
|
||||
printValidationDetail(cmd, &d)
|
||||
}
|
||||
}
|
||||
|
||||
func decodeYamlFile(filename string) (any, error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[%s] file open: %w", filename, err)
|
||||
}
|
||||
|
||||
defer func() { _ = file.Close() }()
|
||||
|
||||
var m any
|
||||
err = yaml.NewDecoder(file).Decode(&m)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[%s] YAML decode: %w", filename, err)
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func decodeTomlFile(filename string) (any, error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[%s] file open: %w", filename, err)
|
||||
}
|
||||
|
||||
defer func() { _ = file.Close() }()
|
||||
|
||||
var m any
|
||||
err = toml.NewDecoder(file).Decode(&m)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[%s] TOML decode: %w", filename, err)
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
140
pkg/commands/config_verify_test.go
Normal file
140
pkg/commands/config_verify_test.go
Normal file
@ -0,0 +1,140 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_createSchemaURL(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
flag string
|
||||
info BuildInfo
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "schema flag only",
|
||||
flag: "https://example.com",
|
||||
expected: "https://example.com",
|
||||
},
|
||||
{
|
||||
desc: "schema flag and build info",
|
||||
flag: "https://example.com",
|
||||
info: BuildInfo{
|
||||
Version: "v1.0.0",
|
||||
Commit: "cd8b11773c6c1f595e8eb98c0d4310af20ae20df",
|
||||
},
|
||||
expected: "https://example.com",
|
||||
},
|
||||
{
|
||||
desc: "version and commit",
|
||||
info: BuildInfo{
|
||||
Version: "v1.0.0",
|
||||
Commit: "cd8b11773c6c1f595e8eb98c0d4310af20ae20df",
|
||||
},
|
||||
expected: "https://golangci-lint.run/jsonschema/golangci.v1.0.jsonschema.json",
|
||||
},
|
||||
{
|
||||
desc: "commit only",
|
||||
info: BuildInfo{
|
||||
Commit: "cd8b11773c6c1f595e8eb98c0d4310af20ae20df",
|
||||
},
|
||||
expected: "https://raw.githubusercontent.com/golangci/golangci-lint/cd8b11773c6c1f595e8eb98c0d4310af20ae20df/jsonschema/golangci.next.jsonschema.json",
|
||||
},
|
||||
{
|
||||
desc: "version devel and commit",
|
||||
info: BuildInfo{
|
||||
Version: "(devel)",
|
||||
Commit: "cd8b11773c6c1f595e8eb98c0d4310af20ae20df",
|
||||
},
|
||||
expected: "https://raw.githubusercontent.com/golangci/golangci-lint/cd8b11773c6c1f595e8eb98c0d4310af20ae20df/jsonschema/golangci.next.jsonschema.json",
|
||||
},
|
||||
{
|
||||
desc: "composite commit info",
|
||||
info: BuildInfo{
|
||||
Version: "",
|
||||
Commit: `(cd8b11773c6c1f595e8eb98c0d4310af20ae20df, modified: "false", mod sum: "123")`,
|
||||
},
|
||||
expected: "https://raw.githubusercontent.com/golangci/golangci-lint/cd8b11773c6c1f595e8eb98c0d4310af20ae20df/jsonschema/golangci.next.jsonschema.json",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||
flags.String("schema", "", "")
|
||||
if test.flag != "" {
|
||||
_ = flags.Set("schema", test.flag)
|
||||
}
|
||||
|
||||
schemaURL, err := createSchemaURL(flags, test.info)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expected, schemaURL)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_createSchemaURL_error(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
info BuildInfo
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "commit unknown",
|
||||
info: BuildInfo{
|
||||
Commit: "unknown",
|
||||
},
|
||||
expected: "unknown commit information",
|
||||
},
|
||||
{
|
||||
desc: "commit ?",
|
||||
info: BuildInfo{
|
||||
Commit: "?",
|
||||
},
|
||||
expected: "version not found",
|
||||
},
|
||||
{
|
||||
desc: "version devel only",
|
||||
info: BuildInfo{
|
||||
Version: "(devel)",
|
||||
},
|
||||
expected: "version not found",
|
||||
},
|
||||
{
|
||||
desc: "invalid version",
|
||||
info: BuildInfo{
|
||||
Version: "example",
|
||||
},
|
||||
expected: "parse version: Malformed version: example",
|
||||
},
|
||||
{
|
||||
desc: "invalid composite commit info",
|
||||
info: BuildInfo{
|
||||
Version: "",
|
||||
Commit: `(cd8b11773c6c1f595e8eb98c0d4310af20ae20df)`,
|
||||
},
|
||||
expected: "commit information not found",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||
flags.String("schema", "", "")
|
||||
|
||||
_, err := createSchemaURL(flags, test.info)
|
||||
require.EqualError(t, err, test.expected)
|
||||
})
|
||||
}
|
||||
}
|
@ -61,7 +61,7 @@ func newRootCommand(info BuildInfo) *rootCommand {
|
||||
newLintersCommand(log).cmd,
|
||||
newRunCommand(log, info).cmd,
|
||||
newCacheCommand().cmd,
|
||||
newConfigCommand(log).cmd,
|
||||
newConfigCommand(log, info).cmd,
|
||||
newVersionCommand(info).cmd,
|
||||
newCustomCommand(log).cmd,
|
||||
)
|
||||
|
100
scripts/website/copy_jsonschema/main.go
Normal file
100
scripts/website/copy_jsonschema/main.go
Normal file
@ -0,0 +1,100 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
hcversion "github.com/hashicorp/go-version"
|
||||
|
||||
"github.com/golangci/golangci-lint/scripts/website/github"
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := copySchemas()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func copySchemas() error {
|
||||
dstDir := filepath.FromSlash("docs/static/jsonschema/")
|
||||
|
||||
err := os.RemoveAll(dstDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("remove dir: %w", err)
|
||||
}
|
||||
|
||||
err = os.MkdirAll(dstDir, os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("make dir: %w", err)
|
||||
}
|
||||
|
||||
// The key is the destination file.
|
||||
// The value is the source file.
|
||||
files := map[string]string{}
|
||||
|
||||
entries, err := os.ReadDir("jsonschema")
|
||||
if err != nil {
|
||||
return fmt.Errorf("read dir: %w", err)
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if strings.HasSuffix(entry.Name(), ".jsonschema.json") {
|
||||
files[entry.Name()] = entry.Name()
|
||||
}
|
||||
}
|
||||
|
||||
latest, err := github.GetLatestVersion()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get latest release version: %w", err)
|
||||
}
|
||||
|
||||
version, err := hcversion.NewVersion(latest)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse version: %w", err)
|
||||
}
|
||||
|
||||
versioned := fmt.Sprintf("golangci.v%d.%d.jsonschema.json", version.Segments()[0], version.Segments()[1])
|
||||
files[versioned] = "golangci.jsonschema.json"
|
||||
|
||||
for dst, src := range files {
|
||||
err := copyFile(filepath.Join("jsonschema", src), filepath.Join(dstDir, dst))
|
||||
if err != nil {
|
||||
return fmt.Errorf("copy files: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyFile(src, dst string) error {
|
||||
source, err := os.Open(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open file %s: %w", src, err)
|
||||
}
|
||||
|
||||
defer func() { _ = source.Close() }()
|
||||
|
||||
info, err := source.Stat()
|
||||
if err != nil {
|
||||
return fmt.Errorf("file %s not found: %w", src, err)
|
||||
}
|
||||
|
||||
destination, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, info.Mode())
|
||||
if err != nil {
|
||||
return fmt.Errorf("create file %s: %w", dst, err)
|
||||
}
|
||||
|
||||
defer func() { _ = destination.Close() }()
|
||||
|
||||
_, err = io.Copy(destination, source)
|
||||
if err != nil {
|
||||
return fmt.Errorf("copy file %s to %s: %w", src, dst, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -3,15 +3,13 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golangci/golangci-lint/internal/renameio"
|
||||
"github.com/golangci/golangci-lint/scripts/website/github"
|
||||
"github.com/golangci/golangci-lint/scripts/website/types"
|
||||
)
|
||||
|
||||
@ -89,46 +87,6 @@ func processDoc(path string, replacements map[string]string, madeReplacements ma
|
||||
return nil
|
||||
}
|
||||
|
||||
type latestRelease struct {
|
||||
TagName string `json:"tag_name"`
|
||||
}
|
||||
|
||||
func getLatestVersion() (string, error) {
|
||||
endpoint := "https://api.github.com/repos/golangci/golangci-lint/releases/latest"
|
||||
|
||||
//nolint:noctx // request timeout handled by the client
|
||||
req, err := http.NewRequest(http.MethodGet, endpoint, http.NoBody)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("prepare a HTTP request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Accept", "application/vnd.github.v3+json")
|
||||
|
||||
client := &http.Client{Timeout: 2 * time.Second}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get HTTP response for the latest tag: %w", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read a body for the latest tag: %w", err)
|
||||
}
|
||||
|
||||
release := latestRelease{}
|
||||
|
||||
err = json.Unmarshal(body, &release)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unmarshal the body for the latest tag: %w", err)
|
||||
}
|
||||
|
||||
return release.TagName, nil
|
||||
}
|
||||
|
||||
func buildTemplateContext() (map[string]string, error) {
|
||||
snippets, err := getExampleSnippets()
|
||||
if err != nil {
|
||||
@ -150,7 +108,7 @@ func buildTemplateContext() (map[string]string, error) {
|
||||
return nil, fmt.Errorf("read CHANGELOG.md: %w", err)
|
||||
}
|
||||
|
||||
latestVersion, err := getLatestVersion()
|
||||
latestVersion, err := github.GetLatestVersion()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get the latest version: %w", err)
|
||||
}
|
||||
|
49
scripts/website/github/github.go
Normal file
49
scripts/website/github/github.go
Normal file
@ -0,0 +1,49 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const endpoint = "https://api.github.com/repos/golangci/golangci-lint/releases/latest"
|
||||
|
||||
type releaseInfo struct {
|
||||
TagName string `json:"tag_name"`
|
||||
}
|
||||
|
||||
// GetLatestVersion gets latest release information.
|
||||
func GetLatestVersion() (string, error) {
|
||||
//nolint:noctx // request timeout handled by the client
|
||||
req, err := http.NewRequest(http.MethodGet, endpoint, http.NoBody)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("prepare a HTTP request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Accept", "application/vnd.github.v3+json")
|
||||
|
||||
client := &http.Client{Timeout: 2 * time.Second}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get HTTP response for the latest tag: %w", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read a body for the latest tag: %w", err)
|
||||
}
|
||||
|
||||
release := releaseInfo{}
|
||||
|
||||
err = json.Unmarshal(body, &release)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unmarshal the body for the latest tag: %w", err)
|
||||
}
|
||||
|
||||
return release.TagName, nil
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user