From c43d47ea3d04f3f7d177d4c6d5715a99632835a6 Mon Sep 17 00:00:00 2001 From: Kazuma Watanabe Date: Sun, 9 Jul 2023 12:45:34 +0000 Subject: [PATCH] Add make release for release automation --- .github/workflows/release.yml | 2 +- .goreleaser.yml | 3 - CHANGELOG.md | 2 + Makefile | 3 + docs/developer-guide/README.md | 1 + docs/developer-guide/releasing.md | 23 +++ go.mod | 8 +- go.sum | 20 +-- plugin/install.go | 2 +- tools.go | 9 -- tools/release/main.go | 250 ++++++++++++++++++++++++++++++ 11 files changed, 296 insertions(+), 27 deletions(-) create mode 100644 docs/developer-guide/releasing.md delete mode 100644 tools.go create mode 100644 tools/release/main.go diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 832b318e5..1dd470a04 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,6 +29,6 @@ jobs: uses: goreleaser/goreleaser-action@v4 with: version: v1.12.3 - args: release --rm-dist + args: release --rm-dist --release-notes tools/release/release-note.md env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.goreleaser.yml b/.goreleaser.yml index 40d043d40..25083f843 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -18,8 +18,6 @@ archives: format: zip files: - none* -changelog: - skip: true checksum: name_template: 'checksums.txt' signs: @@ -38,6 +36,5 @@ release: github: owner: terraform-linters name: tflint - draft: true snapshot: name_template: "{{ .Tag }}-dev" diff --git a/CHANGELOG.md b/CHANGELOG.md index 755daab8d..d696931a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +See https://github.com/terraform-linters/tflint/releases for later releases. + ## 0.47.0 (2023-06-18) This release introduces autofix feature. Running `tflint --fix` will automatically fix issues as possible. Note that not all rules support autofix. In order to support autofix, plugins must be built with SDK v0.17+ and implement autofix. diff --git a/Makefile b/Makefile index 7d81452a7..23288f944 100644 --- a/Makefile +++ b/Makefile @@ -26,4 +26,7 @@ clean: generate: go generate ./... +release: + go run ./tools/release/main.go + .PHONY: prepare test build install e2e lint clean generate diff --git a/docs/developer-guide/README.md b/docs/developer-guide/README.md index 8e8dc6d3a..031943d20 100644 --- a/docs/developer-guide/README.md +++ b/docs/developer-guide/README.md @@ -7,3 +7,4 @@ The goal of this guide is to quickly understand how TFLint works and to help mor - [Architecture](architecture.md) - [Building TFLint](building.md) - [Writing Plugins](plugins.md) +- [Releasing a new version of TFLint](releasing.md) diff --git a/docs/developer-guide/releasing.md b/docs/developer-guide/releasing.md new file mode 100644 index 000000000..804f920c0 --- /dev/null +++ b/docs/developer-guide/releasing.md @@ -0,0 +1,23 @@ +# Releasing a new version of TFLint + +Maintainers with push access to TFLint can publish new releases. +Keep the following in mind when publishing a release: + +- Avoid changing behavior in patch versions as much as possible. Patch version updates should only contain bug fixes. + - However, if the behavior changes due to bug fixes, this is not the case. +- Write readable release notes. It would be nice to know what the benefits are of updating to this version, or what needs to be changed to update. + +To do this efficiently, releasing is automated as much as possible. After merging the changes you want to release into the master branch, run `make release` on the master branch. + +```console +$ make release +``` + +The `make release` does the following: + +- Accept new version as input and automatically rewrites the necessary files. +- Automatically generate a release note. You can edit it interactively. +- Run the required tests. +- Create a commit, tag it and push it to the remote. + +Note that this script does not build any binaries. These run on GitHub Actions. This script just pulls the trigger for it. diff --git a/go.mod b/go.mod index 3c133e1e2..04e6d6817 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/fatih/color v1.15.0 github.com/go-test/deep v1.1.0 github.com/google/go-cmp v0.5.9 - github.com/google/go-github/v35 v35.3.0 + github.com/google/go-github/v53 v53.2.0 github.com/google/uuid v1.3.0 github.com/hashicorp/go-plugin v1.4.10 github.com/hashicorp/go-uuid v1.0.3 @@ -31,7 +31,6 @@ require ( github.com/zclconf/go-cty-yaml v1.0.3 golang.org/x/crypto v0.10.0 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e - golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 golang.org/x/oauth2 v0.9.0 golang.org/x/text v0.10.0 google.golang.org/grpc v1.56.1 @@ -44,12 +43,14 @@ require ( cloud.google.com/go/iam v0.13.0 // indirect cloud.google.com/go/storage v1.28.1 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/aws/aws-sdk-go v1.44.122 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect + github.com/cloudflare/circl v1.3.3 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/go-querystring v1.0.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/googleapis/gax-go/v2 v2.7.1 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -77,7 +78,6 @@ require ( go.opencensus.io v0.24.0 // indirect golang.org/x/net v0.11.0 // indirect golang.org/x/sys v0.9.0 // indirect - golang.org/x/tools v0.10.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.114.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index d098107cc..d094fc473 100644 --- a/go.sum +++ b/go.sum @@ -194,6 +194,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= +github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= @@ -208,6 +210,7 @@ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1U github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bmatcuk/doublestar v1.1.5 h1:2bNwBOmhyFEFcoB3tGvTD5xanq+4kyOZlB8wFYbMjkk= github.com/bmatcuk/doublestar v1.1.5/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= +github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -216,6 +219,9 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= +github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -302,10 +308,10 @@ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8 github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-github/v35 v35.3.0 h1:fU+WBzuukn0VssbayTT+Zo3/ESKX9JYWjbZTLOTEyho= -github.com/google/go-github/v35 v35.3.0/go.mod h1:yWB7uCcVWaUbUP74Aq3whuMySRMatyRmq5U9FTNlbio= -github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-github/v53 v53.2.0 h1:wvz3FyF53v4BK+AsnvCmeNhf8AkTaeh2SoYu/XUvTtI= +github.com/google/go-github/v53 v53.2.0/go.mod h1:XhFRObz+m/l+UCm9b7KSIC3lT3NWSXGt7mOsAWEloao= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -530,7 +536,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -544,7 +549,6 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -637,7 +641,6 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -688,6 +691,7 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -780,8 +784,6 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= -golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/plugin/install.go b/plugin/install.go index c8ca7a00c..c34f43741 100644 --- a/plugin/install.go +++ b/plugin/install.go @@ -11,7 +11,7 @@ import ( "path/filepath" "runtime" - "github.com/google/go-github/v35/github" + "github.com/google/go-github/v53/github" "github.com/terraform-linters/tflint/tflint" "golang.org/x/oauth2" ) diff --git a/tools.go b/tools.go deleted file mode 100644 index a5a7c5702..000000000 --- a/tools.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build tools - -package tools - -import ( - // This package adds tools to go.mod to ensure that all users have the same versions - // All imports should be ignored (_) - _ "golang.org/x/lint/golint" -) diff --git a/tools/release/main.go b/tools/release/main.go new file mode 100644 index 000000000..025f56643 --- /dev/null +++ b/tools/release/main.go @@ -0,0 +1,250 @@ +package main + +import ( + "bufio" + "bytes" + "context" + "fmt" + "io" + "log" + "os" + "os/exec" + "regexp" + "strings" + + "github.com/google/go-github/v53/github" + "github.com/hashicorp/go-version" + "golang.org/x/oauth2" +) + +var token = os.Getenv("GITHUB_TOKEN") +var versionRegexp = regexp.MustCompile(`^\d+\.\d+\.\d+$`) +var goModRequireSDKRegexp = regexp.MustCompile(`github.com/terraform-linters/tflint-plugin-sdk v(.+)`) +var goModRequireBundledPluginRegexp = regexp.MustCompile(`github.com/terraform-linters/tflint-ruleset-terraform v(.+)`) + +func main() { + currentVersion := getCurrentVersion() + log.Printf("current version: %s", currentVersion) + + newVersion := getNewVersion() + log.Printf("new version: %s", newVersion) + + releaseNotePath := "tools/release/release-note.md" + + log.Println("checking requirements...") + if err := checkRequirements(currentVersion, newVersion); err != nil { + log.Fatal(err) + } + + log.Println("rewriting files with new version...") + if err := rewriteFileWithNewVersion("tflint/meta.go", currentVersion, newVersion); err != nil { + log.Fatal(err) + } + if err := rewriteFileWithNewVersion(".github/ISSUE_TEMPLATE/bug.yml", currentVersion, newVersion); err != nil { + log.Fatal(err) + } + + log.Println("generating release notes...") + if err := generateReleaseNote(currentVersion, newVersion, releaseNotePath); err != nil { + log.Fatal(err) + } + if err := editFileInteractive(releaseNotePath); err != nil { + log.Fatal(err) + } + + log.Println("running tests...") + if err := execCommand(os.Stdout, "make", "test"); err != nil { + log.Fatal(err) + } + if err := execCommand(os.Stdout, "make", "e2e"); err != nil { + log.Fatal(err) + } + + log.Println("commiting and tagging...") + if err := execCommand(os.Stdout, "git", "add", "."); err != nil { + log.Fatal(err) + } + if err := execCommand(os.Stdout, "git", "commit", "-m", fmt.Sprintf("Bump up version to v%s", newVersion)); err != nil { + log.Fatal(err) + } + if err := execCommand(os.Stdout, "git", "tag", fmt.Sprintf("v%s", newVersion)); err != nil { + log.Fatal(err) + } + + // if err := execCommand(os.Stdout, "git", "push", "origin", "master", "--tags"); err != nil { + // log.Fatal(err) + // } + log.Printf("pushed v%s", newVersion) +} + +func getCurrentVersion() string { + stdout := &bytes.Buffer{} + if err := execCommand(stdout, "git", "describe", "--tags", "--abbrev=0"); err != nil { + log.Fatal(err) + } + return strings.TrimPrefix(strings.TrimSpace(stdout.String()), "v") +} + +func getNewVersion() string { + reader := bufio.NewReader(os.Stdin) + fmt.Print(`Enter new version (without leading "v"): `) + input, err := reader.ReadString('\n') + if err != nil { + log.Fatal(fmt.Errorf("failed to read user input: %w", err)) + } + version := strings.TrimSpace(input) + + if !versionRegexp.MatchString(version) { + log.Fatal(fmt.Errorf("invalid version: %s", version)) + } + return version +} + +func checkRequirements(old string, new string) error { + if token == "" { + return fmt.Errorf("GITHUB_TOKEN is not set. Required to generate release notes") + } + + oldVersion, err := version.NewVersion(old) + if err != nil { + return fmt.Errorf("failed to parse current version: %w", err) + } + newVersion, err := version.NewVersion(new) + if err != nil { + return fmt.Errorf("failed to parse new version: %w", err) + } + if !newVersion.GreaterThan(oldVersion) { + return fmt.Errorf("new version must be greater than current version") + } + + if err := checkGitStatus(); err != nil { + return fmt.Errorf("failed to check Git status: %w", err) + } + + if err := checkGoModules(); err != nil { + return fmt.Errorf("failed to check Go modules: %w", err) + } + return nil +} + +func checkGitStatus() error { + stdout := &bytes.Buffer{} + if err := execCommand(stdout, "git", "status", "--porcelain"); err != nil { + return err + } + if strings.TrimSpace(stdout.String()) != "" { + return fmt.Errorf("the current working tree is dirty. Please commit or stash changes") + } + + stdout = &bytes.Buffer{} + if err := execCommand(stdout, "git", "rev-parse", "--abbrev-ref", "HEAD"); err != nil { + return err + } + // if strings.TrimSpace(stdout.String()) != "master" { + // return fmt.Errorf("the current branch is not master, got %s", strings.TrimSpace(stdout.String())) + // } + + stdout = &bytes.Buffer{} + if err := execCommand(stdout, "git", "config", "--get", "remote.origin.url"); err != nil { + return err + } + if !strings.Contains(strings.TrimSpace(stdout.String()), "terraform-linters/tflint") { + return fmt.Errorf("remote.origin is not terraform-linters/tflint, got %s", strings.TrimSpace(stdout.String())) + } + return nil +} + +func checkGoModules() error { + bytes, err := os.ReadFile("go.mod") + if err != nil { + return fmt.Errorf("failed to read go.mod: %w", err) + } + content := string(bytes) + + matches := goModRequireSDKRegexp.FindStringSubmatch(content) + if len(matches) != 2 { + return fmt.Errorf(`failed to parse go.mod: did not match "%s"`, goModRequireSDKRegexp.String()) + } + if !versionRegexp.MatchString(matches[1]) { + return fmt.Errorf(`failed to parse go.mod: SDK version "%s" is not stable`, matches[1]) + } + + matches = goModRequireBundledPluginRegexp.FindStringSubmatch(content) + if len(matches) != 2 { + return fmt.Errorf(`failed to parse go.mod: did not match "%s"`, goModRequireBundledPluginRegexp.String()) + } + if !versionRegexp.MatchString(matches[1]) { + return fmt.Errorf(`failed to parse go.mod: bundled plugin version "%s" is not stable`, matches[1]) + } + return nil +} + +func rewriteFileWithNewVersion(path string, old string, new string) error { + log.Printf("rewrite %s", path) + + bytes, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("failed to read %s: %w", path, err) + } + content := string(bytes) + + replaced := strings.ReplaceAll(content, old, new) + if replaced == content { + return fmt.Errorf("%s is not changed", path) + } + + if err := os.WriteFile(path, []byte(replaced), 0644); err != nil { + return fmt.Errorf("failed to write %s: %w", path, err) + } + return nil +} + +func generateReleaseNote(old string, new string, savedPath string) error { + tagName := fmt.Sprintf("v%s", new) + previousTagName := fmt.Sprintf("v%s", old) + targetCommitish := "master" + + client := github.NewClient(oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(&oauth2.Token{ + AccessToken: token, + }))) + + note, _, err := client.Repositories.GenerateReleaseNotes( + context.Background(), + "terraform-linters", + "tflint", + &github.GenerateNotesOptions{ + TagName: tagName, + PreviousTagName: &previousTagName, + TargetCommitish: &targetCommitish, + }, + ) + if err != nil { + return fmt.Errorf("failed to generate release notes: %w", err) + } + + if err := os.WriteFile(savedPath, []byte(note.Body), 0644); err != nil { + return fmt.Errorf("failed to write %s: %w", savedPath, err) + } + return err +} + +func editFileInteractive(path string) error { + editor := "vi" + if e := os.Getenv("EDITOR"); e != "" { + editor = e + } + return execCommand(os.Stdout, editor, path) +} + +func execCommand(stdout io.Writer, name string, args ...string) error { + cmd := exec.Command(name, args...) + cmd.Stdin = os.Stdin + cmd.Stdout = stdout + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + commands := append([]string{name}, args...) + return fmt.Errorf(`failed to exec "%s": %w`, strings.Join(commands, " "), err) + } + return nil +}