pkg-fetch/lib/github.js

153 lines
5.1 KiB
JavaScript

/* eslint-disable camelcase */
import { log, wasReported } from './log.js';
import assert from 'assert';
import fs from 'fs';
import progress from 'request-progress';
import request from 'request';
export class GitHub {
constructor ({ owner, repo }) {
this.owner = owner;
this.repo = repo;
const { GITHUB_USERNAME, GITHUB_PASSWORD } = process.env;
const auth = { user: GITHUB_USERNAME, pass: GITHUB_PASSWORD };
this.request = request.defaults({
auth: auth.user ? auth : undefined,
headers: { 'User-Agent': `${this.owner}/${this.repo}/${GITHUB_USERNAME}` },
timeout: 30 * 1000
});
}
getRelease (tag) {
return new Promise((resolve, reject) => {
const url = `https://api.github.com/repos/${this.owner}/${this.repo}/releases/tags/${tag}`;
this.request(url, (error, response, body) => {
if (error) return reject(wasReported(error.message));
const release = JSON.parse(body);
const { message } = release;
if (message === 'Not Found') return resolve(undefined);
if (message) return reject(wasReported(message));
resolve(release);
});
});
}
getReleaseDraft (tag) {
return new Promise((resolve, reject) => {
const url = `https://api.github.com/repos/${this.owner}/${this.repo}/releases`;
this.request(url, (error, response, body) => {
if (error) return reject(wasReported(error.message));
// here we use `get release by tag` endpoint
// because draft releases are really `untagged`.
// seems that `get release by tag` is for non-drafts.
// hence listing all releases and looking through them
const releases = JSON.parse(body);
if (releases.message) return reject(wasReported(releases.message));
const found = releases.filter(({ tag_name }) => tag_name === tag); // eslint-disable-line camelcase
assert(found.length <= 1);
if (!found.length) return resolve(undefined);
resolve(found[0]);
});
});
}
createRelease (tag) {
return new Promise((resolve, reject) => {
const form = JSON.stringify({
tag_name: tag,
target_commitish: 'master', // TODO maybe git rev-parse HEAD
name: tag,
draft: true,
prerelease: true
});
const url = `https://api.github.com/repos/${this.owner}/${this.repo}/releases`;
this.request.post(url, { form }, (error, response, body) => {
if (error) return reject(wasReported(error.message));
const release = JSON.parse(body);
if (release.message) return reject(wasReported(release.message));
resolve(release);
});
});
}
uploadAsset (file, release, name) {
assert(!(/[\\/]/.test(name)));
return new Promise((resolve, reject) => {
fs.stat(file, (error, stat) => {
if (error) return reject(error);
const headers = {
'Content-Length': stat.size,
'Content-Type': 'application/octet-stream'
};
const rs = fs.createReadStream(file);
const subst = `?name=${name}`;
const url = release.upload_url.replace(/\{\?name,label\}/, subst);
const req = this.request.post(url, {
headers, timeout: 30 * 60 * 1000
}, (error2, response, body) => {
if (error2) return reject(wasReported(error2.message));
const asset = JSON.parse(body);
const { errors } = asset;
if (errors && errors[0]) return reject(wasReported(errors[0].code));
if (asset.message) return reject(wasReported(asset.message));
resolve(asset);
});
rs.pipe(req);
});
});
}
downloadUrl (url, file, short) {
log.enableProgress(short);
log.showProgress(0);
return new Promise((resolve, reject) => {
const headers = { Accept: 'application/octet-stream' };
const ws = fs.createWriteStream(file);
let result;
const req = progress(this.request.get(url, {
headers
}, (error, response) => {
if (error) {
log.disableProgress();
return reject(wasReported(error.message));
}
if (response.statusCode !== 200) {
log.disableProgress();
const message = `${response.statusCode} ${response.body}`;
return reject(wasReported(message, url));
}
log.showProgress(100);
log.disableProgress();
result = response;
}));
req.on('progress', (state) => {
let p;
if (state.size && state.size.transferred && state.size.total) {
p = state.size.transferred / state.size.total;
} else {
p = state.percentage;
}
log.showProgress(p * 100);
});
req.pipe(ws);
ws.on('close', () => {
resolve(result);
}).on('error', (error) => {
reject(error);
});
});
}
async tryDirectly (tag, name, file, short) {
try {
const url = `https://github.com/${this.owner}/${this.repo}/releases/download/${tag}/${name}`;
await this.downloadUrl(url, file, short);
return true;
} catch (error) {
log.info(`Asset '${tag}/${name}' not found by direct link`);
return false;
}
}
}