Skip to content

Commit

Permalink
Support Github Enterprise, upgrade Github client package
Browse files Browse the repository at this point in the history
@octokit/rest is the new (since January) NPM package for accessing the
Github API. There are some differences that are taken care of here.

One of them is setting the mime type on asset upload.

Since octokit initialization happens on init (instead of module
execution), I've hacked up a means to use Sinon for mocking it.

The primary result of this change is to allow for publishing to Github
Enterprise instances.
  • Loading branch information
Allen Luce committed Aug 3, 2018
1 parent c239d3f commit 52e9af0
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 93 deletions.
112 changes: 46 additions & 66 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

var path = require("path");
var fs = require('fs');
var GitHubApi = require("github");
var mime = require("mime-types");
var cwd = process.cwd();

var verbose;
Expand All @@ -11,25 +11,8 @@ var consoleLog = function(x){
};

function NodePreGypGithub() {}

NodePreGypGithub.prototype.github = new GitHubApi({ // set defaults
// required
version: "3.0.0",
// optional
debug: false,
protocol: "https",
host: "api.github.com",
pathPrefix: "", // for some GHEs; none for GitHub
timeout: 5000,
headers: {}
});

NodePreGypGithub.prototype.owner = "";
NodePreGypGithub.prototype.repo = "";
NodePreGypGithub.prototype.package_json = {};
NodePreGypGithub.prototype.release = {};
NodePreGypGithub.prototype.octokit = require("@octokit/rest");
NodePreGypGithub.prototype.stage_dir = path.join(cwd,"build","stage");

NodePreGypGithub.prototype.init = function() {
var ownerRepo, hostPrefix;

Expand All @@ -39,25 +22,30 @@ NodePreGypGithub.prototype.init = function() {
throw new Error('Missing repository.url in package.json');
}
else {
ownerRepo = this.package_json.repository.url.match(/github\.com\/(.*)(?=\.git)/i);
ownerRepo = this.package_json.repository.url.match(/https?:\/\/([^\/]+)\/(.*)(?=\.git)/i);
if(ownerRepo) {
ownerRepo = ownerRepo[1].split('/');
this.host = ownerRepo[1];
ownerRepo = ownerRepo[2].split('/');
this.owner = ownerRepo[0];
this.repo = ownerRepo[1];
}
else throw new Error('A correctly formatted GitHub repository.url was not found within package.json');
}

hostPrefix = 'https://github.com/' + this.owner + '/' + this.repo + '/releases/download/';
hostPrefix = 'https://' + this.host + '/' + this.owner + '/' + this.repo + '/releases/download/';
if(!this.package_json.binary || 'object' !== typeof this.package_json.binary || 'string' !== typeof this.package_json.binary.host){
throw new Error('Missing binary.host in package.json');
}
else if (this.package_json.binary.host.substr(0, hostPrefix.length) !== hostPrefix){
throw new Error('binary.host in package.json should begin with: "' + hostPrefix + '"');
}

this.github.headers = {"user-agent": (this.package_json.name) ? this.package_json.name : "node-pre-gyp-github"}; // GitHub is happy with a unique user agent


this.octokit = NodePreGypGithub.prototype.octokit({
baseUrl: 'https://' + this.host + '/api/v3',
headers: {
"user-agent": (this.package_json.name) ? this.package_json.name : "node-pre-gyp-github"
}
});
};

NodePreGypGithub.prototype.authenticate_settings = function(){
Expand All @@ -71,6 +59,7 @@ NodePreGypGithub.prototype.authenticate_settings = function(){

NodePreGypGithub.prototype.createRelease = function(args, callback) {
var options = {
'host': this.host,
'owner': this.owner,
'repo': this.repo,
'tag_name': this.package_json.version,
Expand All @@ -86,19 +75,21 @@ NodePreGypGithub.prototype.createRelease = function(args, callback) {
options[key] = args[key];
}
});

this.github.authenticate(this.authenticate_settings());
this.github.releases.createRelease(options, callback);
this.octokit.authenticate(this.authenticate_settings());
this.octokit.repos.createRelease(options, callback);
};

NodePreGypGithub.prototype.uploadAsset = function(cfg){
this.github.authenticate(this.authenticate_settings());
this.github.releases.uploadAsset({
this.octokit.authenticate(this.authenticate_settings());
this.octokit.repos.uploadAsset({
url: this.release.upload_url,
owner: this.owner,
id: this.release.id,
repo: this.repo,
name: cfg.fileName,
filePath: cfg.filePath
file: cfg.filePath,
contentType: mime.contentType(cfg.fileName) || 'application/octet-stream',
contentLength: fs.statSync(cfg.filePath).size,
}, function(err){
if(err) throw err;
consoleLog('Staged file ' + cfg.fileName + ' saved to ' + this.owner + '/' + this.repo + ' release ' + this.release.tag_name + ' successfully.');
Expand All @@ -114,19 +105,19 @@ NodePreGypGithub.prototype.uploadAssets = function(){
if(!files.length) throw new Error('No files found within the stage directory: ' + this.stage_dir);

files.forEach(function(file){
asset = this.release.assets.filter(function(element, index, array){
return element.name === file;
if(this.release && this.release.assets) {
asset = this.release.assets.filter(function(element, index, array){
return element.name === file;
});
if(asset.length) {
throw new Error("Staged file " + file + " found but it already exists in release " + this.release.tag_name + ". If you would like to replace it, you must first manually delete it within GitHub.");
}
}
consoleLog("Staged file " + file + " found. Proceeding to upload it.");
this.uploadAsset({
fileName: file,
filePath: path.join(this.stage_dir, file)
});
if(asset.length) {
throw new Error("Staged file " + file + " found but it already exists in release " + this.release.tag_name + ". If you would like to replace it, you must first manually delete it within GitHub.");
}
else {
consoleLog("Staged file " + file + " found. Proceeding to upload it.");
this.uploadAsset({
fileName: file,
filePath: path.join(this.stage_dir, file)
});
}
}.bind(this));
}.bind(this));
};
Expand All @@ -135,13 +126,12 @@ NodePreGypGithub.prototype.publish = function(options) {
options = (typeof options === 'undefined') ? {} : options;
verbose = (typeof options.verbose === 'undefined' || options.verbose) ? true : false;
this.init();
this.github.authenticate(this.authenticate_settings());
this.github.releases.listReleases({
this.octokit.authenticate(this.authenticate_settings());
this.octokit.repos.getReleases({
'owner': this.owner,
'repo': this.repo
}, function(err, data){
var release;

if(err) throw err;

// when remote_path is set expect files to be in stage_dir / remote_path after substitution
Expand All @@ -152,37 +142,27 @@ NodePreGypGithub.prototype.publish = function(options) {
// This is here for backwards compatibility for before binary.remote_path support was added in version 1.2.0.
options.tag_name = this.package_json.version;
}

release = (function(){ // create a new array containing only those who have a matching version.
if(data) {
data = data.filter(function(element, index, array){
return element.tag_name === options.tag_name;
}.bind(this));
return data;
}
else return [];
}.bind(this))();

this.release = release[0];

if(!release.length) {
release = data.data.filter(function(element, index, array){
return element.tag_name === options.tag_name;
});
if(release.length === 0) {
this.createRelease(options, function(err, release) {
if(err) throw err;

this.release = release;
if (release.draft) {
consoleLog('Release ' + release.tag_name + " not found, so a draft release was created. YOU MUST MANUALLY PUBLISH THIS DRAFT WITHIN GITHUB FOR IT TO BE ACCESSIBLE.");
this.release = release.data;
if (this.release.draft) {
consoleLog('Release ' + this.release.tag_name + " not found, so a draft release was created. YOU MUST MANUALLY PUBLISH THIS DRAFT WITHIN GITHUB FOR IT TO BE ACCESSIBLE.");
}
else {
consoleLog('Release ' + release.tag_name + " not found, so a new release was created and published.");
}
this.uploadAssets();
this.uploadAssets(this.release.upload_url);
}.bind(this));
}
else {
this.release = release[0];
this.uploadAssets();
}
}.bind(this));
};

module.exports = NodePreGypGithub;
module.exports = NodePreGypGithub;
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@
"releases"
],
"dependencies": {
"github": "^0.2.4",
"commander": "^2.9.0"
"@octokit/rest": "^15.9.5",
"commander": "^2.9.0",
"mime-types": "^2.1.19",
"sinon": "^6.1.4"
},
"devDependencies": {
"chai": "^3.5.0",
Expand Down
60 changes: 35 additions & 25 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,31 @@ var fs = require("fs");
var Index = require('../index.js');
var index = new Index();
var stage_dir = index.stage_dir;
var Octokit = require("@octokit/rest");
var octokit = Octokit();
var sinon = require('sinon')
var reset_index = function(index_string_ref) {
delete require.cache[require.resolve(index_string_ref)];
return require(index_string_ref);
};

var sandbox = sinon.createSandbox();

var reset_mocks = function() {
sandbox.restore();
process.env.NODE_PRE_GYP_GITHUB_TOKEN = "secret";
fs = reset_index('fs');
fs.readFileSync = function(){return '{"name":"test","version":"0.0.1","repository": {"url":"git+https://github.com/test/test.git"},"binary":{"host":"https://github.com/test/test/releases/download/","remote_path":"{version}"}}';};
index.stage_dir = stage_dir;
index.github.authenticate = function(){};
index.github.releases.listReleases = function(options, cb){
cb(null, [{"tag_name":"0.0.0","assets":[{"name":"filename"}]}]);
};
index.github.releases.createRelease = function(options, cb){
cb(null,{"tag_name":"0.0.1","draft":true,"assets":[{}]});
};
index.github.releases.uploadAsset = function(cfg,cb){cb();};
Index.prototype.octokit = function() {return octokit;};
sandbox.stub(octokit, 'authenticate');
sandbox.stub(octokit.repos, 'getReleases').callsFake(function(options, cb){
cb(null, {data: [{"tag_name":"0.0.0","assets":[{"name":"filename"}]}]});
});
sandbox.stub(octokit.repos, 'createRelease').callsFake(function(options, cb){
cb(null,{data: {"tag_name":"0.0.1","draft":true,"assets":[{}]}});
});
sandbox.stub(octokit.repos, 'uploadAsset').callsFake(function(cfg,cb){cb();});
};

if(!process.env.COVERALLS_SERVICE_NAME) console.log('To post to coveralls.io, be sure to set COVERALLS_SERVICE_NAME environment variable');
Expand All @@ -37,9 +45,8 @@ describe("Publishes packages to GitHub Releases", function() {
fs.readdir = function(filename, cb) {
cb(null,["filename"]);
};
index.github.releases.createRelease = function(options, cb){
cb(null,{"tag_name":"0.0.1","draft":false,"assets":[{}]});
};
fs.statSync = function() {return 0;}
index.publish(options)
expect(function(){ index.publish(options); }).to.not.throw();
});

Expand Down Expand Up @@ -91,22 +98,24 @@ describe("Publishes packages to GitHub Releases", function() {
expect(function(){ index.publish(options); }).to.throw("NODE_PRE_GYP_GITHUB_TOKEN environment variable not found");
});

it("should throw an error when github.releases.listReleases returns an error", function() {
it("should throw an error when octokit.repos.getReleases returns an error", function() {
var options = {'draft': true, 'verbose': false};
reset_mocks();
index.github.releases.listReleases = function(options, cb){
cb(new Error('listReleases error'));
};
expect(function(){ index.publish(options); }).to.throw('listReleases error');

octokit.repos.getReleases.restore();
sandbox.stub(octokit.repos, 'getReleases').callsFake(function(options, cb){
cb(new Error('getReleases error'));
});
expect(function(){ index.publish(options); }).to.throw('getReleases error');
});

it("should throw an error when github.releases.createRelease returns an error", function() {
var options = {'draft': true, 'verbose': false};
reset_mocks();
index.github.releases.listReleases = function(options, cb){
cb(null,null);
octokit.repos.getReleases = function(options, cb){
cb(null,{data: []});
};
index.github.releases.createRelease = function(options, cb){
octokit.repos.createRelease = function(options, cb){
cb(new Error('createRelease error'));
};
expect(function(){ index.publish(options); }).to.throw('createRelease error');
Expand Down Expand Up @@ -136,8 +145,8 @@ describe("Publishes packages to GitHub Releases", function() {
fs.readdir = function(filename, cb) {
cb(null,["filename"]);
};
index.github.releases.listReleases = function(options, cb){
cb(null, [{"tag_name":"0.0.1","assets":[{"name":"filename"}]}]);
octokit.repos.getReleases = function(options, cb){
cb(null, {data: [{"tag_name":"0.0.1","assets":[{"name":"filename"}]}]});
};
expect(function(){ index.publish(options); }).to.throw(/^Staged file .* found but it already exists in release .*. If you would like to replace it, you must first manually delete it within GitHub./i);
});
Expand All @@ -148,7 +157,7 @@ describe("Publishes packages to GitHub Releases", function() {
fs.readdir = function(filename, cb) {
cb(null,["filename"]);
};
index.github.releases.uploadAsset = function(cfg,cb){
octokit.repos.uploadAsset = function(cfg,cb){
cb(new Error('uploadAsset error'));
};
expect(function(){ index.publish(options); }).to.throw("uploadAsset error");
Expand All @@ -165,11 +174,12 @@ describe("Publishes packages to GitHub Releases", function() {
fs.readdir = function(filename, cb) {
cb(null,["filename"]);
};
index.github.releases.createRelease = function(options, cb){
cb(null,{"tag_name":"0.0.1","draft":false,"assets":[{}]});
fs.statSync = function() {return 0;}
octokit.reposcreateRelease = function(options, cb){
cb(null,{data: {"tag_name":"0.0.1","draft":false,"assets":[{}]}});
};
expect(function(){ index.publish(options); }).to.not.throw();
});

});
});
});

0 comments on commit 52e9af0

Please sign in to comment.