diff --git a/__tests__/github.test.ts b/__tests__/github.test.ts index 6202c0b..19ab4ae 100644 --- a/__tests__/github.test.ts +++ b/__tests__/github.test.ts @@ -1,6 +1,11 @@ import * as assert from "assert"; -import { text } from "stream/consumers"; -import { mimeOrDefault, asset } from "../src/github"; +import { + mimeOrDefault, + asset, + Releaser, + Release, + findTagFromReleases, +} from "../src/github"; describe("github", () => { describe("mimeOrDefault", () => { @@ -20,4 +25,251 @@ describe("github", () => { assert.equal(size, 10); }); }); + + describe("findTagFromReleases", () => { + const owner = "owner"; + const repo = "repo"; + + const mockRelease: Release = { + id: 1, + upload_url: `https://api.github.com/repos/${owner}/${repo}/releases/1/assets`, + html_url: `https://github.com/${owner}/${repo}/releases/tag/v1.0.0`, + tag_name: "v1.0.0", + name: "Test Release", + body: "Test body", + target_commitish: "main", + draft: false, + prerelease: false, + assets: [], + } as const; + + const mockReleaser: Releaser = { + getReleaseByTag: () => Promise.reject("Not implemented"), + createRelease: () => Promise.reject("Not implemented"), + updateRelease: () => Promise.reject("Not implemented"), + allReleases: async function* () { + yield { data: [mockRelease] }; + }, + } as const; + + describe("when the tag_name is not an empty string", () => { + const targetTag = "v1.0.0"; + + it("finds a matching release in first batch of results", async () => { + const targetRelease = { + ...mockRelease, + owner, + repo, + tag_name: targetTag, + }; + const otherRelease = { + ...mockRelease, + owner, + repo, + tag_name: "v1.0.1", + }; + + const releaser = { + ...mockReleaser, + allReleases: async function* () { + yield { data: [targetRelease] }; + yield { data: [otherRelease] }; + }, + }; + + const result = await findTagFromReleases( + releaser, + owner, + repo, + targetTag, + ); + + assert.deepStrictEqual(result, targetRelease); + }); + + it("finds a matching release in second batch of results", async () => { + const targetRelease = { + ...mockRelease, + owner, + repo, + tag_name: targetTag, + }; + const otherRelease = { + ...mockRelease, + owner, + repo, + tag_name: "v1.0.1", + }; + + const releaser = { + ...mockReleaser, + allReleases: async function* () { + yield { data: [otherRelease] }; + yield { data: [targetRelease] }; + }, + }; + + const result = await findTagFromReleases( + releaser, + owner, + repo, + targetTag, + ); + assert.deepStrictEqual(result, targetRelease); + }); + + it("returns undefined when a release is not found in any batch", async () => { + const otherRelease = { + ...mockRelease, + owner, + repo, + tag_name: "v1.0.1", + }; + const releaser = { + ...mockReleaser, + allReleases: async function* () { + yield { data: [otherRelease] }; + yield { data: [otherRelease] }; + }, + }; + + const result = await findTagFromReleases( + releaser, + owner, + repo, + targetTag, + ); + + assert.strictEqual(result, undefined); + }); + + it("returns undefined when no releases are returned", async () => { + const releaser = { + ...mockReleaser, + allReleases: async function* () { + yield { data: [] }; + }, + }; + + const result = await findTagFromReleases( + releaser, + owner, + repo, + targetTag, + ); + + assert.strictEqual(result, undefined); + }); + }); + + describe("when the tag_name is an empty string", () => { + const emptyTag = ""; + + it("finds a matching release in first batch of results", async () => { + const targetRelease = { + ...mockRelease, + owner, + repo, + tag_name: emptyTag, + }; + const otherRelease = { + ...mockRelease, + owner, + repo, + tag_name: "v1.0.1", + }; + + const releaser = { + ...mockReleaser, + allReleases: async function* () { + yield { data: [targetRelease] }; + yield { data: [otherRelease] }; + }, + }; + + const result = await findTagFromReleases( + releaser, + owner, + repo, + emptyTag, + ); + + assert.deepStrictEqual(result, targetRelease); + }); + + it("finds a matching release in second batch of results", async () => { + const targetRelease = { + ...mockRelease, + owner, + repo, + tag_name: emptyTag, + }; + const otherRelease = { + ...mockRelease, + owner, + repo, + tag_name: "v1.0.1", + }; + + const releaser = { + ...mockReleaser, + allReleases: async function* () { + yield { data: [otherRelease] }; + yield { data: [targetRelease] }; + }, + }; + + const result = await findTagFromReleases( + releaser, + owner, + repo, + emptyTag, + ); + assert.deepStrictEqual(result, targetRelease); + }); + + it("returns undefined when a release is not found in any batch", async () => { + const otherRelease = { + ...mockRelease, + owner, + repo, + tag_name: "v1.0.1", + }; + const releaser = { + ...mockReleaser, + allReleases: async function* () { + yield { data: [otherRelease] }; + yield { data: [otherRelease] }; + }, + }; + + const result = await findTagFromReleases( + releaser, + owner, + repo, + emptyTag, + ); + + assert.strictEqual(result, undefined); + }); + + it("returns undefined when no releases are returned", async () => { + const releaser = { + ...mockReleaser, + allReleases: async function* () { + yield { data: [] }; + }, + }; + + const result = await findTagFromReleases( + releaser, + owner, + repo, + emptyTag, + ); + + assert.strictEqual(result, undefined); + }); + }); + }); }); diff --git a/src/github.ts b/src/github.ts index 7f8015b..35af897 100644 --- a/src/github.ts +++ b/src/github.ts @@ -229,16 +229,7 @@ export const release = async ( // so we must find one in the list of all releases let _release: Release | undefined = undefined; if (config.input_draft) { - for await (const response of releaser.allReleases({ - owner, - repo, - })) { - _release = response.data.find((release) => release.tag_name === tag); - // detect if we found a release - note that a draft release tag may be an empty string - if (typeof _release !== "undefined") { - break; - } - } + _release = await findTagFromReleases(releaser, owner, repo, tag); } else { _release = ( await releaser.getReleaseByTag({ @@ -342,6 +333,35 @@ export const release = async ( } }; +/** + * Finds a release by tag name from all a repository's releases. + * + * @param releaser - The GitHub API wrapper for release operations + * @param owner - The owner of the repository + * @param repo - The name of the repository + * @param tag - The tag name to search for + * @returns The release with the given tag name, or undefined if no release with that tag name is found + */ +export async function findTagFromReleases( + releaser: Releaser, + owner: string, + repo: string, + tag: string, +) { + let _release: Release | undefined; + for await (const response of releaser.allReleases({ + owner, + repo, + })) { + _release = response.data.find((release) => release.tag_name === tag); + // detect if we found a release - note that a draft release tag may be an empty string + if (typeof _release !== "undefined") { + break; + } + } + return _release; +} + async function createRelease( tag: string, config: Config, diff --git a/src/main.ts b/src/main.ts index 438be84..4d37e9a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -67,9 +67,13 @@ async function run() { const files = paths(config.input_files); if (files.length == 0) { if (config.input_fail_on_unmatched_files) { - throw new Error(`⚠️ ${config.input_files} does not include a valid file.`); + throw new Error( + `⚠️ ${config.input_files} does not include a valid file.`, + ); } else { - console.warn(`🤔 ${config.input_files} does not include a valid file.`); + console.warn( + `🤔 ${config.input_files} does not include a valid file.`, + ); } } const currentAssets = rel.assets;