From 94670f9c3fbf9a3578cb1b2d8d31b2644277e7ba Mon Sep 17 00:00:00 2001 From: Aetherinox Date: Thu, 11 Jul 2024 16:50:28 -0700 Subject: [PATCH] ci: add workflow scripts --- .github/changelog-configuration.json | 95 +++ .github/dependabot.yml | 37 ++ .github/labeler.yml | 45 ++ .github/workflows/issues-new-assign.yml | 838 ++++++++++++++++++++++++ .github/workflows/labels-create.yml | 151 +++++ 5 files changed, 1166 insertions(+) create mode 100644 .github/changelog-configuration.json create mode 100644 .github/dependabot.yml create mode 100644 .github/labeler.yml create mode 100644 .github/workflows/issues-new-assign.yml create mode 100644 .github/workflows/labels-create.yml diff --git a/.github/changelog-configuration.json b/.github/changelog-configuration.json new file mode 100644 index 0000000..1308884 --- /dev/null +++ b/.github/changelog-configuration.json @@ -0,0 +1,95 @@ +{ + "categories": [ + { + "title": "\n\n
\n\n#### 🪛 Continuous integration", + "labels": [ "kind/ci", "ci" ] + }, + { + "title": "\n\n
\n\n#### 🚀 Features", + "labels": [ "kind/feature", "feature", "feat" ] + }, + { + "title": "\n\n
\n\n#### 🐛 Bugfixes", + "labels": [ "kind/bug", "fix", "bug" ] + }, + { + "title": "\n\n
\n\n#### 🔧 Changes", + "labels": [ "kind/chore", "change", "chore" ] + }, + { + "title": "\n\n
\n\nn#### ✨ Optimizations", + "labels": [ "kind/perf", "optimization", "perf", "optimize" ] + }, + { + "title": "\n\n
\n\n#### 🚨 Security ", + "labels": [ "kind/security", "security" ] + }, + { + "title": "\n\n
\n\n#### 🧹 Housekeeping", + "labels": [ "kind/refactor", "refactor", "style" ] + }, + { + "title": "\n\n
\n\n#### 🐒 Miscellaneous", + "labels": [ "kind/misc", "misc" ] + }, + { + "title": "\n\n
\n\n#### ⛔ Deprecated", + "labels": [ "kind/deprecate", "deprecate" ] + }, + { + "title": "\n\n
\n\n#### ⛔ Removed", + "labels": [ "kind/remove", "remove" ] + }, + { + "title": "\n\n
\n\n#### 📦 Build & Dependencies", + "labels": [ "kind/build", "build", "dependency", "dep", "package" ] + }, + { + "title": "\n\n
\n\n#### ✏️ Docs", + "labels": [ "kind/docs", "doc", "docs", "wiki" ] + }, + { + "title": "\n\n
\n\n#### 🧪 Tests & Demo Vault", + "labels": [ "kind/test", "test", "tests", "vault" ] + } + ], + "sort": "ASC", + "pr_template": "- ${{TITLE_ONLY}} : #{{MERGE_SHA}} @#{{AUTHOR}}", + "empty_template": "- No major changes to address in this release", + "custom_placeholders": [ + { + "name": "TITLE_ONLY", + "source": "TITLE", + "transformer": { + "method": "regexr", + "pattern": "(\\w+(\\(.+\\))?: ?)?(.+)", + "target": "$2 $3" + } + } + ], + "label_extractor": [ + { + "pattern": "^(build|ci|change|chore|doc|docs|wiki|remove|deprecate|security|dependency|dep|package|feat|feature|fix|bug|perf|optimize|optimization|refactor|style|test|tests|vault):(.*)", + "target": "$1", + "on_property": "title" + }, + { + "pattern": "^(build|ci|change|chore|doc|docs|wiki|remove|deprecate|security|dependency|dep|package|feat|feature|fix|bug|perf|optimize|optimization|refactor|style|test|tests|vault){1}(\\([\\w\\-\\.]+\\))?(!)?:(.*)", + "target": "$1", + "on_property": "title" + } + ], + "duplicate_filter": { + "pattern": "github.*", + "on_property": "author", + "method": "match" + }, + "max_tags_to_fetch": 200, + "max_pull_requests": 200, + "max_back_track_time_days": 365, + "exclude_merge_branches": [], + "tag_resolver": { + "method": "semver" + }, + "base_branches": [] +} diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..6f733de --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,37 @@ +# MIT License +# +# Copyright (c) 2024 Aetherinox +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +version: 2 +updates: +- package-ecosystem: npm + directory: "/" + schedule: + interval: daily + labels: + - "Type ◦ Dependency" + +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + labels: + - "Type ◦ Git Action" \ No newline at end of file diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000..4085b2c --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,45 @@ +# Number of labels to fetch (optional). Defaults to 100 +numLabels: 40 +# These labels will not be used even if the issue contains them (optional). +# Pass a blank array if no labels are to be excluded. +# excludeLabels: [] +excludeLabels: + - pinned +# custom configuration to override default behaviour +# control explicitly what gets added and when +custom: + - location: title + keywords: + - '[roadmap]' + labels: + - Type ◦ Roadmap + - location: title + keywords: + - '[road-map]' + labels: + - Type ◦ Roadmap + - location: title + keywords: + - '[bug]' + labels: + - Type ◦ Bug + - location: title + keywords: + - '[issue]' + labels: + - Type ◦ Bug + - location: title + keywords: + - '[feature]' + labels: + - Type ◦ Feature + - location: body + keywords: + - 'request feature' + labels: + - Type ◦ Feature + - location: title + keywords: + - '[request]' + labels: + - Type ◦ Feature \ No newline at end of file diff --git a/.github/workflows/issues-new-assign.yml b/.github/workflows/issues-new-assign.yml new file mode 100644 index 0000000..68ec968 --- /dev/null +++ b/.github/workflows/issues-new-assign.yml @@ -0,0 +1,838 @@ +# --------------------------------------------------------------------------------------- +# @parent : github workflow +# @desc : allows you to auto assign labels to new issues and pull requests +# @author : Aetherinox +# @url : https://github.com/Aetherinox +# +# requires the following labels to be created in your repo: +# - bug +# - feature +# - urgent +# - roadmap +# --------------------------------------------------------------------------------------- + +name: "🎫 Issue › New › Assignment" +run-name: "🎫 Issue › New › Assignment - ${{ github.event.issue.number }}: ${{ github.event.issue.title }}" + +# --------------------------------------------------------------------------------------- +# triggers +# --------------------------------------------------------------------------------------- + +on: + issues: + types: + - reopened + - opened + +# --------------------------------------------------------------------------------------- +# environment variables +# --------------------------------------------------------------------------------------- + +env: + PREFIX_BUG: "Bug" + PREFIX_FEATURE: "Feature" + PREFIX_ROADMAP: "Roadmap" + PREFIX_DEPENDENCY: "Dependency" + PREFIX_PR: "PR" + PREFIX_DOCS: "Docs" + PREFIX_GIT: "Git Action" + PREFIX_URGENT: "Urgent" + + LABEL_BUG: "Type ◦ Bug" + LABEL_FEATURE: "Type ◦ Feature" + LABEL_ROADMAP: "Type ◦ Roadmap" + LABEL_DEPENDENCY: "Type ◦ Dependency" + LABEL_PR: "Type ◦ Pull Request" + LABEL_DOCS: "Type ◦ Docs" + LABEL_GIT: "Type ◦ Git Action" + LABEL_URGENT: "⚠ Urgent" + + ASSIGN_USER: Aetherinox + BOT_NAME_1: AdminServ + BOT_NAME_2: AdminServX + BOT_NAME_3: EuropaServ + BOT_NAME_DEPENDABOT: dependabot[bot] + + LABELS_JSON: | + [ + { "name": "Type ◦ Bug", "color": "9a2c2c", "description": "Something isn't working" }, + { "name": "Type ◦ Dependency", "color": "243759", "description": "Item is associated to dependency" }, + { "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" } + ] + +jobs: + + # --------------------------------------------------------------------------------------- + # Verify Existing Labels + # This job will ensure you have labels already created in your repo. + # + # All labels come from the JSON table LABELS_JSON. + # --------------------------------------------------------------------------------------- + + issues-preconfig-labels: + name: ⚙️ Preconfigure + runs-on: ubuntu-latest + steps: + + - name: "✅ Start" + run: | + echo "Assigning labels and assignees" + + - name: "☑️ Checkout" + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # --------------------------------------------------------------------------------------- + # Check if repo has labels currently added to issues + # --------------------------------------------------------------------------------------- + + - name: 🏷️ Verify Existing Labels + uses: actions/github-script@v7 + with: + 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 || '', + 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}` ); + } + } + } + + # --------------------------------------------------------------------------------------- + # Issues > Assign Labels + # --------------------------------------------------------------------------------------- + + issues-assign-labels: + name: >- + 🏷️ Labels › Assign + needs: + - issues-preconfig-labels + runs-on: ubuntu-latest + permissions: + contents: 'read' + id-token: 'write' + issues: 'write' + environment: + name: Orion + steps: + + + # --------------------------------------------------------------------------------------- + # Get Issue Title + # --------------------------------------------------------------------------------------- + + - name: 🏷️ Get Issue Title + uses: actions/github-script@v7 + id: label_issues_initialize + with: + github-token: ${{ secrets.ADMINSERV_TOKEN_CL }} + script: | + + let iss_title = `${ context.payload.issue.title }`; + + core.setOutput( 'issue_title', iss_title ) + core.info( `Setting env issue title: ${ iss_title }` ) + + console.log( "\n\n" ) + + + # --------------------------------------------------------------------------------------- + # Label > Bug + # --------------------------------------------------------------------------------------- + + - name: 🏷️ ${{ env.PREFIX_BUG }} › Assignment + uses: actions/github-script@v7 + id: label_issues_bug + with: + github-token: ${{ secrets.ADMINSERV_TOKEN_CL }} + script: | + + const issueLabels = await github.rest.issues.listLabelsOnIssue( + { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number + }); + + let add_labels = issueLabels.data.map( label => label.name ); + + let iss_title = `${{ steps.label_issues_initialize.outputs.issue_title }}` || `${ context.payload.issue.title }`; + let iss_body = `${ context.payload.issue.body }`; + let iss_author = `${ context.payload.issue.user.login }`; + + const iss_title_lc = iss_title.toLowerCase( ); + + console.log( "Feat Title .................... " + iss_title ) + console.log( "Feat Output ................... " + `${{ steps.label_issues_initialize.outputs.issue_title }}` ) + console.log( "Feat Payload .................. " + `${ context.payload.issue.title }` ) + + /* + Tags + */ + + const bug_tag = `${{ env.PREFIX_BUG }}:`; + const bug_lbl = `${{ env.LABEL_BUG }}`; + const feat_tag = `${{ env.PREFIX_FEATURE }}:`; + const feat_lbl = `${{ env.LABEL_FEATURE }}`; + const urgn_tag = `${{ env.PREFIX_URGENT }}:`; + const urgn_lbl = `${{ env.LABEL_URGENT }}`; + const road_tag = `${{ env.PREFIX_ROADMAP }}:`; + const road_lbl = `${{ env.LABEL_ROADMAP }}`; + + /* + Bugs + */ + + const words = [ "bug", "broke", "issue", "fail" ]; + const bIncWordT = words.some( s => s.includes( iss_title_lc ) || iss_title_lc.includes( s ) ); + + /* + Find regex based phrases + + Regex: + https://regex101.com/r/Z99Gnq/2 + */ + + const findWordList = /^\b(?:I?\s*have\s*(?:a|an)\s*(?:issue|problem|bug))|(?:will\s*not\s*work)|(?:it\s*is\s*(?:broken|broke|stuck))|(?:found\s*(?:an?|the)\s*(?:bug|issue))|(?:can\s*I\s*fix\s*the\s*(?:bug|issue))|(?:(?:does not|doesn'?t|don'?t|won'?t|can'?t|can\s?not|will\s*not)\s*(?:work|load|function))|(?:it\s*(?:will\s?not|won'?t|can\s?not|can'?t))\s*(?:get|find)\s*the\s*(?:website|site|webpage|page)|(?:the\s*(?:window|frame)\s*is\s*(?:blank|white|empty|missing))\b$/igm; + const bFoundMatchTitle = Boolean( findWordList.test( iss_title ) ); + const bFoundMatchBody = Boolean( findWordList.test( iss_body ) ); + + /* + Do not change a title if the item starts with a PR: # + + Regex: + https://regex101.com/r/JOrqbN/1 + */ + + const bug_findPRTitle = /^PR\s?#?(?:[0-9]*:)/igm; + const bug_bFoundPRTitle = Boolean( bug_findPRTitle.test( iss_title ) ); + + console.log( "Title Lowercase ............... " + iss_title_lc ) + console.log( "Startswith " + bug_tag.toLowerCase( ) + "................ " + iss_title_lc.startsWith( bug_tag.toLowerCase( ) ) ) + console.log( "Title Includes Keyword ........ " + bIncWordT ) + console.log( "Title Includes Regex .......... " + bFoundMatchTitle ) + console.log( "Body Includes Regex ........... " + bFoundMatchBody ) + console.log( "\n" ) + + /* + - Check if issue title matches the issue label "Bug:" + - Check if title contains word in words + */ + + if ( iss_title_lc.startsWith( bug_tag.toLowerCase( ) ) || bIncWordT || bFoundMatchTitle || bFoundMatchBody ) + { + + console.log( "⚠️ " + bug_tag + " ---------------------------------------" ) + console.log( "Already starts with " + bug_tag + " ......... " + iss_title_lc.startsWith( bug_tag.toLowerCase( ) ) ) + console.log( "Already starts with " + feat_tag + " ..... " + iss_title_lc.startsWith( feat_tag.toLowerCase( ) ) ) + console.log( "Already starts with " + urgn_tag + " ...... " + iss_title_lc.startsWith( urgn_tag.toLowerCase( ) ) ) + console.log( "Already starts with " + road_tag + " ..... " + iss_title_lc.startsWith( road_tag.toLowerCase( ) ) ) + + add_labels.push( `${ bug_lbl }` ); + + console.log( `Adding Tag ....................... ${ bug_lbl }` ) + console.log( "\n" ) + + if ( iss_author === `${{ env.BOT_NAME_DEPENDABOT }}` ) + core.info( `Skipping: Detected ${ iss_author }` ) + + // Rename title to contain Bug: + // Make sure issue / pr title doesnt already contain a beginning title tag + + if ( iss_author !== `${{ env.BOT_NAME_DEPENDABOT }}` && !bug_bFoundPRTitle && !iss_title_lc.startsWith( bug_tag.toLowerCase( ) ) && !iss_title_lc.startsWith( feat_tag.toLowerCase( ) ) && !iss_title_lc.startsWith( urgn_tag.toLowerCase( ) ) && !iss_title_lc.startsWith( road_tag.toLowerCase( ) ) ) + { + console.log( "Renaming Title" ) + console.log( `Old Title: .................. ${ iss_title }` ) + + const title = context.payload.issue.title + let title_new = title.replace( /^\s?bug\s*(.*?)\b/gi, '' ); + title_new = title.replace( /^\s?fail\s*(.*?)\b/gi, '' ); + title_new = title.replace( /^\s?issue\s*(.*?)\b/gi, '' ); + iss_title = `${ bug_tag } ${ title_new }`; + } + + console.log( `New Title: ...................... ${ iss_title }` ) + + await github.rest.issues.update( + { + owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, + title: `${ iss_title }`, labels: add_labels + } ); + } + + core.setOutput( 'issue_title', iss_title ) + console.log( "\n\n" ) + + # --------------------------------------------------------------------------------------- + # Label > FEATURE + # --------------------------------------------------------------------------------------- + + - name: 🏷️ ${{ env.PREFIX_FEATURE }} › Assignment + uses: actions/github-script@v7 + id: label_issues_feature + with: + github-token: ${{ secrets.ADMINSERV_TOKEN_CL }} + script: | + + const issueLabels = await github.rest.issues.listLabelsOnIssue( + { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number + }); + + let add_labels = issueLabels.data.map( label => label.name ); + + let iss_title = `${{ steps.label_issues_bug.outputs.issue_title }}` || `${ context.payload.issue.title }`; + let iss_body = `${ context.payload.issue.body }`; + + const iss_title_lc = iss_title.toLowerCase( ); + + console.log( "Feat Title .................... " + iss_title ) + console.log( "Feat Output ................... " + `${{ steps.label_issues_bug.outputs.issue_title }}` ) + console.log( "Feat Payload .................. " + `${ context.payload.issue.title }` ) + + /* + Tags + */ + + const bug_tag = `${{ env.PREFIX_BUG }}:`; + const bug_lbl = `${{ env.LABEL_BUG }}`; + const feat_tag = `${{ env.PREFIX_FEATURE }}:`; + const feat_lbl = `${{ env.LABEL_FEATURE }}`; + const urgn_tag = `${{ env.PREFIX_URGENT }}:`; + const urgn_lbl = `${{ env.LABEL_URGENT }}`; + const road_tag = `${{ env.PREFIX_ROADMAP }}:`; + const road_lbl = `${{ env.LABEL_ROADMAP }}`; + + /* + Features + */ + + const words = [ "feature", "request", "add support" ]; + const bIncWordT = words.some( s => s.includes( iss_title_lc ) || iss_title_lc.includes( s ) ); + + /* + Find regex based phrases + + Regex: + https://regex101.com/r/fR1Hm6/1 + */ + + const findWordList = /^(?:(?:request|include|see)\s*(?:an?|the?)\s*(?:feature|addon|addition|plugin))|(?:(?:add|see|get)\s*support\s*(?:for|with|of))|(?:can\s*we\s*get\s*(?:the|a)\s*(?:ability|feature))|(?:💡 Feature:)$/igm; + const bFoundMatchTitle = Boolean( findWordList.test( iss_title ) ); + const bFoundMatchBody = Boolean( findWordList.test( iss_body ) ); + + /* + Do not change a title if the item starts with a PR: # + + Regex: + https://regex101.com/r/JOrqbN/1 + */ + + const feat_findPRTitle = /^PR\s?#?(?:[0-9]*:)/igm; + const feat_bFoundPRTitle = Boolean( feat_findPRTitle.test( iss_title ) ); + + console.log( "Title Lowercase ............... " + iss_title_lc ) + console.log( "Startswith " + feat_tag.toLowerCase( ) + "............ " + iss_title_lc.startsWith( feat_tag.toLowerCase( ) ) ) + console.log( "Title Includes Keyword ........ " + bIncWordT ) + console.log( "Title Includes Regex .......... " + bFoundMatchTitle ) + console.log( "Body Includes Regex ........... " + bFoundMatchBody ) + console.log( "\n" ) + + /* + - Check if issue title matches the issue label "Feature:" + - Check if title contains word in words + */ + + // change TAG per category + if ( iss_title_lc.startsWith( feat_tag.toLowerCase( ) ) || bIncWordT || bFoundMatchTitle || bFoundMatchBody ) + { + + console.log( "⚠️ " + feat_tag + " ---------------------------------------" ) + console.log( "Already starts with " + bug_tag + " ......... " + iss_title_lc.startsWith( bug_tag.toLowerCase( ) ) ) + console.log( "Already starts with " + feat_tag + " ..... " + iss_title_lc.startsWith( feat_tag.toLowerCase( ) ) ) + console.log( "Already starts with " + urgn_tag + " ...... " + iss_title_lc.startsWith( urgn_tag.toLowerCase( ) ) ) + console.log( "Already starts with " + road_tag + " ..... " + iss_title_lc.startsWith( road_tag.toLowerCase( ) ) ) + + // change LBL per category + add_labels.push( `${ feat_lbl }` ); + + console.log( `Adding Tag ....................... ${ feat_lbl }` ) + console.log( "\n" ) + + if ( iss_author === `${{ env.BOT_NAME_DEPENDABOT }}` ) + core.info( `Skipping: Detected ${ iss_author }` ) + + // Rename title to contain Feature: + // Make sure issue / pr title doesnt already contain a beginning title tag + + if ( iss_author !== `${{ env.BOT_NAME_DEPENDABOT }}` && !feat_bFoundPRTitle && !iss_title_lc.startsWith( bug_tag.toLowerCase( ) ) && !iss_title_lc.startsWith( feat_tag.toLowerCase( ) ) && !iss_title_lc.startsWith( urgn_tag.toLowerCase( ) ) && !iss_title_lc.startsWith( road_tag.toLowerCase( ) ) ) + { + console.log( "Renaming Title" ) + console.log( `Old Title: .................. ${ iss_title }` ) + + const title = context.payload.issue.title + let title_new = title.replace( /^\s?feature\s*(.*?)\b/gi, '' ); + title_new = title.replace( /^\s?request\s*(.*?)\b/gi, '' ); + title_new = title.replace( /^\s?add(.*?)\s?feature\s*(.*?)\b/gi, '' ); + title_new = title.replace( /^\s?add(.*?)\s?support\s*(.*?)\b/gi, '' ); + iss_title = `${ feat_tag } ${ title_new }`; // change TAG per category + } + + console.log( `New Title: ...................... ${ iss_title }` ) + + await github.rest.issues.update( + { + owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, + title: `${ iss_title }`, labels: add_labels + } ); + } + + core.setOutput( 'issue_title', iss_title ) + console.log( "\n\n" ) + + # --------------------------------------------------------------------------------------- + # Label > URGENT + # --------------------------------------------------------------------------------------- + + - name: 🏷️ ${{ env.PREFIX_URGENT }} › Assignment + uses: actions/github-script@v7 + id: label_issues_urgent + with: + github-token: ${{ secrets.ADMINSERV_TOKEN_CL }} + script: | + + const issueLabels = await github.rest.issues.listLabelsOnIssue( + { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number + }); + + let add_labels = issueLabels.data.map( label => label.name ); + + let iss_title = `${{ steps.label_issues_feature.outputs.issue_title }}` || `${ context.payload.issue.title }`; + let iss_body = `${ context.payload.issue.body }`; + + const iss_title_lc = iss_title.toLowerCase( ); + + console.log( "Urgn Title .................... " + iss_title ) + console.log( "Urgn Output ................... " + `${{ steps.label_issues_feature.outputs.issue_title }}` ) + console.log( "Urgn Payload .................. " + `${ context.payload.issue.title }` ) + + /* + Tags + */ + + const bug_tag = `${{ env.PREFIX_BUG }}:`; + const bug_lbl = `${{ env.LABEL_BUG }}`; + const feat_tag = `${{ env.PREFIX_FEATURE }}:`; + const feat_lbl = `${{ env.LABEL_FEATURE }}`; + const urgn_tag = `${{ env.PREFIX_URGENT }}:`; + const urgn_lbl = `${{ env.LABEL_URGENT }}`; + const road_tag = `${{ env.PREFIX_ROADMAP }}:`; + const road_lbl = `${{ env.LABEL_ROADMAP }}`; + + /* + Urgent + */ + + const words = [ "urgent", "urgency", "emergency", "important", "critical" ]; + const bIncWordT = words.some( s => s.includes( iss_title_lc ) || iss_title_lc.includes( s ) ); + + /* + Find regex based phrases + + Regex: + https://regex101.com/r/eE9tJX/2 + */ + + const findWordList = /(?:(?:this)?is\s*a?n?\s*?(?:emergency|urgent|important|vital|acute|crucial|grave|pressing|serious|top.?priority|high.?priority))|(?:reply|respond|answer|write|address)\s*(?:immediate|quick|asap|urgent|now|fast|(?:as)?\s*(?:soon|quick|immediate|fast))(?:ly)?|(?:need\s*(?:help|support|fixed|answer|reply|response)!)|(?:emergency|critical|urgen(?:t|cy)|high.?priority)/igm; + const bFoundMatchTitle = Boolean( findWordList.test( iss_title ) ); + const bFoundMatchBody = Boolean( findWordList.test( iss_body ) ); + + /* + Do not change a title if the item starts with a PR: # + + Regex: + https://regex101.com/r/JOrqbN/1 + */ + + const urgn_findPRTitle = /^PR\s?#?(?:[0-9]*:)/igm; + const urgn_bFoundPRTitle = Boolean( urgn_findPRTitle.test( iss_title ) ); + + console.log( "Title Lowercase ............... " + iss_title_lc ) + console.log( "Startswith " + urgn_tag.toLowerCase( ) + "............. " + iss_title_lc.startsWith( urgn_tag.toLowerCase( ) ) ) + console.log( "Title Includes Keyword ........ " + bIncWordT ) + console.log( "Title Includes Regex .......... " + bFoundMatchTitle ) + console.log( "Body Includes Regex ........... " + bFoundMatchBody ) + console.log( "\n" ) + + /* + - Check if issue title matches the issue label "Urgent:" + - Check if title contains word in words + */ + + // change TAG per category + if ( iss_title_lc.startsWith( urgn_tag.toLowerCase( ) ) || bIncWordT || bFoundMatchTitle || bFoundMatchBody ) + { + + console.log( "⚠️ " + urgn_tag + " ---------------------------------------" ) + console.log( "Already starts with " + bug_tag + " ......... " + iss_title_lc.startsWith( bug_tag.toLowerCase( ) ) ) + console.log( "Already starts with " + feat_tag + " ..... " + iss_title_lc.startsWith( feat_tag.toLowerCase( ) ) ) + console.log( "Already starts with " + urgn_tag + " ...... " + iss_title_lc.startsWith( urgn_tag.toLowerCase( ) ) ) + console.log( "Already starts with " + road_tag + " ..... " + iss_title_lc.startsWith( road_tag.toLowerCase( ) ) ) + + // change LBL per category + add_labels.push( `${ urgn_lbl }` ); + + console.log( `Adding Tag ....................... ${ urgn_lbl }` ) + console.log( "\n" ) + + if ( iss_author === `${{ env.BOT_NAME_DEPENDABOT }}` ) + core.info( `Skipping: Detected ${ iss_author }` ) + + // Rename title to contain Urgent: + // Make sure issue / pr title doesnt already contain a beginning title tag + + if ( iss_author !== `${{ env.BOT_NAME_DEPENDABOT }}` && !urgn_bFoundPRTitle && !iss_title_lc.startsWith( bug_tag.toLowerCase( ) ) && !iss_title_lc.startsWith( feat_tag.toLowerCase( ) ) && !iss_title_lc.startsWith( urgn_tag.toLowerCase( ) ) && !iss_title_lc.startsWith( road_tag.toLowerCase( ) ) ) + { + console.log( "Renaming Title" ) + console.log( `Old Title: .................. ${ iss_title }` ) + + const title = context.payload.issue.title + let title_new = title.replace( /^\s?emergency\s*(.*?)\b/gi, '' ); + title_new = title.replace( /^\s?urgent\s*(.*?)\b/gi, '' ); + title_new = title.replace( /^\s?urgency\s*(.*?)\b/gi, '' ); + title_new = title.replace( /^\s?important\s*(.*?)\b/gi, '' ); + title_new = title.replace( /^\s?critical\s*(.*?)\b/gi, '' ); + iss_title = `${ urgn_tag } ${ title_new }`; // change TAG per category + } + + console.log( `New Title: ...................... ${ iss_title }` ) + + await github.rest.issues.update( + { + owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, + title: `${ iss_title }`, labels: add_labels + } ); + } + + core.setOutput( 'issue_title', iss_title ) + console.log( "\n\n" ) + + # --------------------------------------------------------------------------------------- + # Label > ROADMAP + # --------------------------------------------------------------------------------------- + + - name: 🏷️ ${{ env.PREFIX_ROADMAP }} › Assignment + uses: actions/github-script@v7 + id: label_issues_roadmap + with: + github-token: ${{ secrets.ADMINSERV_TOKEN_CL }} + script: | + + const issueLabels = await github.rest.issues.listLabelsOnIssue( + { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number + }); + + let add_labels = issueLabels.data.map( label => label.name ); + + let iss_title = `${{ steps.label_issues_urgent.outputs.issue_title }}` || `${ context.payload.issue.title }`; + let iss_body = `${ context.payload.issue.body }`; + + const iss_title_lc = iss_title.toLowerCase( ); + + console.log( "Road Title .................... " + iss_title ) + console.log( "Road Output ................... " + `${{ steps.label_issues_urgent.outputs.issue_title }}` ) + console.log( "Road Payload .................. " + `${ context.payload.issue.title }` ) + + /* + Tags + */ + + const bug_tag = `${{ env.PREFIX_BUG }}:`; + const bug_lbl = `${{ env.LABEL_BUG }}`; + const feat_tag = `${{ env.PREFIX_FEATURE }}:`; + const feat_lbl = `${{ env.LABEL_FEATURE }}`; + const urgn_tag = `${{ env.PREFIX_URGENT }}:`; + const urgn_lbl = `${{ env.LABEL_URGENT }}`; + const road_tag = `${{ env.PREFIX_ROADMAP }}:`; + const road_lbl = `${{ env.LABEL_ROADMAP }}`; + + /* + Roadmap + */ + + const words = [ "roadmap", "road map", "planned" ]; + const bIncWordT = words.some( s => s.includes( iss_title_lc ) || iss_title_lc.includes( s ) ); + + /* + Find regex based phrases + Roadmap requires headers #Summary and #Proposal | #Objective + + Regex: + https://regex101.com/r/ucajBZ/1 + */ + + const findWordList = /#\s*Summary[\S\s]+#\s*(?:Proposal|Objective)[^\]]+/igm; + const bFoundMatchTitle = Boolean( findWordList.test( iss_title ) ); + const bFoundMatchBody = Boolean( findWordList.test( iss_body ) ); + + /* + Do not change a title if the item starts with a PR: # + + Regex: + https://regex101.com/r/JOrqbN/1 + */ + + const road_findPRTitle = /^PR\s?#?(?:[0-9]*:)/igm; + const road_bFoundPRTitle = Boolean( road_findPRTitle.test( iss_title ) ); + + console.log( "Title Lowercase ............... " + iss_title_lc ) + console.log( "Startswith " + road_tag.toLowerCase( ) + "............ " + iss_title_lc.startsWith( road_tag.toLowerCase( ) ) ) + console.log( "Title Includes Keyword ........ " + bIncWordT ) + console.log( "Title Includes Regex .......... " + bFoundMatchTitle ) + console.log( "Body Includes Regex ........... " + bFoundMatchBody ) + console.log( "\n" ) + + /* + - Check if issue title matches the issue label "Roadmap:" + - Check if title contains word in words + */ + + // change TAG per category + if ( iss_title_lc.startsWith( road_tag.toLowerCase( ) ) || bIncWordT || bFoundMatchTitle || bFoundMatchBody ) + { + + console.log( "⚠️ " + road_tag + " ---------------------------------------" ) + console.log( "Already starts with " + bug_tag + " ...... " + iss_title_lc.startsWith( bug_tag.toLowerCase( ) ) ) + console.log( "Already starts with " + feat_tag + " .. " + iss_title_lc.startsWith( feat_tag.toLowerCase( ) ) ) + console.log( "Already starts with " + urgn_tag + " ... " + iss_title_lc.startsWith( urgn_tag.toLowerCase( ) ) ) + console.log( "Already starts with " + road_tag + " .. " + iss_title_lc.startsWith( road_tag.toLowerCase( ) ) ) + + // change LBL per category + add_labels.push( `${ road_lbl }` ); + + console.log( `Adding Tag .................... ${ road_lbl }` ) + console.log( "\n" ) + + if ( iss_author === `${{ env.BOT_NAME_DEPENDABOT }}` ) + core.info( `Skipping: Detected ${ iss_author }` ) + + // Rename title to contain Roadmap: + // Make sure issue / pr title doesnt already contain a beginning title tag + + if ( iss_author !== `${{ env.BOT_NAME_DEPENDABOT }}` && !road_bFoundPRTitle && !iss_title_lc.startsWith( bug_tag.toLowerCase( ) ) && !iss_title_lc.startsWith( feat_tag.toLowerCase( ) ) && !iss_title_lc.startsWith( urgn_tag.toLowerCase( ) ) && !iss_title_lc.startsWith( road_tag.toLowerCase( ) ) ) + { + console.log( "Renaming Title" ) + console.log( `Old Title: .................. ${ iss_title }` ) + + const title = context.payload.issue.title + let title_new = title.replace( /^\s?broad(.*?)\s?map\s*(.*?)\b/gi, '' ); + title_new = title.replace( /^\s?planned\s*(.*?)\b/gi, '' ); + title_new = title.replace( /^\s?broadmap\s*(.*?)\b/gi, '' ); + iss_title = `${ road_tag } ${ title_new }`; // change TAG per category + } + + console.log( `New Title: .................... ${ iss_title }` ) + + await github.rest.issues.update( + { + owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, + title: `${ iss_title }`, labels: add_labels + } ); + } + + core.setOutput( 'issue_title', iss_title ) + console.log( "\n\n" ) + + # --------------------------------------------------------------------------------------- + # Phrases - Search + # --------------------------------------------------------------------------------------- + + issues-phrase-search: + name: >- + 🏷️ Labels › Phrase Search + needs: + - issues-preconfig-labels + runs-on: ubuntu-latest + permissions: + contents: 'read' + id-token: 'write' + issues: 'write' + environment: + name: Orion + steps: + + # --------------------------------------------------------------------------------------- + # checkout + # --------------------------------------------------------------------------------------- + + - name: "☑️ Prepare" + id: issues-labels-check-checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: 👄 Search Phrases + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.ADMINSERV_TOKEN_CL }} + script: | + const fs = require( 'fs' ); + const iss_title = `${ context.payload.issue.title }`; + const iss_body = `${ context.payload.issue.body }`; + let message = [ "\n
\n" ] + let bHasMessage = false + + /********************************************* + Keyword > OpenGist + **********************************************/ + + let OG_message = + ` + ⚠️ It appears you may be looking for OpenGist help. + - [Opengist Repo](https://github.com/thomiceli/opengist) + - [Opengist Docs](https://github.com/thomiceli/opengist/blob/master/docs/index.md) + - [Opengist Demo](https://opengist.thomice.li/all) + + If you are looking for OpenGist support, it will be very minimal. Please utilize the links above. + + --- + + I am a bot reaching out to you with an automated response. If the above info doesn't apply to you, please ignore it. + `; + + /* + Check for keyword in message title and body + */ + + const OGfindWordList = /(\s*open\s*-?gist\s*)/ig; + const OGbFoundMatchTitle = Boolean( OGfindWordList.test( iss_title ) ); + const OGbFoundMatchBody = Boolean( OGfindWordList.test( iss_body ) ); + + /* + found searched word "Opengist" + append / prepare message for bot to send + */ + + if ( OGbFoundMatchTitle || OGbFoundMatchBody ) + { + message.push ( OG_message ); + bHasMessage = true; + } + + /********************************************* + Keyword > Help + **********************************************/ + + let HE_message = + ` + 💡 It appears you might need help, please check the resources below for documentation that might assist with your issue: + - [Noxenv Documentation](https://aetherinox.github.io/noxenv) + + --- + + I am a bot reaching out to you with an automated response. If the above info doesn't apply to you, please ignore it. + `; + + /* + found searched word "for help" + append / prepare message for bot to send + */ + + const HEfindWordList = /^\b(?:have\s*(?:a|some)?\s*question*s?)|(?:can\s*you\s*(?:tell|help)\s*me)|(?:need\s*(?:some)?\s*(?:help|assistance|guidance))|(?:how\s*can\s*I\s*find)|(?:point\s*me\s*in\s*the\s*direction)|(?:where\s*can\s*I\s*find)|(?:where\s*(?:\N*)\s*(?:\N*)\s*find)|(?:please\s*help)|(?:where\s*\N*\s*(?:located|at))|(?:documentation)\b$/igm; + const HEbFoundMatchTitle = Boolean( HEfindWordList.test( iss_title ) ); + const HEbFoundMatchBody = Boolean( HEfindWordList.test( iss_body ) ); + + if ( HEbFoundMatchTitle || HEbFoundMatchBody ) + { + message.push ( HE_message ); + bHasMessage = true; + } + + /* + Bot has message to send + */ + + if ( bHasMessage == true ) + { + await github.rest.issues.createComment( + { + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: message.join('\n'), + } ); + } + + # --------------------------------------------------------------------------------------- + # Issues > Add Assignees + # --------------------------------------------------------------------------------------- + + issues-assign-assignees: + name: >- + ✍️ Issue › Assignees + runs-on: ubuntu-latest + needs: [ issues-assign-labels ] + if: | + always() + && contains( needs.*.result, 'success' ) + && !contains( needs.*.result, 'failure' ) + permissions: + contents: write + steps: + + - name: ✍️ Set Assignees + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.ADMINSERV_TOKEN_CL }} + script: | + const assignees = [ 'Aetherinox' ]; + + if ( assignees.length > 0 ) + { + try + { + await github.rest.issues.addAssignees( + { + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + assignees + }); + } + catch ( error ) + { + core.setFailed( error.message ); + } + } diff --git a/.github/workflows/labels-create.yml b/.github/workflows/labels-create.yml new file mode 100644 index 0000000..90f34c2 --- /dev/null +++ b/.github/workflows/labels-create.yml @@ -0,0 +1,151 @@ +# --------------------------------------------------------------------------------------- +# @parent : github workflow +# @desc : manually activated workflow to create issue labels +# @author : Aetherinox +# @url : https://github.com/Aetherinox +# --------------------------------------------------------------------------------------- + +name: "🎫 Labels › Create" +run-name: "🎫 Labels › Create" + +# --------------------------------------------------------------------------------------- +# triggers +# --------------------------------------------------------------------------------------- + +on: + workflow_dispatch: + +# --------------------------------------------------------------------------------------- +# environment variables +# --------------------------------------------------------------------------------------- + +env: + ASSIGN_USER: Aetherinox + BOT_NAME_1: AdminServ + BOT_NAME_2: AdminServX + BOT_NAME_3: EuropaServ + BOT_NAME_DEPENDABOT: dependabot[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 ◦ 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 KeeWeb" }, + { "name": "› Customization", "color": "E3F0FC", "description": "Customizing KeeWeb: 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": "› kdbxweb", "color": "eb6420", "description": "Core library: https://github.com/keeweb/kdbxweb" }, + { "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 https://keeweb.info" }, + { "name": "⚠ Urgent", "color": "a8740e", "description": "Requires urgent attention" }, + { "name": "⚠ Announcement", "color": "DB4712", "description": "KeeWeb announcements" }, + { "name": "📰 Progress Report", "color": "392297", "description": "Updates on the development of KeeWeb" }, + { "name": "📦 Release", "color": "277542", "description": "Release announcements" }, + { "name": "✔️ Poll", "color": "972255", "description": "Community polls" }, + { "name": "❔ Question", "color": "FFFFFF", "description": "All questions" } + ] + +jobs: + + # --------------------------------------------------------------------------------------- + # Verify Existing Labels + # This job will ensure you have labels already created in your repo. + # + # All labels come from the JSON table LABELS_JSON. + # --------------------------------------------------------------------------------------- + + issues-labels-create: + name: 🎫 Labels › Create + runs-on: ubuntu-latest + steps: + + - name: "✅ Start" + run: | + echo "Assigning labels and assignees" + + # --------------------------------------------------------------------------------------- + # checkout + # --------------------------------------------------------------------------------------- + + - name: "☑️ Checkout" + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # --------------------------------------------------------------------------------------- + # Check if repo has labels currently added to issues + # --------------------------------------------------------------------------------------- + + - name: 🏷️ Verify Existing Labels + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.ADMINSERV_TOKEN_CL }} + 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 || '', + 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}` ); + } + } + }