From 17e961267dca7732e09e5777f9633424389209e5 Mon Sep 17 00:00:00 2001 From: Pedro Tanaka Date: Thu, 21 Aug 2025 12:54:47 +0200 Subject: [PATCH] feat: add support for reading golangci-lint version from go.mod Implements automatic version detection from go.mod files to keep golangci-lint versions synchronized between the module and CI. - Add new `go-mod-path` input parameter to specify custom go.mod location - Automatically detect and use version from go.mod when no explicit version provided - Force `goinstall` mode when using go.mod versions for compatibility - Support both default go.mod and custom paths (e.g., tools/go.mod) This eliminates the need to manually update versions in multiple places and ensures CI uses the same golangci-lint version as the project. Fixes #1268 Signed-off-by: Pedro Tanaka --- README.md | 87 ++++++++++++++++++++++++++++++++++++++++++ action.yml | 7 ++++ dist/post_run/index.js | 32 ++++++++++++++-- dist/run/index.js | 32 ++++++++++++++-- src/install.ts | 10 ++++- src/version.ts | 28 ++++++++++++-- 6 files changed, 183 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index b519dc2..fc9b584 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,68 @@ You will also likely need to add the following `.gitattributes` file to ensure t +
+Using go.mod Version Example + +If you have golangci-lint as a dependency in your go.mod file, you can use that version automatically: + +```yaml +name: golangci-lint +on: + push: + branches: + - main + - master + pull_request: + +permissions: + contents: read + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: stable + - name: golangci-lint + uses: golangci/golangci-lint-action@v8 + # No version specified - will read from go.mod automatically +``` + +Or if you have a separate tools module: + +```yaml +name: golangci-lint +on: + push: + branches: + - main + - master + pull_request: + +permissions: + contents: read + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: stable + - name: golangci-lint + uses: golangci/golangci-lint-action@v8 + with: + go-mod-path: tools/go.mod +``` + +
+
Go Workspace Example @@ -280,6 +342,29 @@ with:
+### `go-mod-path` + +(optional) + +Path to a go.mod file to read the golangci-lint version from. +If specified and `version` is not set, the action will parse the go.mod file to determine the golangci-lint version. + +Default is "go.mod" in the working directory. + +**Note:** When a version is read from go.mod, it automatically uses `goinstall` install-mode to ensure compatibility with your module's dependencies. + +
+Example + +```yml +uses: golangci/golangci-lint-action@v8 +with: + go-mod-path: tools/go.mod + # ... +``` + +
+ ### `install-mode` (optional) @@ -288,6 +373,8 @@ The mode to install golangci-lint: it can be `binary`, `goinstall`, or `none`. The default value is `binary`. +**Note:** When using `go-mod-path` to read the version from a go.mod file, the install-mode is automatically set to `goinstall`. +
Example diff --git a/action.yml b/action.yml index 5f6b950..1740deb 100644 --- a/action.yml +++ b/action.yml @@ -11,6 +11,13 @@ inputs: - `goinstall`: the value can be v2.3.4, `latest`, or the hash of a commit. - `none`: the value is ignored. required: false + go-mod-path: + description: | + Path to a go.mod file to read the golangci-lint version from. + If specified and version is not set, the action will parse the go.mod file + to determine the golangci-lint version. Default is "go.mod" in the working directory. + Note: When a version is read from go.mod, it automatically uses `goinstall` install-mode. + required: false install-mode: description: "The mode to install golangci-lint. It can be 'binary', 'goinstall', or 'none'." default: "binary" diff --git a/dist/post_run/index.js b/dist/post_run/index.js index 3d96216..47fa3f3 100644 --- a/dist/post_run/index.js +++ b/dist/post_run/index.js @@ -92671,7 +92671,7 @@ const printOutput = (res) => { * @returns path to installed binary of golangci-lint. */ async function install() { - const mode = core.getInput("install-mode").toLowerCase(); + let mode = core.getInput("install-mode").toLowerCase(); if (mode === InstallMode.None) { const binPath = await (0, which_1.default)("golangci-lint", { nothrow: true }); if (!binPath) { @@ -92679,6 +92679,11 @@ async function install() { } return binPath; } + // If version is being read from go.mod, force goinstall mode + if ((0, version_1.isVersionFromGoMod)()) { + core.info(`Version detected from go.mod file, using goinstall mode`); + mode = InstallMode.GoInstall; + } const versionInfo = await (0, version_1.getVersion)(mode); return await installBinary(versionInfo, mode); } @@ -93358,6 +93363,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.stringifyVersion = void 0; +exports.isVersionFromGoMod = isVersionFromGoMod; exports.getVersion = getVersion; const core = __importStar(__nccwpck_require__(7484)); const httpm = __importStar(__nccwpck_require__(4844)); @@ -93412,8 +93418,9 @@ const isLessVersion = (a, b) => { const getRequestedVersion = () => { let requestedVersion = core.getInput(`version`); const workingDirectory = core.getInput(`working-directory`); - let goMod = "go.mod"; - if (workingDirectory) { + const goModPath = core.getInput(`go-mod-path`); + let goMod = goModPath || "go.mod"; + if (!path_1.default.isAbsolute(goMod) && workingDirectory) { goMod = path_1.default.join(workingDirectory, goMod); } if (requestedVersion == "" && fs.existsSync(goMod)) { @@ -93451,10 +93458,27 @@ const fetchVersionMapping = async () => { throw new Error(`failed to get action config: ${exc.message}`); } }; +function isVersionFromGoMod() { + const requestedVersion = core.getInput(`version`); + const goModPath = core.getInput(`go-mod-path`); + const workingDirectory = core.getInput(`working-directory`); + let goMod = goModPath || "go.mod"; + if (!path_1.default.isAbsolute(goMod) && workingDirectory) { + goMod = path_1.default.join(workingDirectory, goMod); + } + return requestedVersion == "" && fs.existsSync(goMod); +} async function getVersion(mode) { core.info(`Finding needed golangci-lint version...`); if (mode == install_1.InstallMode.GoInstall) { - const v = core.getInput(`version`); + let v = core.getInput(`version`); + // If no explicit version is provided, check go.mod + if (!v && isVersionFromGoMod()) { + const reqVersion = getRequestedVersion(); + if (reqVersion) { + v = (0, exports.stringifyVersion)(reqVersion); + } + } return { TargetVersion: v ? v : "latest" }; } const reqVersion = getRequestedVersion(); diff --git a/dist/run/index.js b/dist/run/index.js index 7f2545f..abf4dec 100644 --- a/dist/run/index.js +++ b/dist/run/index.js @@ -92671,7 +92671,7 @@ const printOutput = (res) => { * @returns path to installed binary of golangci-lint. */ async function install() { - const mode = core.getInput("install-mode").toLowerCase(); + let mode = core.getInput("install-mode").toLowerCase(); if (mode === InstallMode.None) { const binPath = await (0, which_1.default)("golangci-lint", { nothrow: true }); if (!binPath) { @@ -92679,6 +92679,11 @@ async function install() { } return binPath; } + // If version is being read from go.mod, force goinstall mode + if ((0, version_1.isVersionFromGoMod)()) { + core.info(`Version detected from go.mod file, using goinstall mode`); + mode = InstallMode.GoInstall; + } const versionInfo = await (0, version_1.getVersion)(mode); return await installBinary(versionInfo, mode); } @@ -93358,6 +93363,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.stringifyVersion = void 0; +exports.isVersionFromGoMod = isVersionFromGoMod; exports.getVersion = getVersion; const core = __importStar(__nccwpck_require__(7484)); const httpm = __importStar(__nccwpck_require__(4844)); @@ -93412,8 +93418,9 @@ const isLessVersion = (a, b) => { const getRequestedVersion = () => { let requestedVersion = core.getInput(`version`); const workingDirectory = core.getInput(`working-directory`); - let goMod = "go.mod"; - if (workingDirectory) { + const goModPath = core.getInput(`go-mod-path`); + let goMod = goModPath || "go.mod"; + if (!path_1.default.isAbsolute(goMod) && workingDirectory) { goMod = path_1.default.join(workingDirectory, goMod); } if (requestedVersion == "" && fs.existsSync(goMod)) { @@ -93451,10 +93458,27 @@ const fetchVersionMapping = async () => { throw new Error(`failed to get action config: ${exc.message}`); } }; +function isVersionFromGoMod() { + const requestedVersion = core.getInput(`version`); + const goModPath = core.getInput(`go-mod-path`); + const workingDirectory = core.getInput(`working-directory`); + let goMod = goModPath || "go.mod"; + if (!path_1.default.isAbsolute(goMod) && workingDirectory) { + goMod = path_1.default.join(workingDirectory, goMod); + } + return requestedVersion == "" && fs.existsSync(goMod); +} async function getVersion(mode) { core.info(`Finding needed golangci-lint version...`); if (mode == install_1.InstallMode.GoInstall) { - const v = core.getInput(`version`); + let v = core.getInput(`version`); + // If no explicit version is provided, check go.mod + if (!v && isVersionFromGoMod()) { + const reqVersion = getRequestedVersion(); + if (reqVersion) { + v = (0, exports.stringifyVersion)(reqVersion); + } + } return { TargetVersion: v ? v : "latest" }; } const reqVersion = getRequestedVersion(); diff --git a/src/install.ts b/src/install.ts index 1f5083f..aef30ca 100644 --- a/src/install.ts +++ b/src/install.ts @@ -6,7 +6,7 @@ import path from "path" import { promisify } from "util" import which from "which" -import { getVersion, VersionInfo } from "./version" +import { getVersion, isVersionFromGoMod, VersionInfo } from "./version" const execShellCommand = promisify(exec) @@ -36,7 +36,7 @@ const printOutput = (res: ExecRes): void => { * @returns path to installed binary of golangci-lint. */ export async function install(): Promise { - const mode = core.getInput("install-mode").toLowerCase() + let mode = core.getInput("install-mode").toLowerCase() if (mode === InstallMode.None) { const binPath = await which("golangci-lint", { nothrow: true }) @@ -46,6 +46,12 @@ export async function install(): Promise { return binPath } + // If version is being read from go.mod, force goinstall mode + if (isVersionFromGoMod()) { + core.info(`Version detected from go.mod file, using goinstall mode`) + mode = InstallMode.GoInstall + } + const versionInfo = await getVersion(mode) return await installBinary(versionInfo, mode) diff --git a/src/version.ts b/src/version.ts index e8ae68a..d0478ea 100644 --- a/src/version.ts +++ b/src/version.ts @@ -68,9 +68,10 @@ const isLessVersion = (a: Version, b: Version): boolean => { const getRequestedVersion = (): Version => { let requestedVersion = core.getInput(`version`) const workingDirectory = core.getInput(`working-directory`) + const goModPath = core.getInput(`go-mod-path`) - let goMod = "go.mod" - if (workingDirectory) { + let goMod = goModPath || "go.mod" + if (!path.isAbsolute(goMod) && workingDirectory) { goMod = path.join(workingDirectory, goMod) } @@ -129,11 +130,32 @@ const fetchVersionMapping = async (): Promise => { } } +export function isVersionFromGoMod(): boolean { + const requestedVersion = core.getInput(`version`) + const goModPath = core.getInput(`go-mod-path`) + const workingDirectory = core.getInput(`working-directory`) + + let goMod = goModPath || "go.mod" + if (!path.isAbsolute(goMod) && workingDirectory) { + goMod = path.join(workingDirectory, goMod) + } + + return requestedVersion == "" && fs.existsSync(goMod) +} + export async function getVersion(mode: InstallMode): Promise { core.info(`Finding needed golangci-lint version...`) if (mode == InstallMode.GoInstall) { - const v: string = core.getInput(`version`) + let v: string = core.getInput(`version`) + + // If no explicit version is provided, check go.mod + if (!v && isVersionFromGoMod()) { + const reqVersion = getRequestedVersion() + if (reqVersion) { + v = stringifyVersion(reqVersion) + } + } return { TargetVersion: v ? v : "latest" } }