From ac5742b4e491d8be2c6768c2df0b7975caee1e3a Mon Sep 17 00:00:00 2001 From: mcasimir Date: Fri, 29 Oct 2021 14:41:07 +0200 Subject: [PATCH] chore: use @mongodb-js/devtools-github-repo --- packages/build/package.json | 1 + packages/build/src/github-repo.spec.ts | 700 ------------------ packages/build/src/github-repo.ts | 340 --------- .../src/homebrew/generate-formula.spec.ts | 2 +- .../build/src/homebrew/generate-formula.ts | 2 +- .../src/homebrew/publish-to-homebrew.spec.ts | 2 +- .../build/src/homebrew/publish-to-homebrew.ts | 2 +- .../src/homebrew/update-homebrew-fork.spec.ts | 2 +- .../src/homebrew/update-homebrew-fork.ts | 2 +- packages/build/src/release.ts | 2 +- packages/build/src/run-draft.spec.ts | 2 +- packages/build/src/run-draft.ts | 2 +- packages/build/src/run-publish.spec.ts | 2 +- packages/build/src/run-publish.ts | 2 +- 14 files changed, 12 insertions(+), 1051 deletions(-) delete mode 100644 packages/build/src/github-repo.spec.ts delete mode 100644 packages/build/src/github-repo.ts diff --git a/packages/build/package.json b/packages/build/package.json index 0713239e7e..46ec3ca211 100644 --- a/packages/build/package.json +++ b/packages/build/package.json @@ -45,6 +45,7 @@ }, "dependencies": { "@mongodb-js/dl-center": "^1.0.1", + "@mongodb-js/devtools-github-repo": "^1.0.1", "node-fetch": "^2.6.0", "pkg-up": "^3.1.0" } diff --git a/packages/build/src/github-repo.spec.ts b/packages/build/src/github-repo.spec.ts deleted file mode 100644 index 0c15f518f8..0000000000 --- a/packages/build/src/github-repo.spec.ts +++ /dev/null @@ -1,700 +0,0 @@ -import { expect } from 'chai'; -import { promises as fs } from 'fs'; -import * as path from 'path'; -import { SinonStub } from 'sinon'; -import sinon from 'ts-sinon'; -import { GithubRepo } from './github-repo'; - -function getTestGithubRepo(octokitStub: any = {}): GithubRepo { - const repo = { - owner: 'mongodb-js', - repo: 'mongosh' - }; - - return new GithubRepo(repo, octokitStub); -} - -class ExistsError extends Error { - status = 422; - errors = [ - { code: 'already_exists' } - ]; - constructor() { - super(); - this.name = 'HttpError'; - } -} - -describe('GithubRepo', () => { - let githubRepo: GithubRepo; - - beforeEach(() => { - githubRepo = getTestGithubRepo(); - }); - - describe('getMostRecentDraftTag', () => { - it('returns undefined if the release version is undefined or empty', async() => { - expect( - await githubRepo.getMostRecentDraftTagForRelease('') - ).to.be.undefined; - - expect( - await githubRepo.getMostRecentDraftTagForRelease(undefined) - ).to.be.undefined; - }); - - it('returns undefined if there is no matching tag', async() => { - githubRepo = getTestGithubRepo({ - paginate: sinon.stub().resolves([ - { name: 'v0.0.6', commit: { sha: 'sha-1' } }, - { name: 'v0.0.3-draft.0', commit: { sha: 'sha-2' } }, - { name: 'v0.0.3-draft.1', commit: { sha: 'sha-3' } }, - { name: 'v0.1.3-draft.8', commit: { sha: 'sha-4' } }, - ]) - }); - - expect( - await githubRepo.getMostRecentDraftTagForRelease('0.0.7') - ).to.be.undefined; - }); - - it('returns the latest draft for a release version if there are multiple', async() => { - githubRepo = getTestGithubRepo({ - paginate: sinon.stub().resolves([ - { name: 'v0.0.6', commit: { sha: 'sha-1' } }, - { name: 'v0.0.30', commit: { sha: 'sha-2' } }, - { name: 'v0.0.30-draft.12', commit: { sha: 'sha-3' } }, - { name: 'v0.0.3-draft.11', commit: { sha: 'sha-4' } }, - { name: 'v0.0.3-draft.2', commit: { sha: 'sha-5' } }, - { name: 'v0.1.3-draft.0', commit: { sha: 'sha-6' } }, - ]) - }); - - expect( - await githubRepo.getMostRecentDraftTagForRelease('0.0.3') - ).to.deep.equal({ name: 'v0.0.3-draft.11', sha: 'sha-4' }); - }); - - it('returns the draft for a release version if there is only one draft', async() => { - githubRepo = getTestGithubRepo({ - paginate: sinon.stub().resolves([ - { name: 'v0.0.6', commit: { sha: 'sha-1' } }, - { name: 'v0.0.30', commit: { sha: 'sha-2' } }, - { name: 'v0.0.30-draft.11', commit: { sha: 'sha-3' } }, - { name: 'v0.0.3-draft.11', commit: { sha: 'sha-4' } }, - { name: 'v0.0.3-draft.2', commit: { sha: 'sha-5' } }, - { name: 'v0.1.3-testrelease.1', commit: { sha: 'sha-x' } }, - { name: 'v0.1.3-draft.0', commit: { sha: 'sha-6' } }, - { name: 'v0.1.3-test', commit: { sha: 'sha-7' } }, - ]) - }); - - expect( - await githubRepo.getMostRecentDraftTagForRelease('0.1.3') - ).to.deep.equal({ name: 'v0.1.3-draft.0', sha: 'sha-6' }); - }); - }); - - describe('getPreviousReleaseTag', () => { - it('returns undefined if the release version is undefined or empty', async() => { - expect( - await githubRepo.getPreviousReleaseTag('') - ).to.be.undefined; - - expect( - await githubRepo.getPreviousReleaseTag(undefined) - ).to.be.undefined; - }); - - it('returns undefined if there is no matching tag', async() => { - githubRepo = getTestGithubRepo({ - paginate: sinon.stub().resolves([ - { name: 'v0.0.6', commit: { sha: 'sha-1' } }, - { name: 'v0.0.3-draft.0', commit: { sha: 'sha-2' } }, - { name: 'v0.0.3-draft.1', commit: { sha: 'sha-3' } }, - { name: 'v0.1.3-draft.8', commit: { sha: 'sha-4' } }, - ]) - }); - - expect( - await githubRepo.getPreviousReleaseTag('0.0.4') - ).to.be.undefined; - }); - - it('returns the previous release for a release version if there are multiple', async() => { - githubRepo = getTestGithubRepo({ - paginate: sinon.stub().resolves([ - { name: 'v0.0.6-draft.1', commit: { sha: 'sha-42' } }, - { name: 'v0.0.6', commit: { sha: 'sha-1' } }, - { name: 'v0.0.30', commit: { sha: 'sha-2' } }, - { name: 'v0.0.6-draft.12', commit: { sha: 'sha-3' } }, - { name: 'v0.0.7-draft.11', commit: { sha: 'sha-4' } }, - { name: 'v0.0.7', commit: { sha: 'sha-x' } }, - { name: 'v0.0.7-draft.2', commit: { sha: 'sha-5' } }, - { name: 'v0.1.3-draft.0', commit: { sha: 'sha-6' } }, - ]) - }); - - expect( - await githubRepo.getPreviousReleaseTag('0.0.7') - ).to.deep.equal({ name: 'v0.0.6', sha: 'sha-1' }); - }); - - it('returns the next lowest release if the tag does not exist yet', async() => { - githubRepo = getTestGithubRepo({ - paginate: sinon.stub().resolves([ - { name: 'v0.0.6', commit: { sha: 'sha-1' } }, - { name: 'v0.0.30', commit: { sha: 'sha-2' } }, - { name: 'v0.0.30-draft.11', commit: { sha: 'sha-3' } }, - { name: 'v0.0.3-draft.11', commit: { sha: 'sha-4' } }, - { name: 'v0.0.3-draft.2', commit: { sha: 'sha-5' } }, - { name: 'v0.1.3-draft.0', commit: { sha: 'sha-6' } }, - { name: 'v0.1.3-test', commit: { sha: 'sha-7' } }, - ]) - }); - - expect( - await githubRepo.getPreviousReleaseTag('0.0.7') - ).to.deep.equal({ name: 'v0.0.6', sha: 'sha-1' }); - }); - - it('returns undefined if there is no previous release', async() => { - githubRepo = getTestGithubRepo({ - paginate: sinon.stub().resolves([ - { name: 'v0.0.6', commit: { sha: 'sha-1' } }, - { name: 'v0.0.30', commit: { sha: 'sha-2' } }, - { name: 'v0.0.30-draft.11', commit: { sha: 'sha-3' } }, - { name: 'v0.0.3-draft.11', commit: { sha: 'sha-4' } }, - { name: 'v0.0.3-draft.2', commit: { sha: 'sha-5' } }, - { name: 'v0.1.3-draft.0', commit: { sha: 'sha-6' } }, - { name: 'v0.1.3-test', commit: { sha: 'sha-7' } }, - ]) - }); - - expect( - await githubRepo.getPreviousReleaseTag('0.0.6') - ).to.be.undefined; - }); - }); - - describe('updateDraftRelease', () => { - let createRelease: SinonStub; - let updateRelease: SinonStub; - - beforeEach(() => { - createRelease = sinon.stub(); - createRelease.resolves(); - updateRelease = sinon.stub(); - updateRelease.resolves(); - githubRepo = getTestGithubRepo({ - repos: { - createRelease, - updateRelease - } - }); - }); - - it('creates a new draft release', async() => { - githubRepo.getReleaseByTag = sinon.stub().resolves(undefined); - - const params = { - name: 'release', - tag: 'v0.8.0', - notes: 'notes' - }; - await githubRepo.updateDraftRelease(params); - expect(createRelease).to.have.been.calledWith({ - owner: 'mongodb-js', - repo: 'mongosh', - name: params.name, - tag_name: params.tag, - body: params.notes, - draft: true - }); - }); - - it('updates an existing draft release', async() => { - githubRepo.getReleaseByTag = sinon.stub().resolves({ - id: 'existing_id', - draft: true - }); - - const params = { - name: 'release', - tag: 'v0.8.0', - notes: 'notes' - }; - await githubRepo.updateDraftRelease(params); - expect(updateRelease).to.have.been.calledWith({ - release_id: 'existing_id', - tag_name: params.tag, - owner: 'mongodb-js', - repo: 'mongosh', - name: params.name, - body: params.notes, - draft: true - }); - }); - - it('fails to update an existing published release', async() => { - githubRepo.getReleaseByTag = sinon.stub().resolves({ - id: 'existing_id' - }); - - const params = { - name: 'release', - tag: 'v0.8.0', - notes: 'notes' - }; - try { - await githubRepo.updateDraftRelease(params); - } catch (e) { - expect(e.message).to.contain('Cannot update an existing release after it was published'); - expect(updateRelease).not.to.have.been.called; - return; - } - expect.fail('Expected error'); - }); - - it('fails on error', async() => { - githubRepo.getReleaseByTag = sinon.stub().resolves(undefined); - - const params = { - name: 'release', - tag: 'v0.8.0', - notes: 'notes' - }; - const expectedError = new Error(); - createRelease.rejects(expectedError); - try { - await githubRepo.updateDraftRelease(params); - } catch (e) { - return expect(e).to.equal(expectedError); - } - expect.fail('Expected error'); - }); - - it('ignores already exists error', async() => { - githubRepo.getReleaseByTag = sinon.stub().resolves(undefined); - - const params = { - name: 'release', - tag: 'v0.8.0', - notes: 'notes' - }; - createRelease.rejects(new ExistsError()); - await githubRepo.updateDraftRelease(params); - expect(createRelease).to.have.been.calledWith({ - owner: 'mongodb-js', - repo: 'mongosh', - name: params.name, - tag_name: params.tag, - body: params.notes, - draft: true - }); - }); - }); - - describe('uploadReleaseAsset', () => { - let octoRequest: SinonStub; - let getReleaseByTag: SinonStub; - let deleteReleaseAsset: SinonStub; - - beforeEach(() => { - octoRequest = sinon.stub(); - octoRequest.resolves(); - getReleaseByTag = sinon.stub(); - getReleaseByTag.rejects(); - deleteReleaseAsset = sinon.stub(); - deleteReleaseAsset.rejects(); - githubRepo = getTestGithubRepo({ - request: octoRequest, - repos: { - deleteReleaseAsset - } - }); - githubRepo.getReleaseByTag = getReleaseByTag; - }); - - it('uploads an asset', async() => { - const release = { - name: 'release', - tag: 'v0.8.0', - notes: '' - }; - getReleaseByTag.resolves({ - upload_url: 'url' - }); - - await githubRepo.uploadReleaseAsset(release.tag, { - path: __filename, - contentType: 'xyz' - }); - expect(deleteReleaseAsset).to.not.have.been.called; - expect(octoRequest).to.have.been.calledWith({ - method: 'POST', - url: 'url', - headers: { - 'content-type': 'xyz' - }, - name: path.basename(__filename), - data: await fs.readFile(__filename) - }); - }); - - it('updates an existing asset by removing the old one first', async() => { - const release = { - name: 'release', - tag: 'v0.8.0', - notes: '' - }; - getReleaseByTag.resolves({ - upload_url: 'url', - assets: [ - { - id: 1, - name: path.basename(__filename), - url: 'assetUrl' - } - ] - }); - deleteReleaseAsset.resolves(); - - await githubRepo.uploadReleaseAsset(release.tag, { - path: __filename, - contentType: 'xyz' - }); - expect(deleteReleaseAsset).to.have.been.calledWith( { - owner: 'mongodb-js', - repo: 'mongosh', - asset_id: 1 - }); - expect(octoRequest).to.have.been.calledWith({ - method: 'POST', - url: 'url', - headers: { - 'content-type': 'xyz' - }, - name: path.basename(__filename), - data: await fs.readFile(__filename) - }); - }); - - it('fails if no release can be found', async() => { - const release = { - name: 'release', - tag: 'v0.8.0', - notes: '' - }; - getReleaseByTag.resolves(undefined); - try { - await githubRepo.uploadReleaseAsset(release.tag, { - path: 'path', - contentType: 'xyz' - }); - } catch (e) { - return expect(e.message).to.contain('Could not look up release for tag'); - } - expect.fail('Expected error'); - }); - }); - - describe('promoteRelease', () => { - describe('when release exists and is in draft', () => { - let octokit: any; - - beforeEach(() => { - octokit = { - paginate: sinon.stub().resolves([{ id: '123', tag_name: 'v0.0.6', draft: true, html_url: 'releaseUrl' }]), - repos: { - updateRelease: sinon.stub().resolves() - } - }; - githubRepo = getTestGithubRepo(octokit); - }); - - it('finds the release corresponding to config.version and sets draft to false', async() => { - const releaseUrl = await githubRepo.promoteRelease({ version: '0.0.6' } as any); - - expect(releaseUrl).to.equal('releaseUrl'); - expect(octokit.repos.updateRelease).to.have.been.calledWith({ - draft: false, - owner: 'mongodb-js', - release_id: '123', - repo: 'mongosh' - }); - }); - }); - - describe('when release exists but is not in draft', () => { - let octokit: any; - - beforeEach(() => { - octokit = { - paginate: sinon.stub().resolves([{ id: '123', tag_name: 'v0.0.6', draft: false }]), - repos: { - updateRelease: sinon.stub().resolves() - } - }; - - githubRepo = getTestGithubRepo(octokit); - }); - - it('does nothing', async() => { - await githubRepo.promoteRelease({ version: '0.0.6' } as any); - - expect(octokit.repos.updateRelease).not.to.have.been.called; - }); - }); - - describe('when release does not exist', () => { - let getReleaseByTag: SinonStub; - beforeEach(() => { - getReleaseByTag = sinon.stub(); - githubRepo = getTestGithubRepo(); - githubRepo.getReleaseByTag = getReleaseByTag; - }); - - it('fails if no release can be found', async() => { - getReleaseByTag.resolves(undefined); - try { - await githubRepo.promoteRelease({ version: '0.8.0' } as any); - } catch (e) { - return expect(e.message).to.contain('Release for v0.8.0 not found'); - } - expect.fail('Expected error'); - }); - }); - }); - - describe('createBranch', () => { - let octokit: any; - let getRef: sinon.SinonStub; - let createRef: sinon.SinonStub; - - beforeEach(() => { - getRef = sinon.stub().rejects(); - createRef = sinon.stub().rejects(); - octokit = { - git: { - getRef, createRef - } - }; - githubRepo = getTestGithubRepo(octokit); - }); - - it('creates the branch based on the given base', async() => { - createRef.withArgs({ - ...githubRepo.repo, - ref: 'refs/heads/newBranch', - sha: 'baseSha' - }).resolves(); - - await githubRepo.createBranch('newBranch', 'baseSha'); - expect(createRef).to.have.been.called; - }); - }); - - describe('deleteBranch', () => { - let octokit: any; - let deleteRef: sinon.SinonStub; - - beforeEach(() => { - deleteRef = sinon.stub().rejects(); - octokit = { - git: { - deleteRef - } - }; - githubRepo = getTestGithubRepo(octokit); - }); - - it('deletes the branch', async() => { - deleteRef.withArgs({ - ...githubRepo.repo, - ref: 'heads/branch' - }).resolves(); - - await githubRepo.deleteBranch('branch'); - expect(deleteRef).to.have.been.called; - }); - }); - - describe('getFileContent', () => { - let octokit: any; - let getContents: sinon.SinonStub; - - beforeEach(() => { - getContents = sinon.stub(); - getContents.rejects(); - - octokit = { - repos: { - getContents - } - }; - githubRepo = getTestGithubRepo(octokit); - }); - - it('loads the file content and decodes it', async() => { - getContents.withArgs({ - ...githubRepo.repo, - path: 'file/path', - ref: 'branch' - }).resolves({ - data: { - type: 'file', - encoding: 'base64', - content: Buffer.from('🎉', 'utf-8').toString('base64'), - sha: 'sha' - } - }); - - const result = await githubRepo.getFileContent('file/path', 'branch'); - expect(result.content).to.equal('🎉'); - expect(result.blobSha).to.equal('sha'); - }); - - it('fails when data type is not file', async() => { - getContents.withArgs({ - ...githubRepo.repo, - path: 'file/path', - ref: 'branch' - }).resolves({ - data: { - type: 'directory' - } - }); - - try { - await githubRepo.getFileContent('file/path', 'branch'); - } catch (e) { - return expect(e.message).to.equal('file/path does not reference a file'); - } - expect.fail('expected error'); - }); - - it('fails when data encoding is not base64', async() => { - getContents.withArgs({ - ...githubRepo.repo, - path: 'file/path', - ref: 'branch' - }).resolves({ - data: { - type: 'file', - encoding: 'whatever' - } - }); - - try { - await githubRepo.getFileContent('file/path', 'branch'); - } catch (e) { - return expect(e.message).to.equal('Octokit returned unexpected encoding: whatever'); - } - expect.fail('expected error'); - }); - }); - - describe('commitFileUpdate', () => { - let octokit: any; - let createOrUpdateFile: sinon.SinonStub; - - beforeEach(() => { - createOrUpdateFile = sinon.stub(); - createOrUpdateFile.rejects(); - - octokit = { - repos: { - createOrUpdateFile - } - }; - githubRepo = getTestGithubRepo(octokit); - }); - - it('commits the file with new content', async() => { - createOrUpdateFile.withArgs({ - ...githubRepo.repo, - message: 'Commit Message', - content: Buffer.from('🎉', 'utf-8').toString('base64'), - path: 'file/path', - sha: 'base', - branch: 'branch' - }).resolves({ - data: { - content: { - sha: 'contentSha' - }, - commit: { - sha: 'commitSha' - } - } - }); - - const result = await githubRepo.commitFileUpdate('Commit Message', 'base', 'file/path', '🎉', 'branch'); - expect(result.blobSha).to.equal('contentSha'); - expect(result.commitSha).to.equal('commitSha'); - }); - }); - - describe('createPullRequest', () => { - let octokit: any; - let createPullRequest: sinon.SinonStub; - - beforeEach(() => { - createPullRequest = sinon.stub(); - createPullRequest.rejects(); - - octokit = { - pulls: { - create: createPullRequest - } - }; - githubRepo = getTestGithubRepo(octokit); - }); - - it('creates a proper PR', async() => { - createPullRequest.withArgs({ - ...githubRepo.repo, - base: 'toBase', - head: 'fromBranch', - title: 'PR', - body: 'description' - }).resolves({ - data: { - number: 42, - html_url: 'url' - } - }); - - const result = await githubRepo.createPullRequest('PR', 'description', 'fromBranch', 'toBase'); - expect(result.prNumber).to.equal(42); - expect(result.url).to.equal('url'); - }); - }); - - describe('mergePullRequest', () => { - let octokit: any; - let mergePullRequest: sinon.SinonStub; - - beforeEach(() => { - mergePullRequest = sinon.stub(); - mergePullRequest.rejects(); - - octokit = { - pulls: { - merge: mergePullRequest - } - }; - githubRepo = getTestGithubRepo(octokit); - }); - - it('merges a PR', async() => { - mergePullRequest.withArgs({ - ...githubRepo.repo, - pull_number: 42 - }).resolves(); - - await githubRepo.mergePullRequest(42); - }); - }); -}); diff --git a/packages/build/src/github-repo.ts b/packages/build/src/github-repo.ts deleted file mode 100644 index 651e43f2a8..0000000000 --- a/packages/build/src/github-repo.ts +++ /dev/null @@ -1,340 +0,0 @@ -/* eslint-disable camelcase */ -import { Octokit } from '@octokit/rest'; -import { promises as fs } from 'fs'; -import path from 'path'; -import semver from 'semver/preload'; -import { Config } from './config'; - -type Repo = { - owner: string; - repo: string; -}; - -type Release = { - name: string; - tag: string; - notes: string; -}; - -type Asset = { - path: string; - contentType: string; -}; - -type Tag = { - name: string; - sha: string; -}; - -type ReleaseDetails = { - draft: boolean; - upload_url: string; - id: number; - assets?: ReleaseAsset[] - html_url: string; -}; - -type ReleaseAsset = { - id: number; - name: string; - url: string; -}; - -type Branch = { - ref: string; - url: string; - object: { - sha: string; - type: string; - url: string; - } -}; - -export class GithubRepo { - private octokit: Octokit; - readonly repo: Readonly; - - constructor(repo: Repo, octokit: Octokit) { - this.octokit = octokit; - this.repo = Object.freeze({ ...repo }); - } - - /** - * Returns the most recent draft tag for the given release version (without leading `v`). - */ - async getMostRecentDraftTagForRelease(releaseVersion: string | undefined): Promise { - if (!releaseVersion) { - return undefined; - } - - const sortedTags = (await this.getTagsOrdered()) - .filter(t => t.name && t.name.startsWith(`v${releaseVersion}-draft`) && t.name.match(/^v\d+\.\d+\.\d+-draft\.\d+/)); - - const mostRecentTag = sortedTags[0]; - return mostRecentTag ? { - name: mostRecentTag.name, - sha: mostRecentTag.sha - } : undefined; - } - - /** - * Returns the predecessor release tag of the given release (without leading `v`). - * @param releaseVersion The successor release - */ - async getPreviousReleaseTag(releaseVersion: string | undefined): Promise { - const releaseSemver = semver.parse(releaseVersion); - if (!releaseSemver) { - return undefined; - } - - return (await this.getTagsOrdered()) - .filter(t => t.name.match(/^v\d+\.\d+\.\d+$/)) - .find(t => { - const tagSemver = semver.parse(t.name); - return tagSemver && tagSemver.compare(releaseSemver) < 0; - }); - } - - /** - * Get all tags from the Github repository, sorted by highest version to lowest version. - */ - private async getTagsOrdered(): Promise { - const tags = await this.octokit - .paginate( - 'GET /repos/:owner/:repo/tags', - this.repo, - ); - - return tags .map(t => ({ - name: t.name, - sha: t.commit.sha, - })).sort((t1, t2) => -1 * semver.compare(t1.name, t2.name)); - } - - /** - * Creates a new draft release or updates an existing draft release for the given details. - * An existing release is discovered by a matching tag. - * - * @param release The release details - */ - async updateDraftRelease(release: Release): Promise { - const existingRelease = await this.getReleaseByTag(release.tag); - if (!existingRelease) { - await this.octokit.repos.createRelease({ - ...this.repo, - tag_name: release.tag, - name: release.name, - body: release.notes, - draft: true - }).catch(this._ignoreAlreadyExistsError()); - } else if (!existingRelease.draft) { - throw new Error('Cannot update an existing release after it was published'); - } else { - await this.octokit.repos.updateRelease({ - ...this.repo, - release_id: existingRelease.id, - tag_name: release.tag, - name: release.name, - body: release.notes, - draft: true - }); - } - } - - /** - * Uploads an asset for a Github release, if the assets already exists - * it will be removed and re-uploaded. - */ - async uploadReleaseAsset(releaseTag: string, asset: Asset): Promise { - const releaseDetails = await this.getReleaseByTag(releaseTag); - - if (releaseDetails === undefined) { - throw new Error(`Could not look up release for tag ${releaseTag}`); - } - - const assetName = path.basename(asset.path); - const existingAsset = releaseDetails.assets?.find(a => a.name === assetName); - - if (existingAsset) { - await this.octokit.repos.deleteReleaseAsset({ - ...this.repo, - asset_id: existingAsset.id - }); - } - - const params = { - method: 'POST', - url: releaseDetails.upload_url, - headers: { - 'content-type': asset.contentType - }, - name: assetName, - data: await fs.readFile(asset.path) - }; - - await this.octokit.request(params); - } - - async getReleaseByTag(tag: string): Promise { - const releases = await this.octokit - .paginate( - 'GET /repos/:owner/:repo/releases', - this.repo, - ); - - return releases.find(({ tag_name }) => tag_name === tag); - } - - async promoteRelease(config: Config): Promise { - const tag = `v${config.version}`; - - const releaseDetails = await this.getReleaseByTag(tag); - - if (!releaseDetails) { - throw new Error(`Release for ${tag} not found.`); - } - - if (!releaseDetails.draft) { - console.info(`Release for ${tag} is already public.`); - return releaseDetails.html_url; - } - - const params = { - ...this.repo, - release_id: releaseDetails.id, - draft: false - }; - - await this.octokit.repos.updateRelease(params); - return releaseDetails.html_url; - } - - /** - * Creates a new branch pointing to the latest commit of the given source branch. - * @param branchName The name of the branch (not including refs/heads/) - * @param sourceSha The SHA hash of the commit to branch off from - */ - async createBranch(branchName: string, sourceSha: string): Promise { - await this.octokit.git.createRef({ - ...this.repo, - ref: `refs/heads/${branchName}`, - sha: sourceSha - }); - } - - /** - * Retrieves the details of the given branch. - * @param branchName The name of the branch (not including refs/heads/) - */ - async getBranchDetails(branchName: string): Promise { - const result = await this.octokit.git.getRef({ - ...this.repo, - ref: `heads/${branchName}` - }); - return result.data; - } - - /** - * Removes the given branch by deleting the corresponding ref. - * @param branchName The branch name to remove (not including refs/heads/) - */ - async deleteBranch(branchName: string): Promise { - await this.octokit.git.deleteRef({ - ...this.repo, - ref: `heads/${branchName}` - }); - } - - /** - * Gets the content of the given file from the repository. - * Assumes the loaded file is a utf-8 encoded text file. - * - * @param pathInRepo Path to the file from the repository root - * @param branchOrTag Branch/tag name to load content from - */ - async getFileContent(pathInRepo: string, branchOrTag: string): Promise<{blobSha: string; content: string;}> { - const response = await this.octokit.repos.getContents({ - ...this.repo, - path: pathInRepo, - ref: branchOrTag - }); - - if (response.data.type !== 'file') { - throw new Error(`${pathInRepo} does not reference a file`); - } else if (response.data.encoding !== 'base64') { - throw new Error(`Octokit returned unexpected encoding: ${response.data.encoding}`); - } - - const content = Buffer.from(response.data.content, 'base64').toString('utf-8'); - return { - blobSha: response.data.sha, - content - }; - } - - /** - * Updates the content of a given file in the repository. - * Assumes the given file content is utf-8 encoded text. - * - * @param message The commit message - * @param baseSha The blob SHA of the file to update - * @param pathInRepo Path to the file from the repository root - * @param newContent New file content - * @param branch Branch name to commit to - */ - async commitFileUpdate(message: string, baseSha: string, pathInRepo: string, newContent: string, branch: string): Promise<{blobSha: string; commitSha: string;}> { - const response = await this.octokit.repos.createOrUpdateFile({ - ...this.repo, - message, - content: Buffer.from(newContent, 'utf-8').toString('base64'), - path: pathInRepo, - sha: baseSha, - branch - }); - - return { - blobSha: response.data.content.sha, - commitSha: response.data.commit.sha - }; - } - - async createPullRequest(title: string, description: string, fromBranch: string, toBaseBranch: string): Promise<{prNumber: number, url: string}> { - const response = await this.octokit.pulls.create({ - ...this.repo, - base: toBaseBranch, - head: fromBranch, - title, - body: description - }); - - return { - prNumber: response.data.number, - url: response.data.html_url - }; - } - - async mergePullRequest(prNumber: number): Promise { - await this.octokit.pulls.merge({ - ...this.repo, - pull_number: prNumber - }); - } - - private _ignoreAlreadyExistsError(): (error: any) => Promise { - // eslint-disable-next-line @typescript-eslint/require-await - return async(error: any): Promise => { - if (this._isAlreadyExistsError(error)) { - return; - } - throw error; - }; - } - - private _isAlreadyExistsError(error: any): boolean { - return error.name === 'HttpError' && - error.status === 422 && - error.errors && - error.errors.length === 1 && - error.errors[0].code === 'already_exists'; - } -} diff --git a/packages/build/src/homebrew/generate-formula.spec.ts b/packages/build/src/homebrew/generate-formula.spec.ts index 82f645d189..d8d14e99dc 100644 --- a/packages/build/src/homebrew/generate-formula.spec.ts +++ b/packages/build/src/homebrew/generate-formula.spec.ts @@ -1,6 +1,6 @@ import chai, { expect } from 'chai'; import sinon from 'ts-sinon'; -import { GithubRepo } from '../github-repo'; +import { GithubRepo } from '@mongodb-js/devtools-github-repo'; import { generateUpdatedFormula } from './generate-formula'; chai.use(require('sinon-chai')); diff --git a/packages/build/src/homebrew/generate-formula.ts b/packages/build/src/homebrew/generate-formula.ts index 156a60e6b9..6d3087752d 100644 --- a/packages/build/src/homebrew/generate-formula.ts +++ b/packages/build/src/homebrew/generate-formula.ts @@ -1,5 +1,5 @@ import * as semver from 'semver'; -import { GithubRepo } from '../github-repo'; +import { GithubRepo } from '@mongodb-js/devtools-github-repo'; export async function generateUpdatedFormula( context: { version: string, sha: string }, diff --git a/packages/build/src/homebrew/publish-to-homebrew.spec.ts b/packages/build/src/homebrew/publish-to-homebrew.spec.ts index bbc0f3b0cf..1b8464c20d 100644 --- a/packages/build/src/homebrew/publish-to-homebrew.spec.ts +++ b/packages/build/src/homebrew/publish-to-homebrew.spec.ts @@ -1,6 +1,6 @@ import chai, { expect } from 'chai'; import sinon from 'ts-sinon'; -import { GithubRepo } from '../github-repo'; +import { GithubRepo } from '@mongodb-js/devtools-github-repo'; import { publishToHomebrew } from './publish-to-homebrew'; chai.use(require('sinon-chai')); diff --git a/packages/build/src/homebrew/publish-to-homebrew.ts b/packages/build/src/homebrew/publish-to-homebrew.ts index 83f89aa240..2c139e30af 100644 --- a/packages/build/src/homebrew/publish-to-homebrew.ts +++ b/packages/build/src/homebrew/publish-to-homebrew.ts @@ -1,4 +1,4 @@ -import { GithubRepo } from '../github-repo'; +import { GithubRepo } from '@mongodb-js/devtools-github-repo'; import { generateUpdatedFormula } from './generate-formula'; import { updateHomebrewFork } from './update-homebrew-fork'; import { httpsSha256 } from './utils'; diff --git a/packages/build/src/homebrew/update-homebrew-fork.spec.ts b/packages/build/src/homebrew/update-homebrew-fork.spec.ts index a5a4f1626d..5b87af4d8a 100644 --- a/packages/build/src/homebrew/update-homebrew-fork.spec.ts +++ b/packages/build/src/homebrew/update-homebrew-fork.spec.ts @@ -1,6 +1,6 @@ import chai, { expect } from 'chai'; import sinon from 'ts-sinon'; -import { GithubRepo } from '../github-repo'; +import { GithubRepo } from '@mongodb-js/devtools-github-repo'; import { updateHomebrewFork } from './update-homebrew-fork'; chai.use(require('sinon-chai')); diff --git a/packages/build/src/homebrew/update-homebrew-fork.ts b/packages/build/src/homebrew/update-homebrew-fork.ts index a595712eae..965a69a406 100644 --- a/packages/build/src/homebrew/update-homebrew-fork.ts +++ b/packages/build/src/homebrew/update-homebrew-fork.ts @@ -1,4 +1,4 @@ -import { GithubRepo } from '../github-repo'; +import { GithubRepo } from '@mongodb-js/devtools-github-repo'; export interface UpdateHomebrewParameters { packageVersion: string; diff --git a/packages/build/src/release.ts b/packages/build/src/release.ts index 602fadd384..f33fafd367 100644 --- a/packages/build/src/release.ts +++ b/packages/build/src/release.ts @@ -7,7 +7,7 @@ import { runCompile } from './compile'; import { Config, getReleaseVersionFromTag, redactConfig } from './config'; import { createAndPublishDownloadCenterConfig, uploadArtifactToDownloadCenter } from './download-center'; import { downloadArtifactFromEvergreen, uploadArtifactToEvergreen } from './evergreen'; -import { GithubRepo } from './github-repo'; +import { GithubRepo } from '@mongodb-js/devtools-github-repo'; import { publishToHomebrew } from './homebrew'; import { bumpNpmPackages, publishNpmPackages } from './npm-packages'; import { runPackage } from './packaging'; diff --git a/packages/build/src/run-draft.spec.ts b/packages/build/src/run-draft.spec.ts index f3268fa05f..c866846207 100644 --- a/packages/build/src/run-draft.spec.ts +++ b/packages/build/src/run-draft.spec.ts @@ -4,7 +4,7 @@ import { ALL_BUILD_VARIANTS, Config } from './config'; import { uploadArtifactToDownloadCenter as uploadArtifactToDownloadCenterFn } from './download-center'; import { downloadArtifactFromEvergreen as downloadArtifactFromEvergreenFn } from './evergreen'; import { generateChangelog as generateChangelogFn } from './git'; -import { GithubRepo } from './github-repo'; +import { GithubRepo } from '@mongodb-js/devtools-github-repo'; import { ensureGithubReleaseExistsAndUpdateChangelogFn, runDraft } from './run-draft'; import { dummyConfig } from '../test/helpers'; diff --git a/packages/build/src/run-draft.ts b/packages/build/src/run-draft.ts index b18d17a3bb..cb7364de40 100644 --- a/packages/build/src/run-draft.ts +++ b/packages/build/src/run-draft.ts @@ -4,7 +4,7 @@ import { ALL_BUILD_VARIANTS, Config, getReleaseVersionFromTag } from './config'; import { uploadArtifactToDownloadCenter as uploadArtifactToDownloadCenterFn } from './download-center'; import { downloadArtifactFromEvergreen as downloadArtifactFromEvergreenFn } from './evergreen'; import { generateChangelog as generateChangelogFn } from './git'; -import { GithubRepo } from './github-repo'; +import { GithubRepo } from '@mongodb-js/devtools-github-repo'; import { getPackageFile } from './packaging'; export async function runDraft( diff --git a/packages/build/src/run-publish.spec.ts b/packages/build/src/run-publish.spec.ts index c2c4b066d4..5aeaaf8a40 100644 --- a/packages/build/src/run-publish.spec.ts +++ b/packages/build/src/run-publish.spec.ts @@ -4,7 +4,7 @@ import type { writeBuildInfo as writeBuildInfoType } from './build-info'; import { Barque } from './barque'; import { Config, shouldDoPublicRelease as shouldDoPublicReleaseFn } from './config'; import { createAndPublishDownloadCenterConfig as createAndPublishDownloadCenterConfigFn } from './download-center'; -import { GithubRepo } from './github-repo'; +import { GithubRepo } from '@mongodb-js/devtools-github-repo'; import type { publishToHomebrew as publishToHomebrewType } from './homebrew'; import type { publishNpmPackages as publishNpmPackagesType } from './npm-packages'; import { runPublish } from './run-publish'; diff --git a/packages/build/src/run-publish.ts b/packages/build/src/run-publish.ts index 9833fcf675..cc92cd934c 100644 --- a/packages/build/src/run-publish.ts +++ b/packages/build/src/run-publish.ts @@ -8,7 +8,7 @@ import { } from './config'; import { createAndPublishDownloadCenterConfig as createAndPublishDownloadCenterConfigFn } from './download-center'; import { getArtifactUrl as getArtifactUrlFn } from './evergreen'; -import { GithubRepo } from './github-repo'; +import { GithubRepo } from '@mongodb-js/devtools-github-repo'; import type { publishToHomebrew as publishToHomebrewType } from './homebrew'; import type { publishNpmPackages as publishNpmPackagesType } from './npm-packages'; import { PackageInformation, getPackageFile } from './packaging';