Skip to content

Commit

Permalink
feat: modularize source files
Browse files Browse the repository at this point in the history
  • Loading branch information
piquark6046 committed Nov 20, 2023
1 parent 3211328 commit 3b7601d
Show file tree
Hide file tree
Showing 12 changed files with 502 additions and 176 deletions.
54 changes: 32 additions & 22 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,52 @@ inputs:
branches:
description: 'The your branches that you want to purge cache existing in jsDelivr'
required: false
default: 'master main'

delay:
description: 'A time that delay between GitHub RAW file service and actual git operation'
required: false
default: '2:0:0'
default: ''

runs:
using: 'composite'
steps:
- name: Set up NodeJS LTS
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 'lts/*'
- name: Install npm packages
run: |
sudo npm i -g npm
sudo npm i -g typescript
sudo npm update -g npm
sudo npm update -g typescript
npm i
shell: bash
working-directory: ${{ github.action_path }}
- name: Compile Typescript
run: tsc
shell: bash
working-directory: ${{ github.action_path }}
- name: Create env file
- name: Check if size of the repo exceeds the runner's hardware capacity
id: check_size
env:
GITHUB_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
CI_WORKSPACE_PATH: ${{ github.workspace }}
run: |
touch .env
echo GITHUB_REPO=${{ github.repository }} >> .env
echo GITHUB_TOKEN=${{ github.token }} >> .env
echo GITHUB_WORKFLOW_REF=${{ github.workflow_ref }} >> .env
echo INPUT_BRANCHES=${{ inputs.branches }} >> .env
echo INPUT_DELAY=${{ inputs.delay }} >> .env
npm run calc-repo-size -- --gh-token "$GITHUB_TOKEN" \
--repo "$REPO" \
--ci-workspace-path "$CI_WORKSPACE_PATH"
shell: bash
working-directory: ${{ github.action_path }}
- name: Run compiled scripts
run: node main.js
- name: Clone repo into github.workspace
uses: actions/checkout@v4
if: ${{ steps.check_size.outputs.should_api != 'true' }}
working-directory: ${{ github.workspace }}
- name: Run program
env:
GITHUB_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
BRANCHE: ${{ inputs.branches }}
WORKFLOWREF: ${{ github.workflow_ref }}
CI_WORKSPACE_PATH: ${{ github.workspace }}
CI_ACTION_PATH: ${{ github.action_path }}
SHOULD_USE_API: ${{ steps.check_size.outputs.should_use_api }}
run: |
npm run ci -- --gh-token "$GITHUB_TOKEN" --repo "$REPO" \
--workflow-ref "$WORKFLOWREF" --branch "$BRANCHE" \
--ci-workspace-path "$CI_WORKSPACE_PATH" \
--ci-action-path "$CI_ACTION_PATH" \
--should-use-api "$SHOULD_USE_API"
shell: bash
working-directory: ${{ github.action_path }}
39 changes: 39 additions & 0 deletions calc-repo-size/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as GitHub from '@octokit/rest'
import * as Actions from '@actions/core'
import * as Commander from 'commander'
import checkDiskSpace from 'check-disk-space'

const Program = new Commander.Command()

Program.option('--debug', 'output extra debugging', false)
.option('--gh-token <TOKEN>', 'GitHub token', '')
.option('--repo <REPO>', 'A GitHub repository. eg: owner/repo', '')
.option('--ci-workspace-path <PATH>', 'A path to the CI workspace.', '')

Program.parse()

type ProgramOptionsType = {
// eslint-disable-next-line @typescript-eslint/naming-convention
debug: boolean;
// eslint-disable-next-line @typescript-eslint/naming-convention
ghToken: string;
// eslint-disable-next-line @typescript-eslint/naming-convention
repo: string;
// eslint-disable-next-line @typescript-eslint/naming-convention
ciWorkspacePath: string;
}
const ProgramOptions: ProgramOptionsType = Program.opts()

const GitHubInstance = new GitHub.Octokit({auth: ProgramOptions.ghToken})
const RepoSize = GitHubInstance.repos.get({owner: ProgramOptions.repo.split('/')[0], repo: ProgramOptions.repo.split('/')[1]})
.then(Response => Response.data.size)
const DiskFreeSize = checkDiskSpace(ProgramOptions.ciWorkspacePath).then(DiskInfo => DiskInfo.free)

await Promise.all([RepoSize, DiskFreeSize]).then(([RepoSizeVaule, DiskFreeSizeVaule]) => {
Actions.info(`calc-repo-size: RepoSize: ${RepoSizeVaule}; DiskFreeSize: ${DiskFreeSizeVaule}`)
if (RepoSizeVaule * 1000 < DiskFreeSizeVaule) {
Actions.setOutput('should_use_api', 'false')
} else {
Actions.setOutput('should_use_api', 'true')
}
})
46 changes: 46 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as Commander from 'commander'
import type * as Types from './sources/types'
import {ExportArgs, IsDebug} from './sources/debug'
import {ReplaceStringWithBooleanInObject} from './sources/utility'
import {GetLatestWorkflowTime} from './sources/actions'
import {ListBranches} from './sources/branches'
import {GetChangedFilesFromSHAToHead, GetCommitSHAFromLatestWorkflowTime} from './sources/commits'
import {PurgeRequestManager} from './sources/requests'

const Program = new Commander.Command()

// Set options.
Program.option('--debug', 'output extra debugging', false)
.option('--gh-token <TOKEN>', 'GitHub token', '')
.option('--repo <REPO>', 'A GitHub repository. eg: owner/repo', '')
.option('--workflow-ref <WORKFLOW_REF>', 'A GitHub workflow ref. eg: refs/heads/master', '')
.option('--branch <BRANCH>', 'A GitHub branch. eg: master', '')
.option('--ci-workspace-path <PATH>', 'A path to the CI workspace.', '')
.option('--ci-action-path <PATH>', 'A path to the CI action.', '')
.option('--should-use-api <TRUE_OR_FALSE>', 'Should use GitHub API?', 'false')

// Initialize Input of the options and export them.
Program.parse()

// Declare the options and print them if the debugging mode is enabled.
const ProgramRawOptions: Types.ProgramOptionsRawType = Program.opts()
if (IsDebug(ProgramRawOptions)) {
ExportArgs(ProgramRawOptions)
}

// Redefine with boolean.
const ProgramOptions = ReplaceStringWithBooleanInObject(ProgramRawOptions) as Types.ProgramOptionsType

// Workflow
const LatestWorkflowRunTime = await GetLatestWorkflowTime(ProgramOptions).then(LatestWorkflowRunTime => LatestWorkflowRunTime)
const Branches = await ListBranches(ProgramOptions).then(Branches => Branches)
const PurgeRequest = new PurgeRequestManager(ProgramOptions)
for (const Branch of Branches) {
// eslint-disable-next-line no-await-in-loop
const CommitSHA = await GetCommitSHAFromLatestWorkflowTime(ProgramOptions, LatestWorkflowRunTime, Branch).then(CommitSHA => CommitSHA)
// eslint-disable-next-line no-await-in-loop
const ChangedFiles = await GetChangedFilesFromSHAToHead(ProgramOptions, CommitSHA, Branch).then(ChangedFiles => ChangedFiles)
PurgeRequest.AddURLs(ChangedFiles, Branch)
}

PurgeRequest.Start()
52 changes: 0 additions & 52 deletions main.ts

This file was deleted.

27 changes: 27 additions & 0 deletions sources/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as GitHub from '@octokit/rest'
import {DateTime} from 'luxon'
import type {ProgramOptionsType} from './types'

/**
* @name GetLatestWorkflowTime
* @description Get the latest workflow time.
* @param {ProgramOptionsType} ProgramOptions The program options.
* @returns {Promise<number>} The latest workflow time in milliseconds.
*/
export async function GetLatestWorkflowTime(ProgramOptions: ProgramOptionsType): Promise<number> {
const GitHubInstance = new GitHub.Octokit({auth: ProgramOptions.ghToken})
const [RepoOwner, RepoName] = ProgramOptions.repo.split('/')
var LatestWorkflowRunTime = Number.MIN_SAFE_INTEGER
const WorkflowRuns = await GitHubInstance.actions.listWorkflowRuns({
owner: RepoOwner, repo: RepoName,
workflow_id: /(?<=^[A-z0-9]+\/[A-z0-9]+\/\.github\/workflows\/).+\.yml(?=@refs\/)/.exec(ProgramOptions.workflowRef)[0],

Check warning

Code scanning / CodeQL

Overly permissive regular expression range Medium

Suspicious character range that is equivalent to [A-Z[]^_`a-z].

Check warning

Code scanning / CodeQL

Overly permissive regular expression range Medium

Suspicious character range that is equivalent to [A-Z[]^_`a-z].
}).then(WorkflowRuns => WorkflowRuns.data.workflow_runs)
for (const WorkflowRun of WorkflowRuns) {
if (WorkflowRun.status === 'completed' && WorkflowRun.conclusion === 'success'
&& DateTime.fromISO(WorkflowRun.updated_at).toMillis() > LatestWorkflowRunTime) {
LatestWorkflowRunTime = DateTime.fromISO(WorkflowRun.updated_at).toMillis()
}
}

return LatestWorkflowRunTime
}
48 changes: 48 additions & 0 deletions sources/branches.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import * as Git from 'simple-git'
import * as GitHub from '@octokit/rest'
import * as Actions from '@actions/core'
import * as Os from 'node:os'
import type * as Types from './types'
import {IsDebug} from './debug'

function CreateGitHubInstance(ProgramOptions: Types.ProgramOptionsType): GitHub.Octokit {
const GitHubInstance = new GitHub.Octokit({auth: ProgramOptions.ghToken})
return GitHubInstance
}

function CreateGitInstance(BasePath: string): Git.SimpleGit {
const GitInstance = Git.simpleGit(BasePath, {maxConcurrentProcesses: Os.cpus().length})
return GitInstance
}

/**
* @name ListBranches
* @description List all branches that should be purged.
* @param {Types.ProgramOptions} ProgramOptions The program options.
* @returns {string[]} A list of branches. The list always contains 'latest' and the current/default branch.
*/
export async function ListBranches(ProgramOptions: Types.ProgramOptionsType): Promise<string[]> {
const Branches: string[] = ['latest']
if (ProgramOptions.shouldUseApi) {
const GitHubInstance = CreateGitHubInstance(ProgramOptions)
const [RepoOwner, RepoName] = ProgramOptions.repo.split('/')
Branches.push((await GitHubInstance.repos.get({owner: RepoOwner, repo: RepoName})).data.default_branch)
const OtherBranches = (await GitHubInstance.repos.listBranches({owner: RepoOwner, repo: RepoName}).then(Branches => Branches.data))
.map(Item => Item.name)
OtherBranches.forEach(Item => Branches.push(ProgramOptions.branch.split(' ').find(Branch => Branch === Item)))
}

if (!ProgramOptions.shouldUseApi) {
const GitInstance = CreateGitInstance(`${ProgramOptions.ciWorkspacePath}/${ProgramOptions.repo.split('/')[1]}`)
Branches.push(await GitInstance.branchLocal().then(Branches => Branches.current))
// Branches[1] is always the current/default branch.
const OtherBranches = (await GitInstance.branchLocal().then(Branches => Branches.all)).filter(Branch => Branch !== Branches[1])
OtherBranches.forEach(Item => Branches.push(ProgramOptions.branch.split(' ').find(Branch => Branch === Item)))
}

if (IsDebug(ProgramOptions)) {
Actions.debug(`ListBranches in branches.ts called: ${JSON.stringify(Branches)}`)
}

return Branches
}
86 changes: 86 additions & 0 deletions sources/commits.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import * as Git from 'simple-git'
import * as GitHub from '@octokit/rest'
import * as Os from 'node:os'
import {DateTime} from 'luxon'
import type * as Types from './types'

function CreateGitHubInstance(ProgramOptions: Types.ProgramOptionsType): GitHub.Octokit {
const GitHubInstance = new GitHub.Octokit({auth: ProgramOptions.ghToken})
return GitHubInstance
}

function CreateGitInstance(BasePath: string): Git.SimpleGit {
const GitInstance = Git.simpleGit(BasePath, {maxConcurrentProcesses: Os.cpus().length})
return GitInstance
}

/**
* @name ListCommitsFromLatestWorkflowTime
* @description List commits using GitHub Octokit or simple-git.
* @param {Types.ProgramOptionsType} ProgramOptions The program options.
* @param {number} LatestWorkflowRunTime The latest workflow time in milliseconds.
* @param {string} Branch The branch or tag name.
* @returns {Promise<string>} SHA of the latest commit.
*/
export async function GetCommitSHAFromLatestWorkflowTime(ProgramOptions: Types.ProgramOptionsType, LatestWorkflowRunTime: number, Branch: string): Promise<string> {
var MatchedCommitTimeAddress = 0
if (ProgramOptions.shouldUseApi) {
const GitHubInstance = CreateGitHubInstance(ProgramOptions)
const GitHubListCommitsRaw = await GitHubInstance.repos.listCommits({
owner: ProgramOptions.repo.split('/')[0],
repo: ProgramOptions.repo.split('/')[1],
sha: Branch,
since: DateTime.fromMillis(LatestWorkflowRunTime).toISO(),
}).then(Response => Response.data)
for (const CommitRaw of GitHubListCommitsRaw) {
if (DateTime.fromISO(CommitRaw.commit.author.date).toMillis() < LatestWorkflowRunTime) {
break
}

MatchedCommitTimeAddress++
}

return GitHubListCommitsRaw[MatchedCommitTimeAddress].sha
}

if (!ProgramOptions.shouldUseApi) {
const GitInstance = CreateGitInstance(`${ProgramOptions.ciWorkspacePath}/${ProgramOptions.repo.split('/')[1]}`)
const GitLogRaw = (await GitInstance.log(['--date=iso-strict', `--since=${DateTime.fromMillis(LatestWorkflowRunTime).toISO()}`])).all
for (const CommitRaw of GitLogRaw) {
if (DateTime.fromISO(CommitRaw.date).toMillis() < LatestWorkflowRunTime) {
break
}

MatchedCommitTimeAddress++
}

return GitLogRaw[MatchedCommitTimeAddress].hash
}
}

/**
* @name GetChangedFilesFromSHAToBranchLatestCommits
* @description Get changed files from a commit to the latest commit in a branch.
* @param {Types.ProgramOptionsType} ProgramOptions The program options.
* @param {stirng} CommitSHA The commit SHA.
* @param {string} Branch The branch name.
* @returns {Promise<string[]>} A list of changed files.
*/
export async function GetChangedFilesFromSHAToHead(ProgramOptions: Types.ProgramOptionsType, CommitSHA: string, Branch: string): Promise<string[]> {
if (ProgramOptions.shouldUseApi) {
const GitHubInstance = CreateGitHubInstance(ProgramOptions)
const GitHubComparingRaw = await GitHubInstance.repos.compareCommits({
owner: ProgramOptions.repo.split('/')[0],
repo: ProgramOptions.repo.split('/')[1],
head: Branch,
base: CommitSHA,
}).then(Response => Response.data)
return GitHubComparingRaw.files.map(File => File.filename)
}

if (!ProgramOptions.shouldUseApi) {
const GitInstance = CreateGitInstance(`${ProgramOptions.ciWorkspacePath}/${ProgramOptions.repo.split('/')[1]}`)
const ChangedFiles = (await GitInstance.diff(['--name-only', `${CommitSHA}...${Branch}`])).split('\n')
return ChangedFiles
}
}
11 changes: 11 additions & 0 deletions sources/debug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as Actions from '@actions/core'
import type * as Types from './types'

export function IsDebug(Args: Types.ProgramOptionsType | Types.ProgramOptionsRawType) {
const ArgsDebug = typeof Args.debug === 'string' ? Args.debug === 'true' : Args.debug
return Actions.isDebug() || ArgsDebug
}

export function ExportArgs(Args: Types.ProgramOptionsType | Types.ProgramOptionsRawType) {
Actions.debug(`ProgramOptions: ${JSON.stringify(Args).replace(/(?<=,"ghToken":")[^"]+/, '***')}`)
}
Loading

0 comments on commit 3b7601d

Please sign in to comment.