diff --git a/.github/internal-cicd/update-workflow-versions.ts b/.github/internal-cicd/update-workflow-versions.ts index 71321281..efd479dc 100644 --- a/.github/internal-cicd/update-workflow-versions.ts +++ b/.github/internal-cicd/update-workflow-versions.ts @@ -24,10 +24,13 @@ if (!Directory.Exists(baseDirPath)) { Deno.exit(0); } +// Clear the console so the token is not visible from the tasks.json file +console.clear(); + const tagRegex = /v[0-9]+\.[0-9]+\.[0-9]+/gm; const newVersion = await Input.prompt({ - message: chalk.blue("What is your name?"), + message: chalk.blue("Enter version to upgrade workflows to:"), hint: "Use a tag with the syntax 'v#.#.#'.", minLength: 5, validate: (value) => { @@ -49,7 +52,7 @@ const allTags = (await tagClient.getAllTags(repoName)).map((t) => t.name); // If the new tag already exists, throw an error if (allTags.includes(newVersion)) { - chalk.red(`Tag '${newVersion}' already exists.`); + console.log(chalk.red(`Tag '${newVersion}' already exists.`)); Deno.exit(0); } diff --git a/.github/workflows/add-new-item-to-project.yml b/.github/workflows/add-new-item-to-project.yml index f63a1654..d7e6130c 100644 --- a/.github/workflows/add-new-item-to-project.yml +++ b/.github/workflows/add-new-item-to-project.yml @@ -36,7 +36,7 @@ jobs: add_new_item_to_project: name: Add New Issue needs: item_number - uses: KinsonDigital/Infrastructure/.github/workflows/add-item-to-project.yml@v12.1.1 + uses: KinsonDigital/Infrastructure/.github/workflows/add-item-to-project.yml@v13.0.0 with: org-name: "${{ vars.ORGANIZATION_NAME }}" org-project-name: "${{ vars.ORG_PROJECT_NAME }}" diff --git a/.github/workflows/build-csharp-project.yml b/.github/workflows/build-csharp-project.yml index ae4f0ac5..17e220ae 100644 --- a/.github/workflows/build-csharp-project.yml +++ b/.github/workflows/build-csharp-project.yml @@ -70,7 +70,7 @@ jobs: resolve_proj_file_path: name: Resolving ${{ inputs.project-name }} Project File Path needs: print_validate_workflow - uses: KinsonDigital/Infrastructure/.github/workflows/resolve-csharp-proj-file.yml@v12.1.1 + uses: KinsonDigital/Infrastructure/.github/workflows/resolve-csharp-proj-file.yml@v13.0.0 with: project-name: ${{ inputs.project-name }} base-path: ${{ inputs.base-path }} diff --git a/.github/workflows/dotnet-action-release.yml b/.github/workflows/dotnet-action-release.yml index 4d0c9cd3..e6a3e40b 100644 --- a/.github/workflows/dotnet-action-release.yml +++ b/.github/workflows/dotnet-action-release.yml @@ -157,7 +157,7 @@ jobs: validate_version: name: Validate Version needs: [print_validate_workflow, validate_branch] - uses: KinsonDigital/Infrastructure/.github/workflows/validate-csharp-version.yml@v12.1.1 + uses: KinsonDigital/Infrastructure/.github/workflows/validate-csharp-version.yml@v13.0.0 with: project-name: "${{ inputs.project-name }}" release-type: "${{ inputs.release-type }}" @@ -168,7 +168,7 @@ jobs: validate_tag: name: Validate Tag needs: validate_version - uses: KinsonDigital/Infrastructure/.github/workflows/validate-tag.yml@v12.1.1 + uses: KinsonDigital/Infrastructure/.github/workflows/validate-tag.yml@v13.0.0 with: project-name: "${{ inputs.project-name }}" release-type: "${{ inputs.release-type }}" @@ -207,7 +207,7 @@ jobs: validate_github_release: name: GitHub Release Does Not Exist needs: validate_version - uses: KinsonDigital/Infrastructure/.github/workflows/validate-github-release.yml@v12.1.1 + uses: KinsonDigital/Infrastructure/.github/workflows/validate-github-release.yml@v13.0.0 with: project-name: "${{ inputs.project-name }}" version: "${{ needs.validate_version.outputs.version }}" @@ -218,7 +218,7 @@ jobs: build_project: name: Build Main Project (${{ inputs.project-name }}) needs: print_validate_workflow - uses: KinsonDigital/Infrastructure/.github/workflows/build-csharp-project.yml@v12.1.1 + uses: KinsonDigital/Infrastructure/.github/workflows/build-csharp-project.yml@v13.0.0 with: project-name: "${{ inputs.project-name }}" runs-on: "${{ inputs.runs-on }}" @@ -229,7 +229,7 @@ jobs: run_tests: name: Run Tests needs: print_validate_workflow - uses: KinsonDigital/Infrastructure/.github/workflows/run-csharp-tests.yml@v12.1.1 + uses: KinsonDigital/Infrastructure/.github/workflows/run-csharp-tests.yml@v13.0.0 with: project-name: "${{ inputs.project-name }}Tests" runs-on: "${{ inputs.runs-on }}" diff --git a/.github/workflows/dotnet-lib-release.yml b/.github/workflows/dotnet-lib-release.yml index 39d2018a..0dc87851 100644 --- a/.github/workflows/dotnet-lib-release.yml +++ b/.github/workflows/dotnet-lib-release.yml @@ -182,7 +182,7 @@ jobs: validate_version: name: Validate Version - uses: KinsonDigital/Infrastructure/.github/workflows/validate-csharp-version.yml@v12.1.1 + uses: KinsonDigital/Infrastructure/.github/workflows/validate-csharp-version.yml@v13.0.0 with: project-name: "${{ inputs.project-name }}" release-type: "${{ inputs.release-type }}" @@ -193,7 +193,7 @@ jobs: validate_tag: name: Validate Tag needs: validate_version - uses: KinsonDigital/Infrastructure/.github/workflows/validate-tag.yml@v12.1.1 + uses: KinsonDigital/Infrastructure/.github/workflows/validate-tag.yml@v13.0.0 with: project-name: "${{ inputs.project-name }}" release-type: "${{ inputs.release-type }}" @@ -205,7 +205,7 @@ jobs: nuget_pkg_does_not_exist: name: Validate NuGet Package Does Not Exist needs: validate_version - uses: KinsonDigital/Infrastructure/.github/workflows/nuget-package-does-not-exist.yml@v12.1.1 + uses: KinsonDigital/Infrastructure/.github/workflows/nuget-package-does-not-exist.yml@v13.0.0 with: project-name: "${{ inputs.project-name }}" version: "${{ needs.validate_version.outputs.version }}" @@ -214,7 +214,7 @@ jobs: validate_milestone_status: name: Validate Milestone Status needs: validate_version - uses: KinsonDigital/Infrastructure/.github/workflows/validate-milestone-status.yml@v12.1.1 + uses: KinsonDigital/Infrastructure/.github/workflows/validate-milestone-status.yml@v13.0.0 with: project-name: "${{ inputs.project-name }}" version: "${{ needs.validate_version.outputs.version }}" @@ -225,7 +225,7 @@ jobs: validate_github_release: name: GitHub Release Does Not Exist needs: [validate_version] - uses: KinsonDigital/Infrastructure/.github/workflows/validate-github-release.yml@v12.1.1 + uses: KinsonDigital/Infrastructure/.github/workflows/validate-github-release.yml@v13.0.0 with: project-name: "${{ inputs.project-name }}" version: "${{ needs.validate_version.outputs.version }}" @@ -236,7 +236,7 @@ jobs: build_project: name: Build Main Project needs: print_validate_workflow - uses: KinsonDigital/Infrastructure/.github/workflows/build-csharp-project.yml@v12.1.1 + uses: KinsonDigital/Infrastructure/.github/workflows/build-csharp-project.yml@v13.0.0 with: project-name: "${{ inputs.project-name }}" runs-on: "${{ inputs.runs-on }}" @@ -247,7 +247,7 @@ jobs: run_tests: name: Run Tests needs: print_validate_workflow - uses: KinsonDigital/Infrastructure/.github/workflows/run-csharp-tests.yml@v12.1.1 + uses: KinsonDigital/Infrastructure/.github/workflows/run-csharp-tests.yml@v13.0.0 with: project-name: "${{ inputs.project-name }}Tests" runs-on: "${{ inputs.runs-on }}" diff --git a/.github/workflows/initial-manual-sync.yml b/.github/workflows/initial-manual-sync.yml new file mode 100644 index 00000000..c813d81f --- /dev/null +++ b/.github/workflows/initial-manual-sync.yml @@ -0,0 +1,112 @@ +name: 🔄️Sync PR To Issue +run-name: 🔄️Sync PR To Issue (${{ inputs.sync-type }}) + + +defaults: + run: + shell: pwsh + + +on: + workflow_call: + inputs: + issue-or-pr-number: + description: Issue or pr number + required: true + type: number + sync-type: + description: Type of sync to perform. ("manual" or "initial") + required: true + type: string + requested-by: # User can be prefixed with 'validate:' + description: User who requested the sync + required: true + type: string + branch: + description: Name of branch. (Only required for initial sync) + required: false + type: string + secrets: + cicd-pat: + required: true + description: The CICD personal access token. + + +jobs: + sync_issue_to_pr: + name: Start Sync Process + runs-on: ubuntu-latest + steps: + - name: Validate Sync Type + run: | + $syncType = "${{ inputs.sync-type }}"; + $validSyncType = $syncType -in "manual", "initial"; + + if ($validSyncType -eq $false) { + $msg = "The sync type `${{ inputs.sync-type }}` is not valid. Valid sync types are 'manual' or 'initial'."; + $msg += " Verify that the 'sync-type' input is correct."; + Write-Host "::error::$msg"; + exit 1; + } + + - name: Check For Skipping + id: skip-checking + run: | + $isManualSync = "${{ inputs.sync-type == 'manual' && contains(github.event.comment.body, '[run-sync]') }}"; + $isInitialSync = "${{ inputs.sync-type == 'initial'}}"; + + $stepOutput = "skip=false"; + + if ($isManualSync -eq "true") { + Write-Host "::notice::The '[run-sync]' command has been invoked."; + } elseif ($isInitialSync -eq "true") { + $branch = "${{ inputs.branch }}"; + $branchRegEx = "${{ vars.FEATURE_BRANCH_REGEX }}"; + $isValidBranch = $branch -match $branchRegEx; + + if ($isValidBranch -eq $true) { + Write-Host "::notice::The PR has been opened from a valid branch."; + } else { + $msg = "::warning::The branch `${{ inputs.branch }}` does not match the regex `${{ vars.FEATURE_BRANCH_REGEX }}`."; + $msg += " Verify that the 'FEATURE_BRANCH_REGEX' org or repo variable is set correctly."; + Write-Host $msg; + } + } else { + Write-Host "::warning::Issue/PR Sync Process Skipped. Possibly sync command was not exactly '[run-sync]' or incorrect PR head branch."; + $stepOutput = "skip=true"; + } + + "$stepOutput" >> $env:GITHUB_OUTPUT; + + - name: Set Up Deno + if: ${{ steps.skip-checking.outputs.skip == 'false' }} + uses: denoland/setup-deno@v1 + with: + deno-version: v1.x + + - name: Run Sync + if: ${{ steps.skip-checking.outputs.skip == 'false' }} + run: | + $scriptUrl = "${{ vars.SCRIPT_BASE_URL }}/${{ vars.CICD_SCRIPTS_VERSION }}/${{ vars.SCRIPT_RELATIVE_DIR_PATH }}/sync-pr-to-issue.ts"; + + Write-Host "::notice::Sync Type: ${{ inputs.sync-type }}"; + Write-Host "::notice::Organization Name: ${{ vars.ORGANIZATION_NAME }}"; + Write-Host "::notice::Project Name: ${{ vars.PROJECT_NAME }}"; + Write-Host "::notice::Requested By: ${{ inputs.requested-by }}"; + Write-Host "::notice::PR Number: ${{ inputs.issue-or-pr-number }}"; + + <# Deno Args: + 1. Organization name + 2. Project name + 3. User requesting sync + 4. Issue or pull request number + 5. PAT + #> + deno run ` + --allow-net ` + "$scriptUrl" ` + "${{ vars.ORGANIZATION_NAME }}" ` + "${{ vars.PROJECT_NAME }}" ` + "${{ inputs.requested-by }}" ` + "${{ inputs.issue-or-pr-number }}" ` + "${{ secrets.cicd-pat }}"; diff --git a/.github/workflows/run-csharp-tests.yml b/.github/workflows/run-csharp-tests.yml index b399f493..1991a44e 100644 --- a/.github/workflows/run-csharp-tests.yml +++ b/.github/workflows/run-csharp-tests.yml @@ -72,7 +72,7 @@ jobs: resolve_proj_file_path: name: Resolving ${{ inputs.project-name }} Project File Path needs: print_validate_workflow - uses: KinsonDigital/Infrastructure/.github/workflows/resolve-csharp-proj-file.yml@v12.1.1 + uses: KinsonDigital/Infrastructure/.github/workflows/resolve-csharp-proj-file.yml@v13.0.0 with: project-name: ${{ inputs.project-name }} base-path: ${{ inputs.base-path }} diff --git a/.github/workflows/sync-issue-to-pr.yml b/.github/workflows/sync-issue-to-pr.yml deleted file mode 100644 index dc2ace54..00000000 --- a/.github/workflows/sync-issue-to-pr.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: 🔄️Sync Issue To PR - - -defaults: - run: - shell: pwsh - - -on: - pull_request: - types: opened - issue_comment: # This event is triggered when creating issue and pr comments - types: created - - -jobs: - sync_issue_to_pr: - name: Start Sync Process - if: | - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '[run-sync]')) || - github.event_name == 'pull_request' && startsWith(github.head_ref, 'feature/') - runs-on: ubuntu-latest - steps: - - name: Set Up Deno - uses: denoland/setup-deno@v1 - with: - deno-version: v1.x - - - name: Sync - run: | - $eventName = "${{ github.event_name }}"; - $scriptUrl = "${{ vars.SCRIPT_BASE_URL }}/${{ vars.CICD_SCRIPTS_VERSION }}/${{ vars.SCRIPT_RELATIVE_DIR_PATH }}/sync-issue-to-pr.ts"; - $issueOrPrNumber = $eventName -eq "pull_request" ? "${{ github.event.number }}" : "${{ github.event.issue.number }}"; - - Write-Host "::notice::Event Type: $eventName"; - Write-Host "::notice::Organization Name: ${{ vars.ORGANIZATION_NAME }}"; - Write-Host "::notice::Project Name: ${{ vars.PROJECT_NAME }}"; - Write-Host "::notice::Requested By: ${{ github.event.sender.login }}"; - Write-Host "::notice::PR Number: $issueOrPrNumber"; - - <# Deno Args: - 1. Organization name - 2. Project name - 3. Triggered by user - 4. Issue or pull request number - 5. PAT - #> - deno run ` - --allow-net ` - "$scriptUrl" ` - "${{ vars.ORGANIZATION_NAME }}" ` - "${{ vars.PROJECT_NAME }}" ` - "${{ github.event.sender.login }}" ` - "$issueOrPrNumber" ` - "${{ secrets.CICD_TOKEN }}"; diff --git a/.github/workflows/sync-pr-to-issue.yml b/.github/workflows/sync-pr-to-issue.yml new file mode 100644 index 00000000..636015f5 --- /dev/null +++ b/.github/workflows/sync-pr-to-issue.yml @@ -0,0 +1,39 @@ +name: 🔄️Sync PR To Issue + + +defaults: + run: + shell: pwsh + + +on: + pull_request_target: + types: opened + issue_comment: # This event is triggered when creating issue and pr comments + types: created + + +jobs: + initial_sync: + name: Start Initial Sync + if: ${{ github.event_name == 'pull_request_target' }} + uses: KinsonDigital/Infrastructure/.github/workflows/initial-manual-sync.yml@v13.0.0 + with: + issue-or-pr-number: ${{ github.event.pull_request.number }} + sync-type: initial + requested-by: ${{ github.event.sender.login }} + branch: ${{ github.event.pull_request.head.ref }} # Input not required for initial sync + secrets: + cicd-pat: ${{ secrets.CICD_TOKEN }} + + + manual_sync: + name: Start Manual Sync + if: ${{ github.event_name == 'issue_comment' && contains(github.event.comment.body, '[run-sync]') }} + uses: KinsonDigital/Infrastructure/.github/workflows/initial-manual-sync.yml@v13.0.0 + with: + issue-or-pr-number: ${{ github.event.issue.number }} + sync-type: manual + requested-by: "validate:${{ github.event.sender.login }}" + secrets: + cicd-pat: ${{ secrets.CICD_TOKEN }} diff --git a/.vscode/launch.json b/.vscode/launch.json index de2ca644..9eb2d6e7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -279,7 +279,7 @@ "name": "Sync Issue To PR", "request": "launch", "type": "node", - "program": "${workspaceFolder}/cicd/scripts/sync-issue-to-pr.ts", + "program": "${workspaceFolder}/cicd/scripts/sync-pr-to-issue.ts", "cwd": "${workspaceFolder}/cicd", "runtimeArgs": [ "run", diff --git a/cicd/scripts/runners/SyncPRToIssueRunner.ts b/cicd/scripts/runners/SyncPRToIssueRunner.ts index 23812120..1ac19fae 100644 --- a/cicd/scripts/runners/SyncPRToIssueRunner.ts +++ b/cicd/scripts/runners/SyncPRToIssueRunner.ts @@ -102,7 +102,7 @@ export class SyncPRToIssueRunner extends ScriptRunner { errorMsg += "\nThe 1st arg is required and must be a valid organization name."; errorMsg += "\nThe 2nd arg is required and must be the GitHub repo name."; errorMsg += "\nThe 3rd arg is required and must be a valid GitHub user that triggered the script to run."; - errorMsg += "\nThe 4th arg is required and must be a valid pull request number."; + errorMsg += "\nThe 4th arg is required and must be a valid issue or pull request number."; errorMsg += "\nThe 5th arg is required and must be a valid GitHub PAT (Personal Access Token)."; Utils.printAsGitHubError(errorMsg); @@ -153,12 +153,21 @@ export class SyncPRToIssueRunner extends ScriptRunner { Deno.exit(1); } - const userIsNotOrgMember = !(await orgClient.userIsOrgAdminMember(orgName, requestByUser)); - if (userIsNotOrgMember) { - let errorMsg = `The user '${requestByUser}' is not member of the`; - errorMsg += ` organization '${orgName}' with the admin role.`; - Utils.printAsGitHubError(errorMsg); - Deno.exit(0); + const validateAsOrgMember = requestByUser.toLowerCase().startsWith("validate:org:"); + + // If the sync is manual, validate that the user is an org member + if (validateAsOrgMember) { + const githubLogin = requestByUser.toLowerCase().startsWith("validate:") + ? requestByUser.replace("validate:", "") + : requestByUser; + + const userIsNotOrgMember = !(await orgClient.userIsOrgAdminMember(orgName, githubLogin)); + if (userIsNotOrgMember) { + let errorMsg = `The user '${requestByUser}' is not member of the`; + errorMsg += ` organization '${orgName}' with the admin role.`; + Utils.printAsGitHubError(errorMsg); + Deno.exit(1); + } } const repoDoesNotExist = !(await repoClient.exists(repoName)); @@ -240,7 +249,7 @@ export class SyncPRToIssueRunner extends ScriptRunner { if (prNumber === 0) { let warningMsg = `The issue '${issueNumber}' does not contain any valid pull request number meta-data.`; - warningMsg += " A pull request was not synced to an issue."; + warningMsg += " The pull request was not synced to an issue."; Utils.printAsGitHubWarning(warningMsg); Deno.exit(0); } @@ -253,7 +262,7 @@ export class SyncPRToIssueRunner extends ScriptRunner { if (!(await issueClient.issueExists(repoName, issueNumber))) { let warningMsg = `The issue '${issueNumber}' does not exist.`; - warningMsg += "A pull request was not synced to an issue."; + warningMsg += " The pull request was not synced to an issue."; Utils.printAsGitHubWarning(warningMsg); Deno.exit(0); } @@ -261,7 +270,7 @@ export class SyncPRToIssueRunner extends ScriptRunner { break; case IssueOrPullRequest.neither: { let warningMsg = `The number '${issueOrPrNumber}' is not an issue or pull request number.`; - warningMsg += "A pull request was not synced to an issue."; + warningMsg += " The pull request was not synced to an issue."; Utils.printAsGitHubWarning(warningMsg); Deno.exit(0); } diff --git a/cicd/scripts/sync-issue-to-pr.ts b/cicd/scripts/sync-pr-to-issue.ts similarity index 100% rename from cicd/scripts/sync-issue-to-pr.ts rename to cicd/scripts/sync-pr-to-issue.ts