@kjanat/actionlint 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +22 -0
- package/README.md +105 -0
- package/bin/actionlint.mjs +4 -0
- package/lib/launch.mjs +34 -0
- package/lib/resolve.mjs +185 -0
- package/man/actionlint.1 +510 -0
- package/package.json +75 -0
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
the MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kaj Kowalski
|
|
4
|
+
Copyright (c) 2021 rhysd
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
10
|
+
of the Software, and to permit persons to whom the Software is furnished to do so,
|
|
11
|
+
subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
17
|
+
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
18
|
+
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
19
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
20
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
|
|
21
|
+
THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
22
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# @kjanat/actionlint
|
|
2
|
+
|
|
3
|
+
Static checker for GitHub Actions workflow files, distributed as a prebuilt binary.
|
|
4
|
+
|
|
5
|
+
This is the npm distribution of [`kjanat/actionlint`](https://github.com/kjanat/actionlint), a fork of
|
|
6
|
+
[rhysd/actionlint](https://github.com/rhysd/actionlint). Installing it puts an `actionlint` executable on your `PATH`; no
|
|
7
|
+
Go toolchain is needed.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install --save-dev @kjanat/actionlint
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or run it without installing:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npx @kjanat/actionlint
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
Run it in a repository and it finds the workflows itself:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
npx actionlint
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
As a package script:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"scripts": {
|
|
34
|
+
"lint:workflows": "actionlint"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`actionlint` exits `0` when it finds nothing, `1` when it reports problems, and `2` or `3` on a usage error or a fatal
|
|
40
|
+
error. Those statuses are forwarded verbatim, so it drops into CI unchanged.
|
|
41
|
+
|
|
42
|
+
See the [usage documentation](https://github.com/kjanat/actionlint/blob/master/docs/usage.md) for the full command line,
|
|
43
|
+
and [the checks list](https://github.com/kjanat/actionlint/blob/master/docs/checks.md) for what it looks for.
|
|
44
|
+
|
|
45
|
+
The manual page ships in the package as `man/actionlint.1`. npm registered man pages with the system `man` program up to
|
|
46
|
+
v11; from v12 it no longer does, so on a current npm read it directly:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
man ./node_modules/@kjanat/actionlint/man/actionlint.1
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### ShellCheck and Pyflakes
|
|
53
|
+
|
|
54
|
+
`actionlint` also checks the shell scripts inside `run:` steps with [ShellCheck][shellcheck], and Python scripts with
|
|
55
|
+
[Pyflakes][pyflakes], when those are on your `PATH`. Neither is bundled here; install them separately to enable those
|
|
56
|
+
checks.
|
|
57
|
+
|
|
58
|
+
## How this package is put together
|
|
59
|
+
|
|
60
|
+
This package contains no binary itself. It declares one `optionalDependencies` entry per platform, each published
|
|
61
|
+
under the `@kjanat-actionlint` scope so the binaries stay out of the `@kjanat` namespace:
|
|
62
|
+
|
|
63
|
+
| Package | Runs on |
|
|
64
|
+
| -------------------------------------------- | --------------------- |
|
|
65
|
+
| `@kjanat-actionlint/actionlint-linux-x64` | Linux x86-64 |
|
|
66
|
+
| `@kjanat-actionlint/actionlint-linux-arm64` | Linux ARM64 |
|
|
67
|
+
| `@kjanat-actionlint/actionlint-darwin-x64` | macOS Intel |
|
|
68
|
+
| `@kjanat-actionlint/actionlint-darwin-arm64` | macOS Apple silicon |
|
|
69
|
+
| `@kjanat-actionlint/actionlint-win32-x64` | Windows x86-64 |
|
|
70
|
+
| `@kjanat-actionlint/actionlint-win32-arm64` | Windows ARM64 |
|
|
71
|
+
| `@kjanat-actionlint/actionlint-linux-ia32` | Linux 32-bit x86 |
|
|
72
|
+
| `@kjanat-actionlint/actionlint-linux-arm` | Linux ARMv6 and ARMv7 |
|
|
73
|
+
| `@kjanat-actionlint/actionlint-win32-ia32` | Windows 32-bit x86 |
|
|
74
|
+
| `@kjanat-actionlint/actionlint-freebsd-x64` | FreeBSD x86-64 |
|
|
75
|
+
| `@kjanat-actionlint/actionlint-freebsd-ia32` | FreeBSD 32-bit x86 |
|
|
76
|
+
|
|
77
|
+
Each declares `os` and `cpu`, so your package manager downloads only the one matching your machine and skips the rest.
|
|
78
|
+
The `actionlint` command here is a small launcher that resolves that package and execs the binary inside it.
|
|
79
|
+
|
|
80
|
+
There is no separate musl build: the binaries are statically linked, so the `linux-*` packages run on Alpine and on
|
|
81
|
+
glibc distributions alike.
|
|
82
|
+
|
|
83
|
+
The binaries are the same ones attached to the [GitHub release][releases]; each archive is verified against the
|
|
84
|
+
release's published checksums before being repackaged.
|
|
85
|
+
|
|
86
|
+
### If the binary is not found
|
|
87
|
+
|
|
88
|
+
The launcher fails with an explanation, but the usual cause is a package manager that skipped optional dependencies.
|
|
89
|
+
Reinstall without `--no-optional` or `--omit=optional`.
|
|
90
|
+
|
|
91
|
+
Using Bun with `minimumReleaseAge`? Add `@kjanat-actionlint/*` to `minimumReleaseAgeExcludes` alongside
|
|
92
|
+
`@kjanat/actionlint`. A fresh release otherwise installs the facade while its binaries are still age-gated.
|
|
93
|
+
|
|
94
|
+
## Other ways to install
|
|
95
|
+
|
|
96
|
+
Homebrew, Arch (AUR), Scoop, Docker, a download script, and `go install` are all covered in
|
|
97
|
+
[the installation documentation](https://github.com/kjanat/actionlint/blob/master/docs/install.md).
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
MIT. See [LICENSE.txt](./LICENSE.txt).
|
|
102
|
+
|
|
103
|
+
[pyflakes]: https://pypi.org/project/pyflakes/
|
|
104
|
+
[releases]: https://github.com/kjanat/actionlint/releases
|
|
105
|
+
[shellcheck]: https://www.shellcheck.net/
|
package/lib/launch.mjs
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { resolveBinary } from '#resolve';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
|
|
5
|
+
const { argv, exit, stderr } = process;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Run the prebuilt binary for this host, forwarding argv, stdio and exit status.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} name - Base name of the executable, without any extension.
|
|
11
|
+
* @returns {void}
|
|
12
|
+
*/
|
|
13
|
+
export default function launch(name) {
|
|
14
|
+
try {
|
|
15
|
+
const result = spawnSync(resolveBinary(name), argv.slice(2), {
|
|
16
|
+
stdio: 'inherit',
|
|
17
|
+
windowsHide: false,
|
|
18
|
+
});
|
|
19
|
+
if (result.error) throw result.error;
|
|
20
|
+
// Re-raise the child's signal on ourselves; `set -e`, traps and Ctrl+C
|
|
21
|
+
// chaining read WIFSIGNALED / 128 + N. POSIX only, Windows falls through.
|
|
22
|
+
if (result.signal && process.platform !== 'win32') {
|
|
23
|
+
process.removeAllListeners(result.signal);
|
|
24
|
+
process.kill(process.pid, result.signal);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// actionlint exits 1 when it finds problems and 2/3 on bad usage or a
|
|
28
|
+
// fatal error, so the status has to be forwarded verbatim.
|
|
29
|
+
exit(result.status ?? 1);
|
|
30
|
+
} catch (err) {
|
|
31
|
+
stderr.write(`${name}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
32
|
+
exit(1);
|
|
33
|
+
}
|
|
34
|
+
}
|
package/lib/resolve.mjs
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { cyan, link, red, yellow } from 'ansispeck';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { arch, platform } from 'node:process';
|
|
6
|
+
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const { bugs, name: pkgName, optionalDependencies } = require('#pkg');
|
|
9
|
+
|
|
10
|
+
const issues = bugs.url;
|
|
11
|
+
const declared = Object.keys(optionalDependencies || {});
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* `existsSync` that never throws; a locked-down root can raise on a stat.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} path
|
|
17
|
+
* @returns {boolean}
|
|
18
|
+
*/
|
|
19
|
+
function exists(path) {
|
|
20
|
+
try {
|
|
21
|
+
return existsSync(path);
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Name of the platform package holding the build for a host.
|
|
29
|
+
*
|
|
30
|
+
* The `<scope>/<binary>-<os>-<cpu>` convention is the contract. The platform
|
|
31
|
+
* packages live in their own scope, apart from the facade.
|
|
32
|
+
*
|
|
33
|
+
* There is no libc dimension. The release binaries are built with
|
|
34
|
+
* `CGO_ENABLED=0`, so one `linux-<cpu>` package covers glibc and musl hosts,
|
|
35
|
+
* Alpine included. A Rust equivalent has to detect the host libc and pick
|
|
36
|
+
* between a gnu and a musl build.
|
|
37
|
+
*
|
|
38
|
+
* @param {string} scope - npm scope the platform packages are published under.
|
|
39
|
+
* @param {string} binary - Base name of the executable.
|
|
40
|
+
* @param {string} os - Node `platform` string.
|
|
41
|
+
* @param {string} cpu - Node `arch` string.
|
|
42
|
+
* @returns {string}
|
|
43
|
+
*/
|
|
44
|
+
export function platformPackage(scope, binary, os, cpu) {
|
|
45
|
+
return `${scope}/${binary}-${os}-${cpu}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Find the declared platform package for a host by its `-<os>-<cpu>` tail.
|
|
50
|
+
*
|
|
51
|
+
* The scope comes from the declaration itself; the facade carries no copy of
|
|
52
|
+
* it. Matching is by name, independent of `optionalDependencies` order.
|
|
53
|
+
*
|
|
54
|
+
* @param {readonly string[]} packages - Declared platform packages.
|
|
55
|
+
* @param {string} binary - Base name of the executable.
|
|
56
|
+
* @param {string} os
|
|
57
|
+
* @param {string} cpu
|
|
58
|
+
* @returns {string | undefined}
|
|
59
|
+
*/
|
|
60
|
+
function declaredPackage(packages, binary, os, cpu) {
|
|
61
|
+
const suffix = `/${binary}-${os}-${cpu}`;
|
|
62
|
+
return packages.find((pkg) => pkg.endsWith(suffix));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Report that this platform has no build at all, then throw.
|
|
67
|
+
*
|
|
68
|
+
* @param {string} os
|
|
69
|
+
* @param {string} cpu
|
|
70
|
+
* @param {readonly string[]} packages
|
|
71
|
+
* @returns {never}
|
|
72
|
+
*/
|
|
73
|
+
function failUnsupported(os, cpu, packages) {
|
|
74
|
+
const indent = ' ';
|
|
75
|
+
const supported = packages
|
|
76
|
+
.map((pkg) => pkg.slice(pkg.indexOf('/') + 1).replace(/^[^-]+-/, ''))
|
|
77
|
+
.sort()
|
|
78
|
+
.join(', ');
|
|
79
|
+
|
|
80
|
+
console.error(
|
|
81
|
+
`${red(pkgName)}: no build is published for ${yellow(`${os}-${cpu}`)}.
|
|
82
|
+
|
|
83
|
+
Published platforms: ${cyan(supported)}
|
|
84
|
+
|
|
85
|
+
Workarounds:
|
|
86
|
+
${indent}- build from source, which works anywhere Go does: ${
|
|
87
|
+
cyan('go install actionlint.kjanat.dev/cmd/actionlint@latest')
|
|
88
|
+
}
|
|
89
|
+
${indent}- ask for this platform: ${link(issues, issues)}
|
|
90
|
+
`,
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
throw new Error(`No published build for ${os}-${cpu}.`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Report that the matching platform package is unusable, then throw.
|
|
98
|
+
*
|
|
99
|
+
* The two ways it can be unusable need different fixes (install it, reinstall
|
|
100
|
+
* it), and `summary` carries that into the thrown error as well as the
|
|
101
|
+
* diagnostic block. A stack trace or CI log may keep only the thrown message.
|
|
102
|
+
*
|
|
103
|
+
* @param {string} wanted
|
|
104
|
+
* @param {string} detail - Why it is unusable, shown in the diagnostic.
|
|
105
|
+
* @param {string} summary - One-line reason carried by the thrown error.
|
|
106
|
+
* @returns {never}
|
|
107
|
+
*/
|
|
108
|
+
function failNotInstalled(wanted, detail, summary) {
|
|
109
|
+
const indent = ' ';
|
|
110
|
+
|
|
111
|
+
console.error(
|
|
112
|
+
`${red(pkgName)}: the platform package for this host is not usable.
|
|
113
|
+
|
|
114
|
+
Expected package: ${cyan(wanted)}
|
|
115
|
+
${indent}- ${detail}
|
|
116
|
+
|
|
117
|
+
This usually means your package manager skipped ${cyan('optionalDependencies')}
|
|
118
|
+
(common with ${cyan('--no-optional')}, ${cyan('--omit=optional')}, or some Docker and CI setups).
|
|
119
|
+
|
|
120
|
+
Workarounds:
|
|
121
|
+
${indent}- reinstall without ${cyan('--no-optional')} / ${cyan('--omit=optional')}
|
|
122
|
+
${indent}- install it explicitly: ${cyan(`npm install ${wanted}`)}
|
|
123
|
+
${indent}- bun + ${cyan('minimumReleaseAge')}: add ${cyan(`${wanted.slice(0, wanted.indexOf('/'))}/*`)} alongside ${
|
|
124
|
+
cyan(pkgName)
|
|
125
|
+
} in ${cyan('minimumReleaseAgeExcludes')}; a fresh release is otherwise age-gated
|
|
126
|
+
${indent}- build from source instead: ${cyan('go install actionlint.kjanat.dev/cmd/actionlint@latest')}
|
|
127
|
+
${indent}- file an issue: ${link(issues, issues)}
|
|
128
|
+
`,
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
throw new Error(`${summary} (${wanted})`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Locate the prebuilt executable matching the current platform and architecture.
|
|
136
|
+
*
|
|
137
|
+
* @param {string} name - Base name of the executable, without any extension.
|
|
138
|
+
* @param {object} [context] - Host details to resolve against; defaults to this process.
|
|
139
|
+
* @param {string} [context.platform] - Node `platform` string.
|
|
140
|
+
* @param {string} [context.arch] - Node `arch` string.
|
|
141
|
+
* @param {readonly string[]} [context.packages] - Declared platform packages.
|
|
142
|
+
* @param {(pkg: string) => string} [context.resolvePackageJson]
|
|
143
|
+
* @param {(path: string) => boolean} [context.fileExists]
|
|
144
|
+
* @returns {string} Filesystem path to the executable.
|
|
145
|
+
* @throws {Error} If no usable binary is installed for this host.
|
|
146
|
+
*/
|
|
147
|
+
export function resolveBinary(name, context = {}) {
|
|
148
|
+
const {
|
|
149
|
+
platform: hostPlatform = platform,
|
|
150
|
+
arch: hostArch = arch,
|
|
151
|
+
packages = declared,
|
|
152
|
+
resolvePackageJson = (pkg) => require.resolve(`${pkg}/package.json`),
|
|
153
|
+
fileExists = exists,
|
|
154
|
+
} = context;
|
|
155
|
+
|
|
156
|
+
const wanted = declaredPackage(packages, name, hostPlatform, hostArch);
|
|
157
|
+
// Absent from the manifest means no build exists for this host at all,
|
|
158
|
+
// which is a different problem from one that exists but was not installed.
|
|
159
|
+
if (!wanted) failUnsupported(hostPlatform, hostArch, packages);
|
|
160
|
+
|
|
161
|
+
let pkgJsonPath;
|
|
162
|
+
try {
|
|
163
|
+
pkgJsonPath = resolvePackageJson(wanted);
|
|
164
|
+
} catch (err) {
|
|
165
|
+
failNotInstalled(
|
|
166
|
+
wanted,
|
|
167
|
+
`not installed (${err instanceof Error ? err.message.split('\n')[0] : String(err)})`,
|
|
168
|
+
'The platform package is not installed',
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const exe = hostPlatform === 'win32' ? `${name}.exe` : name;
|
|
173
|
+
const binPath = join(dirname(pkgJsonPath), 'bin', exe);
|
|
174
|
+
// Resolving package.json proves the package exists; the binary is separate.
|
|
175
|
+
// A half-succeeded install or a hand-deleted bin makes them disagree.
|
|
176
|
+
if (!fileExists(binPath)) {
|
|
177
|
+
failNotInstalled(
|
|
178
|
+
wanted,
|
|
179
|
+
`installed, but its binary is missing at ${binPath}`,
|
|
180
|
+
'The platform package is installed but its binary is missing',
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return binPath;
|
|
185
|
+
}
|
package/man/actionlint.1
ADDED
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
.\" Automatically generated by Pandoc 3.11
|
|
2
|
+
.\"
|
|
3
|
+
.TH "ACTIONLINT" "1" "" "actionlint 1.15.0" "General Commands Manual"
|
|
4
|
+
.SH NAME
|
|
5
|
+
\f[B]actionlint\f[R] \- static checker for GitHub Actions workflow files
|
|
6
|
+
.SH SYNOPSIS
|
|
7
|
+
\f[B]actionlint\f[R] [\f[I]flags\f[R]]
|
|
8
|
+
.PD 0
|
|
9
|
+
.P
|
|
10
|
+
.PD
|
|
11
|
+
\f[B]actionlint\f[R] [\f[I]flags\f[R]] \f[I]file\f[R]...
|
|
12
|
+
.PD 0
|
|
13
|
+
.P
|
|
14
|
+
.PD
|
|
15
|
+
\f[B]actionlint\f[R] [\f[I]flags\f[R]] \-
|
|
16
|
+
.SH DESCRIPTION
|
|
17
|
+
\f[B]actionlint\f[R] checks GitHub Actions workflow files without
|
|
18
|
+
executing the workflows.
|
|
19
|
+
This is the maintained \f[B]kjanat/actionlint\f[R] fork, distributed as
|
|
20
|
+
the Go module \f[B]actionlint.kjanat.dev\f[R].
|
|
21
|
+
.PP
|
|
22
|
+
Checks include workflow syntax, expression types and context
|
|
23
|
+
availability, action inputs and outputs, local action metadata and
|
|
24
|
+
composite steps, reusable workflow inputs and permissions, job
|
|
25
|
+
dependencies, parallel steps, runner labels, event filters, schedules,
|
|
26
|
+
and YAML anchors.
|
|
27
|
+
Security checks report potentially unsafe expression interpolation in
|
|
28
|
+
scripts and hard\-coded credentials.
|
|
29
|
+
Optional ShellCheck and pyflakes integrations check scripts in
|
|
30
|
+
\f[B]run:\f[R] steps.
|
|
31
|
+
.PP
|
|
32
|
+
Repository policy checks can require immutable action references, job
|
|
33
|
+
timeouts, and particular actions.
|
|
34
|
+
These checks are enabled explicitly in the configuration file; ordinary
|
|
35
|
+
workflow checks do not require configuration.
|
|
36
|
+
.SH USAGE
|
|
37
|
+
With no file arguments, \f[B]actionlint\f[R] searches the current
|
|
38
|
+
directory and its parents for a repository containing both
|
|
39
|
+
\f[B].git\f[R] and \f[B].github/workflows\f[R].
|
|
40
|
+
It recursively checks \f[B].yml\f[R] and \f[B].yaml\f[R] files in that
|
|
41
|
+
workflow directory:
|
|
42
|
+
.IP
|
|
43
|
+
.EX
|
|
44
|
+
$ actionlint
|
|
45
|
+
.EE
|
|
46
|
+
.PP
|
|
47
|
+
To check specific workflow files, pass their paths.
|
|
48
|
+
Explicit files can be outside a repository:
|
|
49
|
+
.IP
|
|
50
|
+
.EX
|
|
51
|
+
$ actionlint file1.yaml file2.yaml
|
|
52
|
+
.EE
|
|
53
|
+
.PP
|
|
54
|
+
Pass \f[B]\-\f[R] as the only file argument to read a workflow from
|
|
55
|
+
standard input:
|
|
56
|
+
.IP
|
|
57
|
+
.EX
|
|
58
|
+
$ actionlint \-
|
|
59
|
+
.EE
|
|
60
|
+
.PP
|
|
61
|
+
Use \f[B]\-stdin\-filename\f[R] to label diagnostics.
|
|
62
|
+
If that path exists in a detected repository, actionlint also uses its
|
|
63
|
+
repository configuration and local action metadata.
|
|
64
|
+
For an unsaved file, select configuration explicitly with
|
|
65
|
+
\f[B]\-config\-file\f[R] when needed:
|
|
66
|
+
.IP
|
|
67
|
+
.EX
|
|
68
|
+
$ actionlint \-stdin\-filename .github/workflows/ci.yml \- < .github/workflows/ci.yml
|
|
69
|
+
$ actionlint \-config\-file .github/actionlint.yaml \- < /tmp/workflow.yml
|
|
70
|
+
.EE
|
|
71
|
+
.PP
|
|
72
|
+
Local action metadata and reusable workflows are read from disk when
|
|
73
|
+
referenced by a workflow.
|
|
74
|
+
Passing an \f[B]action.yml\f[R] file directly does not select a
|
|
75
|
+
standalone action\-metadata lint mode.
|
|
76
|
+
.PP
|
|
77
|
+
Place flags before file arguments.
|
|
78
|
+
Both \f[B]\-flag\f[R] and \f[B]\-\-flag\f[R] spellings are accepted, and
|
|
79
|
+
a value can follow a flag or an equals sign.
|
|
80
|
+
Use \f[B]\-\-\f[R] to end flag parsing before a filename that starts
|
|
81
|
+
with a dash.
|
|
82
|
+
Boolean flags accept \f[B]=true\f[R] or \f[B]=false\f[R].
|
|
83
|
+
.PP
|
|
84
|
+
Use \f[B]\-format\f[R] to serialize diagnostics or customize their
|
|
85
|
+
presentation with Go templates:
|
|
86
|
+
.IP
|
|
87
|
+
.EX
|
|
88
|
+
$ actionlint \-format \(aq{{json .}}\(aq
|
|
89
|
+
.EE
|
|
90
|
+
.SH FLAGS
|
|
91
|
+
.TP
|
|
92
|
+
\f[B]\-color\f[R]
|
|
93
|
+
Force colored output, including when standard output is redirected.
|
|
94
|
+
\f[B]\-no\-color\f[R] takes precedence if both flags are set.
|
|
95
|
+
.TP
|
|
96
|
+
\f[B]\-completion\f[R] \f[I]SHELL\f[R]
|
|
97
|
+
Print a shell completion script for the given shell to stdout.
|
|
98
|
+
One of \f[B]bash\f[R], \f[B]fish\f[R], \f[B]powershell\f[R],
|
|
99
|
+
\f[B]zsh\f[R].
|
|
100
|
+
Also accepted are \f[B]pwsh\f[R], a shell path such as \f[B]$SHELL\f[R],
|
|
101
|
+
and \f[B]auto\f[R] to detect the current shell from \f[B]SHELL\f[R],
|
|
102
|
+
falling back to PowerShell when \f[B]PSModulePath\f[R] is set.
|
|
103
|
+
Prints the script and exits without linting.
|
|
104
|
+
See SHELL COMPLETION below.
|
|
105
|
+
.TP
|
|
106
|
+
\f[B]\-config\-file\f[R] \f[I]PATH\f[R]
|
|
107
|
+
Read configuration from \f[I]PATH\f[R] instead of using the detected
|
|
108
|
+
repository\(aqs configuration.
|
|
109
|
+
Relative paths are resolved from the current working directory.
|
|
110
|
+
.TP
|
|
111
|
+
\f[B]\-debug\f[R]
|
|
112
|
+
Write development diagnostics to standard error.
|
|
113
|
+
Use without \f[B]\-verbose\f[R], which takes precedence when both flags
|
|
114
|
+
are set.
|
|
115
|
+
.TP
|
|
116
|
+
\f[B]\-format\f[R] \f[I]FORMAT\f[R]
|
|
117
|
+
Format diagnostics using a Go text template.
|
|
118
|
+
The template receives a sequence of error objects.
|
|
119
|
+
See OUTPUT below.
|
|
120
|
+
This overrides \f[B]\-oneline\f[R].
|
|
121
|
+
.TP
|
|
122
|
+
\f[B]\-ignore\f[R] \f[I]PATTERN\f[R]
|
|
123
|
+
Suppress diagnostics whose message matches \f[I]PATTERN\f[R], using Go
|
|
124
|
+
regular expression syntax.
|
|
125
|
+
Repeat the flag to match any of several patterns:
|
|
126
|
+
\f[B]\-ignore A \-ignore B\f[R] suppresses messages matching either
|
|
127
|
+
pattern.
|
|
128
|
+
Suppressed diagnostics do not cause exit status 1.
|
|
129
|
+
.TP
|
|
130
|
+
\f[B]\-init\-config\f[R]
|
|
131
|
+
Create \f[B].github/actionlint.yaml\f[R] in the detected repository and
|
|
132
|
+
exit without linting.
|
|
133
|
+
Includes the YAML Language Server schema directive for editor
|
|
134
|
+
completion, hover documentation, and validation.
|
|
135
|
+
Requires a repository with \f[B].github/workflows\f[R] and refuses to
|
|
136
|
+
overwrite either supported configuration filename.
|
|
137
|
+
.TP
|
|
138
|
+
\f[B]\-no\-color\f[R]
|
|
139
|
+
Disable colored output, even when \f[B]\-color\f[R] is also set.
|
|
140
|
+
.TP
|
|
141
|
+
\f[B]\-oneline\f[R]
|
|
142
|
+
Print one line per diagnostic, without the source snippet and position
|
|
143
|
+
marker.
|
|
144
|
+
.TP
|
|
145
|
+
\f[B]\-pyflakes\f[R] \f[I]COMMAND\f[R]
|
|
146
|
+
Command used to check Python \f[B]run:\f[R] scripts.
|
|
147
|
+
Accepts an executable name, a path, or a quoted command line such as
|
|
148
|
+
\f[B]\(dqpython3 \-m pyflakes\(dq\f[R] or
|
|
149
|
+
\f[B]\(dquvx pyflakes\(dq\f[R].
|
|
150
|
+
Defaults to \f[B]pyflakes\f[R]; \f[B]\-pyflakes=\f[R] disables the
|
|
151
|
+
integration.
|
|
152
|
+
.TP
|
|
153
|
+
\f[B]\-shellcheck\f[R] \f[I]COMMAND\f[R]
|
|
154
|
+
Command used to check supported shell \f[B]run:\f[R] scripts.
|
|
155
|
+
Accepts an executable name, a path, or a quoted command line such as
|
|
156
|
+
\f[B]\(dqshellcheck \-e SC2086\(dq\f[R].
|
|
157
|
+
Defaults to \f[B]shellcheck\f[R]; \f[B]\-shellcheck=\f[R] disables the
|
|
158
|
+
integration.
|
|
159
|
+
.TP
|
|
160
|
+
\f[B]\-stdin\-filename\f[R] \f[I]NAME\f[R]
|
|
161
|
+
Filename used for standard\-input diagnostics.
|
|
162
|
+
Defaults to \f[B]<stdin>\f[R].
|
|
163
|
+
An existing path also allows repository discovery; see USAGE above.
|
|
164
|
+
.TP
|
|
165
|
+
\f[B]\-verbose\f[R]
|
|
166
|
+
Write progress information to standard error, including file discovery
|
|
167
|
+
and disabled external linter integrations.
|
|
168
|
+
.TP
|
|
169
|
+
\f[B]\-version\f[R]
|
|
170
|
+
Print the build\(aqs module name and version, installation source, Go
|
|
171
|
+
compiler version, and target operating system and architecture, then
|
|
172
|
+
exit.
|
|
173
|
+
.TP
|
|
174
|
+
\f[B]\-help\f[R], \f[B]\-h\f[R]
|
|
175
|
+
Print command usage and flags to standard error, then exit successfully.
|
|
176
|
+
.SH CONFIGURATION
|
|
177
|
+
Configuration is optional.
|
|
178
|
+
In a detected repository, actionlint reads
|
|
179
|
+
\f[B].github/actionlint.yaml\f[R], or \f[B].github/actionlint.yml\f[R]
|
|
180
|
+
if the first filename is absent.
|
|
181
|
+
\f[B]\-config\-file\f[R] selects a different file; settings are not
|
|
182
|
+
merged with the repository file or a user\-global configuration.
|
|
183
|
+
.PP
|
|
184
|
+
The available settings are:
|
|
185
|
+
.TP
|
|
186
|
+
\f[B]self\-hosted\-runner.labels\f[R]
|
|
187
|
+
Additional runner\-label patterns.
|
|
188
|
+
Patterns use Go \f[B]path.Match\f[R] glob syntax.
|
|
189
|
+
.TP
|
|
190
|
+
\f[B]config\-variables\f[R]
|
|
191
|
+
Allowed names in the \f[B]vars\f[R] context.
|
|
192
|
+
Omitted or \f[B]null\f[R] disables this check; an empty list allows no
|
|
193
|
+
configuration variables.
|
|
194
|
+
.TP
|
|
195
|
+
\f[B]config\-secrets\f[R]
|
|
196
|
+
Allowed secret names, compared case\-insensitively.
|
|
197
|
+
Omitted or \f[B]null\f[R] disables this check.
|
|
198
|
+
The built\-in secrets \f[B]GITHUB_TOKEN\f[R],
|
|
199
|
+
\f[B]ACTIONS_STEP_DEBUG\f[R], and \f[B]ACTIONS_RUNNER_DEBUG\f[R], and
|
|
200
|
+
secrets declared in \f[B]on.workflow_call.secrets\f[R], remain allowed
|
|
201
|
+
even with an empty list.
|
|
202
|
+
.TP
|
|
203
|
+
\f[B]assume\-default\-permissions\f[R]
|
|
204
|
+
Permission assumption for a local reusable workflow call when neither
|
|
205
|
+
the calling job nor its workflow declares \f[B]permissions\f[R].
|
|
206
|
+
Defaults to \f[B]restricted\f[R], which assumes read access to
|
|
207
|
+
\f[B]contents\f[R] and \f[B]packages\f[R].
|
|
208
|
+
\f[B]permissive\f[R] assumes write access; \f[B]id\-token\f[R] still
|
|
209
|
+
requires an explicit grant.
|
|
210
|
+
This setting does not change the repository\(aqs actual GitHub
|
|
211
|
+
permissions.
|
|
212
|
+
.TP
|
|
213
|
+
\f[B]paths\f[R]
|
|
214
|
+
Map repository\-relative glob patterns to configuration.
|
|
215
|
+
Patterns use \f[B]/\f[R] separators and support \f[B]**\f[R] and brace
|
|
216
|
+
alternatives.
|
|
217
|
+
Each entry\(aqs \f[B]ignore\f[R] list contains message regular
|
|
218
|
+
expressions, applied in addition to \f[B]\-ignore\f[R].
|
|
219
|
+
.TP
|
|
220
|
+
\f[B]policy.require\-commit\-hash\f[R]
|
|
221
|
+
When \f[B]true\f[R], require action and reusable workflow references to
|
|
222
|
+
use 40\- or 64\-digit hexadecimal commit IDs, and \f[B]docker://\f[R]
|
|
223
|
+
images to use digests.
|
|
224
|
+
Local references and expression\-based references are skipped.
|
|
225
|
+
.TP
|
|
226
|
+
\f[B]policy.require\-job\-timeout\f[R]
|
|
227
|
+
When \f[B]true\f[R], require \f[B]timeout\-minutes\f[R] on jobs that run
|
|
228
|
+
steps.
|
|
229
|
+
A mapping such as \f[B]{ max\-minutes: 60 }\f[R] also limits literal
|
|
230
|
+
timeout values.
|
|
231
|
+
Jobs calling reusable workflows are skipped; expression\-based timeouts
|
|
232
|
+
are not compared with the maximum.
|
|
233
|
+
.TP
|
|
234
|
+
\f[B]policy.required\-actions\f[R]
|
|
235
|
+
List actions every workflow must use.
|
|
236
|
+
Entries accept name and ref glob patterns, such as
|
|
237
|
+
\f[B]actions/checkout\f[R] or \f[B]my\-org/security\-scan\(atv2*\f[R].
|
|
238
|
+
Only steps directly in the workflow are searched.
|
|
239
|
+
Workflows consisting entirely of reusable workflow calls, or containing
|
|
240
|
+
an expression\-based action reference, are skipped.
|
|
241
|
+
.PP
|
|
242
|
+
Policy checks are off until enabled.
|
|
243
|
+
For example:
|
|
244
|
+
.IP
|
|
245
|
+
.EX
|
|
246
|
+
self\-hosted\-runner: { labels: [linux.2xlarge] }
|
|
247
|
+
config\-variables: [DEFAULT_RUNNER]
|
|
248
|
+
config\-secrets: [DEPLOY_TOKEN]
|
|
249
|
+
assume\-default\-permissions: restricted
|
|
250
|
+
policy:
|
|
251
|
+
require\-commit\-hash: true
|
|
252
|
+
require\-job\-timeout: { max\-minutes: 60 }
|
|
253
|
+
required\-actions: [actions/checkout]
|
|
254
|
+
paths:
|
|
255
|
+
\(dq.github/workflows/**/*.{yml,yaml}\(dq:
|
|
256
|
+
ignore: [\(aqshellcheck reported issue in this script: SC2086:.+\(aq]
|
|
257
|
+
.EE
|
|
258
|
+
.PP
|
|
259
|
+
The repository supplies \f[B]actionlint.schema.json\f[R] for editor
|
|
260
|
+
completion and validation.
|
|
261
|
+
See the configuration document below for the schema URL and full
|
|
262
|
+
matching rules.
|
|
263
|
+
.SH EXTERNAL LINTERS
|
|
264
|
+
ShellCheck checks supported shell scripts in \f[B]run:\f[R] steps;
|
|
265
|
+
pyflakes checks Python scripts.
|
|
266
|
+
The executables must be available through \f[B]PATH\f[R] or the
|
|
267
|
+
corresponding command flag.
|
|
268
|
+
If a command cannot be resolved, its integration is skipped;
|
|
269
|
+
\f[B]\-verbose\f[R] explains why.
|
|
270
|
+
.IP
|
|
271
|
+
.EX
|
|
272
|
+
$ actionlint \-shellcheck= \-pyflakes=
|
|
273
|
+
$ actionlint \-shellcheck \(aqshellcheck \-e SC2086\(aq
|
|
274
|
+
$ actionlint \-pyflakes \(aqpython3 \-m pyflakes\(aq
|
|
275
|
+
.EE
|
|
276
|
+
.PP
|
|
277
|
+
Command strings are parsed into an executable and arguments, not
|
|
278
|
+
executed by a shell.
|
|
279
|
+
Shell pipes and redirections are not supported in these flags.
|
|
280
|
+
Your arguments precede actionlint\(aqs own arguments, so do not supply
|
|
281
|
+
input filenames or override ShellCheck\(aqs output format.
|
|
282
|
+
.PP
|
|
283
|
+
actionlint invokes ShellCheck with \f[B]\-\-norc\f[R] and JSON1 output,
|
|
284
|
+
so \f[B].shellcheckrc\f[R] is not read.
|
|
285
|
+
Use \f[B]\-shellcheck\f[R] arguments or \f[B]SHELLCHECK_OPTS\f[R] for
|
|
286
|
+
ShellCheck options.
|
|
287
|
+
Filter pyflakes diagnostics with \f[B]\-ignore\f[R] or the
|
|
288
|
+
configuration\(aqs \f[B]paths\f[R] entries.
|
|
289
|
+
.SH OUTPUT
|
|
290
|
+
Diagnostics go to standard output.
|
|
291
|
+
Command usage, progress logs, and fatal errors go to standard error.
|
|
292
|
+
The default diagnostic includes the file, line, column, message, rule
|
|
293
|
+
name, and source snippet.
|
|
294
|
+
\f[B]\-oneline\f[R] omits the snippet; \f[B]\-format\f[R] replaces the
|
|
295
|
+
diagnostic presentation.
|
|
296
|
+
.PP
|
|
297
|
+
The Go template receives a sequence of errors.
|
|
298
|
+
Each has \f[B]Message\f[R], \f[B]Snippet\f[R], \f[B]Kind\f[R],
|
|
299
|
+
\f[B]Filepath\f[R], \f[B]Line\f[R], \f[B]Column\f[R], and
|
|
300
|
+
\f[B]EndColumn\f[R] fields.
|
|
301
|
+
Line and column numbers start at 1.
|
|
302
|
+
Custom template functions include \f[B]json\f[R], \f[B]replace\f[R],
|
|
303
|
+
\f[B]toPascalCase\f[R], \f[B]allKinds\f[R], and \f[B]getVersion\f[R].
|
|
304
|
+
.PP
|
|
305
|
+
JSON:
|
|
306
|
+
.IP
|
|
307
|
+
.EX
|
|
308
|
+
$ actionlint \-format \(aq{{json .}}\(aq
|
|
309
|
+
.EE
|
|
310
|
+
.PP
|
|
311
|
+
JSON Lines:
|
|
312
|
+
.IP
|
|
313
|
+
.EX
|
|
314
|
+
$ actionlint \-format \(aq{{range .}}{{json .}}{{end}}\(aq
|
|
315
|
+
.EE
|
|
316
|
+
.PP
|
|
317
|
+
Custom lines:
|
|
318
|
+
.IP
|
|
319
|
+
.EX
|
|
320
|
+
$ actionlint \-format \(aq{{range .}}{{.Filepath}}:{{.Line}}:{{.Column}}: {{.Message}} [{{.Kind}}]\(rsn{{end}}\(aq
|
|
321
|
+
.EE
|
|
322
|
+
.PP
|
|
323
|
+
Backslash escapes such as \f[B]\(rsn\f[R] in the format string are
|
|
324
|
+
expanded before the template is parsed.
|
|
325
|
+
.PP
|
|
326
|
+
For SARIF, pass the contents of \f[B]sarif_template.txt\f[R] from the
|
|
327
|
+
actionlint source repository to \f[B]\-format\f[R], for example in Bash:
|
|
328
|
+
.IP
|
|
329
|
+
.EX
|
|
330
|
+
$ actionlint \-format \(dq$(cat sarif_template.txt)\(dq > actionlint.sarif
|
|
331
|
+
.EE
|
|
332
|
+
.PP
|
|
333
|
+
The CLI\(aqs \f[B]\-format\f[R] accepts template text.
|
|
334
|
+
The GitHub Action\(aqs \f[B]format\f[R] input instead accepts names such
|
|
335
|
+
as \f[B]json\f[R] and \f[B]sarif\f[R].
|
|
336
|
+
Changing output format does not change the lint exit status.
|
|
337
|
+
.SH SHELL COMPLETION
|
|
338
|
+
\f[B]\-completion\f[R] generates native completion scripts for Bash,
|
|
339
|
+
Fish, Zsh, and PowerShell.
|
|
340
|
+
Scripts complete flags, flag values, and workflow paths.
|
|
341
|
+
Load one into the current shell session:
|
|
342
|
+
.PP
|
|
343
|
+
Bash:
|
|
344
|
+
.IP
|
|
345
|
+
.EX
|
|
346
|
+
$ source <(actionlint \-completion bash)
|
|
347
|
+
.EE
|
|
348
|
+
.PP
|
|
349
|
+
Fish:
|
|
350
|
+
.IP
|
|
351
|
+
.EX
|
|
352
|
+
$ actionlint \-completion fish | source
|
|
353
|
+
.EE
|
|
354
|
+
.PP
|
|
355
|
+
Zsh, after initializing its completion system:
|
|
356
|
+
.IP
|
|
357
|
+
.EX
|
|
358
|
+
$ autoload \-Uz compinit && compinit
|
|
359
|
+
$ source <(actionlint \-completion zsh)
|
|
360
|
+
.EE
|
|
361
|
+
.PP
|
|
362
|
+
PowerShell:
|
|
363
|
+
.IP
|
|
364
|
+
.EX
|
|
365
|
+
actionlint \-completion powershell | Out\-String | Invoke\-Expression
|
|
366
|
+
.EE
|
|
367
|
+
.PP
|
|
368
|
+
For persistent installation, save Bash output in a directory loaded by
|
|
369
|
+
bash\-completion, Fish output as
|
|
370
|
+
\f[B]\(ti/.config/fish/completions/actionlint.fish\f[R], or Zsh output
|
|
371
|
+
as \f[B]_actionlint\f[R] in a directory on \f[B]fpath\f[R].
|
|
372
|
+
Load PowerShell output from your profile.
|
|
373
|
+
Regenerate saved scripts after upgrading actionlint so they reflect the
|
|
374
|
+
installed CLI.
|
|
375
|
+
See the usage document for setup examples.
|
|
376
|
+
.SH ENVIRONMENT
|
|
377
|
+
.TP
|
|
378
|
+
\f[B]PATH\f[R]
|
|
379
|
+
Used to find ShellCheck and pyflakes, including the executable selected
|
|
380
|
+
by their command flags.
|
|
381
|
+
.TP
|
|
382
|
+
\f[B]NO_COLOR\f[R]
|
|
383
|
+
A nonempty value disables automatic color.
|
|
384
|
+
\f[B]\-color\f[R] can force color; \f[B]\-no\-color\f[R] always disables
|
|
385
|
+
it.
|
|
386
|
+
.TP
|
|
387
|
+
\f[B]SHELL\f[R], \f[B]PSModulePath\f[R]
|
|
388
|
+
Used by \f[B]\-completion auto\f[R].
|
|
389
|
+
A supported shell named by \f[B]SHELL\f[R] takes precedence over the
|
|
390
|
+
PowerShell fallback indicated by a nonempty \f[B]PSModulePath\f[R].
|
|
391
|
+
.TP
|
|
392
|
+
\f[B]SHELLCHECK_OPTS\f[R]
|
|
393
|
+
Additional options interpreted by the external ShellCheck process.
|
|
394
|
+
.SH FILES
|
|
395
|
+
.TP
|
|
396
|
+
\f[B].github/workflows/\f[R]
|
|
397
|
+
Workflow directory scanned when no filenames are provided.
|
|
398
|
+
.TP
|
|
399
|
+
\f[B].github/actionlint.yaml\f[R], \f[B].github/actionlint.yml\f[R]
|
|
400
|
+
Optional repository configuration; the \f[B].yaml\f[R] spelling takes
|
|
401
|
+
precedence.
|
|
402
|
+
.SH DOCUMENTS
|
|
403
|
+
Detailed documentation for this release is available online.
|
|
404
|
+
.SS Checks
|
|
405
|
+
https://github.com/kjanat/actionlint/blob/v1.15.0/docs/checks.md
|
|
406
|
+
.PP
|
|
407
|
+
Full list of all checks done by actionlint with example inputs, outputs,
|
|
408
|
+
and playground links.
|
|
409
|
+
.SS Installation
|
|
410
|
+
https://github.com/kjanat/actionlint/blob/v1.15.0/docs/install.md
|
|
411
|
+
.PP
|
|
412
|
+
Installation instructions.
|
|
413
|
+
Prebuilt binaries, Homebrew package, building from source, a Docker
|
|
414
|
+
image, a download script (for CI) are available.
|
|
415
|
+
.SS Usage
|
|
416
|
+
https://github.com/kjanat/actionlint/blob/v1.15.0/docs/usage.md
|
|
417
|
+
.PP
|
|
418
|
+
CLI usage, shell completion, output templates, the GitHub Action, Docker
|
|
419
|
+
images, and editor and CI integrations.
|
|
420
|
+
.SS Configuration
|
|
421
|
+
https://github.com/kjanat/actionlint/blob/v1.15.0/docs/config.md
|
|
422
|
+
.PP
|
|
423
|
+
Repository configuration, runner labels, variables, secrets, and opt\-in
|
|
424
|
+
policy checks.
|
|
425
|
+
.SS Go API
|
|
426
|
+
https://github.com/kjanat/actionlint/blob/v1.15.0/docs/api.md
|
|
427
|
+
.PP
|
|
428
|
+
How to use actionlint as Go library.
|
|
429
|
+
.SS References
|
|
430
|
+
https://github.com/kjanat/actionlint/blob/v1.15.0/docs/reference.md
|
|
431
|
+
.PP
|
|
432
|
+
Links to resources.
|
|
433
|
+
.SH USAGE ON GITHUB ACTIONS
|
|
434
|
+
The repository provides a GitHub Action with actionlint, ShellCheck, and
|
|
435
|
+
pyflakes in a prebuilt Docker image.
|
|
436
|
+
It reports GitHub annotations by default and fails when problems are
|
|
437
|
+
found:
|
|
438
|
+
.IP
|
|
439
|
+
.EX
|
|
440
|
+
name: Lint GitHub Actions workflows
|
|
441
|
+
on: [push, pull_request]
|
|
442
|
+
permissions: { contents: read }
|
|
443
|
+
|
|
444
|
+
jobs:
|
|
445
|
+
actionlint:
|
|
446
|
+
runs\-on: ubuntu\-latest
|
|
447
|
+
steps:
|
|
448
|
+
\- uses: actions/checkout\(atv7
|
|
449
|
+
with: { persist\-credentials: false }
|
|
450
|
+
\- uses: kjanat/actionlint\(atv1
|
|
451
|
+
.EE
|
|
452
|
+
.PP
|
|
453
|
+
The Docker action requires a Linux runner with a reachable Docker
|
|
454
|
+
daemon.
|
|
455
|
+
To run the binary directly, including on macOS, Windows with Bash, or a
|
|
456
|
+
runner without a Docker daemon, use the download script:
|
|
457
|
+
.IP
|
|
458
|
+
.EX
|
|
459
|
+
\- name: Check workflow files
|
|
460
|
+
run: |
|
|
461
|
+
bash <(curl \-fsSL https://raw.githubusercontent.com/kjanat/actionlint/HEAD/scripts/download\-actionlint.bash) latest
|
|
462
|
+
./actionlint \-color
|
|
463
|
+
shell: bash
|
|
464
|
+
.EE
|
|
465
|
+
.PP
|
|
466
|
+
The script accepts \f[B]latest\f[R] to resolve the newest release, or a
|
|
467
|
+
specific version.
|
|
468
|
+
Without a version argument, it uses the default recorded in the script.
|
|
469
|
+
It writes the executable to the current directory and, on GitHub
|
|
470
|
+
Actions, exposes its path as the \f[B]executable\f[R] step output.
|
|
471
|
+
External linters must be available separately when using the binary.
|
|
472
|
+
.SH EXIT STATUS
|
|
473
|
+
\f[B]actionlint\f[R] command exits with one of the following exit
|
|
474
|
+
statuses.
|
|
475
|
+
.IP \(bu 2
|
|
476
|
+
\f[B]0\f[R]: It ran successfully and no problem was found.
|
|
477
|
+
.IP \(bu 2
|
|
478
|
+
\f[B]1\f[R]: It ran successfully and some problem was found.
|
|
479
|
+
.IP \(bu 2
|
|
480
|
+
\f[B]2\f[R]: Command\-line flag parsing failed, for example because of
|
|
481
|
+
an unknown flag or missing value.
|
|
482
|
+
.IP \(bu 2
|
|
483
|
+
\f[B]3\f[R]: Initialization or linting failed, for example because a
|
|
484
|
+
file cannot be read, a project cannot be found, or a configuration,
|
|
485
|
+
ignore pattern, or output template is invalid.
|
|
486
|
+
.SH PLAYGROUND
|
|
487
|
+
The WebAssembly playground runs actionlint in your browser.
|
|
488
|
+
Workflow linting happens locally in the browser; it does not execute
|
|
489
|
+
workflows or run the external ShellCheck and pyflakes programs.
|
|
490
|
+
.PP
|
|
491
|
+
https://kjanat.github.io/actionlint/
|
|
492
|
+
.PP
|
|
493
|
+
Paste a workflow into the editor to see diagnostics update as you type.
|
|
494
|
+
Select a diagnostic to jump to its source position.
|
|
495
|
+
.SH BUGS
|
|
496
|
+
Report problems with this fork to its issue tracker.
|
|
497
|
+
Include the output of \f[B]\-version\f[R], the relevant configuration,
|
|
498
|
+
and a minimal workflow that reproduces the problem.
|
|
499
|
+
.PP
|
|
500
|
+
https://github.com/kjanat/actionlint/issues
|
|
501
|
+
.SH COPYRIGHT
|
|
502
|
+
\f[B]actionlint\f[R] is licensed under the MIT License.
|
|
503
|
+
.PP
|
|
504
|
+
Copyright (c) 2026 Kaj Kowalski
|
|
505
|
+
.PD 0
|
|
506
|
+
.P
|
|
507
|
+
.PD
|
|
508
|
+
Copyright (c) 2021 rhysd
|
|
509
|
+
.PP
|
|
510
|
+
https://github.com/kjanat/actionlint/blob/HEAD/LICENSE.txt
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kjanat/actionlint",
|
|
3
|
+
"version": "1.15.0",
|
|
4
|
+
"description": "Static checker for GitHub Actions workflow files",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"actionlint",
|
|
7
|
+
"github-actions",
|
|
8
|
+
"workflow",
|
|
9
|
+
"linter",
|
|
10
|
+
"lint",
|
|
11
|
+
"static-analysis",
|
|
12
|
+
"cli",
|
|
13
|
+
"yaml",
|
|
14
|
+
"shellcheck"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://actionlint.kjanat.dev",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/kjanat/actionlint/issues"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/kjanat/actionlint.git"
|
|
23
|
+
},
|
|
24
|
+
"funding": {
|
|
25
|
+
"type": "github",
|
|
26
|
+
"url": "https://github.com/sponsors/kjanat"
|
|
27
|
+
},
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"author": "Kaj Kowalski <info+actionlint@kajkowalski.nl>",
|
|
30
|
+
"type": "module",
|
|
31
|
+
"imports": {
|
|
32
|
+
"#launch": "./lib/launch.mjs",
|
|
33
|
+
"#pkg": "./package.json",
|
|
34
|
+
"#resolve": "./lib/resolve.mjs"
|
|
35
|
+
},
|
|
36
|
+
"exports": {
|
|
37
|
+
"./package.json": "./package.json"
|
|
38
|
+
},
|
|
39
|
+
"bin": {
|
|
40
|
+
"actionlint": "bin/actionlint.mjs"
|
|
41
|
+
},
|
|
42
|
+
"man": [
|
|
43
|
+
"./man/actionlint.1"
|
|
44
|
+
],
|
|
45
|
+
"directories": {
|
|
46
|
+
"lib": "./lib",
|
|
47
|
+
"man": "./man"
|
|
48
|
+
},
|
|
49
|
+
"files": [
|
|
50
|
+
"bin/",
|
|
51
|
+
"lib/",
|
|
52
|
+
"man/",
|
|
53
|
+
"README.md",
|
|
54
|
+
"LICENSE.txt"
|
|
55
|
+
],
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"ansispeck": "^0.4.2"
|
|
58
|
+
},
|
|
59
|
+
"optionalDependencies": {
|
|
60
|
+
"@kjanat-actionlint/actionlint-darwin-arm64": "1.15.0",
|
|
61
|
+
"@kjanat-actionlint/actionlint-darwin-x64": "1.15.0",
|
|
62
|
+
"@kjanat-actionlint/actionlint-freebsd-ia32": "1.15.0",
|
|
63
|
+
"@kjanat-actionlint/actionlint-freebsd-x64": "1.15.0",
|
|
64
|
+
"@kjanat-actionlint/actionlint-linux-arm": "1.15.0",
|
|
65
|
+
"@kjanat-actionlint/actionlint-linux-arm64": "1.15.0",
|
|
66
|
+
"@kjanat-actionlint/actionlint-linux-ia32": "1.15.0",
|
|
67
|
+
"@kjanat-actionlint/actionlint-linux-x64": "1.15.0",
|
|
68
|
+
"@kjanat-actionlint/actionlint-win32-arm64": "1.15.0",
|
|
69
|
+
"@kjanat-actionlint/actionlint-win32-ia32": "1.15.0",
|
|
70
|
+
"@kjanat-actionlint/actionlint-win32-x64": "1.15.0"
|
|
71
|
+
},
|
|
72
|
+
"engines": {
|
|
73
|
+
"node": ">=18.20.0"
|
|
74
|
+
}
|
|
75
|
+
}
|