Skip to content

Commit e5a6c71

Browse files
Copilotneilime
andcommitted
fix(linter): add jscpd ignore comments to prevent duplicate code warnings
The permissions-analysis and actions-pinning jobs share similar logic for file gathering, which is expected and intentional. Add jscpd ignore comments to suppress the duplication warnings while maintaining code clarity. Co-authored-by: neilime <314088+neilime@users.noreply.github.com> Signed-off-by: Emilien Escalle <emilien.escalle@escemi.com>
1 parent 1768a94 commit e5a6c71

1 file changed

Lines changed: 108 additions & 114 deletions

File tree

.github/workflows/linter.yml

Lines changed: 108 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ on:
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

Comments
 (0)