From 3cb185a33c5fdd1ab017ab07db0df9b485e66feb Mon Sep 17 00:00:00 2001 From: Calvin Wilkinson Date: Sat, 4 Nov 2023 13:20:04 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A7Fix=20import=20issue=20in=20mod=20f?= =?UTF-8?q?iles=20(#7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Start work for issue #6 * refactor: update the import paths to relative paths * config: remove import maps * refactor: update all import paths to relative paths * config: setup external dependencies file * cleanup: cleanup code using deno fmt * ci: update deno check script to ignore certain directories * ide: setup launch config for deno check script * config: update deno lock file * config: remove vendored dependencies * config: update deno lock * config: add git ignore for node_modules folder * refactor: update deps imports * ide: ignore node_modules folder * ci: update unit test status check with permission * deps: add dependency to npm chalk library * ci: add ignore folder to deno check script * chore: change what kind of message print error util func does * refactor: improve error handling code in OrgClient * refactor: improve error handling code in GitClient * refactor: improve error handling in GraphQlClient * refactor: improve error handling in IssueClient * refactor: improve error handling in LabelClient * refactor: improve error handling in RepoClient * chore: fix error in utils function * fix: add missing arguments * ide: add permission to task * refactor: improve error handling in GitHubClient * config: improve dependencies * config: update deno lock * config: remove include and exclude for tests * ci: update test status check to be explicit with tests * chore: make functions public * test: create tests for RepoClient * refactor: improve error handling in MilestoneClient * cleanup: remove unused protected funcs * chore: setup launch config and playground with default args * refactor: improve error handling in ProjectClient * ci: improve deno check process * refactor: improve error handling with PullRequestClient * refactor: refactor param names across code base * refactor: improve error handling with the ReleaseClient * refactor: improve error handling of the TagClient * refactor: improve error handling of the UsersClient * fix: fix guards * refactor: improve error handling in WorkflowClient * ide: remove recommended extension * refactor: improve error handling in XClient * refactor: improve error handling of the NuGetClient * refactor: further improve error handling of clients * refactor: force ctor params to be required * refactor: adjust not equals operators * chore: remove utils function * refactor: remove deno exit func from error handling code * refactor: convert Guard functions to throw errors instead of exiting * config: update version from v1.0.0-preview.2 to v1.0.0-preview.3 --- .github/cicd/core/Directory.ts | 2 +- .github/cicd/core/File.ts | 2 +- .github/cicd/core/VersionPuller.ts | 8 +- .github/cicd/playground.ts | 2 + .github/cicd/scripts/check-release-notes.ts | 14 +- .github/cicd/scripts/deno-check.ts | 25 +- .github/cicd/scripts/get-version.ts | 6 +- .github/cicd/scripts/version-checker.ts | 24 +- .github/workflows/build-status-check.yml | 5 +- .github/workflows/release.yml | 15 +- .github/workflows/test-status-check.yml | 2 +- .gitignore | 1 + .vscode/extensions.json | 1 - .vscode/launch.json | 29 ++ .vscode/settings.json | 4 +- .vscode/tasks.json | 1 + GitHubClients/Errors/BadCredentials.ts | 9 + GitHubClients/Errors/GitError.ts | 9 + GitHubClients/Errors/IssueError.ts | 9 + GitHubClients/Errors/LabelError.ts | 9 + GitHubClients/Errors/MilestoneError.ts | 9 + GitHubClients/Errors/NuGetError.ts | 9 + GitHubClients/Errors/OrganizationError.ts | 9 + GitHubClients/Errors/ProjectError.ts | 9 + GitHubClients/Errors/PullRequestError.ts | 9 + GitHubClients/Errors/ReleaseError.ts | 9 + GitHubClients/Errors/RepoError.ts | 9 + GitHubClients/Errors/TagError.ts | 9 + GitHubClients/Errors/UsersError.ts | 19 ++ GitHubClients/Errors/WorkflowError.ts | 9 + GitHubClients/Errors/XError.ts | 9 + GitHubClients/GitClient.ts | 113 ++++++-- GitHubClients/IssueClient.ts | 256 +++++++++++------- GitHubClients/LabelClient.ts | 32 ++- GitHubClients/MilestoneClient.ts | 95 ++++--- GitHubClients/OrgClient.ts | 119 +++++--- GitHubClients/ProjectClient.ts | 186 ++++++++++--- GitHubClients/PullRequestClient.ts | 120 ++++---- GitHubClients/ReleaseClient.ts | 25 +- GitHubClients/RepoClient.ts | 163 ++++++----- GitHubClients/TagClient.ts | 31 ++- GitHubClients/UsersClient.ts | 42 +-- GitHubClients/WorkflowClient.ts | 105 +++---- GitHubClients/mod.ts | 25 +- OtherClients/XClient.ts | 12 +- OtherClients/mod.ts | 3 +- PackageClients/NuGetClient.ts | 50 ++-- PackageClients/mod.ts | 3 +- .../Release-Notes-v1.0.0-preview.3.md | 51 ++++ core/GitHubClient.ts | 39 ++- core/GraphQl/Mutations/AddCommitMutation.ts | 8 +- core/GraphQl/Queries/GetBranchesQuery.ts | 8 +- core/GraphQl/Queries/GetIssueProjectsQuery.ts | 6 +- core/GraphQl/Queries/GetOrgProjectsQueries.ts | 6 +- .../Queries/GetPullRequestProjectsQuery.ts | 6 +- core/GraphQlClient.ts | 112 ++------ core/Guard.ts | 26 +- core/IssueOrPRRequestData.ts | 2 +- core/LinkHeader.ts | 2 +- core/LinkHeaderParser.ts | 8 +- core/Models/GitHubVariablesModel.ts | 2 +- core/Models/GraphQlModels/ErrorModel.ts | 2 +- .../RawModels/RawGitBranchModel.ts | 2 +- .../RawModels/RawRefsGetBranchModel.ts | 2 +- .../GraphQlModels/RequestResponseModel.ts | 7 +- core/Models/IssueModel.ts | 6 +- core/Models/PullRequestHeadOrBaseModel.ts | 2 +- core/Models/PullRequestModel.ts | 10 +- core/Models/TagModel.ts | 2 +- core/Models/WorkflowRunModel.ts | 2 +- core/Models/WorkflowRunsModel.ts | 2 +- core/Types.ts | 4 +- core/Utils.ts | 75 +++-- core/WebApiClient.ts | 22 +- deno.json | 17 +- deno.lock | 117 +++++++- deps.ts | 14 + mod.ts | 28 +- tests/RepoClientTests.ts | 44 +++ tests/UtilsTests.ts | 7 +- .../deno.land/std@0.203.0/encoding/_util.ts | 29 -- .../deno.land/std@0.203.0/encoding/base64.ts | 158 ----------- vendor/deno.land/std@0.203.0/fs/exists.ts | 202 -------------- vendor/import_map.json | 13 - 84 files changed, 1510 insertions(+), 1199 deletions(-) create mode 100644 .gitignore create mode 100644 GitHubClients/Errors/BadCredentials.ts create mode 100644 GitHubClients/Errors/GitError.ts create mode 100644 GitHubClients/Errors/IssueError.ts create mode 100644 GitHubClients/Errors/LabelError.ts create mode 100644 GitHubClients/Errors/MilestoneError.ts create mode 100644 GitHubClients/Errors/NuGetError.ts create mode 100644 GitHubClients/Errors/OrganizationError.ts create mode 100644 GitHubClients/Errors/ProjectError.ts create mode 100644 GitHubClients/Errors/PullRequestError.ts create mode 100644 GitHubClients/Errors/ReleaseError.ts create mode 100644 GitHubClients/Errors/RepoError.ts create mode 100644 GitHubClients/Errors/TagError.ts create mode 100644 GitHubClients/Errors/UsersError.ts create mode 100644 GitHubClients/Errors/WorkflowError.ts create mode 100644 GitHubClients/Errors/XError.ts create mode 100644 ReleaseNotes/PreviewReleases/Release-Notes-v1.0.0-preview.3.md create mode 100644 deps.ts create mode 100644 tests/RepoClientTests.ts delete mode 100644 vendor/deno.land/std@0.203.0/encoding/_util.ts delete mode 100644 vendor/deno.land/std@0.203.0/encoding/base64.ts delete mode 100644 vendor/deno.land/std@0.203.0/fs/exists.ts delete mode 100644 vendor/import_map.json diff --git a/.github/cicd/core/Directory.ts b/.github/cicd/core/Directory.ts index cad80d7..e6b1d69 100644 --- a/.github/cicd/core/Directory.ts +++ b/.github/cicd/core/Directory.ts @@ -1,4 +1,4 @@ -import { existsSync } from "std/fs/exists.ts"; +import { existsSync } from "../../../deps.ts"; /** * Provides directory functionality. diff --git a/.github/cicd/core/File.ts b/.github/cicd/core/File.ts index 212a72e..c9d6399 100644 --- a/.github/cicd/core/File.ts +++ b/.github/cicd/core/File.ts @@ -1,4 +1,4 @@ -import { existsSync } from "std/fs/exists.ts"; +import { existsSync } from "../../../deps.ts"; /** * Provides file functionality. diff --git a/.github/cicd/core/VersionPuller.ts b/.github/cicd/core/VersionPuller.ts index 91f3edb..eb4c1e6 100644 --- a/.github/cicd/core/VersionPuller.ts +++ b/.github/cicd/core/VersionPuller.ts @@ -1,5 +1,5 @@ -import { Utils } from "core/Utils.ts"; -import { Directory } from "cicd-core/Directory.ts"; +import { Utils } from "../../../core/Utils.ts"; +import { Directory } from "../core/Directory.ts"; /** * Pulls the version from a json file. @@ -16,7 +16,7 @@ export class VersionPuller { if (denoJsonFilePath === undefined) { const errorMsg = `The file '${fileName}' could not be found when pulling the version number.`; - Utils.printAsGitHubError(errorMsg); + Utils.printError(errorMsg); Deno.exit(1); } @@ -26,7 +26,7 @@ export class VersionPuller { // If the object contains a property with the name version if (jsonObj.version === undefined) { const errorMsg = `The file '${fileName}' does not contain a version property.`; - Utils.printAsGitHubError(errorMsg); + Utils.printError(errorMsg); Deno.exit(1); } diff --git a/.github/cicd/playground.ts b/.github/cicd/playground.ts index e69de29..3b0fad6 100644 --- a/.github/cicd/playground.ts +++ b/.github/cicd/playground.ts @@ -0,0 +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]; diff --git a/.github/cicd/scripts/check-release-notes.ts b/.github/cicd/scripts/check-release-notes.ts index 7b1ed02..5b7b913 100644 --- a/.github/cicd/scripts/check-release-notes.ts +++ b/.github/cicd/scripts/check-release-notes.ts @@ -1,10 +1,10 @@ -import { Utils } from "core/Utils.ts"; -import { File } from "cicd-core/File.ts"; +import { Utils } from "../../../core/Utils.ts"; +import { File } from "../core/File.ts"; if (Deno.args.length != 2) { let errorMsg = `The required number of arguments is 2 but received ${Deno.args.length}.`; errorMsg += `\nPlease provide the following arguments: version type, version.`; - Utils.printAsGitHubError(errorMsg); + Utils.printError(errorMsg); Deno.exit(100); } @@ -12,7 +12,7 @@ const versionType = Deno.args[0].trim().toLowerCase(); let version = Deno.args[1].trim().toLowerCase(); if (Utils.invalidReleaseType(versionType)) { - Utils.printAsGitHubError(`The version type must be either 'preview' or 'release' but received '${versionType}'.`); + Utils.printError(`The version type must be either 'preview' or 'release' but received '${versionType}'.`); Deno.exit(200); } @@ -22,14 +22,14 @@ let releaseNotesDirName = ""; if (Utils.isPreviewRelease(versionType)) { if (Utils.isNotValidPreviewVersion(version)) { - Utils.printAsGitHubError(`The preview version '${version}' is not valid.`); + Utils.printError(`The preview version '${version}' is not valid.`); Deno.exit(300); } releaseNotesDirName = "PreviewReleases"; } else if (Utils.isProductionRelease(versionType)) { if (Utils.isNotValidProdVersion(version)) { - Utils.printAsGitHubError(`The production version '${version}' is not valid.`); + Utils.printError(`The production version '${version}' is not valid.`); Deno.exit(400); } @@ -39,6 +39,6 @@ if (Utils.isPreviewRelease(versionType)) { const releaseNotesDirPath = `./ReleaseNotes/${releaseNotesDirName}/Release-Notes-${version}.md`; if (File.DoesNotExist(releaseNotesDirPath)) { - Utils.printAsGitHubError(`The release notes '${releaseNotesDirPath}' does not exist.`); + Utils.printError(`The release notes '${releaseNotesDirPath}' does not exist.`); Deno.exit(500); } diff --git a/.github/cicd/scripts/deno-check.ts b/.github/cicd/scripts/deno-check.ts index 86a7529..e9eb180 100644 --- a/.github/cicd/scripts/deno-check.ts +++ b/.github/cicd/scripts/deno-check.ts @@ -1,15 +1,30 @@ import { CLI } from "../core/CLI.ts"; -import { Directory } from "cicd-core/Directory.ts"; +import { Directory } from "../core/Directory.ts"; + +const ignoreDirectories = [ + "./vendor/", + "./node_modules/" +]; const files: string[] = Directory .getFiles("/", true) - .filter(f => f.endsWith(".ts")); + .filter(f => { + const isTypeScriptFile = f.endsWith(".ts"); + + const shouldNotIgnore = ignoreDirectories.every(ignoreDir => !f.startsWith(ignoreDir)) + + return isTypeScriptFile && shouldNotIgnore; + }); const cli: CLI = new CLI(); let failed = false; +console.clear(); console.log(`Checking ${files.length} files . . .`); +let totalPassed = 0; +let totalFailed = 0; + // Perform a deno check on all of the files for await (let file of files) { const logStart = new TextEncoder().encode(`Checking ${file}`); @@ -29,14 +44,20 @@ for await (let file of files) { lines.forEach(line => { logEndValue += ` ${line}\n`; }); + + totalFailed++; } else { logEndValue = "✅\n"; + totalPassed++; } const logEnd = new TextEncoder().encode(logEndValue); Deno.stdout.writeSync(logEnd); }; +const resultsMsg = new TextEncoder().encode(`\nTotal Checks Passed✅: ${totalPassed}\nTotal Checks Failed❌: ${totalFailed}\n`); +Deno.stdout.writeSync(resultsMsg); + if (failed) { Deno.exit(1); } diff --git a/.github/cicd/scripts/get-version.ts b/.github/cicd/scripts/get-version.ts index 0f5cdeb..c912576 100644 --- a/.github/cicd/scripts/get-version.ts +++ b/.github/cicd/scripts/get-version.ts @@ -1,4 +1,4 @@ -import { Utils } from "core/Utils.ts"; +import { Utils } from "../../../core/Utils.ts"; import { VersionPuller } from "../core/VersionPuller.ts"; const versionPuller: VersionPuller = new VersionPuller(); @@ -9,10 +9,10 @@ const outputFilePath = Deno.env.get("GITHUB_OUTPUT") ?? ""; if (outputFilePath === "") { const errorMsg = `The environment variable 'GITHUB_OUTPUT' does not exist or is not set.`; - Utils.printAsGitHubError(errorMsg); + Utils.printError(errorMsg); Deno.exit(1); } Deno.writeTextFileSync(outputFilePath, `version=${version}`); -Utils.printAsGitHubNotice(`The output 'version' has been set to '${version}'.`) +console.log(`The output 'version' has been set to '${version}'.`) diff --git a/.github/cicd/scripts/version-checker.ts b/.github/cicd/scripts/version-checker.ts index c62ff0d..34b1f23 100644 --- a/.github/cicd/scripts/version-checker.ts +++ b/.github/cicd/scripts/version-checker.ts @@ -1,10 +1,10 @@ -import { RepoClient, TagClient, UsersClient } from "github/mod.ts"; -import { Utils } from "core/Utils.ts"; +import { RepoClient, TagClient, UsersClient } from "../../../GitHubClients/mod.ts"; +import { Utils } from "../../../core/Utils.ts"; -if (Deno.args.length !== 5) { +if (Deno.args.length != 5) { let errorMsg = `The required number of arguments is 5 but only ${Deno.args.length}.`; errorMsg += `\nPlease provide the following arguments: version, owner name, repo name, and token.`; - Utils.printAsGitHubError(errorMsg); + Utils.printError(errorMsg); Deno.exit(1); } @@ -17,11 +17,11 @@ version = version.startsWith("v") ? version : `v${version}`; const token = Deno.args[4].trim(); -const userCLient: UsersClient = new UsersClient(token); +const userCLient: UsersClient = new UsersClient(ownerName, repoName, token); if (!await userCLient.userExists(ownerName)) { const errorMsg = `The user '${ownerName}' does not exist.`; - Utils.printAsGitHubError(errorMsg); + Utils.printError(errorMsg); Deno.exit(1); } @@ -29,8 +29,8 @@ const repoClient: RepoClient = new RepoClient(ownerName, repoName, token); if (!await repoClient.exists()) { const errorMsg = `The repository '${repoName}' does not exist.`; - Utils.printAsGitHubError(errorMsg); - Utils.printAsGitHubError(errorMsg); + Utils.printError(errorMsg); + Utils.printError(errorMsg); Deno.exit(1); } @@ -38,13 +38,13 @@ const tagClient: TagClient = new TagClient(ownerName, repoName, token); if (await tagClient.tagExists(version)) { const errorMsg = `The tag '${version}' already exists.`; - Utils.printAsGitHubError(errorMsg); + Utils.printError(errorMsg); Deno.exit(1); } if (versionType != "preview" && versionType != "production") { const errorMsg = `The version type '${versionType}' is not valid. Valid values are 'preview' or 'production' version type.`; - Utils.printAsGitHubError(errorMsg); + Utils.printError(errorMsg); Deno.exit(1); } @@ -52,13 +52,13 @@ if (versionType != "preview" && versionType != "production") { if (versionType === "preview") { if (Utils.isNotValidPreviewVersion(version)) { const errorMsg = `The version '${version}' is not valid. Please provide a valid preview version.`; - Utils.printAsGitHubError(errorMsg); + Utils.printError(errorMsg); Deno.exit(1); } } else if (versionType === "production") { if (Utils.isNotValidProdVersion(version)) { const errorMsg = `The version '${version}' is not valid. Please provide a valid production version.`; - Utils.printAsGitHubError(errorMsg); + Utils.printError(errorMsg); Deno.exit(1); } } diff --git a/.github/workflows/build-status-check.yml b/.github/workflows/build-status-check.yml index 32ff8d4..6014600 100644 --- a/.github/workflows/build-status-check.yml +++ b/.github/workflows/build-status-check.yml @@ -29,7 +29,4 @@ jobs: - name: Run Build run: | - deno run --allow-read --allow-run ` - --no-remote ` - --import-map=${{ github.workspace }}/vendor/import_map.json ` - ./.github/cicd/scripts/deno-check.ts; + deno run --allow-read --allow-run "./.github/cicd/scripts/deno-check.ts"; diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e7ba0b0..c3ac6c8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,6 +21,7 @@ on: default: false type: boolean + jobs: get_version: name: Get Version @@ -39,10 +40,8 @@ jobs: id: get_version run: | $scriptPath = "${{ github.workspace }}/.github/cicd/scripts/get-version.ts"; - deno run --allow-read --allow-write --allow-env ` - --no-remote ` - --import-map=${{ github.workspace }}/vendor/import_map.json ` - "$scriptPath"; + + deno run --allow-read --allow-write --allow-env "$scriptPath"; validate_release: @@ -73,8 +72,7 @@ jobs: deno run ` --allow-net ` - --no-remote ` - --import-map=${{ github.workspace }}/vendor/import_map.json ` + --allow-read ` "$scriptPath" ` "${{ vars.ORGANIZATION_NAME }}" ` "${{ vars.PROJECT_NAME }}" ` @@ -88,12 +86,11 @@ jobs: deno run ` --allow-read ` - --no-remote ` - --import-map=${{ github.workspace }}/vendor/import_map.json ` "$scriptPath" ` "${{ inputs.release-type }}" ` "${{ needs.get_version.outputs.version }}"; + release: name: Run ${{ inputs.release-type }} Release runs-on: ubuntu-latest @@ -126,7 +123,7 @@ jobs: prerelease: ${{ inputs.release-type == 'Preview' }} draft: false - - name: Close Milestone + - name: Close Milestone ${{ needs.get_version.outputs.version }} if: ${{ inputs.dry-run == false }} run: | $scriptUrl = "${{ vars.SCRIPT_BASE_URL }}/${{ vars.CICD_SCRIPTS_VERSION }}/${{ vars.SCRIPT_RELATIVE_DIR_PATH }}"; diff --git a/.github/workflows/test-status-check.yml b/.github/workflows/test-status-check.yml index aa21855..9682052 100644 --- a/.github/workflows/test-status-check.yml +++ b/.github/workflows/test-status-check.yml @@ -28,4 +28,4 @@ jobs: deno-version: ${{ vars.DENO_VERSION }} - name: Run Tests - run: deno test + run: deno test --allow-read ./tests/*Tests.ts; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json index a8c98a1..0195f44 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,7 +1,6 @@ { "recommendations": [ "denoland.vscode-deno", - "humao.rest-client", "github.vscode-github-actions", "redhat.vscode-yaml" ] diff --git a/.vscode/launch.json b/.vscode/launch.json index fb92fa6..b4e8939 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,6 +12,10 @@ "--inspect-wait", "--allow-all", ], + "args": [ + "${env:CICD_TOKEN}", + "${workspaceFolder}", + ], "attachSimplePort": 9229, "runtimeExecutable": "npm", "outputCapture": "std", @@ -129,6 +133,31 @@ "windows": { "runtimeExecutable": "${userHome}\\.deno\\bin\\deno.exe" }, + "linux": { + "runtimeExecutable": "${userHome}/.deno/bin/deno" + }, + }, + { + "name": "Deno Check", + "request": "launch", + "type": "node", + "program": "${workspaceFolder}/.github/cicd/scripts/deno-check.ts", + "cwd": "${workspaceFolder}", + "runtimeArgs": [ + "run", + "--inspect-wait", + "--allow-read", + "--allow-run", + ], + "attachSimplePort": 9229, + "runtimeExecutable": "npm", + "outputCapture": "std", + "skipFiles": [ + "/**" + ], + "windows": { + "runtimeExecutable": "${userHome}\\.deno\\bin\\deno.exe" + }, "linux": { "runtimeExecutable": "${userHome}/.deno/bin/deno" }, diff --git a/.vscode/settings.json b/.vscode/settings.json index 0a18252..d73b321 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,8 @@ { "cSpell.words": [ "cicd", - "Creds" + "Creds", + "flatcontainer" ], "files.exclude": { "**/.git": true, @@ -10,6 +11,7 @@ "**/CVS": true, "**/.DS_Store": true, "**/Thumbs.db": true, + "**/node_modules": true, }, "deno.enable": true, "deno.lint": true, diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 738e591..86f2313 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -9,6 +9,7 @@ "run", "--allow-read", "--allow-run", + "--allow-sys", "${workspaceFolder}/.github/cicd/scripts/deno-check.ts" ], "problemMatcher": [ diff --git a/GitHubClients/Errors/BadCredentials.ts b/GitHubClients/Errors/BadCredentials.ts new file mode 100644 index 0000000..dd927a1 --- /dev/null +++ b/GitHubClients/Errors/BadCredentials.ts @@ -0,0 +1,9 @@ +/** + * Error thrown when there is a bad credentials error. + */ +export class BadCredentialsError extends Error { + constructor(message: string) { + super(message); + this.name = "BadCredentialsError"; + } +} diff --git a/GitHubClients/Errors/GitError.ts b/GitHubClients/Errors/GitError.ts new file mode 100644 index 0000000..733eb75 --- /dev/null +++ b/GitHubClients/Errors/GitError.ts @@ -0,0 +1,9 @@ +/** + * Error thrown when there is an error with the GIT client. + */ +export class GitError extends Error { + constructor(message: string) { + super(message); + this.name = "GitError"; + } +} diff --git a/GitHubClients/Errors/IssueError.ts b/GitHubClients/Errors/IssueError.ts new file mode 100644 index 0000000..c4fa5cc --- /dev/null +++ b/GitHubClients/Errors/IssueError.ts @@ -0,0 +1,9 @@ +/** + * Error thrown when there is an error with the issue client. + */ +export class IssueError extends Error { + constructor(message: string) { + super(message); + this.name = "IssueError"; + } +} diff --git a/GitHubClients/Errors/LabelError.ts b/GitHubClients/Errors/LabelError.ts new file mode 100644 index 0000000..5adc3f2 --- /dev/null +++ b/GitHubClients/Errors/LabelError.ts @@ -0,0 +1,9 @@ +/** + * Error thrown when there is an error with the label client. + */ +export class LabelError extends Error { + constructor(message: string) { + super(message); + this.name = "LabelError"; + } +} diff --git a/GitHubClients/Errors/MilestoneError.ts b/GitHubClients/Errors/MilestoneError.ts new file mode 100644 index 0000000..d8518d7 --- /dev/null +++ b/GitHubClients/Errors/MilestoneError.ts @@ -0,0 +1,9 @@ +/** + * Error thrown when there is an error with the milestone client. + */ +export class MilestoneError extends Error { + constructor(message: string) { + super(message); + this.name = "MilestoneError"; + } +} diff --git a/GitHubClients/Errors/NuGetError.ts b/GitHubClients/Errors/NuGetError.ts new file mode 100644 index 0000000..24e1382 --- /dev/null +++ b/GitHubClients/Errors/NuGetError.ts @@ -0,0 +1,9 @@ +/** + * Error thrown when there is an error with the nuget client. + */ +export class NuGetError extends Error { + constructor(message: string) { + super(message); + this.name = "NuGetError"; + } +} diff --git a/GitHubClients/Errors/OrganizationError.ts b/GitHubClients/Errors/OrganizationError.ts new file mode 100644 index 0000000..80f03cb --- /dev/null +++ b/GitHubClients/Errors/OrganizationError.ts @@ -0,0 +1,9 @@ +/** + * Error thrown when there is an error with the GitHub organization client. + */ +export class OrganizationError extends Error { + constructor(message: string) { + super(message); + this.name = "OrganizationError"; + } +} diff --git a/GitHubClients/Errors/ProjectError.ts b/GitHubClients/Errors/ProjectError.ts new file mode 100644 index 0000000..84c652a --- /dev/null +++ b/GitHubClients/Errors/ProjectError.ts @@ -0,0 +1,9 @@ +/** + * Error thrown when there is an error with the project client. + */ +export class ProjectError extends Error { + constructor(message: string) { + super(message); + this.name = "ProjectError"; + } +} diff --git a/GitHubClients/Errors/PullRequestError.ts b/GitHubClients/Errors/PullRequestError.ts new file mode 100644 index 0000000..3f43cbe --- /dev/null +++ b/GitHubClients/Errors/PullRequestError.ts @@ -0,0 +1,9 @@ +/** + * Error thrown when there is an error with the pull request client. + */ +export class PullRequestError extends Error { + constructor(message: string) { + super(message); + this.name = "PullRequestError"; + } +} diff --git a/GitHubClients/Errors/ReleaseError.ts b/GitHubClients/Errors/ReleaseError.ts new file mode 100644 index 0000000..dde8207 --- /dev/null +++ b/GitHubClients/Errors/ReleaseError.ts @@ -0,0 +1,9 @@ +/** + * Error thrown when there is an error with the release client. + */ +export class ReleaseError extends Error { + constructor(message: string) { + super(message); + this.name = "ReleaseError"; + } +} diff --git a/GitHubClients/Errors/RepoError.ts b/GitHubClients/Errors/RepoError.ts new file mode 100644 index 0000000..f509609 --- /dev/null +++ b/GitHubClients/Errors/RepoError.ts @@ -0,0 +1,9 @@ +/** + * Error thrown when there is an error with the repository client. + */ +export class RepoError extends Error { + constructor(message: string) { + super(message); + this.name = "RepoError"; + } +} diff --git a/GitHubClients/Errors/TagError.ts b/GitHubClients/Errors/TagError.ts new file mode 100644 index 0000000..dd76a3b --- /dev/null +++ b/GitHubClients/Errors/TagError.ts @@ -0,0 +1,9 @@ +/** + * Error thrown when there is an error with the tag client. + */ +export class TagError extends Error { + constructor(message: string) { + super(message); + this.name = "TagError"; + } +} diff --git a/GitHubClients/Errors/UsersError.ts b/GitHubClients/Errors/UsersError.ts new file mode 100644 index 0000000..e32e7e5 --- /dev/null +++ b/GitHubClients/Errors/UsersError.ts @@ -0,0 +1,19 @@ +/** + * Error thrown when there is an error with the users client. + */ +export class UsersError extends Error { + private readonly _httpStatusCode: number = 200; + + constructor(message: string, httpStatusCode?: number) { + super(message); + this.name = "UsersError"; + this._httpStatusCode = httpStatusCode ?? 200; + } + + /** + * Gets the HTTP status code of the error. + */ + public get httpStatusCode(): number { + return this.httpStatusCode; + } +} diff --git a/GitHubClients/Errors/WorkflowError.ts b/GitHubClients/Errors/WorkflowError.ts new file mode 100644 index 0000000..d9aef87 --- /dev/null +++ b/GitHubClients/Errors/WorkflowError.ts @@ -0,0 +1,9 @@ +/** + * Error thrown when there is an error with the workflow client. + */ +export class WorkflowError extends Error { + constructor(message: string) { + super(message); + this.name = "WorkflowError"; + } +} diff --git a/GitHubClients/Errors/XError.ts b/GitHubClients/Errors/XError.ts new file mode 100644 index 0000000..10a0a1f --- /dev/null +++ b/GitHubClients/Errors/XError.ts @@ -0,0 +1,9 @@ +/** + * Error thrown when there is an error with the X client. + */ +export class XError extends Error { + constructor(message: string) { + super(message); + this.name = "XError"; + } +} diff --git a/GitHubClients/GitClient.ts b/GitHubClients/GitClient.ts index 06de222..d97c105 100644 --- a/GitHubClients/GitClient.ts +++ b/GitHubClients/GitClient.ts @@ -1,41 +1,74 @@ -import { GraphQlClient } from "core/GraphQlClient.ts"; -import { createGetBranchesQuery } from "core/GraphQl/Queries/GetBranchesQuery.ts"; -import { Guard } from "core/Guard.ts"; -import { PageInfoModel } from "models/GraphQlModels/PageInfoModel.ts"; -import { GitBranchModel } from "models/GraphQlModels/GitBranchModel.ts"; -import { Utils } from "core/Utils.ts"; -import { RepoClient } from "github/RepoClient.ts"; -import { getCreateBranchMutation } from "core/GraphQl/Mutations/CreateBranchMutation.ts"; -import { RawRefsGetBranchModel } from "models/GraphQlModels/RawModels/RawRefsGetBranchModel.ts"; -import { RawGitBranchModel } from "models/GraphQlModels/RawModels/RawGitBranchModel.ts"; -import { addCommitMutation } from "core/GraphQl/Mutations/AddCommitMutation.ts"; +import { GraphQlClient } from "../core/GraphQlClient.ts"; +import { createGetBranchesQuery } from "../core/GraphQl/Queries/GetBranchesQuery.ts"; +import { Guard } from "../core/Guard.ts"; +import { PageInfoModel } from "../core/Models/GraphQlModels/PageInfoModel.ts"; +import { GitBranchModel } from "../core/Models/GraphQlModels/GitBranchModel.ts"; +import { Utils } from "../core/Utils.ts"; +import { RepoClient } from "./RepoClient.ts"; +import { getCreateBranchMutation } from "../core/GraphQl/Mutations/CreateBranchMutation.ts"; +import { RawRefsGetBranchModel } from "../core/Models/GraphQlModels/RawModels/RawRefsGetBranchModel.ts"; +import { RawGitBranchModel } from "../core/Models/GraphQlModels/RawModels/RawGitBranchModel.ts"; +import { addCommitMutation } from "../core/GraphQl/Mutations/AddCommitMutation.ts"; +import { OrganizationError } from "./Errors/OrganizationError.ts"; +import { GitError } from "./Errors/GitError.ts"; /** * Provides a client for to perform git operations for a GitHub repository. */ export class GitClient extends GraphQlClient { private readonly repoClient: RepoClient; + private readonly isInitialized: boolean = false; /** * Initializes a new instance of the {@link GitClient} class. - * @param repoOwner The owner of the repository. + * @param ownerName The owner of the repository. * @param repoName The name of the repository. * @param token The GitHub token to use for authentication. */ - constructor(repoOwner: string, repoName: string, token: string) { + constructor(ownerName: string, repoName: string, token: string) { const funcName = "GitClient.Ctor"; - Guard.isNothing(repoOwner, funcName, "repoOwner"); + Guard.isNothing(ownerName, funcName, "ownerName"); Guard.isNothing(repoName, funcName, "repoName"); - super(token); + super(ownerName, repoName, token); - this.repoClient = new RepoClient(this.ownerName, repoName, token); + this.repoClient = new RepoClient(ownerName, repoName, token); + this.isInitialized = true; + } + + /** + * Sets the name of the owner of the repository. + */ + public set ownerName(v: string) { + Guard.isNothing("ownerName", v, "v"); + super.ownerName = v.trim(); + + if (!this.isInitialized) { + return; + } + + this.repoClient.ownerName = v; + } + + /** + * Sets the name of the repository. + */ + public set repoName(v: string) { + Guard.isNothing("repoName", v, "v"); + super.repoName = v.trim(); + + if (!this.isInitialized) { + return; + } + + this.repoClient.repoName = v; } /** * Gets a branch with the given branch {@link name}. * @param name The name of the branch. * @returns The branch. + * @throws {@link OrganizationError} Thrown if the branch does not exist. */ public async getBranch(name: string): Promise { Guard.isNothing(name, "getBranch", "name"); @@ -43,8 +76,7 @@ export class GitClient extends GraphQlClient { const branches: GitBranchModel[] = await this.getBranches((branch) => branch.name === name); if (branches.length <= 0) { - Utils.printAsGitHubError(`The branch '${name}' does not exist.`); - Deno.exit(1); + throw new OrganizationError(`The branch '${name}' does not exist.`); } return branches.filter((branch) => branch.name === name)[0]; @@ -55,6 +87,7 @@ export class GitClient extends GraphQlClient { * @param untilPredicate Used to determine when to stop getting branches. * @returns The list of branches for the repository. * @remarks If the {@link untilPredicate} is not provided, all branches will be returned. + * @throws The error {@link GitError} if an error occurs while getting branches. */ public async getBranches(untilPredicate?: (branch: GitBranchModel) => boolean): Promise { const result: GitBranchModel[] = []; @@ -65,11 +98,18 @@ export class GitClient extends GraphQlClient { const cursor: string = Utils.isNothing(pageInfo.endCursor) ? "" : pageInfo.endCursor; const query: string = result.length <= 0 - ? createGetBranchesQuery(this.ownerName, this.repoName) - : createGetBranchesQuery(this.ownerName, this.repoName, 100, cursor); + ? createGetBranchesQuery(super.ownerName, super.repoName) + : createGetBranchesQuery(super.ownerName, super.repoName, 100, cursor); const responseData = await this.executeQuery(query); + if (responseData.errors != undefined) { + const mainMsg = `The following errors occurred while getting branches for the repository '${super.repoName}'`; + const errorMsg = Utils.toErrorMessage(mainMsg, responseData); + + throw new GitError(errorMsg); + } + pageInfo = responseData.data.repository.refs.pageInfo; const rawBranchData = responseData.data.repository.refs; @@ -117,6 +157,9 @@ export class GitClient extends GraphQlClient { * Creates a branch with the given {@link newBranchName} from a branch that matches the given {@link branchFromName}. * @param newBranchName The name of the branch. * @remarks Requires authentication. + * @throws The error {@link GitError} for the following reasons: + * 1. If the given {@link newBranchName} already exists. + * 2. If there was an issue creating the new branch. */ public async createBranch(newBranchName: string, branchFromName: string): Promise { const funcName = "createBranch"; @@ -125,23 +168,29 @@ export class GitClient extends GraphQlClient { if (await this.branchExists(newBranchName)) { const errorMsg = `A branch with the name '${newBranchName}' already exists.`; - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + throw new GitError(errorMsg); } const fromBranch = await this.getBranch(branchFromName); - const repo = await this.repoClient.getRepoByName(); + const repo = await this.repoClient.getRepo(); if (Utils.isNothing(repo.node_id)) { - const errorMsg = `The repository '${this.repoName}' did not return a required node ID.`; - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + const errorMsg = `The repository '${super.repoName}' did not return a required node ID.`; + throw new GitError(errorMsg); } const mutation: string = getCreateBranchMutation(repo.node_id, newBranchName, fromBranch.oid); const responseData = await this.executeQuery(mutation); + + if (responseData.errors != undefined) { + const mainMsg = `The following errors occurred while creating the branch '${newBranchName}'`; + const errorMsg = Utils.toErrorMessage(mainMsg, responseData); + + throw new GitError(errorMsg); + } + const newBranch = responseData.data.createRef.ref; return { @@ -155,6 +204,7 @@ export class GitClient extends GraphQlClient { * Adds a commit with the given {@link commitMessage} to a branch with the given {@link branchName}. * @param branchName The name of the branch. * @param commitMessage The commit message. + * @throws The error {@link GitError} if the commit could not be added. */ public async addCommit(branchName: string, commitMessage: string): Promise { const funcName = "addCommit"; @@ -163,8 +213,15 @@ export class GitClient extends GraphQlClient { const branch = await this.getBranch(branchName); - const mutation = addCommitMutation(this.ownerName, this.repoName, branchName, branch.oid, commitMessage); + const mutation = addCommitMutation(super.ownerName, super.repoName, branchName, branch.oid, commitMessage); - await this.executeQuery(mutation); + const response = await this.executeQuery(mutation); + + if (response.errors != undefined) { + const mainMsg = `The following errors occurred while adding a commit to the branch '${branchName}'`; + const errorMsg = Utils.toErrorMessage(mainMsg, response); + + throw new GitError(errorMsg); + } } } diff --git a/GitHubClients/IssueClient.ts b/GitHubClients/IssueClient.ts index 85d4649..bc9928a 100644 --- a/GitHubClients/IssueClient.ts +++ b/GitHubClients/IssueClient.ts @@ -1,17 +1,19 @@ -import { Guard } from "core/Guard.ts"; -import { LabelClient } from "github/LabelClient.ts"; -import { IssueModel } from "models/IssueModel.ts"; -import { LabelModel } from "models/LabelModel.ts"; -import { Utils } from "core/Utils.ts"; -import { GitHubHttpStatusCodes, IssueOrPRState } from "core/Enums.ts"; -import { GitHubClient } from "core/GitHubClient.ts"; -import { IssueOrPRRequestData } from "core/IssueOrPRRequestData.ts"; +import { Guard } from "../core/Guard.ts"; +import { LabelClient } from "./LabelClient.ts"; +import { IssueModel } from "../core/Models/IssueModel.ts"; +import { LabelModel } from "../core/Models/LabelModel.ts"; +import { Utils } from "../core/Utils.ts"; +import { GitHubHttpStatusCodes, IssueOrPRState } from "../core/Enums.ts"; +import { GitHubClient } from "../core/GitHubClient.ts"; +import { IssueOrPRRequestData } from "../core/IssueOrPRRequestData.ts"; +import { IssueError } from "./Errors/IssueError.ts"; /** * Provides a client for interacting with issues. */ export class IssueClient extends GitHubClient { private readonly labelClient: LabelClient; + private readonly isInitialized: boolean = false; /** * Initializes a new instance of the {@link IssueClient} class. @@ -23,37 +25,78 @@ export class IssueClient extends GitHubClient { constructor(ownerName: string, repoName: string, token?: string) { super(ownerName, repoName, token); this.labelClient = new LabelClient(ownerName, repoName, token); + this.isInitialized = true; } /** - * Gets all of the open issues in a repository with a name that matches the given {@link IssueClient}.{@link this.this.repoName}. + * Sets the name of the owner of the repository. + */ + public set ownerName(v: string) { + Guard.isNothing("ownerName", v, "v"); + super.ownerName = v.trim(); + + if (!this.isInitialized) { + return; + } + + this.labelClient.ownerName = v; + } + + /** + * Sets the name of the repository. + */ + public set repoName(v: string) { + Guard.isNothing("repoName", v, "v"); + super.repoName = v.trim(); + + if (!this.isInitialized) { + return; + } + + this.labelClient.repoName = v; + } + + /** + * Gets all of the open issues in a repository with a name that matches the given {@link IssueClient.repoName}. * @returns The issues. * @remarks Does not require authentication. + * @throws The {@link IssueError} if an error occurs while getting all of the open issues. */ public async getAllOpenIssues(): Promise { - Guard.isNothing(this.repoName, "getAllOpenIssues", "this.repoName"); - return await this.getAllData(async (page: number, qtyPerPage?: number) => { - return await this.getIssues(page, qtyPerPage); + const [issues, response] = await this.getIssues(page, qtyPerPage); + + if (response.status != GitHubHttpStatusCodes.OK) { + const errorMsg = this.buildErrorMsg(`An error occurred trying to get all of the opened issues.`, response); + throw new IssueError(errorMsg); + } + + return Promise.resolve([issues, response]); }); } /** - * Gets all of the closed issues in a repository with a name that matches the given {@link IssueClient}.{@link this.repoName}. + * Gets all of the closed issues in a repository with a name that matches the given {@link IssueClient.repoName}. * @returns The issues. * @remarks Does not require authentication. + * @throws The {@link IssueError} if an error occurs while getting all of the closed issues. */ public async getAllClosedIssues(): Promise { - Guard.isNothing(this.repoName, "getAllClosedIssues", "this.repoName"); - return await this.getAllData(async (page: number, qtyPerPage?: number) => { - return await this.getIssues(page, qtyPerPage, IssueOrPRState.closed); + const [issues, response] = await this.getIssues(page, qtyPerPage, IssueOrPRState.closed); + + if (response.status != GitHubHttpStatusCodes.OK) { + const errorMsg = this.buildErrorMsg("An error occurred trying to get all of the closed issues.", response); + throw new IssueError(errorMsg); + } + + return Promise.resolve([issues, response]); }); } /** * Gets the given {@link page} of issues where the page quantity matches the given {@link qtyPerPage}, in a repository - * with a name that matches the given {@link IssueClient}.{@link this.repoName}, with the given issue {@link state}, + * with a name that matches the given {@link IssueClient.repoName}, with the given issue {@link state}, * {@link labels}, and in a milestone with a number that matches the given {@link milestoneNumber}. * @param page The page of results to return. * @param qtyPerPage The total to return per {@link page}. @@ -61,6 +104,7 @@ export class IssueClient extends GitHubClient { * @param labels The labels to filter by. A null or empty list will not filter the results. * @param milestoneNumber The milestone number to filter by. A null value will not filter the results. * @returns The issue. + * @throws The {@link IssueError} if an error occurs while getting issue data. * @remarks Does not require authentication if the repository is public. * Open and closed issues can reside on different pages. Example: if there are 5 open and 100 issues total, there * is no guarantee that all of the opened issues will be returned if you request the first page with a quantity of 10. @@ -77,10 +121,6 @@ export class IssueClient extends GitHubClient { labels?: string[] | null, milestoneNumber?: number | null, ): Promise<[IssueModel[], Response]> { - const functionName = "getIssues"; - Guard.isNothing(this.repoName, functionName, "this.repoName"); - - this.repoName = this.repoName.trim(); page = Utils.clamp(page, 1, Number.MAX_SAFE_INTEGER); qtyPerPage = Utils.clamp(qtyPerPage, 1, 100); @@ -94,7 +134,7 @@ export class IssueClient extends GitHubClient { const queryParams = `?page=${page}&per_page=${qtyPerPage}&state=${state}${labelListQueryParam}${milestoneNumberQueryParam}`; - const url = `${this.baseUrl}/repos/${this.ownerName}/${this.repoName}/issues${queryParams}`; + const url = `${this.baseUrl}/repos/${super.ownerName}/${super.repoName}/issues${queryParams}`; const response: Response = await this.requestGET(url); @@ -104,19 +144,15 @@ export class IssueClient extends GitHubClient { case GitHubHttpStatusCodes.MovedPermanently: case GitHubHttpStatusCodes.UnprocessableContent: case GitHubHttpStatusCodes.Unauthorized: { - let errorMsg = `An error occurred trying to get the issues for the repository '${this.repoName}'.`; - errorMsg += `\n\tError: ${response.status}(${response.statusText})`; - Utils.printAsGitHubError(errorMsg); - break; + const mainMsg = `An error occurred trying to get the issues for the repository '${super.repoName}'.`; + const errorMsg = this.buildErrorMsg(mainMsg, response); + throw new IssueError(errorMsg); } case GitHubHttpStatusCodes.NotFound: { - const errorMsg = `The organization '${this.ownerName}' or repository '${this.repoName}' does not exist.`; - Utils.printAsGitHubError(errorMsg); - break; + const errorMsg = `The organization '${super.ownerName}' or repository '${super.repoName}' does not exist.`; + throw new IssueError(errorMsg); } } - - Deno.exit(1); } const issues = ( await this.getResponseData(response)).filter((issue) => Utils.isIssue(issue)); @@ -126,16 +162,16 @@ export class IssueClient extends GitHubClient { /** * Gets an issue with the given {@link issueNumber} from a repository with a name that matches given - * {@link IssueClient}.{@link this.repoName}. + * {@link IssueClient.repoName}. * @param issueNumber The issue number. * @returns The issue. + * @throws The {@link IssueError} if an error occurs while getting an issue. */ public async getIssue(issueNumber: number): Promise { - Guard.isNothing(this.repoName, "getIssue", "this.repoName"); Guard.isLessThanOne(issueNumber, "getIssue", "issueNumber"); // REST API Docs: https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28#get-an-issue - const url = `${this.baseUrl}/repos/${this.ownerName}/${this.repoName}/issues/${issueNumber}`; + const url = `${this.baseUrl}/repos/${super.ownerName}/${super.repoName}/issues/${issueNumber}`; const response: Response = await this.requestGET(url); @@ -146,17 +182,13 @@ export class IssueClient extends GitHubClient { case GitHubHttpStatusCodes.NotModified: case GitHubHttpStatusCodes.Unauthorized: case GitHubHttpStatusCodes.Gone: { - let errorMsg = `An error occurred trying to get issue '${issueNumber}'.`; - errorMsg += `\n\tError: ${response.status}(${response.statusText})`; - Utils.printAsGitHubError(errorMsg); - break; + const errorMsg = this.buildErrorMsg(`An error occurred trying to get issue '${issueNumber}'.`, response); + + throw new IssueError(errorMsg); } case GitHubHttpStatusCodes.NotFound: - Utils.printAsGitHubError(`The repository '${this.repoName}' or issue '${issueNumber}' does not exist.`); - break; + throw new IssueError(`The repository '${super.repoName}' or issue '${issueNumber}' does not exist.`); } - - Deno.exit(1); } const issue = await this.getResponseData(response); @@ -164,50 +196,43 @@ export class IssueClient extends GitHubClient { if (Utils.isIssue(issue)) { return issue; } else { - const errorMsg = `The issue '${issueNumber}' in the repository '${this.repoName}' is not an issue.`; - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + throw new IssueError(`The repository '${super.repoName}' or issue '${issueNumber}' does not exist.`); } } /** * Adds the given {@link label} to an issue that matches the given {@link issueNumber} in a repository - * with a name that matches the given {@link IssueClient}.{@link this.repoName}. + * with a name that matches the given {@link IssueClient.repoName}. * @param issueNumber The number of an issue. * @param label The name of the label to add. * @remarks Requires authentication. + * @throws The {@link IssueError} for the following reasons: + * 1. If the given {@link label} does not exist in the repository. + * 2. If there is an issue adding the {@link label} to an issue that matches the given {@link issueNumber}. */ public async addLabel(issueNumber: number, label: string): Promise { - Guard.isNothing(this.repoName, "addLabel", "this.repoName"); Guard.isLessThanOne(issueNumber, "addLabel", "issueNumber"); - Guard.isNothing(label, "addLabel", "this.repoName"); - - if (!this.containsToken()) { - Utils.printAsGitHubError(`The request to add label '${label}' is forbidden. Check the auth token.`); - Deno.exit(1); - } // First check that the label trying to be added exists in the repo const labelDoesNotExist = !(await this.labelClient.labelExists(label)); if (labelDoesNotExist) { - const labelsUrl = `https://github.com/KinsonDigital/${this.repoName}/labels`; - const issueUrl = `https://github.com/KinsonDigital/${this.repoName}/issues/618`; + const labelsUrl = `https://github.com/KinsonDigital/${super.repoName}/labels`; + const issueUrl = `https://github.com/KinsonDigital/${super.repoName}/issues/618`; let errorMsg = `The label '${label}' attempting to be added to issue '${issueNumber}'`; - errorMsg += ` does not exist in the repository '${this.repoName}'.`; + errorMsg += ` does not exist in the repository '${super.repoName}'.`; errorMsg += `\nRepo Labels: ${labelsUrl}`; errorMsg += `\nIssue: ${issueUrl}`; - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + throw new IssueError(errorMsg); } const prLabels: string[] = await this.getLabels(issueNumber); prLabels.push(label); // REST API Docs: https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28#update-an-issue - const url = `${this.baseUrl}/repos/${this.ownerName}/${this.repoName}/issues/${issueNumber}`; + const url = `${this.baseUrl}/repos/${super.ownerName}/${super.repoName}/issues/${issueNumber}`; const response: Response = await this.requestPATCH(url, JSON.stringify({ labels: prLabels })); @@ -220,32 +245,30 @@ export class IssueClient extends GitHubClient { case GitHubHttpStatusCodes.ServiceUnavailable: case GitHubHttpStatusCodes.Forbidden: case GitHubHttpStatusCodes.Unauthorized: { - let errorMsg = `An error occurred trying to add the label '${label}' to issue '${issueNumber}'.`; - errorMsg += `\n\tError: ${response.status}(${response.statusText})`; - Utils.printAsGitHubError(errorMsg); - break; + const errorMsg = this.buildErrorMsg( + `An error occurred trying to add the label '${label}' to issue '${issueNumber}'.`, + response, + ); + throw new IssueError(errorMsg); } case GitHubHttpStatusCodes.NotFound: - Utils.printAsGitHubError(`An issue with the number '${issueNumber}' does not exist.`); - break; + throw new IssueError(`An issue with the number '${issueNumber}' does not exist.`); } - - Deno.exit(1); } } /** * Gets all of the labels for an issue that matches the given {@link issueNumber} in a repository - * with a name that matches the given {@link IssueClient}.{@link this.repoName}. + * with a name that matches the given {@link IssueClient.repoName}. * @param issueNumber The number of an issue. * @returns The labels for an issue. * @remarks Does not require authentication. + * @throws The {@link IssueError} if an error occurs while getting the labels for an issue. */ public async getLabels(issueNumber: number): Promise { - Guard.isNothing(this.repoName, "getLabels", "this.repoName"); Guard.isLessThanOne(issueNumber, "getLabels", "issueNumber"); - const url = `${this.baseUrl}/repos/${this.ownerName}/${this.repoName}/issues/${issueNumber}/labels`; + const url = `${this.baseUrl}/repos/${super.ownerName}/${super.repoName}/issues/${issueNumber}/labels`; const response: Response = await this.requestGET(url); @@ -255,17 +278,15 @@ export class IssueClient extends GitHubClient { case GitHubHttpStatusCodes.MovedPermanently: case GitHubHttpStatusCodes.Gone: case GitHubHttpStatusCodes.Unauthorized: { - let errorMsg = `There was an issue getting the labels for issue '${issueNumber}'.`; - errorMsg += `\n\tError: ${response.status}(${response.statusText})`; - Utils.printAsGitHubError(errorMsg); - break; + const errorMsg = this.buildErrorMsg( + `There was an issue getting the labels for issue '${issueNumber}'.`, + response); + + throw new IssueError(errorMsg); } case GitHubHttpStatusCodes.NotFound: - Utils.printAsGitHubError(`An issue with the number '${issueNumber}' does not exist.`); - break; + throw new IssueError(`An issue with the number '${issueNumber}' does not exist.`); } - - Deno.exit(1); } const responseData = await this.getResponseData(response); @@ -275,60 +296,86 @@ export class IssueClient extends GitHubClient { /** * Returns a value indicating whether or not an issue with the given {@link issueNumber} exists regardless - * of its state, in a repository with a name that matches the given {@link IssueClient}.{@link this.repoName}. + * of its state, in a repository with a name that matches the given {@link IssueClient.repoName}. * @param issueNumber The issue number. * @returns True if the issue exists, otherwise false. + * @throws The {@link IssueError} if an error occurs while checking if an issue exists. */ public async issueExists(issueNumber: number): Promise { Guard.isLessThanOne(issueNumber, "openIssueExist", "issueNumber"); - return await this.openOrClosedIssueExists(issueNumber, IssueOrPRState.any); + try { + return await this.openOrClosedIssueExists(issueNumber, IssueOrPRState.any); + } catch (error) { + if (error instanceof IssueError) { + throw error; + } else { + return Promise.resolve(false); + } + } } /** * Returns a value indicating whether or not an open issue with the given {@link issueNumber} exists in a - * repository with a name that matches the given {@link IssueClient}.{@link this.repoName}. + * repository with a name that matches the given {@link IssueClient.repoName}. * @param issueNumber The issue number. * @returns True if the issue exists and is open, otherwise false. + * @throws The {@link IssueError} if an error occurs while checking if an open issue exists. */ public async openIssueExists(issueNumber: number): Promise { Guard.isLessThanOne(issueNumber, "openIssueExist", "issueNumber"); - return await this.openOrClosedIssueExists(issueNumber, IssueOrPRState.open); + try { + return await this.openOrClosedIssueExists(issueNumber, IssueOrPRState.open); + } catch (error) { + if (error instanceof IssueError) { + throw error; + } else { + return Promise.resolve(false); + } + } } /** * Returns a value indicating whether or not a closed issue with the given {@link issueNumber} exists in a - * repository with a name that matches the given {@link IssueClient}.{@link this.repoName}. + * repository with a name that matches the given {@link IssueClient.repoName}. * @param issueNumber The issue number. * @returns True if the issue exists and is open, otherwise false. + * @throws The {@link IssueError} if an error occurs while checking if an closed issue exists. */ public async closedIssueExists(issueNumber: number): Promise { Guard.isLessThanOne(issueNumber, "closedIssueExist", "issueNumber"); - return await this.openOrClosedIssueExists(issueNumber, IssueOrPRState.closed); + try { + return await this.openOrClosedIssueExists(issueNumber, IssueOrPRState.closed); + } catch (error) { + if (error instanceof IssueError) { + throw error; + } else { + return Promise.resolve(false); + } + } } /** * Updates an issue with the given {@link issueNumber} and {@link issueData}, in a repository with a name - * that matches the given {@link IssueClient}.{@link this.repoName}. + * that matches the given {@link IssueClient.repoName}. * @param issueNumber The issue number. * @param issueData The data to update the issue with. + * @remarks Requires authentication. + * @throws The {@link IssueError} if an error occurs while updating an issue. */ public async updateIssue(issueNumber: number, issueData: IssueOrPRRequestData): Promise { - Guard.isNothing(this.repoName, "updateIssue", "this.repoName"); Guard.isLessThanOne(issueNumber, "updateIssue", "issueNumber"); - this.repoName = this.repoName.trim(); - - const url = `${this.baseUrl}/repos/${this.ownerName}/${this.repoName}/issues/${issueNumber}`; + const url = `${this.baseUrl}/repos/${super.ownerName}/${super.repoName}/issues/${issueNumber}`; const issueBody: string = JSON.stringify(issueData); const response = await this.requestPATCH(url, issueBody); if (response.status != GitHubHttpStatusCodes.OK) { if (response.status === GitHubHttpStatusCodes.NotFound) { - Utils.printAsGitHubError(`An issue with the number '${issueNumber}' does not exist.`); + throw new IssueError(`An issue with the number '${issueNumber}' does not exist.`); } else { switch (response.status) { case GitHubHttpStatusCodes.MovedPermanently: @@ -337,34 +384,41 @@ export class IssueClient extends GitHubClient { case GitHubHttpStatusCodes.ServiceUnavailable: case GitHubHttpStatusCodes.Unauthorized: case GitHubHttpStatusCodes.Forbidden: { - let errorMsg = `An error occurred trying to update issue '${issueNumber}'.`; - errorMsg += `\n\t'Error: ${response.status}(${response.statusText})`; + const errorMsg = this.buildErrorMsg( + `An error occurred trying to update issue '${issueNumber}'.`, + response, + ); - Utils.printAsGitHubError(errorMsg); - break; + throw new IssueError(errorMsg); } } } - - Deno.exit(1); } } /** * Checks if an issue with the given {@link issueNumber } exists with the given {@link state} in a - * repository with a name that matches the given {@link IssueClient}.{@link this.repoName}. + * repository with a name that matches the given {@link IssueClient.repoName}. * @param issueNumber The number of the issue. * @returns True if the issue exists, otherwise false. + * @throws The {@link IssueError} if an error occurs while checking if the open or closed issue exists. */ private async openOrClosedIssueExists(issueNumber: number, state: IssueOrPRState): Promise { - Guard.isNothing(this.repoName, "openOrClosedIssueExists", "this.repoName"); Guard.isLessThanOne(issueNumber, "openOrClosedIssueExists", "issueNumber"); - this.repoName = this.repoName.toLowerCase(); - const issues = await this.getAllDataUntil( async (page: number, qtyPerPage?: number) => { - return await this.getIssues(page, qtyPerPage, state); + try { + const [issues, response] = await this.getIssues(page, qtyPerPage, state); + + return Promise.resolve([issues, response]); + } catch (error) { + if (error instanceof IssueError) { + throw error; + } else { + return Promise.resolve([[], new Response()]); + } + } }, 1, // Start page 100, // Qty per page diff --git a/GitHubClients/LabelClient.ts b/GitHubClients/LabelClient.ts index 5435400..a247d75 100644 --- a/GitHubClients/LabelClient.ts +++ b/GitHubClients/LabelClient.ts @@ -1,8 +1,9 @@ -import { LabelModel } from "core/Models/LabelModel.ts"; -import { Utils } from "core/Utils.ts"; -import { GitHubHttpStatusCodes } from "core/Enums.ts"; -import { GitHubClient } from "core/GitHubClient.ts"; -import { Guard } from "core/Guard.ts"; +import { LabelModel } from "../core/Models/LabelModel.ts"; +import { Utils } from "../core/Utils.ts"; +import { GitHubHttpStatusCodes } from "../core/Enums.ts"; +import { GitHubClient } from "../core/GitHubClient.ts"; +import { Guard } from "../core/Guard.ts"; +import { LabelError } from "./Errors/LabelError.ts"; /** * Provides a client for interacting with labels. @@ -20,7 +21,7 @@ export class LabelClient extends GitHubClient { Guard.isNothing(ownerName, funcName, "ownerName"); Guard.isNothing(repoName, funcName, "repoName"); - super(token); + super(ownerName, repoName, token); } /** @@ -30,6 +31,7 @@ export class LabelClient extends GitHubClient { * @param qtyPerPage The total to return per {@link page}. * @returns A list of labels in the repo. * @remarks Does not require authentication. + * @throws The error {@link LabelError} if the something goes wrong with getting the labels. */ public async getLabels(page: number, qtyPerPage: number): Promise<[LabelModel[], Response]> { page = page < 1 ? 1 : page; @@ -40,8 +42,9 @@ export class LabelClient extends GitHubClient { const response: Response = await this.requestGET(url); if (response.status === GitHubHttpStatusCodes.NotFound) { - Utils.printAsGitHubError(`${response.status} - ${response.statusText}`); - Deno.exit(1); + const errorMsg = this.buildErrorMsg("There was an issue getting the labels.", response); + + throw new LabelError(errorMsg); } return [await this.getResponseData(response), response]; @@ -50,6 +53,7 @@ export class LabelClient extends GitHubClient { /** * Gets all of the labels for a repository with a name that matches the {@link LabelClient}.{@link repoName}. * @returns The list of repository labels. + * @throws The error {@link LabelError} if the something goes wrong with getting all of the labels. */ public async getAllLabels(): Promise { const result: LabelModel[] = []; @@ -71,10 +75,10 @@ export class LabelClient extends GitHubClient { * @param label The name of the label to check for. * @returns True if the label exists, false otherwise. * @remarks Does not require authentication. + * @throws The error {@link LabelError} when something goes wrong with checking if the label exists. */ public async labelExists(label: string): Promise { - const funcName = "labelExists"; - Guard.isNothing(label, funcName, "label"); + Guard.isNothing(label, "labelExists", "label"); const url = `${this.baseUrl}/repos/${this.ownerName}/${this.repoName}/labels/${label}`; const response: Response = await this.requestGET(url); @@ -84,11 +88,11 @@ export class LabelClient extends GitHubClient { } else if (response.status === GitHubHttpStatusCodes.OK) { return true; } else { - let errorMsg = `There was an issue getting the repository label '${label}'.`; - errorMsg += `Error: ${response.status} - ${response.statusText}`; + const errorMsg = this.buildErrorMsg( + `There was an issue checking if the repository label '${label}' exists.`, + response); - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + throw new LabelError(errorMsg); } } } diff --git a/GitHubClients/MilestoneClient.ts b/GitHubClients/MilestoneClient.ts index 6946aae..4c1fcf3 100644 --- a/GitHubClients/MilestoneClient.ts +++ b/GitHubClients/MilestoneClient.ts @@ -1,13 +1,14 @@ -import { Guard } from "core/Guard.ts"; -import { IssueModel } from "core/Models/IssueModel.ts"; -import { MilestoneModel } from "core/Models/MilestoneModel.ts"; -import { PullRequestModel } from "core/Models/PullRequestModel.ts"; -import { Utils } from "core/Utils.ts"; -import { GitHubHttpStatusCodes, IssueOrPRState, MergeState } from "core/Enums.ts"; -import { GitHubClient } from "core/GitHubClient.ts"; -import { IssueClient } from "github/IssueClient.ts"; -import { PullRequestClient } from "github/PullRequestClient.ts"; -import { IssueOrPR } from "core/Types.ts"; +import { Guard } from "../core/Guard.ts"; +import { IssueModel } from "../core/Models/IssueModel.ts"; +import { MilestoneModel } from "../core/Models/MilestoneModel.ts"; +import { PullRequestModel } from "../core/Models/PullRequestModel.ts"; +import { Utils } from "../core/Utils.ts"; +import { GitHubHttpStatusCodes, IssueOrPRState, MergeState } from "../core/Enums.ts"; +import { GitHubClient } from "../core/GitHubClient.ts"; +import { IssueClient } from "./IssueClient.ts"; +import { PullRequestClient } from "./PullRequestClient.ts"; +import { IssueOrPR } from "../core/Types.ts"; +import { MilestoneError } from "./Errors/MilestoneError.ts"; /** * Provides a client for interacting with milestones. @@ -15,6 +16,7 @@ import { IssueOrPR } from "core/Types.ts"; export class MilestoneClient extends GitHubClient { private readonly issueClient: IssueClient; private readonly prClient: PullRequestClient; + private readonly isInitialized: boolean = false; /** * Initializes a new instance of the {@link MilestoneClient} class. @@ -26,12 +28,43 @@ export class MilestoneClient extends GitHubClient { constructor(ownerName: string, repoName: string, token?: string) { const funcName = "MilestoneClient.ctor"; Guard.isNothing(ownerName, funcName, "ownerName"); - Guard.isNothing(repoName, funcName, "this.repoName"); + Guard.isNothing(repoName, funcName, "repoName"); - super(token); + super(ownerName, repoName, token); - this.issueClient = new IssueClient(ownerName, this.repoName, token); - this.prClient = new PullRequestClient(ownerName, this.repoName, token); + this.issueClient = new IssueClient(ownerName, repoName, token); + this.prClient = new PullRequestClient(ownerName, repoName, token); + this.isInitialized = true; + } + + /** + * Sets the name of the owner of the repository. + */ + public set ownerName(v: string) { + Guard.isNothing("ownerName", v, "v"); + super.ownerName = v.trim(); + + if (!this.isInitialized) { + return; + } + + this.issueClient.ownerName = v; + this.prClient.ownerName = v; + } + + /** + * Sets the name of the repository. + */ + public set repoName(v: string) { + Guard.isNothing("repoName", v, "v"); + super.repoName = v.trim(); + + if (!this.isInitialized) { + return; + } + + this.issueClient.repoName = v; + this.prClient.repoName = v; } /** @@ -56,8 +89,7 @@ export class MilestoneClient extends GitHubClient { await Promise.all([issuesPromise, pullRequestsPromise]).then((values) => { issuesAndPullRequests.push(...values[0], ...values[1]); }).catch((error) => { - Utils.printAsGitHubError(`The request to get issues returned error '${error}'`); - Deno.exit(1); + throw new MilestoneError(`The request to get issues returned error '${error}'`); }); return issuesAndPullRequests; @@ -147,8 +179,7 @@ export class MilestoneClient extends GitHubClient { if (milestone === undefined) { const errorMsg = `The milestone '${milestoneName}' could not be found.`; - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + throw new MilestoneError(errorMsg) } return milestone; @@ -166,17 +197,16 @@ export class MilestoneClient extends GitHubClient { qtyPerPage = Utils.clamp(qtyPerPage, 1, 100); const queryParams = `?state=all&page=${page}&per_page=${qtyPerPage}`; - const url = `${this.baseUrl}/repos/${this.ownerName}/${this.repoName}/milestones${queryParams}`; + const url = `${this.baseUrl}/repos/${super.ownerName}/${super.repoName}/milestones${queryParams}`; const response: Response = await this.requestGET(url); // If there is an error if (response.status != GitHubHttpStatusCodes.OK) { - let errorMsg = `The milestones for the repository owner '${this.ownerName}'`; - errorMsg += ` and for the repository '${this.repoName}' could not be found.`; + let errorMsg = `The milestones for the repository owner '${super.ownerName}'`; + errorMsg += ` and for the repository '${super.repoName}' could not be found.`; - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + throw new MilestoneError(errorMsg); } return [ await this.getResponseData(response), response]; @@ -220,21 +250,16 @@ export class MilestoneClient extends GitHubClient { const milestone: MilestoneModel = await this.getMilestoneByName(milestoneName); - const url = `${this.baseUrl}/repos/${this.ownerName}/${this.repoName}/milestones/${milestone.number}`; + const url = `${this.baseUrl}/repos/${super.ownerName}/${super.repoName}/milestones/${milestone.number}`; const response: Response = await this.requestPATCH(url, JSON.stringify({ state: "closed" })); // If there is an error - if (response.status === GitHubHttpStatusCodes.OK) { - Utils.printAsGitHubNotice(`✅The milestone '${milestoneName}' has been closed.✅`); - } else if (response.status === GitHubHttpStatusCodes.NotFound) { - Utils.printAsGitHubError(`The organization '${this.ownerName}' or repo '${this.repoName}' does not exist.`); - Deno.exit(1); - } else { - let errorMsg = `An error occurred trying to close milestone '${milestoneName}(${milestone.number})'.`; - errorMsg += `\nError: ${response.status}(${response.statusText})`; - - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + if (response.status != GitHubHttpStatusCodes.OK) { + const errorMsg = this.buildErrorMsg( + `An error occurred trying to close milestone '${milestoneName}(${milestone.number})'.`, + response); + + throw new MilestoneError(errorMsg); } } } diff --git a/GitHubClients/OrgClient.ts b/GitHubClients/OrgClient.ts index 0044867..bb30be0 100644 --- a/GitHubClients/OrgClient.ts +++ b/GitHubClients/OrgClient.ts @@ -1,10 +1,11 @@ -import { GitHubHttpStatusCodes, OrgMemberRole } from "core/Enums.ts"; -import { GitHubClient } from "core/GitHubClient.ts"; -import { Guard } from "core/Guard.ts"; -import { GitHubVarModel } from "core/Models/GitHubVarModel.ts"; -import { GitHubVariablesModel } from "core/Models/GitHubVariablesModel.ts"; -import { UserModel } from "core/Models/UserModel.ts"; -import { Utils } from "core/Utils.ts"; +import { GitHubHttpStatusCodes, OrgMemberRole } from "../core/Enums.ts"; +import { GitHubClient } from "../core/GitHubClient.ts"; +import { Guard } from "../core/Guard.ts"; +import { GitHubVarModel } from "../core/Models/GitHubVarModel.ts"; +import { GitHubVariablesModel } from "../core/Models/GitHubVariablesModel.ts"; +import { UserModel } from "../core/Models/UserModel.ts"; +import { Utils } from "../core/Utils.ts"; +import { OrganizationError } from "./Errors/OrganizationError.ts"; /** * Provides a client for interacting with issues. @@ -56,14 +57,6 @@ export class OrgClient extends GitHubClient { const response = await this.requestGET(url); - if (response.status != GitHubHttpStatusCodes.OK) { - let errorMsg = `An error occurred when getting the private members for the organization '${this.ownerName}'.`; - errorMsg += `\nError: ${response.status}(${response.statusText})`; - - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); - } - return [await this.getResponseData(response), response]; } @@ -89,14 +82,6 @@ export class OrgClient extends GitHubClient { const response = await this.requestGET(url); - if (response.status != GitHubHttpStatusCodes.OK) { - let errorMsg = `An error occurred when getting the public members for the organization '${this.ownerName}'.`; - errorMsg += `\nError: ${response.status}(${response.statusText})`; - - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); - } - return [await this.getResponseData(response), response]; } @@ -105,10 +90,21 @@ export class OrgClient extends GitHubClient { * that has the given {@link OrgMemberRole.admin} role. * @returns The list of private members for an organization. * @remarks Requires authentication. + * @throws The error {@type OrganizationError} if the request to get private admin members fails. */ public async getPrivateAdminMembers(): Promise { return await this.getAllData(async (page: number, qtyPerPage?: number) => { - return await this.getPrivateMembers(page, qtyPerPage, OrgMemberRole.admin); + const [userModels, response] = await this.getPrivateMembers(page, qtyPerPage, OrgMemberRole.admin); + + if (response.status != GitHubHttpStatusCodes.OK) { + const errorMsg = this.buildErrorMsg( + `An error occurred when getting the private admin members for the organization '${this.ownerName}'.`, + response); + + throw new OrganizationError(errorMsg); + } + + return [userModels, response]; }); } @@ -117,10 +113,21 @@ export class OrgClient extends GitHubClient { * that has the {@link OrgMemberRole.member} role. * @returns The list of private members for an organization. * @remarks Requires authentication. + * @throws The error {@link OrganizationError} if the request to get private non-admin members fails. */ public async getPrivateNonAdminMembers(): Promise { return await this.getAllData(async (page: number, qtyPerPage?: number) => { - return await this.getPrivateMembers(page, qtyPerPage, OrgMemberRole.member); + const [users, response] = await this.getPrivateMembers(page, qtyPerPage, OrgMemberRole.member); + + if (response.status != GitHubHttpStatusCodes.OK) { + const errorMsg = this.buildErrorMsg( + `An error occurred when getting the private non-admin members for the organization '${this.ownerName}'.`, + response); + + throw new OrganizationError(errorMsg); + } + + return Promise.resolve([users, response]); }); } @@ -129,10 +136,21 @@ export class OrgClient extends GitHubClient { * and has any role. * @returns The list of private members for an organization. * @remarks Requires authentication. + * @throws The error {@link OrganizationError} if the request to get all private members fails. */ public async getAllPrivateMembers(): Promise { return await this.getAllData(async (page: number, qtyPerPage?: number) => { - return await this.getPrivateMembers(page, qtyPerPage, OrgMemberRole.all); + const [users, response] = await this.getPrivateMembers(page, qtyPerPage, OrgMemberRole.all); + + if (response.status != GitHubHttpStatusCodes.OK) { + const errorMsg = this.buildErrorMsg( + `An error occurred when getting the private members for the organization '${this.ownerName}'.`, + response); + + throw new OrganizationError(errorMsg); + } + + return Promise.resolve([users, response]); }); } @@ -141,10 +159,21 @@ export class OrgClient extends GitHubClient { * and has the given {@link OrgMemberRole.admin} role. * @returns The list of public members for an organization. * @remarks Does not require authentication. + * @throws The error {@link OrganizationError} if the request to get all public admin members fails. */ public async getPublicAdminMembers(): Promise { return await this.getAllData(async (page: number, qtyPerPage?: number) => { - return await this.getPublicMembers(page, qtyPerPage, OrgMemberRole.admin); + const [users, response] = await this.getPublicMembers(page, qtyPerPage, OrgMemberRole.admin); + + if (response.status != GitHubHttpStatusCodes.OK) { + const errorMsg = this.buildErrorMsg( + `An error occurred when getting the public admin members for the organization '${this.ownerName}'.`, + response); + + throw new OrganizationError(errorMsg); + } + + return Promise.resolve([users, response]); }); } @@ -153,10 +182,21 @@ export class OrgClient extends GitHubClient { * that does not have the given {@link OrgMemberRole.admin} role. * @returns The list of public members for an organization. * @remarks Does not require authentication. + * @throws The error {@type OrganizationError} if the request to get all public non-admin members fails. */ public async getPublicNonAdminMembers(): Promise { return await this.getAllData(async (page: number, qtyPerPage?: number) => { - return await this.getPublicMembers(page, qtyPerPage, OrgMemberRole.member); + const [users, response] = await this.getPublicMembers(page, qtyPerPage, OrgMemberRole.member); + + if (response.status != GitHubHttpStatusCodes.OK) { + const errorMsg = this.buildErrorMsg( + `An error occurred when getting the public non-admin members for the organization '${this.ownerName}'.`, + response); + + throw new OrganizationError(errorMsg); + } + + return Promise.resolve([users, response]); }); } @@ -165,10 +205,21 @@ export class OrgClient extends GitHubClient { * given {@link OrgClient}.{@link ownerName} with an {@link OrgMemberRole.admin} role. * @returns The list of public members for an organization. * @remarks Does not require authentication. + * @throws The error {@link OrganizationError} if the request to get all public members fails. */ public async getAllPublicMembers(): Promise { return await this.getAllData(async (page: number, qtyPerPage?: number) => { - return await this.getPublicMembers(page, qtyPerPage, OrgMemberRole.all); + const [users, response] = await this.getPublicMembers(page, qtyPerPage, OrgMemberRole.all); + + if (response.status != GitHubHttpStatusCodes.OK) { + const errorMsg = this.buildErrorMsg( + `An error occurred when getting the public members for the organization '${this.ownerName}'.`, + response); + + throw new OrganizationError(errorMsg); + } + + return Promise.resolve([users, response]); }); } @@ -222,7 +273,7 @@ export class OrgClient extends GitHubClient { /** * Gets a value indicating whether or not a user with a name that matches the - * given {@link username} is a member of an organization with a name that matches, + * given {@link username} is a member of an organization with a name that matches, * the {@link OrgClient}.{@link ownerName} and has an admin role. * @param username The username of the user that might exist in the organization. * @returns True if the user is a member of the organization, false otherwise. @@ -245,11 +296,11 @@ export class OrgClient extends GitHubClient { const response = await this.requestGET(url); if (response.status != GitHubHttpStatusCodes.OK) { - let errorMsg = `An error occurred when getting the variables for the organization '${this.ownerName}'.`; - errorMsg += `\nError: ${response.status}(${response.statusText})`; + const errorMsg = this.buildErrorMsg( + `An error occurred when getting the variables for the organization '${this.ownerName}'.`, + response); - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + throw new OrganizationError(errorMsg); } const vars = await this.getResponseData(response); diff --git a/GitHubClients/ProjectClient.ts b/GitHubClients/ProjectClient.ts index df21520..1bfc79a 100644 --- a/GitHubClients/ProjectClient.ts +++ b/GitHubClients/ProjectClient.ts @@ -1,17 +1,25 @@ -import { createOrgProjectsQuery } from "core/GraphQl/Queries/GetOrgProjectsQueries.ts"; -import { ProjectModel } from "core/Models/ProjectModel.ts"; -import { GraphQlClient } from "core/GraphQlClient.ts"; -import { Guard } from "core/Guard.ts"; -import { Utils } from "core/Utils.ts"; -import { createLinkItemToProjectMutation } from "core/GraphQl/Mutations/AddToProjectMutation.ts"; -import { createGetIssueProjectsQuery } from "core/GraphQl/Queries/GetIssueProjectsQuery.ts"; -import { createGetPullRequestProjectsQuery } from "core/GraphQl/Queries/GetPullRequestProjectsQuery.ts"; +import { createOrgProjectsQuery } from "../core/GraphQl/Queries/GetOrgProjectsQueries.ts"; +import { ProjectModel } from "../core/Models/ProjectModel.ts"; +import { GraphQlClient } from "../core/GraphQlClient.ts"; +import { Guard } from "../core/Guard.ts"; +import { Utils } from "../core/Utils.ts"; +import { createLinkItemToProjectMutation } from "../core/GraphQl/Mutations/AddToProjectMutation.ts"; +import { createGetIssueProjectsQuery } from "../core/GraphQl/Queries/GetIssueProjectsQuery.ts"; +import { createGetPullRequestProjectsQuery } from "../core/GraphQl/Queries/GetPullRequestProjectsQuery.ts"; +import { ProjectError } from "./Errors/ProjectError.ts"; +import { IssueClient, PullRequestClient } from "./mod.ts"; +import { IssueModel } from "../core/Models/IssueModel.ts"; +import { PullRequestModel } from "../core/Models/PullRequestModel.ts"; /** - * Gets or saves data related to GitHub organization projects. + * Gets or saves data related to GitHub organization V2 projects. * @remarks This client requires authentication for all requests. */ export class ProjectClient extends GraphQlClient { + private readonly issueClient: IssueClient; + private readonly prClient: PullRequestClient; + private readonly isInitialized: boolean = false; + /** * Initializes a new instance of the {@link ProjectClient} class. * @param ownerName The name of the owner of the repository to use. @@ -23,17 +31,60 @@ export class ProjectClient extends GraphQlClient { Guard.isNothing(ownerName, funcName, "ownerName"); Guard.isNothing(repoName, funcName, "repoName"); - super(token); + super(ownerName, repoName, token); + + this.issueClient = new IssueClient(ownerName, repoName, token); + this.prClient = new PullRequestClient(ownerName, repoName, token); + + this.isInitialized = true; + } + + /** + * Sets the name of the owner of the repository. + */ + public set ownerName(v: string) { + Guard.isNothing("ownerName", v, "v"); + super.ownerName = v.trim(); + + if (!this.isInitialized) { + return; + } + + this.issueClient.ownerName; + this.prClient.ownerName; + } + + /** + * Sets the name of the repository. + */ + public set repoName(v: string) { + Guard.isNothing("repoName", v, "v"); + super.repoName = v.trim(); + + if (!this.isInitialized) { + return; + } + + this.issueClient.repoName = v; + this.prClient.repoName = v; } /** * Gets a list of the GitHub organization projects. * @returns The list of projects. + * @throws The error {@link ProjectError} if an error occurs while getting the projects. */ public async getOrgProjects(): Promise { - const query = createOrgProjectsQuery(this.ownerName); + const query = createOrgProjectsQuery(super.ownerName); const responseData = await this.executeQuery(query); + if (responseData.errors != undefined) { + const mainMsg = "The following errors occurred while getting the organizational projects."; + const errorMsg = Utils.toErrorMessage(mainMsg, responseData); + + throw new ProjectError(errorMsg); + } + return responseData.data.organization.projectsV2.nodes; } @@ -43,7 +94,7 @@ export class ProjectClient extends GraphQlClient { * @returns True if the project exists, otherwise false. */ public async exists(projectName: string): Promise { - Guard.isNothing(projectName, "projectExists"); + Guard.isNothing(projectName, "exists"); projectName = projectName.trim(); const projects = await this.getOrgProjects(); @@ -52,17 +103,22 @@ export class ProjectClient extends GraphQlClient { } /** - * Adds an issue or pull request with the given {@link contentId} to a project with the given {@link projectName}. - * @param contentId The node id of an issue or pull request. + * Adds an issue with the given {@link issueNumber} to a project with the given {@link projectName}. + * @param issueNumber The issue number. * @param projectName The name of the project. - * @throws Throws an error if the project does not exist. + * @throws The error {@link ProjectError} if an error occurs while adding the issue to the project. */ - public async addToProject(contentId: string, projectName: string): Promise { - const funcName = "addToProject"; - Guard.isNothing(contentId, funcName); - Guard.isNothing(projectName, funcName); + public async addIssueToProject(issueNumber: number, projectName: string): Promise { + Guard.isNothing(projectName, "addIssueToProject"); + + let issue: IssueModel; + + try { + issue = await this.issueClient.getIssue(issueNumber) + } catch (error) { + throw new ProjectError(error.message); + } - contentId = contentId.trim(); projectName = projectName.trim(); const projects = await this.getOrgProjects(); @@ -70,27 +126,88 @@ export class ProjectClient extends GraphQlClient { const project: ProjectModel | undefined = projects.find((project) => project.title.trim() === projectName); if (project === undefined) { - Utils.printAsGitHubError(`The project '${projectName}' does not exist.`); - Deno.exit(1); + throw new ProjectError(`The project '${projectName}' does not exist.`); + } + + if (Utils.isNothing(issue.node_id)) { + const errorMsg = `The issue '${issueNumber}' does not have a node ID.`; + throw new ProjectError(errorMsg); } - const query = createLinkItemToProjectMutation(contentId, project.id); - await this.executeQuery(query); + const query = createLinkItemToProjectMutation(issue.node_id, project.id); + const response = await this.executeQuery(query); + + if (response.errors != undefined) { + const mainMsg = `The following errors occurred while adding the issue '${issueNumber}' to the project '${projectName}'.`; + const errorMsg = Utils.toErrorMessage(mainMsg, response); + + throw new ProjectError(errorMsg); + } } + /** + * Adds a pull request with the given {@link prNumber} to a project with the given {@link projectName}. + * @param prNumber The pull request number. + * @param projectName The name of the project. + * @throws The error {@link ProjectError} if an error occurs while adding the pull request to the project. + */ + public async addPullRequestToProject(prNumber: number, projectName: string): Promise { + Guard.isNothing(projectName, "addPullRequestToProject"); + + let pr: PullRequestModel; + + try { + pr = await this.prClient.getPullRequest(prNumber) + } catch (error) { + throw new ProjectError(error.message); + } + + projectName = projectName.trim(); + + const projects = await this.getOrgProjects(); + + const project: ProjectModel | undefined = projects.find((project) => project.title.trim() === projectName); + + if (project === undefined) { + throw new ProjectError(`The project '${projectName}' does not exist.`); + } + + if (Utils.isNothing(pr.node_id)) { + const errorMsg = `The pull request '${prNumber}' does not have a node ID.`; + throw new ProjectError(errorMsg); + } + + const query = createLinkItemToProjectMutation(pr.node_id, project.id); + const response = await this.executeQuery(query); + + if (response.errors != undefined) { + const mainMsg = `The following errors occurred while adding the pull request '${prNumber}' to the project '${projectName}'.`; + const errorMsg = Utils.toErrorMessage(mainMsg, response); + + throw new ProjectError(errorMsg); + } + } + /** * Gets a list of the organizational projects for an issue that has the given {@link issueNumber}, - * in a repository with a name that matches the given {@link repoName}. + * in a repository with a name that matches the {@link ProjectClient}.{@link repoName}. * @param issueNumber The issue number. * @returns The list of organizational projects that the issue is assigned to. + * @throws The error {@link ProjectError} if an error occurs while getting the issues projects. */ public async getIssueProjects(issueNumber: number): Promise { - const funcName = "getIssueProjects"; - Guard.isLessThanOne(issueNumber, funcName); + Guard.isLessThanOne(issueNumber, "getIssueProjects"); - const query = createGetIssueProjectsQuery(this.ownerName, this.repoName, issueNumber); + const query = createGetIssueProjectsQuery(super.ownerName, super.repoName, issueNumber); const responseData = await this.executeQuery(query); + if (responseData.errors != undefined) { + const mainMsg = `The following errors occurred while getting the organizational projects for the issue '${issueNumber}'.`; + const errorMsg = Utils.toErrorMessage(mainMsg, responseData); + + throw new ProjectError(errorMsg); + } + return responseData.data.repository.issue.projectsV2.nodes; } @@ -99,14 +216,21 @@ export class ProjectClient extends GraphQlClient { * in a repository with a name that matches the given {@link repoName}. * @param prNumber The issue number. * @returns The list of organizational projects that the issue is assigned to. + * @throws The error {@link ProjectError} if an error occurs while getting the pull requests projects. */ public async getPullRequestProjects(prNumber: number): Promise { - const funcName = "getPullRequestProjects"; - Guard.isLessThanOne(prNumber, funcName); + Guard.isLessThanOne(prNumber, "getPullRequestProjects"); - const query = createGetPullRequestProjectsQuery(this.ownerName, this.repoName, prNumber); + const query = createGetPullRequestProjectsQuery(super.ownerName, super.repoName, prNumber); const responseData = await this.executeQuery(query); + if (responseData.errors != undefined) { + const mainMsg = `The following errors occurred while getting the organizational projects for the pull request '${prNumber}'.`; + const errorMsg = Utils.toErrorMessage(mainMsg, responseData); + + throw new ProjectError(errorMsg); + } + return responseData.data.repository.pullRequest.projectsV2.nodes; } } diff --git a/GitHubClients/PullRequestClient.ts b/GitHubClients/PullRequestClient.ts index 21863f5..c346a31 100644 --- a/GitHubClients/PullRequestClient.ts +++ b/GitHubClients/PullRequestClient.ts @@ -1,10 +1,11 @@ -import { Guard } from "core/Guard.ts"; -import { LabelClient } from "github/LabelClient.ts"; -import { PullRequestModel } from "models/PullRequestModel.ts"; -import { Utils } from "core/Utils.ts"; -import { GitHubHttpStatusCodes, IssueOrPRState, MergeState } from "core/Enums.ts"; -import { GitHubClient } from "core/GitHubClient.ts"; -import { IssueOrPRRequestData } from "core/IssueOrPRRequestData.ts"; +import { Guard } from "../core/Guard.ts"; +import { LabelClient } from "./LabelClient.ts"; +import { PullRequestModel } from "../core/Models/PullRequestModel.ts"; +import { Utils } from "../core/Utils.ts"; +import { GitHubHttpStatusCodes, IssueOrPRState, MergeState } from "../core/Enums.ts"; +import { GitHubClient } from "../core/GitHubClient.ts"; +import { IssueOrPRRequestData } from "../core/IssueOrPRRequestData.ts"; +import { PullRequestError } from "./Errors/PullRequestError.ts"; /** * Provides a client for interacting with pull requests. @@ -20,7 +21,7 @@ export class PullRequestClient extends GitHubClient { * @remarks If no token is provided, then the client will not be authenticated. */ constructor(ownerName: string, repoName: string, token?: string) { - const funcName = "TagClient.ctor"; + const funcName = "PullRequestClient.ctor"; Guard.isNothing(ownerName, funcName, "ownerName"); Guard.isNothing(repoName, funcName, "repoName"); @@ -33,6 +34,7 @@ export class PullRequestClient extends GitHubClient { * the given{@link PullRequestClient}.{@link repoName}. * @returns The pull request. * @remarks Does not require authentication. + * @throws The {@link PullRequestError} when something goes wrong with getting all of the pull requests. */ public async getAllOpenPullRequests(): Promise { return await this.getAllData(async (page, qtyPerPage) => { @@ -45,6 +47,7 @@ export class PullRequestClient extends GitHubClient { * given {@link PullRequestClient}.{@link repoName}. * @returns The pull request. * @remarks Does not require authentication. + * @throws The {@link PullRequestError} when something goes wrong with getting all of the pull requests. */ public async getAllClosedPullRequests(): Promise { return await this.getAllData(async (page, qtyPerPage) => { @@ -69,6 +72,7 @@ export class PullRequestClient extends GitHubClient { * The {@link page} value must be greater than 0. If less than 1, the value of 1 will be used. * The {@link qtyPerPage} value must be a value between 1 and 100. If less than 1, the value will * be set to 1, if greater than 100, the value of 100 will be used. + * @thrown The {@link PullRequestError} when something goes wrong with getting the pull requests. */ public async getPullRequests( page = 1, @@ -106,19 +110,17 @@ export class PullRequestClient extends GitHubClient { case GitHubHttpStatusCodes.MovedPermanently: case GitHubHttpStatusCodes.UnprocessableContent: case GitHubHttpStatusCodes.Unauthorized: { - let errorMsg = `An error occurred trying to get the pull requests for the repository '${this.repoName}'.`; - errorMsg += `\n\tError: ${response.status}(${response.statusText})`; - Utils.printAsGitHubError(errorMsg); - break; + const errorMsg = this.buildErrorMsg( + `An error occurred trying to get the pull requests for the repository '${this.repoName}'.`, + response); + + throw new PullRequestError(errorMsg); } case GitHubHttpStatusCodes.NotFound: { const errorMsg = `The organization '${this.ownerName}' or repository '${this.repoName}' does not exist.`; - Utils.printAsGitHubError(errorMsg); - break; + throw new PullRequestError(errorMsg); } } - - Deno.exit(1); } // Get all of the pull requests that are with any merge state @@ -148,6 +150,7 @@ export class PullRequestClient extends GitHubClient { * @param prNumber The number of the pull request. * @returns The labels for the pull request. * @remarks Does not require authentication. + * @throws The {@link PullRequestError} when something goes wrong with getting the labels. */ public async getLabels(prNumber: number): Promise { Guard.isLessThanOne(prNumber, "getLabels", "prNumber"); @@ -161,6 +164,7 @@ export class PullRequestClient extends GitHubClient { * @param prNumber The number of the pull request. * @returns The pull request. * @remarks Does not require authentication. + * @throws The {@link PullRequestError} when something goes wrong with getting a pull request. */ public async getPullRequest(prNumber: number): Promise { Guard.isLessThanOne(prNumber, "getPullRequest", "prNumber"); @@ -179,15 +183,11 @@ export class PullRequestClient extends GitHubClient { case GitHubHttpStatusCodes.Unauthorized: { let errorMsg = `An error occurred trying to get the pull request '${prNumber}'.`; errorMsg += `\n\tError '${response.status}(${response.statusText})'`; - Utils.printAsGitHubError(errorMsg); - break; + throw new PullRequestError(errorMsg); } case GitHubHttpStatusCodes.NotFound: - Utils.printAsGitHubError(`The pull request number '${prNumber}' does not exist.`); - break; + throw new PullRequestError(`The pull request number '${prNumber}' does not exist.`); } - - Deno.exit(1); } return await this.getResponseData(response); @@ -199,14 +199,14 @@ export class PullRequestClient extends GitHubClient { * @param prNumber The number of the pull request. * @param label The name of the label to add. * @remarks Requires authentication. + * @throws The {@link PullRequestError} when something goes wrong with adding a label. */ public async addLabel(prNumber: number, label: string): Promise { Guard.isLessThanOne(prNumber, "addLabel", "prNumber"); Guard.isNothing(label, "addLabel", "label"); if (!this.containsToken()) { - Utils.printAsGitHubError(`The request to add label '${label}' is forbidden. Check the auth token.`); - Deno.exit(1); + throw new PullRequestError(`The request to add label '${label}' is forbidden. Check the auth token.`); } // First check that the label trying to be added exists in the repo @@ -220,8 +220,7 @@ export class PullRequestClient extends GitHubClient { errorMsg += `\nRepo Labels: ${labelsUrl}`; errorMsg += `\nPull Request: ${prUrl}`; - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + throw new PullRequestError(errorMsg); } const prLabels: string[] = await this.getLabels(prNumber); @@ -240,17 +239,15 @@ export class PullRequestClient extends GitHubClient { case GitHubHttpStatusCodes.ServiceUnavailable: case GitHubHttpStatusCodes.Forbidden: case GitHubHttpStatusCodes.Unauthorized: { - let errorMsg = `An error occurred trying to add the label '${label}' to pull request '${prNumber}'.`; - errorMsg += `\n\tError: ${response.status}(${response.statusText})`; - Utils.printAsGitHubError(errorMsg); - break; + const errorMsg = this.buildErrorMsg( + `An error occurred trying to add the label '${label}' to pull request '${prNumber}'.`, + response); + + throw new PullRequestError(errorMsg); } case GitHubHttpStatusCodes.NotFound: - Utils.printAsGitHubError(`The pull request number '${prNumber}' does not exist.`); - break; + throw new PullRequestError(`The pull request number '${prNumber}' does not exist.`); } - - Deno.exit(1); } } @@ -259,6 +256,7 @@ export class PullRequestClient extends GitHubClient { * the given {@link PullRequestClient}.{@link repoName}. * @param prNumber The number of the pull request. * @returns True if the pull request exists, otherwise false. + * @throws The {@link PullRequestError} when something goes wrong with checking if a pull request exists. */ public async pullRequestExists(prNumber: number): Promise { Guard.isLessThanOne(prNumber, "pullRequestExists", "prNumber"); @@ -275,10 +273,11 @@ export class PullRequestClient extends GitHubClient { case GitHubHttpStatusCodes.InternalServerError: case GitHubHttpStatusCodes.ServiceUnavailable: case GitHubHttpStatusCodes.Unauthorized: { - let errorMsg = `An error occurred checking if pull request '${prNumber}' exists.`; - errorMsg = `\n\tError: ${response.status}(${response.statusText})`; - Utils.printAsGitHubError(errorMsg); - break; + const errorMsg = this.buildErrorMsg( + `An error occurred checking if pull request '${prNumber}' exists.`, + response); + + throw new PullRequestError(errorMsg); } case GitHubHttpStatusCodes.NotFound: return false; @@ -305,6 +304,7 @@ export class PullRequestClient extends GitHubClient { * in a repository with a name that matches the given {@link PullRequestClient}.{@link repoName}. * @param prNumber The pull request number. * @param prRequestData The data to update the pull request with. + * @throws The {@link PullRequestError} when something goes wrong with updating a pull request. */ public async updatePullRequest(prNumber: number, prRequestData: IssueOrPRRequestData): Promise { Guard.isLessThanOne(prNumber, "updatePullRequest", "prNumber"); @@ -312,8 +312,8 @@ export class PullRequestClient extends GitHubClient { const prDoesNotExist = !(await this.pullRequestExists(prNumber)); if (prDoesNotExist) { - Utils.printAsGitHubError(`A pull request with the number '${prNumber}' does not exist in the repo '${this.repoName}'.`); - Deno.exit(1); + const errorMsg = `A pull request with the number '${prNumber}' does not exist in the repo '${this.repoName}'.`; + throw new PullRequestError(errorMsg); } this.repoName = this.repoName.trim(); @@ -325,7 +325,7 @@ export class PullRequestClient extends GitHubClient { if (response.status != GitHubHttpStatusCodes.OK) { if (response.status === GitHubHttpStatusCodes.NotFound) { - Utils.printAsGitHubError(`An pull request with the number '${prNumber}' does not exist.`); + throw new PullRequestError(`An pull request with the number '${prNumber}' does not exist.`); } else { switch (response.status) { case GitHubHttpStatusCodes.MovedPermanently: @@ -334,16 +334,14 @@ export class PullRequestClient extends GitHubClient { case GitHubHttpStatusCodes.ServiceUnavailable: case GitHubHttpStatusCodes.Forbidden: case GitHubHttpStatusCodes.Unauthorized: { - let errorMsg = `An error occurred trying to update pull request '${prNumber}'.`; - errorMsg += `\n\t'Error: ${response.status}(${response.statusText})`; + const errorMsg = this.buildErrorMsg( + `An error occurred trying to update pull request '${prNumber}'.`, + response); - Utils.printAsGitHubError(errorMsg); - break; + throw new PullRequestError(errorMsg); } } } - - Deno.exit(1); } } @@ -353,10 +351,12 @@ export class PullRequestClient extends GitHubClient { * the given {@link PullRequestClient}.{@link repoName}. * @param prNumber The pull request number. * @param reviewer The reviewer to request. + * @throws The {@link PullRequestError} when something goes wrong with requesting a pull request reviewer. */ public async requestReviewer(prNumber: number, reviewer: string): Promise { - Guard.isLessThanOne(prNumber, "requestReviewer", "prNumber"); - Guard.isNothing(reviewer, "requestReviewer", "reviewer"); + const funcName = "requestReviewer"; + Guard.isLessThanOne(prNumber, funcName, "prNumber"); + Guard.isNothing(reviewer, funcName, "reviewer"); this.repoName = this.repoName.trim(); reviewer = reviewer.trim(); @@ -367,12 +367,12 @@ export class PullRequestClient extends GitHubClient { const response = await this.requestPOST(url, body); if (response.status != GitHubHttpStatusCodes.Created) { - let errorMsg = `An error occurred trying to request the reviewer '${reviewer}' for pull request '${prNumber}'.`; - errorMsg += `\n\t'Error: ${response.status}(${response.statusText})`; - errorMsg += `\n\t'PR: ${Utils.buildPullRequestUrl(this.ownerName, this.repoName, prNumber)}'`; + const errorMsg = this.buildErrorMsg( + `An error occurred trying to request the reviewer '${reviewer}' for pull request '${prNumber}'.` + + `\n\t'PR: ${Utils.buildPullRequestUrl(this.ownerName, this.repoName, prNumber)}'`, + response); - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + throw new PullRequestError(errorMsg); } } @@ -381,6 +381,7 @@ export class PullRequestClient extends GitHubClient { * that matches the given {@link PullRequestClient}.{@link repoName}. * @param prNumber The pull request number. * @returns True if the pull request exists and is open, otherwise false. + * @throws The {@link PullRequestError} when something goes wrong with checking if a closed pull request exists. */ public async closedPullRequestExists(prNumber: number): Promise { Guard.isLessThanOne(prNumber, "closedPullRequestExists", "issueNumber"); @@ -398,6 +399,7 @@ export class PullRequestClient extends GitHubClient { * @param maintainerCanModify The value indicating whether or not maintainers can modify the pull request. * @param isDraft The value indicating whether or not the pull request is a draft pull request. * @returns The newly created pull request. + * @throws The {@link PullRequestError} when something goes wrong with creating a pull request. */ public async createPullRequest( title: string, @@ -427,9 +429,10 @@ export class PullRequestClient extends GitHubClient { const response = await this.requestPOST(url, JSON.stringify(body)); if (response.status != GitHubHttpStatusCodes.Created) { - const errorMsg = `Error: ${response.status}(${response.statusText})`; - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + const errorMsg = this.buildErrorMsg("There was an issue creating the pull request.", + response); + + throw new PullRequestError(errorMsg); } const newPullRequest = await response.json() as PullRequestModel; @@ -442,12 +445,13 @@ export class PullRequestClient extends GitHubClient { * repository with a name that matches the given {@link PullRequestClient}.{@link repoName}. * @param prNumber The number of the issue. * @returns True if the pull request exists, otherwise false. + * @throws The {@link PullRequestError} when something goes wrong with checking if an open or closed pull request exists. */ private async openOrClosedPullRequestExists( prNumber: number, state: IssueOrPRState, ): Promise { - Guard.isLessThanOne(prNumber, "openOrClosedPullRequestExists", "issueNumber"); + Guard.isLessThanOne(prNumber, "openOrClosedPullRequestExists", "prNumber"); const issues = await this.getAllDataUntil( async (page: number, qtyPerPage?: number) => { diff --git a/GitHubClients/ReleaseClient.ts b/GitHubClients/ReleaseClient.ts index d9cc8e6..d331683 100644 --- a/GitHubClients/ReleaseClient.ts +++ b/GitHubClients/ReleaseClient.ts @@ -1,8 +1,9 @@ -import { GitHubHttpStatusCodes } from "core/Enums.ts"; -import { GitHubClient } from "core/GitHubClient.ts"; -import { Guard } from "core/Guard.ts"; -import { ReleaseModel } from "core/Models/ReleaseModel.ts"; -import { Utils } from "core/Utils.ts"; +import { GitHubHttpStatusCodes } from "../core/Enums.ts"; +import { GitHubClient } from "../core/GitHubClient.ts"; +import { Guard } from "../core/Guard.ts"; +import { ReleaseModel } from "../core/Models/ReleaseModel.ts"; +import { Utils } from "../core/Utils.ts"; +import { ReleaseError } from "./Errors/ReleaseError.ts"; /** * Provides a client for interacting with GitHub releases. @@ -35,6 +36,7 @@ export class ReleaseClient extends GitHubClient { * The {@link page} value must be greater than 0. If less than 1, the value of 1 will be used. * The {@link qtyPerPage} value must be a value between 1 and 100. If less than 1, the value will * be set to 1, if greater than 100, the value will be set to 100. + * @throws The {@link ReleaseError} if there was an issue getting the releases. */ public async getReleases(page: number, qtyPerPage: number): Promise<[ReleaseModel[], Response]> { page = page < 1 ? 1 : page; @@ -50,9 +52,7 @@ export class ReleaseClient extends GitHubClient { let errorMsg = `The releases for the repository owner '${this.ownerName}'`; errorMsg += ` and for the repository '${this.repoName}' could not be found.`; - Utils.printAsGitHubError(errorMsg); - - Deno.exit(1); + throw new ReleaseError(errorMsg); } return [ await this.getResponseData(response), response]; @@ -63,6 +63,7 @@ export class ReleaseClient extends GitHubClient { * where the tag name matches the given {@link tagName}. * @param tagName The tag of the release to get. * @returns The release for the given repository and tag. + * @throws The {@link ReleaseError} if there was an issue getting the release. */ public async getReleaseByTag(tagName: string): Promise { const funcName = "getReleaseByTag"; @@ -86,8 +87,7 @@ export class ReleaseClient extends GitHubClient { if (foundRelease === undefined) { const errorMsg = `A release with the tag '${tagName}' for the repository '${this.repoName}' could not be found.`; - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + throw new ReleaseError(errorMsg); } return foundRelease; @@ -98,6 +98,7 @@ export class ReleaseClient extends GitHubClient { * where the name that the name of the release matches the given {@link releaseName}. * @param releaseName The name of the release to get. * @returns The release for the given repository and name. + * @throws The {@link ReleaseError} if there was an issue getting the release. */ public async getReleaseByName(releaseName: string): Promise { const funcName = "getReleaseByName"; @@ -121,8 +122,7 @@ export class ReleaseClient extends GitHubClient { if (foundRelease === undefined) { const errorMsg = `A release with the name '${releaseName}' for the repository '${this.repoName}' could not be found.`; - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + throw new ReleaseError(errorMsg); } return foundRelease; @@ -133,6 +133,7 @@ export class ReleaseClient extends GitHubClient { * for a repository with a name that matches the given {@link ReleaseClient}.{@link this.repoName}. * @param tagName The name of the tag tied to the release. * @returns The release for the given repository and name. + * @throws The {@link ReleaseError} if there was an issue checking if the release exists. */ public async releaseExists(tagName: string): Promise { const funcName = "releaseExists"; diff --git a/GitHubClients/RepoClient.ts b/GitHubClients/RepoClient.ts index 5035ad8..341ec7c 100644 --- a/GitHubClients/RepoClient.ts +++ b/GitHubClients/RepoClient.ts @@ -1,12 +1,13 @@ -import { decodeBase64, encodeBase64 } from "std/encoding/base64.ts"; -import { GitHubHttpStatusCodes } from "core/Enums.ts"; -import { GitHubClient } from "core/GitHubClient.ts"; -import { Guard } from "core/Guard.ts"; -import { FileContentModel } from "models/FileContentModel.ts"; -import { RepoModel } from "models/RepoModel.ts"; -import { Utils } from "core/Utils.ts"; -import { GitHubVarModel } from "models/GitHubVarModel.ts"; -import { GitHubVariablesModel } from "models/GitHubVariablesModel.ts"; +import { decodeBase64, encodeBase64 } from "../deps.ts"; +import { GitHubHttpStatusCodes } from "../core/Enums.ts"; +import { GitHubClient } from "../core/GitHubClient.ts"; +import { Guard } from "../core/Guard.ts"; +import { FileContentModel } from "../core/Models/FileContentModel.ts"; +import { RepoModel } from "../core/Models/RepoModel.ts"; +import { Utils } from "../core/Utils.ts"; +import { GitHubVarModel } from "../core/Models/GitHubVarModel.ts"; +import { GitHubVariablesModel } from "../core/Models/GitHubVariablesModel.ts"; +import { RepoError } from "./Errors/RepoError.ts"; /** * Provides a client for interacting with GitHub repositories. @@ -14,22 +15,23 @@ import { GitHubVariablesModel } from "models/GitHubVariablesModel.ts"; export class RepoClient extends GitHubClient { /** * Initializes a new instance of the {@link RepoClient} class. - * @param repoOwner The name of the owner of a repository. + * @param ownerName The name of the owner of a repository. * @param repoName The name of a repository. * @param token The GitHub token to use for authentication. */ - constructor(repoOwner: string, repoName: string, token?: string) { - super(repoOwner, repoName, token); + constructor(ownerName: string, repoName: string, token?: string) { + super(ownerName, repoName, token); } /** - * Gets information about a repository with a name that matches the given {@link RepoClient}/{@link repoName}. - * @returns A repository. + * Gets information about a repository with a name that matches the given {@link RepoClient}.{@link repoName}. + * @returns A repository information. + * @throws The {@link RepoError} if the repository does not exist. */ - public async getRepoByName(): Promise { + public async getRepo(): Promise { const foundRepos = await this.getAllDataUntil( async (page, qtyPerPage) => { - return await this.getUserRepos(page, qtyPerPage ?? 100); + return await this.getOwnerRepos(page, qtyPerPage ?? 100); }, 1, // Start page 100, // Qty per page @@ -41,23 +43,24 @@ export class RepoClient extends GitHubClient { const foundRepo: RepoModel | undefined = foundRepos.find((repo) => repo.name.trim().toLowerCase() === this.repoName); if (foundRepo === undefined) { - Utils.printAsGitHubError(`The repository '${this.repoName}' was not found.`); - Deno.exit(1); + throw new RepoError(`The repository '${this.repoName}' was not found.`); } return foundRepo; } /** - * Gets a {@link page} of repositories with a quantity that matches the given {@link qtyPerPage}. + * Gets a {@link page} of repositories owned by the currently set {@link RepoClient}.{@link ownerName} with a quantity that + * matches the given {@link qtyPerPage}. * @param page The page number of the results to get. * @param qtyPerPage The quantity of results to get per page. * @returns A list of repositories. * @remarks The {@link page} value must be greater than 0. If less than 1, the value of 1 will be used. * The {@link qtyPerPage} value must be a value between 1 and 100. If less than 1, the value will * be set to 1, if greater than 100, the value will be set to 100. + * @throws The {@link RepoError} if the repository owner does not exist. */ - public async getUserRepos(page: number, qtyPerPage: number): Promise<[RepoModel[], Response]> { + public async getOwnerRepos(page: number, qtyPerPage: number): Promise<[RepoModel[], Response]> { page = page < 1 ? 1 : page; qtyPerPage = Utils.clamp(qtyPerPage, 1, 100); @@ -68,11 +71,11 @@ export class RepoClient extends GitHubClient { // If there is an error if (response.status === GitHubHttpStatusCodes.NotFound) { - let errorMsg = `Not found. Check that the repository owner '${this.ownerName}' is a valid repository owner.`; - errorMsg += `\nError: ${response.status}(${response.statusText})`; - Utils.printAsGitHubError(errorMsg); + const errorMsg = this.buildErrorMsg( + `Not found. Check that the repository owner '${this.ownerName}' is a valid repository owner.`, + response); - Deno.exit(1); + throw new RepoError(errorMsg); } return [ await this.getResponseData(response), response]; @@ -81,6 +84,7 @@ export class RepoClient extends GitHubClient { /** * Checks if a repository with a name that matches the {@link RepoClient}.{@link repoName} exists. * @returns True if the repo exists; otherwise, false. + * @throws The {@link RepoError} if there was a problem checking if the repository exists. */ public async exists(): Promise { const url = `${this.baseUrl}/repos/${this.ownerName}/${this.repoName}`; @@ -95,9 +99,11 @@ export class RepoClient extends GitHubClient { switch (response.status) { case GitHubHttpStatusCodes.MovedPermanently: case GitHubHttpStatusCodes.Forbidden: { - const errorMsg = `Error: ${response.status} (${response.statusText})`; - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + const errorMsg = this.buildErrorMsg( + `There was a problem checking if the repository exists.`, + response); + + throw new RepoError(errorMsg); } } @@ -110,21 +116,14 @@ export class RepoClient extends GitHubClient { * @param relativeFilePath The relative path of the file. * @returns The content of the file. * @remarks The {@link relativeFilePath} is relative to the root of the repository. + * @throws The {@link RepoError} if there was a problem getting the file content. */ public async getFileContent(branchName: string, relativeFilePath: string): Promise { const funcName = "getFileContent"; Guard.isNothing(relativeFilePath, funcName, "relativeFilePath"); - relativeFilePath = relativeFilePath.trim(); - relativeFilePath = relativeFilePath.startsWith("/") ? relativeFilePath : `/${relativeFilePath}`; - const fileContentModel = await this.getFileContentResult(branchName, relativeFilePath); - if (fileContentModel === undefined || fileContentModel === null) { - Utils.printAsGitHubError("Error: 404(Not Found)"); - Deno.exit(1); - } - const decodedContent = decodeBase64(fileContentModel.content); // Return the file content after it has been decoded from base64 @@ -140,6 +139,7 @@ export class RepoClient extends GitHubClient { * @param relativeFilePath The relative path of the file. * @returns True if the file exists; otherwise, false. * @remarks The {@link relativeFilePath} is relative to the root of the repository. + * @throws The {@link RepoError} if there was problem checking if the file exists. */ public async fileExists(branchName: string, relativeFilePath: string): Promise { const funcName = "fileExists"; @@ -154,8 +154,17 @@ export class RepoClient extends GitHubClient { const response: Response = await this.requestGET(url); - if (response.status === GitHubHttpStatusCodes.NotFound) { - return false; + if (response.status != GitHubHttpStatusCodes.OK) { + if (response.status === GitHubHttpStatusCodes.NotFound) { + return false; + } else { + const errorMsg = this.buildErrorMsg( + `There was a problem checking if the file '${relativeFilePath}' exists in the` + + ` repository '${this.repoName}' in the branch '${branchName}'.`, + response); + + throw new RepoError(errorMsg); + } } return true; @@ -164,6 +173,7 @@ export class RepoClient extends GitHubClient { /** * Gets a list of all the variables for a repository with a name that matches the given {@link RepoClient}.{@link repoName}. * @returns A list of all repositories variables. + * @throws The {@link RepoError} if there was a problem getting the variables. */ public async getVariables(): Promise { return await this.getAllData(async (page: number, qtyPerPage?: number) => { @@ -173,11 +183,11 @@ export class RepoClient extends GitHubClient { const response = await this.requestGET(url); if (response.status != GitHubHttpStatusCodes.OK) { - let errorMsg = `An error occurred when getting the variables for the organization '${this.ownerName}'.`; - errorMsg += `\nError: ${response.status}(${response.statusText})`; + const errorMsg = this.buildErrorMsg( + `An error occurred when getting the variables for the owner '${this.ownerName}'.`, + response); - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + throw new RepoError(errorMsg); } const vars = await this.getResponseData(response); @@ -191,15 +201,15 @@ export class RepoClient extends GitHubClient { * in a repository with a name that matches the given {@link RepoClient}.{@link repoName}. * @param variableName The name of the variable. * @returns True if the variable exists; otherwise, false. + * @throws The {@link RepoError} if there was a problem checking if the variable exists. */ public async repoVariableExists(variableName: string): Promise { Guard.isNothing(variableName, "repoVariableExists", "variableName"); const variables = await this.getVariables(); - const variable = variables.find((v) => v.name === variableName); - return variable !== undefined && variable !== null; + return variable != undefined && variable != null; } /** @@ -207,6 +217,7 @@ export class RepoClient extends GitHubClient { * the given {@link variableName} in a repository with a name that matches the given {@link RepoClient}.{@link repoName}. * @param variableName The name of the variable. * @param variableValue The value of the variable. + * @throws The {@link RepoError} if there was a problem updating the variable or if the variable does not exist. */ public async updateVariable(variableName: string, variableValue: string): Promise { const funcName = "updateVariable"; @@ -214,8 +225,7 @@ export class RepoClient extends GitHubClient { Guard.isNothing(variableValue, funcName, "variableValue"); if (!(await this.repoVariableExists(variableName))) { - Utils.printAsGitHubError(`The variable '${variableName}' does not exist for the repository '${this.repoName}'.`); - Deno.exit(1); + throw new RepoError(`The variable '${variableName}' does not exist for the repository '${this.repoName}'.`); } const url = `${this.baseUrl}/repos/${this.ownerName}/${this.repoName}/actions/variables/${variableName}`; @@ -228,11 +238,12 @@ export class RepoClient extends GitHubClient { const response = await this.requestPATCH(url, JSON.stringify(body)); if (response.status != GitHubHttpStatusCodes.NoContent) { - let errorMsg = `An error occurred when updating the variable '${variableName}' for the repository '${this.repoName}'.`; - errorMsg += `\nError: ${response.status}(${response.statusText})`; + const errorMsg = this.buildErrorMsg( + `An error occurred when updating the variable '${variableName}'` + + ` for the repository '${this.repoName}'.`, + response); - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + throw new RepoError(errorMsg); } } @@ -245,6 +256,7 @@ export class RepoClient extends GitHubClient { * @param relativeFilePath The relative path of where to add the file. * @param fileContent The content of the file. * @param commitMessage The commit message. + * @throws The {@link RepoError} if there was a problem creating the file or if the file already exists. */ public async createFile( branchName: string, @@ -261,6 +273,11 @@ export class RepoClient extends GitHubClient { relativeFilePath = Utils.normalizePath(relativeFilePath); Utils.trimAllStartingValue("/", relativeFilePath); + if (await this.fileExists(branchName, relativeFilePath)) { + const errorMsg = `The file '${relativeFilePath}' already exists in the repository '${this.repoName}'.`; + throw new RepoError(errorMsg); + } + const body = { message: commitMessage, content: encodeBase64(fileContent), @@ -271,12 +288,12 @@ export class RepoClient extends GitHubClient { const response = await this.requestPUT(url, JSON.stringify(body)); if (response.status != GitHubHttpStatusCodes.OK && response.status != GitHubHttpStatusCodes.Created) { - let errorMsg = `An error occurred when creating the file '${relativeFilePath}' in the repository '${this.repoName}'`; - errorMsg += ` for branch '${branchName}'.`; - errorMsg += `\nError: ${response.status}(${response.statusText})`; + const errorMsg = this.buildErrorMsg( + `An error occurred when creating the file '${relativeFilePath}' in the repository '${this.repoName}'` + + ` for branch '${branchName}'.`, + response); - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + throw new RepoError(errorMsg); } } @@ -289,6 +306,7 @@ export class RepoClient extends GitHubClient { * @param fileContent The content of the file. * @param commitMessage The commit message. * @remarks If the file does not exist, an error will be thrown. + * @throws The {@link RepoError} if there was a problem updating the file or if the file does not exist. */ public async updateFile( branchName: string, @@ -305,15 +323,13 @@ export class RepoClient extends GitHubClient { relativeFilePath = Utils.normalizePath(relativeFilePath); Utils.trimAllStartingValue("/", relativeFilePath); - const fileContentModel = await this.getFileContentResult(branchName, relativeFilePath); - - if (fileContentModel === undefined || fileContentModel === null) { - let errorMsg = `The file '${relativeFilePath}' does not exist in the repository`; - errorMsg += `\n '${this.repoName}', in branch '${branchName}'.`; - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + if (await this.fileExists(branchName, relativeFilePath)) { + const errorMsg = `The file '${relativeFilePath}' already exists in the repository '${this.repoName}'.`; + throw new RepoError(errorMsg); } + const fileContentModel = await this.getFileContentResult(branchName, relativeFilePath); + const body = { message: commitMessage, content: encodeBase64(fileContent), @@ -325,26 +341,28 @@ export class RepoClient extends GitHubClient { const response = await this.requestPUT(url, JSON.stringify(body)); if (response.status != GitHubHttpStatusCodes.OK && response.status != GitHubHttpStatusCodes.Created) { - let errorMsg = `An error occurred when creating the file '${relativeFilePath}' in the repository '${this.repoName}'`; - errorMsg += ` for branch '${branchName}'.`; - errorMsg += `\nError: ${response.status}(${response.statusText})`; + const errorMsg = this.buildErrorMsg( + `An error occurred when creating the file '${relativeFilePath}' in the repository '${this.repoName}'` + + ` for branch '${branchName}'.`, + response); - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + throw new RepoError(errorMsg); } } /** * Gets the content of a file in a repository with a name that matches the given {@link RepoClient}.{@link repoName}, - * on a branch with a name that matches the given {@link branchName} at the * given {@link relativeFilePath}. + * on a branch with a name that matches the given {@link branchName} at the given {@link relativeFilePath}. + * @param branchName The name of the branch. * @param relativeFilePath The relative path of the file. * @returns The content of the file and a boolean flag indicating whether or not the file exists. * @remarks The {@link relativeFilePath} is relative to the root of the repository. + * @throws The {@link RepoError} if there was a problem getting the file content. */ private async getFileContentResult( branchName: string, relativeFilePath: string, - ): Promise { + ): Promise { const funcName = "getFileContentWithResult"; Guard.isNothing(branchName, funcName, "branchName"); Guard.isNothing(relativeFilePath, funcName, "relativeFilePath"); @@ -359,11 +377,12 @@ export class RepoClient extends GitHubClient { switch (response.status) { case GitHubHttpStatusCodes.NotFound: - return null; case GitHubHttpStatusCodes.TemporaryRedirect: - case GitHubHttpStatusCodes.Forbidden: - Utils.printAsGitHubError(`Error: ${response.status} (${response.statusText})`); - Deno.exit(1); + case GitHubHttpStatusCodes.Forbidden: { + const errorMsg = this.buildErrorMsg("There was an issue getting the file content", response); + + throw new RepoError(errorMsg); + } } const responseData = await this.getResponseData(response); diff --git a/GitHubClients/TagClient.ts b/GitHubClients/TagClient.ts index 94ce8d2..9d77302 100644 --- a/GitHubClients/TagClient.ts +++ b/GitHubClients/TagClient.ts @@ -1,8 +1,9 @@ -import { Guard } from "core/Guard.ts"; -import { TagModel } from "core/Models/TagModel.ts"; -import { Utils } from "core/Utils.ts"; -import { GitHubHttpStatusCodes } from "core/Enums.ts"; -import { GitHubClient } from "core/GitHubClient.ts"; +import { Guard } from "../core/Guard.ts"; +import { TagModel } from "../core/Models/TagModel.ts"; +import { Utils } from "../core/Utils.ts"; +import { GitHubHttpStatusCodes } from "../core/Enums.ts"; +import { GitHubClient } from "../core/GitHubClient.ts"; +import { TagError } from "./Errors/TagError.ts"; /** * Provides a client for interacting with GitHub GIT tags. @@ -32,10 +33,9 @@ export class TagClient extends GitHubClient { * The {@link page} value must be greater than 0. If less than 1, the value of 1 will be used. * The {@link qtyPerPage} value must be a value between 1 and 100. If less than 1, the value will * be set to 1, if greater than 100, the value will be set to 100. + * @throws The {@link TagError} if there was an issue getting the tags. */ public async getTags(page: number, qtyPerPage: number): Promise<[TagModel[], Response]> { - Guard.isNothing(this.repoName, "getTags", "repoName"); - page = page < 1 ? 1 : page; qtyPerPage = Utils.clamp(qtyPerPage, 1, 100); @@ -46,8 +46,9 @@ export class TagClient extends GitHubClient { // If there is an error if (response.status === GitHubHttpStatusCodes.NotFound) { - Utils.printAsGitHubError(`${response.status} - ${response.statusText}`); - Deno.exit(1); + const errorMsg = this.buildErrorMsg("There was an issue getting the tags.", response); + + throw new TagError(errorMsg); } return [ await this.getResponseData(response), response]; @@ -56,6 +57,7 @@ export class TagClient extends GitHubClient { /** * Gets all of the tags in a repository with a name that matches the {@link TagClient}.{@link repoName}. * @returns All of the tags. + * @throws The {@link TagError} if there was an issue getting all of the tags. */ public async getAllTags(): Promise { const result: TagModel[] = []; @@ -75,10 +77,10 @@ export class TagClient extends GitHubClient { * Gets a tag with the given {@link tagName} in a repository with a name that matches the {@link TagClient}.{@link repoName}. * @param tagName The name of the tag. * @returns Returns the tag with the given name. + * @throws The {@link TagError} if there was an issue getting the tag. */ public async getTagByName(tagName: string): Promise { - const funcName = "getTagByName"; - Guard.isNothing(tagName, funcName, "tagName"); + Guard.isNothing(tagName, "getTagByName", "tagName"); tagName = tagName.trim(); @@ -96,8 +98,7 @@ export class TagClient extends GitHubClient { const foundTag = foundTags.find((tag: TagModel) => tag.name.trim() === tagName); if (foundTag === undefined) { - Utils.printAsGitHubError(`The tag '${tagName}' could not be found.`); - Deno.exit(1); + throw new TagError(`The tag '${tagName}' could not be found.`); } return foundTag; @@ -107,10 +108,10 @@ export class TagClient extends GitHubClient { * Searches for a tag with the given {@link tagName} in a repository that matches the {@link TagClient}.{@link repoName}. * @param tagName The name of the tag. * @returns True if the tag exists, false otherwise. + * @throws The {@link TagError} if there was an issue checking if the tag exists. */ public async tagExists(tagName: string): Promise { - const funcName = "tagExists"; - Guard.isNothing(tagName, funcName, "tagName"); + Guard.isNothing(tagName, "tagExists", "tagName"); tagName = tagName.trim(); diff --git a/GitHubClients/UsersClient.ts b/GitHubClients/UsersClient.ts index e07c1f2..288bd1d 100644 --- a/GitHubClients/UsersClient.ts +++ b/GitHubClients/UsersClient.ts @@ -1,8 +1,8 @@ -import { Guard } from "core/Guard.ts"; -import { Utils } from "core/Utils.ts"; -import { GitHubHttpStatusCodes } from "core/Enums.ts"; -import { GitHubClient } from "core/GitHubClient.ts"; -import { UserModel } from "core/Models/UserModel.ts"; +import { Guard } from "../core/Guard.ts"; +import { GitHubHttpStatusCodes } from "../core/Enums.ts"; +import { GitHubClient } from "../core/GitHubClient.ts"; +import { UserModel } from "../core/Models/UserModel.ts"; +import { UsersError } from "./Errors/UsersError.ts"; /** * Provides a client for interacting with users. @@ -10,17 +10,20 @@ import { UserModel } from "core/Models/UserModel.ts"; export class UsersClient extends GitHubClient { /** * Initializes a new instance of the {@link UsersClient} class. + * @param ownerName The name of the owner of a repository. + * @param repoName The name of a repository. * @param token The GitHub token to use for authentication. * @remarks If no token is provided, then the client will not be authenticated. */ - constructor(token?: string) { - super("", "", token); + constructor(ownerName: string, repoName: string, token?: string) { + super(ownerName, repoName, token); } /** * Gets a user that matches the given {@link userName}. * @param userName The name of the user. * @returns The user. + * @throws The {@link UsersError} if there was an issue getting the user. */ public async getUser(userName: string): Promise { Guard.isNothing(userName, "getIssue", "repoName"); @@ -30,10 +33,13 @@ export class UsersClient extends GitHubClient { const response: Response = await this.requestGET(url); - // If there is an error - if (response.status === GitHubHttpStatusCodes.NotFound) { - Utils.printAsGitHubError(`The user '${userName}' does not exist.`); - Deno.exit(1); + if (response.status != GitHubHttpStatusCodes.OK) { + // If there is an error + if (response.status === GitHubHttpStatusCodes.NotFound) { + throw new UsersError(`The user '${userName}' does not exist.`, response.status); + } else { + throw new UsersError(`There was an issue getting the user '${userName}'.`, response.status); + } } return await this.getResponseData(response); @@ -43,6 +49,7 @@ export class UsersClient extends GitHubClient { * Returns a value indicating whether or not a users exists with the given {@link userName}. * @param userName The user's name. * @returns True if the user exists, otherwise false. + * @throws The {@link UsersError} if there was an issue checking if the user exists. */ public async userExists(userName: string): Promise { Guard.isNothing(userName, "userExists", "issueNumber"); @@ -52,11 +59,14 @@ export class UsersClient extends GitHubClient { const response: Response = await this.requestGET(url); - // If there is an error - if (response.status === GitHubHttpStatusCodes.NotFound) { - return false; - } else { - return true; + if (response.status != GitHubHttpStatusCodes.OK) { + if (response.status === GitHubHttpStatusCodes.NotFound) { + return false; + } else { + throw new UsersError(`There was an issue checking if the user '${userName}' exists.`, response.status); + } } + + return true; } } diff --git a/GitHubClients/WorkflowClient.ts b/GitHubClients/WorkflowClient.ts index 23c551f..ec36586 100644 --- a/GitHubClients/WorkflowClient.ts +++ b/GitHubClients/WorkflowClient.ts @@ -1,11 +1,12 @@ -import { Guard } from "core/Guard.ts"; -import { Utils } from "core/Utils.ts"; -import { GitHubHttpStatusCodes, WorkflowEvent, WorkflowRunStatus } from "core/Enums.ts"; -import { GitHubClient } from "core/GitHubClient.ts"; -import { WorkflowRunModel } from "core/Models/WorkflowRunModel.ts"; -import { WorkflowRunsModel } from "core/Models/WorkflowRunsModel.ts"; -import { AnyBranch } from "core/Types.ts"; -import { GithubResponse } from "github/GithubResponse.ts"; +import { Guard } from "../core/Guard.ts"; +import { Utils } from "../core/Utils.ts"; +import { GitHubHttpStatusCodes, WorkflowEvent, WorkflowRunStatus } from "../core/Enums.ts"; +import { GitHubClient } from "../core/GitHubClient.ts"; +import { WorkflowRunModel } from "../core/Models/WorkflowRunModel.ts"; +import { WorkflowRunsModel } from "../core/Models/WorkflowRunsModel.ts"; +import { AnyBranch } from "../core/Types.ts"; +import { GithubResponse } from "../GitHubClients/GithubResponse.ts"; +import { WorkflowError } from "./Errors/WorkflowError.ts"; /** * Provides a client for interacting with workflow runs. @@ -21,10 +22,10 @@ export class WorkflowClient extends GitHubClient { * @remarks If no token is provided, then the client will not be authenticated. */ constructor(ownerName: string, repoName: string, token?: string) { - const funcName = "ProjectClient.ctor"; + const funcName = "WorkflowClient.ctor"; Guard.isNothing(ownerName, funcName, "ownerName"); Guard.isNothing(repoName, funcName, "repoName"); - + super(ownerName, repoName, token); } @@ -41,6 +42,7 @@ export class WorkflowClient extends GitHubClient { * The {@link page} value must be greater than 0. If less than 1, the value of 1 will be used. * The {@link qtyPerPage} value must be a value between 1 and 100. If less than 1, the value will * be set to 1, if greater than 100, the value will be set to 100. + * @throws The {@link WorkflowError} if there was an issue getting the workflow runs. */ public async getWorkflowRuns( branch: string | null | AnyBranch, @@ -64,10 +66,12 @@ export class WorkflowClient extends GitHubClient { // If there is an error if (response.status != GitHubHttpStatusCodes.OK) { - let errorMsg = `An error occurred trying to get the workflow runs for the repository '${this.repoName}'.`; - errorMsg = `\n\tError: ${response.status}(${response.statusText})`; - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + const errorMsg = this.buildErrorMsg( + `An error occurred trying to get the workflow runs for the repository '${this.repoName}'.`, + response, + ); + + throw new WorkflowError(errorMsg); } const workflowRuns: WorkflowRunsModel = await this.getResponseData(response); @@ -78,6 +82,7 @@ export class WorkflowClient extends GitHubClient { /** * Gets a list of all the workflow runs for a repository with a name that matches the {@link WorkflowClient}.{@link repoName}. * @returns The list of workflow runs. + * @throws The {@link WorkflowError} if there was an issue getting all of the workflow runs. */ public async getAllWorkflowRuns(): Promise { Guard.isNothing(this.repoName, "getAllWorkflowRuns", "repoName"); @@ -100,13 +105,13 @@ export class WorkflowClient extends GitHubClient { * @param event The event that triggered the workflow runs. * @returns The workflow runs. * @remarks Does not require authentication. + * @throws The {@link WorkflowError} if there was an issue getting the completed workflow runs. */ public async getCompletedWorkflowRunsByBranch( branch: string, event: WorkflowEvent, ): Promise { - const funcName = "getCompletedWorkflowRunsByBranch"; - Guard.isNothing(branch, funcName, "branch"); + Guard.isNothing(branch, "getCompletedWorkflowRunsByBranch", "branch"); branch = branch.trim(); @@ -123,6 +128,7 @@ export class WorkflowClient extends GitHubClient { * @param event The event that triggered the workflow runs. * @returns The workflow runs. * @remarks Does not require authentication. + * @throws The {@link WorkflowError} if there was an issue getting the completed workflow runs. */ public async getCompletedWorkflowRuns(event: WorkflowEvent): Promise { const result = await this.getAllData(async (page: number, qtyPerPage?: number) => { @@ -140,13 +146,13 @@ export class WorkflowClient extends GitHubClient { * @param event The event that triggered the workflow runs. * @returns The workflow runs. * @remarks Does not require authentication. + * @throws The {@link WorkflowError} if there was an issue getting the failed workflow runs. */ public async getFailedWorkflowRunsByBranch( branch: string, event: WorkflowEvent, ): Promise { - const funcName = "getFailedWorkflowRunsByBranch"; - Guard.isNothing(branch, funcName, "branch"); + Guard.isNothing(branch, "getFailedWorkflowRunsByBranch", "branch"); branch = branch.trim(); @@ -163,6 +169,7 @@ export class WorkflowClient extends GitHubClient { * @param event The event that triggered the workflow runs. * @returns The workflow runs. * @remarks Does not require authentication. + * @throws The {@link WorkflowError} if there was an issue getting the failed workflow runs. */ public async getFailedWorkflowRuns(event: WorkflowEvent): Promise { Guard.isNothing("getFailedWorkflowRuns", "repoName"); @@ -182,6 +189,7 @@ export class WorkflowClient extends GitHubClient { * @param endDate The end date of when the workflow run was created. * @returns The workflow runs. * @remarks Does not require authentication. + * @throws The {@link WorkflowError} if there was an issue getting the workflow runs. */ public async getWorkflowRunsBetweenDates(startDate: Date, endDate: Date): Promise { const result = await this.getAllFilteredData( @@ -215,6 +223,7 @@ export class WorkflowClient extends GitHubClient { * the {@link WorkflowClient}.{@link repoName}. * @param title The title of the workflow runs. * @returns The workflow runs. + * @throws The {@link WorkflowError} if there was an issue getting the workflow runs. */ public async getAllWorkflowRunsByTitle(title: string): Promise { const funcName = "getAllWorkflowRunsByTitle"; @@ -245,10 +254,10 @@ export class WorkflowClient extends GitHubClient { * the {@link WorkflowClient}.{@link repoName}. * @param title The title of the workflow run. * @returns The workflow run. + * @throws The {@link WorkflowError} if there was an issue getting the workflow run. */ public async getWorkflowRunByTitle(title: string): Promise { - const funcName = "getWorkflowRunByTitle"; - Guard.isNothing(title, funcName, "title"); + Guard.isNothing(title, "getWorkflowRunByTitle", "title"); title = title.trim(); @@ -272,8 +281,7 @@ export class WorkflowClient extends GitHubClient { const workflowRun = workflowRuns.find((run) => run.display_title.trim() === title); if (workflowRun === undefined) { - Utils.printAsGitHubError(`A workflow run with the title '${title}' was not found.`); - Deno.exit(1); + throw new WorkflowError(`A workflow run with the title '${title}' was not found.`); } return workflowRun; @@ -285,10 +293,10 @@ export class WorkflowClient extends GitHubClient { * @param prNumber The number of the pull request. * @returns The workflow runs for a pull request number that matches the given {@link prNumber}. * @remarks Does not require authentication. + * @throws The {@link WorkflowError} if there was an issue getting the workflow runs. */ public async getWorkflowRunsForPR(prNumber: number): Promise { - const funcName = "getWorkflowRunsForPR"; - Guard.isLessThanOne(prNumber, funcName, "prNumber"); + Guard.isLessThanOne(prNumber, "getWorkflowRunsForPR", "prNumber"); const result = await this.getAllDataUntil( async (page: number, qtyPerPage?: number) => { @@ -320,9 +328,9 @@ export class WorkflowClient extends GitHubClient { * Gets all of the workflow runs for pull requests for a repository with a name that matches the * {@link WorkflowClient}.{@link repoName}. * @returns All of the workflow runs that are for a pull request. + * @throws The {@link WorkflowError} if there was an issue getting the workflow runs. */ public async getPullRequestWorkflowRuns(): Promise { - return await this.getAllFilteredData( async (page: number, qtyPerPage?: number) => { return await this.getWorkflowRuns( @@ -345,6 +353,7 @@ export class WorkflowClient extends GitHubClient { /** * Deletes the given {@link workflowRun} in a repository with a name that matches the {@link WorkflowClient}.{@link repoName}. + * @throws The {@link WorkflowError} if there was an issue deleting the workflow run. * @remarks Requires authentication. */ public async deleteWorkflow(workflowRun: WorkflowRunModel): Promise { @@ -356,9 +365,11 @@ export class WorkflowClient extends GitHubClient { switch (response.status) { case GitHubHttpStatusCodes.Forbidden: case GitHubHttpStatusCodes.NotFound: { - let errorMsg = `An error occurred trying to delete the workflow run '${workflowRun.name}(${workflowRun.id})'`; - errorMsg += `Error: ${response.status}(${response.statusText})`, Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + const errorMsg = this.buildErrorMsg( + `An error occurred trying to delete the workflow run '${workflowRun.name}(${workflowRun.id})'`, + response); + + throw new WorkflowError(errorMsg); } } } @@ -368,6 +379,7 @@ export class WorkflowClient extends GitHubClient { * given {@link branchName} for a repository that matches the {@link WorkflowClient}.{@link repoName}. * @param branchName The name of the branch. * @param workflowFileName The file name of the workflow. + * @throws The {@link WorkflowError} if there was an issue executing the workflow. */ public async executeWorkflow( branchName: string, @@ -385,7 +397,7 @@ export class WorkflowClient extends GitHubClient { if (!workflowFileName.endsWith(".yml") && !workflowFileName.endsWith(".yaml")) { let errorMsg = `The workflow file name '${workflowFileName}' does not contain the correct extension.`; errorMsg += `\nThe workflow file name must end with '.yml' or '.yaml'.`; - Deno.exit(1); + throw new WorkflowError(errorMsg); } let body = {}; @@ -405,8 +417,7 @@ export class WorkflowClient extends GitHubClient { errorMsg += `\n\tWorkflow: ${workflowFileName}`; errorMsg += `\n\tBranch: ${branchName}`; errorMsg += `\n\tRepository: ${this.repoName}`; - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + throw new WorkflowError(errorMsg); } }); @@ -418,37 +429,39 @@ export class WorkflowClient extends GitHubClient { const url = `${this.baseUrl}/repos/${this.ownerName}/${this.repoName}/actions/workflows/${workflowFileName}/dispatches`; - const requestResponse: Response = await this.requestPOST(url, body); + const response: Response = await this.requestPOST(url, body); - if (requestResponse.status != GitHubHttpStatusCodes.NoContent) { + if (response.status != GitHubHttpStatusCodes.NoContent) { let errorMsg = ""; - switch (requestResponse.status) { + switch (response.status) { case GitHubHttpStatusCodes.NotFound: { - errorMsg = `The workflow '${workflowFileName}' could not be found on branch `; - errorMsg += `'${branchName}' in the repository '${this.repoName}'.'`; - errorMsg += `\n\tError: ${requestResponse.status}(${requestResponse.statusText})`; + errorMsg = this.buildErrorMsg( + `The workflow '${workflowFileName}' could not be found on ` + + `branch '${branchName}' in the repository '${this.repoName}'.'`, + response, + ); break; } case GitHubHttpStatusCodes.UnprocessableContent: { errorMsg = `The workflow '${workflowFileName}' on branch '${branchName}' in the repository `; errorMsg += `'${this.repoName}' was not processable.`; - const githubResponse: GithubResponse = JSON.parse(await requestResponse.text()); - - errorMsg += `\n\tError: ${requestResponse.status}(${requestResponse.statusText})`; + const githubResponse: GithubResponse = JSON.parse(await response.text()); errorMsg += `\n${githubResponse.message}\n${githubResponse.documentation_url}`; + + errorMsg = this.buildErrorMsg(errorMsg, response); + break; } default: { - errorMsg = `An error occurred trying to execute the workflow '${workflowFileName}' on branch `; - errorMsg += `'${branchName}' in the repository '${this.repoName}'.'`; - errorMsg += `\n\tError: ${requestResponse.status}(${requestResponse.statusText})`; - Utils.printAsGitHubError(errorMsg); + errorMsg = this.buildErrorMsg( + `An error occurred trying to execute the workflow '${workflowFileName}' on branch ` + + `'${branchName}' in the repository '${this.repoName}'.'`, + response); break; } } - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + throw new WorkflowError(errorMsg); } } } diff --git a/GitHubClients/mod.ts b/GitHubClients/mod.ts index 12ebc41..c524ab0 100644 --- a/GitHubClients/mod.ts +++ b/GitHubClients/mod.ts @@ -1,12 +1,13 @@ -export { LabelClient } from "github/LabelClient.ts"; -export { IssueClient } from "github/IssueClient.ts"; -export { GitClient } from "github/GitClient.ts"; -export { MilestoneClient } from "github/MilestoneClient.ts"; -export { OrgClient } from "github/OrgClient.ts"; -export { ProjectClient } from "github/ProjectClient.ts"; -export { PullRequestClient } from "github/PullRequestClient.ts"; -export { ReleaseClient } from "github/ReleaseClient.ts"; -export { RepoClient } from "github/RepoClient.ts"; -export { TagClient } from "github/TagClient.ts"; -export { UsersClient } from "github/UsersClient.ts"; -export { WorkflowClient } from "github/WorkflowClient.ts"; +// NOTE: Do not refactor the import paths below to use the import maps. Leave them as relative paths. +export { LabelClient } from "./LabelClient.ts"; +export { IssueClient } from "./IssueClient.ts"; +export { GitClient } from "./GitClient.ts"; +export { MilestoneClient } from "./MilestoneClient.ts"; +export { OrgClient } from "./OrgClient.ts"; +export { ProjectClient } from "./ProjectClient.ts"; +export { PullRequestClient } from "./PullRequestClient.ts"; +export { ReleaseClient } from "./ReleaseClient.ts"; +export { RepoClient } from "./RepoClient.ts"; +export { TagClient } from "./TagClient.ts"; +export { UsersClient } from "./UsersClient.ts"; +export { WorkflowClient } from "./WorkflowClient.ts"; diff --git a/OtherClients/XClient.ts b/OtherClients/XClient.ts index 5ec2789..bd28726 100644 --- a/OtherClients/XClient.ts +++ b/OtherClients/XClient.ts @@ -1,7 +1,7 @@ -import { XAuthValues } from "other/XAuthValue.ts"; -import { TweetV2PostTweetResult, TwitterApi } from "twitter"; -import { Utils } from "core/Utils.ts"; -import { WebApiClient } from "core/WebApiClient.ts"; +import { XAuthValues } from "./XAuthValue.ts"; +import { TweetV2PostTweetResult, TwitterApi } from "../deps.ts"; +import { WebApiClient } from "../core/WebApiClient.ts"; +import { XError } from "../GitHubClients/Errors/XError.ts"; /** * Provides twitter functionality. @@ -36,10 +36,8 @@ export class XClient extends WebApiClient { let errorMsg = `Error Title: ${error.title}`; errorMsg += `\nError Detail: ${error.detail}`; - Utils.printAsGitHubError(errorMsg); + throw new XError(errorMsg); }); - } else { - Utils.printAsGitHubNotice(`${tweetResult.data.id}\n${tweetResult.data.text}`); } } } diff --git a/OtherClients/mod.ts b/OtherClients/mod.ts index 243a157..8703890 100644 --- a/OtherClients/mod.ts +++ b/OtherClients/mod.ts @@ -1 +1,2 @@ -export { XClient } from "other/XClient.ts"; +// NOTE: Do not refactor the import paths below to use the import maps. Leave them as relative paths. +export { XClient } from "./XClient.ts"; diff --git a/PackageClients/NuGetClient.ts b/PackageClients/NuGetClient.ts index d8335fd..35a0849 100644 --- a/PackageClients/NuGetClient.ts +++ b/PackageClients/NuGetClient.ts @@ -1,7 +1,7 @@ -import { WebApiClient } from "core/WebApiClient.ts"; -import { Guard } from "core/Guard.ts"; -import { Utils } from "core/Utils.ts"; -import { NuGetHttpStatusCodes } from "core/Enums.ts"; +import { WebApiClient } from "../core/WebApiClient.ts"; +import { Guard } from "../core/Guard.ts"; +import { NuGetHttpStatusCodes } from "../core/Enums.ts"; +import { NuGetError } from "../GitHubClients/Errors/NuGetError.ts"; /** * References: @@ -27,6 +27,7 @@ export class NuGetClient extends WebApiClient { * Checks if a package that matches the given {@link packageName} exists in the NuGet registry. * @param packageName The name of the NuGet package. * @returns True if the package exists, otherwise false. + * @throws The {@link NuGetError} if there was an issue checking for the package. */ public async packageExists(packageName: string): Promise { Guard.isNothing(packageName, "packageExists", "packageName"); @@ -40,11 +41,11 @@ export class NuGetClient extends WebApiClient { if (this.statusCodeValid(statusCode)) { return statusCode === NuGetHttpStatusCodes.SuccessWithResponseBody; } else { - let errorMsg = `There was an issue checking for the '${packageName}' NuGet package.`; - errorMsg += `\n${this.getErrorMsg(response)}}`; + const errorMsg = this.buildErrorMsg( + `There was an issue checking for the '${packageName}' NuGet package.`, + response); - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + throw new NuGetError(errorMsg); } } @@ -52,6 +53,7 @@ export class NuGetClient extends WebApiClient { * Gets all of the versions for a NuGet package that matches the given {@link packageName}. * @param packageName The name of the NuGet package. * @returns The versions of the given NuGet package. + * @throws The {@link NuGetError} if there was an issue getting the versions for a package. */ public async getPackageVersions(packageName: string): Promise { Guard.isNothing(packageName, "getPackageVersions", "packageName"); @@ -67,11 +69,11 @@ export class NuGetClient extends WebApiClient { return data.versions; } else { - let errorMsg = `There was an issue getting the versions for the '${packageName}' NuGet package.`; - errorMsg += `\n${this.getErrorMsg(response)}}`; + const errorMsg = this.buildErrorMsg( + `There was an issue getting the versions for the '${packageName}' NuGet package.`, + response); - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + throw new NuGetError(errorMsg); } } @@ -80,15 +82,18 @@ export class NuGetClient extends WebApiClient { * @param packageName The name of the NuGet package. * @param version The version of the NuGet package. * @returns True if the package exists with the given version, otherwise false. + * @throws The {@link NuGetError} if there was an issue checking for a specific package version. */ public async packageWithVersionExists(packageName: string, version: string): Promise { - Guard.isNothing(packageName, "getPackageVersions", "packageName"); + const funcName = "packageWithVersionExists"; + Guard.isNothing(packageName, funcName, "packageName"); + Guard.isNothing(packageName, funcName, "version"); packageName = packageName.trim().toLowerCase(); version = version.trim().toLowerCase(); const url = this.buildUrl(packageName); - + const response: Response = await this.requestGET(url); const statusCode: NuGetHttpStatusCodes = response.status as NuGetHttpStatusCodes; @@ -103,11 +108,11 @@ export class NuGetClient extends WebApiClient { return false; } } else { - let errorMsg = `There was an issue getting information about the '${packageName}' NuGet package.`; - errorMsg += `\n${this.getErrorMsg(response)}}`; + const errorMsg = this.buildErrorMsg( + `There was an issue getting information about the '${packageName}' NuGet package.`, + response); - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + throw new NuGetError(errorMsg); } } @@ -136,15 +141,6 @@ export class NuGetClient extends WebApiClient { return true; } - /** - * Gets the error message from the given response. - * @param response The response to get the data from. - * @returns The error status code and text. - */ - private getErrorMsg(response: Response): string { - return `Error: ${response.status} - ${response.statusText}`; - } - /** * Builds the URL for the NuGet package info. * @param packageName The name of the NuGet package. diff --git a/PackageClients/mod.ts b/PackageClients/mod.ts index aa94808..f41ba12 100644 --- a/PackageClients/mod.ts +++ b/PackageClients/mod.ts @@ -1 +1,2 @@ -export { NuGetClient } from "package/NuGetClient.ts"; +// NOTE: Do not refactor the import paths below to use the import maps. Leave them as relative paths. +export { NuGetClient } from "./NuGetClient.ts"; diff --git a/ReleaseNotes/PreviewReleases/Release-Notes-v1.0.0-preview.3.md b/ReleaseNotes/PreviewReleases/Release-Notes-v1.0.0-preview.3.md new file mode 100644 index 0000000..35e7553 --- /dev/null +++ b/ReleaseNotes/PreviewReleases/Release-Notes-v1.0.0-preview.3.md @@ -0,0 +1,51 @@ +

+kd_clients Preview Release Notes - v1.0.0-preview.3 +

+ +

Quick Reminder

+ +
+ +As with all software, there is always a chance for issues and bugs, especially for preview releases, which is why your input is greatly appreciated. 🙏🏼 +
+ +

New Features ✨

+ +1. [#6](https://github.com/KinsonDigital/kd_clients/issues/6) - Made the following functions public in the `WebApiClient` class. + - `requestGET` + - `requestPOST` + - `requestPATCH` + - `requestDELETE` + - `requestPUT` +1. [#6](https://github.com/KinsonDigital/kd_clients/issues/6) - Added the following error classes that can be thrown in the various clients. + - BadCredentialsError + - GitError + - IssueError + - LabelError + - MilestoneError + - NuGetError + - OrganizationError + - ProjectError + - PullRequestError + - ReleaseError + - RepoError + - TagError + - UsersError + - WorkflowError + - XError + +

Breaking Changes 🧨

+ +1. [#6](https://github.com/KinsonDigital/kd_clients/issues/6) - Implemented the following breaking changes: + - Changed the order of the `GraphQlClient` constructor params from `(token: string, ownerName?:string, repoName?:string)` to `(ownerName:string, repoName:string, token:string)`. + - Changed the `GraphQlClient` constructor parameters `ownerName` and `repoName` from optional to required. + - Changed the `GitHubClient` constructor parameters `ownerName` and `repoName` from optional to required. + - Refactored the name of the `GitClient` constructor parameter named `repoOwner` to `ownerName`. + - Refactored the name of the `RepoClient` constructor parameter named `repoOwner` to `ownerName`. + + +

Bug Fixes 🐛

+ + +1. [#6](https://github.com/KinsonDigital/kd_clients/issues/6) - Fixed import issue in mod files. + diff --git a/core/GitHubClient.ts b/core/GitHubClient.ts index a58bc24..070179e 100644 --- a/core/GitHubClient.ts +++ b/core/GitHubClient.ts @@ -1,8 +1,8 @@ -import { Utils } from "core/Utils.ts"; -import { LinkHeaderParser } from "core/LinkHeaderParser.ts"; -import { WebApiClient } from "core/WebApiClient.ts"; -import { GetDataFunc } from "core/Types.ts"; -import { Guard } from "core/Guard.ts"; +import { Utils } from "./Utils.ts"; +import { LinkHeaderParser } from "./LinkHeaderParser.ts"; +import { WebApiClient } from "./WebApiClient.ts"; +import { GetDataFunc } from "./Types.ts"; +import { Guard } from "./Guard.ts"; /** * Provides a base class for HTTP clients. @@ -20,7 +20,7 @@ export abstract class GitHubClient extends WebApiClient { * @param token The GitHub token to use for authentication. * @remarks If no token is provided, then the client will not be authenticated. */ - constructor(ownerName?: string, repoName?: string, token?: string) { + constructor(ownerName: string, repoName: string, token?: string) { super(); this.ownerName = Utils.isNothing(ownerName) ? "" : ownerName.trim(); @@ -33,7 +33,7 @@ export abstract class GitHubClient extends WebApiClient { if (!Utils.isNothing(token)) { this.headers.append("Authorization", `Bearer ${token}`); } - } + } /** * Gets the name of the owner of the repository. @@ -46,22 +46,22 @@ export abstract class GitHubClient extends WebApiClient { * Sets the name of the owner of the repository. */ public set ownerName(v: string) { - Guard.isNothing("ownerName", v, "v"); + Guard.isNothing(v, "ownerName", "v"); this._ownerName = v.trim(); } - + /** * Gets the name of the repository. - */ - public get repoName() : string { + */ + public get repoName(): string { return this._repoName; } - + /** * Sets the name of the repository. - */ - public set repoName(v : string) { - Guard.isNothing("repoName", v, "v"); + */ + public set repoName(v: string) { + Guard.isNothing(v, "repoName", "v"); this._repoName = v.trim(); } @@ -124,8 +124,7 @@ export abstract class GitHubClient extends WebApiClient { } catch (error) { let errorMsg = "There was an issue getting all of the data using pagination."; errorMsg += `\n${error.message}`; - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + throw new Error(errorMsg); } return allData; @@ -209,8 +208,7 @@ export abstract class GitHubClient extends WebApiClient { let errorMsg = "There was an issue getting all of the data using pagination."; errorMsg += `\n${error.message}`; - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + throw new Error(errorMsg); } } @@ -235,8 +233,7 @@ export abstract class GitHubClient extends WebApiClient { } catch (error) { let errorMsg = "There was an issue getting all of the data using pagination."; errorMsg += `\n${error.message}`; - Utils.printAsGitHubError(errorMsg); - Deno.exit(1); + throw new Error(errorMsg); } } diff --git a/core/GraphQl/Mutations/AddCommitMutation.ts b/core/GraphQl/Mutations/AddCommitMutation.ts index eed8ed3..93a0e20 100644 --- a/core/GraphQl/Mutations/AddCommitMutation.ts +++ b/core/GraphQl/Mutations/AddCommitMutation.ts @@ -1,8 +1,8 @@ /** * Creates a GraphQL mutation to add a commit to a branch that matches the given {@link branchName}, * in a repository with a name that matches the given {@link repoName}, and that is owned by a GitHub - * user with a login name that matches the given {@link repoOwner}. - * @param repoOwner The owner of the repository. + * user with a login name that matches the given {@link ownerName}. + * @param ownerName The owner of the repository. * @param repoName The name of the repository. * @param branchName The name of the branch. * @param branchHeadOid The OID of the head of the branch. @@ -10,13 +10,13 @@ * @returns The GraphQL mutation. */ export const addCommitMutation = ( - repoOwner: string, + ownerName: string, repoName: string, branchName: string, branchHeadOid: string, commitMessage: string, ): string => { - const repoNameWithOwner = `${repoOwner}/${repoName}`; + const repoNameWithOwner = `${ownerName}/${repoName}`; return `mutation { createCommitOnBranch (input: { diff --git a/core/GraphQl/Queries/GetBranchesQuery.ts b/core/GraphQl/Queries/GetBranchesQuery.ts index 7033c33..6ba7edb 100644 --- a/core/GraphQl/Queries/GetBranchesQuery.ts +++ b/core/GraphQl/Queries/GetBranchesQuery.ts @@ -1,14 +1,14 @@ -import { Utils } from "core/Utils.ts"; +import { Utils } from "../../Utils.ts"; /** * Get a list of branches for a GitHub repository. - * @param repoOwner The owner of the repository. + * @param ownerName The owner of the repository. * @param repoName The name of the repository. * @param first The number of branches to get. * @param cursor The cursor to use for pagination. * @returns The list of branches. */ -export const createGetBranchesQuery = (repoOwner: string, repoName: string, first?: number, cursor?: string): string => { +export const createGetBranchesQuery = (ownerName: string, repoName: string, first?: number, cursor?: string): string => { first = !Utils.isNothing(first) && first > 100 ? 100 : first; first = !Utils.isNothing(first) && first <= 0 ? 1 : first; @@ -17,7 +17,7 @@ export const createGetBranchesQuery = (repoOwner: string, repoName: string, firs const cursorValue = Utils.isNothing(cursor) ? "" : `, after: "${cursor}"`; return `{ - repository (owner: "${repoOwner}", name: "${repoName}") { + repository (owner: "${ownerName}", name: "${repoName}") { refs (refPrefix: "refs/heads/"${firstValue}${cursorValue}, orderBy: { field: TAG_COMMIT_DATE, direction: DESC }) { nodes { id diff --git a/core/GraphQl/Queries/GetIssueProjectsQuery.ts b/core/GraphQl/Queries/GetIssueProjectsQuery.ts index d9332f5..781d9be 100644 --- a/core/GraphQl/Queries/GetIssueProjectsQuery.ts +++ b/core/GraphQl/Queries/GetIssueProjectsQuery.ts @@ -1,11 +1,11 @@ /** * Gets the organizational project for an issue. - * @param repoOwner The owner of the organization. + * @param ownerName The owner of the organization. * @returns The list of projects. */ -export const createGetIssueProjectsQuery = (repoOwner: string, repoName: string, issueNumber: number): string => { +export const createGetIssueProjectsQuery = (ownerName: string, repoName: string, issueNumber: number): string => { return `{ - repository(owner: "${repoOwner}", name: "${repoName}") { + repository(owner: "${ownerName}", name: "${repoName}") { issue(number: ${issueNumber}) { projectsV2 (first: 100) { nodes { diff --git a/core/GraphQl/Queries/GetOrgProjectsQueries.ts b/core/GraphQl/Queries/GetOrgProjectsQueries.ts index 7cbe063..6b872b9 100644 --- a/core/GraphQl/Queries/GetOrgProjectsQueries.ts +++ b/core/GraphQl/Queries/GetOrgProjectsQueries.ts @@ -1,11 +1,11 @@ /** * Get a list of the GitHub organization projects. - * @param repoOwner The owner of the organization. + * @param ownerName The owner of the organization. * @returns The list of projects. */ -export const createOrgProjectsQuery = (repoOwner: string): string => { +export const createOrgProjectsQuery = (ownerName: string): string => { return `{ - organization(login: "${repoOwner}") { + organization(login: "${ownerName}") { projectsV2 (first: 100) { nodes { id diff --git a/core/GraphQl/Queries/GetPullRequestProjectsQuery.ts b/core/GraphQl/Queries/GetPullRequestProjectsQuery.ts index b535b80..cd82baa 100644 --- a/core/GraphQl/Queries/GetPullRequestProjectsQuery.ts +++ b/core/GraphQl/Queries/GetPullRequestProjectsQuery.ts @@ -1,11 +1,11 @@ /** * Gets the organizational project for a pull request. - * @param repoOwner The owner of the organization. + * @param ownerName The owner of the organization. * @returns The list of projects. */ -export const createGetPullRequestProjectsQuery = (repoOwner: string, repoName: string, prNumber: number): string => { +export const createGetPullRequestProjectsQuery = (ownerName: string, repoName: string, prNumber: number): string => { return `{ - repository(owner: "${repoOwner}", name: "${repoName}") { + repository(owner: "${ownerName}", name: "${repoName}") { pullRequest(number: ${prNumber}) { projectsV2 (first: 100) { nodes { diff --git a/core/GraphQlClient.ts b/core/GraphQlClient.ts index a137713..c275bfa 100644 --- a/core/GraphQlClient.ts +++ b/core/GraphQlClient.ts @@ -1,8 +1,7 @@ -import { ErrorModel } from "models/GraphQlModels/ErrorModel.ts"; -import { RequestResponseModel } from "models/GraphQlModels/RequestResponseModel.ts"; -import { BadCredentials } from "core/Types.ts"; -import { Utils } from "core/Utils.ts"; -import { Guard } from "core/Guard.ts"; +import { RequestResponseModel } from "./Models/GraphQlModels/RequestResponseModel.ts"; +import { Utils } from "./Utils.ts"; +import { Guard } from "./Guard.ts"; +import { BadCredentialsError } from "../GitHubClients/Errors/BadCredentials.ts"; /** * Provides a base class for HTTP clients. @@ -15,12 +14,12 @@ export abstract class GraphQlClient { /** * Initializes a new instance of the {@link GraphQlClient} class. - * @param token The GitHub token to use for authentication. - * @param ownerName The name of the owner of the repository to use. + * @param ownerName The name of the repository owner. * @param repoName The name of a repository. + * @param token The GitHub token to use for authentication. * @remarks If no token is provided, then the client will not be authenticated. */ - constructor(token: string, ownerName?: string, repoName?: string) { + constructor(ownerName: string, repoName: string, token: string) { this.ownerName = Utils.isNothing(ownerName) ? "" : ownerName.trim(); this.repoName = Utils.isNothing(repoName) ? "" : repoName.trim(); @@ -41,18 +40,18 @@ export abstract class GraphQlClient { Guard.isNothing("ownerName", v, "v"); this._ownerName = v.trim(); } - + /** * Gets the name of the repository. - */ - public get repoName() : string { + */ + public get repoName(): string { return this._repoName; } - + /** * Sets the name of the repository. - */ - public set repoName(v : string) { + */ + public set repoName(v: string) { Guard.isNothing("repoName", v, "v"); this._repoName = v.trim(); } @@ -68,41 +67,13 @@ export abstract class GraphQlClient { /** * Gets the response data from a request. * @param response The response from a request. - * @param throwWithErrors Whether or not to throw and error if the response contains errors. * @returns The response data. * @remarks This method will throw an error if the response contains errors. */ - protected async getResponseData(response: Response, throwWithErrors = true): Promise { + protected async getResponseData(response: Response): Promise { const responseText = await response.text(); const responseData = await JSON.parse(responseText); - if (throwWithErrors === true) { - await this.throwIfErrors(responseText); - } - - return responseData; - } - - /** - * Throws an error and exists the process if the response contains errors. - * @param {response | string} response The response object or response text from a request. - */ - protected async throwIfErrors(response: Response | string): Promise { - const responseText = this.isResponse(response) ? await response.text() : response; - const responseData = await JSON.parse(responseText); - - if (this.containsErrors(responseData)) { - const errors: ErrorModel[] = this.getErrors(responseData); - const errorMessages: string[] = errors.map((e) => e.message); - const error = `${errorMessages.join("\n")}`; - - Utils.printAsGitHubError(error); - Deno.exit(1); - } else if (this.isBadCredentialError(responseData)) { - Utils.printAsGitHubError(`There was an issue making your GraphQL request.\nError: ${responseData.message}`); - Deno.exit(1); - } - return responseData; } @@ -121,59 +92,12 @@ export abstract class GraphQlClient { headers: this.headers, }); - return await this.getResponseData(response); - } - - /** - * Gets all of the errors from the response data. - * @param responseData The response data from the request. - * @returns The list of errors. - */ - private getErrors(responseData: RequestResponseModel): ErrorModel[] { - if (this.containsErrors(responseData)) { - const errors: ErrorModel[] | undefined = responseData["errors"]; - - if (errors === undefined) { - return []; - } + const result = await this.getResponseData(response); - return errors; + if (!Utils.isNothing(result.message) && result.message === "Bad credentials") { + throw new BadCredentialsError("The GraphQL query could not be executed because the credentials are invalid."); } - return []; - } - - /** - * Returns a value indicating whether or not the given {@link responseData} contains any errors. - * @param response The response to check if it contains an errors object. - * @returns True if the object contains errors, false otherwise. - */ - private containsErrors( - responseData: RequestResponseModel, - key = "errors", - ): responseData is RequestResponseModel & { [k in typeof key]: string } { - return key in responseData; - } - - /** - * Returns a value indicating whether or not the given object is a bad credentials object. - * @param responseOrBadCreds The response or bad credentials object to check. - * @returns True if the object is a bad credentials object, false otherwise. - */ - private isBadCredentialError( - responseOrBadCreds: RequestResponseModel | BadCredentials, - ): responseOrBadCreds is BadCredentials { - return "documentation_url" in responseOrBadCreds && - "message" in responseOrBadCreds && - responseOrBadCreds.message === "Bad credentials"; - } - - /** - * Gets a value indicating whether or not the given object is a Response object. - * @param responseOrString The Response or string object to check. - * @returns True if the object is a Response object, false otherwise. - */ - private isResponse(responseOrString: Response | string): responseOrString is Response { - return responseOrString instanceof Response; + return result; } } diff --git a/core/Guard.ts b/core/Guard.ts index 5485003..eacb9c4 100644 --- a/core/Guard.ts +++ b/core/Guard.ts @@ -1,4 +1,4 @@ -import { Utils } from "core/Utils.ts"; +import { Utils } from "./Utils.ts"; /** * A class that contains functions to check if values are invalid. @@ -15,17 +15,17 @@ export class Guard { paramName = "", ): void { if (Utils.isNothing(value)) { - Utils.printAsGitHubError("The value is null, undefined, or empty."); + let errorMsg = "The value is null, undefined, or empty."; if (funcName != "") { - console.log(`Function Name: ${funcName}`); + errorMsg += `\nFunction Name: ${funcName}`; } if (paramName != "") { - console.log(`Param Name: ${paramName}`); + errorMsg += `\nParam Name: ${paramName}`; } - Deno.exit(1); + throw new Error(errorMsg); } } @@ -38,31 +38,31 @@ export class Guard { public static isLessThanOne(value: undefined | null | number, funcName = "", paramName = ""): void { const isNullOrUndefined = value === undefined || value === null; if (isNullOrUndefined || isNaN(value) || !isFinite(value)) { - Utils.printAsGitHubError("The value is undefined, null, NaN, Infinite, -Infinity."); + let errorMsg = "The value is undefined, null, NaN, Infinite, -Infinity."; if (funcName != "") { - console.log(`Function Name: ${funcName}`); + errorMsg += `\nFunction Name: ${funcName}`; } if (paramName != "") { - console.log(`Param Name: ${paramName}`); + errorMsg += `\nParam Name: ${paramName}`; } - Deno.exit(1); + throw new Error(errorMsg); } if (value < 0) { - Utils.printAsGitHubError("The value is less than or equal to zero."); + let errorMsg = "The value is less than or equal to zero."; if (funcName != "") { - console.log(`Function Name: ${funcName}`); + errorMsg += `\nFunction Name: ${funcName}`; } if (paramName != "") { - console.log(`Param Name: ${paramName}`); + errorMsg += `\nParam Name: ${paramName}`; } - Deno.exit(1); + throw new Error(errorMsg); } } } diff --git a/core/IssueOrPRRequestData.ts b/core/IssueOrPRRequestData.ts index f889f76..d77a891 100644 --- a/core/IssueOrPRRequestData.ts +++ b/core/IssueOrPRRequestData.ts @@ -1,4 +1,4 @@ -import { IssueState, StateReason } from "core/Enums.ts"; +import { IssueState, StateReason } from "./Enums.ts"; /** * Represents the body of an HTTP GitHub API issue request. diff --git a/core/LinkHeader.ts b/core/LinkHeader.ts index c27686c..8989a67 100644 --- a/core/LinkHeader.ts +++ b/core/LinkHeader.ts @@ -1,4 +1,4 @@ -import { PageInfo } from "core/PageInfo.ts"; +import { PageInfo } from "./PageInfo.ts"; /** * Represents a response link header from a GitHub API response diff --git a/core/LinkHeaderParser.ts b/core/LinkHeaderParser.ts index 6f7cf61..e2232dc 100644 --- a/core/LinkHeaderParser.ts +++ b/core/LinkHeaderParser.ts @@ -1,6 +1,6 @@ -import { LinkHeader } from "core/LinkHeader.ts"; -import { PageInfo } from "core/PageInfo.ts"; -import { Utils } from "core/Utils.ts"; +import { LinkHeader } from "./LinkHeader.ts"; +import { PageInfo } from "./PageInfo.ts"; +import { Utils } from "./Utils.ts"; /** * Parses link headers to collect pagination information. @@ -23,7 +23,7 @@ export class LinkHeaderParser { const headerSections: string[] = Utils.splitByComma(linkHeader).map((i) => i.trim()); const linkHeaderInfo: LinkHeader = { - prevPage: 0, + prevPage: 0, nextPage: 0, totalPages: 0, pageData: [], diff --git a/core/Models/GitHubVariablesModel.ts b/core/Models/GitHubVariablesModel.ts index 2f39a41..1f44118 100644 --- a/core/Models/GitHubVariablesModel.ts +++ b/core/Models/GitHubVariablesModel.ts @@ -1,4 +1,4 @@ -import { GitHubVarModel } from "models/GitHubVarModel.ts"; +import { GitHubVarModel } from "./GitHubVarModel.ts"; /** * Represents multiple variables for an organization and/or repository. diff --git a/core/Models/GraphQlModels/ErrorModel.ts b/core/Models/GraphQlModels/ErrorModel.ts index 732b6d2..ad1290e 100644 --- a/core/Models/GraphQlModels/ErrorModel.ts +++ b/core/Models/GraphQlModels/ErrorModel.ts @@ -1,4 +1,4 @@ -import { LocationModel } from "models/GraphQlModels/LocationModel.ts"; +import { LocationModel } from "./LocationModel.ts"; /** * Represents an error. diff --git a/core/Models/GraphQlModels/RawModels/RawGitBranchModel.ts b/core/Models/GraphQlModels/RawModels/RawGitBranchModel.ts index 4b073a6..587e704 100644 --- a/core/Models/GraphQlModels/RawModels/RawGitBranchModel.ts +++ b/core/Models/GraphQlModels/RawModels/RawGitBranchModel.ts @@ -1,4 +1,4 @@ -import { RawGetBranchTargetModel } from "models/GraphQlModels/RawModels/RawGetBranchTargetModel.ts"; +import { RawGetBranchTargetModel } from "./RawGetBranchTargetModel.ts"; /** * Represents a raw git branch model that is unchanged from the GraphQL query. diff --git a/core/Models/GraphQlModels/RawModels/RawRefsGetBranchModel.ts b/core/Models/GraphQlModels/RawModels/RawRefsGetBranchModel.ts index 69635bd..9c40e35 100644 --- a/core/Models/GraphQlModels/RawModels/RawRefsGetBranchModel.ts +++ b/core/Models/GraphQlModels/RawModels/RawRefsGetBranchModel.ts @@ -1,4 +1,4 @@ -import { RawGitBranchModel } from "models/GraphQlModels/RawModels/RawGitBranchModel.ts"; +import { RawGitBranchModel } from "./RawGitBranchModel.ts"; /** * Represents the raw git branch refs model that is unchanged from the GraphQL query. diff --git a/core/Models/GraphQlModels/RequestResponseModel.ts b/core/Models/GraphQlModels/RequestResponseModel.ts index a40d3c8..66b3c2f 100644 --- a/core/Models/GraphQlModels/RequestResponseModel.ts +++ b/core/Models/GraphQlModels/RequestResponseModel.ts @@ -1,4 +1,4 @@ -import { ErrorModel } from "models/GraphQlModels/ErrorModel.ts"; +import { ErrorModel } from "./ErrorModel.ts"; /** * Represents a request response. @@ -10,6 +10,11 @@ export type RequestResponseModel = { // deno-lint-ignore no-explicit-any data: any; + /** + * The message returned from the request. + */ + message?: string; + /** * The errors returned from the request. */ diff --git a/core/Models/IssueModel.ts b/core/Models/IssueModel.ts index 4dfd74a..6f030a2 100644 --- a/core/Models/IssueModel.ts +++ b/core/Models/IssueModel.ts @@ -1,6 +1,6 @@ -import { LabelModel } from "models/LabelModel.ts"; -import { MilestoneModel } from "models/MilestoneModel.ts"; -import { UserModel } from "models/UserModel.ts"; +import { LabelModel } from "./LabelModel.ts"; +import { MilestoneModel } from "./MilestoneModel.ts"; +import { UserModel } from "./UserModel.ts"; /** * Represents a GitHub issue. diff --git a/core/Models/PullRequestHeadOrBaseModel.ts b/core/Models/PullRequestHeadOrBaseModel.ts index 2958662..2ba08c8 100644 --- a/core/Models/PullRequestHeadOrBaseModel.ts +++ b/core/Models/PullRequestHeadOrBaseModel.ts @@ -1,4 +1,4 @@ -import { RepoModel } from "models/RepoModel.ts"; +import { RepoModel } from "./RepoModel.ts"; /** * Holds information about a pull requests head or base branches. diff --git a/core/Models/PullRequestModel.ts b/core/Models/PullRequestModel.ts index a1d74f6..8133b28 100644 --- a/core/Models/PullRequestModel.ts +++ b/core/Models/PullRequestModel.ts @@ -1,8 +1,8 @@ -import { LabelModel } from "models/LabelModel.ts"; -import { MilestoneModel } from "models/MilestoneModel.ts"; -import { PullRequestHeadOrBaseModel } from "models/PullRequestHeadOrBaseModel.ts"; -import { PullRequestInfoModel } from "models/PullRequestInfo.ts"; -import { UserModel } from "models/UserModel.ts"; +import { LabelModel } from "./LabelModel.ts"; +import { MilestoneModel } from "./MilestoneModel.ts"; +import { PullRequestHeadOrBaseModel } from "./PullRequestHeadOrBaseModel.ts"; +import { PullRequestInfoModel } from "./PullRequestInfo.ts"; +import { UserModel } from "./UserModel.ts"; /** * Represents a GitHub pull request. diff --git a/core/Models/TagModel.ts b/core/Models/TagModel.ts index 5a5bf4a..4ceee86 100644 --- a/core/Models/TagModel.ts +++ b/core/Models/TagModel.ts @@ -1,4 +1,4 @@ -import { CommitModel } from "models/CommitModel.ts"; +import { CommitModel } from "./CommitModel.ts"; /** * Represents a GIT tag. diff --git a/core/Models/WorkflowRunModel.ts b/core/Models/WorkflowRunModel.ts index bf2e2cc..767bf95 100644 --- a/core/Models/WorkflowRunModel.ts +++ b/core/Models/WorkflowRunModel.ts @@ -1,4 +1,4 @@ -import { PullRequestModel } from "models/PullRequestModel.ts"; +import { PullRequestModel } from "./PullRequestModel.ts"; /** * Represents a single workflow run. diff --git a/core/Models/WorkflowRunsModel.ts b/core/Models/WorkflowRunsModel.ts index de383cc..cc4e3b0 100644 --- a/core/Models/WorkflowRunsModel.ts +++ b/core/Models/WorkflowRunsModel.ts @@ -1,4 +1,4 @@ -import { WorkflowRunModel } from "models/WorkflowRunModel.ts"; +import { WorkflowRunModel } from "./WorkflowRunModel.ts"; /** * Represents a list of workflow runs. diff --git a/core/Types.ts b/core/Types.ts index f9e644f..8ca9406 100644 --- a/core/Types.ts +++ b/core/Types.ts @@ -1,5 +1,5 @@ -import { IssueModel } from "models/IssueModel.ts"; -import { PullRequestModel } from "models/PullRequestModel.ts"; +import { IssueModel } from "./Models/IssueModel.ts"; +import { PullRequestModel } from "./Models/PullRequestModel.ts"; /** * Represents a GitHub issue or pull request. diff --git a/core/Utils.ts b/core/Utils.ts index d982cfc..27c42f5 100644 --- a/core/Utils.ts +++ b/core/Utils.ts @@ -1,7 +1,9 @@ -import { Guard } from "core/Guard.ts"; -import { IssueModel } from "models/IssueModel.ts"; -import { PullRequestModel } from "models/PullRequestModel.ts"; -import { ReleaseType } from "core/Enums.ts"; +import { Guard } from "./Guard.ts"; +import { ReleaseType } from "./Enums.ts"; +import { IssueModel } from "./Models/IssueModel.ts"; +import { PullRequestModel } from "./Models/PullRequestModel.ts"; +import { chalk } from "../deps.ts"; +import { RequestResponseModel } from "./Models/GraphQlModels/RequestResponseModel.ts"; /** * Provides utility functions. @@ -132,22 +134,12 @@ export class Utils { } /** - * Prints the given {@link message} as a GitHub notice. + * Prints the given {@link message} as a error. * @param message The message to print. */ - public static printAsGitHubNotice(message: string): void { + public static printError(message: string): void { Utils.printEmptyLine(); - console.log(`::notice::${message}`); - Utils.printEmptyLine(); - } - - /** - * Prints the given {@link message} as a GitHub error. - * @param message The message to print. - */ - public static printAsGitHubError(message: string): void { - Utils.printEmptyLine(); - console.log(`::error::${message}`); + console.log(chalk.red(message)); Utils.printEmptyLine(); } @@ -171,34 +163,34 @@ export class Utils { /** * Builds a URL to a pull request that matches the given {@link prNumber} in a repository with a - * name that matches the given {@link repoName} and is owned by the given {@link repoOwner}. - * @param repoOwner The owner of the repository. + * name that matches the given {@link repoName} and is owned by the given {@link ownerName}. + * @param ownerName The owner of the repository. * @param repoName The name of the repository. * @param prNumber The pull request number. * @returns The URL to the issue. */ - public static buildPullRequestUrl(repoOwner: string, repoName: string, prNumber: number): string { + public static buildPullRequestUrl(ownerName: string, repoName: string, prNumber: number): string { const funcName = "buildPullRequestUrl"; - Guard.isNothing(repoOwner, funcName, "repoOwner"); + Guard.isNothing(ownerName, funcName, "ownerName"); Guard.isNothing(repoName, funcName, "repoName"); Guard.isLessThanOne(prNumber, funcName, "prNumber"); - return `https://github.com/${repoOwner}/${repoName}/pull/${prNumber}`; + return `https://github.com/${ownerName}/${repoName}/pull/${prNumber}`; } /** * Builds a URL to the labels page of a repository with a name that matches the given {@link repoName} - * and is owned by the given {@link repoOwner}. - * @param repoOwner The owner of the repository. + * and is owned by the given {@link ownerName}. + * @param ownerName The owner of the repository. * @param repoName The name of the repository. * @returns The URL to the repository labels page. */ - public static buildLabelsUrl(repoOwner: string, repoName: string): string { + public static buildLabelsUrl(ownerName: string, repoName: string): string { const funcName = "buildLabelsUrl"; - Guard.isNothing(repoOwner, funcName, "repoOwner"); + Guard.isNothing(ownerName, funcName, "ownerName"); Guard.isNothing(repoName, funcName, "repoName"); - return `https://github.com/${repoOwner}/${repoName}/labels`; + return `https://github.com/${ownerName}/${repoName}/labels`; } /** @@ -269,7 +261,7 @@ export class Utils { public static invalidReleaseType(value: string): value is ReleaseType { return value != "preview" && value != "production"; } - + /** * Returns a value indicating whether or not the given {@link value} is a valid preview release type. * @param value The value to check. @@ -278,7 +270,7 @@ export class Utils { public static isPreviewRelease(value: string): value is ReleaseType { return value === "preview"; } - + /** * Returns a value indicating whether or not the given {@link value} is a valid production release type. * @param value The value to check. @@ -287,4 +279,29 @@ export class Utils { public static isProductionRelease(value: string): value is ReleaseType { return value === "production"; } + + /** + * Combines the given {@link mainMsg} and {@link requestResponse} error messages into one error message. + * @param mainMsg The main error message. + * @param requestResponse The request response that might contain more error messages. + * @returns The main and response error messages combined. + */ + public static toErrorMessage(mainMsg: string, requestResponse: RequestResponseModel): string { + const errorMessages: string[] = []; + + mainMsg = mainMsg.endsWith(":") ? mainMsg : `${mainMsg}:`; + + if (requestResponse.errors === undefined) { + return mainMsg; + } + + requestResponse.errors.forEach((error) => { + errorMessages.push(error.message); + }); + + let errorMsg = `The following errors occurred:`; + errorMsg += `\n${errorMessages.join("\n")}`; + + return errorMsg; + } } diff --git a/core/WebApiClient.ts b/core/WebApiClient.ts index c0eca6e..50f42fa 100644 --- a/core/WebApiClient.ts +++ b/core/WebApiClient.ts @@ -1,4 +1,4 @@ -import { Guard } from "core/Guard.ts"; +import { Guard } from "./Guard.ts"; /** * Provides a base class for HTTP clients. @@ -25,7 +25,7 @@ export abstract class WebApiClient { * @param url The URL of the request. * @returns The response from the request. */ - protected async requestGET(url: string): Promise { + public async requestGET(url: string): Promise { Guard.isNothing(url, "fetchGET", "url"); return await fetch(url, { @@ -40,7 +40,7 @@ export abstract class WebApiClient { * @param body The body of the request. * @returns The response from the request. */ - protected async requestPOST(url: string, body: string | object): Promise { + public async requestPOST(url: string, body: string | object): Promise { const funcName = "fetchPOST"; Guard.isNothing(url, funcName, "url"); Guard.isNothing(body, funcName, "body"); @@ -60,7 +60,7 @@ export abstract class WebApiClient { * @param body The body of the request. * @returns The response from the request. */ - protected async requestPATCH(url: string, body: string): Promise { + public async requestPATCH(url: string, body: string): Promise { const funcName = "fetchPATCH"; Guard.isNothing(url, funcName, "url"); Guard.isNothing(body, funcName, "body"); @@ -77,7 +77,7 @@ export abstract class WebApiClient { * @param url The URL of the request. * @returns The response from the request. */ - protected async requestDELETE(url: string): Promise { + public async requestDELETE(url: string): Promise { Guard.isNothing(url, "fetchDELETE", "url"); return await fetch(url, { @@ -92,7 +92,7 @@ export abstract class WebApiClient { * @param body The body of the request. * @returns The response from the request. */ - protected async requestPUT(url: string, body: string): Promise { + public async requestPUT(url: string, body: string): Promise { const funcName = "fetchPUT"; Guard.isNothing(url, funcName, "url"); Guard.isNothing(body, funcName, "body"); @@ -117,4 +117,14 @@ export abstract class WebApiClient { this.headers.append(name, value); } } + + /** + * Builds an error message from the given error message and response. + * @param errorMsg The error message to use. + * @param response The response to get the data from. + * @returns The error status code and text. + */ + protected buildErrorMsg(errorMessage: string, response: Response): string { + return `${errorMessage}\nError: ${response.status}(${response.statusText})`; + } } diff --git a/deno.json b/deno.json index c644df1..d893da1 100644 --- a/deno.json +++ b/deno.json @@ -1,5 +1,5 @@ { - "version": "v1.0.0-preview.2", + "version": "v1.0.0-preview.3", "lint": { "include": [ "core/", @@ -29,20 +29,5 @@ "indentWidth": 4, "semiColons": true, "singleQuote": false - }, - "test": { - "exclude": ["*.js"], - "include": ["tests/*Tests.ts"] - }, - "imports": { - "core/": "./core/", - "cicd-core/": "./.github/cicd/core/", - "github/": "./GitHubClients/", - "other/": "./OtherClients/", - "package/": "./PackageClients/", - "models/": "./core/Models/", - "twitter": "npm:twitter-api-v2@1.15.0", - "assertions/": "https://deno.land/std@0.203.0/assert/", - "std/": "https://deno.land/std@0.203.0/" } } diff --git a/deno.lock b/deno.lock index 53f84d2..c922a15 100644 --- a/deno.lock +++ b/deno.lock @@ -2,9 +2,43 @@ "version": "3", "packages": { "specifiers": { + "npm:chalk@4.1.1": "npm:chalk@4.1.1", "npm:twitter-api-v2@1.15.0": "npm:twitter-api-v2@1.15.0" }, "npm": { + "ansi-styles@4.3.0": { + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "color-convert@2.0.1" + } + }, + "chalk@4.1.1": { + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dependencies": { + "ansi-styles": "ansi-styles@4.3.0", + "supports-color": "supports-color@7.2.0" + } + }, + "color-convert@2.0.1": { + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "color-name@1.1.4" + } + }, + "color-name@1.1.4": { + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dependencies": {} + }, + "has-flag@4.0.0": { + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dependencies": {} + }, + "supports-color@7.2.0": { + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "has-flag@4.0.0" + } + }, "twitter-api-v2@1.15.0": { "integrity": "sha512-Cqg3pIGhSwPyFBndpBrucdeNXecNFnYcXy3ixQ4brJHd/3k1CAtBVcX0e3s6jRYl/QIx5BmyGXS/SHEGtYZ3gw==", "dependencies": {} @@ -47,6 +81,87 @@ "https://deno.land/std@0.203.0/encoding/_util.ts": "f368920189c4fe6592ab2e93bd7ded8f3065b84f95cd3e036a4a10a75649dcba", "https://deno.land/std@0.203.0/encoding/base64.ts": "cc03110d6518170aeaa68ec97f89c6d6e2276294b30807e7332591d7ce2e4b72", "https://deno.land/std@0.203.0/fmt/colors.ts": "c51c4642678eb690dcf5ffee5918b675bf01a33fba82acf303701ae1a4f8c8d9", - "https://deno.land/std@0.203.0/fs/exists.ts": "cb59a853d84871d87acab0e7936a4dac11282957f8e195102c5a7acb42546bb8" + "https://deno.land/std@0.203.0/fs/_util.ts": "fbf57dcdc9f7bc8128d60301eece608246971a7836a3bb1e78da75314f08b978", + "https://deno.land/std@0.203.0/fs/copy.ts": "23cc1c465babe5ca4d69778821e2f8addc44593e30a5ca0b902b3784eed75bb6", + "https://deno.land/std@0.203.0/fs/empty_dir.ts": "2e52cd4674d18e2e007175c80449fc3d263786a1361e858d9dfa9360a6581b47", + "https://deno.land/std@0.203.0/fs/ensure_dir.ts": "dc64c4c75c64721d4e3fb681f1382f803ff3d2868f08563ff923fdd20d071c40", + "https://deno.land/std@0.203.0/fs/ensure_file.ts": "39ac83cc283a20ec2735e956adf5de3e8a3334e0b6820547b5772f71c49ae083", + "https://deno.land/std@0.203.0/fs/ensure_link.ts": "c15e69c48556d78aae31b83e0c0ece04b7b8bc0951412f5b759aceb6fde7f0ac", + "https://deno.land/std@0.203.0/fs/ensure_symlink.ts": "b389c8568f0656d145ac7ece472afe710815cccbb2ebfd19da7978379ae143fe", + "https://deno.land/std@0.203.0/fs/eol.ts": "f1f2eb348a750c34500741987b21d65607f352cf7205f48f4319d417fff42842", + "https://deno.land/std@0.203.0/fs/exists.ts": "cb59a853d84871d87acab0e7936a4dac11282957f8e195102c5a7acb42546bb8", + "https://deno.land/std@0.203.0/fs/expand_glob.ts": "52b8b6f5b1fa585c348250da1c80ce5d820746cb4a75d874b3599646f677d3a7", + "https://deno.land/std@0.203.0/fs/mod.ts": "bc3d0acd488cc7b42627044caf47d72019846d459279544e1934418955ba4898", + "https://deno.land/std@0.203.0/fs/move.ts": "b4f8f46730b40c32ea3c0bc8eb0fd0e8139249a698883c7b3756424cf19785c9", + "https://deno.land/std@0.203.0/fs/walk.ts": "a16146724a6aaf9efdb92023a74e9805195c3469900744ce5de4113b07b29779", + "https://deno.land/std@0.203.0/path/_basename.ts": "057d420c9049821f983f784fd87fa73ac471901fb628920b67972b0f44319343", + "https://deno.land/std@0.203.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", + "https://deno.land/std@0.203.0/path/_dirname.ts": "355e297236b2218600aee7a5301b937204c62e12da9db4b0b044993d9e658395", + "https://deno.land/std@0.203.0/path/_extname.ts": "eaaa5aae1acf1f03254d681bd6a8ce42a9cb5b7ff2213a9d4740e8ab31283664", + "https://deno.land/std@0.203.0/path/_format.ts": "4a99270d6810f082e614309164fad75d6f1a483b68eed97c830a506cc589f8b4", + "https://deno.land/std@0.203.0/path/_from_file_url.ts": "6eadfae2e6f63ad9ee46b26db4a1b16583055c0392acedfb50ed2fc694b6f581", + "https://deno.land/std@0.203.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", + "https://deno.land/std@0.203.0/path/_is_absolute.ts": "05dac10b5e93c63198b92e3687baa2be178df5321c527dc555266c0f4f51558c", + "https://deno.land/std@0.203.0/path/_join.ts": "815f5e85b042285175b1492dd5781240ce126c23bd97bad6b8211fe7129c538e", + "https://deno.land/std@0.203.0/path/_normalize.ts": "a19ec8706b2707f9dd974662a5cd89fad438e62ab1857e08b314a8eb49a34d81", + "https://deno.land/std@0.203.0/path/_os.ts": "30b0c2875f360c9296dbe6b7f2d528f0f9c741cecad2e97f803f5219e91b40a2", + "https://deno.land/std@0.203.0/path/_parse.ts": "0f9b0ff43682dd9964eb1c4398610c4e165d8db9d3ac9d594220217adf480cfa", + "https://deno.land/std@0.203.0/path/_relative.ts": "27bdeffb5311a47d85be26d37ad1969979359f7636c5cd9fcf05dcd0d5099dc5", + "https://deno.land/std@0.203.0/path/_resolve.ts": "7a3616f1093735ed327e758313b79c3c04ea921808ca5f19ddf240cb68d0adf6", + "https://deno.land/std@0.203.0/path/_to_file_url.ts": "a141e4a525303e1a3a0c0571fd024552b5f3553a2af7d75d1ff3a503dcbb66d8", + "https://deno.land/std@0.203.0/path/_to_namespaced_path.ts": "0d5f4caa2ed98ef7a8786286df6af804b50e38859ae897b5b5b4c8c5930a75c8", + "https://deno.land/std@0.203.0/path/_util.ts": "4e191b1bac6b3bf0c31aab42e5ca2e01a86ab5a0d2e08b75acf8585047a86221", + "https://deno.land/std@0.203.0/path/basename.ts": "bdfa5a624c6a45564dc6758ef2077f2822978a6dbe77b0a3514f7d1f81362930", + "https://deno.land/std@0.203.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", + "https://deno.land/std@0.203.0/path/dirname.ts": "b6533f4ee4174a526dec50c279534df5345836dfdc15318400b08c62a62a39dd", + "https://deno.land/std@0.203.0/path/extname.ts": "62c4b376300795342fe1e4746c0de518b4dc9c4b0b4617bfee62a2973a9555cf", + "https://deno.land/std@0.203.0/path/format.ts": "110270b238514dd68455a4c54956215a1aff7e37e22e4427b7771cefe1920aa5", + "https://deno.land/std@0.203.0/path/from_file_url.ts": "9f5cb58d58be14c775ec2e57fc70029ac8b17ed3bd7fe93e475b07280adde0ac", + "https://deno.land/std@0.203.0/path/glob.ts": "593e2c3573883225c25c5a21aaa8e9382a696b8e175ea20a3b6a1471ad17aaed", + "https://deno.land/std@0.203.0/path/is_absolute.ts": "0b92eb35a0a8780e9f16f16bb23655b67dace6a8e0d92d42039e518ee38103c1", + "https://deno.land/std@0.203.0/path/join.ts": "31c5419f23d91655b08ec7aec403f4e4cd1a63d39e28f6e42642ea207c2734f8", + "https://deno.land/std@0.203.0/path/mod.ts": "6e1efb0b13121463aedb53ea51dabf5639a3172ab58c89900bbb72b486872532", + "https://deno.land/std@0.203.0/path/normalize.ts": "6ea523e0040979dd7ae2f1be5bf2083941881a252554c0f32566a18b03021955", + "https://deno.land/std@0.203.0/path/parse.ts": "be8de342bb9e1924d78dc4d93c45215c152db7bf738ec32475560424b119b394", + "https://deno.land/std@0.203.0/path/posix.ts": "0a1c1952d132323a88736d03e92bd236f3ed5f9f079e5823fae07c8d978ee61b", + "https://deno.land/std@0.203.0/path/relative.ts": "8bedac226afd360afc45d451a6c29fabceaf32978526bcb38e0c852661f66c61", + "https://deno.land/std@0.203.0/path/resolve.ts": "133161e4949fc97f9ca67988d51376b0f5eef8968a6372325ab84d39d30b80dc", + "https://deno.land/std@0.203.0/path/separator.ts": "40a3e9a4ad10bef23bc2cd6c610291b6c502a06237c2c4cd034a15ca78dedc1f", + "https://deno.land/std@0.203.0/path/to_file_url.ts": "00e6322373dd51ad109956b775e4e72e5f9fa68ce2c6b04e4af2a6eed3825d31", + "https://deno.land/std@0.203.0/path/to_namespaced_path.ts": "1b1db3055c343ab389901adfbda34e82b7386bcd1c744d54f9c1496ee0fd0c3d", + "https://deno.land/std@0.203.0/path/win32.ts": "8b3f80ef7a462511d5e8020ff490edcaa0a0d118f1b1e9da50e2916bdd73f9dd", + "https://deno.land/std@0.204.0/assert/_constants.ts": "8a9da298c26750b28b326b297316cdde860bc237533b07e1337c021379e6b2a9", + "https://deno.land/std@0.204.0/assert/_diff.ts": "58e1461cc61d8eb1eacbf2a010932bf6a05b79344b02ca38095f9b805795dc48", + "https://deno.land/std@0.204.0/assert/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7", + "https://deno.land/std@0.204.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", + "https://deno.land/std@0.204.0/assert/assert_almost_equals.ts": "e15ca1f34d0d5e0afae63b3f5d975cbd18335a132e42b0c747d282f62ad2cd6c", + "https://deno.land/std@0.204.0/assert/assert_array_includes.ts": "6856d7f2c3544bc6e62fb4646dfefa3d1df5ff14744d1bca19f0cbaf3b0d66c9", + "https://deno.land/std@0.204.0/assert/assert_equals.ts": "d8ec8a22447fbaf2fc9d7c3ed2e66790fdb74beae3e482855d75782218d68227", + "https://deno.land/std@0.204.0/assert/assert_exists.ts": "407cb6b9fb23a835cd8d5ad804e2e2edbbbf3870e322d53f79e1c7a512e2efd7", + "https://deno.land/std@0.204.0/assert/assert_false.ts": "0ccbcaae910f52c857192ff16ea08bda40fdc79de80846c206bfc061e8c851c6", + "https://deno.land/std@0.204.0/assert/assert_greater.ts": "ae2158a2d19313bf675bf7251d31c6dc52973edb12ac64ac8fc7064152af3e63", + "https://deno.land/std@0.204.0/assert/assert_greater_or_equal.ts": "1439da5ebbe20855446cac50097ac78b9742abe8e9a43e7de1ce1426d556e89c", + "https://deno.land/std@0.204.0/assert/assert_instance_of.ts": "3aedb3d8186e120812d2b3a5dea66a6e42bf8c57a8bd927645770bd21eea554c", + "https://deno.land/std@0.204.0/assert/assert_is_error.ts": "c21113094a51a296ffaf036767d616a78a2ae5f9f7bbd464cd0197476498b94b", + "https://deno.land/std@0.204.0/assert/assert_less.ts": "aec695db57db42ec3e2b62e97e1e93db0063f5a6ec133326cc290ff4b71b47e4", + "https://deno.land/std@0.204.0/assert/assert_less_or_equal.ts": "5fa8b6a3ffa20fd0a05032fe7257bf985d207b85685fdbcd23651b70f928c848", + "https://deno.land/std@0.204.0/assert/assert_match.ts": "c4083f80600bc190309903c95e397a7c9257ff8b5ae5c7ef91e834704e672e9b", + "https://deno.land/std@0.204.0/assert/assert_not_equals.ts": "9f1acab95bd1f5fc9a1b17b8027d894509a745d91bac1718fdab51dc76831754", + "https://deno.land/std@0.204.0/assert/assert_not_instance_of.ts": "0c14d3dfd9ab7a5276ed8ed0b18c703d79a3d106102077ec437bfe7ed912bd22", + "https://deno.land/std@0.204.0/assert/assert_not_match.ts": "3796a5b0c57a1ce6c1c57883dd4286be13a26f715ea662318ab43a8491a13ab0", + "https://deno.land/std@0.204.0/assert/assert_not_strict_equals.ts": "ca6c6d645e95fbc873d25320efeb8c4c6089a9a5e09f92d7c1c4b6e935c2a6ad", + "https://deno.land/std@0.204.0/assert/assert_object_match.ts": "d8fc2867cfd92eeacf9cea621e10336b666de1874a6767b5ec48988838370b54", + "https://deno.land/std@0.204.0/assert/assert_rejects.ts": "45c59724de2701e3b1f67c391d6c71c392363635aad3f68a1b3408f9efca0057", + "https://deno.land/std@0.204.0/assert/assert_strict_equals.ts": "b1f538a7ea5f8348aeca261d4f9ca603127c665e0f2bbfeb91fa272787c87265", + "https://deno.land/std@0.204.0/assert/assert_string_includes.ts": "b821d39ebf5cb0200a348863c86d8c4c4b398e02012ce74ad15666fc4b631b0c", + "https://deno.land/std@0.204.0/assert/assert_throws.ts": "63784e951475cb7bdfd59878cd25a0931e18f6dc32a6077c454b2cd94f4f4bcd", + "https://deno.land/std@0.204.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", + "https://deno.land/std@0.204.0/assert/equal.ts": "9f1a46d5993966d2596c44e5858eec821859b45f783a5ee2f7a695dfc12d8ece", + "https://deno.land/std@0.204.0/assert/fail.ts": "c36353d7ae6e1f7933d45f8ea51e358c8c4b67d7e7502028598fe1fea062e278", + "https://deno.land/std@0.204.0/assert/mod.ts": "37c49a26aae2b254bbe25723434dc28cd7532e444cf0b481a97c045d110ec085", + "https://deno.land/std@0.204.0/assert/unimplemented.ts": "d56fbeecb1f108331a380f72e3e010a1f161baa6956fd0f7cf3e095ae1a4c75a", + "https://deno.land/std@0.204.0/assert/unreachable.ts": "4600dc0baf7d9c15a7f7d234f00c23bca8f3eba8b140286aaca7aa998cf9a536", + "https://deno.land/std@0.204.0/fmt/colors.ts": "c51c4642678eb690dcf5ffee5918b675bf01a33fba82acf303701ae1a4f8c8d9", + "https://deno.land/std@0.204.0/testing/mock.ts": "6576b4aa55ee20b1990d656a78fff83599e190948c00e9f25a7f3ac5e9d6492d" } } diff --git a/deps.ts b/deps.ts new file mode 100644 index 0000000..f76f56d --- /dev/null +++ b/deps.ts @@ -0,0 +1,14 @@ +import chalk from "npm:chalk@4.1.1"; +import { decodeBase64, encodeBase64 } from "https://deno.land/std@0.203.0/encoding/base64.ts"; +import { existsSync } from "https://deno.land/std@0.203.0/fs/exists.ts"; +import { TweetV2PostTweetResult, TwitterApi } from "npm:twitter-api-v2@1.15.0"; +import { assert, assertEquals, assertThrows, assertRejects, equal } from "https://deno.land/std@0.204.0/assert/mod.ts"; +import { assertSpyCall, assertSpyCalls, spy, stub, returnsNext, returnsArg } from "https://deno.land/std@0.204.0/testing/mock.ts"; + +export { decodeBase64, encodeBase64 }; +export { existsSync }; +export { TwitterApi }; +export type { TweetV2PostTweetResult }; +export { chalk }; +export { assert, assertEquals, assertThrows, assertRejects, equal }; +export { assertSpyCall, assertSpyCalls, spy, stub, returnsNext, returnsArg } diff --git a/mod.ts b/mod.ts index bb3c8c6..c5b7101 100644 --- a/mod.ts +++ b/mod.ts @@ -1,14 +1,14 @@ -export { LabelClient } from "github/LabelClient.ts"; -export { IssueClient } from "github/IssueClient.ts"; -export { GitClient } from "github/GitClient.ts"; -export { MilestoneClient } from "github/MilestoneClient.ts"; -export { OrgClient } from "github/OrgClient.ts"; -export { ProjectClient } from "github/ProjectClient.ts"; -export { PullRequestClient } from "github/PullRequestClient.ts"; -export { ReleaseClient } from "github/ReleaseClient.ts"; -export { RepoClient } from "github/RepoClient.ts"; -export { TagClient } from "github/TagClient.ts"; -export { UsersClient } from "github/UsersClient.ts"; -export { WorkflowClient } from "github/WorkflowClient.ts"; -export { XClient } from "other/XClient.ts"; -export { NuGetClient } from "package/NuGetClient.ts"; +export { LabelClient } from "./GitHubClients/LabelClient.ts"; +export { IssueClient } from "./GitHubClients/IssueClient.ts"; +export { GitClient } from "./GitHubClients/GitClient.ts"; +export { MilestoneClient } from "./GitHubClients/MilestoneClient.ts"; +export { OrgClient } from "./GitHubClients/OrgClient.ts"; +export { ProjectClient } from "./GitHubClients/ProjectClient.ts"; +export { PullRequestClient } from "./GitHubClients/PullRequestClient.ts"; +export { ReleaseClient } from "./GitHubClients/ReleaseClient.ts"; +export { RepoClient } from "./GitHubClients/RepoClient.ts"; +export { TagClient } from "./GitHubClients/TagClient.ts"; +export { UsersClient } from "./GitHubClients/UsersClient.ts"; +export { WorkflowClient } from "./GitHubClients/WorkflowClient.ts"; +export { XClient } from "./OtherClients/XClient.ts"; +export { NuGetClient } from "./PackageClients/NuGetClient.ts"; diff --git a/tests/RepoClientTests.ts b/tests/RepoClientTests.ts new file mode 100644 index 0000000..7c4e007 --- /dev/null +++ b/tests/RepoClientTests.ts @@ -0,0 +1,44 @@ +import { RepoError } from "../GitHubClients/Errors/RepoError.ts"; +import { RepoModel } from "../core/Models/RepoModel.ts"; +import { assertEquals, assertRejects, assertSpyCalls, stub } from "../deps.ts"; +import { RepoClient } from "../mod.ts"; + +Deno.test("getRepo |> when_invoked |> gets_repository", async () => { + // Arrange + const client = new RepoClient("test-owner", "test-repo", "test-token"); + const data: RepoModel[] = [{ + id: 1, + name: "test-repo", + url: "test-url", + }]; + const response: Response = new Response(null, { status: 200 }); + + const myStub = stub( + client, + "getOwnerRepos", + (_page, _qtyPerPage) => Promise.resolve<[RepoModel[], Response]>([data, response])); + + + // Act + const actual = await client.getRepo(); + + // Assert + assertEquals(actual, data[0]); + assertSpyCalls(myStub, 1); +}); + +Deno.test("getRepo |> when_repo_does_not_exist |> throws_error", async () => { + // Arrange + const client = new RepoClient("test-owner", "test-repo", "test-token"); + const data: RepoModel[] = [{ + id: 1, + name: "other-repo", + url: "test-url", + }]; + const response: Response = new Response(null, { status: 200 }); + + stub(client, "getOwnerRepos", (_page, _qtyPerPage) => Promise.resolve<[RepoModel[], Response]>([data, response])); + + // Act & Assert + await assertRejects(async () => await client.getRepo(), RepoError, "The repository 'test-repo' was not found."); +}); diff --git a/tests/UtilsTests.ts b/tests/UtilsTests.ts index 8a7de72..b1439e5 100644 --- a/tests/UtilsTests.ts +++ b/tests/UtilsTests.ts @@ -1,5 +1,5 @@ -import { assertEquals } from "assertions/mod.ts"; -import { Utils } from "core/Utils.ts"; +import { assertEquals } from "../deps.ts"; +import { Utils } from "../core/Utils.ts"; Deno.test("clamp |> when_num_is_larger_than_max |> returns_max_value", () => { // Arrange @@ -13,7 +13,6 @@ Deno.test("clamp |> when_num_is_larger_than_max |> returns_max_value", () => { assertEquals(actual, expected); }); - Deno.test("clamp |> when_num_is_smaller_than_max |> returns_min_value", () => { // Arrange const expected = 100; @@ -26,7 +25,6 @@ Deno.test("clamp |> when_num_is_smaller_than_max |> returns_min_value", () => { assertEquals(actual, expected); }); - Deno.test("invalidReleaseType |> with_invalid_type |> returns_true", () => { // Arrange const invalidValue = "is_invalid"; @@ -49,7 +47,6 @@ Deno.test("invalidReleaseType |> with_valid_preview_type |> returns_false", () = assertEquals(actual, false); }); - Deno.test("invalidReleaseType |> with_valid_prod_type |> returns_false", () => { // Arrange const validValue = "production"; diff --git a/vendor/deno.land/std@0.203.0/encoding/_util.ts b/vendor/deno.land/std@0.203.0/encoding/_util.ts deleted file mode 100644 index b829da9..0000000 --- a/vendor/deno.land/std@0.203.0/encoding/_util.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -const encoder = new TextEncoder(); - -function getTypeName(value: unknown): string { - const type = typeof value; - if (type !== "object") { - return type; - } else if (value === null) { - return "null"; - } else { - return value?.constructor?.name ?? "object"; - } -} - -export function validateBinaryLike(source: unknown): Uint8Array { - if (typeof source === "string") { - return encoder.encode(source); - } else if (source instanceof Uint8Array) { - return source; - } else if (source instanceof ArrayBuffer) { - return new Uint8Array(source); - } - throw new TypeError( - `The input must be a Uint8Array, a string, or an ArrayBuffer. Received a value of the type ${ - getTypeName(source) - }.`, - ); -} diff --git a/vendor/deno.land/std@0.203.0/encoding/base64.ts b/vendor/deno.land/std@0.203.0/encoding/base64.ts deleted file mode 100644 index c8d42d8..0000000 --- a/vendor/deno.land/std@0.203.0/encoding/base64.ts +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -// This module is browser compatible. - -import { validateBinaryLike } from "./_util.ts"; - -/** - * {@linkcode encodeBase64} and {@linkcode decodeBase64} for - * [base64](https://en.wikipedia.org/wiki/Base64) encoding. - * - * This module is browser compatible. - * - * @example - * ```ts - * import { - * decodeBase64, - * encodeBase64, - * } from "https://deno.land/std@$STD_VERSION/encoding/base64.ts"; - * - * const b64Repr = "Zm9vYg=="; - * - * const binaryData = decodeBase64(b64Repr); - * console.log(binaryData); - * // => Uint8Array [ 102, 111, 111, 98 ] - * - * console.log(encodeBase64(binaryData)); - * // => Zm9vYg== - * ``` - * - * @module - */ - -const base64abc = [ - "A", - "B", - "C", - "D", - "E", - "F", - "G", - "H", - "I", - "J", - "K", - "L", - "M", - "N", - "O", - "P", - "Q", - "R", - "S", - "T", - "U", - "V", - "W", - "X", - "Y", - "Z", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "0", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "+", - "/", -]; - -/** - * @deprecated (will be removed in 0.210.0) Use a `encodeBase64` instead. - * - * CREDIT: https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727 - * Encodes a given Uint8Array, ArrayBuffer or string into RFC4648 base64 representation - * @param data - */ -export const encode = encodeBase64; - -/** - * @deprecated (will be removed in 0.210.0) Use a `decodeBase64` instead. - * - * Decodes a given RFC4648 base64 encoded string - * @param b64 - */ -export const decode = decodeBase64; - -/** - * Encodes a given Uint8Array, ArrayBuffer or string into RFC4648 base64 representation - */ -export function encodeBase64(data: ArrayBuffer | Uint8Array | string): string { - // CREDIT: https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727 - const uint8 = validateBinaryLike(data); - let result = "", - i; - const l = uint8.length; - for (i = 2; i < l; i += 3) { - result += base64abc[uint8[i - 2] >> 2]; - result += base64abc[((uint8[i - 2] & 0x03) << 4) | (uint8[i - 1] >> 4)]; - result += base64abc[((uint8[i - 1] & 0x0f) << 2) | (uint8[i] >> 6)]; - result += base64abc[uint8[i] & 0x3f]; - } - if (i === l + 1) { - // 1 octet yet to write - result += base64abc[uint8[i - 2] >> 2]; - result += base64abc[(uint8[i - 2] & 0x03) << 4]; - result += "=="; - } - if (i === l) { - // 2 octets yet to write - result += base64abc[uint8[i - 2] >> 2]; - result += base64abc[((uint8[i - 2] & 0x03) << 4) | (uint8[i - 1] >> 4)]; - result += base64abc[(uint8[i - 1] & 0x0f) << 2]; - result += "="; - } - return result; -} - -/** - * Decodes a given RFC4648 base64 encoded string - */ -export function decodeBase64(b64: string): Uint8Array { - const binString = atob(b64); - const size = binString.length; - const bytes = new Uint8Array(size); - for (let i = 0; i < size; i++) { - bytes[i] = binString.charCodeAt(i); - } - return bytes; -} diff --git a/vendor/deno.land/std@0.203.0/fs/exists.ts b/vendor/deno.land/std@0.203.0/fs/exists.ts deleted file mode 100644 index fcd6287..0000000 --- a/vendor/deno.land/std@0.203.0/fs/exists.ts +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -export interface ExistsOptions { - /** - * When `true`, will check if the path is readable by the user as well. - * @default {false} - */ - isReadable?: boolean; - /** - * When `true`, will check if the path is a directory as well. - * Directory symlinks are included. - * @default {false} - */ - isDirectory?: boolean; - /** - * When `true`, will check if the path is a file as well. - * File symlinks are included. - * @default {false} - */ - isFile?: boolean; -} - -/** - * Test whether or not the given path exists by checking with the file system. Please consider to check if the path is readable and either a file or a directory by providing additional `options`: - * - * ```ts - * import { exists } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; - * const isReadableDir = await exists("./foo", { - * isReadable: true, - * isDirectory: true - * }); - * const isReadableFile = await exists("./bar", { - * isReadable: true, - * isFile: true - * }); - * ``` - * - * Note: Do not use this function if performing a check before another operation on that file. Doing so creates a race condition. Instead, perform the actual file operation directly. - * - * Bad: - * ```ts - * import { exists } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; - * - * if (await exists("./foo")) { - * await Deno.remove("./foo"); - * } - * ``` - * - * Good: - * ```ts - * // Notice no use of exists - * try { - * await Deno.remove("./foo", { recursive: true }); - * } catch (error) { - * if (!(error instanceof Deno.errors.NotFound)) { - * throw error; - * } - * // Do nothing... - * } - * ``` - * @see https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use - */ -export async function exists( - path: string | URL, - options?: ExistsOptions, -): Promise { - try { - const stat = await Deno.stat(path); - if ( - options && - (options.isReadable || options.isDirectory || options.isFile) - ) { - if (options.isDirectory && options.isFile) { - throw new TypeError( - "ExistsOptions.options.isDirectory and ExistsOptions.options.isFile must not be true together.", - ); - } - if ( - (options.isDirectory && !stat.isDirectory) || - (options.isFile && !stat.isFile) - ) { - return false; - } - if (options.isReadable) { - if (stat.mode === null) { - return true; // Exclusive on Non-POSIX systems - } - if (Deno.uid() === stat.uid) { - return (stat.mode & 0o400) === 0o400; // User is owner and can read? - } else if (Deno.gid() === stat.gid) { - return (stat.mode & 0o040) === 0o040; // User group is owner and can read? - } - return (stat.mode & 0o004) === 0o004; // Others can read? - } - } - return true; - } catch (error) { - if (error instanceof Deno.errors.NotFound) { - return false; - } - if (error instanceof Deno.errors.PermissionDenied) { - if ( - (await Deno.permissions.query({ name: "read", path })).state === - "granted" - ) { - // --allow-read not missing - return !options?.isReadable; // PermissionDenied was raised by file system, so the item exists, but can't be read - } - } - throw error; - } -} - -/** - * Test whether or not the given path exists by checking with the file system. Please consider to check if the path is readable and either a file or a directory by providing additional `options`: - * - * ```ts - * import { existsSync } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; - * const isReadableDir = existsSync("./foo", { - * isReadable: true, - * isDirectory: true - * }); - * const isReadableFile = existsSync("./bar", { - * isReadable: true, - * isFile: true - * }); - * ``` - * - * Note: do not use this function if performing a check before another operation on that file. Doing so creates a race condition. Instead, perform the actual file operation directly. - * - * Bad: - * ```ts - * import { existsSync } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; - * - * if (existsSync("./foo")) { - * Deno.removeSync("./foo"); - * } - * ``` - * - * Good: - * ```ts - * // Notice no use of existsSync - * try { - * Deno.removeSync("./foo", { recursive: true }); - * } catch (error) { - * if (!(error instanceof Deno.errors.NotFound)) { - * throw error; - * } - * // Do nothing... - * } - * ``` - * @see https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use - */ -export function existsSync( - path: string | URL, - options?: ExistsOptions, -): boolean { - try { - const stat = Deno.statSync(path); - if ( - options && - (options.isReadable || options.isDirectory || options.isFile) - ) { - if (options.isDirectory && options.isFile) { - throw new TypeError( - "ExistsOptions.options.isDirectory and ExistsOptions.options.isFile must not be true together.", - ); - } - if ( - (options.isDirectory && !stat.isDirectory) || - (options.isFile && !stat.isFile) - ) { - return false; - } - if (options.isReadable) { - if (stat.mode === null) { - return true; // Exclusive on Non-POSIX systems - } - if (Deno.uid() === stat.uid) { - return (stat.mode & 0o400) === 0o400; // User is owner and can read? - } else if (Deno.gid() === stat.gid) { - return (stat.mode & 0o040) === 0o040; // User group is owner and can read? - } - return (stat.mode & 0o004) === 0o004; // Others can read? - } - } - return true; - } catch (error) { - if (error instanceof Deno.errors.NotFound) { - return false; - } - if (error instanceof Deno.errors.PermissionDenied) { - if ( - Deno.permissions.querySync({ name: "read", path }).state === "granted" - ) { - // --allow-read not missing - return !options?.isReadable; // PermissionDenied was raised by file system, so the item exists, but can't be read - } - } - throw error; - } -} diff --git a/vendor/import_map.json b/vendor/import_map.json deleted file mode 100644 index 806f270..0000000 --- a/vendor/import_map.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "imports": { - "package/": "../PackageClients/", - "other/": "../OtherClients/", - "models/": "../core/Models/", - "github/": "../GitHubClients/", - "core/": "../core/", - "cicd-core/": "../.github/cicd/core/", - "std/fs/exists.ts": "./deno.land/std@0.203.0/fs/exists.ts", - "std/encoding/base64.ts": "./deno.land/std@0.203.0/encoding/base64.ts", - "https://deno.land/": "./deno.land/" - } -}