|
| 1 | +#!/usr/bin/env node |
| 2 | + |
| 3 | +const program = require("commander"); |
| 4 | +const prettyBytes = require("pretty-bytes"); |
| 5 | +const chalk = require("chalk"); |
| 6 | +const _ = require("lodash"); |
| 7 | +const moment = require("moment"); |
| 8 | +var inquirer = require("inquirer"); |
| 9 | +const Octokit = require("@octokit/rest"); |
| 10 | + |
| 11 | +const dotenv = require("dotenv"); |
| 12 | + |
| 13 | +dotenv.config(); |
| 14 | + |
| 15 | +program.option( |
| 16 | + "-t, --token <PAT>", |
| 17 | + "Your GitHub PAT (leave blank for prompt or set $GH_PAT)" |
| 18 | +); |
| 19 | +program.option( |
| 20 | + "-u, --user <username>", |
| 21 | + "Your GitHub username (leave blank for prompt or set $GH_USER)" |
| 22 | +); |
| 23 | +program.option("-r, --repo <repository>", "Repository name"); |
| 24 | + |
| 25 | +program.parse(process.argv); |
| 26 | +const showArtifacts = async ({ owner, repo, PAT }) => { |
| 27 | + var loader = ["/ Loading", "| Loading", "\\ Loading", "- Loading"]; |
| 28 | + var i = 4; |
| 29 | + var ui = new inquirer.ui.BottomBar({ bottomBar: loader[i % 4] }); |
| 30 | + |
| 31 | + const loadingInterval = setInterval(() => { |
| 32 | + ui.updateBottomBar(loader[i++ % 4]); |
| 33 | + }, 200); |
| 34 | + |
| 35 | + const octokit = new Octokit({ |
| 36 | + auth: PAT |
| 37 | + }); |
| 38 | + |
| 39 | + const prefs = { owner, repo }; |
| 40 | + ui.log.write(`${chalk.dim("[1/3]")} 🔍 Getting list of workflows...`); |
| 41 | + |
| 42 | + const { |
| 43 | + data: { workflows } |
| 44 | + } = await octokit.actions.listRepoWorkflows({ ...prefs }); |
| 45 | + |
| 46 | + let everything = {}; |
| 47 | + |
| 48 | + ui.log.write(`${chalk.dim("[2/3]")} 🏃♀️ Getting list of workflow runs...`); |
| 49 | + |
| 50 | + let runs = await workflows.reduce(async (promisedRuns, w) => { |
| 51 | + const memo = await promisedRuns; |
| 52 | + |
| 53 | + const { |
| 54 | + data: { workflow_runs } |
| 55 | + } = await octokit.actions.listWorkflowRuns({ ...prefs, workflow_id: w.id }); |
| 56 | + |
| 57 | + everything[w.id] = { |
| 58 | + name: w.name, |
| 59 | + id: w.id, |
| 60 | + updated_at: w.updated_at, |
| 61 | + state: w.updated_at, |
| 62 | + runs: workflow_runs.reduce( |
| 63 | + (r, { id, run_number, status, conclusion, html_url }) => { |
| 64 | + return { |
| 65 | + ...r, |
| 66 | + [id]: { |
| 67 | + id, |
| 68 | + workflow_id: w.id, |
| 69 | + run_number, |
| 70 | + status, |
| 71 | + conclusion, |
| 72 | + html_url, |
| 73 | + artifacts: [] |
| 74 | + } |
| 75 | + }; |
| 76 | + }, |
| 77 | + {} |
| 78 | + ) |
| 79 | + }; |
| 80 | + |
| 81 | + if (!workflow_runs.length) return memo; |
| 82 | + return [...memo, ...workflow_runs]; |
| 83 | + }, []); |
| 84 | + |
| 85 | + ui.log.write( |
| 86 | + `${chalk.dim( |
| 87 | + "[3/3]" |
| 88 | + )} 📦 Getting list of artifacts for each run... (this may take a while)` |
| 89 | + ); |
| 90 | + |
| 91 | + let all_artifacts = await runs.reduce(async (promisedArtifact, r) => { |
| 92 | + const memo = await promisedArtifact; |
| 93 | + |
| 94 | + const { |
| 95 | + data: { artifacts } |
| 96 | + } = await octokit.actions.listWorkflowRunArtifacts({ |
| 97 | + ...prefs, |
| 98 | + run_id: r.id |
| 99 | + }); |
| 100 | + |
| 101 | + if (!artifacts.length) return memo; |
| 102 | + |
| 103 | + const run_wf = _.find(everything, wf => wf.runs[r.id] != undefined); |
| 104 | + if (run_wf && everything[run_wf.id]) { |
| 105 | + everything[run_wf.id].runs[r.id].artifacts = artifacts; |
| 106 | + } |
| 107 | + |
| 108 | + return [...memo, ...artifacts]; |
| 109 | + }, []); |
| 110 | + |
| 111 | + let output = []; |
| 112 | + _.each(everything, wf => { |
| 113 | + _.each(wf.runs, ({ run_number, artifacts }) => { |
| 114 | + _.each(artifacts, ({ id, name, size_in_bytes, created_at }) => { |
| 115 | + output.push({ |
| 116 | + name, |
| 117 | + artifact_id: id, |
| 118 | + size: prettyBytes(size_in_bytes), |
| 119 | + size_in_bytes, |
| 120 | + created: moment(created_at).format("dddd, MMMM Do YYYY, h:mm:ss a"), |
| 121 | + created_at, |
| 122 | + run_number, |
| 123 | + workflow: wf.name |
| 124 | + }); |
| 125 | + }); |
| 126 | + }); |
| 127 | + }); |
| 128 | + |
| 129 | + const out = _.orderBy(output, ["size_in_bytes"], ["desc"]); |
| 130 | + clearInterval(loadingInterval); |
| 131 | + |
| 132 | + inquirer |
| 133 | + .prompt([ |
| 134 | + { |
| 135 | + type: "checkbox", |
| 136 | + name: "artifact_ids", |
| 137 | + message: "Select the artifacts you want to delete", |
| 138 | + choices: output.map((row, k) => ({ |
| 139 | + name: `${row.workflow} - ${row.name}, ${row.size} (${row.created}, ID: ${row.artifact_id}, Run #: ${row.run_number})`, |
| 140 | + value: row.artifact_id |
| 141 | + })) |
| 142 | + } |
| 143 | + ]) |
| 144 | + .then(answers => { |
| 145 | + if (answers.artifact_ids.length == 0) { |
| 146 | + process.exit(); |
| 147 | + } |
| 148 | + |
| 149 | + inquirer |
| 150 | + .prompt([ |
| 151 | + { |
| 152 | + type: "confirm", |
| 153 | + name: "delete", |
| 154 | + message: `You are about to delete ${answers.artifact_ids.length} artifacts permanently. Are you sure?` |
| 155 | + } |
| 156 | + ]) |
| 157 | + .then(confirm => { |
| 158 | + if (!confirm.delete) process.exit(); |
| 159 | + |
| 160 | + answers.artifact_ids.map(aid => { |
| 161 | + octokit.actions |
| 162 | + .deleteArtifact({ ...prefs, artifact_id: aid }) |
| 163 | + .then(r => { |
| 164 | + console.log( |
| 165 | + r.status === 204 |
| 166 | + ? `${chalk.green("[OK]")} Artifact with ID ${chalk.dim( |
| 167 | + aid |
| 168 | + )} deleted` |
| 169 | + : `${chalk.red("[ERR]")} Artifact with ID ${chalk.dim( |
| 170 | + aid |
| 171 | + )} could not be deleted.` |
| 172 | + ); |
| 173 | + }) |
| 174 | + .catch(e => { |
| 175 | + console.error(e.status, e.message); |
| 176 | + }); |
| 177 | + }); |
| 178 | + }); |
| 179 | + }); |
| 180 | +}; |
| 181 | + |
| 182 | +inquirer |
| 183 | + .prompt([ |
| 184 | + { |
| 185 | + type: "password", |
| 186 | + name: "PAT", |
| 187 | + message: "What's your GitHub PAT?", |
| 188 | + default: function() { |
| 189 | + return program.token || process.env.GH_PAT; |
| 190 | + } |
| 191 | + }, |
| 192 | + { |
| 193 | + type: "input", |
| 194 | + name: "owner", |
| 195 | + message: "Your username?", |
| 196 | + default: function() { |
| 197 | + return program.user || process.env.GH_USER; |
| 198 | + } |
| 199 | + }, |
| 200 | + { |
| 201 | + type: "input", |
| 202 | + name: "repo", |
| 203 | + message: "Which repository?", |
| 204 | + default: function() { |
| 205 | + return program.repo; |
| 206 | + } |
| 207 | + } |
| 208 | + ]) |
| 209 | + .then(answers => { |
| 210 | + showArtifacts({ ...answers }); |
| 211 | + }); |
0 commit comments