@mathquis/node-pre-gyp-github
Version:
A node-pre-gyp module which provides the ability to publish to GitHub releases.
141 lines (113 loc) • 4.29 kB
JavaScript
const Path = require('path');
const File = require('fs');
const Mime = require('mime-types');
const {Octokit} = require('@octokit/rest');
async function publish(options) {
options || (options = {})
const verbose = !!options.verbose
function log(message) {
console.log(message)
}
function debug(message) {
if ( verbose ) console.log(`[DEBUG] ${message}`)
}
const stageDir = Path.resolve(process.cwd(), './build/stage')
// Validate token
const token = process.env['NODE_PRE_GYP_GITHUB_TOKEN']
if ( !token ) {
throw new Error('NODE_PRE_GYP_GITHUB_TOKEN environment variable not found')
}
// Read package.json
let metadata
try {
const packagePath = Path.resolve(process.cwd(), './package.json')
const content = await File.promises.readFile(packagePath)
metadata = JSON.parse(content)
} catch (err) {
throw new Error(`Unable to read package.json (${err.message})`)
}
// Validate repository
const {repository} = metadata
if ( !repository || !repository.url ) {
throw new Error('Missing repository.url in package.json');
}
const match = repository.url.match(/https?:\/\/([^\/]+)\/(.*)(?=\.git)/i);
if( !match ) {
throw new Error('A correctly formatted GitHub repository.url was not found within package.json');
}
const [, repositoryHost, repositoryUri] = match;
const [owner, repo] = repositoryUri.split('/');
// Validate binary
const hostPrefix = `https://${repositoryHost}/${owner}/${repo}/releases/download/`;
const {binary} = metadata
if ( !binary || !binary.host ) {
throw new Error('Missing binary.host in package.json');
}
if ( hostPrefix !== binary.host ) {
throw new Error(`Invalid binary.host: Should be ${hostPrefix}`)
}
// Validate remote path
if ( !binary.remote_path ) {
throw new Error('Missing binary.remote_path')
}
const tagName = binary.remote_path.replace(/\{version\}/g, metadata.version);
const tagDir = Path.join(stageDir, tagName);
// Github API client
const client = new Octokit({
auth: token,
baseUrl: `https://api.${repositoryHost}`
})
const {data: releases} = await client.repos.listReleases({
owner, repo
})
let release = releases.find(release => release.tag_name === tagName)
// Create a release if none found
if( !release ) {
release = await client.repos.createRelease( {
'host': repositoryHost,
'owner': owner,
'repo': repo,
'tag_name': metadata.version,
'target_commitish': 'master',
'name': 'v' + metadata.version,
'body': `$[metadata.name} ${metadata.version}`,
'draft': !!options.draft,
'prerelease': false
});
}
// List stage dir assets
const files = await File.promises.readdir(tagDir)
if( files.length === 0 ) {
throw new Error('No files found within the stage directory: ' + tagDir);
}
// Upload assets to Github
await files.reduce(async (p, file) => {
await p
if( release && release.assets ) {
const asset = release.assets.find(asset => asset.name === file)
if( asset ) {
// TODO: Should be a warning
log("Staged file " + file + " found but it already exists in release " + release.tag_name + ". If you would like to replace it, you must first manually delete it within GitHub.");
return;
}
}
const fileName = file
const filePath = Path.resolve(tagDir, file)
const fileContent = await File.promises.readFile(filePath)
log("Staged file " + file + " found. Proceeding to upload it.");
await client.repos.uploadReleaseAsset({
url: release.upload_url,
owner: owner,
id: release.id,
repo: repo,
name: fileName,
data: fileContent,
contentType: Mime.contentType(fileName) || 'application/octet-stream',
contentLength: fileContent.length
})
log('Staged file ' + fileName + ' saved to ' + owner + '/' + repo + ' release ' + release.tag_name + ' successfully.');
}, Promise.resolve());
log('Done')
}
module.exports = {publish};
;