|
| 1 | +#!/usr/bin/env node |
| 2 | + |
| 3 | +/** |
| 4 | + * @fileoverview GitHub Actions Reporting CLI - Generates reports on GitHub Actions usage across enterprises, |
| 5 | + * organizations, users, and individual repositories. Supports CSV, JSON, and Markdown output formats. |
| 6 | + * |
| 7 | + * This CLI tool helps organizations audit their GitHub Actions usage by collecting data about workflows, |
| 8 | + * secrets, variables, permissions, and action dependencies. It supports GitHub Enterprise Cloud/Server |
| 9 | + * and provides caching for improved performance on large datasets. |
| 10 | + * |
| 11 | + * @author Stefan Stölzle |
| 12 | + * @license MIT |
| 13 | + */ |
| 14 | + |
| 15 | +import chalk from 'chalk' |
| 16 | +import meow from 'meow' |
| 17 | + |
| 18 | +// Report class |
| 19 | +import Report from './src/report/report.js' |
| 20 | + |
| 21 | +// Utilities |
| 22 | +import cacheInstance from './src/util/cache.js' |
| 23 | +import log from './src/util/log.js' |
| 24 | + |
| 25 | +const {blue, bold, dim, yellow} = chalk |
| 26 | + |
| 27 | +/** |
| 28 | + * Creates the help text for the CLI application. |
| 29 | + * @returns {string} The formatted help text with usage, options, and examples |
| 30 | + */ |
| 31 | +function createHelpText() { |
| 32 | + return ` |
| 33 | + ${bold('Usage')} |
| 34 | + ${blue(`action-reporting-cli`)} ${yellow(`[options]`)} |
| 35 | +
|
| 36 | + ${bold('Required options')} ${dim(`[one of]`)} |
| 37 | + ${yellow(`--enterprise`)}, ${yellow(`-e`)} GitHub Enterprise (Cloud|Server) account slug ${dim( |
| 38 | + '(e.g. enterprise)', |
| 39 | + )}. |
| 40 | + ${yellow(`--owner`)}, ${yellow(`-o`)} GitHub organization/user login ${dim('(e.g. owner)')}. |
| 41 | + ${dim( |
| 42 | + `If ${yellow(`--owner`)} is a user, results for the authenticated user (${yellow( |
| 43 | + `--token`, |
| 44 | + )}) will be returned.`, |
| 45 | + )} |
| 46 | + ${yellow(`--repository`)}, ${yellow(`-r`)} GitHub repository name with owner ${dim('(e.g. owner/repo)')}. |
| 47 | +
|
| 48 | + ${bold('Additional options')} |
| 49 | + ${yellow(`--token`)}, ${yellow(`-t`)} GitHub Personal Access Token (PAT) ${dim('(default GITHUB_TOKEN)')}. |
| 50 | + ${yellow(`--hostname`)} GitHub Enterprise Server ${bold('hostname')} ${dim('(default api.github.com)')}. |
| 51 | + ${dim(`For example: ${yellow('github.example.com')}`)} |
| 52 | +
|
| 53 | + ${bold('Report options')} |
| 54 | + ${yellow(`--all`)} Report all below. |
| 55 | +
|
| 56 | + ${yellow(`--listeners`)} Report ${bold('on')} listeners used. |
| 57 | + ${yellow(`--permissions`)} Report ${bold('permissions')} values for GITHUB_TOKEN. |
| 58 | + ${yellow(`--runs-on`)} Report ${bold('runs-on')} values. |
| 59 | + ${yellow(`--secrets`)} Report ${bold('secrets')} used. |
| 60 | + ${yellow(`--uses`)} Report ${bold('uses')} values. |
| 61 | + ${yellow(`--exclude`)} Exclude GitHub Actions created by GitHub. |
| 62 | + ${dim( |
| 63 | + `From https://github.com/actions and https://github.com/github organizations. |
| 64 | + Only applies to ${yellow(`--uses`)}.`, |
| 65 | + )} |
| 66 | + ${yellow(`--unique`)} List unique GitHub Actions. |
| 67 | + ${dim( |
| 68 | + `Possible values are ${yellow('true')}, ${yellow('false')} and ${yellow('both')}. |
| 69 | + Only applies to ${yellow(`--uses`)}.`, |
| 70 | + )} |
| 71 | + ${dim(`Will create an additional ${bold('*-unique.{csv,json,md}')} report file.`)} |
| 72 | + ${yellow(`--vars`)} Report ${bold('vars')} used. |
| 73 | +
|
| 74 | + ${bold('Output options')} |
| 75 | + ${yellow(`--csv`)} Path to save CSV output ${dim('(e.g. /path/to/reports/report.csv)')}. |
| 76 | + ${yellow(`--json`)} Path to save JSON output ${dim('(e.g. /path/to/reports/report.json)')}. |
| 77 | + ${yellow(`--md`)} Path to save markdown output ${dim('(e.g. /path/to/reports/report.md)')}. |
| 78 | +
|
| 79 | + ${bold('Helper options')} |
| 80 | + ${yellow(`--debug`)}, ${yellow(`-d`)} Enable debug mode. |
| 81 | + ${yellow(`--skipCache`)} Disable caching. |
| 82 | + ${yellow(`--help`)}, ${yellow(`-h`)} Print action-reporting help. |
| 83 | + ${yellow(`--version`)}, ${yellow(`-v`)} Print action-reporting version.` |
| 84 | +} |
| 85 | + |
| 86 | +/** |
| 87 | + * Creates the CLI flags configuration object. |
| 88 | + * @returns {object} The CLI flags configuration for meow |
| 89 | + */ |
| 90 | +const CLI_FLAGS = { |
| 91 | + debug: { |
| 92 | + type: 'boolean', |
| 93 | + default: false, |
| 94 | + shortFlag: 'd', |
| 95 | + }, |
| 96 | + skipCache: { |
| 97 | + type: 'boolean', |
| 98 | + default: false, |
| 99 | + }, |
| 100 | + help: { |
| 101 | + type: 'boolean', |
| 102 | + shortFlag: 'h', |
| 103 | + }, |
| 104 | + version: { |
| 105 | + type: 'boolean', |
| 106 | + shortFlag: 'v', |
| 107 | + }, |
| 108 | + enterprise: { |
| 109 | + type: 'string', |
| 110 | + shortFlag: 'e', |
| 111 | + }, |
| 112 | + owner: { |
| 113 | + type: 'string', |
| 114 | + shortFlag: 'o', |
| 115 | + }, |
| 116 | + repository: { |
| 117 | + type: 'string', |
| 118 | + shortFlag: 'r', |
| 119 | + }, |
| 120 | + token: { |
| 121 | + type: 'string', |
| 122 | + default: process.env.GITHUB_TOKEN || '', |
| 123 | + shortFlag: 't', |
| 124 | + }, |
| 125 | + all: { |
| 126 | + type: 'boolean', |
| 127 | + default: false, |
| 128 | + }, |
| 129 | + listeners: { |
| 130 | + type: 'boolean', |
| 131 | + default: false, |
| 132 | + }, |
| 133 | + permissions: { |
| 134 | + type: 'boolean', |
| 135 | + default: false, |
| 136 | + }, |
| 137 | + runsOn: { |
| 138 | + type: 'boolean', |
| 139 | + default: false, |
| 140 | + }, |
| 141 | + secrets: { |
| 142 | + type: 'boolean', |
| 143 | + default: false, |
| 144 | + }, |
| 145 | + uses: { |
| 146 | + type: 'boolean', |
| 147 | + default: false, |
| 148 | + }, |
| 149 | + exclude: { |
| 150 | + type: 'boolean', |
| 151 | + default: false, |
| 152 | + }, |
| 153 | + unique: { |
| 154 | + type: 'string', |
| 155 | + default: 'false', |
| 156 | + }, |
| 157 | + vars: { |
| 158 | + type: 'boolean', |
| 159 | + default: false, |
| 160 | + }, |
| 161 | + cache: { |
| 162 | + type: 'boolean', |
| 163 | + default: false, |
| 164 | + }, |
| 165 | + hostname: { |
| 166 | + type: 'string', |
| 167 | + }, |
| 168 | + csv: { |
| 169 | + type: 'string', |
| 170 | + }, |
| 171 | + md: { |
| 172 | + type: 'string', |
| 173 | + }, |
| 174 | + json: { |
| 175 | + type: 'string', |
| 176 | + }, |
| 177 | +} |
| 178 | + |
| 179 | +const cli = meow(createHelpText(), { |
| 180 | + booleanDefault: undefined, |
| 181 | + description: false, |
| 182 | + hardRejection: false, |
| 183 | + allowUnknownFlags: false, |
| 184 | + importMeta: import.meta, |
| 185 | + inferType: false, |
| 186 | + input: [], |
| 187 | + flags: CLI_FLAGS, |
| 188 | +}) |
| 189 | + |
| 190 | +/** |
| 191 | + * Main execution function that orchestrates the CLI application. |
| 192 | + * Handles input validation, option processing, and delegates to appropriate processing functions. |
| 193 | + * @async |
| 194 | + * @returns {Promise<void>} |
| 195 | + * @throws {Error} When validation fails or processing encounters errors |
| 196 | + */ |
| 197 | +async function main() { |
| 198 | + console.log(`${bold('@stoe/action-reporting-cli')} ${dim(`v${cli.pkg.version}`)}\n`) |
| 199 | + |
| 200 | + const {token, hostname, enterprise, owner, repository, debug, help, version} = cli.flags |
| 201 | + const entity = enterprise || owner || repository |
| 202 | + const logger = log(entity, token, debug) |
| 203 | + const cache = cacheInstance(null, logger) |
| 204 | + |
| 205 | + try { |
| 206 | + // Handle help and version flags early exit |
| 207 | + if (help) cli.showHelp(0) |
| 208 | + if (version) cli.showVersion(0) |
| 209 | + |
| 210 | + const report = new Report(cli.flags, logger, cache) |
| 211 | + let results |
| 212 | + |
| 213 | + if (enterprise) { |
| 214 | + results = await report.processEnterprise(enterprise, token, hostname, debug) |
| 215 | + } else if (owner) { |
| 216 | + results = await report.processOwner(owner, token, hostname, debug) |
| 217 | + } else if (repository) { |
| 218 | + results = await report.processRepository(repository, token, hostname, debug) |
| 219 | + } |
| 220 | + |
| 221 | + const reportData = await report.createReport(results) |
| 222 | + reportData.length && (await report.saveReports(reportData)) |
| 223 | + } catch (error) { |
| 224 | + logger.fail(error.message) |
| 225 | + |
| 226 | + // Log error stack trace in debug mode |
| 227 | + debug && logger.error(error.stack) |
| 228 | + } |
| 229 | +} |
| 230 | + |
| 231 | +// Execute the main function |
| 232 | +main() |
0 commit comments