mirror of https://github.com/sualko/cloud_bbb
build: enhance publish script
create change log, upload to app store, some fixespull/16/head
parent
b0c44d8fa5
commit
44daf3f959
|
@ -5,6 +5,7 @@
|
||||||
2,
|
2,
|
||||||
"always",
|
"always",
|
||||||
[
|
[
|
||||||
|
"release",
|
||||||
"build",
|
"build",
|
||||||
"ci",
|
"ci",
|
||||||
"chore",
|
"chore",
|
||||||
|
|
|
@ -86,6 +86,7 @@
|
||||||
"eslint-plugin-standard": "^4.0.1",
|
"eslint-plugin-standard": "^4.0.1",
|
||||||
"file-loader": "^6.0.0",
|
"file-loader": "^6.0.0",
|
||||||
"husky": "^4.2.5",
|
"husky": "^4.2.5",
|
||||||
|
"inquirer": "^7.1.0",
|
||||||
"node-sass": "^4.13.1",
|
"node-sass": "^4.13.1",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
|
|
|
@ -6,14 +6,17 @@ require('colors').setTheme({
|
||||||
|
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
const https = require('https');
|
||||||
const { Octokit } = require("@octokit/rest");
|
const { Octokit } = require("@octokit/rest");
|
||||||
const execa = require('execa');
|
const execa = require('execa');
|
||||||
|
const inquirer = require('inquirer');
|
||||||
const git = require('simple-git/promise')();
|
const git = require('simple-git/promise')();
|
||||||
const package = require('../package.json');
|
const package = require('../package.json');
|
||||||
|
|
||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
|
|
||||||
const commitMessage = `build: ${package.version}`;
|
const isDryRun = process.argv.indexOf('--dry-run') > 1;
|
||||||
|
const commitMessage = `release: ${package.version} :tada:`;
|
||||||
const tagName = `v${package.version}`;
|
const tagName = `v${package.version}`;
|
||||||
const files = [
|
const files = [
|
||||||
path.join(__dirname, '..', 'archives', `bbb-v${package.version}.tar.gz`),
|
path.join(__dirname, '..', 'archives', `bbb-v${package.version}.tar.gz`),
|
||||||
|
@ -22,8 +25,14 @@ const files = [
|
||||||
path.join(__dirname, '..', 'archives', `bbb-v${package.version}.tar.gz.sig`),
|
path.join(__dirname, '..', 'archives', `bbb-v${package.version}.tar.gz.sig`),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
function pull() {
|
||||||
|
return git.pull('origin', 'master');
|
||||||
|
}
|
||||||
|
|
||||||
async function notAlreadyTagged() {
|
async function notAlreadyTagged() {
|
||||||
return (await git.tags()).all.indexOf(tagName) < 0;
|
if ((await git.tags()).all.includes(tagName)) {
|
||||||
|
throw 'version already tagged';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function lastCommitNotBuild() {
|
async function lastCommitNotBuild() {
|
||||||
|
@ -34,21 +43,118 @@ async function isMasterBranch() {
|
||||||
return (await git.branch()) === 'master';
|
return (await git.branch()) === 'master';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function generateChangelog() {
|
||||||
|
const latestTag = (await git.tags()).latest;
|
||||||
|
const title = `v${package.version}` === latestTag ? '[Unreleased]' : `${package.version} (${new Date().toISOString().split('T')[0]})`;
|
||||||
|
|
||||||
|
const logs = await git.log({
|
||||||
|
from: latestTag,
|
||||||
|
to: 'HEAD'
|
||||||
|
});
|
||||||
|
|
||||||
|
const sections = [{
|
||||||
|
type: 'feat',
|
||||||
|
label: 'Added',
|
||||||
|
}, {
|
||||||
|
type: 'fix',
|
||||||
|
label: 'Fixed',
|
||||||
|
}];
|
||||||
|
|
||||||
|
const entries = {};
|
||||||
|
|
||||||
|
logs.all.forEach(log => {
|
||||||
|
let [, type, scope, description] = log.message.match(/^([a-z]+)(?:\((\w+)\))?: (.+)/);
|
||||||
|
let entry = { type, scope, description, issues: [] };
|
||||||
|
|
||||||
|
for (let match of log.body.match(/(?:fix|fixes|closes?|refs?) #(\d+)/g)) {
|
||||||
|
const [, number] = match.match(/(\d+)$/);
|
||||||
|
|
||||||
|
entry.issues.push(number);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!entries[type]) {
|
||||||
|
entries[type] = []
|
||||||
|
}
|
||||||
|
|
||||||
|
entries[type].push(entry);
|
||||||
|
});
|
||||||
|
|
||||||
|
let changeLog = `## ${title}\n`;
|
||||||
|
|
||||||
|
function stringifyEntry(entry) {
|
||||||
|
let issues = entry.issues.map(issue => {
|
||||||
|
return `[#${issue}](https://github.com/sualko/cloud_bbb/issues/${issue})`;
|
||||||
|
}).join('');
|
||||||
|
return `- ${issues} ${entry.description}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
sections.forEach(section => {
|
||||||
|
if (!entries[section.type]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
changeLog += `### ${section.label}\n`;
|
||||||
|
|
||||||
|
entries[section.type].forEach(entry => {
|
||||||
|
changeLog += stringifyEntry(entry);
|
||||||
|
});
|
||||||
|
|
||||||
|
delete entries[section.type];
|
||||||
|
|
||||||
|
changeLog += `\n`
|
||||||
|
});
|
||||||
|
|
||||||
|
const miscKeys = Object.keys(entries);
|
||||||
|
|
||||||
|
if (miscKeys && miscKeys.length > 0) {
|
||||||
|
changeLog += `### Misc\n`;
|
||||||
|
|
||||||
|
miscKeys.forEach(type => {
|
||||||
|
entries[type].forEach(entry => {
|
||||||
|
changeLog += stringifyEntry(entry);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return changeLog;
|
||||||
|
}
|
||||||
|
|
||||||
function hasChangeLogEntry() {
|
function hasChangeLogEntry() {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
fs.readFile(path.join(__dirname, '..', 'CHANGELOG.md'), function (err, data) {
|
fs.readFile(path.join(__dirname, '..', 'CHANGELOG.md'), function (err, data) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
resolve(data.includes(`[${package.version}]`));
|
if (!data.includes(`## ${package.version}`)) {
|
||||||
|
throw `Found no change log entry for ${package.version}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function commitChangeLog() {
|
||||||
|
let status = await git.status();
|
||||||
|
|
||||||
|
if (status.staged.length > 0) {
|
||||||
|
throw 'Repo not clean. Found staged files.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDryRun) {
|
||||||
|
await git.add('CHANGELOG.md');
|
||||||
|
await git.commit('docs: update change log');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function hasArchiveAndSignatures() {
|
async function hasArchiveAndSignatures() {
|
||||||
return files.map(file => fs.existsSync(file)).indexOf(false) < 0;
|
return files.map(file => fs.existsSync(file)).indexOf(false) < 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function stageAllFiles() {
|
async function stageAllFiles() {
|
||||||
|
if (isDryRun) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let gitProcess = execa('git', ['add', '-u']);
|
let gitProcess = execa('git', ['add', '-u']);
|
||||||
|
|
||||||
gitProcess.stdout.pipe(process.stdout);
|
gitProcess.stdout.pipe(process.stdout);
|
||||||
|
@ -65,29 +171,54 @@ function showStagedDiff() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function keypress() {
|
async function keypress() {
|
||||||
process.stdin.setRawMode(true);
|
return inquirer.prompt([{
|
||||||
|
type: 'input',
|
||||||
return new Promise(resolve => process.stdin.once('data', () => {
|
name: 'keypress',
|
||||||
process.stdin.setRawMode(false)
|
message: 'Press any key to continue... (where is the any key?)',
|
||||||
resolve()
|
}]);
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function commit() {
|
function commit() {
|
||||||
|
if (isDryRun) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return git.commit(commitMessage, ['-S', '-n']);
|
return git.commit(commitMessage, ['-S', '-n']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function wantToContinue(message) {
|
||||||
|
let answers = await inquirer.prompt([{
|
||||||
|
type: 'confirm',
|
||||||
|
name: 'continue',
|
||||||
|
message,
|
||||||
|
default: false,
|
||||||
|
}]);
|
||||||
|
|
||||||
|
if (!answers.continue) {
|
||||||
|
process.exit(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function push() {
|
function push() {
|
||||||
|
if (isDryRun) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return git.push('origin', 'master');
|
return git.push('origin', 'master');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createGithubRelease() {
|
async function createGithubRelease(changeLog) {
|
||||||
|
if (!process.env.GITHUB_TOKEN) {
|
||||||
|
throw 'Github token missing'
|
||||||
|
}
|
||||||
|
|
||||||
const octokit = new Octokit({
|
const octokit = new Octokit({
|
||||||
auth: process.env.GITHUB_TOKEN,
|
auth: process.env.GITHUB_TOKEN,
|
||||||
userAgent: 'custom releaser for sualko/cloud_bbb',
|
userAgent: 'custom releaser for sualko/cloud_bbb',
|
||||||
});
|
});
|
||||||
|
|
||||||
let matches = (await git.remote(['get-url', 'origin'])).match(/^git@github\.com:(.+)\/(.+)\.git$/);
|
let origin = (await git.remote(['get-url', 'origin'])).trim();
|
||||||
|
let matches = origin.match(/^git@github\.com:(.+)\/(.+)\.git$/);
|
||||||
|
|
||||||
if (!matches) {
|
if (!matches) {
|
||||||
throw 'Origin is not configured or no ssh url';
|
throw 'Origin is not configured or no ssh url';
|
||||||
|
@ -95,33 +226,129 @@ async function createGithubRelease() {
|
||||||
|
|
||||||
const owner = matches[1];
|
const owner = matches[1];
|
||||||
const repo = matches[2];
|
const repo = matches[2];
|
||||||
|
const releaseOptions = {
|
||||||
let releaseResponse = await octokit.repos.createRelease({
|
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
tag_name: tagName,
|
tag_name: tagName,
|
||||||
name: tagName,
|
name: tagName,
|
||||||
body: '', //@TODO
|
body: changeLog,
|
||||||
draft: true,
|
draft: true,
|
||||||
prerelease: !/^\d+\.\d+\.\d+$/.test(package.version),
|
prerelease: !/^\d+\.\d+\.\d+$/.test(package.version),
|
||||||
});
|
};
|
||||||
|
|
||||||
console.log('Draft created, see ' + releaseResponse.data.html_url);
|
if (isDryRun) {
|
||||||
|
console.log('github release options', releaseOptions);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let releaseResponse = await octokit.repos.createRelease(releaseOptions);
|
||||||
|
|
||||||
|
console.log(`Draft created, see ${releaseResponse.data.html_url}`.verbose);
|
||||||
|
|
||||||
|
function getMimeType(filename) {
|
||||||
|
if (filename.endsWith('.asc') || filename.endsWith('sig')) {
|
||||||
|
return 'application/pgp-signature';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filename.endsWith('.tar.gz')) {
|
||||||
|
return 'application/gzip';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filename.endsWith('.ncsig')) {
|
||||||
|
return 'text/plain';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'application/octet-stream';
|
||||||
|
}
|
||||||
|
|
||||||
|
let assetUrls = [];
|
||||||
|
|
||||||
files.forEach(async file => {
|
files.forEach(async file => {
|
||||||
let assetResponse = await octokit.repos.uploadReleaseAsset({
|
const filename = path.basename(file);
|
||||||
|
const uploadOptions = {
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
release_id: releaseResponse.data.id,
|
release_id: releaseResponse.data.id,
|
||||||
data: fs.createReadStream(file),
|
data: fs.createReadStream(file),
|
||||||
name: path.basename(file),
|
headers: {
|
||||||
});
|
'content-type': getMimeType(filename),
|
||||||
|
'content-length': fs.statSync(file)[size],
|
||||||
|
},
|
||||||
|
name: filename,
|
||||||
|
};
|
||||||
|
|
||||||
console.log('Asset uploaded: ' + assetResponse.data.name);
|
let assetResponse = await octokit.repos.uploadReleaseAsset(uploadOptions);
|
||||||
})
|
|
||||||
|
console.log(`Asset uploaded: ${assetResponse.data.name}`.verbose);
|
||||||
|
|
||||||
|
assetUrls.push(assetResponse.data.browser_download_url);
|
||||||
|
});
|
||||||
|
|
||||||
|
return assetUrls;
|
||||||
}
|
}
|
||||||
|
|
||||||
(async () => {
|
async function uploadToNextcloudStore(archiveUrl) {
|
||||||
|
if(!process.env.NEXTCLOUD_TOKEN) {
|
||||||
|
throw 'Nextcloud token missing';
|
||||||
|
}
|
||||||
|
|
||||||
|
const hostname = 'apps.nextcloud.com';
|
||||||
|
const apiEndpoint = '/api/v1/apps/releases';
|
||||||
|
const signatureFile = files.find(file => file.endsWith('.ncsig'));
|
||||||
|
const data = JSON.stringify({
|
||||||
|
download: archiveUrl,
|
||||||
|
signature: fs.readFileSync(signatureFile, 'utf-8'),
|
||||||
|
nightly: false,
|
||||||
|
});
|
||||||
|
const options = {
|
||||||
|
hostname,
|
||||||
|
port: 443,
|
||||||
|
path: apiEndpoint,
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Content-Length': data.length,
|
||||||
|
'Authorization': `Token ${process.env.NEXTCLOUD_TOKEN}`,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isDryRun) {
|
||||||
|
console.log('nextcloud app store request', options, data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const req = https.request(options, res => {
|
||||||
|
if (res.statusCode === 200) {
|
||||||
|
console.log('App release was updated successfully'.verbose);
|
||||||
|
resolve();
|
||||||
|
} else if (res.statusCode === 201) {
|
||||||
|
console.log('App release was created successfully'.verbose);
|
||||||
|
resolve();
|
||||||
|
} else if (res.statusCode === 400) {
|
||||||
|
reject('App release was not accepted');
|
||||||
|
} else {
|
||||||
|
reject('App release rejected with status ' + res.statusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.on('data', d => {
|
||||||
|
process.stdout.write(d)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
req.on('error', error => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
req.write(data);
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
await pull();
|
||||||
|
console.log(`✔ pulled latest changes`.green);
|
||||||
|
|
||||||
await notAlreadyTagged();
|
await notAlreadyTagged();
|
||||||
console.log(`✔ not already tagged`.green);
|
console.log(`✔ not already tagged`.green);
|
||||||
|
|
||||||
|
@ -131,9 +358,19 @@ async function createGithubRelease() {
|
||||||
await isMasterBranch();
|
await isMasterBranch();
|
||||||
console.log(`✔ this is the master branch`.green);
|
console.log(`✔ this is the master branch`.green);
|
||||||
|
|
||||||
|
const changeLog = await generateChangelog();
|
||||||
|
console.log(changeLog.verbose);
|
||||||
|
console.log(`✔ change log generated`.green);
|
||||||
|
|
||||||
|
console.log('Press any key to continue...');
|
||||||
|
await keypress();
|
||||||
|
|
||||||
await hasChangeLogEntry();
|
await hasChangeLogEntry();
|
||||||
console.log(`✔ there is a change log entry for this version`.green);
|
console.log(`✔ there is a change log entry for this version`.green);
|
||||||
|
|
||||||
|
await commitChangeLog();
|
||||||
|
console.log(`✔ change log commited`.green);
|
||||||
|
|
||||||
await hasArchiveAndSignatures();
|
await hasArchiveAndSignatures();
|
||||||
console.log(`✔ found archive and signatures`.green);
|
console.log(`✔ found archive and signatures`.green);
|
||||||
|
|
||||||
|
@ -142,21 +379,29 @@ async function createGithubRelease() {
|
||||||
|
|
||||||
await showStagedDiff();
|
await showStagedDiff();
|
||||||
|
|
||||||
console.log('Press any key to continue...');
|
await wantToContinue('Should I commit those changes?');
|
||||||
await keypress();
|
|
||||||
|
|
||||||
await commit();
|
await commit();
|
||||||
console.log(`✔ All files commited`.green);
|
console.log(`✔ All files commited`.green);
|
||||||
|
|
||||||
console.log('Press any key to continue...');
|
await wantToContinue('Should I push all pending commits?');
|
||||||
await keypress();
|
|
||||||
|
|
||||||
await push();
|
await push();
|
||||||
console.log(`✔ All commits pushed`.green);
|
console.log(`✔ All commits pushed`.green);
|
||||||
|
|
||||||
await createGithubRelease();
|
await wantToContinue('Should I continue to create a Github release?');
|
||||||
console.log(`✔ released on github`.green);
|
|
||||||
})();
|
|
||||||
|
|
||||||
// create changelog
|
const assetUrls = await createGithubRelease(changeLog);
|
||||||
// upload nextcloud app store
|
console.log(`✔ released on github`.green);
|
||||||
|
|
||||||
|
const archiveAssetUrl = assetUrls.find(url => url.endsWith('.tar.gz'));
|
||||||
|
|
||||||
|
await wantToContinue('Should I continue to upload the release to the app store?');
|
||||||
|
|
||||||
|
await uploadToNextcloudStore(archiveAssetUrl);
|
||||||
|
console.log(`✔ released in Nextcloud app store`.green);
|
||||||
|
};
|
||||||
|
|
||||||
|
run().catch(err => {
|
||||||
|
console.log(`✘ ${err.toString()}`.error);
|
||||||
|
});
|
||||||
|
|
|
@ -4452,7 +4452,7 @@ ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
|
||||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
|
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
|
||||||
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
|
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
|
||||||
|
|
||||||
inquirer@^7.0.0:
|
inquirer@^7.0.0, inquirer@^7.1.0:
|
||||||
version "7.1.0"
|
version "7.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.1.0.tgz#1298a01859883e17c7264b82870ae1034f92dd29"
|
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.1.0.tgz#1298a01859883e17c7264b82870ae1034f92dd29"
|
||||||
integrity sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==
|
integrity sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==
|
||||||
|
|
Loading…
Reference in New Issue