Skip to content

Commit

Permalink
Refactors action sourcecode to typescript
Browse files Browse the repository at this point in the history
  • Loading branch information
hanseartic committed Dec 17, 2024
1 parent fa45e8b commit 6625f10
Show file tree
Hide file tree
Showing 13 changed files with 820 additions and 6,700 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.idea
index.guard
dist
node_modules
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# 🧹 *Purge workflow runs* action [![Unit Test](https://github.com/otto-de/purge-deprecated-workflow-runs/actions/workflows/test.yml/badge.svg)](https://github.com/otto-de/purge-deprecated-workflow-runs/actions/workflows/test.yml) ![OSS Lifecycle](https://img.shields.io/osslifecycle?file_url=https%3A%2F%2Fgithub.com%2Fotto-de%2Fpurge-deprecated-workflow-runs%2Fblob%2Fmain%2FOSSMETADATA)

[![Unit Test](https://github.com/otto-de/purge-deprecated-workflow-runs/actions/workflows/test.yml/badge.svg)](https://github.com/otto-de/purge-deprecated-workflow-runs/actions/workflows/test.yml) ![OSS Lifecycle](https://img.shields.io/osslifecycle?file_url=https%3A%2F%2Fgithub.com%2Fotto-de%2Fpurge-deprecated-workflow-runs%2Fblob%2Fmain%2FOSSMETADATA)
# 🧹 *Purge workflow runs* action

This GH action removes action runs from a repository. By default, obsolete workflow runs are deleted. Additional
filter can be applied to deleted workflow runs by status/conclusion - see input parameters below for details.
Expand Down Expand Up @@ -55,7 +55,7 @@ All inputs are optional - if any input is not given, the default value will be u
`remove-older-than`</dt>
<dd>

- Remove workflows from the list that are older than the given timeframe (e.g. '10S', '30M', '12H', '7d', '2w', '1m', '6y')
- Remove workflows from the list that are older than the given timeframe (e.g. '10s', '30m', '12h', '7d', '2w', '6y')
- Accepts a (multiline) `string` in the format of `NU [W]` where `N` is a number, `U` is a time unit and optionally `W` is the workflow name.
The following units are supported:
- `s` for seconds
Expand Down Expand Up @@ -129,7 +129,7 @@ jobs:

The following example will remove all workflow runs that are older than 4 weeks and all runs of the current workflow older than 1 day:
```yaml
name: Weekly purge of workflow runs older than a week
name: Weekly purge of all workflow runs older than four weeks and all runs of the current workflow older than one day
on:
schedule:
- cron: '0 0 * * 0'
Expand Down
2 changes: 1 addition & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ inputs:
default: false
runs:
using: node20
main: index.js
main: index.guard.js
branding:
icon: trash-2
color: yellow
3 changes: 3 additions & 0 deletions index.guard.js

Large diffs are not rendered by default.

44 changes: 44 additions & 0 deletions index.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import * as core from '@actions/core'
import { Octokit } from '@octokit/rest';

type Release = {
html_url: string
draft: boolean
prerelease: boolean
tag_name: string
}

async function getReleases(): Promise<Release[]> {
const octokit = new Octokit({auth: core.getInput('token') || process.env.GITHUB_TOKEN})
const [ owner, repo ] = process.env.GITHUB_ACTION_REPOSITORY?.split('/') ?? [undefined, undefined]
if (!owner || !repo) {
core.setFailed('Failed to determine owner and repository for this action.')
return []
}
return octokit.rest.repos.listReleases({ owner, repo })
.then(({ data }) => data)
.then((json) => json.map((release): Release => ({
html_url: release.html_url,
draft: release.draft,
prerelease: release.prerelease,
tag_name: release.tag_name,
})))
}

core.setFailed('Must use a tagged release of this action! See summary for more details.')
getReleases()
.then((releases) => {
const latestStable = releases.find((release: Release) => !release.prerelease && !release.draft)?.tag_name ?? 'v1'
return core.summary
.addHeading('🏷️ Only tagged releases of this action can be used in workflows', 3)
.addRaw('Only tagged releases of this action can be used, e.g.\n', true)
.addRaw('```yaml', true)
.addRaw(`- uses: ${process.env.GITHUB_ACTION_REPOSITORY}@${latestStable}`, true)
.addRaw('```\n', true)
.addRaw('----\nThe following releases are available:', true)
.addRaw(releases.map((release) => `* [${release.tag_name}](${release.html_url})`).join('\n'), true)
.write({overwrite: true})
})
.catch(() => {
core.notice('Failed to fetch releases from GitHub.')
})
61 changes: 39 additions & 22 deletions index.js → index.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,44 @@
import core from '@actions/core';
import github from '@actions/github';
import * as core from '@actions/core';
import * as github from '@actions/github';
import { Octokit } from '@octokit/rest';
import { extract as extractOlderThanInMs, filterWorkflowRuns as filterWorkflowRunsOlderThan} from './process_older_than.js';
import {
extract as extractOlderThanInMs,
filterWorkflowRuns as filterWorkflowRunsOlderThan, WorkflowRun
} from './process_older_than.js';
type Extractor<T> = (data: string | boolean) => T
const getInput = <T, F extends T | T[] = T>(name: string, fallback?: F, extractor?: Extractor<T>): F extends T ? T : T[] => {
if (!extractor) {
extractor = (data) => data as T
}
if (Array.isArray(fallback)) {
const ml = core.getMultilineInput(name).map(extractor).filter(Boolean)
return (ml.length ? ml : fallback) as any
}

const getInput = (name, fallback = undefined, extractor = (data) => data) => {
let input: string | boolean
try {
return extractor(core.getBooleanInput(name) ?? fallback)
input = core.getBooleanInput(name)
} catch {
const ml = core.getMultilineInput(name).map(extractor).filter(Boolean)
return ml.length ? ml : fallback
input = core.getInput(name)
}
if (!input) {
return fallback as any
}
return extractor(input) as any
}

const run2Id = (run) => run.id
const run2Id = (run: WorkflowRun): number => run.id

const run = async () => {
try {
const token = core.getInput('token') || process.env.GITHUB_TOKEN;
const deleteObsolete = getInput('remove-obsolete', true)
const deleteByStatus = {
const deleteObsolete = getInput<boolean>('remove-obsolete', true)
const deleteByConclusion = {
// property names must match workflow run conclusions:
// see: https://docs.github.com/en/rest/actions/workflow-runs?apiVersion=2022-11-28#list-workflow-runs-for-a-repository
cancelled: getInput('remove-cancelled', false),
failure: getInput('remove-failed', false),
skipped: getInput('remove-skipped', false),
cancelled: getInput<boolean | string[], boolean>('remove-cancelled', false),
failure: getInput<boolean | string[], boolean>('remove-failed', false),
skipped: getInput<boolean | string[], boolean>('remove-skipped', false),
}
const deleteOlderThan = getInput('remove-older-than', [], extractOlderThanInMs)
const {owner, repo} = github.context.repo
Expand Down Expand Up @@ -55,7 +70,7 @@ const run = async () => {
}))
)

const idsToDelete = []
const idsToDelete: number[] = []
if (deleteObsolete) {
const workflowRunIdsWithoutWorkflow = workflowRuns
.filter(run => !workflowIds.includes(run.workflow_id))
Expand All @@ -64,15 +79,16 @@ const run = async () => {
idsToDelete.push(...workflowRunIdsWithoutWorkflow)
}

for (const status in deleteByStatus) {
if (deleteByStatus[status]) {
for (const conclusion in deleteByConclusion) {
const conclusionValue = deleteByConclusion[conclusion as keyof typeof deleteByConclusion]
if (conclusionValue) {
const idsToDeleteByStatus = workflowRuns
.filter(run => {
if (run.conclusion !== status) { return false }
return deleteByStatus[status] === true || deleteByStatus[status].includes(run.name)
if (run.conclusion !== conclusion) { return false }
return conclusionValue === true || (run.name && conclusionValue.includes(run.name))
})
.map(run2Id)
core.info(`Found ${idsToDeleteByStatus.length} workflow runs with status [${status}].`);
core.info(`Found ${idsToDeleteByStatus.length} workflow runs with status [${conclusion}].`);
idsToDelete.push(...idsToDeleteByStatus)
}
}
Expand All @@ -81,7 +97,7 @@ const run = async () => {
idsToDelete.push(...filterWorkflowRunsOlderThan(workflowRuns, deleteOlderThan).map(run2Id))
}

const uniqueRunIdsToDelete = [...new Set(idsToDelete)]
const uniqueRunIdsToDelete = Array.from(new Set(idsToDelete))
await Promise.all(uniqueRunIdsToDelete.map(run_id => octokit.request(
"DELETE /repos/{owner}/{repo}/actions/runs/{run_id}",
{
Expand All @@ -92,8 +108,9 @@ const run = async () => {
.then(() => core.info("Removed run with id: " + run_id))
));
} catch (error) {
core.error(error)
core.info('Error occurred: ' + JSON.stringify({error}))
core.error(JSON.stringify({error}))
}
}

run()
void run()
Loading

0 comments on commit 6625f10

Please sign in to comment.