Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package pinact for openSUSE:Factory checked in at 2025-12-11 18:40:10 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/pinact (Old) and /work/SRC/openSUSE:Factory/.pinact.new.1939 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "pinact" Thu Dec 11 18:40:10 2025 rev:5 rq:1322122 version:3.5.0 Changes: -------- --- /work/SRC/openSUSE:Factory/pinact/pinact.changes 2025-12-09 12:55:08.288003575 +0100 +++ /work/SRC/openSUSE:Factory/.pinact.new.1939/pinact.changes 2025-12-11 18:42:05.787376649 +0100 @@ -1,0 +2,39 @@ +Thu Dec 11 07:06:45 UTC 2025 - Johannes Kastl <[email protected]> + +- Update to version 3.5.0: + * Features + - #1266 #1267 Add --min-age (-m) option + Skip recently released versions when updating actions with -u + flag. + + pinact run -u --min-age 7 + # or using short alias + pinact run -u -m 7 + + This helps avoid updating to potentially unstable versions + that haven't had time to prove their stability. + - For GitHub Releases, the PublishedAt date is checked + - For tags, the commit's Committer.Date is checked + + * Refactoring + - #1259 Use Destination pattern for urfave/cli flags + * Dependencies + - chore(deps): update dependency aquaproj/aqua to v2.55.3 + (#1264) + - chore(deps): update dependency aquaproj/aqua-registry to + v4.444.2 (#1263) + - fix(deps): update module golang.org/x/oauth2 to v0.34.0 + (#1262) + - fix(deps): update module github.com/google/go-github/v79 to + v80 (#1261) + - fix(deps): update module + github.com/suzuki-shunsuke/urfave-cli-v3-util to v0.1.2 + (#1260) + - chore(deps): update dependency suzuki-shunsuke/nllint to v1 + (#1258) + - chore(deps): update dependency crate-ci/typos to v1.40.0 + (#1257) + - chore(deps): update dependency suzuki-shunsuke/pinact to + v3.4.6 (#1256) + +------------------------------------------------------------------- Old: ---- pinact-3.4.6.obscpio New: ---- pinact-3.5.0.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ pinact.spec ++++++ --- /var/tmp/diff_new_pack.bME6Aj/_old 2025-12-11 18:42:06.931424716 +0100 +++ /var/tmp/diff_new_pack.bME6Aj/_new 2025-12-11 18:42:06.935424885 +0100 @@ -17,7 +17,7 @@ Name: pinact -Version: 3.4.6 +Version: 3.5.0 Release: 0 Summary: CLI to edit GitHub Workflows and pin versions of Actions and Reusable Workflows License: MIT ++++++ _service ++++++ --- /var/tmp/diff_new_pack.bME6Aj/_old 2025-12-11 18:42:06.999427574 +0100 +++ /var/tmp/diff_new_pack.bME6Aj/_new 2025-12-11 18:42:07.015428246 +0100 @@ -3,7 +3,7 @@ <param name="url">https://github.com/suzuki-shunsuke/pinact.git</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">refs/tags/v3.4.6</param> + <param name="revision">refs/tags/v3.5.0</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="changesgenerate">enable</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.bME6Aj/_old 2025-12-11 18:42:07.051429758 +0100 +++ /var/tmp/diff_new_pack.bME6Aj/_new 2025-12-11 18:42:07.055429927 +0100 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/suzuki-shunsuke/pinact.git</param> - <param name="changesrevision">618040dc89e0bb646233febee951d40f0e12923a</param></service></servicedata> + <param name="changesrevision">d641fa98cef0f5b794e4bb78974f486f46caefe5</param></service></servicedata> (No newline at EOF) ++++++ pinact-3.4.6.obscpio -> pinact-3.5.0.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinact-3.4.6/README.md new/pinact-3.5.0/README.md --- old/pinact-3.4.6/README.md 2025-12-07 01:13:02.000000000 +0100 +++ new/pinact-3.5.0/README.md 2025-12-09 14:40:20.000000000 +0100 @@ -164,6 +164,22 @@ pinact run -u ``` +#### Skip recently released versions + +[#1266](https://github.com/suzuki-shunsuke/pinact/pull/1266) pinact >= v3.5.0 + +You can skip recently released versions using the `--min-age` (`-m`) option. +This helps avoid updating to potentially unstable versions that haven't had time to prove their stability. + +```sh +pinact run -u --min-age 7 +``` + +This command skips versions released within the last 7 days. + +- For GitHub Releases, the `PublishedAt` date is checked +- For tags, the commit's `Committer.Date` is checked (requires additional API call) + ### Fix example codes in documents pinact can fix example codes in documents too. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinact-3.4.6/USAGE.md new/pinact-3.5.0/USAGE.md --- old/pinact-3.4.6/USAGE.md 2025-12-07 01:13:02.000000000 +0100 +++ new/pinact-3.5.0/USAGE.md 2025-12-09 14:40:20.000000000 +0100 @@ -11,7 +11,7 @@ pinact [global options] [command [command options]] VERSION: - 3.4.5 + 3.4.6 COMMANDS: init Create .pinact.yaml if it doesn't exist diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinact-3.4.6/aqua/aqua-checksums.json new/pinact-3.5.0/aqua/aqua-checksums.json --- old/pinact-3.4.6/aqua/aqua-checksums.json 2025-12-07 01:13:02.000000000 +0100 +++ new/pinact-3.5.0/aqua/aqua-checksums.json 2025-12-09 14:40:20.000000000 +0100 @@ -26,28 +26,28 @@ "algorithm": "sha256" }, { - "id": "github_release/github.com/crate-ci/typos/v1.37.0/typos-v1.37.0-aarch64-apple-darwin.tar.gz", - "checksum": "02FB67869AA9EF50D29FE2C0755EB80B2ED4A55157E43ABC1DC922BA07422313", + "id": "github_release/github.com/crate-ci/typos/v1.40.0/typos-v1.40.0-aarch64-apple-darwin.tar.gz", + "checksum": "1EA9ED6520B94D0E1148942E3EF80A997FF8DB856E1389EDAA9A5BDAFF658FA4", "algorithm": "sha256" }, { - "id": "github_release/github.com/crate-ci/typos/v1.37.0/typos-v1.37.0-aarch64-unknown-linux-musl.tar.gz", - "checksum": "CC182A26E736C0CAB017D49BA44F717547D47529FEF5CFF502F1588EF533EE36", + "id": "github_release/github.com/crate-ci/typos/v1.40.0/typos-v1.40.0-aarch64-unknown-linux-musl.tar.gz", + "checksum": "349B2C3F7C7FBA125E978DF232FAA9C5A57C33AA144F88CBC250C8C6D3E8E054", "algorithm": "sha256" }, { - "id": "github_release/github.com/crate-ci/typos/v1.37.0/typos-v1.37.0-x86_64-apple-darwin.tar.gz", - "checksum": "EA33A7E1F8BB9F8D3750B1A7E56E5355AC95B27D9E537F6E1390600E1C9F9AAC", + "id": "github_release/github.com/crate-ci/typos/v1.40.0/typos-v1.40.0-x86_64-apple-darwin.tar.gz", + "checksum": "51368551A37E15464438EA5C95AD29CB7239BFDEFD69EE9A9BE5FF3D45FC4D19", "algorithm": "sha256" }, { - "id": "github_release/github.com/crate-ci/typos/v1.37.0/typos-v1.37.0-x86_64-pc-windows-msvc.zip", - "checksum": "42EF223AC174993EDBA2FE91290E49618A9CF91462A3FC7CC076AB1D73FC60EC", + "id": "github_release/github.com/crate-ci/typos/v1.40.0/typos-v1.40.0-x86_64-pc-windows-msvc.zip", + "checksum": "F13426420749FAE31136E15A245C8EB144D6D3D681B3300D54D1A129999A140D", "algorithm": "sha256" }, { - "id": "github_release/github.com/crate-ci/typos/v1.37.0/typos-v1.37.0-x86_64-unknown-linux-musl.tar.gz", - "checksum": "7333D1082AC005910E3FC31B8F0C01AABF9463F2B793DC5851C73FCF861B8043", + "id": "github_release/github.com/crate-ci/typos/v1.40.0/typos-v1.40.0-x86_64-unknown-linux-musl.tar.gz", + "checksum": "485405D0A92871F45EAD0703D23C04AE6969AD4A6E5799794F55EB04B9F07801", "algorithm": "sha256" }, { @@ -276,68 +276,68 @@ "algorithm": "sha256" }, { - "id": "github_release/github.com/suzuki-shunsuke/nllint/v0.1.2/nllint_darwin_amd64.tar.gz", - "checksum": "EAD8931E0F048904E9D85E45E90F4E3BDF13B5D2EE5F2BCCC35782BC3EC4CD8F", + "id": "github_release/github.com/suzuki-shunsuke/nllint/v1.0.0/nllint_darwin_amd64.tar.gz", + "checksum": "5733518D44F7EAE2B4B16B0EA6617597C89B2EB084A4E6101A1DC03716266B6C", "algorithm": "sha256" }, { - "id": "github_release/github.com/suzuki-shunsuke/nllint/v0.1.2/nllint_darwin_arm64.tar.gz", - "checksum": "0DB73E6DC366FBB1EABAAE3D65C9C1E2F8B8DD09D9022D436D46120DBD5B96FA", + "id": "github_release/github.com/suzuki-shunsuke/nllint/v1.0.0/nllint_darwin_arm64.tar.gz", + "checksum": "E854EE0AA0DD83273D3B68E335BC025FDA721A32A3373091DFDEC3A582D1EC41", "algorithm": "sha256" }, { - "id": "github_release/github.com/suzuki-shunsuke/nllint/v0.1.2/nllint_linux_amd64.tar.gz", - "checksum": "F903F622DF9C89A694BEC4218AC2957D1E579627B662DF92E53FF725A9597E5D", + "id": "github_release/github.com/suzuki-shunsuke/nllint/v1.0.0/nllint_linux_amd64.tar.gz", + "checksum": "F54EC24CE1C344B611F6D80155396101D38E72B7F88E2CA8B9BFADC16307AE35", "algorithm": "sha256" }, { - "id": "github_release/github.com/suzuki-shunsuke/nllint/v0.1.2/nllint_linux_arm64.tar.gz", - "checksum": "FE2F89E5B03A1EBF6079071CA5E2836350735A6B1000C975AE27525C4C077488", + "id": "github_release/github.com/suzuki-shunsuke/nllint/v1.0.0/nllint_linux_arm64.tar.gz", + "checksum": "EC8D94494C70F4285A39375F385A4E52F945C597A3A5361ADF3DD9F1C8263CE4", "algorithm": "sha256" }, { - "id": "github_release/github.com/suzuki-shunsuke/nllint/v0.1.2/nllint_windows_amd64.zip", - "checksum": "7AAADA136832D8E9D251515CB177D19B664EDB4072E63696BCEA42DD2B2D34E0", + "id": "github_release/github.com/suzuki-shunsuke/nllint/v1.0.0/nllint_windows_amd64.zip", + "checksum": "4ABEBC98AF160C06C988F1421F0CF991FBDBFB6789CC1681C5C8E33C48A70160", "algorithm": "sha256" }, { - "id": "github_release/github.com/suzuki-shunsuke/nllint/v0.1.2/nllint_windows_arm64.zip", - "checksum": "C1C92F73778BECCB6422F004B1113B0AA7060C3757AF39EB8DF3E0573167D681", + "id": "github_release/github.com/suzuki-shunsuke/nllint/v1.0.0/nllint_windows_arm64.zip", + "checksum": "7493FF32F027507BDA9E9308E4AA25D640AA16B0287F7656A7C0A61AF907D270", "algorithm": "sha256" }, { - "id": "github_release/github.com/suzuki-shunsuke/pinact/v3.4.5/pinact_darwin_amd64.tar.gz", - "checksum": "20FECEC1BD5EB27093E817401FD7F92856EDADC78DB0C8903FE0B7D9AD335057", + "id": "github_release/github.com/suzuki-shunsuke/pinact/v3.4.6/pinact_darwin_amd64.tar.gz", + "checksum": "13B6167EA25BEFA2115F7E44BBA2CFF5B2FB6448D98B3BD7B4592EDA973B7B12", "algorithm": "sha256" }, { - "id": "github_release/github.com/suzuki-shunsuke/pinact/v3.4.5/pinact_darwin_arm64.tar.gz", - "checksum": "1B067E4990DADAFE2E916DB00C5E6D17E3FAA9885D91BD99CE2D8B1487281299", + "id": "github_release/github.com/suzuki-shunsuke/pinact/v3.4.6/pinact_darwin_arm64.tar.gz", + "checksum": "1D6E8239537EADA29E94285EA1E5D1A1DB27DB9320A9621C9E1D6427F118AEE4", "algorithm": "sha256" }, { - "id": "github_release/github.com/suzuki-shunsuke/pinact/v3.4.5/pinact_linux_amd64.tar.gz", - "checksum": "30681D82E66B1B6BED1280715EF21C0EF7D036340433B375E272CD7A6046C9F7", + "id": "github_release/github.com/suzuki-shunsuke/pinact/v3.4.6/pinact_linux_amd64.tar.gz", + "checksum": "1876DDD571BA8452C07A1B9B9A325BDFE074A467BE9A81F3FD42743BF6043618", "algorithm": "sha256" }, { - "id": "github_release/github.com/suzuki-shunsuke/pinact/v3.4.5/pinact_linux_arm64.tar.gz", - "checksum": "3B5E9595E01692B9ACFF10DC79C98E1E883924327AF67693B5798A7177968A92", + "id": "github_release/github.com/suzuki-shunsuke/pinact/v3.4.6/pinact_linux_arm64.tar.gz", + "checksum": "6AEFEA25EB7B9D1C7D03E6B42564F20D2F30EFEFECB991428B33348418397C14", "algorithm": "sha256" }, { - "id": "github_release/github.com/suzuki-shunsuke/pinact/v3.4.5/pinact_windows_amd64.zip", - "checksum": "3BD7FFB59274B3895A4CBA3DEC4EB412BCEA2F361652BCF69C1DAE0B6D25396B", + "id": "github_release/github.com/suzuki-shunsuke/pinact/v3.4.6/pinact_windows_amd64.zip", + "checksum": "FE68E29AD61ECBF48F7DCF3ABBE722EFC626237E43FAD04E1EC5DB23CDF05E94", "algorithm": "sha256" }, { - "id": "github_release/github.com/suzuki-shunsuke/pinact/v3.4.5/pinact_windows_arm64.zip", - "checksum": "6F5D0EC9C93431AE099E606B4B8B46E2E9054DCF83D60F8D001074C3331410F9", + "id": "github_release/github.com/suzuki-shunsuke/pinact/v3.4.6/pinact_windows_arm64.zip", + "checksum": "D8D57E69AD40717BD5B1E9DDEEF5AEFDA150F4CF21AFA1A548E783DDD792101A", "algorithm": "sha256" }, { - "id": "registries/github_content/github.com/aquaproj/aqua-registry/v4.444.0/registry.yaml", - "checksum": "529A890C667CFC10A3F567D92D381DF66219ADD4BA587BE3EFFBA12D390A1B43AC8051BEBFCC6DCC0E7F84DC40A72F8897764E186F49C3D72B2059E9EE1B1A24", + "id": "registries/github_content/github.com/aquaproj/aqua-registry/v4.444.2/registry.yaml", + "checksum": "3278676BF2A1DDBFE46EAA952F2D8DF226166CA799685328FA9E8E1B1B2FCAA0C422764A12306E0EB9BEC559E9CFB1031A2A5EAD4282B689A69D50B257B93510", "algorithm": "sha512" } ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinact-3.4.6/aqua/aqua.yaml new/pinact-3.5.0/aqua/aqua.yaml --- old/pinact-3.4.6/aqua/aqua.yaml 2025-12-07 01:13:02.000000000 +0100 +++ new/pinact-3.5.0/aqua/aqua.yaml 2025-12-09 14:40:20.000000000 +0100 @@ -1,5 +1,5 @@ --- -# yaml-language-server: $schema=https://raw.githubusercontent.com/aquaproj/aqua/v2.55.2/json-schema/aqua-yaml.json +# yaml-language-server: $schema=https://raw.githubusercontent.com/aquaproj/aqua/v2.55.3/json-schema/aqua-yaml.json # aqua - Declarative CLI Version Manager # https://aquaproj.github.io/ checksum: @@ -7,5 +7,5 @@ require_checksum: true registries: - type: standard - ref: v4.444.0 # renovate: depName=aquaproj/aqua-registry + ref: v4.444.2 # renovate: depName=aquaproj/aqua-registry import_dir: imports diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinact-3.4.6/aqua/imports/nllint.yaml new/pinact-3.5.0/aqua/imports/nllint.yaml --- old/pinact-3.4.6/aqua/imports/nllint.yaml 2025-12-07 01:13:02.000000000 +0100 +++ new/pinact-3.5.0/aqua/imports/nllint.yaml 2025-12-09 14:40:20.000000000 +0100 @@ -1,2 +1,2 @@ packages: - - name: suzuki-shunsuke/[email protected] + - name: suzuki-shunsuke/[email protected] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinact-3.4.6/aqua/imports/pinact.yaml new/pinact-3.5.0/aqua/imports/pinact.yaml --- old/pinact-3.4.6/aqua/imports/pinact.yaml 2025-12-07 01:13:02.000000000 +0100 +++ new/pinact-3.5.0/aqua/imports/pinact.yaml 2025-12-09 14:40:20.000000000 +0100 @@ -1,2 +1,2 @@ packages: - - name: suzuki-shunsuke/[email protected] + - name: suzuki-shunsuke/[email protected] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinact-3.4.6/aqua/imports/typos.yaml new/pinact-3.5.0/aqua/imports/typos.yaml --- old/pinact-3.4.6/aqua/imports/typos.yaml 2025-12-07 01:13:02.000000000 +0100 +++ new/pinact-3.5.0/aqua/imports/typos.yaml 2025-12-09 14:40:20.000000000 +0100 @@ -1,2 +1,2 @@ packages: - - name: crate-ci/[email protected] + - name: crate-ci/[email protected] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinact-3.4.6/go.mod new/pinact-3.5.0/go.mod --- old/pinact-3.4.6/go.mod 2025-12-07 01:13:02.000000000 +0100 +++ new/pinact-3.5.0/go.mod 2025-12-09 14:40:20.000000000 +0100 @@ -6,15 +6,15 @@ github.com/fatih/color v1.18.0 github.com/goccy/go-yaml v1.19.0 github.com/google/go-cmp v0.7.0 - github.com/google/go-github/v79 v79.0.0 + github.com/google/go-github/v80 v80.0.0 github.com/hashicorp/go-version v1.8.0 github.com/spf13/afero v1.15.0 github.com/suzuki-shunsuke/gen-go-jsonschema v0.1.0 github.com/suzuki-shunsuke/slog-error v0.2.1 github.com/suzuki-shunsuke/slog-util v0.3.0 - github.com/suzuki-shunsuke/urfave-cli-v3-util v0.1.1 + github.com/suzuki-shunsuke/urfave-cli-v3-util v0.1.2 github.com/urfave/cli/v3 v3.6.1 - golang.org/x/oauth2 v0.33.0 + golang.org/x/oauth2 v0.34.0 gopkg.in/yaml.v3 v3.0.1 ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinact-3.4.6/go.sum new/pinact-3.5.0/go.sum --- old/pinact-3.4.6/go.sum 2025-12-07 01:13:02.000000000 +0100 +++ new/pinact-3.5.0/go.sum 2025-12-09 14:40:20.000000000 +0100 @@ -20,8 +20,8 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-github/v79 v79.0.0 h1:MdodQojuFPBhmtwHiBcIGLw/e/wei2PvFX9ndxK0X4Y= -github.com/google/go-github/v79 v79.0.0/go.mod h1:OAFbNhq7fQwohojb06iIIQAB9CBGYLq999myfUFnrS4= +github.com/google/go-github/v80 v80.0.0 h1:BTyk3QOHekrk5VF+jIGz1TNEsmeoQG9K/UWaaP+EWQs= +github.com/google/go-github/v80 v80.0.0/go.mod h1:pRo4AIMdHW83HNMGfNysgSAv0vmu+/pkY8nZO9FT9Yo= 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/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= @@ -65,16 +65,16 @@ github.com/suzuki-shunsuke/slog-error v0.2.1/go.mod h1:w45QyO2G0uiEuo9hhrcLqqRl3hmYon9jGgq9CrCxxOY= github.com/suzuki-shunsuke/slog-util v0.3.0 h1:s+Go2yZqBnJCyV4kj1MDJEITfS7ELdDAEKk/aCulBkQ= github.com/suzuki-shunsuke/slog-util v0.3.0/go.mod h1:PgZMd+2rC8pA9jBbXDfkI8mTuWYAiaVkKxjrbLtfN5I= -github.com/suzuki-shunsuke/urfave-cli-v3-util v0.1.1 h1:JRaK9Z1c3VHXhyXTkFPHB3iN4ZRkcW+Wj6tz8NnntSE= -github.com/suzuki-shunsuke/urfave-cli-v3-util v0.1.1/go.mod h1:tqv8QxCUDNTsAjDoWJpmUp8XDjco+w+MU7EA0bwOXno= +github.com/suzuki-shunsuke/urfave-cli-v3-util v0.1.2 h1:kH/PPZR4k/maR9BZYNt/9iwHwfHy25rr6N/SVUF7DxQ= +github.com/suzuki-shunsuke/urfave-cli-v3-util v0.1.2/go.mod h1:tqv8QxCUDNTsAjDoWJpmUp8XDjco+w+MU7EA0bwOXno= github.com/urfave/cli/v3 v3.6.1 h1:j8Qq8NyUawj/7rTYdBGrxcH7A/j7/G8Q5LhWEW4G3Mo= github.com/urfave/cli/v3 v3.6.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/zalando/go-keyring v0.2.6 h1:r7Yc3+H+Ux0+M72zacZoItR3UDxeWfKTcabvkI8ua9s= github.com/zalando/go-keyring v0.2.6/go.mod h1:2TCrxYrbUNYfNS/Kgy/LSrkSQzZ5UPVH85RwfczwvcI= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinact-3.4.6/pkg/cli/flag/flag.go new/pinact-3.5.0/pkg/cli/flag/flag.go --- old/pinact-3.4.6/pkg/cli/flag/flag.go 1970-01-01 01:00:00.000000000 +0100 +++ new/pinact-3.5.0/pkg/cli/flag/flag.go 2025-12-09 14:40:20.000000000 +0100 @@ -0,0 +1,26 @@ +package flag + +import "github.com/urfave/cli/v3" + +type GlobalFlags struct { + LogLevel string + Config string +} + +func (gf *GlobalFlags) Flags() []cli.Flag { + return []cli.Flag{ + &cli.StringFlag{ + Name: "log-level", + Usage: "log level", + Sources: cli.EnvVars("PINACT_LOG_LEVEL"), + Destination: &gf.LogLevel, + }, + &cli.StringFlag{ + Name: "config", + Aliases: []string{"c"}, + Usage: "configuration file path", + Sources: cli.EnvVars("PINACT_CONFIG"), + Destination: &gf.Config, + }, + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinact-3.4.6/pkg/cli/initcmd/command.go new/pinact-3.5.0/pkg/cli/initcmd/command.go --- old/pinact-3.4.6/pkg/cli/initcmd/command.go 2025-12-07 01:13:02.000000000 +0100 +++ new/pinact-3.5.0/pkg/cli/initcmd/command.go 2025-12-09 14:40:20.000000000 +0100 @@ -11,25 +11,33 @@ "os" "github.com/spf13/afero" + "github.com/suzuki-shunsuke/pinact/v3/pkg/cli/flag" "github.com/suzuki-shunsuke/pinact/v3/pkg/controller/run" "github.com/suzuki-shunsuke/pinact/v3/pkg/github" "github.com/suzuki-shunsuke/slog-util/slogutil" - "github.com/suzuki-shunsuke/urfave-cli-v3-util/urfave" "github.com/urfave/cli/v3" ) +type Flags struct { + *flag.GlobalFlags + + Args []string + FirstArg string +} + // New creates a new init command instance with the provided logger. // It returns a CLI command that can be registered with the main CLI application. -func New(logger *slogutil.Logger) *cli.Command { +func New(logger *slogutil.Logger, globalFlags *flag.GlobalFlags) *cli.Command { r := &runner{} - return r.Command(logger) + return r.Command(logger, globalFlags) } type runner struct{} // Command returns the CLI command definition for the init subcommand. // It defines the command name, usage, description, and action handler. -func (r *runner) Command(logger *slogutil.Logger) *cli.Command { +func (r *runner) Command(logger *slogutil.Logger, globalFlags *flag.GlobalFlags) *cli.Command { + flags := &Flags{GlobalFlags: globalFlags} return &cli.Command{ Name: "init", Usage: "Create .pinact.yaml if it doesn't exist", @@ -43,7 +51,11 @@ $ pinact init .github/pinact.yaml `, - Action: urfave.Action(r.action, logger), + Action: func(ctx context.Context, cmd *cli.Command) error { + flags.Args = cmd.Args().Slice() + flags.FirstArg = cmd.Args().First() + return r.action(ctx, logger, flags) + }, } } @@ -51,7 +63,7 @@ // It creates a default .pinact.yaml configuration file in the specified location. // The function sets up the necessary controllers and services, determines the output // path for the configuration file, and delegates to the controller's Init method. -func (r *runner) action(ctx context.Context, c *cli.Command, logger *slogutil.Logger) error { +func (r *runner) action(ctx context.Context, logger *slogutil.Logger, flags *Flags) error { pwd, err := os.Getwd() if err != nil { return fmt.Errorf("get the current directory: %w", err) @@ -62,21 +74,18 @@ Releases: map[string]*run.ListReleasesResult{}, Commits: map[string]*run.GetCommitSHA1Result{}, RepositoriesService: gh.Repositories, - }, gh.PullRequests, afero.NewOsFs(), nil, nil, &run.ParamRun{ - WorkflowFilePaths: c.Args().Slice(), - ConfigFilePath: c.String("config"), + }, gh.PullRequests, nil, afero.NewOsFs(), nil, nil, &run.ParamRun{ + WorkflowFilePaths: flags.Args, + ConfigFilePath: flags.Config, PWD: pwd, - IsVerify: c.Bool("verify"), - Check: c.Bool("check"), - Update: c.Bool("update"), }) - if err := logger.SetLevel(c.String("log-level")); err != nil { + if err := logger.SetLevel(flags.LogLevel); err != nil { return fmt.Errorf("set log level: %w", err) } - configFilePath := c.Args().First() + configFilePath := flags.FirstArg if configFilePath == "" { - configFilePath = c.String("config") + configFilePath = flags.Config } if configFilePath == "" { configFilePath = ".pinact.yaml" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinact-3.4.6/pkg/cli/migrate/command.go new/pinact-3.5.0/pkg/cli/migrate/command.go --- old/pinact-3.4.6/pkg/cli/migrate/command.go 2025-12-07 01:13:02.000000000 +0100 +++ new/pinact-3.5.0/pkg/cli/migrate/command.go 2025-12-09 14:40:20.000000000 +0100 @@ -10,10 +10,10 @@ "fmt" "github.com/spf13/afero" + "github.com/suzuki-shunsuke/pinact/v3/pkg/cli/flag" "github.com/suzuki-shunsuke/pinact/v3/pkg/config" "github.com/suzuki-shunsuke/pinact/v3/pkg/controller/migrate" "github.com/suzuki-shunsuke/slog-util/slogutil" - "github.com/suzuki-shunsuke/urfave-cli-v3-util/urfave" "github.com/urfave/cli/v3" ) @@ -23,9 +23,9 @@ // It initializes a runner with the provided logger and returns // the configured CLI command for migrating pinact configuration files. // Returns a pointer to the configured CLI command. -func New(logger *slogutil.Logger) *cli.Command { +func New(logger *slogutil.Logger, globalFlags *flag.GlobalFlags) *cli.Command { r := runner{} - return r.Command(logger) + return r.Command(logger, globalFlags) } // Command builds and returns the migrate CLI command configuration. @@ -33,7 +33,7 @@ // for the migrate subcommand. // // Returns a pointer to the configured CLI command. -func (r *runner) Command(logger *slogutil.Logger) *cli.Command { +func (r *runner) Command(logger *slogutil.Logger, globalFlags *flag.GlobalFlags) *cli.Command { return &cli.Command{ Name: "migrate", Usage: "Migrate .pinact.yaml", @@ -41,7 +41,9 @@ $ pinact migrate `, - Action: urfave.Action(r.action, logger), + Action: func(_ context.Context, _ *cli.Command) error { + return r.action(logger, globalFlags) + }, } } @@ -49,13 +51,13 @@ // It configures logging, creates the filesystem interface and controller, // then performs the configuration file migration. // Returns an error if migration fails or logging configuration fails. -func (r *runner) action(_ context.Context, c *cli.Command, logger *slogutil.Logger) error { - if err := logger.SetLevel(c.String("log-level")); err != nil { +func (r *runner) action(logger *slogutil.Logger, flags *flag.GlobalFlags) error { + if err := logger.SetLevel(flags.LogLevel); err != nil { return fmt.Errorf("set log level: %w", err) } fs := afero.NewOsFs() ctrl := migrate.New(fs, config.NewFinder(fs), &migrate.Param{ - ConfigFilePath: c.String("config"), + ConfigFilePath: flags.Config, }) return ctrl.Migrate(logger.Logger) //nolint:wrapcheck diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinact-3.4.6/pkg/cli/run/command.go new/pinact-3.5.0/pkg/cli/run/command.go --- old/pinact-3.4.6/pkg/cli/run/command.go 2025-12-07 01:13:02.000000000 +0100 +++ new/pinact-3.5.0/pkg/cli/run/command.go 2025-12-09 14:40:20.000000000 +0100 @@ -10,6 +10,7 @@ import ( "context" "encoding/json" + "errors" "fmt" "os" "regexp" @@ -17,15 +18,35 @@ "github.com/fatih/color" "github.com/spf13/afero" + "github.com/suzuki-shunsuke/pinact/v3/pkg/cli/flag" "github.com/suzuki-shunsuke/pinact/v3/pkg/config" "github.com/suzuki-shunsuke/pinact/v3/pkg/controller/run" "github.com/suzuki-shunsuke/pinact/v3/pkg/github" "github.com/suzuki-shunsuke/slog-error/slogerr" "github.com/suzuki-shunsuke/slog-util/slogutil" - "github.com/suzuki-shunsuke/urfave-cli-v3-util/urfave" "github.com/urfave/cli/v3" ) +type Flags struct { + *flag.GlobalFlags + + Verify bool + Check bool + Update bool + Review bool + Fix bool + FixIsSet bool + Diff bool + RepoOwner string + RepoName string + SHA string + PR int + Include []string + Exclude []string + Args []string + MinAge int +} + // New creates a new run command for the CLI. // It initializes a runner with the provided logger and returns // the configured CLI command for pinning GitHub Actions versions. @@ -35,9 +56,9 @@ // - logLevelVar: slog level variable for dynamic log level changes // // Returns a pointer to the configured CLI command. -func New(logger *slogutil.Logger) *cli.Command { +func New(logger *slogutil.Logger, globalFlags *flag.GlobalFlags) *cli.Command { r := &runner{} - return r.Command(logger) + return r.Command(logger, globalFlags) } type runner struct{} @@ -48,7 +69,8 @@ // like check, diff, fix, update, and review. // // Returns a pointer to the configured CLI command. -func (r *runner) Command(logger *slogutil.Logger) *cli.Command { //nolint:funlen +func (r *runner) Command(logger *slogutil.Logger, globalFlags *flag.GlobalFlags) *cli.Command { //nolint:funlen + flags := &Flags{GlobalFlags: globalFlags} return &cli.Command{ Name: "run", Usage: "Pin GitHub Actions versions", @@ -62,60 +84,88 @@ $ pinact run .github/actions/foo/action.yaml .github/actions/bar/action.yaml `, - Action: urfave.Action(r.action, logger), + Action: func(ctx context.Context, cmd *cli.Command) error { + flags.FixIsSet = cmd.IsSet("fix") + flags.Args = cmd.Args().Slice() + return r.action(ctx, logger, flags) + }, Flags: []cli.Flag{ &cli.BoolFlag{ - Name: "verify", - Aliases: []string{"v"}, - Usage: "Verify if pairs of commit SHA and version are correct", + Name: "verify", + Aliases: []string{"v"}, + Usage: "Verify if pairs of commit SHA and version are correct", + Destination: &flags.Verify, }, &cli.BoolFlag{ - Name: "check", - Usage: "Exit with a non-zero status code if actions are not pinned. If this is true, files aren't updated", + Name: "check", + Usage: "Exit with a non-zero status code if actions are not pinned. If this is true, files aren't updated", + Destination: &flags.Check, }, &cli.BoolFlag{ - Name: "update", - Aliases: []string{"u"}, - Usage: "Update actions to latest versions", + Name: "update", + Aliases: []string{"u"}, + Usage: "Update actions to latest versions", + Destination: &flags.Update, }, &cli.BoolFlag{ - Name: "review", - Usage: "Create reviews", + Name: "review", + Usage: "Create reviews", + Destination: &flags.Review, }, &cli.BoolFlag{ - Name: "fix", - Usage: "Fix code. By default, this is true. If -check or -diff is true, this is false by default", + Name: "fix", + Usage: "Fix code. By default, this is true. If -check or -diff is true, this is false by default", + Destination: &flags.Fix, }, &cli.BoolFlag{ - Name: "diff", - Usage: "Output diff. By default, this is false", + Name: "diff", + Usage: "Output diff. By default, this is false", + Destination: &flags.Diff, }, &cli.StringFlag{ - Name: "repo-owner", - Usage: "GitHub repository owner", - Sources: cli.EnvVars("GITHUB_REPOSITORY_OWNER"), + Name: "repo-owner", + Usage: "GitHub repository owner", + Sources: cli.EnvVars("GITHUB_REPOSITORY_OWNER"), + Destination: &flags.RepoOwner, }, &cli.StringFlag{ - Name: "repo-name", - Usage: "GitHub repository name", + Name: "repo-name", + Usage: "GitHub repository name", + Destination: &flags.RepoName, }, &cli.StringFlag{ - Name: "sha", - Usage: "Commit SHA to be reviewed", + Name: "sha", + Usage: "Commit SHA to be reviewed", + Destination: &flags.SHA, }, &cli.IntFlag{ - Name: "pr", - Usage: "GitHub pull request number", + Name: "pr", + Usage: "GitHub pull request number", + Destination: &flags.PR, }, &cli.StringSliceFlag{ - Name: "include", - Aliases: []string{"i"}, - Usage: "A regular expression to fix actions", + Name: "include", + Aliases: []string{"i"}, + Usage: "A regular expression to fix actions", + Destination: &flags.Include, }, &cli.StringSliceFlag{ - Name: "exclude", - Aliases: []string{"e"}, - Usage: "A regular expression to exclude actions", + Name: "exclude", + Aliases: []string{"e"}, + Usage: "A regular expression to exclude actions", + Destination: &flags.Exclude, + }, + &cli.IntFlag{ + Name: "min-age", + Aliases: []string{"m"}, + Usage: "Skip versions released within the specified number of days (requires -u)", + Destination: &flags.MinAge, + Validator: func(i int) error { + if i < 0 { + return errors.New("--min-age must be a non-negative integer") + } + return nil + }, }, }, } @@ -195,12 +245,12 @@ // It configures logging, processes GitHub Actions context, parses includes/excludes, // sets up the controller, and executes the pinning operation. // Returns an error if the operation fails. -func (r *runner) action(ctx context.Context, c *cli.Command, logger *slogutil.Logger) error { //nolint:cyclop,funlen +func (r *runner) action(ctx context.Context, logger *slogutil.Logger, flags *Flags) error { //nolint:cyclop,funlen isGitHubActions := os.Getenv("GITHUB_ACTIONS") == "true" if isGitHubActions { color.NoColor = false } - if err := logger.SetLevel(c.String("log-level")); err != nil { + if err := logger.SetLevel(flags.LogLevel); err != nil { return fmt.Errorf("set log level: %w", err) } @@ -212,12 +262,12 @@ gh := github.New(ctx, logger.Logger) fs := afero.NewOsFs() var review *run.Review - if c.Bool("review") { + if flags.Review { review = &run.Review{ - RepoOwner: c.String("repo-owner"), - RepoName: c.String("repo-name"), - PullRequest: c.Int("pr"), - SHA: c.String("sha"), + RepoOwner: flags.RepoOwner, + RepoName: flags.RepoName, + PullRequest: flags.PR, + SHA: flags.SHA, } if isGitHubActions { if err := r.setReview(fs, review); err != nil { @@ -229,31 +279,35 @@ review = nil } } - includes, err := parseIncludes(c.StringSlice("include")) + if flags.MinAge > 0 && !flags.Update { + return errors.New("--min-age requires --update (-u) flag") + } + includes, err := parseIncludes(flags.Include) if err != nil { return err } - excludes, err := parseExcludes(c.StringSlice("exclude")) + excludes, err := parseExcludes(flags.Exclude) if err != nil { return err } param := &run.ParamRun{ - WorkflowFilePaths: c.Args().Slice(), - ConfigFilePath: c.String("config"), + WorkflowFilePaths: flags.Args, + ConfigFilePath: flags.Config, PWD: pwd, - IsVerify: c.Bool("verify"), - Check: c.Bool("check"), - Update: c.Bool("update"), - Diff: c.Bool("diff"), + IsVerify: flags.Verify, + Check: flags.Check, + Update: flags.Update, + Diff: flags.Diff, Fix: true, IsGitHubActions: isGitHubActions, Stderr: os.Stderr, Review: review, Includes: includes, Excludes: excludes, + MinAge: flags.MinAge, } - if c.IsSet("fix") { - param.Fix = c.Bool("fix") + if flags.FixIsSet { + param.Fix = flags.Fix } else if param.Check || param.Diff { param.Fix = false } @@ -262,7 +316,10 @@ Releases: map[string]*run.ListReleasesResult{}, Commits: map[string]*run.GetCommitSHA1Result{}, RepositoriesService: gh.Repositories, - }, gh.PullRequests, fs, config.NewFinder(fs), config.NewReader(fs), param) + }, gh.PullRequests, &run.GitServiceImpl{ + GitService: gh.Git, + Commits: map[string]*run.GetCommitResult{}, + }, fs, config.NewFinder(fs), config.NewReader(fs), param) return ctrl.Run(ctx, logger.Logger) //nolint:wrapcheck } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinact-3.4.6/pkg/cli/runner.go new/pinact-3.5.0/pkg/cli/runner.go --- old/pinact-3.4.6/pkg/cli/runner.go 2025-12-07 01:13:02.000000000 +0100 +++ new/pinact-3.5.0/pkg/cli/runner.go 2025-12-09 14:40:20.000000000 +0100 @@ -8,6 +8,7 @@ import ( "context" + "github.com/suzuki-shunsuke/pinact/v3/pkg/cli/flag" "github.com/suzuki-shunsuke/pinact/v3/pkg/cli/initcmd" "github.com/suzuki-shunsuke/pinact/v3/pkg/cli/migrate" "github.com/suzuki-shunsuke/pinact/v3/pkg/cli/run" @@ -30,28 +31,15 @@ // // Returns an error if command parsing or execution fails. func Run(ctx context.Context, logger *slogutil.Logger, env *urfave.Env) error { + globalFlags := &flag.GlobalFlags{} return urfave.Command(env, &cli.Command{ //nolint:wrapcheck Name: "pinact", Usage: "Pin GitHub Actions versions. https://github.com/suzuki-shunsuke/pinact", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "log-level", - Usage: "log level", - Sources: cli.EnvVars("PINACT_LOG_LEVEL"), - }, - &cli.StringFlag{ - Name: "config", - Aliases: []string{ - "c", - }, - Usage: "configuration file path", - Sources: cli.EnvVars("PINACT_CONFIG"), - }, - }, + Flags: globalFlags.Flags(), Commands: []*cli.Command{ - initcmd.New(logger), - run.New(logger), - migrate.New(logger), + initcmd.New(logger, globalFlags), + run.New(logger, globalFlags), + migrate.New(logger, globalFlags), token.New(logger), }, }).Run(ctx, env.Args) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinact-3.4.6/pkg/controller/run/controller.go new/pinact-3.5.0/pkg/controller/run/controller.go --- old/pinact-3.4.6/pkg/controller/run/controller.go 2025-12-07 01:13:02.000000000 +0100 +++ new/pinact-3.5.0/pkg/controller/run/controller.go 2025-12-09 14:40:20.000000000 +0100 @@ -16,6 +16,7 @@ type Controller struct { repositoriesService RepositoriesService pullRequestsService PullRequestsService + gitService *GitServiceImpl fs afero.Fs cfg *config.Config param *ParamRun @@ -40,16 +41,18 @@ // Parameters: // - repositoriesService: GitHub API service for repository operations // - pullRequestsService: GitHub API service for pull request operations +// - gitService: GitHub API service for git operations (optional, for cooldown feature) // - fs: filesystem interface for file operations // - cfgFinder: service for locating configuration files // - cfgReader: service for reading and parsing configuration files // - param: operation parameters and settings // // Returns a pointer to the configured Controller. -func New(repositoriesService RepositoriesService, pullRequestsService PullRequestsService, fs afero.Fs, cfgFinder ConfigFinder, cfgReader ConfigReader, param *ParamRun) *Controller { +func New(repositoriesService RepositoriesService, pullRequestsService PullRequestsService, gitService *GitServiceImpl, fs afero.Fs, cfgFinder ConfigFinder, cfgReader ConfigReader, param *ParamRun) *Controller { return &Controller{ repositoriesService: repositoriesService, pullRequestsService: pullRequestsService, + gitService: gitService, param: param, fs: fs, cfgFinder: cfgFinder, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinact-3.4.6/pkg/controller/run/github.go new/pinact-3.5.0/pkg/controller/run/github.go --- old/pinact-3.4.6/pkg/controller/run/github.go 2025-12-07 01:13:02.000000000 +0100 +++ new/pinact-3.5.0/pkg/controller/run/github.go 2025-12-09 14:40:20.000000000 +0100 @@ -5,6 +5,7 @@ "errors" "fmt" "log/slog" + "time" "github.com/hashicorp/go-version" "github.com/suzuki-shunsuke/pinact/v3/pkg/github" @@ -21,6 +22,36 @@ CreateComment(ctx context.Context, owner, repo string, number int, comment *github.PullRequestComment) (*github.PullRequestComment, *github.Response, error) } +type GitService interface { + GetCommit(ctx context.Context, owner, repo, sha string) (*github.Commit, *github.Response, error) +} + +type GitServiceImpl struct { + GitService GitService + Commits map[string]*GetCommitResult +} + +type GetCommitResult struct { + Commit *github.Commit + Response *github.Response + err error +} + +// GetCommit retrieves a commit object with caching. +func (g *GitServiceImpl) GetCommit(ctx context.Context, owner, repo, sha string) (*github.Commit, *github.Response, error) { + key := fmt.Sprintf("%s/%s/%s", owner, repo, sha) + if result, ok := g.Commits[key]; ok { + return result.Commit, result.Response, result.err + } + commit, resp, err := g.GitService.GetCommit(ctx, owner, repo, sha) + g.Commits[key] = &GetCommitResult{ + Commit: commit, + Response: resp, + err: err, + } + return commit, resp, err //nolint:wrapcheck +} + // GetCommitSHA1 retrieves the commit SHA for a given reference with caching. // It first checks the cache and returns cached results if available. // Otherwise, it calls the underlying service and caches the result. @@ -147,14 +178,21 @@ // Returns the latest version string or an error. func (c *Controller) getLatestVersion(ctx context.Context, logger *slog.Logger, owner, repo, currentVersion string) (string, error) { isStable := isStableVersion(currentVersion) - lv, err := c.getLatestVersionFromReleases(ctx, logger, owner, repo, isStable) + + // Calculate cutoff once for min-age filtering + var cutoff time.Time + if c.param.MinAge > 0 { + cutoff = time.Now().AddDate(0, 0, -c.param.MinAge) + } + + lv, err := c.getLatestVersionFromReleases(ctx, logger, owner, repo, isStable, cutoff) if err != nil { slogerr.WithError(logger, err).Debug("get the latest version from releases") } if lv != "" { return lv, nil } - return c.getLatestVersionFromTags(ctx, logger, owner, repo, isStable) + return c.getLatestVersionFromTags(ctx, logger, owner, repo, isStable, cutoff) } func isStableVersion(v string) bool { @@ -201,10 +239,11 @@ // - logger: slog logger for structured logging // - owner: repository owner // - repo: repository name -// - currentVersion: current version to check if stable (empty string to include all versions) +// - isStable: whether to filter out prerelease versions +// - cutoff: skip releases published after this time (zero value means no filtering) // // Returns the latest version string or an error. -func (c *Controller) getLatestVersionFromReleases(ctx context.Context, logger *slog.Logger, owner, repo string, isStable bool) (string, error) { +func (c *Controller) getLatestVersionFromReleases(ctx context.Context, logger *slog.Logger, owner, repo string, isStable bool, cutoff time.Time) (string, error) { opts := &github.ListOptions{ PerPage: 30, //nolint:mnd } @@ -221,6 +260,13 @@ continue } tag := release.GetTagName() + // Skip releases within cooldown period + if !cutoff.IsZero() && release.GetPublishedAt().After(cutoff) { + logger.Info("skip release due to cooldown", + "tag", tag, + "published_at", release.GetPublishedAt()) + continue + } ls, lv, err := compare(latestSemver, latestVersion, tag) latestSemver = ls latestVersion = lv @@ -236,6 +282,26 @@ return latestVersion, nil } +// checkTagCooldown checks if a tag should be skipped due to cooldown period. +// Returns true if the tag should be skipped. +func (c *Controller) checkTagCooldown(ctx context.Context, logger *slog.Logger, owner, repo, tagName, sha string, cutoff time.Time) bool { + if cutoff.IsZero() || c.gitService == nil || sha == "" { + return false + } + commit, _, err := c.gitService.GetCommit(ctx, owner, repo, sha) + if err != nil { + slogerr.WithError(logger, err).Warn("skip tag: failed to get commit for cooldown check", "tag", tagName, "sha", sha) + return true + } + if commit.GetCommitter().GetDate().After(cutoff) { + logger.Info("skip tag due to cooldown", + "tag", tagName, + "committed_at", commit.GetCommitter().GetDate()) + return true + } + return false +} + // getLatestVersionFromTags finds the latest version from repository tags. // It retrieves tags from GitHub API and compares them to find the highest // version using semantic versioning when possible, falling back to string comparison. @@ -246,10 +312,11 @@ // - logger: slog logger for structured logging // - owner: repository owner // - repo: repository name -// - currentVersion: current version to check if stable (empty string to include all versions) +// - isStable: whether to filter out prerelease versions +// - cutoff: skip tags committed after this time (zero value means no filtering) // // Returns the latest version string or an error. -func (c *Controller) getLatestVersionFromTags(ctx context.Context, logger *slog.Logger, owner, repo string, isStable bool) (string, error) { +func (c *Controller) getLatestVersionFromTags(ctx context.Context, logger *slog.Logger, owner, repo string, isStable bool, cutoff time.Time) (string, error) { opts := &github.ListOptions{ PerPage: 30, //nolint:mnd } @@ -270,6 +337,11 @@ } } + // Skip tags within cooldown period + if c.checkTagCooldown(ctx, logger, owner, repo, t, tag.GetCommit().GetSHA(), cutoff) { + continue + } + ls, lv, err := compare(latestSemver, latestVersion, t) latestSemver = ls latestVersion = lv diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinact-3.4.6/pkg/controller/run/github_internal_test.go new/pinact-3.5.0/pkg/controller/run/github_internal_test.go --- old/pinact-3.4.6/pkg/controller/run/github_internal_test.go 2025-12-07 01:13:02.000000000 +0100 +++ new/pinact-3.5.0/pkg/controller/run/github_internal_test.go 2025-12-09 14:40:20.000000000 +0100 @@ -5,6 +5,7 @@ "errors" "log/slog" "testing" + "time" "github.com/hashicorp/go-version" "github.com/suzuki-shunsuke/pinact/v3/pkg/github" @@ -322,7 +323,7 @@ ctx := t.Context() logger := slog.New(slog.DiscardHandler) - gotVersion, err := c.getLatestVersionFromReleases(ctx, logger, "owner", "repo", tt.isStable) + gotVersion, err := c.getLatestVersionFromReleases(ctx, logger, "owner", "repo", tt.isStable, time.Time{}) if (err != nil) != tt.wantErr { t.Errorf("getLatestVersionFromReleases() error = %v, wantErr %v", err, tt.wantErr) @@ -479,7 +480,7 @@ ctx := t.Context() logger := slog.New(slog.DiscardHandler) - gotVersion, err := c.getLatestVersionFromTags(ctx, logger, "owner", "repo", false) + gotVersion, err := c.getLatestVersionFromTags(ctx, logger, "owner", "repo", false, time.Time{}) if (err != nil) != tt.wantErr { t.Errorf("getLatestVersionFromTags() error = %v, wantErr %v", err, tt.wantErr) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinact-3.4.6/pkg/controller/run/parse_line_internal_test.go new/pinact-3.5.0/pkg/controller/run/parse_line_internal_test.go --- old/pinact-3.4.6/pkg/controller/run/parse_line_internal_test.go 2025-12-07 01:13:02.000000000 +0100 +++ new/pinact-3.5.0/pkg/controller/run/parse_line_internal_test.go 2025-12-09 14:40:20.000000000 +0100 @@ -204,7 +204,7 @@ SHA: "ee0669bd1cc54295c223e0bb666b733df41de1c5", }, }, - }, nil, fs, config.NewFinder(fs), config.NewReader(fs), &ParamRun{}) + }, nil, nil, fs, config.NewFinder(fs), config.NewReader(fs), &ParamRun{}) line, err := ctrl.parseLine(t.Context(), logger, d.line) if err != nil { if d.isErr { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinact-3.4.6/pkg/controller/run/run.go new/pinact-3.5.0/pkg/controller/run/run.go --- old/pinact-3.4.6/pkg/controller/run/run.go 2025-12-07 01:13:02.000000000 +0100 +++ new/pinact-3.5.0/pkg/controller/run/run.go 2025-12-09 14:40:20.000000000 +0100 @@ -31,6 +31,7 @@ Review *Review Includes []*regexp.Regexp Excludes []*regexp.Regexp + MinAge int } type Review struct { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinact-3.4.6/pkg/github/github.go new/pinact-3.5.0/pkg/github/github.go --- old/pinact-3.4.6/pkg/github/github.go 2025-12-07 01:13:02.000000000 +0100 +++ new/pinact-3.5.0/pkg/github/github.go 2025-12-09 14:40:20.000000000 +0100 @@ -13,7 +13,7 @@ "net/http" "os" - "github.com/google/go-github/v79/github" + "github.com/google/go-github/v80/github" "github.com/suzuki-shunsuke/urfave-cli-v3-util/keyring/ghtoken" "golang.org/x/oauth2" ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinact-3.4.6/specs/1265/README.md new/pinact-3.5.0/specs/1265/README.md --- old/pinact-3.4.6/specs/1265/README.md 1970-01-01 01:00:00.000000000 +0100 +++ new/pinact-3.5.0/specs/1265/README.md 2025-12-09 14:40:20.000000000 +0100 @@ -0,0 +1,40 @@ +# Spec: Add `--min-age` option to `pinact run` + +- [#1265](https://github.com/suzuki-shunsuke/pinact/pull/1265) +- [#1266](https://github.com/suzuki-shunsuke/pinact/pull/1266) +- [#1267](https://github.com/suzuki-shunsuke/pinact/pull/1267) + +## Overview + +Add a `--min-age` (`-m`) option to `pinact run` that works in conjunction with the `-u` (update) option. This option filters update targets based on release age - only versions released more than the specified number of days ago will be considered for updates. + +## Behavior + +- When `--min-age N` is specified, versions released within the last N days are skipped +- Example: `pinact run -u --min-age 7` will exclude any versions released within the past 7 days from updates +- The `-u` option fetches GitHub Releases or tags via GitHub API; this feature checks the release/tag creation date against the min-age period +- When a version is skipped due to min-age, output a debug log message + +## Validation Rules + +- **Error**: If `--min-age` is specified without `-u` option +- **Error**: If `--min-age` is given a negative value +- **Default**: If `--min-age` is not specified or `--min-age 0`, all versions are eligible for update (existing behavior) + +## Use Case + +This feature helps avoid updating to recently released, potentially unstable versions, allowing users to update only to versions that have had time to prove their stability. + +## Example Usage + +```sh +pinact run -u --min-age 7 +# or using the short alias +pinact run -u -m 7 +``` + +## Date Fields Used + +- **Releases**: Use `PublishedAt` field from GitHub API +- **Tags**: Use `Committer.Date` from the commit object (requires additional API call to `GET /repos/{owner}/{repo}/git/commits/{sha}`) + - If the commit cannot be fetched, the tag is skipped and a warning is logged diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinact-3.4.6/specs/1265/history.md new/pinact-3.5.0/specs/1265/history.md --- old/pinact-3.4.6/specs/1265/history.md 1970-01-01 01:00:00.000000000 +0100 +++ new/pinact-3.5.0/specs/1265/history.md 2025-12-09 14:40:20.000000000 +0100 @@ -0,0 +1,21 @@ +# History + +1. 2025-12 `--cooldown` was renamed to `--min-age` + +https://github.com/suzuki-shunsuke/pinact/issues/1265#issuecomment-3632228752 + +## Background + +The `--cooldown` option was originally implemented in PR #1266. +However, the option name was renamed to `--min-age` with alias `-m` due to the following reasons: + +1. **Alias conflict**: The short alias `-c` was already used by `--config` option, so `--cooldown` could not have a convenient short alias. +2. **Desire for short alias**: Since `cooldown` is a relatively long option name, a short alias was desired for usability. +3. **Semantic clarity**: `--min-age` better describes the option's behavior - it specifies the minimum age (in days) that a version must have to be considered for updates. + +### Changes Made + +- Renamed `--cooldown` to `--min-age` +- Added `-m` as a short alias +- Updated all internal variable names from `Cooldown` to `MinAge` +- Updated documentation and error messages diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinact-3.4.6/specs/1265/plan.md new/pinact-3.5.0/specs/1265/plan.md --- old/pinact-3.4.6/specs/1265/plan.md 1970-01-01 01:00:00.000000000 +0100 +++ new/pinact-3.5.0/specs/1265/plan.md 2025-12-09 14:40:20.000000000 +0100 @@ -0,0 +1,89 @@ +# Implementation Plan: Add `--min-age` option to `pinact run` + +## Files to Modify + +### 1. `pkg/cli/run/command.go` +- Add `MinAge int` field to `Flags` struct +- Add `--min-age` IntFlag definition with `Validator` and `-m` alias +- Add validation in `action()` method for flag combination + +### 2. `pkg/controller/run/run.go` +- Add `MinAge int` field to `ParamRun` struct + +### 3. `pkg/controller/run/github.go` +- Add `GitService` interface for getting commits +- Modify `getLatestVersionFromReleases()` to filter releases by `PublishedAt` date +- Modify `getLatestVersionFromTags()` to filter tags by commit date +- Add debug logging when a version is skipped due to min-age + +## Implementation Details + +### CLI Flag Definition +```go +&cli.IntFlag{ + Name: "min-age", + Aliases: []string{"m"}, + Usage: "Skip versions released within the specified number of days (requires -u)", + Destination: &flags.MinAge, + Validator: func(i int) error { + if i < 0 { + return errors.New("--min-age must be a non-negative integer") + } + return nil + }, +}, +``` + +### Validation Logic (in `action()`) +```go +if flags.MinAge > 0 && !flags.Update { + return errors.New("--min-age requires --update (-u) flag") +} +``` + +### MinAge Cutoff Calculation +Calculate `cutoff` once in `getLatestVersion()` and pass to filtering functions: +```go +func (c *Controller) getLatestVersion(ctx context.Context, logger *slog.Logger, owner, repo, currentVersion string) (string, error) { + isStable := isStableVersion(currentVersion) + + // Calculate cutoff once for min-age filtering + var cutoff time.Time + if c.param.MinAge > 0 { + cutoff = time.Now().AddDate(0, 0, -c.param.MinAge) + } + + lv, err := c.getLatestVersionFromReleases(ctx, logger, owner, repo, isStable, cutoff) + // ... + return c.getLatestVersionFromTags(ctx, logger, owner, repo, isStable, cutoff) +} +``` + +### Release Filtering Logic +```go +// cutoff is passed as parameter (zero value means no filtering) +if !cutoff.IsZero() && release.GetPublishedAt().Time.After(cutoff) { + logger.Debug("skip release due to min-age", + slog.String("tag", tag), + slog.Time("published_at", release.GetPublishedAt().Time)) + continue +} +``` + +### Tag Handling +- Use `GitService.GetCommit(ctx, owner, repo, sha)` to get commit info +- Check `commit.GetCommitter().GetDate().Time` against `cutoff` parameter +- Cache results to avoid redundant API calls + +## Execution Order + +1. Add `MinAge int` field to `Flags` struct in `pkg/cli/run/command.go` +2. Add `--min-age` IntFlag definition with `Validator` and `-m` alias +3. Add validation in `action()` method for flag combination +4. Add `MinAge int` field to `ParamRun` struct in `pkg/controller/run/run.go` +5. Pass `MinAge` to `ParamRun` in `action()` method +6. Add `GitService` interface to `pkg/controller/run/github.go` +7. Implement min-age filtering in `getLatestVersionFromReleases()` +8. Implement min-age filtering in `getLatestVersionFromTags()` +9. Wire up `gh.Git` to controller in `action()` method +10. Run `cmdx v` and `cmdx t` to validate diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinact-3.4.6/specs/1265/test.md new/pinact-3.5.0/specs/1265/test.md --- old/pinact-3.4.6/specs/1265/test.md 1970-01-01 01:00:00.000000000 +0100 +++ new/pinact-3.5.0/specs/1265/test.md 2025-12-09 14:40:20.000000000 +0100 @@ -0,0 +1,138 @@ +# Test Plan: `--min-age` option + +## Validation Tests + +### 1. Error when `--min-age` is specified without `-u` + +```sh +pinact run --min-age 7 +``` + +**Expected**: Error message indicating `--min-age requires --update (-u) flag` + +### 2. Error when `--min-age` is negative + +```sh +pinact run -u --min-age -1 +``` + +**Expected**: Error message indicating `--min-age must be a non-negative integer` + +### 3. `--min-age 0` should work (no filtering) + +```sh +pinact run -u --min-age 0 +``` + +**Expected**: Behaves same as `pinact run -u` (all versions eligible) + +## Functional Tests + +### 4. Skip recently released versions (releases) + +Test with a repository that has GitHub Releases. + +```sh +# Use a large min-age to skip recent releases +pinact run -u --min-age 9999 --log-level info +# or using short alias +pinact run -u -m 9999 --log-level info +``` + +**Expected**: +- Info log messages like `skip release due to cooldown` with tag and published_at +- Action not updated to the latest version + +### 5. Skip recently released versions (tags) + +Test with a repository that only uses tags (no releases). + +```sh +pinact run -u --min-age 9999 --log-level info +``` + +**Expected**: +- Info log messages like `skip tag due to cooldown` with tag and committed_at +- Action not updated to the latest version + +### 6. Update to eligible version + +```sh +# Use small min-age that allows some versions +pinact run -u --min-age 30 --log-level info +``` + +**Expected**: +- Recent versions skipped (info logs) +- Action updated to the latest eligible version (older than 30 days) + +### 7. No update when all versions are within min-age + +```sh +# Test with a very new action or large min-age +pinact run -u --min-age 9999 +``` + +**Expected**: No changes to the file (current version retained) + +## Edge Cases + +### 8. Mixed releases and tags + +Test with a repository that has both releases and tags with different dates. + +**Expected**: Min-age filtering applied consistently + +### 9. Commit fetch failure for tags + +Test scenario where commit cannot be fetched (e.g., deleted commit, API error). + +```sh +pinact run -u --min-age 7 --log-level warn +``` + +**Expected**: +- Warning log: `skip tag: failed to get commit for cooldown check` +- That tag is skipped + +### 10. Stable version filtering combined with min-age + +Test with a prerelease current version and stable version: + +```yaml +- uses: owner/repo@sha # v1.0.0-beta +``` + +```sh +pinact run -u --min-age 30 +``` + +**Expected**: Both prerelease filtering and min-age filtering applied + +## Test Workflow Files + +### Sample workflow for testing + +`.github/workflows/test.yaml`: + +```yaml +name: Test +on: push +jobs: + test: + runs-on: ubuntu-latest + steps: + # Test with actions/checkout (has releases) + - uses: actions/checkout@v4 + + # Test with an action that only has tags + - uses: suzuki-shunsuke/tfcmt@v1 +``` + +Run: + +```sh +pinact run -u --min-age 7 .github/workflows/test.yaml --log-level info +# or +pinact run -u -m 7 .github/workflows/test.yaml --log-level info +``` ++++++ pinact.obsinfo ++++++ --- /var/tmp/diff_new_pack.bME6Aj/_old 2025-12-11 18:42:07.387443876 +0100 +++ /var/tmp/diff_new_pack.bME6Aj/_new 2025-12-11 18:42:07.391444045 +0100 @@ -1,5 +1,5 @@ name: pinact -version: 3.4.6 -mtime: 1765066382 -commit: 618040dc89e0bb646233febee951d40f0e12923a +version: 3.5.0 +mtime: 1765287620 +commit: d641fa98cef0f5b794e4bb78974f486f46caefe5 ++++++ vendor.tar.gz ++++++ ++++ 154429 lines of diff (skipped)
