# #
# @type github workflow
# @author Aetherinox
# @url https://github.com/Aetherinox
# @usage pull request auto-scan
# scans all of the files related to a particular pull request
# if the code in the files being submitted contains code that is forbidden,
# a report is generated and posted as a comment in the PR.
# sends notifications to discord using webhooks
#
# @notes skips title changes if the author of the PR is renovate[bat]
#
# @secrets secrets.SELF_TOKEN self github personal access token (fine-grained)
# secrets.SELF_TOKEN_CL self github personal access token (classic)
# secrets.NPM_TOKEN self npmjs access token
# secrets.PYPI_API_TOKEN self Pypi API token (production site) - https://pypi.org/
# secrets.PYPI_API_TEST_TOKEN self Pypi API token (test site) - https://test.pypi.org/
# secrets.SELF_DOCKERHUB_TOKEN self Dockerhub token
# secrets.CODECOV_TOKEN codecov upload token for nodejs projects
# secrets.MAXMIND_GELITE_TOKEN maxmind API token
# secrets.CF_ACCOUNT_ID cloudflare account id
# secrets.CF_ACCOUNT_TOKEN cloudflare account token
# secrets.ORG_TOKEN org github personal access token (fine-grained)
# secrets.ORG_TOKEN_CL org github personal access token (classic)
# secrets.ORG_DOCKERHUB_TOKEN org dockerhub secret
# secrets.ORG_GITEA_TOKEN org gitea personal access token (classic) with package:write permission
# secrets.BOT_GPG_KEY_ASC bot gpg private key (armored) | BEGIN PGP PRIVATE KEY BLOCK
# secrets.BOT_GPG_KEY_B64 bot gpg private key (binary) converted to base64
# secrets.BOT_GPG_PASSPHRASE bot gpg private key passphrase
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_RELEASES discord webhook to report release notifications from github to discord
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_WORKFLOWS discord webhook to report workflow notifications from github to discord
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_UPDATES discord webhook to report activity notifications from github to discord
#
# @local these workflows can be tested locally through the use of `act`
# https://github.com/nektos/act
# Extract act to folder
# Add system env var with path to act.exe
# Run the commands:
# git pull https://github.com/username/repo
# act -W .github/workflows/issues-scan.yml -P ubuntu-latest=catthehacker/ubuntu:full-22.04
# act -W .github/workflows/issues-scan.yml -s TOKEN_CL=XXXXXXXXXX --pull=false
# #
name: 'π« PR βΊ Scan'
run-name: 'π« PR βΊ Scan'
# #
# triggers
# #
on:
pull_request_target:
branches:
- main
# #
# environment variables
# #
env:
DISCORD_BOT_NAME: 'Europa'
DISCORD_BOT_AVATAR: 'https://i.imgur.com/UqwMom1.jpeg'
DISCORD_BOT_EMBED_AUTHOR_ICON: 'https://avatars.githubusercontent.com/u/200161462'
DISCORD_BOT_EMBED_THUMBNAIL: 'https://avatars.githubusercontent.com/u/200161462'
LABEL_CHECK_STATUS_FAILED: AC βΊ Failed
LABEL_CHECK_REVIEW_READY: AC βΊ Passed
LABEL_CHECK_CHANGES_REQ: AC βΊ Changes Required
LABEL_CHECK_REVIEW_REQ: AC βΊ Review Required
LABEL_CHECK_REBASE_REQ: AC βΊ Needs Rebase
LABEL_CHECK_SECURITY_ERR: AC βΊ Security Warning
LABEL_CHECK_STATUS_CHGMADE: AC βΊ Changes Made
LABEL_CHECK_SCAN_SKIPPED: AC βΊ Skipped Scan
LABEL_TYPE_PR: Type βΊ Pull Request
LABEL_TYPE_DEPENDENCY: Type βΊ Dependency
LABEL_TYPE_GITACTION: Type βΊ Git Action
LABEL_TYPE_MAINTENANCE: Type βΊ Lock Maintenance
ASSIGN_USER: Aetherinox
BOT_NAME_1: EuropaServ
BOT_NAME_2: BinaryServ
BOT_NAME_DEPENDABOT: dependabot[bot]
BOT_NAME_RENOVATE: renovate[bot]
LABELS_JSON: |
[
{ "name": "AC βΊ Changes Made", "color": "8F1784", "description": "Requested changes have been made and are pending a re-scan" },
{ "name": "AC βΊ Changes Required", "color": "8F1784", "description": "Requires changes to be made to the package before being accepted" },
{ "name": "AC βΊ Failed", "color": "a61f2d", "description": "Autocheck failed to run through a complete cycle, requires investigation" },
{ "name": "AC βΊ Needs Rebase", "color": "8F1784", "description": "Due to the permissions on the requesting repo, this pull request must be rebased by the author" },
{ "name": "AC βΊ Passed", "color": "146b4a", "description": "Ready to be reviewed" },
{ "name": "AC βΊ Review Required", "color": "8F1784", "description": "PR needs to be reviewed by another person, after the requested changes have been made" },
{ "name": "AC βΊ Security Warning", "color": "761620", "description": "Does not conform to developer policies, or includes potentially dangerous code" },
{ "name": "AC βΊ Skipped Scan", "color": "8F1784", "description": "Author has skipped code scan" },
{ "name": "Status βΊ Duplicate", "color": "75536b", "description": "Issue or pull request already exists" },
{ "name": "Status βΊ Accepted", "color": "2e7539", "description": "This pull request has been accepted" },
{ "name": "Status βΊ Autoclosed", "color": "3E0915", "description": "Originally stale and was autoclosed for no activity" },
{ "name": "Status βΊ Denied", "color": "ba4058", "description": "Pull request has been denied" },
{ "name": "Status βΊ Locked", "color": "550F45", "description": "Automatically locked by AdminServ for a prolonged period of inactivity" },
{ "name": "Status βΊ Need Info", "color": "2E3C4C", "description": "Not enough information to resolve" },
{ "name": "Status βΊ No Action", "color": "030406", "description": "Closed without any action being taken" },
{ "name": "Status βΊ Pending", "color": "984b12", "description": "Pending pull request" },
{ "name": "Status βΊ Released", "color": "1b6626", "description": "Issues or PR has been implemented and is now live" },
{ "name": "Status βΊ Reopened", "color": "8a6f14", "description": "A previously closed PR which has been re-opened" },
{ "name": "Status βΊ Review", "color": "9e1451", "description": "Currently pending review" },
{ "name": "Status βΊ Stale", "color": "928282", "description": "Has not had any activity in over 30 days" },
{ "name": "Type βΊ Bug", "color": "9a2c2c", "description": "Something isn't working" },
{ "name": "Type βΊ Dependency", "color": "243759", "description": "Item is associated to dependency" },
{ "name": "Type βΊ Lock Maintenance", "color": "FBCA04", "description": "Sync package-lock.json" },
{ "name": "Type βΊ Docs", "color": "0e588d", "description": "Improvements or modifications to docs" },
{ "name": "Type βΊ Feature", "color": "3c4e93", "description": "Feature request" },
{ "name": "Type βΊ Git Action", "color": "030406", "description": "GitHub Action / workflow" },
{ "name": "Type βΊ Pull Request", "color": "8F1784", "description": "Normal pull request" },
{ "name": "Type βΊ Roadmap", "color": "8F1784", "description": "Feature or bug currently planned for implementation" },
{ "name": "Type βΊ Internal", "color": "A51994", "description": "Assigned items are for internal developer use" },
{ "name": "Build βΊ Desktop", "color": "c7ca4a", "description": "Specific to desktop" },
{ "name": "Build βΊ Linux", "color": "c7ca4a", "description": "Specific to Linux" },
{ "name": "Build βΊ MacOS", "color": "c7ca4a", "description": "Specific to MacOS" },
{ "name": "Build βΊ Mobile", "color": "c7ca4a", "description": "Specific to mobile" },
{ "name": "Build βΊ Web", "color": "c7ca4a", "description": "Specific to web" },
{ "name": "Build βΊ Windows", "color": "c7ca4a", "description": "Specific to Windows" },
{ "name": "βΊ API", "color": "F99B50", "description": "Plugin API, CLI, browser JS API" },
{ "name": "βΊ Auto-type", "color": "9141E0", "description": "Auto-type functionality in desktop apps" },
{ "name": "βΊ Browser", "color": "9141E0", "description": "Browser plugins and passing data to <=> from app" },
{ "name": "βΊ Customization", "color": "E3F0FC", "description": "Customizations: plugins, themes, configs" },
{ "name": "βΊ Design", "color": "FA70DE", "description": "Design related queries" },
{ "name": "βΊ Dist", "color": "FA70DE", "description": "Installers and other forms of software distribution" },
{ "name": "βΊ Enterprise", "color": "11447a", "description": "Issues about collaboration, administration, and so on" },
{ "name": "βΊ Hardware", "color": "5a7503", "description": "YubiKey, other tokens, biometrics" },
{ "name": "βΊ Import/Export", "color": "F5FFCC", "description": "Import from and export to different file formats" },
{ "name": "βΊ Improvement", "color": "185c98", "description": "Enhance an existing feature" },
{ "name": "βΊ Performance", "color": "006b75", "description": "Web and desktop performance issues" },
{ "name": "βΊ Plugin Request", "color": "FCE9CA", "description": "Requested changes should be implemented as a plugin" },
{ "name": "βΊ Security", "color": "F75D39", "description": "Security issues" },
{ "name": "βΊ Self-Hosting", "color": "fad8c7", "description": "Self-hosting installations and configs" },
{ "name": "βΊ Storage", "color": "5319e7", "description": "Storage providers: Dropbox, Google, WebDAV, etc." },
{ "name": "βΊ Updater", "color": "1BADDE", "description": "Auto-updater issues" },
{ "name": "βΊ UX", "color": "1BADDE", "description": "UX and usability" },
{ "name": "βΊ Website", "color": "fef2c0", "description": "Website related issues" },
{ "name": "β Urgent", "color": "a8740e", "description": "Requires urgent attention" },
{ "name": "β Announcement", "color": "DB4712", "description": "Announcements" },
{ "name": "π° Progress Report", "color": "392297", "description": "Development updates" },
{ "name": "π¦ Release", "color": "277542", "description": "Release announcements" },
{ "name": "βοΈ Poll", "color": "972255", "description": "Community polls" },
{ "name": "β Question", "color": "FFFFFF", "description": "All questions" }
]
# #
# jobs
# #
jobs:
# #
# Job βΊ PR Scan
#
# automatically scan a pull request once it is submitted
# #
job-pr-scan:
name: >-
π« Issues βΊ Autoscan
runs-on: ubuntu-latest
# runs-on: apollo-x64
timeout-minutes: 5
permissions:
contents: read
actions: read
issues: write
pull-requests: read
steps:
# #
# PR βΊ Scan βΊ Checkout
# #
- name: 'βοΈ Checkout'
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# PR βΊ Scan βΊ Job Information
# #
- name: >-
π Load Job
uses: qoomon/actions--context@v4
id: 'context'
# #
# PR βΊ Scan βΊ Start
# #
- name: >-
β
Start
run: |
echo "β"
echo "βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ"
echo " Starting Job ${{ steps.context.outputs.job_name }}"
echo "βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ"
YEAR="$(date +'%Y')"
echo "YEAR=${YEAR}" >> $GITHUB_ENV
NOW="$(date +'%m-%d-%Y %H:%M:%S')" # 02-25-2025 12:49:48
echo "NOW=${NOW}" >> $GITHUB_ENV
NOW_SHORT="$(date +'%m-%d-%Y')" # 02-25-2025
echo "NOW_SHORT=${NOW_SHORT}" >> $GITHUB_ENV
NOW_LONG="$(date +'%m-%d-%Y %H:%M')" # 02-25-2025 12:49
echo "NOW_LONG=${NOW_LONG}" >> $GITHUB_ENV
NOW_DOCKER="$(date +'%Y%m%d')" # 20250225
echo "NOW_DOCKER=${NOW_DOCKER}" >> $GITHUB_ENV
NOW_DOCKER_TS="$(date -u +'%FT%T.%3NZ')" # 2025-02-25T12:50:11.569Z
echo "NOW_DOCKER_TS=${NOW_DOCKER_TS}" >> $GITHUB_ENV
SHA1="$(git rev-parse HEAD)" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1=${SHA1}" >> $GITHUB_ENV
SHA1_GH="$(echo ${GITHUB_SHA})" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1_GH=${SHA1_GH}" >> $GITHUB_ENV
PKG_VER_1DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
echo "PKG_VER_1DIGIT=${PKG_VER_1DIGIT}" >> $GITHUB_ENV
PKG_VER_2DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
echo "PKG_VER_2DIGIT=${PKG_VER_2DIGIT}" >> $GITHUB_ENV
echo "βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ"
echo "β"
echo "β"
sudo apt -qq update
sudo apt -qq install tree
echo "β"
echo "β"
echo "βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ"
echo "β"
echo "β"
echo " Runner .............. ${{ runner.name }}"
echo " Workflow ............ ${{ github.workflow }} (#${{ github.workflow_ref }})"
echo " Run Number .......... ${{ github.run_number }}"
echo " Ref ................. ${{ github.ref }}"
echo " Ref Name ............ ${{ github.ref_name }}"
echo " Event Name .......... ${{ github.event_name }}"
echo " Repo ................ ${{ github.repository }}"
echo " Repo Owner .......... ${{ github.repository_owner }}"
echo " Run ID .............. https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
echo " Triggered By ........ ${{ github.actor }}"
echo " SHA 1 (GITHUB_SHA) .. ${GITHUB_SHA}"
echo " SHA 2 (github.sha) .. ${{ github.sha }}"
echo " SHA 3 (env.SHA1) .... ${SHA1}"
echo " SHA 4 (env.SHA1_GH) . ${SHA1_GH}"
echo " Workspace ........... ${{ github.workspace }}"
echo " PWD ................. ${PWD}"
echo " Job Name ............ ${{ steps.context.outputs.job_name }}"
echo " Job ID .............. ${{ steps.context.outputs.job_id }}"
echo " Job URL ............. ${{ steps.context.outputs.job_url }}"
echo " Run ID .............. ${{ steps.context.outputs.run_id }}"
echo " Run Attempt ......... ${{ steps.context.outputs.run_attempt }}"
echo " Run Number .......... ${{ steps.context.outputs.run_number }}"
echo " Run URL ............. ${{ steps.context.outputs.run_url }}"
echo " Run Env ............. ${{ steps.context.outputs.environment }}"
echo " Run Env URL ......... ${{ steps.context.outputs.environment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Deployment URL .. ${{ steps.context.outputs.deployment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Runner Name ..... ${{ steps.context.outputs.runner_name }}"
echo " Run Runner ID ....... ${{ steps.context.outputs.runner_id }}"
echo " Year ................ ${YEAR}"
echo " Now ................. ${NOW}"
echo " Now (Short) ......... ${NOW_SHORT}"
echo " Now (Long) .......... ${NOW_LONG}"
echo " Now (Docker) ........ ${NOW_DOCKER}"
echo " Now (Docker TS) ..... ${NOW_DOCKER_TS}"
echo "β"
echo "β"
echo "βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ"
echo "β"
echo "β"
tree -I node_modules -I .git
echo "β"
echo "β"
echo "βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ"
echo "β"
echo "β"
# #
# PR Scan βΊ Labels βΊ Verify Existing
#
# check if repo has all of the needed issue / pr labels; create label if not exists
#
# action needed if using 'pull_request' and 'issue_comment'
# to get the pull request, you would normally use ${{ github.event.number }}
# however this isnt available for 'issue_comment'
# #
- name: >-
π« Labels βΊ Verify Existing
uses: actions/github-script@v7
with:
github-token: ${{ secrets.ADMINSERV_TOKEN_CL || github.token }}
script: |
const labels = JSON.parse( process.env.LABELS_JSON );
for ( const label of labels )
{
try
{
await github.rest.issues.createLabel(
{
owner: context.repo.owner,
repo: context.repo.repo,
name: label.name,
description: label.description || 'No Description',
color: label.color
});
}
catch ( err )
{
if ( err.status === 422 )
{
console.log( `Label '${label.name}' already exists. Skipping.` );
}
else
{
console.error( `Error creating label '${label.name}': ${err}` );
}
}
}
# #
# PR Scan βΊ Assign Pull Request ID to variable
#
# get id (number) for pr when submitted
# #
- name: >-
#οΈβ£ Pull-Request ID βΊ Set
id: task_prscan_issue_num_set
uses: actions/github-script@v7
with:
github-token: ${{ secrets.ADMINSERV_TOKEN_CL || github.token }}
script: |
if ( context.issue.number )
{
// Return issue number if present
return context.issue.number;
}
else
{
const data = (
await github.rest.repos.listPullRequestsAssociatedWithCommit(
{
commit_sha: context.sha,
owner: context.repo.owner,
repo: context.repo.repo,
})
).data[0];
if (data) {
return data.number;
} else {
return '32';
}
}
result-encoding: string
# #
# PR Scan βΊ Pull-Request ID βΊ Print
#
# prints the pr number detected
# #
- name: >-
#οΈβ£ Pull-Request ID βΊ Print
run: |
echo '${{ steps.task_prscan_issue_num_set.outputs.result }}'
# #
# PR Scan βΊ Checkout
# #
- name: >-
βοΈ Checkout
uses: actions/checkout@v4
if: |
( github.event_name == 'pull_request_target' ) || ( github.event_name == 'pull_request' ) || ( github.event_name == 'issue_comment' && contains( github.event.comment.html_url, '/pull/' ) && contains( github.event.comment.body, '/rescan' ) )
with:
token: ${{ secrets.ADMINSERV_TOKEN_CL || github.token }}
fetch-depth: 0
ref: "refs/pull/${{ steps.task_prscan_issue_num_set.outputs.result }}/merge"
# #
# PR Scan βΊ Setup NodeJS
# #
- name: >-
βοΈ Setup Node
uses: actions/setup-node@v6
# #
# PR Scan βΊ Get List of Changed Files
#
# Effortlessly track all changed files and directories relative to a target branch,
# the current branch (preceding commit or the last remote commit), multiple branches,
# or custom commits returning relative paths from the project root using this
# GitHub action.
# #
- name: >-
π Get changed files
id: task_prscan_changed_files_get
uses: tj-actions/changed-files@v46
with:
separator: ","
# #
# PR Scan βΊ List All Changed Files
# #
- name: >-
π List all added files
id: task_prscan_added_files_get
run: |
for file in ${CHANGED_FILES}; do
echo "$file was changed"
done
env:
ADDED_FILES: ${{ steps.task_prscan_changed_files_get.outputs.added_files }}
MODIFIED_FILES: ${{ steps.task_prscan_changed_files_get.outputs.modified_files }}
CHANGED_FILES: ${{ steps.task_prscan_changed_files_get.outputs.all_changed_files }}
COUNT_ADDED: ${{ steps.task_prscan_changed_files_get.outputs.added_files_count }}
COUNT_MODIFIED: ${{ steps.task_prscan_changed_files_get.outputs.modified_files_count }}
COUNT_DELETED: ${{ steps.task_prscan_changed_files_get.outputs.deleted_files_count }}
COUNT_RENAMED: ${{ steps.task_prscan_changed_files_get.outputs.renamed_files_count }}
COUNT_COPIED: ${{ steps.task_prscan_changed_files_get.outputs.copied_files_count }}
# #
# PR Scan βΊ List Directories / File Structure
# #
- name: >-
π List Directories
run: |
ls
# #
# PR Scan βΊ Autocheck
# #
- name: >-
βοΈ Run Autocheck
uses: actions/github-script@v7
with:
github-token: ${{ secrets.ADMINSERV_TOKEN_CL }}
script: |
console.log('Running Autoscan')
console.log(JSON.stringify(context, null, 4));
console.log(JSON.stringify(github, null, 4));
let ct = context;
/* #
# Example PR used for local act testing
# Uncomment to use in local env
#
# can be tested using act:
# - https://github.com/nektos/act
# command:
# git pull https://github.com/username/repo
# act -W .github/workflows/issues-scan.yml -P ubuntu-latest=catthehacker/ubuntu:full-22.04
# act -W .github/workflows/issues-scan.yml -s TOKEN_CL=XXXXXXXXXX --pull=false
# */
/*
ct = {
"issue": {
"number": 32
},
"repo": {
"owner": "Aetherinox",
"repo": "TheRepoName"
},
"payload": {
"action": "synchronize",
"after": "f087c5bea8800f41018ef328463531ea247547ef",
"before": "a8bdd791b80b2fbb78169234690ccb61b9d014f1",
"number": 32,
"organization": {
"avatar_url": "https://avatars.githubusercontent.com/u/200161462?v=4",
"events_url": "https://api.github.com/orgs/Aetherinox/events",
"hooks_url": "https://api.github.com/orgs/Aetherinox/hooks",
"issues_url": "https://api.github.com/orgs/Aetherinox/issues",
"login": "Aetherinox",
"members_url": "https://api.github.com/orgs/Aetherinox/members{/member}",
"public_members_url": "https://api.github.com/orgs/Aetherinox/public_members{/member}",
"repos_url": "https://api.github.com/orgs/Aetherinox/repos",
"url": "https://api.github.com/orgs/Aetherinox"
},
"pull_request": {
"created_at": "2025-03-17T23:32:22Z",
"updated_at": "2025-03-17T23:32:22Z",
"url": "https://api.github.com/repos/Aetherinox/TheRepoName",
"title": "Test PR Workflow",
"head": {
"ref": "main"
},
"base": {
"ref": "main"
},
"user": {
"login": "Aetherinox"
},
"labels": [
{
"color": "146b4a",
"default": false,
"description": "Ready to be reviewed",
"id": 7821944832,
"name": "AC βΊ Passed",
"node_id": "LA_kwDONW-GkM8AAAAB0jloAA",
"url": "https://api.github.com/repos/Aetherinox/TheRepoName/labels/AC%20%E2%80%BA%20Passed"
},
{
"color": "8F1784",
"default": false,
"description": "Normal pull request",
"id": 7821944963,
"name": "Type βΊ Pull Request",
"node_id": "LA_kwDONW-GkM8AAAAB0jlogw",
"url": "https://api.github.com/repos/Aetherinox/TheRepoName/labels/Type%20%E2%97%A6%20Pull%20Request"
}
]
},
},
"eventName": "pull_request_target",
"sha": "c938f7a21247f69b29cf352d0c6890a63f260d47",
"ref": "refs/heads/main",
"workflow": "π« Issues βΊ Scan",
"action": "task_prscan_run",
"actor": "renovate[bot]",
"job": "job-pr-autoscan",
"runNumber": 45,
"runId": 13911964505,
"apiUrl": "https://api.github.com",
"serverUrl": "https://github.com",
"graphqlUrl": "https://api.github.com/graphql"
}
*/
/* #
# if local env ct isn't used, set ct to context for production
# */
if (!ct) {
ct = context;
}
const fs = require( 'fs' );
const escape_html = ( unsafe ) => unsafe.replace( /&/g, '&' ).replace( //g, '>' ).replace( /"/g, '"' ).replace( /'/g, ''' );
const labels = [];
/* #
# Get existing labels and add to list
# */
const labelsExisting = await github.rest.issues.listLabelsOnIssue({
issue_number: ct.issue.number,
owner: ct.repo.owner,
repo: ct.repo.repo
})
labelsExisting.data.forEach(({ name }) => {
labels.push(name);
});
const files_List = `${{ steps.task_prscan_changed_files_get.outputs.all_changed_files }}` || ''
const files_Array = files_List.split(',')
const branch_ref = `${ ct.payload.pull_request.head.ref }`
let message = [ "\n
\n" ]
message.push ( "## Automatic Self-Check - #" + ct.issue.number + "\n" );
message.push ( `The details of our automated scan for your pull request are listed below. If our scan detected errors, they must be corrected before this pull request will be advanced to the review stage:\n` );
message.push ( "\n
\n\n---\n\n
\n\n" );
message.push ( "### About\nThis pull request includes the following information:" );
let bHasError = false;
let bHasWarning = false;
let date = new Date( `${ ct.payload.pull_request.created_at }` );
date.toISOString( )
const actor = '${{ github.actor }}';
const dateTimeformat = ( date ) =>
{
let month = date.getMonth( ) + 1;
month = month.toString( ).padStart( 2, '0' );
let day = date.getDate( ).toString( ).padStart( 2, '0' );
let year = date.getFullYear( ).toString( ).padStart( 2, '0' );
let hours = date.getHours();
let minutes = date.getMinutes();
let x = hours >= 12 ? 'PM' : 'AM';
hours = hours % 12;
hours = hours ? hours : 12;
minutes = minutes.toString( ).padStart( 2, '0' );
let mergeTime = month + '.' + day + '.' + year + ' ' + hours + ':' + minutes + ' ' + x;
return mergeTime;
}
let date_created = dateTimeformat( date ) + " UTC";
/* #
# ct.payload.pull_request.base.repo.owner.login
# */
let md_table =
`
| Category | Value |
| --- | --- |
| Title | [ ` + ct.payload.pull_request.title + ` ](https://github.com/` + ct.repo.owner + `/` + ct.repo.repo + `/pull/` + ct.payload.pull_request.number + `) |
| Created | [ ` + date_created + ` ](https://worldtimebuddy.com) |
| ID | ` + ct.payload.pull_request.html_url + ` |
| Author | [ ` + ct.payload.pull_request.user.login + ` ](https://github.com/` + ct.repo.owner + `/) |
| Repo | [ ` + ct.repo.repo + ` ](https://github.com/` + ct.repo.owner + `/` + ct.repo.repo + `) |
| Branch | [ ` + ct.payload.pull_request.head.ref + `](https://github.com/` + ct.repo.owner + `/` + ct.repo.repo + `/tree/` + ct.payload.pull_request.head.ref + `) β [ ` + ct.payload.pull_request.base.ref + `](https://github.com/` + ct.repo.owner + `/` + ct.repo.repo + `/tree/` + ct.payload.pull_request.base.ref + `) |
| Added Files | ${{ steps.task_prscan_changed_files_get.outputs.added_files_count }} |
| Modified Files | ${{ steps.task_prscan_changed_files_get.outputs.all_modified_files_count }} |
| Renamed Files | ${{ steps.task_prscan_changed_files_get.outputs.renamed_files_count }} |
| Copied Files | ${{ steps.task_prscan_changed_files_get.outputs.deleted_files_count }} |
| Deleted Files | ${{ steps.task_prscan_changed_files_get.outputs.deleted_files_count }} |
`;
message.push ( md_table );
let error_Generic = "\n" +
"- `MyPlugin`\n" +
"- `MyPluginSettings`\n" +
"- `SampleSettings`\n" +
"- `SampleSettingTab`\n" +
"- `SampleModal`\n"
let warn_BadWords = "\n" +
"- `General`\n" +
"- `Settings`\n"
/*
Loop files
*/
const files_skipped = [];
/*
List of files to skip check
Entries are CASE sensitive
For folders, append / at the end of the parent directory
*/
const type_dependency =
[
"dependabot/npm_and_yarn",
"renovate/github_actions",
"renovate/testing-library",
"renovate/electron",
"renovate/aetherinox/noxenv",
"renovate/playwright",
"renovate/types",
"renovate/@types",
"renovate/eslint",
"renovate/stylistic",
"renovate/jimp",
"renovate/custom-electron-prompt",
"renovate/moment",
"renovate/semver",
"renovate/toasted",
"renovate/uuid"
];
const type_gitaction =
[
"dependabot/github_actions",
"renovate/github_actions"
];
const type_maint =
[
"renovate/lock-file-maintenance"
];
const files_skipList =
[
".github",
".gitea",
".gitignore",
"LICENSE",
".md",
".yml",
"plugins.json",
"package.json",
"package-lock.json",
"rollup.config.js",
"index.js",
"gistr.js",
"Docs/",
"tests/"
];
for ( const file of files_Array )
{
const errors = [];
const addError = ( error ) =>
{
errors.push ( `:x: ${error}` );
console.log ( 'Found Issues: ' + error );
bHasError = true;
};
const warnings = [];
const addWarning = ( warning ) =>
{
warnings.push ( `:warning: ${warning}` );
console.log ( 'Found Warnings: ' + warning );
bHasWarning = true;
}
/*
Regex Searches
*/
const file_current = file;
const filesData = fs.readFileSync( file_current, 'utf8' );
const bContainsStyle = /([A-Za-z]+\.style\.[A-Za-z]+)/gi.test( filesData );
const bFuncFetch = /(fetch)\((.*)\)(\[([^\]]*)\])?/gim.test( filesData );
const bVar = /^(?:var|)\s(\w+)\s*=\s*/gm.test( filesData );
const bLookBehind = /\(\?<[=!].*?\)/gmi.test( filesData );
const bMarkdownHtmlNode = /new\s+NodeHtmlMarkdown/gmi.test( filesData );
const bAsTFile = /as\s+TFile/g.test( filesData );
const bAsTFolder = /as\s+TFolder/g.test( filesData );
const bAsAny = /\((.*? as Any\s*)\)/gi.test( filesData );
const bInnerHTML = /^\s?.*[a-zA-Z0-9_]+\.innerHTML*\s?.*$/gm.test( filesData );
const bOuterHTML = /^\s?.*[a-zA-Z0-9_]+\.outerHTML*\s?.*$/gm.test( filesData );
// const bFuncConsoleLog = /(console.log)\((.*)\)(\[([^\]]*)\])?/gim.test( filesData );
const bFuncSetTimeout = /(setTimeout)\((.*)\)(\[([^\]]*)\])?/gim.test( filesData );
const bFuncFS_Chk1 = /(require)\s?\((\s?(?:'|")fs(?:'|"))\s?\)?/gim.test( filesData );
const bFuncFS_Chk2 = /from\s+(?:'|")fs(?:'|")\s?/gim.test( filesData );
const bFuncFS_ExistsSync = /(fs.existsSync)\((.*)\)(\[([^\]]*)\])?/gm.test( filesData );
const bFuncFS_MkdirSync = /(fs.mkdirSync)\((.*)\)(\[([^\]]*)\])?/gm.test( filesData );
const bFoundBadWord = /(?:'|").*(Settings|General).*(?:'|")?/gmi.test( filesData );
const bContainsGeneric = /(?:^|(?<= ))(MyPlugin|MyPluginSettings|SampleSettings|SampleSettingTab|SampleModal|Sample Plugin|my-plugin)(?:(?= )|$)/gim.test( filesData );
const check_depGetUnpinnedLeaf = "app.workspace.getUnpinnedLeaf"
const bFileSkip = files_skipList.some( s => s.includes( file_current ) || file_current.includes( s ) );
if ( bFileSkip == true )
{
files_skipped.push( file_current );
continue;
}
/*
Header
*/
message.push ( "\n
\n\n---\n\n
\n" );
message.push ( "### π " + file_current + "\n" );
message = message.concat( warnings );
/*
Skip File
all contents in the array below will be skipped.
E.g: any file which resides in the .github folder will be skipped.
any file which ends in .yml will be skipped.
*/
/*
( Deprecated ) app.workspace.getUnpinnedLeaf
@usage : obsidian.md
*/
/*
if ( filesData.toLowerCase( ).includes( check_depGetUnpinnedLeaf.toLowerCase( ) ) )
{
addError( "This function is deprecated, use `this.app.workspace.getLeaf( false )` instead" );
}
*/
/*
Using inline style
*/
if ( bContainsStyle == true )
{
addError( "Avoid assigning `inline styles` via JavaScript or in HTML. Move these styles to CSS so that they are adaptable by themes and other plugins." );
}
/*
Using fetch
*/
if ( bFuncFetch == true )
{
addError( "Do not handle http data with `fetch( )`. Use the Obsidian API -> `requestUrl` method instead, which will make sure that network requests work on every platform." );
}
/*
Using var
*/
if ( bVar == true )
{
addError( "Change all instances of `var` to **const** or **let**. var has function-level scope, and leads to bugs." );
}
/*
Using lookbehind
*/
if ( bLookBehind == true )
{
addError( "Lookbehinds are not supported in iOS < 16.4" );
}
/*
Using HTML Node
*/
if ( bMarkdownHtmlNode == true )
{
addError( "Do not use `NodeHtmlMarkdown`. Use Obsidian API -> `htmlToMarkdown` instead." );
}
/*
As TFile
*/
if ( bAsTFile == true )
{
addError( "Do not cast `as TFile`, use `instanceof` instead to check if the item is actually a file / folder" );
}
/*
As TFolder
*/
if ( bAsTFolder == true )
{
addError( "Do not cast `as TFolder`, use `instanceof` instead to check if the item is actually a file / folder" );
}
/*
Casting to Any
*/
if ( bAsAny == true )
{
addError( "Do not cast to `Any`" );
}
/*
innerHTML
*/
if ( bInnerHTML == true )
{
addError( `Using \`innerHTML\` is a security risk.` );
}
/*
outerHTML
*/
if ( bOuterHTML == true )
{
addError( `Using \`outerHTML\` is a security risk.` );
}
/*
setTimeout
*/
if ( bFuncSetTimeout == true )
{
addError( "Do not utilize `setTimeout`, utilize Obsidian API -> `sleep`. E.g: `await sleep( X )`" );
}
/*
require("fs")
*/
if ( bFuncFS_Chk1 == true || bFuncFS_Chk2 == true )
{
addError( "`fs` import only available from Node.js runtime, this will throw errors for users running on mobile" );
}
/*
require("fs") / fs.existsSync
*/
if ( bFuncFS_ExistsSync == true )
{
addError( "`fs` import only available from Node.js runtime, this will throw errors for users running on mobile." );
}
/*
require("fs") / fs.mkdirSync
*/
if ( bFuncFS_MkdirSync == true )
{
addError( "`fs` import only available from Node.js runtime, this will throw errors for users running on mobile." );
}
/*
Generic Calls
*/
if ( bContainsGeneric == true )
{
addError( "Rename sample classes to something that makes sense. You are not allowed to have names such as: " + error_Generic );
}
/*
console.log found
*/
/*
if ( bFuncConsoleLog == true )
{
addWarning( "Avoid unnecessary logging or ensure logging only occurs in development environment." );
}
*/
/*
Bad words found
*/
if ( bFoundBadWord == true && file != "package.json" && file != "manifest.json" )
{
addWarning( "A restricted word was found in your code. Generic words are not allowed in strings such as: " + warn_BadWords );
}
if ( errors.length > 0 || warnings.length > 0 )
{
/*
Errors
*/
if ( errors.length > 0 )
{
message.push ( "\n\n\n> [!CAUTION]\n> Errors must be fixed prior to a pull request being reviewed and accepted.
The file `" + file + "` contains the following errors:\n\n
\n\n" );
message = message.concat( errors );
}
/*
Warnings
*/
if ( warnings.length > 0 )
{
if ( errors.length > 0 )
{
message.push ( "\n
\n
\n" )
}
message.push ( "\n\n\n> [!WARNING]\n> Warnings are suggestions that do not require fixing, but are recommended before this pull request is reviewed and accepted.
The file `" + file + "` contains the following warnings:\n\n
\n\n" );
message = message.concat( warnings );
}
}
else
{
message.push ( "\n\n\n> [!NOTE]\n> The file `" + file + "` contains no errors\n\n
\n\n" );
}
}
if ( files_skipped.length > 0 )
{
message.push ( "\n
\n\n---\n
\n" );
message.push ( "### β Skipped Files\n" );
message.push ( "\n\n\n> [!TIP]\n> The following file(s) have been skipped:\n\n
\n\n" );
for ( const file_skipped of files_skipped )
{
message.push ( "- " + file_skipped );
}
}
/*
footer
*/
message.push ( "\n
\n\n---\n
\n" );
message.push ( `This check was done automatically. Do NOT open a new PR for re-validation. Instead, to trigger this check again, make a change to your PR and wait a few minutes, or close and re-open it.` );
/*
Has Errors
*/
if ( bHasError == true )
{
labels.push( "${{ env.LABEL_CHECK_STATUS_FAILED }}" );
core.setFailed( "Pull Request Failed Autocheck: " + ct.issue.number + ": " + ct.payload.pull_request.title + "." );
}
/*
No Errors
*/
if ( bHasError == false )
{
/*
change pr title
*/
const pr_title = `${ ct.payload.pull_request.title }`;
const pr_title_append = `PR ${ ct.issue.number }:`;
if ( !pr_title.startsWith( pr_title_append ) && actor != "${{ env.BOT_NAME_RENOVATE }}" )
{
await github.rest.pulls.update(
{
owner: ct.repo.owner,
repo: ct.repo.repo,
pull_number: ct.issue.number,
title: `${ pr_title_append } ${ ct.payload.pull_request.title }`
} );
}
if ( !ct.payload.pull_request.labels.filter( label => label.name === "${{ env.LABEL_CHECK_CHANGES_REQ }}" ).length > 0 )
labels.push( "${{ env.LABEL_CHECK_REVIEW_READY }}" );
}
/*
Determine Labels
*/
const bGitaction = type_gitaction.some( s => s.includes( branch_ref ) || branch_ref.includes( s ) );
const bDependency = type_dependency.some( s => s.includes( branch_ref ) || branch_ref.includes( s ) );
const bMaintenance = type_maint.some( s => s.includes( branch_ref ) || branch_ref.includes( s ) );
if ( actor == "${{ env.BOT_NAME_DEPENDABOT }}" && bDependency || actor == "${{ env.BOT_NAME_RENOVATE }}" && bDependency )
labels.push( "${{ env.LABEL_TYPE_DEPENDENCY }}" );
else if ( actor == "${{ env.BOT_NAME_DEPENDABOT }}" && bGitaction || actor == "${{ env.BOT_NAME_RENOVATE }}" && bGitaction )
labels.push( "${{ env.LABEL_TYPE_GITACTION }}" );
else if ( actor == "${{ env.BOT_NAME_DEPENDABOT }}" && bMaintenance || actor == "${{ env.BOT_NAME_RENOVATE }}" && bMaintenance )
labels.push( "${{ env.LABEL_TYPE_MAINTENANCE }}" );
if ( ct.payload.pull_request.labels.filter( label => label.name === "${{ env.LABEL_CHECK_CHANGES_REQ }}" ).length > 0 )
labels.push( "${{ env.LABEL_CHECK_CHANGES_REQ }}" );
if (ct.payload.pull_request.labels.filter(label => label.name === "${{ env.LABEL_CHECK_REBASE_REQ }}" ).length > 0 )
labels.push( "${{ env.LABEL_CHECK_REBASE_REQ }}" );
if ( ct.payload.pull_request.labels.filter(label => label.name === "${{ env.LABEL_CHECK_SECURITY_ERR }}" ).length > 0 )
labels.push( "${{ env.LABEL_CHECK_SECURITY_ERR }}" );
if (ct.payload.pull_request.labels.filter( label => label.name === "${{ env.LABEL_CHECK_STATUS_CHGMADE }}" ).length > 0 )
labels.push( "${{ env.LABEL_CHECK_STATUS_CHGMADE }}" );
if ( ct.payload.pull_request.labels.filter( label => label.name === "${{ env.LABEL_CHECK_SCAN_SKIPPED }}" ).length > 0 )
labels.push( "${{ env.LABEL_CHECK_SCAN_SKIPPED }}" );
labels.push( "${{ env.LABEL_TYPE_PR }}" );
/*
Set Label
*/
await github.rest.issues.setLabels(
{
issue_number: ct.issue.number,
owner: ct.repo.owner,
repo: ct.repo.repo,
labels,
} );
/*
Create Comment
*/
await github.rest.issues.createComment(
{
issue_number: ct.issue.number,
owner: ct.repo.owner,
repo: ct.repo.repo,
body: message.join('\n'),
} );
# #
# Autoscan βΊ Get Weekly Commits
# #
- name: >-
π Get Weekly Commit List
run: |
echo 'WEEKLY_COMMITS<> $GITHUB_ENV
git log --format="[\`%h\`](${{ github.server_url }}/${{ github.repository }}/commit/%H) %s - %an" --since=7.days >> $GITHUB_ENV
echo 'EOF' >> $GITHUB_ENV
# #
# Autoscan βΊ Notify Github βΊ Success
# #
- name: >-
π Send Discord Webhook Message (Success)
uses: tsickert/discord-webhook@v7.0.0
if: success()
with:
username: ${{ env.DISCORD_BOT_NAME }}
avatar-url: ${{ env.DISCORD_BOT_AVATAR }}
webhook-url: ${{ secrets.DISCORD_WEBHOOK_CHAN_GITHUB_TVAPP2_WORKfLOWS }}
embed-title: "βοΈ ${{ github.workflow_ref }}"
embed-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
embed-thumbnail-url: ${{ env.DISCORD_BOT_EMBED_THUMBNAIL }}
embed-description: |
## π« α²ΌIssues βΊ Scan α²Ό${{ job.status == 'success' && 'β
' || 'β' }}
**${{ job.status == 'success' && 'β
Success' || 'β Failure' }}** βΊ Your container just ran the `Issues βΊ Scan` workflow. Every time this workflow is ran, your list of pull requests will be scanned to determine what files have been changed. It will scan each modified file and see if the code conforms with our rules, and will then post a status report inside the pull request that is open.
The PR will be assigned tags depending on the outcome of the code scan. If issues are detected in the code, a list of each file and issue will be posted in the PR.
- Workflow: `${{ github.workflow }} (#${{github.run_number}})`
- Runner: `${{ runner.name }}`
- Triggered By: `${{ github.actor }}`
- Status: `${{ job.status == 'success' && 'β
Successful' || 'β Failed' }}`
## ${{ github.event.pull_request.title }} (${{ github.event.pull_request.number }})
- Pull Request: ${{ github.event.pull_request.html_url }}
- Author: https://github.com/${{ github.event.pull_request.user.login }} (${{ github.event.pull_request.author_association }})
- Repo: https://github.com/${{ github.repository }}
- Branch: `${{ github.head_ref }}`
- Author: https://github.com/${{ github.event.pull_request.user.login }} (${{ github.event.pull_request.author_association }})
- Status: `${{ github.event.pull_request.state }}`
### Scan Results
- Added Files: ${{ steps.task_prscan_added_files_get.outputs.added_files_count }}
- Modified Files: ${{ steps.task_prscan_added_files_get.outputs.all_modified_files_count }}
- Renamed Files: ${{ steps.task_prscan_added_files_get.outputs.renamed_files_count }}
- Copied Files: ${{ steps.task_prscan_added_files_get.outputs.copied_files_count }}
- Deleted Files: ${{ steps.task_prscan_added_files_get.outputs.deleted_files_count }}
embed-color: ${{ job.status == 'success' && '5763719' || '15418782' }}
embed-footer-text: "Completed at ${{ env.NOW }} UTC"
embed-timestamp: "${{ env.NOW_LONG }}"
embed-author-name: "${{ github.event.pull_request.user.login }}"
embed-author-url: "${{ github.event.pull_request.html_url }}"
embed-author-icon-url: "${{ github.event.pull_request.user.avatar_url }}"
# #
# Autoscan βΊ Notify Github βΊ Failure
# #
- name: >-
π Send Discord Webhook Message (Failure)
uses: tsickert/discord-webhook@v7.0.0
if: failure()
with:
username: ${{ env.DISCORD_BOT_NAME }}
avatar-url: ${{ env.DISCORD_BOT_AVATAR }}
webhook-url: ${{ secrets.DISCORD_WEBHOOK_CHAN_GITHUB_TVAPP2_WORKfLOWS }}
embed-title: "βοΈ ${{ github.workflow_ref }}"
embed-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
embed-thumbnail-url: ${{ env.DISCORD_BOT_EMBED_THUMBNAIL }}
embed-description: |
## π« α²ΌIssues βΊ Scan α²Ό${{ job.status == 'success' && 'β
' || 'β' }}
**${{ job.status == 'success' && 'β
Success' || 'β Failure' }}** βΊ Your container just ran the `Issues βΊ Scan` workflow. Every time this workflow is ran, your list of pull requests will be scanned to determine what files have been changed. It will scan each modified file and see if the code conforms with our rules, and will then post a status report inside the pull request that is open.
The PR will be assigned tags depending on the outcome of the code scan. If issues are detected in the code, a list of each file and issue will be posted in the PR.
- Workflow: `${{ github.workflow }} (#${{github.run_number}})`
- Runner: `${{ runner.name }}`
- Triggered By: `${{ github.actor }}`
- Status: `${{ job.status == 'success' && 'β
Successful' || 'β Failed' }}`
## ${{ github.event.pull_request.title }} (${{ github.event.pull_request.number }})
- Pull Request: ${{ github.event.pull_request.html_url }}
- Author: https://github.com/${{ github.event.pull_request.user.login }} (${{ github.event.pull_request.author_association }})
- Repo: https://github.com/${{ github.repository }}
- Branch: `${{ github.head_ref }}`
- Author: https://github.com/${{ github.event.pull_request.user.login }} (${{ github.event.pull_request.author_association }})
- Status: `${{ github.event.pull_request.state }}`
### Scan Results
- Added Files: ${{ steps.task_prscan_added_files_get.outputs.added_files_count }}
- Modified Files: ${{ steps.task_prscan_added_files_get.outputs.all_modified_files_count }}
- Renamed Files: ${{ steps.task_prscan_added_files_get.outputs.renamed_files_count }}
- Copied Files: ${{ steps.task_prscan_added_files_get.outputs.copied_files_count }}
- Deleted Files: ${{ steps.task_prscan_added_files_get.outputs.deleted_files_count }}
embed-color: ${{ job.status == 'success' && '5763719' || '15418782' }}
embed-footer-text: "Completed at ${{ env.NOW }} UTC"
embed-timestamp: "${{ env.NOW_LONG }}"
embed-author-name: "${{ github.event.pull_request.user.login }}"
embed-author-url: "${{ github.event.pull_request.html_url }}"
embed-author-icon-url: "${{ github.event.pull_request.user.avatar_url }}"