@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 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/
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import launch from '#launch';
3
+
4
+ launch('actionlint');
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
+ }
@@ -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
+ }
@@ -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
+ }