|
| 1 | +class StaticAnalysis { |
| 2 | + constructor(dataProvider, process) { |
| 3 | + this.dataProvider = dataProvider; |
| 4 | + this.process = process; |
| 5 | + } |
| 6 | + |
| 7 | + isCommitHash(ref) { |
| 8 | + return /^[0-9a-f]{40}$/.test(ref); |
| 9 | + } |
| 10 | + |
| 11 | + findYamlFiles(dir) { |
| 12 | + let results = []; |
| 13 | + const list = this.dataProvider.readDirectory(dir); |
| 14 | + list.forEach(file => { |
| 15 | + const fullPath = this.dataProvider.path.join(dir, file); |
| 16 | + const stat = this.dataProvider.getFileStats(fullPath); |
| 17 | + if (stat && stat.isDirectory()) { |
| 18 | + results = results.concat(this.findYamlFiles(fullPath)); |
| 19 | + } else if (file.endsWith('.yml') || file.endsWith('.yaml')) { |
| 20 | + results.push(fullPath); |
| 21 | + } |
| 22 | + }); |
| 23 | + return results; |
| 24 | + } |
| 25 | + |
| 26 | + scanFile(filePath) { |
| 27 | + const content = this.dataProvider.readFile(filePath); |
| 28 | + const regex = /uses:\s*[\w-]+\/[\w-]+@([\w-.]+)/g; |
| 29 | + let match; |
| 30 | + let warnings = []; |
| 31 | + while ((match = regex.exec(content)) !== null) { |
| 32 | + const ref = match[1]; |
| 33 | + if (!this.isCommitHash(ref)) { |
| 34 | + warnings.push(`Warning: In file ${filePath}, '${match[0]}' does not use a commit hash.`); |
| 35 | + } |
| 36 | + } |
| 37 | + return warnings; |
| 38 | + } |
| 39 | + |
| 40 | + run() { |
| 41 | + const workflowDir = this.dataProvider.path.join(this.process.cwd(), '.github', 'workflows'); |
| 42 | + if (!this.dataProvider.fileExists(workflowDir)) { |
| 43 | + console.error(`Error: Directory ${workflowDir} does not exist.`); |
| 44 | + this.process.exit(1); |
| 45 | + } |
| 46 | + |
| 47 | + const yamlFiles = this.findYamlFiles(workflowDir); |
| 48 | + let allWarnings = []; |
| 49 | + |
| 50 | + yamlFiles.forEach(filePath => { |
| 51 | + const warnings = this.scanFile(filePath); |
| 52 | + allWarnings = allWarnings.concat(warnings); |
| 53 | + }); |
| 54 | + |
| 55 | + if (allWarnings.length > 0) { |
| 56 | + allWarnings.forEach(warning => console.warn(warning)); |
| 57 | + console.log('To fix these issues, refer to the solution in the following Jira ticket: https://virdocs.atlassian.net/browse/RD-2964'); |
| 58 | + this.process.exit(0); // TODO: Exit with non-zero code if warnings are found |
| 59 | + } else { |
| 60 | + console.log('No issues found. All action calls use commit hashes.'); |
| 61 | + this.process.exit(0); |
| 62 | + } |
| 63 | + } |
| 64 | +} |
| 65 | + |
| 66 | +class DataProvider { |
| 67 | + constructor(fs, path) { |
| 68 | + this.fs = fs; |
| 69 | + this.path = path; |
| 70 | + } |
| 71 | + |
| 72 | + readDirectory(dir) { |
| 73 | + return this.fs.readdirSync(dir); |
| 74 | + } |
| 75 | + |
| 76 | + readFile(filePath) { |
| 77 | + return this.fs.readFileSync(filePath, 'utf8'); |
| 78 | + } |
| 79 | + |
| 80 | + fileExists(filePath) { |
| 81 | + return this.fs.existsSync(filePath); |
| 82 | + } |
| 83 | + |
| 84 | + getFileStats(filePath) { |
| 85 | + return this.fs.statSync(filePath); |
| 86 | + } |
| 87 | +} |
| 88 | + |
| 89 | +// Dependency injection |
| 90 | +const fs = require('fs'); |
| 91 | +const path = require('path'); |
| 92 | +const process = require('process'); |
| 93 | + |
| 94 | +const dataProvider = new DataProvider(fs, path); |
| 95 | +const staticAnalysis = new StaticAnalysis(dataProvider, process); |
| 96 | +staticAnalysis.run(); |
0 commit comments