From 828d8737f89f11a63c073e795decb220456ad343 Mon Sep 17 00:00:00 2001 From: Calvin Wilkinson Date: Tue, 8 Oct 2024 21:05:19 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A7Remove=20all=20code=20related=20to?= =?UTF-8?q?=20printing=20out=20arg=20values=20(#232)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Start work for issue #213 * chore: create function to validate and get env vars * chore: create functions for validation * security: printing of arguments removed * ci: refactor add item to project script to use env vars * ci: refactor milestone exists script to use env vars * config: adjust dir separators * ci: refactor resolve cs proj script to use env vars * config: reformat deno config json * ci: refactor release tweet script to use env vars * ci: refactor transpile readme script to use env vars * refactor: change how workspace dir path is extracted * chore: improve version check * refactor: change validate sdk setup script to use env vars * chore: add todo comments * refactor: update close milestone script to use env vars * refactor: update nuget package check script to use env vars * refactor: update validate version script to use env vars * refactor: update validate github release script to use env vars * refactor: update validate tag script to use env vars * refactor: update workflow version status check script to use env vars * fix: fix error log message * config: set vscode title and workbench color * refactor: update playground and launch config to use env vars --- .../scripts/workflow-version-status-check.ts | 41 +-- .github/workflows/add-item-to-project.yml | 24 +- .github/workflows/dotnet-action-release.yml | 2 +- .github/workflows/dotnet-lib-release.yml | 89 ++---- .../nuget-package-does-not-exist.yml | 13 +- .../workflows/resolve-csharp-proj-file.yml | 27 +- .github/workflows/validate-csharp-version.yml | 16 +- .github/workflows/validate-github-release.yml | 18 +- .../workflows/validate-milestone-status.yml | 23 +- ...dk-setup.yml => validate-sdk-versions.yml} | 17 +- .github/workflows/validate-tag.yml | 22 +- .../workflow-version-status-check.yml | 14 +- .vscode/launch.json | 257 ++++++++---------- .vscode/settings.json | 4 + cicd/core/GetEnvVar.ts | 21 ++ cicd/core/Guard.ts | 2 + cicd/core/Services/CSharpVersionService.ts | 2 +- .../Services/ReadMeTranspilerService.ts} | 68 +---- .../Services/ValidateSDKVersionService.ts | 103 +++++++ cicd/core/TwitterAuthValues.ts | 24 -- cicd/core/Utils.ts | 50 +--- cicd/core/Validators.ts | 69 +++++ cicd/playground.ts | 4 +- cicd/scripts/add-item-to-project.ts | 36 ++- cicd/scripts/close-milestone.ts | 54 ++-- cicd/scripts/github-release-does-not-exist.ts | 79 ++---- cicd/scripts/milestone-exists.ts | 53 +--- cicd/scripts/mod.ts | 11 - cicd/scripts/nuget-pkg-does-not-exist.ts | 52 ++-- cicd/scripts/resolve-csproj.ts | 57 +++- .../scripts/runners/AddItemToProjectRunner.ts | 125 --------- cicd/scripts/runners/ResolveCsProjRunner.ts | 84 ------ cicd/scripts/runners/ScriptRunner.ts | 12 - .../scripts/runners/SendReleaseTweetRunner.ts | 206 -------------- .../scripts/runners/ValidateSDKSetupRunner.ts | 156 ----------- cicd/scripts/send-release-tweet.ts | 73 ++++- cicd/scripts/transpile-readme.ts | 24 +- cicd/scripts/validate-sdk-setup.ts | 16 -- cicd/scripts/validate-sdk-versions.ts | 10 + cicd/scripts/validate-tag.ts | 106 +++----- cicd/scripts/validate-version.ts | 82 ++---- deno.json | 11 +- deno.lock | 35 +++ 43 files changed, 796 insertions(+), 1396 deletions(-) rename .github/workflows/{validate-sdk-setup.yml => validate-sdk-versions.yml} (81%) create mode 100644 cicd/core/GetEnvVar.ts rename cicd/{scripts/runners/TranspileReadMeRunner.ts => core/Services/ReadMeTranspilerService.ts} (81%) create mode 100644 cicd/core/Services/ValidateSDKVersionService.ts delete mode 100644 cicd/core/TwitterAuthValues.ts create mode 100644 cicd/core/Validators.ts delete mode 100644 cicd/scripts/runners/AddItemToProjectRunner.ts delete mode 100644 cicd/scripts/runners/ResolveCsProjRunner.ts delete mode 100644 cicd/scripts/runners/SendReleaseTweetRunner.ts delete mode 100644 cicd/scripts/runners/ValidateSDKSetupRunner.ts delete mode 100644 cicd/scripts/validate-sdk-setup.ts create mode 100644 cicd/scripts/validate-sdk-versions.ts diff --git a/.github/internal-cicd/scripts/workflow-version-status-check.ts b/.github/internal-cicd/scripts/workflow-version-status-check.ts index d009276a..3a476681 100644 --- a/.github/internal-cicd/scripts/workflow-version-status-check.ts +++ b/.github/internal-cicd/scripts/workflow-version-status-check.ts @@ -1,32 +1,33 @@ +import { walkSync } from "@std/fs/walk"; +import { exists } from "@std/fs/exists"; +import { basename } from "@std/path/basename"; import { TagClient } from "../../../deps.ts"; -import { Directory } from "../../../deps.ts"; -import { File } from "../../../cicd/core/File.ts"; -import { Path } from "../../../cicd/core/Path.ts"; import { Utils } from "../../../cicd/core/Utils.ts"; +import getEnvVar from "../../../cicd/core/GetEnvVar.ts"; +import { validateOrgExists, validateRepoExists } from "../../../cicd/core/Validators.ts"; -if (Deno.args.length != 2) { - let errorMsg = "Invalid number of arguments."; - errorMsg += "\nArg 1: Fully qualified directory path of where to search for YAML files."; - errorMsg += "\nArg 2: GitHub token."; - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); -} +const scriptFileName = new URL(import.meta.url).pathname.split("/").pop(); -let baseDirPath = Deno.args[0]; -const token = Deno.args[1]; +const ownerName = getEnvVar("OWNER_NAME", scriptFileName); +const repoName = getEnvVar("REPO_NAME", scriptFileName); +let baseDirPath = getEnvVar("BASE_DIR_PATH", scriptFileName); +baseDirPath = Utils.normalizePath(baseDirPath); +const token = getEnvVar("GITHUB_TOKEN", scriptFileName); -baseDirPath = Utils.normalizePath(baseDirPath.trim()); +await validateOrgExists(scriptFileName); +await validateRepoExists(scriptFileName); -if (!Directory.Exists(baseDirPath)) { +if (!exists(baseDirPath)) { Utils.printAsGitHubError(`Directory '${baseDirPath}' does not exist.`); Deno.exit(1); } -const ownerName = "KinsonDigital"; -const repoName = "Infrastructure"; -const allFiles = Directory.getFiles(baseDirPath, true); +const yamlFiles = [...walkSync(baseDirPath, { + includeDirs: false, + includeFiles: true, + exts: [".yaml", ".yml"], +})].map((e) => e.path); -const yamlFiles = allFiles.filter((file) => file.toLowerCase().endsWith(".yaml") || file.toLowerCase().endsWith(".yml")); const tagClient: TagClient = new TagClient(ownerName, repoName, token); const existingReleaseTags = (await tagClient.getAllTags()).map((t) => t.name); @@ -54,7 +55,7 @@ yamlFiles.forEach(yamlFile => { workflowRefs: [] }; - const fileContent = File.LoadFile(yamlFile); + const fileContent = Deno.readTextFileSync(yamlFile); const possibleUpdates = fileContent.match(reusableWorkflowRegex)?.map((w) => w) ?? []; @@ -84,7 +85,7 @@ const errorMsgs: string[] = []; // Print out all of the workflows that need to be updated as an error workflowsToUpdate.forEach(workflowToUpdate => { - const filePath = Path.getFileName(workflowToUpdate.filePath); + const filePath = basename(workflowToUpdate.filePath); const workflowErrors: string[] = workflowToUpdate.workflowRefs.map((workflowRef) => { return `Workflow reference '${workflowRef}' in file '${filePath}' needs to be updated.`; diff --git a/.github/workflows/add-item-to-project.yml b/.github/workflows/add-item-to-project.yml index 0a454112..c0e77614 100644 --- a/.github/workflows/add-item-to-project.yml +++ b/.github/workflows/add-item-to-project.yml @@ -46,13 +46,11 @@ jobs: exit 1; } - if ("${{ inputs.org-project-name }}" -eq "") { Write-Host ":error::The 'org-project-name' workflow input cannot be empty."; exit 1; } - if ("${{ inputs.repo-name }}" -eq "") { Write-Host ":error::The 'repo-name' workflow input cannot be empty."; exit 1; @@ -69,6 +67,12 @@ jobs: deno-version: ${{ vars.DENO_VERSION }} - name: Add To Project + env: + REPO_OWNER: "${{ inputs.org-name }}" + REPO_NAME: "${{ inputs.org-project-name }}" + PROJECT_NAME: "${{ inputs.repo-name }}" + ISSUE_OR_PR_NUMBER: "${{ inputs.item-number }}" + GITHUB_TOKEN: "${{ secrets.cicd-pat }}" run: | # Construct the URL to the organizations CICD scripts $scriptUrl = "${{ vars.SCRIPT_BASE_URL }}/${{ vars.CICD_SCRIPTS_VERSION }}/${{ vars.SCRIPT_RELATIVE_DIR_PATH }}"; @@ -78,18 +82,4 @@ jobs: Write-Host "::notice::NuGet Package Check Script URL: $scriptUrl"; - <# Deno Args: - 1. Organization name - 2. Project name - 3. Repo name - 4. Issue or PR number - 5. PAT - #> - deno run ` - --allow-net --allow-read ` - "$scriptUrl" ` - "${{ inputs.org-name }}" ` - "${{ inputs.org-project-name }}" ` - "${{ inputs.repo-name }}" ` - "${{ inputs.item-number }}" ` - "${{ secrets.cicd-pat }}"; + deno run --allow-net --allow-read "$scriptUrl"; diff --git a/.github/workflows/dotnet-action-release.yml b/.github/workflows/dotnet-action-release.yml index dcd3b732..461b21fa 100644 --- a/.github/workflows/dotnet-action-release.yml +++ b/.github/workflows/dotnet-action-release.yml @@ -180,7 +180,7 @@ jobs: validate_sdk_setup: name: Validate SDK Setup needs: print_validate_workflow - uses: KinsonDigital/Infrastructure/.github/workflows/validate-sdk-setup.yml@v13.6.3 + uses: KinsonDigital/Infrastructure/.github/workflows/validate-sdk-versions.yml@v13.6.3 with: repo-name: "${{ inputs.project-name }}" secrets: diff --git a/.github/workflows/dotnet-lib-release.yml b/.github/workflows/dotnet-lib-release.yml index 71b77675..c1d1bbb8 100644 --- a/.github/workflows/dotnet-lib-release.yml +++ b/.github/workflows/dotnet-lib-release.yml @@ -85,6 +85,11 @@ on: description: The Twitter access token secret. +env: + OWNER_NAME: "${{ vars.ORGANIZATION_NAME }}" + REPO_NAME: "${{ inputs.project-name }}" + + jobs: print_validate_workflow: name: Print & Validate DotNet Lib Release Workflow @@ -198,7 +203,7 @@ jobs: validate_sdk_setup: name: Validate SDK Setup needs: print_validate_workflow - uses: KinsonDigital/Infrastructure/.github/workflows/validate-sdk-setup.yml@v13.6.3 + uses: KinsonDigital/Infrastructure/.github/workflows/validate-sdk-versions.yml@v13.6.3 with: repo-name: "${{ inputs.project-name }}" secrets: @@ -305,26 +310,15 @@ jobs: - name: Transpile README if: inputs.transpile-readme == true - run: | - $scriptUrl = "${{ steps.script-url.outputs.url }}/transpile-readme.ts"; - $readmeDirPath = "$env:GITHUB_WORKSPACE"; - - <# Deno Args: - 1. Readme file directory path - 2. PAT - #> - deno run ` - --allow-read ` - --allow-write ` - "$scriptUrl" ` - "$readmeDirPath" ` - "${{ secrets.cicd-pat }}"; + env: + BASE_DIR_PATH: "${{ github.workspace }}" + run: deno run -ERW "${{ steps.script-url.outputs.url }}/transpile-readme.ts"; - name: Create Nuget Package run: | dotnet pack ` - "$env:GITHUB_WORKSPACE/${{ inputs.project-name }}/${{ inputs.project-name }}.csproj" ` - -o "$env:GITHUB_WORKSPACE" ` + "${{ github.workspace }}/${{ inputs.project-name }}/${{ inputs.project-name }}.csproj" ` + -o "${{ github.workspace }}" ` -c ${{ inputs.build-config }}; - name: Publish Nuget Package @@ -334,14 +328,14 @@ jobs: $version = $version.StartsWith("v") ? $version.Substring(1) : $version; dotnet nuget push ` - "$env:GITHUB_WORKSPACE/${{ vars.ORGANIZATION_NAME }}.${{ inputs.project-name }}.$version.nupkg" ` + "${{ github.workspace }}/${{ vars.ORGANIZATION_NAME }}.${{ inputs.project-name }}.$version.nupkg" ` --api-key ${{ secrets.nuget-org-api-key }} ` --source https://api.nuget.org/v3/index.json; - name: Get GitHub Workspace id: base-dir-path run: | - "github-workspace=$env:GITHUB_WORKSPACE" >> $env:GITHUB_OUTPUT; + "github-workspace=${{ github.workspace }}" >> $env:GITHUB_OUTPUT; - name: Create GitHub Release ${{ inputs.dry-run == true && '(Dry Run)' || '' }} if: ${{ inputs.dry-run == false }} @@ -373,49 +367,26 @@ jobs: -F $fullReleaseNotesPath ` $isPreRelease ` $fullReleaseNotesPath; - - name: Close Milestone if: ${{ inputs.dry-run == false }} - run: | - $scriptUrl = "${{ steps.script-url.outputs.url }}/close-milestone.ts"; - - <# Deno Args: - 1. Project name - 2. Milestone name - This is the version - 3. PAT - #> - deno run ` - --allow-read --allow-net ` - "$scriptUrl" ` - "${{ vars.ORGANIZATION_NAME }}" ` - "${{ inputs.project-name }}" ` - "${{ needs.validate_version.outputs.version }}" ` - "${{ secrets.cicd-pat }}"; + env: + MILESTONE_NAME: "${{ needs.validate_version.outputs.version }}" + GITHUB_TOKEN: "${{ secrets.cicd-pat }}" + run: deno run -ERN "${{ steps.script-url.outputs.url }}/close-milestone.ts"; - name: Send Twitter Announcement if: ${{ inputs.send-release-tweet == true && inputs.dry-run == false }} - run: | - $scriptUrl = "${{ steps.script-url.outputs.url }}/send-release-tweet.ts"; - - <# Deno Args: - 1. Repo owner - 2. Project name - 3. Version - 4. Twitter consumer api key - 5. Twitter consumer api secret - 6. Twitter access token - 7. Twitter access token secret - 8. PAT - #> - deno run ` - --allow-read --allow-net --allow-env ` - "$scriptUrl" ` - "${{ vars.ORGANIZATION_NAME }}" ` - "${{ inputs.project-name }}" ` - "${{ needs.validate_version.outputs.version }}" ` - "${{ secrets.twitter-consumer-api-key }}" ` - "${{ secrets.twitter-consumer-api-secret }}" ` - "${{ secrets.twitter-access-token }}" ` - "${{ secrets.twitter-access-token-secret }}" ` - "${{ secrets.cicd-pat }}"; + env: + VERSION: "${{ needs.validate_version.outputs.version }}" + GITHUB_TOKEN: "${{ secrets.cicd-pat }}" + RELEASE_TWEET_TEMPLATE_REPO_NAME: "${{ vars.RELEASE_TWEET_TEMPLATE_REPO_NAME }}" + RELEASE_TWEET_TEMPLATE_BRANCH_NAME: "${{ vars.RELEASE_TWEET_TEMPLATE_BRANCH_NAME }}" + RELATIVE_RELEASE_TWEET_TEMPLATE_FILE_PATH: "${{ vars.RELATIVE_RELEASE_TWEET_TEMPLATE_FILE_PATH }}" + DISCORD_INVITE_CODE: "${{ vars.DISCORD_INVITE_CODE }}" + TWITTER_BROADCAST_ENABLED: "${{ vars.TWITTER_BROADCAST_ENABLED }}" + TWITTER_ACCESS_TOKEN_KEY: "${{ secrets.twitter-access-token }}" + TWITTER_ACCESS_TOKEN_SECRET: "${{ secrets.twitter-access-token-secret }}" + TWITTER_CONSUMER_API_KEY: "${{ secrets.twitter-consumer-api-key }}" + TWITTER_CONSUMER_API_SECRET: "${{ secrets.twitter-consumer-api-secret }}" + run: deno run -ERN "${{ steps.script-url.outputs.url }}/send-release-tweet.ts"; diff --git a/.github/workflows/nuget-package-does-not-exist.yml b/.github/workflows/nuget-package-does-not-exist.yml index 3c144ae9..3dba1eb1 100644 --- a/.github/workflows/nuget-package-does-not-exist.yml +++ b/.github/workflows/nuget-package-does-not-exist.yml @@ -58,6 +58,9 @@ jobs: deno-version: ${{ vars.DENO_VERSION }} - name: Package Does Not Exist + env: + NUGET_PKG_NAME: "${{ vars.ORGANIZATION_NAME }}.${{ inputs.project-name }}" + NUGET_PKG_VERSION: "${{ inputs.version }}" run: | # Construct the URL to the organizations CICD scripts $scriptUrl = "${{ vars.SCRIPT_BASE_URL }}/${{ vars.CICD_SCRIPTS_VERSION }}/${{ vars.SCRIPT_RELATIVE_DIR_PATH }}"; @@ -67,12 +70,4 @@ jobs: Write-Host "::notice::NuGet Package Check Script URL: $scriptUrl"; - <# Deno Args: - 1. Package name - 2. Version - #> - deno run ` - --allow-net --allow-read ` - "$scriptUrl" ` - "${{ vars.ORGANIZATION_NAME }}.${{ inputs.project-name }}" ` - "${{ inputs.version }}"; + deno run -ERN "$scriptUrl"; diff --git a/.github/workflows/resolve-csharp-proj-file.yml b/.github/workflows/resolve-csharp-proj-file.yml index 3742c7da..fc22a7d6 100644 --- a/.github/workflows/resolve-csharp-proj-file.yml +++ b/.github/workflows/resolve-csharp-proj-file.yml @@ -34,12 +34,9 @@ jobs: outputs: project-file-path: ${{ steps.resolve-file-path.outputs.project-file-path }} steps: - - name: Print Environment Variables - run: Get-ChildItem -Path Env:* | Sort-Object Name - - uses: actions/checkout@v4 - - name: Set Up Deno + - name: Set Up Deno (${{ vars.DENO_VERSION }}) uses: denoland/setup-deno@v1 with: deno-version: ${{ vars.DENO_VERSION }} @@ -77,20 +74,8 @@ jobs: - name: Resolve Project File Path id: resolve-file-path - run: | - $scriptUrl = "${{ steps.script-url.outputs.url }}/resolve-csproj.ts"; - $basePath = "${{ inputs.base-path }}" -eq "" ? "${{ github.workspace }}" : "${{ inputs.base-path }}"; - - <# Deno Args: - 1. The name of the project - 2. The fully qualified directory path to start the search - 3. The GitHub token - #> - deno run ` - --allow-read ` - --allow-write ` - --allow-env ` - "$scriptUrl" ` - "${{ vars.PROJECT_NAME }}" ` - "$basePath" ` - "${{ secrets.cicd-pat }}"; + env: + PROJECT_NAME: "${{ vars.PROJECT_NAME }}" + BASE_DIR_PATH: "${{ inputs.base-path == '' && github.workspace || inputs.base-path }}" + GITHUB_TOKEN: ${{ secrets.cicd-pat }} + run: deno run -ERW "${{ steps.script-url.outputs.url }}/resolve-csproj.ts"; diff --git a/.github/workflows/validate-csharp-version.yml b/.github/workflows/validate-csharp-version.yml index 643b5282..a6b624f0 100644 --- a/.github/workflows/validate-csharp-version.yml +++ b/.github/workflows/validate-csharp-version.yml @@ -79,6 +79,9 @@ jobs: - name: Validate Version id: validate-version + env: + VERSION: "${{ steps.get-version.outputs.version }}" + RELEASE_TYPE: "${{ inputs.release-type }}" run: | # Construct the URL to the organizations CICD scripts $scriptUrl = "${{ vars.SCRIPT_BASE_URL }}/${{ vars.CICD_SCRIPTS_VERSION }}/${{ vars.SCRIPT_RELATIVE_DIR_PATH }}"; @@ -88,18 +91,7 @@ jobs: Write-Host "::notice::Validate DotNet Version Script URL: $scriptUrl"; - # Get the release version and make sure it starts with the letter 'v' - $releaseVersion = "${{ steps.get-version.outputs.version }}".Trim().ToLower(); - $releaseVersion = $releaseVersion.StartsWith("v") ? $releaseVersion : "v$releaseVersion"; - # Set the output of this step "version=$releaseVersion" >> $env:GITHUB_OUTPUT; - <# Deno Args: - 1. Version - 2. Version type - #> - deno run ` - "$scriptUrl" ` - "$releaseVersion" ` - "${{ inputs.release-type }}"; + deno run -E "$scriptUrl"; diff --git a/.github/workflows/validate-github-release.yml b/.github/workflows/validate-github-release.yml index 70090131..048f9a75 100644 --- a/.github/workflows/validate-github-release.yml +++ b/.github/workflows/validate-github-release.yml @@ -71,6 +71,11 @@ jobs: deno-version: ${{ vars.DENO_VERSION }} - name: GitHub Release Does Not Exist + env: + OWNER_NAME: "${{ vars.ORGANIZATION_NAME }}" + REPO_NAME: "${{ inputs.project-name }}" + TAG_NAME: "${{ inputs.version }}" + GITHUB_TOKEN: "${{ secrets.cicd-pat }}" run: | # Construct the URL to the organizations CICD scripts $scriptUrl = "${{ vars.SCRIPT_BASE_URL }}/${{ vars.CICD_SCRIPTS_VERSION }}/${{ vars.SCRIPT_RELATIVE_DIR_PATH }}"; @@ -80,15 +85,4 @@ jobs: Write-Host "::notice::Validate GitHub Release Version Script URL: $scriptUrl"; - <# Deno Args: - 1. Repo name - 2. TagName - This is prev or prod version - 3. PAT - #> - deno run ` - --allow-net --allow-read ` - "$scriptUrl" ` - "${{ vars.ORGANIZATION_NAME }}" ` - "${{ inputs.project-name }}" ` - "${{ inputs.version }}" ` - "${{ secrets.cicd-pat }}"; + deno run -ERN "$scriptUrl"; diff --git a/.github/workflows/validate-milestone-status.yml b/.github/workflows/validate-milestone-status.yml index dba549b8..128f8529 100644 --- a/.github/workflows/validate-milestone-status.yml +++ b/.github/workflows/validate-milestone-status.yml @@ -22,6 +22,10 @@ on: required: true description: The CICD personal access token. +env: + OWNER_NAME: "${{ vars.ORGANIZATION_NAME }}" + REPO_NAME: "${{ INPUTS.project-name }}" + jobs: print_validate_workflow: @@ -81,21 +85,10 @@ jobs: "url=$url" >> "$env:GITHUB_OUTPUT"; - name: Milestone Exists - run: | - $scriptUrl = "${{ steps.script-url.outputs.url }}/milestone-exists.ts"; - - <# Deno Args: - 1. Project name - 2. Milestone title - This is a prev or prod version - 3. PAT - #> - deno run ` - --allow-net --allow-read ` - "$scriptUrl" ` - "${{ vars.ORGANIZATION_NAME }}" ` - "${{ inputs.project-name }}" ` - "${{ inputs.version }}" ` - "${{ secrets.cicd-pat }}"; + env: + MILESTONE_TITLE: "${{ inputs.version }}" + GITHUB_TOKEN: ${{ secrets.cicd-pat }} + run: deno run -ERNS "${{ steps.script-url.outputs.url }}/milestone-exists.ts"; - name: Validate Milestone run: | diff --git a/.github/workflows/validate-sdk-setup.yml b/.github/workflows/validate-sdk-versions.yml similarity index 81% rename from .github/workflows/validate-sdk-setup.yml rename to .github/workflows/validate-sdk-versions.yml index 1d8b2d8f..1972c079 100644 --- a/.github/workflows/validate-sdk-setup.yml +++ b/.github/workflows/validate-sdk-versions.yml @@ -55,21 +55,14 @@ jobs: deno-version: ${{ vars.DENO_VERSION }} - name: Generate Release Notes + env: + BASE_SEARCH_DIR_PATHL: "${{ github.workspace }}" + NET_SDK_VERSIONL: "${{ vars.NET_SDK_VERSION }}" run: | # Construct the URL to the organizations CICD scripts $scriptUrl = "${{ vars.SCRIPT_BASE_URL }}/${{ vars.CICD_SCRIPTS_VERSION }}/${{ vars.SCRIPT_RELATIVE_DIR_PATH }}"; $scriptUrl = $scriptUrl.Replace("\", "/").Replace("//", "/"); $scriptUrl = $scriptUrl.EndsWith("/") ? $scriptUrl.Substring(0, $scriptUrl.Length - 1) : $scriptUrl; - $scriptUrl = "$scriptUrl/validate-sdk-setup.ts"; + $scriptUrl = "$scriptUrl/validate-sdk-versions.ts"; - <# Deno Args: - 1. Organization name - 2. Project name - 3. PAT - #> - deno run ` - --allow-read --allow-net ` - "$scriptUrl" ` - "${{ vars.ORGANIZATION_NAME }}" ` - "${{ inputs.repo-name }}" ` - "${{ secrets.cicd-pat }}"; + deno run -ER "$scriptUrl"; diff --git a/.github/workflows/validate-tag.yml b/.github/workflows/validate-tag.yml index af1d953e..345419e5 100644 --- a/.github/workflows/validate-tag.yml +++ b/.github/workflows/validate-tag.yml @@ -83,6 +83,12 @@ jobs: deno-version: ${{ vars.DENO_VERSION }} - name: Validate Tag + env: + OWNER_NAME: "${{ vars.ORGANIZATION_NAME }}" + REPO_NAME: "${{ inputs.project-name }}" + RELEASE_TYPE: "${{ inputs.release-type }}" + TAG_NAME: "${{ inputs.tag-name }}" + GITHUB_TOKEN: "${{ secrets.cicd-pat }}" run: | # Construct the URL to the organizations CICD scripts $scriptUrl = "${{ vars.SCRIPT_BASE_URL }}/${{ vars.CICD_SCRIPTS_VERSION }}/${{ vars.SCRIPT_RELATIVE_DIR_PATH }}"; @@ -92,18 +98,4 @@ jobs: Write-Host "::notice::Validate Tag Script URL: $scriptUrl"; - <# Deno Args: - 1. Repo name - 2. Tag type - 3. Release type - Either 'Preview' or 'Production' - 3. Tag name - This is the version - 4. PAT - #> - deno run ` - --allow-net --allow-read ` - "$scriptUrl" ` - "${{ vars.ORGANIZATION_NAME }}" ` - "${{ inputs.project-name }}" ` - "${{ inputs.release-type }}" ` - "${{ inputs.tag-name }}" ` - "${{ secrets.cicd-pat }}"; + deno run -ERNS "$scriptUrl"; diff --git a/.github/workflows/workflow-version-status-check.yml b/.github/workflows/workflow-version-status-check.yml index a74382e2..f09a0142 100644 --- a/.github/workflows/workflow-version-status-check.yml +++ b/.github/workflows/workflow-version-status-check.yml @@ -27,11 +27,9 @@ jobs: deno-version: ${{ vars.DENO_VERSION }} - name: Run Status Check Script - run: | - $scriptPath = "${{ github.workspace }}/.github/internal-cicd/scripts/workflow-version-status-check.ts"; - - deno run ` - --allow-read --allow-net ` - $scriptPath ` - "${{ github.workspace }}/.github/workflows" ` - "${{ secrets.CICD_TOKEN }}"; + env: + OWNER_NAME: "${{ vars.ORGANIZATION_NAME }}" + REPO_NAME: "${{ vars.PROJECT_NAME }}" + BASE_DIR_PATH: "${{ github.workspace }}/.github/workflows" + GITHUB_TOKEN: "${{ secrets.CICD_TOKEN }}" + run: deno run -ERN "${{ github.workspace }}/.github/internal-cicd/scripts/workflow-version-status-check.ts"; diff --git a/.vscode/launch.json b/.vscode/launch.json index ec080433..ced1ebb9 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,13 +15,13 @@ "--inspect-wait", "--allow-all" ], - "args": [ - "${env:CICD_TOKEN}", - "${workspaceFolder}", - ], + "env": { + "ROOT_REPO_DIR_PATH": "${workspaceFolder}", + "GITHUB_TOKEN": "${env:CICD_TOKEN}", + }, "attachSimplePort": 9229, "windows": { - "runtimeExecutable": "${userHome}\\.deno\\bin\\deno.exe" + "runtimeExecutable": "${userHome}/.deno/bin/deno.exe" }, "linux": { "runtimeExecutable": "${userHome}/.deno/bin/deno" @@ -47,7 +47,7 @@ ], "attachSimplePort": 9229, "windows": { - "runtimeExecutable": "${userHome}\\.deno\\bin\\deno.exe" + "runtimeExecutable": "${userHome}/.deno/bin/deno.exe" }, "linux": { "runtimeExecutable": "${userHome}/.deno/bin/deno" @@ -74,7 +74,7 @@ "attachSimplePort": 9229, "console": "integratedTerminal", "windows": { - "runtimeExecutable": "${userHome}\\.deno\\bin\\deno.exe" + "runtimeExecutable": "${userHome}/.deno/bin/deno.exe" }, "linux": { "runtimeExecutable": "${userHome}/.deno/bin/deno" @@ -88,18 +88,18 @@ "cwd": "${workspaceFolder}/cicd", "runtimeArgs": [ "run", + "-ENR", "--inspect-wait", - "--allow-all" ], - "args": [ - "KinsonDigital", - "Infrastructure", - "v1.2.3-preview.4", - "${env:CICD_TOKEN}", - ], + "env": { + "OWNER_NAME": "KinsonDigital", + "REPO_NAME": "Infrastructure", + "MILESTONE_NAME": "v1.2.3-preview.4", + "GITHUB_TOKEN": "${env:CICD_TOKEN}", + }, "attachSimplePort": 9229, "windows": { - "runtimeExecutable": "${userHome}\\.deno\\bin\\deno.exe" + "runtimeExecutable": "${userHome}/.deno/bin/deno.exe" }, "linux": { "runtimeExecutable": "${userHome}/.deno/bin/deno" @@ -113,18 +113,18 @@ "cwd": "${workspaceFolder}/cicd/scripts", "runtimeArgs": [ "run", + "-ERN", "--inspect-wait", - "--allow-all" ], - "args": [ - "KinsonDigital", - "Infrastructure", - "v1.0.0-preview.10", - "${env:CICD_TOKEN}", - ], + "env": { + "OWNER_NAME": "KinsonDigital", + "REPO_NAME": "Infrastructure", + "TAG_NAME": "v13.6.3", + "GITHUB_TOKEN": "${env:CICD_TOKEN}", + }, "attachSimplePort": 9229, "windows": { - "runtimeExecutable": "${userHome}\\.deno\\bin\\deno.exe" + "runtimeExecutable": "${userHome}/.deno/bin/deno.exe" }, "linux": { "runtimeExecutable": "${userHome}/.deno/bin/deno" @@ -153,7 +153,7 @@ "attachSimplePort": 9229, "console": "integratedTerminal", "windows": { - "runtimeExecutable": "${userHome}\\.deno\\bin\\deno.exe" + "runtimeExecutable": "${userHome}/.deno/bin/deno.exe" }, "linux": { "runtimeExecutable": "${userHome}/.deno/bin/deno" @@ -167,19 +167,19 @@ "cwd": "${workspaceFolder}", "runtimeArgs": [ "run", + "-ERNS", "--inspect-wait", - "--allow-all" ], - "args": [ - "KinsonDigital", - "Infrastructure", - "v1.2.3-preview.4", - "${env:CICD_TOKEN}", - ], + "env": { + "OWNER_NAME": "KinsonDigital", + "REPO_NAME": "Infrastructure", + "MILESTONE_TITLE": "v1.2.3-preview.4", + "GITHUB_TOKEN": "${env:CICD_TOKEN}" + }, "attachSimplePort": 9229, "console": "integratedTerminal", "windows": { - "runtimeExecutable": "${userHome}\\.deno\\bin\\deno.exe" + "runtimeExecutable": "${userHome}/.deno/bin/deno.exe" }, "linux": { "runtimeExecutable": "${userHome}/.deno/bin/deno" @@ -204,7 +204,7 @@ ], "attachSimplePort": 9229, "windows": { - "runtimeExecutable": "${userHome}\\.deno\\bin\\deno.exe" + "runtimeExecutable": "${userHome}/.deno/bin/deno.exe" }, "linux": { "runtimeExecutable": "${userHome}/.deno/bin/deno" @@ -214,17 +214,20 @@ "name": "NuGet Package Exists", "request": "launch", "type": "node", - "program": "${workspaceFolder}/cicd/scripts/nuget-pkg-exists.ts", - "cwd": "${workspaceFolder}/cicd/scripts", + "program": "${workspaceFolder}/cicd/scripts/nuget-pkg-does-not-exist.ts", + "cwd": "${workspaceFolder}", "runtimeArgs": [ "run", + "-ERN", "--inspect-wait", - "--allow-all" ], - "args": ["KinsonDigital.Velaptor"], + "env": { + "NUGET_PKG_NAME": "KinsonDigital.Velaptor", + "NUGET_PKG_VERSION": "1.0.0-preview.36", + }, "attachSimplePort": 9229, "windows": { - "runtimeExecutable": "${userHome}\\.deno\\bin\\deno.exe" + "runtimeExecutable": "${userHome}/.deno/bin/deno.exe" }, "linux": { "runtimeExecutable": "${userHome}/.deno/bin/deno" @@ -250,7 +253,7 @@ ], "attachSimplePort": 9229, "windows": { - "runtimeExecutable": "${userHome}\\.deno\\bin\\deno.exe" + "runtimeExecutable": "${userHome}/.deno/bin/deno.exe" }, "linux": { "runtimeExecutable": "${userHome}/.deno/bin/deno" @@ -264,22 +267,27 @@ "cwd": "${workspaceFolder}/cicd/scripts", "runtimeArgs": [ "run", + "-ERN", "--inspect-wait", - "--allow-all" ], - "args": [ - "KinsonDigital", - "Infrastructure", - "v1.0.0-preview.15", - "${env:TWITTER_CONSUMER_API_KEY}", - "${env:TWITTER_CONSUMER_API_SECRET}", - "${env:TWITTER_ACCESS_TOKEN_KEY}", - "${env:TWITTER_ACCESS_TOKEN_SECRET}", - "${env:CICD_TOKEN}", - ], + "env": { + "OWNER_NAME": "KinsonDigital", + "REPO_NAME": "Infrastructure", + "VERSION": "v1.0.0-preview.15", + "GITHUB_TOKEN": "${env:CICD_TOKEN}", + "RELEASE_TWEET_TEMPLATE_REPO_NAME": "Infrastructure", + "RELEASE_TWEET_TEMPLATE_BRANCH_NAME": "main", + "RELATIVE_RELEASE_TWEET_TEMPLATE_FILE_PATH": "cicd/templates/release-tweet-template.txt", + "DISCORD_INVITE_CODE": "qewu6fNgv7", + "TWITTER_BROADCAST_ENABLED": "true", + "TWITTER_ACCESS_TOKEN_KEY": "${env:TWITTER_ACCESS_TOKEN_KEY}", + "TWITTER_ACCESS_TOKEN_SECRET": "${env:TWITTER_ACCESS_TOKEN_SECRET}", + "TWITTER_CONSUMER_API_KEY": "${env:TWITTER_CONSUMER_API_KEY}", + "TWITTER_CONSUMER_API_SECRET": "${env:TWITTER_CONSUMER_API_SECRET}", + }, "attachSimplePort": 9229, "windows": { - "runtimeExecutable": "${userHome}\\.deno\\bin\\deno.exe" + "runtimeExecutable": "${userHome}/.deno/bin/deno.exe" }, "linux": { "runtimeExecutable": "${userHome}/.deno/bin/deno" @@ -305,7 +313,7 @@ ], "attachSimplePort": 9229, "windows": { - "runtimeExecutable": "${userHome}\\.deno\\bin\\deno.exe" + "runtimeExecutable": "${userHome}/.deno/bin/deno.exe" }, "linux": { "runtimeExecutable": "${userHome}/.deno/bin/deno" @@ -331,7 +339,7 @@ ], "attachSimplePort": 9229, "windows": { - "runtimeExecutable": "${userHome}\\.deno\\bin\\deno.exe" + "runtimeExecutable": "${userHome}/.deno/bin/deno.exe" }, "linux": { "runtimeExecutable": "${userHome}/.deno/bin/deno" @@ -357,7 +365,7 @@ ], "attachSimplePort": 9229, "windows": { - "runtimeExecutable": "${userHome}\\.deno\\bin\\deno.exe" + "runtimeExecutable": "${userHome}/.deno/bin/deno.exe" }, "linux": { "runtimeExecutable": "${userHome}/.deno/bin/deno" @@ -367,20 +375,19 @@ "name": "Transpile README", "request": "launch", "type": "node", - "program": "${workspaceFolder}/cicd/scripts/transpile-readme.ts", + "program": "${workspaceFolder}/cicd/core/services/transpile-readme.ts", "cwd": "${workspaceFolder}/cicd", "runtimeArgs": [ "run", + "-ERW", "--inspect-wait", - "--allow-all" ], - "args": [ - "${workspaceFolder}", - "${env:CICD_TOKEN}", - ], + "env": { + "BASE_DIR_PATH": "${workspaceFolder}", + }, "attachSimplePort": 9229, "windows": { - "runtimeExecutable": "${userHome}\\.deno\\bin\\deno.exe" + "runtimeExecutable": "${userHome}/.deno/bin/deno.exe" }, "linux": { "runtimeExecutable": "${userHome}/.deno/bin/deno" @@ -403,7 +410,7 @@ ], "attachSimplePort": 9229, "windows": { - "runtimeExecutable": "${userHome}\\.deno\\bin\\deno.exe" + "runtimeExecutable": "${userHome}/.deno/bin/deno.exe" }, "linux": { "runtimeExecutable": "${userHome}/.deno/bin/deno" @@ -427,111 +434,84 @@ "attachSimplePort": 9229, "console": "integratedTerminal", "windows": { - "runtimeExecutable": "${userHome}\\.deno\\bin\\deno.exe" - }, - "linux": { - "runtimeExecutable": "${userHome}/.deno/bin/deno" - } - }, - { // WORKFLOW VERSION STATUS CHECK - "name": "Workflow Version Status Check", - "request": "launch", - "type": "node", - "program": "${workspaceFolder}/.github/internal-cicd/scripts/workflow-version-status-check.ts", - "cwd": "${workspaceFolder}", - "runtimeArgs": [ - "run", - "--inspect-wait", - "--allow-all" - ], - "args": [ - "${workspaceFolder}/.github/workflows", - "${env:CICD_TOKEN}", - ], - "attachSimplePort": 9229, - "windows": { - "runtimeExecutable": "${userHome}\\.deno\\bin\\deno.exe" + "runtimeExecutable": "${userHome}/.deno/bin/deno.exe" }, "linux": { "runtimeExecutable": "${userHome}/.deno/bin/deno" } }, - { // VALIDATE EITHER TAG - "name": "Validate Either Tag", + { // RESOLVE CSPROJ File + "name": "Resolve CSProj File", "request": "launch", "type": "node", - "program": "${workspaceFolder}/cicd/scripts/validate-tag.ts", + "program": "${workspaceFolder}/cicd/scripts/resolve-csproj.ts", "cwd": "${workspaceFolder}", "runtimeArgs": [ "run", + "-ERW", "--inspect-wait", - "--allow-all" ], - "args": [ - "KinsonDigital", - "PackageMonster", - "Either", - "v1.2.3", - "${ env:CICD_TOKEN }", - ], + "env": { + "PROJECT_NAME": "VelaptorTesting", + "BASE_DIR_PATH": "K:/SOFTWARE-DEVELOPMENT/PERSONAL/Velaptor", + "GITHUB_TOKEN": "${env:CICD_TOKEN}" + }, "attachSimplePort": 9229, "console": "integratedTerminal", "windows": { - "runtimeExecutable": "${userHome}\\.deno\\bin\\deno.exe" + "runtimeExecutable": "${userHome}/.deno/bin/deno.exe" }, "linux": { "runtimeExecutable": "${userHome}/.deno/bin/deno" } }, - { // VALIDATE PREVIEW TAG - "name": "Validate Preview Tag", + { // WORKFLOW VERSION STATUS CHECK + "name": "Workflow Version Status Check", "request": "launch", "type": "node", - "program": "${workspaceFolder}/cicd/scripts/validate-tag.ts", + "program": "${workspaceFolder}/.github/internal-cicd/scripts/workflow-version-status-check.ts", "cwd": "${workspaceFolder}", "runtimeArgs": [ "run", + "-ERN", "--inspect-wait", - "--allow-all" ], - "args": [ - "KinsonDigital", - "PackageMonster", - "Preview", - "v1.2.3-preview.5", - "${ env:CICD_TOKEN }", - ], + "env": { + "OWNER_NAME": "KinsonDigital", + "REPO_NAME": "Infrastructure", + "BASE_DIR_PATH": "${workspaceFolder}/.github/workflows", + "GITHUB_TOKEN": "${env:CICD_TOKEN}", + }, "attachSimplePort": 9229, - "console": "integratedTerminal", "windows": { - "runtimeExecutable": "${userHome}\\.deno\\bin\\deno.exe" + "runtimeExecutable": "${userHome}/.deno/bin/deno.exe" }, "linux": { "runtimeExecutable": "${userHome}/.deno/bin/deno" } }, - { // VALIDATE PROD TAG - "name": "Validate Prod Tag", + { // VALIDATE TAG + "name": "Validate Tag", "request": "launch", "type": "node", "program": "${workspaceFolder}/cicd/scripts/validate-tag.ts", "cwd": "${workspaceFolder}", "runtimeArgs": [ "run", + "-ERNS", "--inspect-wait", - "--allow-all" ], - "args": [ - "KinsonDigital", - "PackageMonster", - "Production", - "v1.2.3", - "${ env:CICD_TOKEN }", - ], + "env": { + "OWNER_NAME": "KinsonDigital", + "REPO_NAME": "Infrastructure", + "RELEASE_TYPE": "Production", + "TAG_NAME": "v13.6.3", + "GITHUB_TOKEN": "${env:CICD_TOKEN}", + }, "attachSimplePort": 9229, "console": "integratedTerminal", "windows": { - "runtimeExecutable": "${userHome}\\.deno\\bin\\deno.exe" + "runtimeExecutable": "${userHome}/.deno/bin/deno.exe" }, "linux": { "runtimeExecutable": "${userHome}/.deno/bin/deno" @@ -557,7 +537,7 @@ ], "attachSimplePort": 9229, "windows": { - "runtimeExecutable": "${userHome}\\.deno\\bin\\deno.exe" + "runtimeExecutable": "${userHome}/.deno/bin/deno.exe" }, "linux": { "runtimeExecutable": "${userHome}/.deno/bin/deno" @@ -571,13 +551,16 @@ "cwd": "${workspaceFolder}/cicd", "runtimeArgs": [ "run", + "-E", "--inspect-wait", - "--allow-all" ], - "args": ["v1.2.3-preview.4", "preview"], + "env": { + "VERSION": "v1.2.3-preview.4", + "RELEASE_TYPE": "preview", + }, "attachSimplePort": 9229, "windows": { - "runtimeExecutable": "${userHome}\\.deno\\bin\\deno.exe" + "runtimeExecutable": "${userHome}/.deno/bin/deno.exe" }, "linux": { "runtimeExecutable": "${userHome}/.deno/bin/deno" @@ -602,32 +585,30 @@ "/**" ], "windows": { - "runtimeExecutable": "${userHome}\\.deno\\bin\\deno.exe" + "runtimeExecutable": "${userHome}/.deno/bin/deno.exe" }, "linux": { "runtimeExecutable": "${userHome}/.deno/bin/deno" }, }, - { // VALIDATE SDK SETUP - "name": "Validate SDK Setup", + { // VALIDATE SDK Versions + "name": "Validate SDK Versions", "request": "launch", "type": "node", - "program": "${workspaceFolder}/cicd/scripts/validate-sdk-setup.ts", + "program": "${workspaceFolder}/cicd/scripts/validate-sdk-versions.ts", "cwd": "${workspaceFolder}", "runtimeArgs": [ "run", + "-ER", "--inspect-wait", - "--allow-read", - "--allow-net" ], - "args": [ - "KinsonDigital", - "Infrastructure", - "${env:CICD_TOKEN}", - ], + "env": { + "BASE_SEARCH_DIR_PATH": "${workspaceFolder}", + "NET_SDK_VERSION": "7.0", + }, "attachSimplePort": 9229, "windows": { - "runtimeExecutable": "${userHome}\\.deno\\bin\\deno.exe" + "runtimeExecutable": "${userHome}/.deno/bin/deno.exe" }, "linux": { "runtimeExecutable": "${userHome}/.deno/bin/deno" diff --git a/.vscode/settings.json b/.vscode/settings.json index da4ab75a..098aaabf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,8 @@ { + "window.title": "🦕${activeRepositoryName} - [${activeRepositoryBranchName}]", + "workbench.colorCustomizations": { + "titleBar.activeBackground": "#21351f" + }, "cSpell.words": [ "cicd", "Creds", diff --git a/cicd/core/GetEnvVar.ts b/cicd/core/GetEnvVar.ts new file mode 100644 index 00000000..0e0fb5e2 --- /dev/null +++ b/cicd/core/GetEnvVar.ts @@ -0,0 +1,21 @@ +import { Utils } from "./Utils.ts"; + +/** + * Get the value of an environment variable after checking if it exists. + * @param name The name of the environment variable. + * @remarks This function will throw an error if the environment variable does not exist. + */ +const getEnvVar = (name: string, scriptFileName?: string, throwErrorIfMissing: boolean = true): string => { + const value = (Deno.env.get(name) ?? "").trim(); + + if (Utils.isNothing(value) && throwErrorIfMissing) { + const fileName = Utils.isNothing(scriptFileName) ? "" : `\n\t${scriptFileName}`; + const errorMsg = `The '${name}' environment variable does not exist.${fileName}`; + Utils.printAsGitHubError(errorMsg); + Deno.exit(1); + } + + return value; +} + +export default getEnvVar; diff --git a/cicd/core/Guard.ts b/cicd/core/Guard.ts index fa03c4f5..c5803741 100644 --- a/cicd/core/Guard.ts +++ b/cicd/core/Guard.ts @@ -1,5 +1,7 @@ import { Utils } from "./Utils.ts"; +// TODO: Rename this class to 'ParamGuards' + /** * A class that contains functions to check if values are invalid. */ diff --git a/cicd/core/Services/CSharpVersionService.ts b/cicd/core/Services/CSharpVersionService.ts index c83dc462..20270470 100644 --- a/cicd/core/Services/CSharpVersionService.ts +++ b/cicd/core/Services/CSharpVersionService.ts @@ -70,7 +70,7 @@ export class CSharpVersionService extends VersionServiceBase { * @inheritdoc */ public versionIsValid(version: string): boolean { - return Utils.isValidDotnetSDKVersion(version); + return Utils.isValidPreviewVersion(version) && Utils.isValidProdVersion(version); } /** diff --git a/cicd/scripts/runners/TranspileReadMeRunner.ts b/cicd/core/Services/ReadMeTranspilerService.ts similarity index 81% rename from cicd/scripts/runners/TranspileReadMeRunner.ts rename to cicd/core/Services/ReadMeTranspilerService.ts index 299c74a1..7c16e39a 100644 --- a/cicd/scripts/runners/TranspileReadMeRunner.ts +++ b/cicd/core/Services/ReadMeTranspilerService.ts @@ -1,12 +1,12 @@ -import { Directory, File } from "../../../deps.ts"; -import { GitHubLogType } from "../../core/Enums.ts"; -import { Utils } from "../../core/Utils.ts"; -import { ScriptRunner } from "./ScriptRunner.ts"; +import { existsSync } from "@std/fs/exists"; +import { Utils } from "../Utils.ts"; + +// TODO: replace File references /** * Transpiles the HTML content in a README.md file to markdown. */ -export class TranspileReadMeRunner extends ScriptRunner { +export class ReadMeTranspilerService { private readonly readmeFileName = "README.md"; private readonly divStartTagRegEx = //gm; private readonly divEndTagRegEx = /<\/div\s*>/gm; @@ -23,33 +23,20 @@ export class TranspileReadMeRunner extends ScriptRunner { private readonly imgMarkdownLightModeRegex = /!\[.+\]\(.+-light-mode\.(svg|png|jpg|jpeg)#gh-light-mode-only\)/gm; private readonly imgMarkdownDarkModeRegex = /!\[.+\]\(.+-dark-mode\.(svg|png|jpg|jpeg)#gh-dark-mode-only\)/gm; - /** - * Initializes a new instance of the {@link TranspileReadMeRunner} class. - * @param args The script arguments. - * @param scriptName The name of the script executing the runner. - */ - constructor(args: string[]) { - super(args); - } - /** * Runs the transpile readme script. */ - public async run(): Promise { - await super.run(); - - const [dirPath] = this.args; - + public transpile(dirPath: string): void { const readmeFilePath = `${dirPath}/${this.readmeFileName}`; - if (File.DoesNotExist(readmeFilePath)) { + if (!existsSync(readmeFilePath)) { let errorMsg = "Error with transpiling readme."; errorMsg += `\nThe given path '${readmeFilePath}' is not a valid file path.`; Utils.printAsGitHubError(errorMsg); Deno.exit(1); } - let readmeFileContent = File.LoadFile(readmeFilePath); + let readmeFileContent = Deno.readTextFileSync(readmeFilePath); // Remove start and end div tags readmeFileContent = readmeFileContent.replace(this.divStartTagRegEx, ""); @@ -71,44 +58,7 @@ export class TranspileReadMeRunner extends ScriptRunner { readmeFileContent = this.bumpMarkdownLinksToLeft(readmeFileContent); // Overwrite the README.md file with the transpiled content - File.SaveFile(readmeFilePath, readmeFileContent); - - Utils.printAsGitHubNotice("Successfully transpiled the README.md file."); - } - - /** - * @inheritdoc - */ - // deno-lint-ignore require-await - protected async validateArgs(args: string[]): Promise { - if (args.length != 2) { - const mainMsg = `The cicd script must have 2 arguments but has ${args.length} argument(s).`; - - const argDescriptions = [ - "Required and must be a valid directory path to the 'README.md' file.", - "Required and must be a valid GitHub PAT (Personal Access Token).", - ]; - - Utils.printAsGitHubError(mainMsg); - Utils.printAsNumberedList(" Arg: ", argDescriptions, GitHubLogType.normal); - Deno.exit(1); - } - - if (Directory.DoesNotExist(args[0])) { - Utils.printAsGitHubError(`The given path '${args[0]}' is not a valid directory path.`); - Deno.exit(1); - } - } - - /** - * @inheritdoc - */ - protected mutateArgs(args: string[]): string[] { - let [dirPath, token] = args; - dirPath = Utils.normalizePath(args[0]); - dirPath = Utils.trimAllEndingValue(dirPath, "/"); - - return [dirPath, token]; + Deno.writeTextFileSync(readmeFilePath, readmeFileContent); } /** diff --git a/cicd/core/Services/ValidateSDKVersionService.ts b/cicd/core/Services/ValidateSDKVersionService.ts new file mode 100644 index 00000000..1041e494 --- /dev/null +++ b/cicd/core/Services/ValidateSDKVersionService.ts @@ -0,0 +1,103 @@ +import { walkSync } from "../../../deps.ts"; +import { Guard } from "../Guard.ts"; +import { Utils } from "../Utils.ts"; + +/** + * Validates that a dotnet SDK setup for al dotnet projects are correct. + */ +export class ValidateSDKVersionService { + private readonly targetFrameworkRegex = /\s*net.+\s*<\/TargetFramework\s*>/gm; + private readonly dotnetSDKVersionRegex = /^([1-9]\d*|0)(\.([1-9]\d*|0)|)(\.([1-9]\d*|0)|)$/gm; + private readonly csprojTargetFrameworkVersionRegex = /net([1-9]\d*|0)(\.([1-9]\d*|0)|)(\.([1-9]\d*|0)|)/gm; + + /** + * Validates the given {@link expectedSdkVersion} against all of the csproj files in the current directory. + * @param searchBaseDirPath The base directory path to search for csproj files. + * @param expectedSdkVersion The version to validate. + * @remarks If any of the csproj file SDK version do not match, the workflow will fail. + */ + public validate(searchBaseDirPath: string, expectedSdkVersion: string): void { + Guard.isNothing(expectedSdkVersion, "validate"); + + expectedSdkVersion = expectedSdkVersion.startsWith("v") ? expectedSdkVersion.substring(1) : expectedSdkVersion; + + if (!this.dotnetSDKVersionRegex.test(expectedSdkVersion.trim().toLowerCase())) { + Utils.printAsGitHubError(`The NET_SDK_VERSION variable is not a valid dotnet version: ${expectedSdkVersion}`); + Deno.exit(1); + } + + // Find all of the csproj files for inspection + const csprojEntries = walkSync(searchBaseDirPath, { + includeDirs: false, + includeFiles: true, + exts: [".csproj"], + }); + + const csProFiles = [...csprojEntries].map((entry) => entry.path.replaceAll("\\", "/")) + .filter((path) => !/(\/bin\/|\/obj\/)/.test(path)); + + const filesWithoutTargetFramework: string[] = []; + const nonMatchingVersions: [string, string][] = []; + + for (const csProjFile of csProFiles) { + const fileData = Deno.readTextFileSync(csProjFile); + + let currentSdkVersion = ""; + + // If the target framework XML exists, check the version value. + if (this.targetFrameworkRegex.test(fileData)) { + try { + currentSdkVersion = this.getCSProjTargetFrameworkVersion(fileData).replace("net", ""); + } catch (error) { + nonMatchingVersions.push([csProjFile, error.message]); + } + + const versionsMatch = expectedSdkVersion === currentSdkVersion; + + if (versionsMatch) { + continue; + } + + const errorMsg = `The current target framework version '${currentSdkVersion}' in the csproj file '${csProjFile}' does not match the expected` + + ` version of '${expectedSdkVersion}'. Please check the 'NET_SDK_VERSION' repository variable.`; + nonMatchingVersions.push([csProjFile, errorMsg]); + } else { + filesWithoutTargetFramework.push(csProjFile); + } + } + + // If there are any issues with any of the files, print them out. + if (filesWithoutTargetFramework.length > 0 || nonMatchingVersions.length > 0) { + filesWithoutTargetFramework.forEach((fileWithout) => { + Utils.printAsGitHubError(`The file '${fileWithout}' does not have a target framework defined.`); + }); + + nonMatchingVersions.forEach((nonMatchingVersion) => { + const errorMsg = `The file '${nonMatchingVersion[0]}' has a target framework version that does not ` + + `match the NET_SDK_VERSION variable.\n${nonMatchingVersion[1]}`; + Utils.printAsGitHubError(errorMsg); + }); + + Deno.exit(1); + } + } + + /** + * Gets the first occurrence of a dotnet target framework version found in the given {@link csProjFileData}. + * @param csProjFileData The csproj file data that might contain the target framework version. + * @returns The dotnet SDK version. + */ + public getCSProjTargetFrameworkVersion(csProjFileData: string): string { + const tagMatches = csProjFileData.match(this.targetFrameworkRegex); + + const targetFrameworkTags = tagMatches === null || tagMatches.length === 0 ? [] : [...tagMatches]; + + if (targetFrameworkTags.length === 0) { + throw new Error("Could not find any target framework XML tags in the given csproj file data."); + } + + const matches: string[] = targetFrameworkTags[0].match(this.csprojTargetFrameworkVersionRegex) ?? []; + + return matches.length > 0 ? matches[0] : ""; + } +} diff --git a/cicd/core/TwitterAuthValues.ts b/cicd/core/TwitterAuthValues.ts deleted file mode 100644 index 346e3598..00000000 --- a/cicd/core/TwitterAuthValues.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Represents the Twitter secrets. - */ -export interface TwitterAuthValues { - /** - * Gets or sets the consumer key. - */ - consumer_api_key: string; - - /** - * Gets or sets the consumer secret. - */ - consumer_api_secret: string; - - /** - * Gets or sets the access token key. - */ - access_token_key: string; - - /** - * Gets or sets the access token secret. - */ - access_token_secret: string; -} diff --git a/cicd/core/Utils.ts b/cicd/core/Utils.ts index 1ff74f2f..7df5856c 100644 --- a/cicd/core/Utils.ts +++ b/cicd/core/Utils.ts @@ -8,9 +8,6 @@ import { IssueModel, LabelModel, ProjectModel, PullRequestModel, UserModel } fro export class Utils { private static readonly prodVersionRegex = /^v([1-9]\d*|0)\.([1-9]\d*|0)\.([1-9]\d*|0)$/; private static readonly prevVersionRegex = /^v([1-9]\d*|0)\.([1-9]\d*|0)\.([1-9]\d*|0)-preview\.([1-9]\d*|0)$/; - private static readonly dotnetSDKVersionRegex = /([1-9]\d*|0)\.([1-9]\d*|0)\.([1-9]\d*|0)/gm; - private static readonly csprojTargetFrameworkVersionRegex = /net([1-9]\d*|0)(\.([1-9]\d*|0)|)(\.([1-9]\d*|0)|)/gm; - private static readonly targetFrameworkRegex = /\s*net.+\s*<\/TargetFramework\s*>/gm; private static readonly featureBranchRegex = /^feature\/[1-9][0-9]*-(?!-)[a-z-]+/gm; /** @@ -42,6 +39,8 @@ export class Utils { console.log("::endgroup::"); } + // TODO: Move this function to a Guards class + /** * Checks if the value is null, undefined, or empty. * @param value The value to check. @@ -251,7 +250,7 @@ export class Utils { * @param version The version to check. * @returns True if the version is a valid production version, otherwise false. */ - public static validProdVersion(version: string): boolean { + public static isValidProdVersion(version: string): boolean { return this.prodVersionRegex.test(version.trim().toLowerCase()); } @@ -261,7 +260,7 @@ export class Utils { * @returns True if the version is not a valid production version, otherwise false. */ public static isNotValidProdVersion(version: string): boolean { - return !Utils.validProdVersion(version); + return !Utils.isValidProdVersion(version); } /** @@ -269,7 +268,7 @@ export class Utils { * @param version The version to check. * @returns True if the version is a valid preview version, otherwise false. */ - public static validPreviewVersion(version: string): boolean { + public static isValidPreviewVersion(version: string): boolean { return this.prevVersionRegex.test(version.trim().toLowerCase()); } @@ -279,44 +278,7 @@ export class Utils { * @returns True if the version is not a valid preview version, otherwise false. */ public static isNotValidPreviewVersion(version: string): boolean { - return !Utils.validPreviewVersion(version); - } - - /** - * Returns a value indicating whether or not the given {@link version} is a valid version. - * @param version The version to check. - * @returns True if the version is a valid version, otherwise false. - */ - public static isValidDotnetSDKVersion(version: string): boolean { - return this.dotnetSDKVersionRegex.test(version.trim().toLowerCase()); - } - - /** - * Returns a value indicating whether or not the given {@link csProjFileData} contains the target framework XML tag. - * @param csProjFileData The csproj file data that might contain the target framework version. - * @returns True if the target framework XML tag exists in the given {@link csProjFileData}, otherwise false. - */ - public static targetFrameworkXMLExists(csProjFileData: string): boolean { - return this.targetFrameworkRegex.test(csProjFileData); - } - - /** - * Gets the first occurrence of a dotnet target framework version found in the given {@link csProjFileData}. - * @param csProjFileData The csproj file data that might contain the target framework version. - * @returns The dotnet SDK version. - */ - public static getCSProjTargetFrameworkVersion(csProjFileData: string): string { - const tagMatches = csProjFileData.match(this.targetFrameworkRegex); - - const targetFrameworkTags = tagMatches === null || tagMatches.length === 0 ? [] : [...tagMatches]; - - if (targetFrameworkTags.length === 0) { - throw new Error("Could not find any target framework XML tags in the given csproj file data."); - } - - const matches: string[] = targetFrameworkTags[0].match(this.csprojTargetFrameworkVersionRegex) ?? []; - - return matches.length > 0 ? matches[0] : ""; + return !Utils.isValidPreviewVersion(version); } /** diff --git a/cicd/core/Validators.ts b/cicd/core/Validators.ts new file mode 100644 index 00000000..810baf19 --- /dev/null +++ b/cicd/core/Validators.ts @@ -0,0 +1,69 @@ +import { MilestoneClient, OrgClient, RepoClient } from "../../deps.ts"; +import getEnvVar from "./GetEnvVar.ts"; +import { Utils } from "./Utils.ts"; + +/** + * Validates that a GitHub organization exists. + * @param scriptFileName The name of the script file. + * @remarks The owner and token are retrieved from the environment variables 'OWNER_NAME' and 'GITHUB_TOKEN'. + */ +const validateOrgExists = async (scriptFileName?: string): Promise => { + const ownerName = getEnvVar("OWNER_NAME", scriptFileName); + const token = getEnvVar("GITHUB_TOKEN", scriptFileName) + + const orgClient = new OrgClient(ownerName, token); + + // If the org does not exist + if (!(await orgClient.exists())) { + const errorMsg = `The organization '${ownerName}' does not exist.` + + (Utils.isNothing(scriptFileName) ? "" : `\n\t${scriptFileName}`); + + Utils.printAsGitHubError(errorMsg); + Deno.exit(1); + } +} + +/** + * Validates that a GitHub organization exists. + * @param scriptFileName The name of the script file. + * @remarks The owner and token are retrieved from the environment variables 'OWNER_NAME' 'REPO_NAME', and 'GITHUB_TOKEN'. + */ +const validateRepoExists = async (scriptFileName?: string): Promise => { + const ownerName = getEnvVar("OWNER_NAME", scriptFileName); + const repoName = getEnvVar("REPO_NAME", scriptFileName); + const token = getEnvVar("GITHUB_TOKEN", scriptFileName) + + const repoClient = new RepoClient(ownerName, repoName, token); + + if (!(await repoClient.exists())) { + const errorMsg = `The repository '${repoName}' does not exist.` + + (Utils.isNothing(scriptFileName) ? "" : `\n\t${scriptFileName}`); + + Utils.printAsGitHubError(errorMsg); + Deno.exit(1); + } +} + +/** + * Validates that a GitHub milestone exists. + * @param milestoneTitle The title of the milestone. + * @param scriptFileName The name of the script file. + * @remarks The owner and token are retrieved from the environment variables 'OWNER_NAME' 'REPO_NAME', and 'GITHUB_TOKEN'. + */ +const validateMilestoneExists = async (milestoneTitle: string, scriptFileName?: string): Promise => { + const ownerName = getEnvVar("OWNER_NAME", scriptFileName); + const repoName = getEnvVar("REPO_NAME", scriptFileName); + const token = getEnvVar("GITHUB_TOKEN", scriptFileName) + + const milestoneClient = new MilestoneClient(ownerName, repoName, token); + + if (!(await milestoneClient.milestoneExists(milestoneTitle))) { + const errorMsg = `The milestone '${milestoneTitle}' for repo '${repoName}' does not exist.` + + (Utils.isNothing(scriptFileName) ? "" : `\n\t${scriptFileName}`); + + Utils.printAsGitHubError(errorMsg); + Deno.exit(1); + } +} + +export { validateOrgExists, validateRepoExists, validateMilestoneExists }; diff --git a/cicd/playground.ts b/cicd/playground.ts index 3b0fad64..a3321eed 100644 --- a/cicd/playground.ts +++ b/cicd/playground.ts @@ -1,2 +1,2 @@ -const _token = Deno.args[0]; // NOTE: This is coming from the launch.config json file as an environment variable -const _rootRepoDirPath = Deno.args[1]; +const _token = (Deno.env.get("ROOT_REPO_DIR_PATH") ?? "").trim(); +const _rootRepoDirPath = (Deno.env.get("GITHUB_TOKEN") ?? "").trim(); diff --git a/cicd/scripts/add-item-to-project.ts b/cicd/scripts/add-item-to-project.ts index 39806bda..6edebcde 100644 --- a/cicd/scripts/add-item-to-project.ts +++ b/cicd/scripts/add-item-to-project.ts @@ -1,6 +1,34 @@ -import { AddItemToProjectRunner } from "./runners/AddItemToProjectRunner.ts"; +import { ProjectClient, IssueClient, PullRequestClient } from "../../deps.ts"; +import getEnvVar from "../core/GetEnvVar.ts"; +import { Utils } from "../core/Utils.ts"; -const AddItemToProjectExecutor: AddItemToProjectRunner = new AddItemToProjectRunner(Deno.args); -await AddItemToProjectExecutor.run(); +const scriptFileName = new URL(import.meta.url).pathname.split("/").pop(); -export default AddItemToProjectExecutor; +const ownerName = getEnvVar("REPO_OWNER", scriptFileName); +const repoName = getEnvVar("REPO_NAME", scriptFileName); +const token = getEnvVar("GITHUB_TOKEN", scriptFileName); +const projectName = getEnvVar("PROJECT_NAME", scriptFileName); +const issueOrPrNumberStr = getEnvVar("ISSUE_OR_PR_NUMBER", scriptFileName); + +const projectClient = new ProjectClient(ownerName, repoName, token); +const issueClient = new IssueClient(ownerName, repoName, token); +const prClient = new PullRequestClient(ownerName, repoName, token); + +const issueOrPrNumber = parseInt(issueOrPrNumberStr); + +const isIssueNumber = await issueClient.issueExists(issueOrPrNumber); +const isPRNumber = await prClient.pullRequestExists(issueOrPrNumber); + +if (isIssueNumber && !isPRNumber) { + await projectClient.addIssueToProject(issueOrPrNumber, projectName); +} else if (!isIssueNumber && isPRNumber) { + await projectClient.addPullRequestToProject(issueOrPrNumber, projectName); +} else { + const errorMsg = `Could not distinguish between the issue or pull request number '${issueOrPrNumber}'.`; + Utils.printAsGitHubError(errorMsg); + Deno.exit(1); +} + +const itemType = isIssueNumber && !isPRNumber ? "issue" : "pull request"; + +Utils.printAsGitHubNotice(`The ${itemType} has been added to the ${projectName} project.`); diff --git a/cicd/scripts/close-milestone.ts b/cicd/scripts/close-milestone.ts index b18f9ac1..f17c1a9b 100644 --- a/cicd/scripts/close-milestone.ts +++ b/cicd/scripts/close-milestone.ts @@ -1,47 +1,29 @@ import { MilestoneClient, RepoClient } from "../../deps.ts"; import { MilestoneModel } from "../../deps.ts"; +import getEnvVar from "../core/GetEnvVar.ts"; import { Utils } from "../core/Utils.ts"; +import { validateOrgExists, validateRepoExists } from "../core/Validators.ts"; -const closeMilestoneExecutor = async () => { - if (Deno.args.length != 4) { - let errorMsg = `The cicd script must have 4 arguments but has ${Deno.args.length} argument(s).`; - errorMsg += "\nThe 1st arg is required and must be the GitHub name of the owner of the repository."; - errorMsg += "\nThe 2st arg is required and must be the GitHub repo name."; - errorMsg += "\nThe 3rd arg is required and must be a valid milestone name."; - errorMsg += "\nThe 4th arg is required and must be a valid GitHub PAT (Personal Access Token)."; +const scriptFileName = new URL(import.meta.url).pathname.split("/").pop(); - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); - } +const ownerName = getEnvVar("OWNER_NAME", scriptFileName); +const repoName = getEnvVar("REPO_NAME", scriptFileName); +const milestoneName = getEnvVar("MILESTONE_NAME", scriptFileName); +const token = getEnvVar("GITHUB_TOKEN", scriptFileName); - const ownerName: string = Deno.args[0].trim(); - const repoName: string = Deno.args[1].trim(); - const milestoneName: string = Deno.args[2].trim(); - const token = Deno.args[3].trim(); +const repoClient: RepoClient = new RepoClient(ownerName, repoName, token); +const repoDoesNotExist = !(await repoClient.exists()); - // Print out all of the arguments - Utils.printInGroup("Script Arguments", [ - `Owner Name (Required): ${ownerName}`, - `Repo Name (Required): ${repoName}`, - `Milestone Name (Required): ${milestoneName}`, - `GitHub Token (Required): ****`, - ]); +if (repoDoesNotExist) { + Utils.printAsGitHubError(`The repository '${repoName}' does not exist.`); + Deno.exit(1); +} - const repoClient: RepoClient = new RepoClient(ownerName, repoName, token); - const repoDoesNotExist = !(await repoClient.exists()); +await validateOrgExists(scriptFileName); +await validateRepoExists(scriptFileName); - if (repoDoesNotExist) { - Utils.printAsGitHubError(`The repository '${repoName}' does not exist.`); - Deno.exit(1); - } +const milestoneClient: MilestoneClient = new MilestoneClient(ownerName, repoName, token); - const milestoneClient: MilestoneClient = new MilestoneClient(ownerName, repoName, token); +const milestone: MilestoneModel = await milestoneClient.getMilestoneByName(milestoneName); - const milestone: MilestoneModel = await milestoneClient.getMilestoneByName(milestoneName); - - await milestoneClient.closeMilestone(milestone.title); -}; - -closeMilestoneExecutor(); - -export default closeMilestoneExecutor; +await milestoneClient.closeMilestone(milestone.title); diff --git a/cicd/scripts/github-release-does-not-exist.ts b/cicd/scripts/github-release-does-not-exist.ts index 36b84bc6..1e75e0e5 100644 --- a/cicd/scripts/github-release-does-not-exist.ts +++ b/cicd/scripts/github-release-does-not-exist.ts @@ -1,52 +1,33 @@ import { ReleaseClient } from "../../deps.ts"; +import getEnvVar from "../core/GetEnvVar.ts"; import { Utils } from "../core/Utils.ts"; +import { validateOrgExists, validateRepoExists } from "../core/Validators.ts"; -const githubReleaseDoesNotExistExecutor = async () => { - if (Deno.args.length != 4) { - let errorMsg = `The cicd script must have 4 arguments but has ${Deno.args.length} argument(s).`; - errorMsg += "\nThe 1st arg is required and must be a the name of the repository owner."; - errorMsg += "\nThe 2nd arg is required and must be a the name of the repository."; - errorMsg += "\nThe 3rd arg is required and must be the name of the tag for the release."; - errorMsg += "\nThe 4th arg is required and must be a GitHub PAT (Personal Access Token)."; - - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); - } - - const ownerName = Deno.args[0].trim(); - const repoName = Deno.args[1].trim(); - const tagName = Deno.args[2].trim(); - const githubToken = Deno.args[3].trim(); - - // Print out all of the arguments - Utils.printInGroup("Script Arguments", [ - `Repo Owner (Required): ${repoName}`, - `Repository Name (Required): ${repoName}`, - `Tag Name (Required): ${tagName}`, - `GitHub Token (Required): ${githubToken}`, - ]); - - // Validate the tag - if (Utils.isNotValidProdVersion(tagName) && Utils.isNotValidPreviewVersion(tagName)) { - Utils.printAsGitHubError( - `The tag name '${tagName}' is not a valid tag name. The tag name must be a valid production or preview version.`, - ); - Deno.exit(1); - } - - const releaseClient: ReleaseClient = new ReleaseClient(ownerName, repoName, githubToken); - - const releaseExists = await releaseClient.releaseExists(tagName); - - if (releaseExists) { - let errorMsg = `A release for the tag '${tagName}' already exists.`; - errorMsg += "\nIs the tag provided the incorrect tag?"; - - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); - } -}; - -githubReleaseDoesNotExistExecutor(); - -export default githubReleaseDoesNotExistExecutor; +const scriptFileName = new URL(import.meta.url).pathname.split("/").pop(); + +const ownerName = getEnvVar("OWNER_NAME", scriptFileName); +const repoName = getEnvVar("REPO_NAME", scriptFileName); +const tagName = getEnvVar("TAG_NAME", scriptFileName); +const githubToken = getEnvVar("GITHUB_TOKEN", scriptFileName); + +// Validate the tag +if (Utils.isNotValidProdVersion(tagName) && Utils.isNotValidPreviewVersion(tagName)) { + const errorMsg = `The tag name '${tagName}' is not a valid tag name. The tag name must be a valid production or preview version.`; + Utils.printAsGitHubError(errorMsg); + Deno.exit(1); +} + +await validateOrgExists(scriptFileName); +await validateRepoExists(scriptFileName); + +const releaseClient: ReleaseClient = new ReleaseClient(ownerName, repoName, githubToken); + +const releaseExists = await releaseClient.releaseExists(tagName); + +if (releaseExists) { + const errorMsg = `A release for the tag '${tagName}' already exists.` + + "\nIs the tag provided the incorrect tag?"; + + Utils.printAsGitHubError(errorMsg); + Deno.exit(1); +} diff --git a/cicd/scripts/milestone-exists.ts b/cicd/scripts/milestone-exists.ts index 2e3c764e..3ff46b43 100644 --- a/cicd/scripts/milestone-exists.ts +++ b/cicd/scripts/milestone-exists.ts @@ -1,50 +1,9 @@ -import { MilestoneClient, RepoClient } from "../../deps.ts"; -import { Utils } from "../core/Utils.ts"; +import getEnvVar from "../core/GetEnvVar.ts"; +import { validateMilestoneExists, validateRepoExists } from "../core/Validators.ts"; -const milestoneExistsExecutor = async () => { - if (Deno.args.length != 4) { - let errorMsg = `The cicd script must have 4 arguments but has ${Deno.args.length} argument(s).`; - errorMsg += "\nThe 1st arg is required and must be the GitHub repository owner name."; - errorMsg += "\nThe 2nd arg is required and must be the GitHub repo name."; - errorMsg += "\nThe 3rd arg is required and must be the title of the milestone."; - errorMsg += "\nThe 4th arg is required and must be a GitHub PAT (Personal Access Token)."; +const scriptFileName = new URL(import.meta.url).pathname.split("/").pop(); - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); - } +const milestoneTitle = getEnvVar("MILESTONE_TITLE", scriptFileName); - const ownerName = Deno.args[0].trim(); - const repoName = Deno.args[1].trim(); - const milestoneTitle = Deno.args[2].trim(); - const githubToken = Deno.args[3].trim(); - - // Print out all of the arguments - Utils.printInGroup("Script Arguments", [ - `Repo Owner (Required): ${ownerName}`, - `Repo Name (Required): ${repoName}`, - `Milestone (Required): ${milestoneTitle}`, - `GitHub Token (Required): ${Utils.isNothing(githubToken) ? "Not Provided" : "****"}`, - ]); - - const repoClient: RepoClient = new RepoClient(ownerName, repoName, githubToken); - const repoDoesNotExist = !(await repoClient.exists()); - - if (repoDoesNotExist) { - Utils.printAsGitHubError(`The repository '${repoName}' does not exist.`); - Deno.exit(1); - } - - const milestoneClient: MilestoneClient = new MilestoneClient(ownerName, repoName, githubToken); - - const milestoneDoesNotExist = !(await milestoneClient.milestoneExists(milestoneTitle)); - - // Check if the milestone exists - if (milestoneDoesNotExist) { - Utils.printAsGitHubError(`The milestone '${milestoneTitle}' for repo '${repoName}' does not exist.`); - Deno.exit(1); - } -}; - -milestoneExistsExecutor(); - -export default milestoneExistsExecutor; +await validateRepoExists(scriptFileName); +await validateMilestoneExists(milestoneTitle, scriptFileName); diff --git a/cicd/scripts/mod.ts b/cicd/scripts/mod.ts index 17a6cd82..50e13952 100644 --- a/cicd/scripts/mod.ts +++ b/cicd/scripts/mod.ts @@ -1,17 +1,6 @@ // deno-lint-ignore-file no-unused-vars -import AddItemToProjectExecutor from "./add-item-to-project.ts"; -import closeMilestoneExecutor from "./close-milestone.ts"; -import githubReleaseDoesNotExistExecutor from "./github-release-does-not-exist.ts"; import labelIfHeadBranchExecutor from "./label-if-head-branch.ts"; -import milestoneExistsExecutor from "./milestone-exists.ts"; import milestoneItemsAllClosedExecutor from "./milestone-items-all-closed.ts"; -import nugetPkgDoesNotExistExecutor from "./nuget-pkg-does-not-exist.ts"; import prepareReleaseExecutor from "./prepare-release.ts"; -import resolveCsProjExecutor from "./resolve-csproj.ts"; -import sendReleaseTweetExecutor from "./send-release-tweet.ts"; import syncPrToIssueExecutor from "./sync-pr-to-issue.ts"; -import transpileReadMeExecutor from "./transpile-readme.ts"; -import validateTagExecutor from "./validate-tag.ts"; -import validateVersionExecutor from "./validate-version.ts"; -import validateSDKSetupExecutor from "./validate-sdk-setup.ts"; diff --git a/cicd/scripts/nuget-pkg-does-not-exist.ts b/cicd/scripts/nuget-pkg-does-not-exist.ts index 3b248a55..daa4bf08 100644 --- a/cicd/scripts/nuget-pkg-does-not-exist.ts +++ b/cicd/scripts/nuget-pkg-does-not-exist.ts @@ -1,45 +1,27 @@ import { NuGetClient } from "../../deps.ts"; +import getEnvVar from "../core/GetEnvVar.ts"; import { Utils } from "../core/Utils.ts"; -const nugetPkgDoesNotExistExecutor = async () => { - if (Deno.args.length != 2) { - let errorMsg = `The cicd script must have 2 arguments but has ${Deno.args.length} argument(s).`; - errorMsg += "\nThe 1st arg is required and must be a valid NuGet package name."; - errorMsg += "\nThe 2nd arg is required and must be a valid NuGet package version."; +const scriptFileName = new URL(import.meta.url).pathname.split("/").pop(); - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); - } +const packageName = getEnvVar("NUGET_PKG_NAME", scriptFileName); +let version = getEnvVar("NUGET_PKG_VERSION", scriptFileName); - const packageName = Deno.args[0].trim(); - let version = Deno.args[1].trim(); +// NuGet package versions do not start with the letter 'v' +version = version.startsWith("v") ? version.substring(1) : version; - // NuGet package versions do not start with the letter 'v' - version = version.startsWith("v") ? version.substring(1) : version; +const client: NuGetClient = new NuGetClient(); - // Print out all of the arguments - Utils.printInGroup("Script Arguments", [ - `NuGet Package Name (Required): ${packageName}`, - `Package Version (Required): ${version}`, - ]); +const packageDoestNotExist = !(await client.packageExists(packageName)); - const client: NuGetClient = new NuGetClient(); +if (packageDoestNotExist) { + Utils.printAsGitHubError(`The NuGet package '${packageName}' does not exist.`); + Deno.exit(1); +} - const packageDoestNotExist = !(await client.packageExists(packageName)); +const packageVersionExists: boolean = await client.packageWithVersionExists(packageName, version); - if (packageDoestNotExist) { - Utils.printAsGitHubError(`The NuGet package '${packageName}' does not exist.`); - Deno.exit(1); - } - - const packageVersionExists: boolean = await client.packageWithVersionExists(packageName, version); - - if (packageVersionExists) { - Utils.printAsGitHubError(`The NuGet package '${packageName}' with version '${version}' already exists.`); - Deno.exit(1); - } -}; - -nugetPkgDoesNotExistExecutor(); - -export default nugetPkgDoesNotExistExecutor; +if (packageVersionExists) { + Utils.printAsGitHubError(`The NuGet package '${packageName}' with version '${version}' already exists.`); + Deno.exit(1); +} diff --git a/cicd/scripts/resolve-csproj.ts b/cicd/scripts/resolve-csproj.ts index 43a72db4..38c72d0b 100644 --- a/cicd/scripts/resolve-csproj.ts +++ b/cicd/scripts/resolve-csproj.ts @@ -1,6 +1,55 @@ -import { ResolveCsProjRunner } from "./runners/ResolveCsProjRunner.ts"; +import { walkSync } from "@std/fs"; -const resolveCsProjExecutor: ResolveCsProjRunner = new ResolveCsProjRunner(Deno.args); -await resolveCsProjExecutor.run(); +import { Utils } from "../core/Utils.ts"; -export default resolveCsProjExecutor; +const scriptFileName = new URL(import.meta.url).pathname.split("/").pop(); + +const projName = (Deno.env.get("PROJECT_NAME") ?? "").trim(); + +if (Utils.isNothing(projName)) { + Utils.printAsGitHubError(`The 'PROJECT_NAME' environment variable does not exist.\n\t${scriptFileName}`); + Deno.exit(1); +} + +const baseDirPath = (Deno.env.get("BASE_DIR_PATH") ?? "").trim(); + +if (Utils.isNothing(baseDirPath)) { + Utils.printAsGitHubError(`The 'BASE_DIR_PATH' environment variable does not exist.\n\t${scriptFileName}`); + Deno.exit(1); +} + +const token = (Deno.env.get("GITHUB_TOKEN") ?? "").trim(); + +if (Utils.isNothing(token)) { + Utils.printAsGitHubError(`The 'GITHUB_TOKEN' environment variable does not exist.\n\t${scriptFileName}`); + Deno.exit(1); +} + +const filteredResults = [...walkSync(baseDirPath, { + includeDirs: true, + includeFiles: true, + exts: [".csproj"], + match: [new RegExp(`.*${projName}\\..*`)] +})].map((entry) => entry.path); + +if (filteredResults.length <= 0) { + const errorMsg = `No csproj files were found in '${baseDirPath}' for the project '${projName}'.`; + Utils.printAsGitHubError(errorMsg); + Deno.exit(300); +} + +const outputFilePath = Deno.env.get("GITHUB_OUTPUT"); + +if (outputFilePath === undefined) { + const errorMsg = "The GITHUB_OUTPUT environment variable is not set."; + Utils.printAsGitHubError(errorMsg); + Deno.exit(400); +} + +const outputName = "project-file-path"; +const csProjFilePath = filteredResults[0]; +const output = `\n${outputName}=${csProjFilePath}`; + +Deno.writeTextFileSync(outputFilePath, output); + +Utils.printAsGitHubNotice(`Set output '${outputName}' set to a value of '${csProjFilePath}'.`); diff --git a/cicd/scripts/runners/AddItemToProjectRunner.ts b/cicd/scripts/runners/AddItemToProjectRunner.ts deleted file mode 100644 index b935223f..00000000 --- a/cicd/scripts/runners/AddItemToProjectRunner.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { IssueClient } from "../../../deps.ts"; -import { OrgClient } from "../../../deps.ts"; -import { ProjectClient } from "../../../deps.ts"; -import { PullRequestClient } from "../../../deps.ts"; -import { RepoClient } from "../../../deps.ts"; -import { Utils } from "../../core/Utils.ts"; -import { ScriptRunner } from "./ScriptRunner.ts"; - -/** - * Adds an issue or pull request to a V2 GitHub project. - */ -export class AddItemToProjectRunner extends ScriptRunner { - private readonly projectClient: ProjectClient; - private readonly issueClient: IssueClient; - private readonly prClient: PullRequestClient; - - /** - * Initializes a new instance of the {@link AddItemToProjectRunner} class. - * @param args The arguments to process. - */ - constructor(args: string[]) { - super(args); - - const [ownerName, _, repoName] = this.args; - - this.projectClient = new ProjectClient(ownerName, repoName, this.token); - this.issueClient = new IssueClient(ownerName, repoName, this.token); - this.prClient = new PullRequestClient(ownerName, repoName, this.token); - } - - /** - * Runs the runner to add the item to the project. - */ - public async run(): Promise { - await super.run(); - - const [, projectName, _, issueOrPrNumberStr] = this.args; - - const issueOrPrNumber = parseInt(issueOrPrNumberStr); - - const isIssueNumber = await this.issueClient.issueExists(issueOrPrNumber); - const isPRNumber = await this.prClient.pullRequestExists(issueOrPrNumber); - - if (isIssueNumber && !isPRNumber) { - await this.projectClient.addIssueToProject(issueOrPrNumber, projectName); - } else if (!isIssueNumber && isPRNumber) { - await this.projectClient.addPullRequestToProject(issueOrPrNumber, projectName); - } else { - const errorMsg = `Could not distinguish between the issue or pull request number '${issueOrPrNumber}'.`; - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); - } - - const itemType = isIssueNumber && !isPRNumber ? "issue" : "pull request"; - - Utils.printAsGitHubNotice(`The ${itemType} has been added to the ${projectName} project.`); - } - - /** - * @inheritdoc - */ - protected async validateArgs(args: string[]): Promise { - if (args.length != 5) { - let errorMsg = `The cicd script must have at 5 arguments but has ${args.length} arguments(s).`; - errorMsg += "\nThe 1st arg is required and must be a valid organization name."; - errorMsg += "\nThe 2nd arg is required and must be a valid GitHub V2 project name."; - errorMsg += "\nThe 3rd arg is required and must be the GitHub repo name."; - 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); - Deno.exit(1); - } - - const [ownerName, projectName, repoName, issueOrPRNumberStr] = args; - - const orgClient = new OrgClient(ownerName, this.token); - const repoClient = new RepoClient(ownerName, repoName, this.token); - - if (!Utils.isNumeric(issueOrPRNumberStr)) { - Utils.printAsGitHubError(`The given issue or pull request number '${issueOrPRNumberStr}' is not a valid number.`); - Deno.exit(1); - } - - if (!(await orgClient.exists())) { - Utils.printAsGitHubError(`The organization '${ownerName}' does not exist.`); - Deno.exit(1); - } - - if (!(await repoClient.exists())) { - Utils.printAsGitHubError(`The repository '${repoName}' does not exist.`); - Deno.exit(1); - } - - if (!(await this.projectClient.exists(projectName))) { - Utils.printAsGitHubError(`The V2 project '${projectName}' does not exist.`); - Deno.exit(1); - } - const issueOrPrNumber = parseInt(issueOrPRNumberStr); - - const isIssueNumber = await this.issueClient.issueExists(issueOrPrNumber); - const isPRNumber = await this.prClient.pullRequestExists(issueOrPrNumber); - - // If the issue or PR number does not exist - if (!isIssueNumber && !isPRNumber) { - Utils.printAsGitHubError(`The issue or pull request number '${issueOrPrNumber}' does not exist.`); - Deno.exit(1); - } - } - - /** - * @inheritdoc - */ - protected mutateArgs(args: string[]): string[] { - let [orgName, projectName, repoName, issueOrPRNumberStr, token] = args; - - orgName = orgName.trim(); - projectName = projectName.trim(); - repoName = repoName.trim(); - issueOrPRNumberStr = issueOrPRNumberStr.trim(); - token = token.trim(); - - return [orgName, projectName, repoName, issueOrPRNumberStr, token]; - } -} diff --git a/cicd/scripts/runners/ResolveCsProjRunner.ts b/cicd/scripts/runners/ResolveCsProjRunner.ts deleted file mode 100644 index 2d4f5997..00000000 --- a/cicd/scripts/runners/ResolveCsProjRunner.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { Directory, File, Path } from "../../../deps.ts"; -import { Utils } from "../../core/Utils.ts"; -import { ScriptRunner } from "./ScriptRunner.ts"; - -/** - * Resolves dotnet csproj files from a relative directory path. - */ -export class ResolveCsProjRunner extends ScriptRunner { - /** - * @inheritdoc - */ - public async run(): Promise { - await super.run(); - - const [projName, baseDirPath] = this.args; - - const filteredResults: string[] = Directory.getFiles(baseDirPath, true) - .filter((f) => { - const fileName = Path.getFileName(f); - - return fileName.endsWith(".csproj") && fileName.includes(projName); - }); - - if (filteredResults.length <= 0) { - const errorMsg = `No csproj files were found in '${baseDirPath}' for the project '${projName}'.`; - Utils.printAsGitHubError(errorMsg); - Deno.exit(300); - } - - const outputFilePath = Deno.env.get("GITHUB_OUTPUT"); - - if (outputFilePath === undefined) { - const errorMsg = "The GITHUB_OUTPUT environment variable is not set."; - Utils.printAsGitHubError(errorMsg); - Deno.exit(400); - } - - const outputName = "project-file-path"; - const csProjFilePath = filteredResults[0]; - const output = `${outputName}=${csProjFilePath}`; - - File.SaveFile(outputFilePath, output); - - Utils.printAsGitHubNotice(`Set output '${outputName}' set to a value of '${csProjFilePath}'.`); - } - - /** - * @inheritdoc - */ - protected validateArgs(args: string[]): Promise { - if (args.length != 3) { - let errorMsg = `The cicd script must have at 3 arguments but has ${args.length} arguments(s).`; - errorMsg += "\nThe 1st arg is required and must be the name of the project."; - errorMsg += "\nThe 2nd arg is required and must be base directory path to start the search."; - errorMsg += "\nThe 3th arg is required and must be a valid GitHub PAT (Personal Access Token)."; - - Utils.printAsGitHubError(errorMsg); - Deno.exit(100); - } - - const [_, baseDirPath] = args; - - if (Directory.DoesNotExist(baseDirPath)) { - const errorMsg = `The base directory path '${baseDirPath}' does not exist.`; - Utils.printAsGitHubError(errorMsg); - Deno.exit(200); - } - - return Promise.resolve(); - } - - /** - * @inheritdoc - */ - protected mutateArgs(args: string[]): string[] { - let [projName, baseDirPath, token] = args; - - projName = projName.trim(); - baseDirPath = baseDirPath.trim(); - token = token.trim(); - - return [projName, baseDirPath, token]; - } -} diff --git a/cicd/scripts/runners/ScriptRunner.ts b/cicd/scripts/runners/ScriptRunner.ts index 5dbd79ba..486f4987 100644 --- a/cicd/scripts/runners/ScriptRunner.ts +++ b/cicd/scripts/runners/ScriptRunner.ts @@ -46,20 +46,8 @@ export abstract class ScriptRunner { * Runs a script. */ public async run(): Promise { - const argValuesBeforeMutation = this.args.map((arg) => { - return arg.startsWith(this.fineGrainedTokenPrefix) || arg.startsWith(this.classicTokenPrefix) ? "***" : arg; - }); - - Utils.printInGroup("Script Arguments (Before Mutation):", argValuesBeforeMutation); - this.args = this.mutateArgs(this.args); - const argValuesAfterMutation = this.args.map((arg) => { - return arg.startsWith(this.fineGrainedTokenPrefix) || arg.startsWith(this.classicTokenPrefix) ? "***" : arg; - }); - - Utils.printInGroup("Script Arguments (After Mutation):", argValuesAfterMutation); - await this.validateArgs(this.args); } diff --git a/cicd/scripts/runners/SendReleaseTweetRunner.ts b/cicd/scripts/runners/SendReleaseTweetRunner.ts deleted file mode 100644 index fdeabeb3..00000000 --- a/cicd/scripts/runners/SendReleaseTweetRunner.ts +++ /dev/null @@ -1,206 +0,0 @@ -import { OrgClient, RepoClient, XClient } from "../../../deps.ts"; -import { GitHubLogType } from "../../core/Enums.ts"; -import { ReleaseTweetBuilder } from "../../core/ReleaseTweetBuilder.ts"; -import { GitHubVariableService } from "../../core/Services/GitHubVariableService.ts"; -import { TwitterAuthValues } from "../../core/TwitterAuthValues.ts"; -import { Utils } from "../../core/Utils.ts"; -import { ScriptRunner } from "./ScriptRunner.ts"; - -/** - * Sends release tweets. - */ -export class SendReleaseTweetRunner extends ScriptRunner { - private static readonly TWITTER_BROADCAST_ENABLED = "TWITTER_BROADCAST_ENABLED"; - private static readonly DISCORD_INVITE_CODE = "DISCORD_INVITE_CODE"; - private static readonly RELEASE_TWEET_TEMPLATE_REPO_NAME = "RELEASE_TWEET_TEMPLATE_REPO_NAME"; - private static readonly RELEASE_TWEET_TEMPLATE_BRANCH_NAME = "RELEASE_TWEET_TEMPLATE_BRANCH_NAME"; - private static readonly RELATIVE_RELEASE_TWEET_TEMPLATE_FILE_PATH = "RELATIVE_RELEASE_TWEET_TEMPLATE_FILE_PATH"; - private readonly githubVarService: GitHubVariableService; - - /** - * Initializes a new instance of the {@link SendReleaseTweetRunner} class. - * @param args The arguments to process. - */ - constructor(args: string[]) { - super(args); - - const [ownerName, repoName] = this.args; - - this.githubVarService = new GitHubVariableService(ownerName, repoName, this.token); - } - - public async run(): Promise { - await super.run(); - - const [ownerName, repoName, version, consumerAPIKey, consumerAPISecret, accessTokenKey, accessTokenSecret] = this.args; - - this.githubVarService.setOrgAndRepo(ownerName, repoName); - - let twitterBroadcastEnabled = await this.githubVarService.getValue( - SendReleaseTweetRunner.TWITTER_BROADCAST_ENABLED, - false, - ); - twitterBroadcastEnabled = twitterBroadcastEnabled.toLowerCase(); - - if (Utils.isNothing(twitterBroadcastEnabled) || twitterBroadcastEnabled === "false") { - let noticeMsg = `No tweet broadcast will be performed.`; - noticeMsg += - `\nTo enable tweet broadcasting, set the '${SendReleaseTweetRunner.TWITTER_BROADCAST_ENABLED}' variable to 'true'.`; - noticeMsg += "\nIf the variable is missing, empty, or set to 'false', no tweet broadcast will be performed."; - Utils.printAsGitHubNotice(noticeMsg); - Deno.exit(0); - } - - const discordInviteCode = await this.githubVarService.getValue(SendReleaseTweetRunner.DISCORD_INVITE_CODE, false); - const templateRepoName = await this.githubVarService.getValue( - SendReleaseTweetRunner.RELEASE_TWEET_TEMPLATE_REPO_NAME, - false, - ); - const templateBranchName = await this.githubVarService.getValue( - SendReleaseTweetRunner.RELEASE_TWEET_TEMPLATE_BRANCH_NAME, - false, - ); - const relativeTemplateFilePath = await this.githubVarService.getValue( - SendReleaseTweetRunner.RELATIVE_RELEASE_TWEET_TEMPLATE_FILE_PATH, - false, - ); - - const authValues: TwitterAuthValues = { - consumer_api_key: consumerAPIKey, - consumer_api_secret: consumerAPISecret, - access_token_key: accessTokenKey, - access_token_secret: accessTokenSecret, - }; - - const tweetBuilder: ReleaseTweetBuilder = new ReleaseTweetBuilder(ownerName, templateRepoName); - - const tweet = await tweetBuilder.buildTweet( - templateBranchName, - relativeTemplateFilePath, - repoName, - version, - discordInviteCode, - ); - - const twitterClient: XClient = new XClient(authValues); - await twitterClient.tweet(tweet); - - const noticeMsg = `A release tweet was successfully broadcasted for the '${repoName}' project for version '${version}'.`; - Utils.printAsGitHubNotice(noticeMsg); - } - - /** - * @inheritdoc - */ - protected async validateArgs(args: string[]): Promise { - if (Deno.args.length != 8) { - const errorMsg = `The cicd script must have 8 arguments but has ${args.length} argument(s).`; - - const argDescriptions = [ - "Required and must be the name of a repository owner.", - "Required and must be a project name", - "Required and must be a valid version. ", - "Required and must be a valid twitter consumer api key.", - "Required and must be a valid twitter consumer api secret.", - "Required and must be a valid twitter access token key.", - "Required and must be a valid twitter access token secret.", - "Required and must be a GitHub PAT (Personal Access Token).", - ]; - - Utils.printAsGitHubError(errorMsg); - Utils.printAsNumberedList(" Arg: ", argDescriptions, GitHubLogType.normal); - Deno.exit(1); - } - - this.printOrgRepoVarsUsed(); - - let [ownerName, repoName, version] = args; - - this.githubVarService.setOrgAndRepo(ownerName, repoName); - - ownerName = ownerName.trim(); - repoName = repoName.trim(); - - const orgClient = new OrgClient(ownerName, this.token); - const repoClient = new RepoClient(ownerName, repoName, this.token); - - // If the org does not exist - if (!(await orgClient.exists())) { - Utils.printAsGitHubError(`The organization '${ownerName}' does not exist.`); - Deno.exit(1); - } - - // If the repo does not exist - if (!(await repoClient.exists())) { - Utils.printAsGitHubError(`The repository '${repoName}' does not exist.`); - Deno.exit(1); - } - - if (Utils.isNotValidPreviewVersion(version) && Utils.isNotValidProdVersion(version)) { - let errorMsg = `The version '${version}' is not a valid preview or production version.`; - errorMsg += "\nRequired Syntax: v#.#.# or v#.#.#-preview.#"; - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); - } - - const twitterBroadcastEnabled = (await this.githubVarService.getValue( - SendReleaseTweetRunner.TWITTER_BROADCAST_ENABLED, - false, - )).toLowerCase(); - - // Print out all of the required variables but only if the twitter broadcast is enabled - if (!Utils.isNothing(twitterBroadcastEnabled) && twitterBroadcastEnabled === "true") { - const orgRepoVariables = this.getRequiredVars(); - - // Check if all of the required org and/or repo variables exist - const [orgRepoVarExist, missingVars] = await this.githubVarService.allVarsExist(orgRepoVariables); - - if (!orgRepoVarExist) { - const missingVarErrors: string[] = []; - - for (let i = 0; i < missingVars.length; i++) { - const missingVarName = missingVars[i]; - - missingVarErrors.push(`The required org/repo variable '${missingVarName}' is missing.`); - } - - Utils.printAsGitHubErrors(missingVarErrors); - Deno.exit(1); - } - } - } - - /** - * @inheritdoc - */ - protected mutateArgs(args: string[]): string[] { - args = Utils.trimAll(args); - - let [ownerName, repoName, version, consumerAPIKey, consumerAPISecret, accessTokenKey, accessTokenSecret, token] = args; - - version = version.toLowerCase(); - version = version.startsWith("v") ? version : `v${version}`; - - return [ownerName, repoName, version, consumerAPIKey, consumerAPISecret, accessTokenKey, accessTokenSecret, token]; - } - - /** - * Prints the required org or repo variables for the runner. - */ - private printOrgRepoVarsUsed(): void { - const title = "Required Org Or Repo Variables (if release tweet is enabled)"; - Utils.printInGroup(title, this.getRequiredVars()); - } - - /* Gets the list of required vars. - * @returns The list of required vars. - */ - private getRequiredVars(): string[] { - return [ - SendReleaseTweetRunner.DISCORD_INVITE_CODE, - SendReleaseTweetRunner.RELEASE_TWEET_TEMPLATE_REPO_NAME, - SendReleaseTweetRunner.RELEASE_TWEET_TEMPLATE_BRANCH_NAME, - SendReleaseTweetRunner.RELATIVE_RELEASE_TWEET_TEMPLATE_FILE_PATH, - ]; - } -} diff --git a/cicd/scripts/runners/ValidateSDKSetupRunner.ts b/cicd/scripts/runners/ValidateSDKSetupRunner.ts deleted file mode 100644 index 936f3b67..00000000 --- a/cicd/scripts/runners/ValidateSDKSetupRunner.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { RepoClient, walkSync } from "../../../deps.ts"; -import { Utils } from "../../core/Utils.ts"; -import { ScriptRunner } from "./ScriptRunner.ts"; - -/** - * Validates that a dotnet SDK setup for a dotnet project is correct. - */ -export class ValidateSDKSetupRunner extends ScriptRunner { - private readonly repoClient: RepoClient; - - /** - * Initializes a new instance of the {@link ValidateSDKSetupRunner} class. - * @param args The arguments to process. - */ - constructor(args: string[]) { - super(args); - - const [repoOwner, repoName] = this.args; - - this.repoClient = new RepoClient(repoOwner, repoName, this.token); - } - - public async run(): Promise { - const baseDirPath = Deno.cwd(); - - // Find all of the csproj files for inspection - const entries = walkSync(baseDirPath, { - includeDirs: false, - includeFiles: true, - exts: [".csproj"], - }); - - let dotnetSDKVersion = (await this.repoClient.getVariables()) - .find((repoVar) => repoVar.name === "NET_SDK_VERSION")?.value.trim(); - - if (Utils.isNothing(dotnetSDKVersion)) { - throw new Error("The NET_SDK_VERSION variable is not defined."); - } - - dotnetSDKVersion = dotnetSDKVersion.startsWith("v") ? dotnetSDKVersion.substring(1) : dotnetSDKVersion; - - if (!Utils.isValidDotnetSDKVersion(dotnetSDKVersion)) { - Utils.printAsGitHubError(`The NET_SDK_VERSION variable is not a valid dotnet version: ${dotnetSDKVersion}`); - Deno.exit(1); - } - - const csProFiles = [...entries].map((entry) => entry.path.replaceAll("\\", "/")) - .filter((path) => !/(\/bin\/|\/obj\/)/.test(path)); - - const filesWithoutTargetFramework: string[] = []; - const nonMatchingVersions: [string, string][] = []; - - for (const csProjFile of csProFiles) { - const fileData = Deno.readTextFileSync(csProjFile); - - let targetFrameworkVersion = ""; - - // If the target framework XML exists, check the version value. - if (Utils.targetFrameworkXMLExists(fileData)) { - try { - targetFrameworkVersion = Utils.getCSProjTargetFrameworkVersion(fileData).replace("net", ""); - } catch (error) { - nonMatchingVersions.push([csProjFile, error.message]); - } - - const versionsMatch = this.versionsMatch(dotnetSDKVersion, targetFrameworkVersion); - - if (versionsMatch) { - continue; - } - - const errorMsg = `The target framework version in the csproj file '${csProjFile}' does not match the repo` + - ` variable NET_SDK_VERSION.`; - nonMatchingVersions.push([csProjFile, errorMsg]); - } else { - filesWithoutTargetFramework.push(csProjFile); - } - } - - // If there are any issues with any of the files, print them out. - if (filesWithoutTargetFramework.length > 0 || nonMatchingVersions.length > 0) { - filesWithoutTargetFramework.forEach((fileWithout) => { - Utils.printAsGitHubError(`The file '${fileWithout}' does not have a target framework defined.`); - }); - - nonMatchingVersions.forEach((nonMatchingVersion) => { - const errorMsg = `The file '${nonMatchingVersion[0]}' has a target framework version that does not ` + - `match the NET_SDK_VERSION variable.\n${nonMatchingVersion[1]}`; - Utils.printAsGitHubError(errorMsg); - }); - - Deno.exit(1); - } - } - - /** - * @inheritdoc - */ - protected async validateArgs(args: string[]): Promise { - const [repoOwner, repoName] = args; - - // Validate that the repo exists - const repoExists = await this.repoClient.exists(); - - if (!repoExists) { - throw new Error(`The repo ${repoOwner}/${repoName} does not exist.`); - } - - return Promise.resolve(); - } - - /** - * @inheritdoc - */ - protected mutateArgs(args: string[]): string[] { - let [repoOwner, repoName] = args; - - repoOwner = repoOwner.trim(); - repoName = repoName.trim(); - - return [repoOwner, repoName]; - } - - /** - * Gets a value indicating whether or not the {@link repoVarSections} and {@link csprojVersion} versions match. - * @param repoVarVersion The version from the repository variable. - * @param csprojVersion The version from the csproj file. - * @returns True if the versions match, false otherwise. - * @remarks This function takes the csproj version as precedence in regards to the major, minor, and patch sections. - * If the csproj version does not have a minor or path version, the result will be true as long as the major version - * of both values match. - */ - private versionsMatch(repoVarVersion: string, csprojVersion: string): boolean { - repoVarVersion = repoVarVersion.trim().toLowerCase(); - csprojVersion = csprojVersion.trim().toLowerCase(); - - const csprojSections = csprojVersion.split("."); - const repoVarSections = repoVarVersion.split("."); - - if (repoVarSections.length < 3) { - Utils.printAsGitHubError(`The NET_SDK_VERSION variable does not have a major, minor, and patch section.`); - Deno.exit(1); - } - - for (let i = 0; i < csprojSections.length; i++) { - const targetVersionSection = csprojSections[i]; - const repoVarSection = repoVarSections[i]; - - if (targetVersionSection !== repoVarSection) { - return false; - } - } - - return true; - } -} diff --git a/cicd/scripts/send-release-tweet.ts b/cicd/scripts/send-release-tweet.ts index 39ffeba4..da40618d 100644 --- a/cicd/scripts/send-release-tweet.ts +++ b/cicd/scripts/send-release-tweet.ts @@ -1,6 +1,71 @@ -import { SendReleaseTweetRunner } from "./runners/SendReleaseTweetRunner.ts"; +import { XAuthValues } from "https://deno.land/x/kd_clients@v1.0.0-preview.8/OtherClients/XAuthValue.ts"; +import { XClient } from "../../deps.ts"; +import { ReleaseTweetBuilder } from "../core/ReleaseTweetBuilder.ts"; +import { GitHubVariableService } from "../core/Services/GitHubVariableService.ts"; +import { Utils } from "../core/Utils.ts"; +import getEnvVar from "../core/GetEnvVar.ts"; +import { validateOrgExists, validateRepoExists } from "../core/Validators.ts"; -const sendReleaseTweetExecutor = new SendReleaseTweetRunner(Deno.args); -await sendReleaseTweetExecutor.run(); +const scriptFileName = new URL(import.meta.url).pathname.split("/").pop(); -export default sendReleaseTweetExecutor; +const ownerName = getEnvVar("OWNER_NAME", scriptFileName); +const repoName = getEnvVar("REPO_NAME", scriptFileName); +let version = getEnvVar("VERSION", scriptFileName).toLowerCase(); +const consumerAPIKey = getEnvVar("TWITTER_CONSUMER_API_KEY", scriptFileName); +const consumerAPISecret = getEnvVar("TWITTER_CONSUMER_API_SECRET", scriptFileName); +const accessTokenKey = getEnvVar("TWITTER_ACCESS_TOKEN_KEY", scriptFileName); +const accessTokenSecret = getEnvVar("TWITTER_ACCESS_TOKEN_SECRET", scriptFileName); +const githubToken = getEnvVar("GITHUB_TOKEN", scriptFileName); + +// TODO: update workflows to use these environment variables +const TWITTER_BROADCAST_ENABLED = "TWITTER_BROADCAST_ENABLED"; +const twitterBroadcastEnabled = getEnvVar(TWITTER_BROADCAST_ENABLED, scriptFileName, false).toLowerCase(); +const templateRepoName = getEnvVar("RELEASE_TWEET_TEMPLATE_REPO_NAME", scriptFileName); +const templateBranchName = getEnvVar("RELEASE_TWEET_TEMPLATE_BRANCH_NAME", scriptFileName); +const relativeTemplateFilePath = getEnvVar("RELATIVE_RELEASE_TWEET_TEMPLATE_FILE_PATH", scriptFileName); +const discordInviteCode = getEnvVar("DISCORD_INVITE_CODE", scriptFileName); + +version = version.startsWith("v") ? version : `v${version}`; + +await validateOrgExists(scriptFileName); +await validateRepoExists(scriptFileName); + +if (Utils.isNotValidPreviewVersion(version) && Utils.isNotValidProdVersion(version)) { + let errorMsg = `The version '${version}' is not a valid preview or production version.`; + errorMsg += "\nRequired Syntax: v#.#.# or v#.#.#-preview.#"; + Utils.printAsGitHubError(errorMsg); + Deno.exit(1); +} + +const githubVarService = new GitHubVariableService(ownerName, repoName, githubToken); +githubVarService.setOrgAndRepo(ownerName, repoName); + +if (Utils.isNothing(twitterBroadcastEnabled) || twitterBroadcastEnabled === "false") { + const noticeMsg = `No tweet broadcast will be performed.` + + `\nTo enable tweet broadcasting, set the '${TWITTER_BROADCAST_ENABLED}' variable to 'true'.` + + "\nIf the variable is missing, empty, or set to 'false', no tweet broadcast will be performed."; + Utils.printAsGitHubNotice(noticeMsg); + Deno.exit(0); +} + +const authValues: XAuthValues = { + consumer_api_key: consumerAPIKey, + consumer_api_secret: consumerAPISecret, + access_token_key: accessTokenKey, + access_token_secret: accessTokenSecret, +}; + +const tweetBuilder: ReleaseTweetBuilder = new ReleaseTweetBuilder(ownerName, templateRepoName, githubToken); + +const tweet = await tweetBuilder.buildTweet( + templateBranchName, + relativeTemplateFilePath, + repoName, + version, + discordInviteCode, +); + +const twitterClient: XClient = new XClient(authValues); +await twitterClient.tweet(tweet); + +Utils.printAsGitHubNotice(`A release tweet was successfully broadcasted for the '${repoName}' project for version '${version}'.`); diff --git a/cicd/scripts/transpile-readme.ts b/cicd/scripts/transpile-readme.ts index 306176e8..55a2a2b3 100644 --- a/cicd/scripts/transpile-readme.ts +++ b/cicd/scripts/transpile-readme.ts @@ -1,6 +1,22 @@ -import { TranspileReadMeRunner } from "./runners/TranspileReadMeRunner.ts"; +import { existsSync } from "@std/fs/exists"; +import { Utils } from "../core/Utils.ts"; +import { ReadMeTranspilerService } from "../core/Services/ReadMeTranspilerService.ts"; +import getEnvVar from "../core/GetEnvVar.ts"; -const transpileReadMeExecutor: TranspileReadMeRunner = new TranspileReadMeRunner(Deno.args); -await transpileReadMeExecutor.run(); +const scriptFileName = new URL(import.meta.url).pathname.split("/").pop(); -export default transpileReadMeExecutor; +const baseDirPath = getEnvVar("BASE_DIR_PATH", scriptFileName); + +const readmeFilePath = `${baseDirPath}/README.md`; + +if (!existsSync(readmeFilePath)) { + const errorMsg = `\nThe given path '${readmeFilePath}' is not a valid file path.\n\t${scriptFileName}`; + Utils.printAsGitHubError(errorMsg); + Deno.exit(1); +} + +const transpiler = new ReadMeTranspilerService(); + +transpiler.transpile(baseDirPath); + +Utils.printAsGitHubNotice("Successfully transpiled the README.md file."); diff --git a/cicd/scripts/validate-sdk-setup.ts b/cicd/scripts/validate-sdk-setup.ts deleted file mode 100644 index 44bc052f..00000000 --- a/cicd/scripts/validate-sdk-setup.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ValidateSDKSetupRunner } from "./runners/ValidateSDKSetupRunner.ts"; - -const validateSDKSetupExecutor = async () => { - const runner = new ValidateSDKSetupRunner(Deno.args); - - try { - await runner.run(); - } catch (error) { - console.error(error); - Deno.exit(1); - } -}; - -validateSDKSetupExecutor(); - -export default validateSDKSetupExecutor; diff --git a/cicd/scripts/validate-sdk-versions.ts b/cicd/scripts/validate-sdk-versions.ts new file mode 100644 index 00000000..3350f223 --- /dev/null +++ b/cicd/scripts/validate-sdk-versions.ts @@ -0,0 +1,10 @@ +import getEnvVar from "../core/GetEnvVar.ts"; +import { ValidateSDKVersionService } from "../core/Services/ValidateSDKVersionService.ts"; + +const scriptFileName = new URL(import.meta.url).pathname.split("/").pop(); + +const baseSearchDirPath = getEnvVar("BASE_SEARCH_DIR_PATH", scriptFileName); +const dotnetSdkVersion = getEnvVar("NET_SDK_VERSION", scriptFileName); + +const validator = new ValidateSDKVersionService(); +validator.validate(baseSearchDirPath, dotnetSdkVersion); diff --git a/cicd/scripts/validate-tag.ts b/cicd/scripts/validate-tag.ts index 0f4d3ce5..5de51b1c 100644 --- a/cicd/scripts/validate-tag.ts +++ b/cicd/scripts/validate-tag.ts @@ -1,86 +1,46 @@ -import { RepoClient, TagClient } from "../../deps.ts"; +import { TagClient } from "../../deps.ts"; +import getEnvVar from "../core/GetEnvVar.ts"; import { Utils } from "../core/Utils.ts"; +import { validateOrgExists, validateRepoExists } from "../core/Validators.ts"; -const validateTagExecutor = async () => { - // Validate the arguments - if (Deno.args.length != 5) { - let errorMsg = `The cicd script must have 4 arguments but has ${Deno.args.length} argument(s).`; - errorMsg += "\nThe 1st arg is required and must be a valid GitHub repository owner name."; - errorMsg += "\nThe 2nd arg is required and must be a valid GitHub repo."; - errorMsg += "\nThe 3rd arg is required and must be either 'production', 'preview' or 'either'."; - errorMsg += "\nThe 4th arg is required and must be the name of the tag."; - errorMsg += "\nThe 5th arg is required and must be a GitHub PAT (Personal Access Token)."; +type ReleaseType = "production" | "preview"; - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); - } +const scriptFileName = new URL(import.meta.url).pathname.split("/").pop(); - const ownerName: string = Deno.args[0]; - const repoName: string = Deno.args[1]; - const tagType: string = Deno.args[2].toLowerCase(); - let tag: string = Deno.args[3].trim(); - tag = tag.startsWith("v") ? tag : `v${tag}`; - const token = Deno.args[4].trim(); +const ownerName: string = getEnvVar("OWNER_NAME", scriptFileName); +const repoName: string = getEnvVar("REPO_NAME", scriptFileName); +const releaseType = getEnvVar("RELEASE_TYPE", scriptFileName).toLowerCase(); +let tag: string = getEnvVar("TAG_NAME", scriptFileName); +tag = tag.startsWith("v") ? tag : `v${tag}`; +const token = getEnvVar("GITHUB_TOKEN", scriptFileName); - // Print out all of the arguments - Utils.printInGroup("Script Arguments", [ - `Owner Name (Required): ${ownerName}`, - `Repo Name (Required): ${repoName}`, - `Tag Type (Required): ${tagType}`, - `Tag (Required): ${tag}`, - `GitHub Token (Required): ****`, - ]); +const releaseTypeInvalid = releaseType != "production" && releaseType != "preview"; - const versionTypeInvalid = tagType != "production" && tagType != "preview" && tagType != "either"; +if (releaseTypeInvalid) { + const errorMsg = `The tag type argument '${releaseType}' is invalid. Valid values are 'production', 'preview' or 'either'.`; + Utils.printAsGitHubError(errorMsg); + Deno.exit(1); +} - if (versionTypeInvalid) { - Utils.printAsGitHubError( - `The tag type argument '${tagType}' is invalid. Valid values are 'production', 'preview' or 'either'.`, - ); - Deno.exit(1); - } +const tagIsInvalid = releaseType === "production" + ? Utils.isNotValidProdVersion(tag) + : Utils.isNotValidPreviewVersion(tag); - let tagIsInvalid = false; +if (tagIsInvalid) { + const tagTypeStr = releaseType === "production" || releaseType === "preview" ? releaseType : "production or preview"; - switch (tagType) { - case "production": - tagIsInvalid = Utils.isNotValidProdVersion(tag); - break; - case "preview": - tagIsInvalid = Utils.isNotValidPreviewVersion(tag); - break; - case "either": - tagIsInvalid = Utils.isNotValidProdVersion(tag) || Utils.isNotValidPreviewVersion(tag); - break; - default: - break; - } + Utils.printAsGitHubError(`The tag is not in the correct ${tagTypeStr} version syntax.`); + Deno.exit(1); +} - if (tagIsInvalid) { - const tagTypeStr = tagType === "production" || tagType === "preview" ? tagType : "production or preview"; +await validateOrgExists(scriptFileName); +await validateRepoExists(scriptFileName); - Utils.printAsGitHubError(`The tag is not in the correct ${tagTypeStr} version syntax.`); - Deno.exit(1); - } +const tagClient: TagClient = new TagClient(ownerName, repoName, token); - const repoClient: RepoClient = new RepoClient(ownerName, repoName, token); - const repoDoesNotExist = !(await repoClient.exists()); +const tagExists = await tagClient.tagExists(tag); - if (repoDoesNotExist) { - Utils.printAsGitHubError(`The repository '${repoName}' does not exist.`); - Deno.exit(1); - } - - const tagClient: TagClient = new TagClient(ownerName, repoName, token); - - const tagExists = await tagClient.tagExists(tag); - - if (tagExists) { - Utils.printAsGitHubError(`The tag '${tag}' already exists.`); - Deno.exit(1); - } -}; - -validateTagExecutor(); - -export default validateTagExecutor; +if (tagExists) { + Utils.printAsGitHubError(`The tag '${tag}' already exists.`); + Deno.exit(1); +} diff --git a/cicd/scripts/validate-version.ts b/cicd/scripts/validate-version.ts index 94bd45bf..65582222 100644 --- a/cicd/scripts/validate-version.ts +++ b/cicd/scripts/validate-version.ts @@ -1,69 +1,35 @@ +import getEnvVar from "../core/GetEnvVar.ts"; import { Utils } from "../core/Utils.ts"; -const validateVersionExecutor = () => { - // Validate the arguments - if (Deno.args.length != 2) { - let errorMsg = `The cicd script must have 2 arguments but has ${Deno.args.length} argument(s).`; - errorMsg += "\nThe 1st arg is required and must be a production or preview version."; +type ReleaseType = "production" | "preview"; - errorMsg += - "\nThe 2nd arg is required and must be the version type. Valid values are 'production', 'preview' or 'either'."; - errorMsg += "\n\tThe production version syntax is as follows: v.."; - errorMsg += "\n\tThe preview version syntax is as follows: v..-preview."; +const scriptFileName = new URL(import.meta.url).pathname.split("/").pop(); - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); - } +let version: string = getEnvVar("VERSION", scriptFileName).toLowerCase(); +version = version.startsWith("v") ? version : `v${version}`; - let version: string = Deno.args[0].toLowerCase(); - version = version.startsWith("v") ? version : `v${version}`; +const releaseType: ReleaseType = getEnvVar("RELEASE_TYPE", scriptFileName).toLowerCase(); - const versionType: string = Deno.args[1].toLowerCase(); +const releaseTypeInvalid = releaseType != "production" && releaseType != "preview"; - const versionTypeInvalid = versionType != "production" && versionType != "preview" && versionType != "either"; +if (releaseTypeInvalid) { + const errorMsg = `The version type argument '${releaseType}' is invalid. Valid values are 'production' or 'preview'.`; + Utils.printAsGitHubError(errorMsg); + Deno.exit(1); +} - if (versionTypeInvalid) { - Utils.printAsGitHubError( - `The version type argument '${versionType}' is invalid. Valid values are 'production', 'preview' or 'either'.`, - ); - Deno.exit(1); - } +const versionIsInvalid = releaseType === "production" + ? Utils.isNotValidProdVersion(version) + : Utils.isNotValidPreviewVersion(version); - // Print out all of the arguments - Utils.printInGroup("Script Arguments", [ - `Version (Required): ${version}`, - `Version Type (Required): ${versionType}`, - ]); +if (versionIsInvalid) { + const releaseTypeStr = releaseType === "production" || releaseType === "preview" ? releaseType : "production or preview"; + const errorMsg = `\nThe version is not in the correct ${releaseTypeStr} version syntax.` + + "\n\tThe production version syntax is as follows: v.." + + "\n\tThe preview version syntax is as follows: v..-preview."; - let versionIsInvalid = false; + Utils.printAsGitHubError(errorMsg); + Deno.exit(1); +} - switch (versionType) { - case "production": - versionIsInvalid = Utils.isNotValidProdVersion(version); - break; - case "preview": - versionIsInvalid = Utils.isNotValidPreviewVersion(version); - break; - case "either": - versionIsInvalid = Utils.isNotValidProdVersion(version) || Utils.isNotValidPreviewVersion(version); - break; - default: - break; - } - - if (versionIsInvalid) { - const tagTypeStr = version === "production" || version === "preview" ? version : "production or preview"; - - let errorMsg = `\nThe version is not in the correct ${tagTypeStr} version syntax.`; - errorMsg += "\n\tThe production version syntax is as follows: v.."; - errorMsg += "\n\tThe preview version syntax is as follows: v..-preview."; - - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); - } else { - Utils.printAsGitHubNotice(`✅The ${versionType} version '${version}' is valid!!✅`); - } -}; - -validateVersionExecutor(); -export default validateVersionExecutor; +Utils.printAsGitHubNotice(`✅The ${releaseType} version '${version}' is valid!!✅`); diff --git a/deno.json b/deno.json index bbb321e0..8a4236d7 100644 --- a/deno.json +++ b/deno.json @@ -16,8 +16,12 @@ ] }, "fmt": { - "include": ["cicd/"], - "exclude": ["**/*.md"], + "include": [ + "cicd/" + ], + "exclude": [ + "**/*.md" + ], "useTabs": true, "lineWidth": 130, "indentWidth": 4, @@ -25,6 +29,7 @@ "singleQuote": false }, "imports": { - "std/": "https://deno.land/std@0.203.0/" + "@std/fs": "jsr:@std/fs@^1.0.4", + "@std/path": "jsr:@std/path@^1.0.6" } } diff --git a/deno.lock b/deno.lock index ffa0ec57..ab270516 100644 --- a/deno.lock +++ b/deno.lock @@ -2,12 +2,30 @@ "version": "3", "packages": { "specifiers": { + "jsr:@std/fs@^1.0.4": "jsr:@std/fs@1.0.4", + "jsr:@std/path@^1.0.6": "jsr:@std/path@1.0.6", + "npm:@types/node": "npm:@types/node@18.16.19", "npm:chalk@4.1.1": "npm:chalk@4.1.1", "npm:chalk@5.3.0": "npm:chalk@5.3.0", "npm:superjson@1.13.3": "npm:superjson@1.13.3", "npm:twitter-api-v2@1.15.0": "npm:twitter-api-v2@1.15.0" }, + "jsr": { + "@std/fs@1.0.4": { + "integrity": "2907d32d8d1d9e540588fd5fe0ec21ee638134bd51df327ad4e443aaef07123c", + "dependencies": [ + "jsr:@std/path@^1.0.6" + ] + }, + "@std/path@1.0.6": { + "integrity": "ab2c55f902b380cf28e0eec501b4906e4c1960d13f00e11cfbcd21de15f18fed" + } + }, "npm": { + "@types/node@18.16.19": { + "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==", + "dependencies": {} + }, "ansi-styles@4.3.0": { "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dependencies": { @@ -68,6 +86,17 @@ } }, "remote": { + "https://deno.land/std@0.106.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58", + "https://deno.land/std@0.106.0/_util/os.ts": "dfb186cc4e968c770ab6cc3288bd65f4871be03b93beecae57d657232ecffcac", + "https://deno.land/std@0.106.0/path/_constants.ts": "1247fee4a79b70c89f23499691ef169b41b6ccf01887a0abd131009c5581b853", + "https://deno.land/std@0.106.0/path/_interface.ts": "1fa73b02aaa24867e481a48492b44f2598cd9dfa513c7b34001437007d3642e4", + "https://deno.land/std@0.106.0/path/_util.ts": "2e06a3b9e79beaf62687196bd4b60a4c391d862cfa007a20fc3a39f778ba073b", + "https://deno.land/std@0.106.0/path/common.ts": "eaf03d08b569e8a87e674e4e265e099f237472b6fd135b3cbeae5827035ea14a", + "https://deno.land/std@0.106.0/path/glob.ts": "3b84af55c53febacf6afe214c095624b22a56b6f57d7312157479cc783a0de65", + "https://deno.land/std@0.106.0/path/mod.ts": "4465dc494f271b02569edbb4a18d727063b5dbd6ed84283ff906260970a15d12", + "https://deno.land/std@0.106.0/path/posix.ts": "b81974c768d298f8dcd2c720229639b3803ca4a241fa9a355c762fa2bc5ef0c1", + "https://deno.land/std@0.106.0/path/separator.ts": "8fdcf289b1b76fd726a508f57d3370ca029ae6976fcde5044007f062e643ff1c", + "https://deno.land/std@0.106.0/path/win32.ts": "f4a3d4a3f2c9fe894da046d5eac48b5e789a0ebec5152b2c0985efe96a9f7ae1", "https://deno.land/std@0.196.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", "https://deno.land/std@0.196.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", "https://deno.land/std@0.196.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", @@ -357,5 +386,11 @@ "https://deno.land/x/kd_clients@v1.0.0-preview.8/core/Utils.ts": "aee538b5263f055a57827bc168067c98e02a98081767843182505415ed39e1f1", "https://deno.land/x/kd_clients@v1.0.0-preview.8/core/WebApiClient.ts": "ad9f538a96c06da7087cd91d4141298dba3f70a5ff7e48748416342db6ee01e7", "https://deno.land/x/kd_clients@v1.0.0-preview.8/deps.ts": "da70fd766668d0c177407dea89912b2d4e154605fe242533d603fb6af6e2305c" + }, + "workspace": { + "dependencies": [ + "jsr:@std/fs@^1.0.4", + "jsr:@std/path@^1.0.6" + ] } }