-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding local publishing script and library. @Version 0.2.2
- Loading branch information
Showing
3 changed files
with
249 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
require('dotenv-expand').expand(require('dotenv').config()); | ||
const {Octokit} = require('octokit'); | ||
const {program, Option} = require('commander'); | ||
const GitHub = require('../src/dev/github'); | ||
|
||
const dumper = (data) => { console.log(JSON.stringify(data, null, 2)); }; | ||
const octo = new Octokit({auth: process.env.GITHUB_TOKEN}); | ||
const gh = new GitHub(octo); | ||
|
||
program | ||
.description('Publish a new tag and release to GitHub') | ||
.addOption(new Option('-o, --owner <owner>', 'Repository owner').env('GITHUB_OWNER')) | ||
.addOption(new Option('-r, --repo <repo>', 'Repository name').env('GITHUB_REPO')) | ||
.addOption(new Option('-b, --branch <branch>', 'Branch to publish').default('main').env('GITHUB_BRANCH')) | ||
.addOption(new Option('-v, --version <version>', 'Version to publish')) | ||
.addOption(new Option('-m, --message <message>', 'Release message')); | ||
|
||
program.parse(process.argv); | ||
let opts = program.optsWithGlobals(); | ||
|
||
// Validate options | ||
if (opts.owner === "" || opts.owner === undefined) { | ||
console.error('Repository owner is required'); | ||
process.exit(1); | ||
} | ||
|
||
if (opts.repo === "" || opts.repo === undefined) { | ||
console.error('Repository name is required'); | ||
process.exit(2); | ||
} | ||
|
||
if (opts.version === "" || opts.version === undefined) { | ||
opts.version = require('../package.json').version; | ||
} | ||
|
||
if (opts.message === "" || opts.message === undefined) { | ||
opts.message = `Release ${opts.version}`; | ||
} | ||
|
||
if (opts.branch === "" || opts.branch === undefined) { | ||
opts.branch = main; | ||
} | ||
|
||
|
||
(async () => { | ||
console.log('Beginning Pre-Flight Checks'); | ||
let releaseExists = null; | ||
let tagExists = null; | ||
let remoteHash = null; | ||
|
||
// Check for existing release | ||
console.log(` Checking for existing release ${opts.version}`); | ||
let release = await gh.getReleases(opts.owner, opts.repo, opts.version); | ||
|
||
if (release.length > 0) { | ||
console.error(` - Release ${opts.version} already exists`); | ||
process.exit(3); | ||
} | ||
console.log(` + Release ${opts.version} does not exist`); | ||
|
||
// Check for existing tag | ||
console.log(` Checking for existing tag ${opts.version}`); | ||
let tag = await gh.getTags(opts.owner, opts.repo, opts.version); | ||
|
||
if (tag.length > 0) { | ||
console.error(` - Tag ${opts.version} already exists`); | ||
process.exit(4); | ||
} | ||
console.log(` + Tag ${opts.version} does not exist`); | ||
|
||
// Fetching existing remote hash for branch | ||
console.log(` Fetching remote hash for ${opts.branch}`); | ||
remoteHash = await gh.fetchRemoteBranchSha(opts.owner, opts.repo); | ||
|
||
if (remoteHash === "") { | ||
console.error(` - Remote hash for ${opts.branch} is empty`); | ||
process.exit(5); | ||
} | ||
console.log(` + Remote hash for ${opts.branch} is ${remoteHash}`); | ||
|
||
// Fetch remote package.json from main branch | ||
console.log(` Fetching remote package.json from ${opts.branch}`); | ||
let remotePackage = await gh.fetchRemotePackage(opts.owner, opts.repo, opts.branch); | ||
|
||
if (remotePackage === "") { | ||
console.error(` - Remote package.json for ${opts.branch} is empty`); | ||
process.exit(6); | ||
} | ||
console.log(` + Remote package.json for ${opts.branch} is not empty and shows version ${remotePackage.version}`); | ||
|
||
// Currently limit to publishing the same version as the package.json locally | ||
if (remotePackage.version !== opts.version) { | ||
console.error(` - Remote package.json version ${remotePackage.version} does not match ${opts.version}`); | ||
process.exit(7); | ||
} | ||
console.log(` + Remote package.json version ${remotePackage.version} matches ${opts.version}`); | ||
|
||
let releaseData = { | ||
owner: opts.owner, | ||
repo: opts.repo, | ||
tag_name: opts.version, | ||
target_commitish: remoteHash, | ||
name: `Relese ${opts.version}`, | ||
body: opts.message, | ||
draft: false, | ||
prerelease: true, | ||
generate_release_notes: false | ||
}; | ||
|
||
let result = await gh.createRelease(releaseData); | ||
|
||
console.log('Release Created'); | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
/** | ||
* GitHub helper | ||
*/ | ||
|
||
const { SearchCodeRequest } = require('octokit'); | ||
|
||
|
||
class GitHub { | ||
constructor(octokit) { | ||
this.octokit = octokit; | ||
} | ||
|
||
/** | ||
* Get releases for a repository | ||
* | ||
* @param {string} repoOwner Repository owner | ||
* @param {string} repoName Repostiory name | ||
* @param {string} version Semver to match | ||
* @returns array Matching release(s) | ||
*/ | ||
async getReleases(repoOwner, repoName, version="") { | ||
let releases = await this.octokit.request('GET /repos/{owner}/{repo}/releases', { | ||
owner: repoOwner, | ||
repo: repoName | ||
}); | ||
|
||
// Filter the releases to only those that match the version we're trying to publish | ||
if (version !== "") { | ||
releases = releases.data.filter((release) => { | ||
return release.tag_name === version; | ||
}); | ||
} | ||
|
||
return releases; | ||
}; | ||
|
||
/** | ||
* Get tags for a repository | ||
* | ||
* @param {string} repoOwner Repository owner | ||
* @param {string} repoName Repostiory name | ||
* @param {string} version Semver to match | ||
* @returns array Matching tag(s) | ||
*/ | ||
async getTags(repoOwner, repoName, version="") { | ||
let tags = await this.octokit.request('GET /repos/{owner}/{repo}/tags', { | ||
owner: repoOwner, | ||
repo: repoName | ||
}); | ||
|
||
// Filter the releases to only those that match the version we're trying to publish | ||
if (version !== "") { | ||
tags = tags.data.filter((tag) => { | ||
return tag.name === version; | ||
}); | ||
} | ||
|
||
return tags; | ||
}; | ||
|
||
/** | ||
* Fetch remote package.json from main branch | ||
* | ||
* @param {string} repoOwner Repository owner | ||
* @param {string} repoName Repostiory name | ||
* @returns object package.json contents | ||
* @throws Error if the request fails or the contents cannot be decoded | ||
* @todo This should probably be a generic function that takes a branch name | ||
*/ | ||
async fetchRemotePackage(repoOwner, repoName) { | ||
let search = await this.octokit.request(`GET /repos/${repoOwner}/${repoName}/contents/package.json`, { | ||
ref: 'main', | ||
per_page: 1, | ||
page: 1 | ||
}); | ||
|
||
if (search.status !== 200) { | ||
throw new Error(`Failed to fetch remote main version: ${search.status}`); | ||
} | ||
|
||
try { | ||
let contents = JSON.parse(Buffer.from(search.data.content, 'base64').toString()); | ||
return contents; | ||
} catch (err) { | ||
throw new Error(`Failed to decode remote main version: ${err.message}`); | ||
} | ||
}; | ||
|
||
/** | ||
* Fetch remote main branch SHA | ||
* | ||
* @param {string} repoOwner Repository owner | ||
* @param {string} repoName Repostiory name | ||
* @returns string SHA of the last commit on the main branch | ||
* @throws Error if the request fails or the SHA cannot be found | ||
* @todo This should probably be a generic function that takes a branch name | ||
*/ | ||
async fetchRemoteBranchSha(repoOwner, repoName) { | ||
let lastCommit = await this.octokit.request(`GET /repos/${repoOwner}/${repoName}/commits`, { | ||
ref: 'main', | ||
per_page: 1, | ||
page: 1 | ||
}); | ||
|
||
if (lastCommit.status !== 200) { | ||
throw new Error(`Failed to fetch remote main branch: ${lastCommit.status}`); | ||
} | ||
|
||
if (lastCommit.data.length === 0) { | ||
throw new Error(`Failed to fetch remote main branch: No commits found`); | ||
} | ||
|
||
if (lastCommit.data[0].sha) { | ||
return lastCommit.data[0].sha; | ||
} | ||
|
||
throw new Error(`Failed to fetch remote main branch: No commit SHA found`); | ||
}; | ||
|
||
/** | ||
* Create a release from a generated manifest | ||
*/ | ||
async createRelease(manifest) { | ||
let release = await this.octokit.request(`POST /repos/${manifest.owner}/${manifest.repo}/releases`, manifest); | ||
|
||
if (release.status !== 201) { | ||
throw new Error(`Failed to create release: ${release.status}`); | ||
} | ||
|
||
return release.data; | ||
} | ||
|
||
}; | ||
|
||
module.exports = GitHub; |