Skip to content

Commit

Permalink
Adding local publishing script and library. @Version 0.2.2
Browse files Browse the repository at this point in the history
  • Loading branch information
timcrider committed May 7, 2023
1 parent 947a41c commit c2ebf8a
Show file tree
Hide file tree
Showing 3 changed files with 249 additions and 1 deletion.
113 changes: 113 additions & 0 deletions bin/publish.js
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');
})();
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@timcrider/aquasec-client",
"version": "0.2.1",
"version": "0.2.2",
"engines": {
"node": ">=18.0.0"
},
Expand Down
135 changes: 135 additions & 0 deletions src/dev/github.js
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;

0 comments on commit c2ebf8a

Please sign in to comment.