From 3da8bd416a854285c502ba9663ff115627f3169e Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Thu, 20 Nov 2025 17:12:16 +0100 Subject: [PATCH 1/8] refactor: change the code to be more flexible --- src/cache.ts | 16 +++++-- src/install.ts | 29 ++++++++---- src/patch.ts | 10 +---- src/plugins.ts | 27 +++++------- src/run.ts | 117 ++++++++++++++++++++----------------------------- 5 files changed, 94 insertions(+), 105 deletions(-) diff --git a/src/cache.ts b/src/cache.ts index 57b817e9f6..2e51782bfa 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -74,7 +74,10 @@ async function buildCacheKeys(): Promise { } export async function restoreCache(): Promise { - if (core.getBooleanInput(`skip-cache`, { required: true })) return + if (core.getBooleanInput(`skip-cache`, { required: true })) { + core.info(`Skipping cache restoration`) + return + } if (!utils.isValidEvent()) { utils.logWarning( @@ -116,8 +119,15 @@ export async function restoreCache(): Promise { } export async function saveCache(): Promise { - if (core.getBooleanInput(`skip-cache`, { required: true })) return - if (core.getBooleanInput(`skip-save-cache`, { required: true })) return + if (core.getBooleanInput(`skip-cache`, { required: true })) { + core.info(`Skipping cache saving`) + return + } + + if (core.getBooleanInput(`skip-save-cache`, { required: true })) { + core.info(`Skipping cache saving`) + return + } // Validate inputs, this can cause task failure if (!utils.isValidEvent()) { diff --git a/src/install.ts b/src/install.ts index 8d71ec79e5..f8b2b18b4f 100644 --- a/src/install.ts +++ b/src/install.ts @@ -1,6 +1,7 @@ import * as core from "@actions/core" import * as tc from "@actions/tool-cache" import { exec, ExecOptionsWithStringEncoding } from "child_process" +import fs from "fs" import os from "os" import path from "path" import { promisify } from "util" @@ -8,7 +9,7 @@ import which from "which" import { getVersion, VersionInfo } from "./version" -const execShellCommand = promisify(exec) +const execCommand = promisify(exec) export enum InstallMode { Binary = "binary", @@ -21,13 +22,15 @@ type ExecRes = { stderr: string } -const printOutput = (res: ExecRes): void => { +const printOutput = (res: ExecRes): ExecRes => { if (res.stdout) { core.info(res.stdout) } if (res.stderr) { core.info(res.stderr) } + + return res } /** @@ -36,6 +39,17 @@ const printOutput = (res: ExecRes): void => { * @returns path to installed binary of golangci-lint. */ export async function install(): Promise { + const problemMatchers = core.getBooleanInput(`problem-matchers`) + + if (problemMatchers) { + const matchersPath = path.join(__dirname, "../..", "problem-matchers.json") + if (fs.existsSync(matchersPath)) { + // Adds problem matchers. + // https://github.com/actions/setup-go/blob/cdcb36043654635271a94b9a6d1392de5bb323a7/src/main.ts#L81-L83 + core.info(`##[add-matcher]${matchersPath}`) + } + } + const mode = core.getInput("install-mode").toLowerCase() if (mode === InstallMode.None) { @@ -84,17 +98,14 @@ async function goInstall(versionInfo: VersionInfo): Promise { const options: ExecOptionsWithStringEncoding = { env: { ...process.env, CGO_ENABLED: "1" } } - const exres = await execShellCommand( - `go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@${versionInfo.TargetVersion}`, - options + await execCommand(`go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@${versionInfo.TargetVersion}`, options).then( + printOutput ) - printOutput(exres) - const res = await execShellCommand( + const res = await execCommand( `go install -n github.com/golangci/golangci-lint/v2/cmd/golangci-lint@${versionInfo.TargetVersion}`, options - ) - printOutput(res) + ).then(printOutput) // The output of `go install -n` when the binary is already installed is `touch `. const binPath = res.stderr diff --git a/src/patch.ts b/src/patch.ts index f2bb41266e..bbe89c5fa7 100644 --- a/src/patch.ts +++ b/src/patch.ts @@ -17,10 +17,6 @@ export function isOnlyNewIssues(): boolean { } export async function fetchPatch(): Promise { - if (!isOnlyNewIssues()) { - return `` - } - const ctx = github.context switch (ctx.eventName) { @@ -70,8 +66,7 @@ async function fetchPullRequestPatch(ctx: Context): Promise { } try { - const tempDir = await createTempDir() - const patchPath = path.join(tempDir, "pull.patch") + const patchPath = await createTempDir().then((tempDir) => path.join(tempDir, "pull.patch")) core.info(`Writing patch to ${patchPath}`) await writeFile(patchPath, alterDiffPatch(patch)) return patchPath @@ -108,8 +103,7 @@ async function fetchPushPatch(ctx: Context): Promise { } try { - const tempDir = await createTempDir() - const patchPath = path.join(tempDir, "push.patch") + const patchPath = await createTempDir().then((tempDir) => path.join(tempDir, "push.patch")) core.info(`Writing patch to ${patchPath}`) await writeFile(patchPath, alterDiffPatch(patch)) return patchPath diff --git a/src/plugins.ts b/src/plugins.ts index 97a624f3c1..6d11d6de55 100644 --- a/src/plugins.ts +++ b/src/plugins.ts @@ -5,7 +5,7 @@ import * as path from "path" import { promisify } from "util" import YAML from "yaml" -const execShellCommand = promisify(exec) +const execCommand = promisify(exec) type ExecRes = { stdout: string @@ -40,7 +40,7 @@ export async function install(binPath: string): Promise { .find((filePath) => fs.existsSync(filePath)) if (!configFile || configFile === "") { - return "" + return binPath } core.info(`Found configuration for the plugin module system : ${configFile}`) @@ -74,18 +74,15 @@ export async function install(binPath: string): Promise { core.info(`Running [${cmd}] in [${rootDir}] ...`) - try { - const options: ExecOptionsWithStringEncoding = { - cwd: rootDir, - } - - const res = await execShellCommand(cmd, options) - printOutput(res) - - core.info(`Built custom golangci-lint binary in ${Date.now() - startedAt}ms`) - - return path.join(rootDir, config.destination, config.name) - } catch (exc) { - throw new Error(`Failed to build custom golangci-lint binary: ${exc.message}`) + const options: ExecOptionsWithStringEncoding = { + cwd: rootDir, } + + return execCommand(cmd, options) + .then(printOutput) + .then(() => core.info(`Built custom golangci-lint binary in ${Date.now() - startedAt}ms`)) + .then(() => path.join(rootDir, config.destination, config.name)) + .catch((exc) => { + throw new Error(`Failed to build custom golangci-lint binary: ${exc.message}`) + }) } diff --git a/src/run.ts b/src/run.ts index 8687b1ee0e..34426d66cc 100644 --- a/src/run.ts +++ b/src/run.ts @@ -10,37 +10,7 @@ import { install } from "./install" import { fetchPatch, isOnlyNewIssues } from "./patch" import * as plugins from "./plugins" -const execShellCommand = promisify(exec) - -type Env = { - binPath: string - patchPath: string -} - -async function prepareEnv(installOnly: boolean): Promise { - const startedAt = Date.now() - - // Prepare cache, lint and go in parallel. - await restoreCache() - - let binPath = await install() - - // Build custom golangci-lint if needed. - const customBinPath = await plugins.install(binPath) - if (customBinPath !== "") { - binPath = customBinPath - } - - if (installOnly) { - return { binPath, patchPath: `` } - } - - const patchPath = await fetchPatch() - - core.info(`Prepared env in ${Date.now() - startedAt}ms`) - - return { binPath, patchPath } -} +const execCommand = promisify(exec) type ExecRes = { stdout: string @@ -56,13 +26,7 @@ const printOutput = (res: ExecRes): void => { } } -async function runLint(binPath: string, patchPath: string): Promise { - const debug = core.getInput(`debug`) - if (debug.split(`,`).includes(`cache`)) { - const res = await execShellCommand(`${binPath} cache status`) - printOutput(res) - } - +async function runLint(binPath: string): Promise { const userArgs = core.getInput(`args`) const addedArgs: string[] = [] @@ -77,17 +41,6 @@ async function runLint(binPath: string, patchPath: string): Promise { const userArgsMap = new Map(userArgsList) const userArgNames = new Set(userArgsList.map(([key]) => key)) - const problemMatchers = core.getBooleanInput(`problem-matchers`) - - if (problemMatchers) { - const matchersPath = path.join(__dirname, "../..", "problem-matchers.json") - if (fs.existsSync(matchersPath)) { - // Adds problem matchers. - // https://github.com/actions/setup-go/blob/cdcb36043654635271a94b9a6d1392de5bb323a7/src/main.ts#L81-L83 - core.info(`##[add-matcher]${matchersPath}`) - } - } - if (isOnlyNewIssues()) { if ( userArgNames.has(`new`) || @@ -99,6 +52,7 @@ async function runLint(binPath: string, patchPath: string): Promise { } const ctx = github.context + const patchPath = await fetchPatch() core.info(`only new issues on ${ctx.eventName}: ${patchPath}`) @@ -150,22 +104,21 @@ async function runLint(binPath: string, patchPath: string): Promise { core.info(`Running [${cmd}] in [${cmdArgs.cwd || process.cwd()}] ...`) const startedAt = Date.now() - try { - const res = await execShellCommand(cmd, cmdArgs) - printOutput(res) - core.info(`golangci-lint found no issues`) - } catch (exc) { - // This logging passes issues to GitHub annotations but comments can be more convenient for some users. - printOutput(exc) - - if (exc.code === 1) { - core.setFailed(`issues found`) - } else { - core.setFailed(`golangci-lint exit with code ${exc.code}`) - } - } - core.info(`Ran golangci-lint in ${Date.now() - startedAt}ms`) + return execCommand(cmd, cmdArgs) + .then(printOutput) + .then(() => core.info(`golangci-lint found no issues`)) + .catch((exc) => { + // This logging passes issues to GitHub annotations. + printOutput(exc) + + if (exc.code === 1) { + core.setFailed(`issues found`) + } else { + core.setFailed(`golangci-lint exit with code ${exc.code}`) + } + }) + .finally(() => core.info(`Ran golangci-lint in ${Date.now() - startedAt}ms`)) } async function runVerify(binPath: string, userArgsMap: Map, cmdArgs: ExecOptionsWithStringEncoding): Promise { @@ -186,8 +139,7 @@ async function runVerify(binPath: string, userArgsMap: Map, cmdA core.info(`Running [${cmdVerify}] in [${cmdArgs.cwd || process.cwd()}] ...`) - const res = await execShellCommand(cmdVerify, cmdArgs) - printOutput(res) + await execCommand(cmdVerify, cmdArgs).then(printOutput) } async function getConfigPath(binPath: string, userArgsMap: Map, cmdArgs: ExecOptionsWithStringEncoding): Promise { @@ -199,26 +151,51 @@ async function getConfigPath(binPath: string, userArgsMap: Map, core.info(`Running [${cmdConfigPath}] in [${cmdArgs.cwd || process.cwd()}] ...`) try { - const resPath = await execShellCommand(cmdConfigPath, cmdArgs) + const resPath = await execCommand(cmdConfigPath, cmdArgs) return resPath.stderr.trim() } catch { return `` } } +async function debugAction(binPath: string) { + const flags = core.getInput(`debug`).split(`,`) + + if (flags.includes(`clean`)) { + const cmd = `${binPath} cache clean` + + core.info(`Running [${cmd}] ...`) + + await execCommand(cmd).then(printOutput) + } + + if (flags.includes(`cache`)) { + const cmd = `${binPath} cache status` + + core.info(`Running [${cmd}] ...`) + + await execCommand(cmd).then(printOutput) + } +} + export async function run(): Promise { try { - const installOnly = core.getBooleanInput(`install-only`, { required: true }) + await core.group(`Restore cache`, restoreCache) - const { binPath, patchPath } = await core.group(`prepare environment`, () => prepareEnv(installOnly)) + const binPath = await core.group(`Install`, () => install().then(plugins.install)) core.addPath(path.dirname(binPath)) + if (core.getInput(`debug`)) { + await core.group(`Debug`, () => debugAction(binPath)) + } + + const installOnly = core.getBooleanInput(`install-only`, { required: true }) if (installOnly) { return } - await core.group(`run golangci-lint`, () => runLint(binPath, patchPath)) + await core.group(`run golangci-lint`, () => runLint(binPath)) } catch (error) { core.error(`Failed to run: ${error}, ${error.stack}`) core.setFailed(error.message) From e9b51884a8d0670da6b6f438596941c045ff9d20 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 21 Nov 2025 02:25:36 +0100 Subject: [PATCH 2/8] refactor: modify working directory usage inside run --- src/run.ts | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/run.ts b/src/run.ts index 34426d66cc..ca92e0f4c0 100644 --- a/src/run.ts +++ b/src/run.ts @@ -26,7 +26,7 @@ const printOutput = (res: ExecRes): void => { } } -async function runLint(binPath: string): Promise { +async function runLint(binPath: string, rootDir: string): Promise { const userArgs = core.getInput(`args`) const addedArgs: string[] = [] @@ -84,17 +84,12 @@ async function runLint(binPath: string): Promise { const cmdArgs: ExecOptionsWithStringEncoding = {} - const workingDirectory = core.getInput(`working-directory`) - if (workingDirectory) { - if (!fs.existsSync(workingDirectory) || !fs.lstatSync(workingDirectory).isDirectory()) { - throw new Error(`working-directory (${workingDirectory}) was not a path`) - } - + if (rootDir) { if (!userArgNames.has(`path-prefix`) && !userArgNames.has(`path-mode`)) { addedArgs.push(`--path-mode=abs`) } - cmdArgs.cwd = path.resolve(workingDirectory) + cmdArgs.cwd = path.resolve(rootDir) } await runVerify(binPath, userArgsMap, cmdArgs) @@ -178,6 +173,17 @@ async function debugAction(binPath: string) { } } +function getWorkingDirectory(): string { + const workingDirectory = core.getInput(`working-directory`) + if (workingDirectory) { + if (!fs.existsSync(workingDirectory) || !fs.lstatSync(workingDirectory).isDirectory()) { + throw new Error(`working-directory (${workingDirectory}) was not a path`) + } + } + + return workingDirectory +} + export async function run(): Promise { try { await core.group(`Restore cache`, restoreCache) @@ -195,7 +201,7 @@ export async function run(): Promise { return } - await core.group(`run golangci-lint`, () => runLint(binPath)) + await core.group(`run golangci-lint`, () => runLint(binPath, getWorkingDirectory())) } catch (error) { core.error(`Failed to run: ${error}, ${error.stack}`) core.setFailed(error.message) From 161a55b8c4ce4bf22019386237c618d9194ef339 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 21 Nov 2025 03:18:27 +0100 Subject: [PATCH 3/8] feat: automatic-module-directories --- src/run.ts | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/src/run.ts b/src/run.ts index ca92e0f4c0..7faeafb5d1 100644 --- a/src/run.ts +++ b/src/run.ts @@ -26,7 +26,7 @@ const printOutput = (res: ExecRes): void => { } } -async function runLint(binPath: string, rootDir: string): Promise { +async function runGolangciLint(binPath: string, rootDir: string): Promise { const userArgs = core.getInput(`args`) const addedArgs: string[] = [] @@ -184,6 +184,42 @@ function getWorkingDirectory(): string { return workingDirectory } +function modulesAutoDetection(rootDir: string): string[] { + const o: fs.GlobOptions = { + cwd: rootDir, + exclude: ["**/vendor/**", "**/node_modules/**", "**/.git/**", "**/dist/**"], + } + + const matches = fs.globSync("**/go.mod", o) + + const dirs = matches + .filter((m) => typeof m === "string") + .map((m) => path.resolve(rootDir, path.dirname(m))) + .sort() + + return [...new Set(dirs)] +} + +async function runLint(binPath: string): Promise { + const workingDirectory = getWorkingDirectory() + + const experimental = core.getInput(`experimental`).split(`,`) + + if (experimental.includes(`automatic-module-directories`)) { + const wds = modulesAutoDetection(workingDirectory) + + const cwd = process.cwd() + + for (const wd of wds) { + await core.group(`run golangci-lint in ${path.relative(cwd, wd)}`, () => runGolangciLint(binPath, wd)) + } + + return + } + + await core.group(`run golangci-lint`, () => runGolangciLint(binPath, workingDirectory)) +} + export async function run(): Promise { try { await core.group(`Restore cache`, restoreCache) @@ -201,7 +237,7 @@ export async function run(): Promise { return } - await core.group(`run golangci-lint`, () => runLint(binPath, getWorkingDirectory())) + await runLint(binPath) } catch (error) { core.error(`Failed to run: ${error}, ${error.stack}`) core.setFailed(error.message) From 50132d86ac91f9844d903190fa4d3748e1d5817b Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 21 Nov 2025 03:31:08 +0100 Subject: [PATCH 4/8] tests: add sample --- sample-monorepo/a/go.mod | 3 +++ sample-monorepo/a/sample.go | 26 ++++++++++++++++++++++++++ sample-monorepo/a/suba/go.mod | 3 +++ sample-monorepo/a/suba/sample.go | 26 ++++++++++++++++++++++++++ sample-monorepo/b/go.mod | 3 +++ sample-monorepo/b/sample.go | 26 ++++++++++++++++++++++++++ sample-monorepo/c/go.mod | 3 +++ sample-monorepo/c/sample.go | 26 ++++++++++++++++++++++++++ 8 files changed, 116 insertions(+) create mode 100644 sample-monorepo/a/go.mod create mode 100644 sample-monorepo/a/sample.go create mode 100644 sample-monorepo/a/suba/go.mod create mode 100644 sample-monorepo/a/suba/sample.go create mode 100644 sample-monorepo/b/go.mod create mode 100644 sample-monorepo/b/sample.go create mode 100644 sample-monorepo/c/go.mod create mode 100644 sample-monorepo/c/sample.go diff --git a/sample-monorepo/a/go.mod b/sample-monorepo/a/go.mod new file mode 100644 index 0000000000..fa18c999c0 --- /dev/null +++ b/sample-monorepo/a/go.mod @@ -0,0 +1,3 @@ +module github.com/golangci/actiona + +go 1.24.0 diff --git a/sample-monorepo/a/sample.go b/sample-monorepo/a/sample.go new file mode 100644 index 0000000000..80504b0df1 --- /dev/null +++ b/sample-monorepo/a/sample.go @@ -0,0 +1,26 @@ +// Package sample is used as test input for golangci action. +package sample + +import ( + "crypto/md5" + "encoding/hex" + "errors" +) + +// Hash~ +func Hash(data string) string { + retError() + retError2() + + h := md5.New() + h.Write([]byte(data)) + return hex.EncodeToString(h.Sum(nil)) +} + +func retError() error { + return errors.New("err") +} + +func retError2() error { + return errors.New("err2") +} diff --git a/sample-monorepo/a/suba/go.mod b/sample-monorepo/a/suba/go.mod new file mode 100644 index 0000000000..2f75be60cd --- /dev/null +++ b/sample-monorepo/a/suba/go.mod @@ -0,0 +1,3 @@ +module github.com/golangci/actiona/suba + +go 1.24.0 diff --git a/sample-monorepo/a/suba/sample.go b/sample-monorepo/a/suba/sample.go new file mode 100644 index 0000000000..80504b0df1 --- /dev/null +++ b/sample-monorepo/a/suba/sample.go @@ -0,0 +1,26 @@ +// Package sample is used as test input for golangci action. +package sample + +import ( + "crypto/md5" + "encoding/hex" + "errors" +) + +// Hash~ +func Hash(data string) string { + retError() + retError2() + + h := md5.New() + h.Write([]byte(data)) + return hex.EncodeToString(h.Sum(nil)) +} + +func retError() error { + return errors.New("err") +} + +func retError2() error { + return errors.New("err2") +} diff --git a/sample-monorepo/b/go.mod b/sample-monorepo/b/go.mod new file mode 100644 index 0000000000..7be68bcf73 --- /dev/null +++ b/sample-monorepo/b/go.mod @@ -0,0 +1,3 @@ +module github.com/golangci/actionb + +go 1.24.0 diff --git a/sample-monorepo/b/sample.go b/sample-monorepo/b/sample.go new file mode 100644 index 0000000000..80504b0df1 --- /dev/null +++ b/sample-monorepo/b/sample.go @@ -0,0 +1,26 @@ +// Package sample is used as test input for golangci action. +package sample + +import ( + "crypto/md5" + "encoding/hex" + "errors" +) + +// Hash~ +func Hash(data string) string { + retError() + retError2() + + h := md5.New() + h.Write([]byte(data)) + return hex.EncodeToString(h.Sum(nil)) +} + +func retError() error { + return errors.New("err") +} + +func retError2() error { + return errors.New("err2") +} diff --git a/sample-monorepo/c/go.mod b/sample-monorepo/c/go.mod new file mode 100644 index 0000000000..bfcbddc1b3 --- /dev/null +++ b/sample-monorepo/c/go.mod @@ -0,0 +1,3 @@ +module github.com/golangci/actionc + +go 1.24.0 diff --git a/sample-monorepo/c/sample.go b/sample-monorepo/c/sample.go new file mode 100644 index 0000000000..80504b0df1 --- /dev/null +++ b/sample-monorepo/c/sample.go @@ -0,0 +1,26 @@ +// Package sample is used as test input for golangci action. +package sample + +import ( + "crypto/md5" + "encoding/hex" + "errors" +) + +// Hash~ +func Hash(data string) string { + retError() + retError2() + + h := md5.New() + h.Write([]byte(data)) + return hex.EncodeToString(h.Sum(nil)) +} + +func retError() error { + return errors.New("err") +} + +func retError2() error { + return errors.New("err2") +} From f4e410b9ce3e1f936d92dd5e24c6217656979e11 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 21 Nov 2025 03:33:54 +0100 Subject: [PATCH 5/8] chore: add a CI job for automatic-module-directories --- .github/workflows/test.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0dd5127023..5fa489cd3f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -146,3 +146,30 @@ jobs: version: ${{ matrix.version }} working-directory: sample-plugins args: --timeout=5m --issues-exit-code=0 ./... + + test-monorepo: + needs: [ build ] + strategy: + matrix: + os: + - ubuntu-latest + - ubuntu-22.04-arm + - macos-latest + - windows-latest + runs-on: ${{ matrix.os }} + permissions: + contents: read + pull-requests: read + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-node@v6 + with: + node-version: 24.x + - uses: actions/setup-go@v6 + with: + go-version: oldstable + - uses: ./ + with: + working-directory: sample-monorepo + experimental: "automatic-module-directories" + args: --timeout=5m --issues-exit-code=0 ./... From 988291118b608b9b219e3b82f9fce5782a4d3046 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 21 Nov 2025 04:26:24 +0100 Subject: [PATCH 6/8] docs: add experimental inside the action manifest --- action.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/action.yml b/action.yml index cb3120c094..4fedce7ceb 100644 --- a/action.yml +++ b/action.yml @@ -65,6 +65,12 @@ inputs: example: "cache,clean" default: "" required: false + experimental: + description: | + Experimental options for the action. + List of comma separated options. + default: "" + required: false runs: using: "node24" main: "dist/run/index.js" From 921f54fb18d2fdc541b30c3e256cdfcb2226a8a8 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 21 Nov 2025 04:27:06 +0100 Subject: [PATCH 7/8] docs: add experimental inside readme --- README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/README.md b/README.md index 9551bf4998..4d6e8b30d9 100644 --- a/README.md +++ b/README.md @@ -275,6 +275,7 @@ You will also likely need to add the following `.gitattributes` file to ensure t | [`skip-save-cache`](#skip-save-cache) | Don't save cache. | | [`cache-invalidation-interval`](#cache-invalidation-interval) | Number of days before cache invalidation. | | [`problem-matchers`](#problem-matchers) | Forces the usage of the embedded problem matchers. | +| [Experimental](#experimental) | Experimental options | ### Installation @@ -552,6 +553,47 @@ with: +### Experimental + +The following options are experimental: those may or may not be supported in the future, and so they will be either converted into a dedicated option or removed. + +List of comma-separated options. + +
+Example + +```yaml +uses: golangci/golangci-lint-action@v9 +with: + experimental: "foo,bar" +``` + +
+ +#### `automatic-module-directories` + +(optional) + +This option will run golangci-lint in each module directory, useful for monorepos. + +The automatic detection of modules uses the `working-directory` as the base directory if defined, otherwise the root directory. + +> [!IMPORTANT] +> - The cache key will refer to the `working-directory` (if defined) because all the golangci-lint runs must use the same cache directory/key. +> - The version detection will only work if the project has a single module. +> - If the project has multiple modules, the custom build file must be located in the repository root ( or `working-directory`). + +
+Example + +```yaml +uses: golangci/golangci-lint-action@v9 +with: + experimental: "automatic-module-directories" +``` + +
+ ## Annotations Currently, GitHub parses the action's output and creates [annotations](https://github.blog/2018-12-14-introducing-check-runs-and-annotations/). From 8eed76a27c82ea7e845df6e2a59fb3a8bd14cfca Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 21 Nov 2025 04:24:35 +0100 Subject: [PATCH 8/8] chore: generate --- dist/post_run/index.js | 186 +++++++++++++++++++++++------------------ dist/run/index.js | 186 +++++++++++++++++++++++------------------ 2 files changed, 208 insertions(+), 164 deletions(-) diff --git a/dist/post_run/index.js b/dist/post_run/index.js index 957503db6f..f86b54ad45 100644 --- a/dist/post_run/index.js +++ b/dist/post_run/index.js @@ -96844,8 +96844,10 @@ async function buildCacheKeys() { return keys; } async function restoreCache() { - if (core.getBooleanInput(`skip-cache`, { required: true })) + if (core.getBooleanInput(`skip-cache`, { required: true })) { + core.info(`Skipping cache restoration`); return; + } if (!utils.isValidEvent()) { utils.logWarning(`Event Validation Error: The event type ${process.env[constants_1.Events.Key]} is not supported because it's not tied to a branch or tag ref.`); return; @@ -96881,10 +96883,14 @@ async function restoreCache() { } } async function saveCache() { - if (core.getBooleanInput(`skip-cache`, { required: true })) + if (core.getBooleanInput(`skip-cache`, { required: true })) { + core.info(`Skipping cache saving`); return; - if (core.getBooleanInput(`skip-save-cache`, { required: true })) + } + if (core.getBooleanInput(`skip-save-cache`, { required: true })) { + core.info(`Skipping cache saving`); return; + } // Validate inputs, this can cause task failure if (!utils.isValidEvent()) { utils.logWarning(`Event Validation Error: The event type ${process.env[constants_1.Events.Key]} is not supported because it's not tied to a branch or tag ref.`); @@ -96999,12 +97005,13 @@ exports.installBinary = installBinary; const core = __importStar(__nccwpck_require__(37484)); const tc = __importStar(__nccwpck_require__(33472)); const child_process_1 = __nccwpck_require__(35317); +const fs_1 = __importDefault(__nccwpck_require__(79896)); const os_1 = __importDefault(__nccwpck_require__(70857)); const path_1 = __importDefault(__nccwpck_require__(16928)); const util_1 = __nccwpck_require__(39023); const which_1 = __importDefault(__nccwpck_require__(11189)); const version_1 = __nccwpck_require__(311); -const execShellCommand = (0, util_1.promisify)(child_process_1.exec); +const execCommand = (0, util_1.promisify)(child_process_1.exec); var InstallMode; (function (InstallMode) { InstallMode["Binary"] = "binary"; @@ -97018,6 +97025,7 @@ const printOutput = (res) => { if (res.stderr) { core.info(res.stderr); } + return res; }; /** * Install golangci-lint. @@ -97025,6 +97033,15 @@ const printOutput = (res) => { * @returns path to installed binary of golangci-lint. */ async function install() { + const problemMatchers = core.getBooleanInput(`problem-matchers`); + if (problemMatchers) { + const matchersPath = path_1.default.join(__dirname, "../..", "problem-matchers.json"); + if (fs_1.default.existsSync(matchersPath)) { + // Adds problem matchers. + // https://github.com/actions/setup-go/blob/cdcb36043654635271a94b9a6d1392de5bb323a7/src/main.ts#L81-L83 + core.info(`##[add-matcher]${matchersPath}`); + } + } const mode = core.getInput("install-mode").toLowerCase(); if (mode === InstallMode.None) { const binPath = await (0, which_1.default)("golangci-lint", { nothrow: true }); @@ -97064,10 +97081,8 @@ async function goInstall(versionInfo) { core.info(`Installing golangci-lint ${versionInfo.TargetVersion}...`); const startedAt = Date.now(); const options = { env: { ...process.env, CGO_ENABLED: "1" } }; - const exres = await execShellCommand(`go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@${versionInfo.TargetVersion}`, options); - printOutput(exres); - const res = await execShellCommand(`go install -n github.com/golangci/golangci-lint/v2/cmd/golangci-lint@${versionInfo.TargetVersion}`, options); - printOutput(res); + await execCommand(`go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@${versionInfo.TargetVersion}`, options).then(printOutput); + const res = await execCommand(`go install -n github.com/golangci/golangci-lint/v2/cmd/golangci-lint@${versionInfo.TargetVersion}`, options).then(printOutput); // The output of `go install -n` when the binary is already installed is `touch `. const binPath = res.stderr .split(/\r?\n/) @@ -97196,9 +97211,6 @@ function isOnlyNewIssues() { return core.getBooleanInput(`only-new-issues`, { required: true }); } async function fetchPatch() { - if (!isOnlyNewIssues()) { - return ``; - } const ctx = github.context; switch (ctx.eventName) { case `pull_request`: @@ -97242,8 +97254,7 @@ async function fetchPullRequestPatch(ctx) { return ``; // don't fail the action, but analyze without patch } try { - const tempDir = await createTempDir(); - const patchPath = path_1.default.join(tempDir, "pull.patch"); + const patchPath = await createTempDir().then((tempDir) => path_1.default.join(tempDir, "pull.patch")); core.info(`Writing patch to ${patchPath}`); await writeFile(patchPath, (0, diffUtils_1.alterDiffPatch)(patch)); return patchPath; @@ -97277,8 +97288,7 @@ async function fetchPushPatch(ctx) { return ``; // don't fail the action, but analyze without patch } try { - const tempDir = await createTempDir(); - const patchPath = path_1.default.join(tempDir, "push.patch"); + const patchPath = await createTempDir().then((tempDir) => path_1.default.join(tempDir, "push.patch")); core.info(`Writing patch to ${patchPath}`); await writeFile(patchPath, (0, diffUtils_1.alterDiffPatch)(patch)); return patchPath; @@ -97341,7 +97351,7 @@ const fs = __importStar(__nccwpck_require__(79896)); const path = __importStar(__nccwpck_require__(16928)); const util_1 = __nccwpck_require__(39023); const yaml_1 = __importDefault(__nccwpck_require__(38815)); -const execShellCommand = (0, util_1.promisify)(child_process_1.exec); +const execCommand = (0, util_1.promisify)(child_process_1.exec); const printOutput = (res) => { if (res.stdout) { core.info(res.stdout); @@ -97366,7 +97376,7 @@ async function install(binPath) { .map((filename) => path.join(rootDir, filename)) .find((filePath) => fs.existsSync(filePath)); if (!configFile || configFile === "") { - return ""; + return binPath; } core.info(`Found configuration for the plugin module system : ${configFile}`); core.info(`Building and installing custom golangci-lint binary...`); @@ -97388,18 +97398,16 @@ async function install(binPath) { } const cmd = `${binPath} custom`; core.info(`Running [${cmd}] in [${rootDir}] ...`); - try { - const options = { - cwd: rootDir, - }; - const res = await execShellCommand(cmd, options); - printOutput(res); - core.info(`Built custom golangci-lint binary in ${Date.now() - startedAt}ms`); - return path.join(rootDir, config.destination, config.name); - } - catch (exc) { + const options = { + cwd: rootDir, + }; + return execCommand(cmd, options) + .then(printOutput) + .then(() => core.info(`Built custom golangci-lint binary in ${Date.now() - startedAt}ms`)) + .then(() => path.join(rootDir, config.destination, config.name)) + .catch((exc) => { throw new Error(`Failed to build custom golangci-lint binary: ${exc.message}`); - } + }); } @@ -97456,24 +97464,7 @@ const cache_1 = __nccwpck_require__(97377); const install_1 = __nccwpck_require__(90232); const patch_1 = __nccwpck_require__(47161); const plugins = __importStar(__nccwpck_require__(96067)); -const execShellCommand = (0, util_1.promisify)(child_process_1.exec); -async function prepareEnv(installOnly) { - const startedAt = Date.now(); - // Prepare cache, lint and go in parallel. - await (0, cache_1.restoreCache)(); - let binPath = await (0, install_1.install)(); - // Build custom golangci-lint if needed. - const customBinPath = await plugins.install(binPath); - if (customBinPath !== "") { - binPath = customBinPath; - } - if (installOnly) { - return { binPath, patchPath: `` }; - } - const patchPath = await (0, patch_1.fetchPatch)(); - core.info(`Prepared env in ${Date.now() - startedAt}ms`); - return { binPath, patchPath }; -} +const execCommand = (0, util_1.promisify)(child_process_1.exec); const printOutput = (res) => { if (res.stdout) { core.info(res.stdout); @@ -97482,12 +97473,7 @@ const printOutput = (res) => { core.info(res.stderr); } }; -async function runLint(binPath, patchPath) { - const debug = core.getInput(`debug`); - if (debug.split(`,`).includes(`cache`)) { - const res = await execShellCommand(`${binPath} cache status`); - printOutput(res); - } +async function runGolangciLint(binPath, rootDir) { const userArgs = core.getInput(`args`); const addedArgs = []; const userArgsList = userArgs @@ -97499,15 +97485,6 @@ async function runLint(binPath, patchPath) { .map(([key, value]) => [key.toLowerCase(), value ?? ""]); const userArgsMap = new Map(userArgsList); const userArgNames = new Set(userArgsList.map(([key]) => key)); - const problemMatchers = core.getBooleanInput(`problem-matchers`); - if (problemMatchers) { - const matchersPath = path.join(__dirname, "../..", "problem-matchers.json"); - if (fs.existsSync(matchersPath)) { - // Adds problem matchers. - // https://github.com/actions/setup-go/blob/cdcb36043654635271a94b9a6d1392de5bb323a7/src/main.ts#L81-L83 - core.info(`##[add-matcher]${matchersPath}`); - } - } if ((0, patch_1.isOnlyNewIssues)()) { if (userArgNames.has(`new`) || userArgNames.has(`new-from-rev`) || @@ -97516,6 +97493,7 @@ async function runLint(binPath, patchPath) { throw new Error(`please, don't specify manually --new* args when requesting only new issues`); } const ctx = github.context; + const patchPath = await (0, patch_1.fetchPatch)(); core.info(`only new issues on ${ctx.eventName}: ${patchPath}`); switch (ctx.eventName) { case `pull_request`: @@ -97541,27 +97519,21 @@ async function runLint(binPath, patchPath) { } } const cmdArgs = {}; - const workingDirectory = core.getInput(`working-directory`); - if (workingDirectory) { - if (!fs.existsSync(workingDirectory) || !fs.lstatSync(workingDirectory).isDirectory()) { - throw new Error(`working-directory (${workingDirectory}) was not a path`); - } + if (rootDir) { if (!userArgNames.has(`path-prefix`) && !userArgNames.has(`path-mode`)) { addedArgs.push(`--path-mode=abs`); } - cmdArgs.cwd = path.resolve(workingDirectory); + cmdArgs.cwd = path.resolve(rootDir); } await runVerify(binPath, userArgsMap, cmdArgs); const cmd = `${binPath} run ${addedArgs.join(` `)} ${userArgs}`.trimEnd(); core.info(`Running [${cmd}] in [${cmdArgs.cwd || process.cwd()}] ...`); const startedAt = Date.now(); - try { - const res = await execShellCommand(cmd, cmdArgs); - printOutput(res); - core.info(`golangci-lint found no issues`); - } - catch (exc) { - // This logging passes issues to GitHub annotations but comments can be more convenient for some users. + return execCommand(cmd, cmdArgs) + .then(printOutput) + .then(() => core.info(`golangci-lint found no issues`)) + .catch((exc) => { + // This logging passes issues to GitHub annotations. printOutput(exc); if (exc.code === 1) { core.setFailed(`issues found`); @@ -97569,8 +97541,8 @@ async function runLint(binPath, patchPath) { else { core.setFailed(`golangci-lint exit with code ${exc.code}`); } - } - core.info(`Ran golangci-lint in ${Date.now() - startedAt}ms`); + }) + .finally(() => core.info(`Ran golangci-lint in ${Date.now() - startedAt}ms`)); } async function runVerify(binPath, userArgsMap, cmdArgs) { const verify = core.getBooleanInput(`verify`, { required: true }); @@ -97586,8 +97558,7 @@ async function runVerify(binPath, userArgsMap, cmdArgs) { cmdVerify += ` --config=${userArgsMap.get("config")}`; } core.info(`Running [${cmdVerify}] in [${cmdArgs.cwd || process.cwd()}] ...`); - const res = await execShellCommand(cmdVerify, cmdArgs); - printOutput(res); + await execCommand(cmdVerify, cmdArgs).then(printOutput); } async function getConfigPath(binPath, userArgsMap, cmdArgs) { let cmdConfigPath = `${binPath} config path`; @@ -97596,22 +97567,73 @@ async function getConfigPath(binPath, userArgsMap, cmdArgs) { } core.info(`Running [${cmdConfigPath}] in [${cmdArgs.cwd || process.cwd()}] ...`); try { - const resPath = await execShellCommand(cmdConfigPath, cmdArgs); + const resPath = await execCommand(cmdConfigPath, cmdArgs); return resPath.stderr.trim(); } catch { return ``; } } +async function debugAction(binPath) { + const flags = core.getInput(`debug`).split(`,`); + if (flags.includes(`clean`)) { + const cmd = `${binPath} cache clean`; + core.info(`Running [${cmd}] ...`); + await execCommand(cmd).then(printOutput); + } + if (flags.includes(`cache`)) { + const cmd = `${binPath} cache status`; + core.info(`Running [${cmd}] ...`); + await execCommand(cmd).then(printOutput); + } +} +function getWorkingDirectory() { + const workingDirectory = core.getInput(`working-directory`); + if (workingDirectory) { + if (!fs.existsSync(workingDirectory) || !fs.lstatSync(workingDirectory).isDirectory()) { + throw new Error(`working-directory (${workingDirectory}) was not a path`); + } + } + return workingDirectory; +} +function modulesAutoDetection(rootDir) { + const o = { + cwd: rootDir, + exclude: ["**/vendor/**", "**/node_modules/**", "**/.git/**", "**/dist/**"], + }; + const matches = fs.globSync("**/go.mod", o); + const dirs = matches + .filter((m) => typeof m === "string") + .map((m) => path.resolve(rootDir, path.dirname(m))) + .sort(); + return [...new Set(dirs)]; +} +async function runLint(binPath) { + const workingDirectory = getWorkingDirectory(); + const experimental = core.getInput(`experimental`).split(`,`); + if (experimental.includes(`automatic-module-directories`)) { + const wds = modulesAutoDetection(workingDirectory); + const cwd = process.cwd(); + for (const wd of wds) { + await core.group(`run golangci-lint in ${path.relative(cwd, wd)}`, () => runGolangciLint(binPath, wd)); + } + return; + } + await core.group(`run golangci-lint`, () => runGolangciLint(binPath, workingDirectory)); +} async function run() { try { - const installOnly = core.getBooleanInput(`install-only`, { required: true }); - const { binPath, patchPath } = await core.group(`prepare environment`, () => prepareEnv(installOnly)); + await core.group(`Restore cache`, cache_1.restoreCache); + const binPath = await core.group(`Install`, () => (0, install_1.install)().then(plugins.install)); core.addPath(path.dirname(binPath)); + if (core.getInput(`debug`)) { + await core.group(`Debug`, () => debugAction(binPath)); + } + const installOnly = core.getBooleanInput(`install-only`, { required: true }); if (installOnly) { return; } - await core.group(`run golangci-lint`, () => runLint(binPath, patchPath)); + await runLint(binPath); } catch (error) { core.error(`Failed to run: ${error}, ${error.stack}`); diff --git a/dist/run/index.js b/dist/run/index.js index 073f718316..98236e9dcf 100644 --- a/dist/run/index.js +++ b/dist/run/index.js @@ -96844,8 +96844,10 @@ async function buildCacheKeys() { return keys; } async function restoreCache() { - if (core.getBooleanInput(`skip-cache`, { required: true })) + if (core.getBooleanInput(`skip-cache`, { required: true })) { + core.info(`Skipping cache restoration`); return; + } if (!utils.isValidEvent()) { utils.logWarning(`Event Validation Error: The event type ${process.env[constants_1.Events.Key]} is not supported because it's not tied to a branch or tag ref.`); return; @@ -96881,10 +96883,14 @@ async function restoreCache() { } } async function saveCache() { - if (core.getBooleanInput(`skip-cache`, { required: true })) + if (core.getBooleanInput(`skip-cache`, { required: true })) { + core.info(`Skipping cache saving`); return; - if (core.getBooleanInput(`skip-save-cache`, { required: true })) + } + if (core.getBooleanInput(`skip-save-cache`, { required: true })) { + core.info(`Skipping cache saving`); return; + } // Validate inputs, this can cause task failure if (!utils.isValidEvent()) { utils.logWarning(`Event Validation Error: The event type ${process.env[constants_1.Events.Key]} is not supported because it's not tied to a branch or tag ref.`); @@ -96999,12 +97005,13 @@ exports.installBinary = installBinary; const core = __importStar(__nccwpck_require__(37484)); const tc = __importStar(__nccwpck_require__(33472)); const child_process_1 = __nccwpck_require__(35317); +const fs_1 = __importDefault(__nccwpck_require__(79896)); const os_1 = __importDefault(__nccwpck_require__(70857)); const path_1 = __importDefault(__nccwpck_require__(16928)); const util_1 = __nccwpck_require__(39023); const which_1 = __importDefault(__nccwpck_require__(11189)); const version_1 = __nccwpck_require__(311); -const execShellCommand = (0, util_1.promisify)(child_process_1.exec); +const execCommand = (0, util_1.promisify)(child_process_1.exec); var InstallMode; (function (InstallMode) { InstallMode["Binary"] = "binary"; @@ -97018,6 +97025,7 @@ const printOutput = (res) => { if (res.stderr) { core.info(res.stderr); } + return res; }; /** * Install golangci-lint. @@ -97025,6 +97033,15 @@ const printOutput = (res) => { * @returns path to installed binary of golangci-lint. */ async function install() { + const problemMatchers = core.getBooleanInput(`problem-matchers`); + if (problemMatchers) { + const matchersPath = path_1.default.join(__dirname, "../..", "problem-matchers.json"); + if (fs_1.default.existsSync(matchersPath)) { + // Adds problem matchers. + // https://github.com/actions/setup-go/blob/cdcb36043654635271a94b9a6d1392de5bb323a7/src/main.ts#L81-L83 + core.info(`##[add-matcher]${matchersPath}`); + } + } const mode = core.getInput("install-mode").toLowerCase(); if (mode === InstallMode.None) { const binPath = await (0, which_1.default)("golangci-lint", { nothrow: true }); @@ -97064,10 +97081,8 @@ async function goInstall(versionInfo) { core.info(`Installing golangci-lint ${versionInfo.TargetVersion}...`); const startedAt = Date.now(); const options = { env: { ...process.env, CGO_ENABLED: "1" } }; - const exres = await execShellCommand(`go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@${versionInfo.TargetVersion}`, options); - printOutput(exres); - const res = await execShellCommand(`go install -n github.com/golangci/golangci-lint/v2/cmd/golangci-lint@${versionInfo.TargetVersion}`, options); - printOutput(res); + await execCommand(`go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@${versionInfo.TargetVersion}`, options).then(printOutput); + const res = await execCommand(`go install -n github.com/golangci/golangci-lint/v2/cmd/golangci-lint@${versionInfo.TargetVersion}`, options).then(printOutput); // The output of `go install -n` when the binary is already installed is `touch `. const binPath = res.stderr .split(/\r?\n/) @@ -97196,9 +97211,6 @@ function isOnlyNewIssues() { return core.getBooleanInput(`only-new-issues`, { required: true }); } async function fetchPatch() { - if (!isOnlyNewIssues()) { - return ``; - } const ctx = github.context; switch (ctx.eventName) { case `pull_request`: @@ -97242,8 +97254,7 @@ async function fetchPullRequestPatch(ctx) { return ``; // don't fail the action, but analyze without patch } try { - const tempDir = await createTempDir(); - const patchPath = path_1.default.join(tempDir, "pull.patch"); + const patchPath = await createTempDir().then((tempDir) => path_1.default.join(tempDir, "pull.patch")); core.info(`Writing patch to ${patchPath}`); await writeFile(patchPath, (0, diffUtils_1.alterDiffPatch)(patch)); return patchPath; @@ -97277,8 +97288,7 @@ async function fetchPushPatch(ctx) { return ``; // don't fail the action, but analyze without patch } try { - const tempDir = await createTempDir(); - const patchPath = path_1.default.join(tempDir, "push.patch"); + const patchPath = await createTempDir().then((tempDir) => path_1.default.join(tempDir, "push.patch")); core.info(`Writing patch to ${patchPath}`); await writeFile(patchPath, (0, diffUtils_1.alterDiffPatch)(patch)); return patchPath; @@ -97341,7 +97351,7 @@ const fs = __importStar(__nccwpck_require__(79896)); const path = __importStar(__nccwpck_require__(16928)); const util_1 = __nccwpck_require__(39023); const yaml_1 = __importDefault(__nccwpck_require__(38815)); -const execShellCommand = (0, util_1.promisify)(child_process_1.exec); +const execCommand = (0, util_1.promisify)(child_process_1.exec); const printOutput = (res) => { if (res.stdout) { core.info(res.stdout); @@ -97366,7 +97376,7 @@ async function install(binPath) { .map((filename) => path.join(rootDir, filename)) .find((filePath) => fs.existsSync(filePath)); if (!configFile || configFile === "") { - return ""; + return binPath; } core.info(`Found configuration for the plugin module system : ${configFile}`); core.info(`Building and installing custom golangci-lint binary...`); @@ -97388,18 +97398,16 @@ async function install(binPath) { } const cmd = `${binPath} custom`; core.info(`Running [${cmd}] in [${rootDir}] ...`); - try { - const options = { - cwd: rootDir, - }; - const res = await execShellCommand(cmd, options); - printOutput(res); - core.info(`Built custom golangci-lint binary in ${Date.now() - startedAt}ms`); - return path.join(rootDir, config.destination, config.name); - } - catch (exc) { + const options = { + cwd: rootDir, + }; + return execCommand(cmd, options) + .then(printOutput) + .then(() => core.info(`Built custom golangci-lint binary in ${Date.now() - startedAt}ms`)) + .then(() => path.join(rootDir, config.destination, config.name)) + .catch((exc) => { throw new Error(`Failed to build custom golangci-lint binary: ${exc.message}`); - } + }); } @@ -97456,24 +97464,7 @@ const cache_1 = __nccwpck_require__(97377); const install_1 = __nccwpck_require__(90232); const patch_1 = __nccwpck_require__(47161); const plugins = __importStar(__nccwpck_require__(96067)); -const execShellCommand = (0, util_1.promisify)(child_process_1.exec); -async function prepareEnv(installOnly) { - const startedAt = Date.now(); - // Prepare cache, lint and go in parallel. - await (0, cache_1.restoreCache)(); - let binPath = await (0, install_1.install)(); - // Build custom golangci-lint if needed. - const customBinPath = await plugins.install(binPath); - if (customBinPath !== "") { - binPath = customBinPath; - } - if (installOnly) { - return { binPath, patchPath: `` }; - } - const patchPath = await (0, patch_1.fetchPatch)(); - core.info(`Prepared env in ${Date.now() - startedAt}ms`); - return { binPath, patchPath }; -} +const execCommand = (0, util_1.promisify)(child_process_1.exec); const printOutput = (res) => { if (res.stdout) { core.info(res.stdout); @@ -97482,12 +97473,7 @@ const printOutput = (res) => { core.info(res.stderr); } }; -async function runLint(binPath, patchPath) { - const debug = core.getInput(`debug`); - if (debug.split(`,`).includes(`cache`)) { - const res = await execShellCommand(`${binPath} cache status`); - printOutput(res); - } +async function runGolangciLint(binPath, rootDir) { const userArgs = core.getInput(`args`); const addedArgs = []; const userArgsList = userArgs @@ -97499,15 +97485,6 @@ async function runLint(binPath, patchPath) { .map(([key, value]) => [key.toLowerCase(), value ?? ""]); const userArgsMap = new Map(userArgsList); const userArgNames = new Set(userArgsList.map(([key]) => key)); - const problemMatchers = core.getBooleanInput(`problem-matchers`); - if (problemMatchers) { - const matchersPath = path.join(__dirname, "../..", "problem-matchers.json"); - if (fs.existsSync(matchersPath)) { - // Adds problem matchers. - // https://github.com/actions/setup-go/blob/cdcb36043654635271a94b9a6d1392de5bb323a7/src/main.ts#L81-L83 - core.info(`##[add-matcher]${matchersPath}`); - } - } if ((0, patch_1.isOnlyNewIssues)()) { if (userArgNames.has(`new`) || userArgNames.has(`new-from-rev`) || @@ -97516,6 +97493,7 @@ async function runLint(binPath, patchPath) { throw new Error(`please, don't specify manually --new* args when requesting only new issues`); } const ctx = github.context; + const patchPath = await (0, patch_1.fetchPatch)(); core.info(`only new issues on ${ctx.eventName}: ${patchPath}`); switch (ctx.eventName) { case `pull_request`: @@ -97541,27 +97519,21 @@ async function runLint(binPath, patchPath) { } } const cmdArgs = {}; - const workingDirectory = core.getInput(`working-directory`); - if (workingDirectory) { - if (!fs.existsSync(workingDirectory) || !fs.lstatSync(workingDirectory).isDirectory()) { - throw new Error(`working-directory (${workingDirectory}) was not a path`); - } + if (rootDir) { if (!userArgNames.has(`path-prefix`) && !userArgNames.has(`path-mode`)) { addedArgs.push(`--path-mode=abs`); } - cmdArgs.cwd = path.resolve(workingDirectory); + cmdArgs.cwd = path.resolve(rootDir); } await runVerify(binPath, userArgsMap, cmdArgs); const cmd = `${binPath} run ${addedArgs.join(` `)} ${userArgs}`.trimEnd(); core.info(`Running [${cmd}] in [${cmdArgs.cwd || process.cwd()}] ...`); const startedAt = Date.now(); - try { - const res = await execShellCommand(cmd, cmdArgs); - printOutput(res); - core.info(`golangci-lint found no issues`); - } - catch (exc) { - // This logging passes issues to GitHub annotations but comments can be more convenient for some users. + return execCommand(cmd, cmdArgs) + .then(printOutput) + .then(() => core.info(`golangci-lint found no issues`)) + .catch((exc) => { + // This logging passes issues to GitHub annotations. printOutput(exc); if (exc.code === 1) { core.setFailed(`issues found`); @@ -97569,8 +97541,8 @@ async function runLint(binPath, patchPath) { else { core.setFailed(`golangci-lint exit with code ${exc.code}`); } - } - core.info(`Ran golangci-lint in ${Date.now() - startedAt}ms`); + }) + .finally(() => core.info(`Ran golangci-lint in ${Date.now() - startedAt}ms`)); } async function runVerify(binPath, userArgsMap, cmdArgs) { const verify = core.getBooleanInput(`verify`, { required: true }); @@ -97586,8 +97558,7 @@ async function runVerify(binPath, userArgsMap, cmdArgs) { cmdVerify += ` --config=${userArgsMap.get("config")}`; } core.info(`Running [${cmdVerify}] in [${cmdArgs.cwd || process.cwd()}] ...`); - const res = await execShellCommand(cmdVerify, cmdArgs); - printOutput(res); + await execCommand(cmdVerify, cmdArgs).then(printOutput); } async function getConfigPath(binPath, userArgsMap, cmdArgs) { let cmdConfigPath = `${binPath} config path`; @@ -97596,22 +97567,73 @@ async function getConfigPath(binPath, userArgsMap, cmdArgs) { } core.info(`Running [${cmdConfigPath}] in [${cmdArgs.cwd || process.cwd()}] ...`); try { - const resPath = await execShellCommand(cmdConfigPath, cmdArgs); + const resPath = await execCommand(cmdConfigPath, cmdArgs); return resPath.stderr.trim(); } catch { return ``; } } +async function debugAction(binPath) { + const flags = core.getInput(`debug`).split(`,`); + if (flags.includes(`clean`)) { + const cmd = `${binPath} cache clean`; + core.info(`Running [${cmd}] ...`); + await execCommand(cmd).then(printOutput); + } + if (flags.includes(`cache`)) { + const cmd = `${binPath} cache status`; + core.info(`Running [${cmd}] ...`); + await execCommand(cmd).then(printOutput); + } +} +function getWorkingDirectory() { + const workingDirectory = core.getInput(`working-directory`); + if (workingDirectory) { + if (!fs.existsSync(workingDirectory) || !fs.lstatSync(workingDirectory).isDirectory()) { + throw new Error(`working-directory (${workingDirectory}) was not a path`); + } + } + return workingDirectory; +} +function modulesAutoDetection(rootDir) { + const o = { + cwd: rootDir, + exclude: ["**/vendor/**", "**/node_modules/**", "**/.git/**", "**/dist/**"], + }; + const matches = fs.globSync("**/go.mod", o); + const dirs = matches + .filter((m) => typeof m === "string") + .map((m) => path.resolve(rootDir, path.dirname(m))) + .sort(); + return [...new Set(dirs)]; +} +async function runLint(binPath) { + const workingDirectory = getWorkingDirectory(); + const experimental = core.getInput(`experimental`).split(`,`); + if (experimental.includes(`automatic-module-directories`)) { + const wds = modulesAutoDetection(workingDirectory); + const cwd = process.cwd(); + for (const wd of wds) { + await core.group(`run golangci-lint in ${path.relative(cwd, wd)}`, () => runGolangciLint(binPath, wd)); + } + return; + } + await core.group(`run golangci-lint`, () => runGolangciLint(binPath, workingDirectory)); +} async function run() { try { - const installOnly = core.getBooleanInput(`install-only`, { required: true }); - const { binPath, patchPath } = await core.group(`prepare environment`, () => prepareEnv(installOnly)); + await core.group(`Restore cache`, cache_1.restoreCache); + const binPath = await core.group(`Install`, () => (0, install_1.install)().then(plugins.install)); core.addPath(path.dirname(binPath)); + if (core.getInput(`debug`)) { + await core.group(`Debug`, () => debugAction(binPath)); + } + const installOnly = core.getBooleanInput(`install-only`, { required: true }); if (installOnly) { return; } - await core.group(`run golangci-lint`, () => runLint(binPath, patchPath)); + await runLint(binPath); } catch (error) { core.error(`Failed to run: ${error}, ${error.stack}`);