Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🚧Add new funcs to release client #66

Merged
merged 11 commits into from
May 15, 2024
160 changes: 150 additions & 10 deletions GitHubClients/ReleaseClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,14 +182,91 @@ export class ReleaseClient extends GitHubClient {
}

/**
* Uploads one or more assets to a release that matches a tag or title by the given {@link tag}.
* Returns a value indicating whether a release with an id or tag matches the given {@link releaseIdOrTag},
* and a release asset with an id or name matches the given {@link assetIdOrName}.
* @param releaseIdOrTag The release id or tag name.
* @param assetIdOrName The asset id or name.
* @returns True if the asset exists, otherwise false.
* @throws The following errors:
* 1. An {@link AuthError} if the request is unauthorized.
* 2. A {@link ReleaseError} if the parameters are a value less than or equal to 0, undefined, null, or empty.
*/
public async assetExists(releaseIdOrTag: number | string, assetIdOrName: number | string): Promise<boolean> {
const funcName = "assetExists";
Guard.isNothing(releaseIdOrTag, funcName, "releaseIdOrTag");
Guard.isNothing(assetIdOrName, funcName, "assetIdOrName");

if (Utils.isNumber(releaseIdOrTag)) {
Guard.isLessThanOne(releaseIdOrTag);
}

if (Utils.isNumber(assetIdOrName)) {
Guard.isLessThanOne(assetIdOrName);
}

const release = Utils.isNumber(releaseIdOrTag)
? await this.getReleaseById(releaseIdOrTag)
: await this.getReleaseByTag(releaseIdOrTag);

return release.assets.find((asset) => {
return Utils.isNumber(assetIdOrName) ? asset.id === assetIdOrName : asset.name === assetIdOrName;
}) !== undefined;
}

/**
* Uploads an asset to a release that matches a tag or id by the given {@link releaseIdOrTag}.
* @param releaseIdOrTag The release id or tag name.
* @param filePath The fully qualified or relative path to the asset to upload.
* @param overwrite True to overwrite the asset if it exists, otherwise false.
* @remarks If the {@link overwrite} is set to 'false', an error will be thrown if the asset already exists.
* @throws Throws the following errors:
* 1. An {@link Error} if the {@link releaseIdOrTag} or {@link filePath} are undefined, null, or empty.
* 2. A {@link ReleaseError} if the file path does not exist.
* 3. A {@link ReleaseError} if the asset already exists and {@link overwrite} is set to false.
* 4. An {@link AuthError} if the request is unauthorized.
*/
public async uploadAsset(releaseIdOrTag: number | string, filePath: string, overwrite?: boolean): Promise<void> {
const funcName = "uploadAsset";
Guard.isNothing(releaseIdOrTag, funcName, "releaseIdOrTag");
Guard.isNothing(filePath, funcName, "filePath");

if (Utils.isNumber(releaseIdOrTag)) {
Guard.isLessThanOne(releaseIdOrTag);
}

filePath = this.normalizePath(filePath);

if (overwrite === true) {
const assetName = basename(filePath);
const assetExists = await this.assetExists(releaseIdOrTag, assetName);

if (assetExists) {
const asset = await this.getAsset(releaseIdOrTag, assetName);

await this.deleteAsset(releaseIdOrTag, asset.id);
}
}

if (!existsSync(filePath)) {
throw new ReleaseError(`The file path '${filePath}' does not exist.`);
}

const release = Utils.isNumber(releaseIdOrTag)
? await this.getReleaseById(releaseIdOrTag)
: await this.getReleaseByTag(releaseIdOrTag);

await this.uploadAssetInternal(filePath, release.id);
}

/**
* Uploads one or more assets to a release with a name that matches the given {@link tag}.
* @param tag The tag or title of the release to upload the asset to.
* @param filePaths One or more relative or fully qualified paths of files to upload.
* @throws A {@link ReleaseError} if there was an issue uploading the asset.
* @returns An asynchronous promise of the operation.
* @throws An {@link AuthError} or {@link ReleaseError}.
*/
public async uploadAssetsByReleaseTag(tag: string, filePaths: string | string[]): Promise<void> {
public async uploadAssetsByReleaseTag(tag: string, filePaths: string[]): Promise<void> {
const funcName = "uploadAssetsByReleaseTag";
Guard.isNothing(tag, funcName, "tagOrTitle");
Guard.isNothing(filePaths, funcName, "filePaths");
Expand All @@ -201,7 +278,7 @@ export class ReleaseClient extends GitHubClient {
throw new ReleaseError(errorMsg);
}

const filesToUpload = Array.isArray(filePaths) ? filePaths.map((p) => p.trim()) : [filePaths.trim()];
const filesToUpload = filePaths.map((p) => p.trim());

const invalidPaths = filesToUpload.filter((filePath: string) => Utils.isNotFilePath(filePath));

Expand All @@ -226,7 +303,7 @@ export class ReleaseClient extends GitHubClient {

// Gather all of the work to be done
for (const filePath of filesToUpload) {
uploadWork.push(this.uploadFile(filePath, release.id));
uploadWork.push(this.uploadAssetInternal(filePath, release.id));
}

// Wait for completion of all the uploads
Expand All @@ -248,15 +325,15 @@ export class ReleaseClient extends GitHubClient {
}

/**
* Uploads one or more assets to a release that matches a tag or title by the given {@link name}.
* Uploads one or more assets to a release with a name that matches the given {@link name}.
* @param name The tag or title of the release to upload the asset to.
* @param filePaths One or more relative or fully qualified paths of files to upload.
* @param options Various options to use when uploading the asset.
* @throws A {@link ReleaseError} if there was an issue uploading the asset.
* @returns An asynchronous promise of the operation.
* @throws An {@link AuthError} or {@link ReleaseError}.
*/
public async uploadAssetsByReleaseName(name: string, filePaths: string | string[]): Promise<void> {
public async uploadAssetsByReleaseName(name: string, filePaths: string[]): Promise<void> {
const funcName = "uploadAssetsByReleaseName";
Guard.isNothing(name, funcName, "name");
Guard.isNothing(filePaths, funcName, "filePaths");
Expand All @@ -268,7 +345,7 @@ export class ReleaseClient extends GitHubClient {
throw new ReleaseError(errorMsg);
}

const filesToUpload = Array.isArray(filePaths) ? filePaths.map((p) => p.trim()) : [filePaths.trim()];
const filesToUpload = filePaths.map((p) => p.trim());

const invalidPaths = filesToUpload.filter((filePath: string) => Utils.isNotFilePath(filePath));

Expand All @@ -293,7 +370,7 @@ export class ReleaseClient extends GitHubClient {

// Gather all of the work to be done
for (const filePath of filesToUpload) {
uploadWork.push(this.uploadFile(filePath, release.id));
uploadWork.push(this.uploadAssetInternal(filePath, release.id));
}

// Wait for completion of all the uploads
Expand All @@ -314,6 +391,65 @@ export class ReleaseClient extends GitHubClient {
}
}

/**
* Deletes an asset with an id or name that matches the given {@link assetIdOrName},
* from a release with an id or tag that matches the given {@link releaseIdOrTag}.
* @param releaseIdOrTag The release id or tag name.
* @param assetIdOrName The asset id or name.
* @param errorWhenNotFound True to throw an error if the asset is not found, otherwise false.
* @throws The following errors:
* 1. An {@link Error} if the {@link releaseIdOrTag} or {@link assetIdOrName} are undefined, null, or empty.
* 2. An {@link AuthError} if the request is unauthorized.
* 3. A {@link ReleaseError} if the asset did not exist. This only occurs if the {@link errorWhenNotFound} is set to true.
*/
public async deleteAsset(
releaseIdOrTag: number | string,
assetIdOrName: number | string,
errorWhenNotFound?: boolean,
): Promise<void> {
const funcName = "deleteAsset";
Guard.isNothing(releaseIdOrTag, funcName, "releaseIdOrTag");
Guard.isNothing(assetIdOrName, funcName, "assetIdOrName");

if (Utils.isNumber(releaseIdOrTag)) {
Guard.isLessThanOne(releaseIdOrTag);
}

if (Utils.isNumber(assetIdOrName)) {
Guard.isLessThanOne(assetIdOrName);
}

const release = Utils.isNumber(releaseIdOrTag)
? await this.getReleaseById(releaseIdOrTag)
: await this.getReleaseByTag(releaseIdOrTag);

const asset = release.assets.find((a) => {
return Utils.isNumber(assetIdOrName) ? a.id === assetIdOrName : a.name === assetIdOrName;
});

if (errorWhenNotFound === true && asset === undefined) {
const assetType = Utils.isNumber(assetIdOrName) ? "id" : "name";
const errorMsg = `An asset with the ${assetType} '${assetIdOrName}' could not be found.`;

throw new ReleaseError(errorMsg);
}

const url = `https://api.github.com/repos/${this.ownerName}/${this.repoName}/releases/assets/${assetIdOrName}`;

const response = await this.requestDELETE(url);

if (response.status === GitHubHttpStatusCodes.Unauthorized) {
throw new AuthError();
} else if (response.status === GitHubHttpStatusCodes.NotFound) {
return;
} else if (response.status !== GitHubHttpStatusCodes.NoContent) {
const assetType = Utils.isNumber(assetIdOrName) ? "id" : "name";
const errorMsg = `The asset with the ${assetType} '${assetIdOrName}' could not be deleted.`;

throw new ReleaseError(errorMsg);
}
}

/**
* Downloads an asset with an id that matches the given {@link assetId} to the given {@link dirPath}.
* @param assetId The id of the asset to download.
Expand Down Expand Up @@ -495,15 +631,15 @@ export class ReleaseClient extends GitHubClient {
}

/**
* Uploads a file using the given {@link filePath} to a release that matches the given {@link releaseId}.
* Uploads a file as a release asset at the given {@link filePath} to a release that matches the given {@link releaseId}.
* @param filePath The path of the file to upload.
* @param releaseId The id of the release to upload the file to.
* @param options Various options to use when uploading the file.
* @throws A {@link ReleaseError} if there was an issue uploading the file.
* @returns An asynchronous promise of the operation.
* @throws An {@link AuthError} or {@link ReleaseError}.
*/
private async uploadFile(
private async uploadAssetInternal(
filePath: string,
releaseId: number,
): Promise<void> {
Expand All @@ -521,8 +657,12 @@ export class ReleaseClient extends GitHubClient {

if (response.status === GitHubHttpStatusCodes.Unauthorized) {
throw new AuthError();
} else if (response.status === GitHubHttpStatusCodes.UnprocessableContent) {
// This status code is returned when the asset already exists
throw new ReleaseError(`The asset '${fileName}' already exists in the release with the release id '${releaseId}'.`);
} else if (response.status !== GitHubHttpStatusCodes.Created) {
const errorMsg = `The asset '${fileName}' could not be uploaded to the release with the release id '${releaseId}'.`;

throw new ReleaseError(errorMsg);
}
}
Expand Down
1 change: 1 addition & 0 deletions deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"tasks": {
"check": "powershell cls && deno run -A ./.github/cicd/scripts/deno-check.ts",
"test": "powershell cls && deno test -A ./tests/*Tests.ts",
"test-watch": "powershell cls && deno test --watch -A ./tests/*Tests.ts",
"lint": "powershell cls && deno lint",
"format": "powershell cls && deno fmt",
"format-check": "powershell cls && deno fmt --check",
Expand Down
Loading