4242 ./action.yml
4343 ./.github/workflows/**/*.yml
4444 ./actions/**/*.yml
45+ workflow-files :
46+ description : |
47+ List of files or directories where GitHub workflows are located.
48+ Supports glob patterns.
49+ Leave empty to disable the check.
50+ type : string
51+ required : false
52+ default : |
53+ ./.github/workflows/*.yml
4554 lint-all :
4655 description : " Run checks on all files, not just the changed ones."
4756 type : boolean
@@ -137,10 +146,12 @@ jobs:
137146 with :
138147 category : " /language:${{matrix.language}}"
139148
140- actions-pinning :
141- name : 📌 Check GitHub Actions Pinning
149+ prepare-actions-linting :
142150 runs-on : ${{ fromJson(inputs.runs-on) }}
143- if : ${{ inputs.action-files }}
151+ if : ${{ inputs.action-files || inputs.workflow-files }}
152+ outputs :
153+ action-files : ${{ steps.get-files-to-lint.outputs.action-files }}
154+ workflow-names : ${{ steps.get-files-to-lint.outputs.workflow-names }}
144155 steps :
145156 - uses : actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
146157 with :
@@ -151,156 +162,139 @@ jobs:
151162 uses : tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0
152163 if : ${{ inputs.lint-all == false }}
153164 with :
154- files : ${{ inputs.action-files }}
165+ files : |
166+ ${{ inputs.action-files }}
167+ ${{ inputs.workflow-files }}
155168 dir_names_exclude_current_dir : true
156169
157170 - id : get-files-to-lint
158171 uses : actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
159172 env :
160- CHANGED_FILES : ${{ toJSON(steps.changed-files.outputs.all_changed_and_modified_files) }};
173+ CHANGED_FILES_OUTPUT : ${{ toJSON(steps.changed-files.outputs.all_changed_and_modified_files) }};
161174 ACTION_FILES_INPUT : ${{ toJSON(inputs.action-files) }}
175+ WORKFLOW_FILES_INPUT : ${{ toJSON(inputs.workflow-files) }}
162176 with :
163177 script : |
164178 const fs = require("node:fs");
165179 const path = require("node:path");
166180
167- const changedFiles = process.env.CHANGED_FILES;
181+ const changedFilesOutput = process.env.CHANGED_FILES_OUTPUT;
182+ core.debug(`Changed files output: ${changedFilesOutput}`);
168183
169- let actionFiles = [];
170- if (changedFiles !== null) {
171- actionFiles = changedFiles.split(" ").filter(file => file && fs.existsSync(file));
172- } else {
173- const actionFilesInput = process.env.ACTION_FILES_INPUT;
184+ const actionFilesInput = process.env.ACTION_FILES_INPUT;
185+ core.debug(`Action files input: ${actionFilesInput}`);
174186
175- for (const actionFile of actionFilesInput.split("\n")) {
176- let sanitizedActionFile = actionFile.trim();
177- if (sanitizedActionFile === "") {
187+ const workflowFilesInput = process.env.WORKFLOW_FILES_INPUT;
188+ core.debug(`Workflow files input: ${workflowFilesInput}`);
189+
190+ function parseFilePatterns(filePatterns) {
191+ const patterns = [];
192+ for (const filePattern of filePatterns.split("\n")) {
193+ let sanitizedFilePattern = filePattern.trim();
194+ if (sanitizedFilePattern === "") {
178195 continue;
179196 }
180197
181- if (path.isAbsolute(sanitizedActionFile )) {
182- // Ensure actionFile is within the workspace
183- if (!sanitizedActionFile .startsWith(process.env.GITHUB_WORKSPACE)) {
184- return core.setFailed(`Action file / directory is not within the workspace: ${sanitizedActionFile }`);
198+ if (path.isAbsolute(sanitizedFilePattern )) {
199+ // Ensure filePattern is within the workspace
200+ if (!sanitizedFilePattern .startsWith(process.env.GITHUB_WORKSPACE)) {
201+ return core.setFailed(`File / directory is not within the workspace: ${sanitizedFilePattern }`);
185202 }
186203 } else {
187- sanitizedActionFile = path.join(process.env.GITHUB_WORKSPACE, sanitizedActionFile );
204+ sanitizedFilePattern = path.join(process.env.GITHUB_WORKSPACE, sanitizedFilePattern );
188205 }
189- actionFiles.push(sanitizedActionFile);
190- }
191-
192- if (actionFiles.length === 0) {
193- return core.setFailed("No action files to lint.");
194- }
195-
196- async function getActionFiles(actionFile) {
197- const globber = await glob.create(actionFile,{ matchactionFilesInput: false });
198- return await globber.glob();
206+ patterns.push(sanitizedFilePattern);
199207 }
208+ return patterns;
209+ }
200210
201- actionFiles = (await Promise.all(actionFiles.map(getActionFiles)))
211+ async function findFilesByPatterns(filePatterns) {
212+ const foundFiles = (await Promise.all(filePatterns.map(
213+ async (filePattern) => {
214+ const globber = await glob.create(filePattern, { excludeHiddenFiles: false });
215+ return await globber.glob();
216+ }
217+ )))
202218 .flat()
203219 .map((file) => path.relative(process.env.GITHUB_WORKSPACE, file));
204220
205- if (actionFiles.length === 0) {
206- return core.setFailed("No action files to lint.");
207- }
221+ return [...new Set(foundFiles)];
208222 }
209223
210- const files = actionFiles.map((file) => path.relative(process.env.GITHUB_WORKSPACE, file));
211- const filesOutput = [...new Set(files)].join(" ").trim();
212-
213- if (filesOutput.length === 0) {
214- return;
224+ let changedFiles = null;
225+ if (changedFilesOutput) {
226+ changedFiles = changedFilesOutput.split(" ").filter(file => file && fs.existsSync(file));
215227 }
216228
217- core.setOutput("files", filesOutput);
218-
219- - id : ratchet
220- # FIXME: should be updated by dependabot. See https://github.com/dependabot/dependabot-core/issues/8362
221- uses : " docker://ghcr.io/sethvargo/ratchet:0.11.3@sha256:242445a1c55430ad7477e6fcf2027c77d03f5760702537bca4cf622e7338fc81" # 0.11.3
222- if : ${{ steps.get-files-to-lint.outputs.files }}
223- with :
224- args : " lint --format human --format actions ${{ steps.get-files-to-lint.outputs.files }}"
225-
226- permissions-analysis :
227- name : 🔐 Workflow Permissions Analysis
228- runs-on : ${{ fromJson(inputs.runs-on) }}
229- if : ${{ inputs.action-files }}
230- permissions :
231- contents : read
232- steps :
233- - uses : actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
234- with :
235- fetch-depth : " ${{ inputs.lint-all && 1 || 0 }}"
236-
237- - id : changed-files
238- uses : tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0
239- if : ${{ inputs.lint-all == false }}
240- with :
241- files : ${{ inputs.action-files }}
242- dir_names_exclude_current_dir : true
243-
244- - id : get-files-to-analyze
245- uses : actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
246- with :
247- script : |
248- const fs = require("node:fs");
249- const path = require("node:path");
250-
251- const changedFiles = ${{ toJSON(steps.changed-files.outputs.all_changed_and_modified_files) }};
252-
229+ let workflowNames = [];
230+ const parsedWorkflowFiles = parseFilePatterns(workflowFilesInput);
231+ core.debug(`Parsed workflow files: ${parsedWorkflowFiles}`);
232+ let workflowFiles = await findFilesByPatterns(parsedWorkflowFiles);
233+ core.debug(`Workflow files: ${workflowFiles}`);
234+
253235 let actionFiles = [];
254236 if (changedFiles !== null) {
255- actionFiles = changedFiles.split(" ").filter(file => file && fs.existsSync(file));
237+ actionFiles = changedFiles;
238+ workflowFiles = workflowFiles.filter(file => changedFiles.includes(file));
256239 } else {
257- const actionFilesInput = ${{ toJson(inputs.action-files) }};
258-
259- for (const actionFile of actionFilesInput.split("\n")) {
260- let sanitizedActionFile = actionFile.trim();
261- if (sanitizedActionFile === "") {
262- continue;
263- }
240+ const parsedActionFiles = parseFilePatterns(actionFilesInput);
241+ core.debug(`Parsed action files: ${parsedActionFiles}`);
264242
265- if (path.isAbsolute(sanitizedActionFile)) {
266- // Ensure actionFile is within the workspace
267- if (!sanitizedActionFile.startsWith(process.env.GITHUB_WORKSPACE)) {
268- return core.setFailed(`Action file / directory is not within the workspace: ${sanitizedActionFile}`);
269- }
270- } else {
271- sanitizedActionFile = path.join(process.env.GITHUB_WORKSPACE, sanitizedActionFile);
272- }
273- actionFiles.push(sanitizedActionFile);
274- }
275-
276- if (actionFiles.length === 0) {
277- return core.setFailed("No action files to analyze.");
243+ if (parsedActionFiles.length === 0) {
244+ return core.setFailed("No action files to lint.");
278245 }
279246
280- async function getActionFiles(actionFile) {
281- const globber = await glob.create(actionFile,{ matchactionFilesInput: false });
282- return await globber.glob();
283- }
247+ actionFiles = await findFilesByPatterns(parsedActionFiles);
248+ core.debug(`Action files: ${actionFiles}`);
249+ }
284250
285- actionFiles = (await Promise.all( actionFiles.map(getActionFiles)))
286- .flat()
287- .map((file) => path.relative(process.env.GITHUB_WORKSPACE, file));
251+ if ( actionFiles.length > 0) {
252+ core.setOutput("action-files", actionFiles);
253+ }
288254
289- if (actionFiles.length === 0) {
290- return core.setFailed("No action files to analyze.");
255+ for (const workflowFile of workflowFiles) {
256+ try {
257+ const workflowContent = fs.readFileSync(workflowFile, "utf8");
258+ const match = workflowContent.match(/name:\s*(.+)/);
259+ if (match) {
260+ workflowNames.push(match[1].trim());
261+ } else {
262+ workflowNames.push(path.basename(workflowFile, path.extname(workflowFile)));
263+ }
264+ } catch (error) {
265+ return core.setFailed(`Failed to read workflow file ${workflowFile}: ${error.message}`);
291266 }
292267 }
293-
294- const files = actionFiles.map((file) => path.relative(process.env.GITHUB_WORKSPACE, file));
295- const filesOutput = [...new Set(files)].join("\n").trim();
296-
297- if (filesOutput.length === 0) {
298- return;
268+ workflowNames = [...new Set(workflowNames)];
269+ if (workflowNames.length > 0) {
270+ core.setOutput("workflow-names", JSON.stringify(workflowNames));
299271 }
300272
301- core.setOutput("files", filesOutput);
273+ actions-pinning :
274+ name : 📌 Check GitHub Actions Pinning
275+ needs : prepare-actions-linting
276+ runs-on : ${{ fromJson(inputs.runs-on) }}
277+ if : ${{ needs.prepare-actions-linting.outputs.action-files }}
278+ steps :
279+ - uses : actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
280+
281+ # FIXME: should be updated by dependabot. See https://github.com/dependabot/dependabot-core/issues/8362
282+ - uses : " docker://ghcr.io/sethvargo/ratchet:0.11.3@sha256:242445a1c55430ad7477e6fcf2027c77d03f5760702537bca4cf622e7338fc81" # 0.11.3
283+ if : ${{ needs.prepare-actions-linting.outputs.action-files }}
284+ with :
285+ args : " lint --format human --format actions ${{ needs.prepare-actions-linting.outputs.action-files }}"
302286
303- - uses : GitHubSecurityLab/actions-permissions@f24e0c210427f4c43e516265a4ca94e1e30e95f9 # v1.0.10
304- if : ${{ steps.get-files-to-analyze.outputs.files }}
287+ permissions-analysis :
288+ name : 🔐 Workflow Permissions Analysis
289+ runs-on : ${{ fromJson(inputs.runs-on) }}
290+ needs : prepare-actions-linting
291+ if : ${{ needs.prepare-actions-linting.outputs.workflow-names }}
292+ permissions :
293+ actions : read
294+ strategy :
295+ matrix :
296+ name : ${{ fromJson(needs.prepare-actions-linting.outputs.workflow-names) }}
297+ steps :
298+ - uses : GitHubSecurityLab/actions-permissions/advisor@37c927c24552caa0ef6040ab0876db729cc12754 # v1.0.2-beta7
305299 with :
306- path : ${{ steps.get-files-to-analyze.outputs.files }}
300+ name : ${{ matrix.name }}
0 commit comments