2021-02-01 10:57:41 +01:00
|
|
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
2024-10-25 17:47:48 +02:00
|
|
|
/**
|
|
|
|
* npm run release:build [--dry-run] [--stable]
|
|
|
|
*
|
|
|
|
* --dry-run don't commit CHANGELOG.md
|
|
|
|
* --stable generate changelog
|
|
|
|
*
|
|
|
|
*/
|
2021-02-01 10:57:41 +01:00
|
|
|
const colors = require('colors');
|
|
|
|
const fs = require('fs');
|
|
|
|
const path = require('path');
|
|
|
|
const libxml = require('libxmljs');
|
|
|
|
const https = require('https');
|
|
|
|
const archiver = require('archiver');
|
2022-07-24 14:06:59 +02:00
|
|
|
const simpleGit = require('simple-git');
|
2021-02-01 10:57:41 +01:00
|
|
|
const inquirer = require('inquirer');
|
|
|
|
const { exec } = require('child_process');
|
2021-02-24 15:45:55 +01:00
|
|
|
const { generateChangelog, hasChangeLogEntry } = require('./imports/changelog');
|
2021-01-31 18:40:35 +01:00
|
|
|
|
|
|
|
const packageInfo = require('../package.json');
|
|
|
|
|
|
|
|
colors.setTheme({
|
2020-04-29 14:16:57 +02:00
|
|
|
verbose: 'cyan',
|
|
|
|
warn: 'yellow',
|
|
|
|
error: 'red',
|
2020-04-28 01:08:00 +02:00
|
|
|
});
|
|
|
|
|
2021-01-31 18:40:35 +01:00
|
|
|
const git = simpleGit();
|
2020-04-28 01:08:00 +02:00
|
|
|
const infoXmlPath = './appinfo/info.xml';
|
|
|
|
const isStableRelease = process.argv.indexOf('--stable') > 1;
|
2021-01-31 18:40:35 +01:00
|
|
|
const isDryRun = process.argv.indexOf('--dry-run') > 1;
|
2020-04-28 01:08:00 +02:00
|
|
|
|
|
|
|
async function getVersion() {
|
2021-01-31 18:40:35 +01:00
|
|
|
return packageInfo.version + (!isStableRelease ? '-git.' + (await git.raw(['rev-parse', '--short', 'HEAD'])).trim() : '');
|
2020-04-28 01:08:00 +02:00
|
|
|
}
|
|
|
|
|
2021-01-31 18:40:35 +01:00
|
|
|
run().catch(err => {
|
|
|
|
console.log(`✘ ${err.toString()}`.error);
|
|
|
|
});
|
2020-04-28 01:08:00 +02:00
|
|
|
|
|
|
|
async function run() {
|
2020-04-29 14:16:57 +02:00
|
|
|
const appId = await prepareInfoXml();
|
|
|
|
await createRelease(appId);
|
2020-04-28 01:08:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
async function prepareInfoXml() {
|
2020-04-29 14:16:57 +02:00
|
|
|
const infoFile = fs.readFileSync(infoXmlPath);
|
|
|
|
const xmlDoc = libxml.parseXml(infoFile);
|
2020-04-28 01:08:00 +02:00
|
|
|
|
2020-04-29 14:16:57 +02:00
|
|
|
updateVersion(xmlDoc, await getVersion());
|
|
|
|
await validateXml(xmlDoc);
|
2020-04-28 01:08:00 +02:00
|
|
|
|
2020-04-29 14:16:57 +02:00
|
|
|
return xmlDoc.get('//id').text();
|
2020-04-28 01:08:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function updateVersion(xmlDoc, version) {
|
2020-04-29 14:16:57 +02:00
|
|
|
const versionChild = xmlDoc.get('//version');
|
|
|
|
const currentVersion = versionChild.text();
|
2020-04-28 01:08:00 +02:00
|
|
|
|
2020-04-29 14:16:57 +02:00
|
|
|
if (version !== currentVersion) {
|
|
|
|
console.log(`✔ Update version in info.xml to ${version}.`.green);
|
2020-04-28 01:08:00 +02:00
|
|
|
|
2020-04-29 14:16:57 +02:00
|
|
|
versionChild.text(version);
|
2020-04-28 01:08:00 +02:00
|
|
|
|
2020-04-29 14:16:57 +02:00
|
|
|
fs.writeFileSync(infoXmlPath, xmlDoc.toString());
|
|
|
|
}
|
2020-04-28 01:08:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
async function createRelease(appId) {
|
2020-04-29 14:16:57 +02:00
|
|
|
const version = await getVersion();
|
|
|
|
console.log(`I'm now building ${appId} in version ${version}.`.verbose);
|
2020-04-28 01:08:00 +02:00
|
|
|
|
2021-04-19 11:34:47 +02:00
|
|
|
if (isStableRelease) {
|
|
|
|
await isRepoClean();
|
|
|
|
console.log('✔ repo is clean'.green);
|
|
|
|
}
|
2021-01-31 18:40:35 +01:00
|
|
|
|
2022-07-24 14:06:59 +02:00
|
|
|
const execa = (await import('execa')).execaCommand;
|
|
|
|
|
2024-09-18 21:22:10 +02:00
|
|
|
const composerDev = await execa('composer install');
|
|
|
|
console.log(composerDev.stdout, composerDev.stderr);
|
2020-04-29 14:16:57 +02:00
|
|
|
console.log('✔ composer dev dependencies installed'.green);
|
2020-04-28 01:08:00 +02:00
|
|
|
|
2024-10-08 15:46:02 +02:00
|
|
|
await execa('yarn lint');
|
2020-04-29 14:16:57 +02:00
|
|
|
console.log('✔ linters are happy'.green);
|
2020-04-28 01:08:00 +02:00
|
|
|
|
2024-09-18 21:22:10 +02:00
|
|
|
const composerNoDev = await execa('composer install --no-dev');
|
|
|
|
console.log(composerNoDev.stdout, composerNoDev.stderr);
|
2020-04-29 14:16:57 +02:00
|
|
|
console.log('✔ composer dependencies installed'.green);
|
2020-04-28 01:08:00 +02:00
|
|
|
|
2024-10-08 15:46:02 +02:00
|
|
|
await execa('yarn build');
|
|
|
|
console.log('✔ webpack done'.green);
|
2020-04-28 01:08:00 +02:00
|
|
|
|
2021-01-31 18:40:35 +01:00
|
|
|
await updateChangelog();
|
|
|
|
|
2020-04-29 14:16:57 +02:00
|
|
|
const filePath = await createArchive(appId, appId + '-v' + version);
|
|
|
|
await createNextcloudSignature(appId, filePath);
|
|
|
|
await createGPGSignature(filePath);
|
|
|
|
await createGPGArmorSignature(filePath);
|
2020-06-15 13:27:57 +02:00
|
|
|
|
2024-09-18 21:22:10 +02:00
|
|
|
await execa('composer install');
|
2020-06-15 13:27:57 +02:00
|
|
|
console.log('✔ composer dev dependencies installed'.green);
|
2020-04-28 01:08:00 +02:00
|
|
|
}
|
|
|
|
|
2021-01-31 18:40:35 +01:00
|
|
|
async function isRepoClean() {
|
|
|
|
const status = await git.status();
|
|
|
|
|
2021-04-19 11:34:47 +02:00
|
|
|
if (status.staged.length > 1) {
|
2021-01-31 18:40:35 +01:00
|
|
|
throw 'Repo not clean. Found staged files.';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (status.modified.length > 2 || !status.modified.includes('package.json') || !status.modified.includes('appinfo/info.xml')) {
|
|
|
|
throw 'Repo not clean. Found modified files.';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (status.not_added.length > 0) {
|
|
|
|
throw 'Repo not clean. Found not added files.';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function updateChangelog() {
|
|
|
|
if (!isStableRelease) {
|
|
|
|
console.log('Skip changelog for non-stable releases.'.warn);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const changeLog = await generateChangelog(packageInfo.version);
|
|
|
|
console.log('✔ change log generated'.green);
|
|
|
|
|
|
|
|
console.log(changeLog);
|
|
|
|
|
|
|
|
console.log('Press any key to continue...');
|
|
|
|
await keypress();
|
|
|
|
|
|
|
|
await hasChangeLogEntry(packageInfo.version);
|
|
|
|
console.log('✔ there is a change log entry for this version'.green);
|
|
|
|
|
|
|
|
await commitChangeLog();
|
|
|
|
console.log('✔ change log commited'.green);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function keypress() {
|
|
|
|
return inquirer.prompt([{
|
|
|
|
type: 'input',
|
|
|
|
name: 'keypress',
|
|
|
|
message: 'Press any key to continue... (where is the any key?)',
|
|
|
|
}]);
|
|
|
|
}
|
|
|
|
|
2021-02-24 15:45:55 +01:00
|
|
|
async function commitChangeLog() {
|
2021-01-31 18:40:35 +01:00
|
|
|
const 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', ['-n']);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-28 01:08:00 +02:00
|
|
|
|
|
|
|
function createArchive(appId, fileBaseName) {
|
2024-10-25 17:47:48 +02:00
|
|
|
const archivesPath = path.normalize(__dirname + '/../archives/');
|
|
|
|
if (!fs.existsSync(archivesPath)){
|
|
|
|
fs.mkdirSync(archivesPath);
|
|
|
|
}
|
|
|
|
|
2020-04-29 14:16:57 +02:00
|
|
|
const fileName = `${fileBaseName}.tar.gz`;
|
|
|
|
const filePath = path.normalize(__dirname + `/../archives/${fileName}`);
|
|
|
|
const output = fs.createWriteStream(filePath);
|
|
|
|
const archive = archiver('tar', {
|
|
|
|
gzip: true,
|
|
|
|
});
|
|
|
|
|
|
|
|
archive.on('warning', function (err) {
|
|
|
|
if (err.code === 'ENOENT') {
|
|
|
|
console.warn('Archive warning: '.warn, err);
|
|
|
|
} else {
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
archive.on('error', function (err) {
|
|
|
|
throw err;
|
|
|
|
});
|
|
|
|
|
|
|
|
archive.pipe(output);
|
|
|
|
|
|
|
|
function addDirectory(name) {
|
|
|
|
archive.directory(name + '/', `${appId}/${name}/`);
|
|
|
|
}
|
|
|
|
|
|
|
|
function addFile(name) {
|
|
|
|
archive.file(name, { name: `${appId}/${name}` });
|
|
|
|
}
|
|
|
|
|
|
|
|
addDirectory('appinfo');
|
|
|
|
addDirectory('img');
|
|
|
|
addDirectory('js'),
|
2020-05-24 18:49:19 +02:00
|
|
|
addDirectory('l10n'),
|
2020-04-29 14:16:57 +02:00
|
|
|
addDirectory('lib');
|
|
|
|
addDirectory('templates');
|
|
|
|
addFile('COPYING');
|
|
|
|
addFile('README.md');
|
2021-01-22 16:44:38 +01:00
|
|
|
addFile('CHANGELOG.md');
|
2020-04-29 14:16:57 +02:00
|
|
|
|
|
|
|
archive.glob('vendor/**/*', {
|
|
|
|
ignore: ['.git'],
|
|
|
|
}, {
|
|
|
|
prefix: appId,
|
|
|
|
});
|
|
|
|
|
|
|
|
return new Promise(resolve => {
|
|
|
|
output.on('close', function () {
|
|
|
|
console.log(`✔ Wrote ${archive.pointer()} bytes to ${fileName}`.green);
|
|
|
|
|
|
|
|
resolve(filePath);
|
|
|
|
});
|
|
|
|
|
|
|
|
archive.finalize();
|
|
|
|
});
|
2020-04-28 01:08:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function createNextcloudSignature(appId, filePath) {
|
2021-02-24 15:45:55 +01:00
|
|
|
return new Promise((resolve) => {
|
2020-04-29 14:16:57 +02:00
|
|
|
const sigPath = filePath + '.ncsig';
|
|
|
|
exec(`openssl dgst -sha512 -sign ~/.nextcloud/certificates/${appId}.key ${filePath} | openssl base64 > ${sigPath}`, (error, stdout, stderr) => {
|
|
|
|
if (error) {
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stdout) {
|
|
|
|
console.log(`stdout: ${stdout}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stderr) {
|
|
|
|
console.log(`stderr: ${stderr}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log(`✔ created Nextcloud signature: ${path.basename(sigPath)}`.green);
|
|
|
|
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
});
|
2020-04-28 01:08:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function createGPGSignature(filePath) {
|
2021-02-24 15:45:55 +01:00
|
|
|
return new Promise((resolve) => {
|
2020-04-29 14:16:57 +02:00
|
|
|
exec(`gpg --yes --detach-sign "${filePath}"`, (error, stdout, stderr) => {
|
|
|
|
if (error) {
|
|
|
|
throw error;
|
|
|
|
}
|
2020-04-28 01:08:00 +02:00
|
|
|
|
2020-04-29 14:16:57 +02:00
|
|
|
if (stdout) {
|
|
|
|
console.log(`stdout: ${stdout}`);
|
|
|
|
}
|
2020-04-28 01:08:00 +02:00
|
|
|
|
2020-04-29 14:16:57 +02:00
|
|
|
if (stderr) {
|
|
|
|
console.log(`stderr: ${stderr}`);
|
|
|
|
}
|
2020-04-28 01:08:00 +02:00
|
|
|
|
2020-04-29 14:16:57 +02:00
|
|
|
console.log(`✔ created detached signature: ${path.basename(filePath)}.sig`.green);
|
2020-04-28 01:08:00 +02:00
|
|
|
|
2020-04-29 14:16:57 +02:00
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
});
|
2020-04-28 01:08:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function createGPGArmorSignature(filePath) {
|
2021-02-24 15:45:55 +01:00
|
|
|
return new Promise((resolve) => {
|
2020-04-29 14:16:57 +02:00
|
|
|
exec(`gpg --yes --detach-sign --armor "${filePath}"`, (error, stdout, stderr) => {
|
|
|
|
if (error) {
|
|
|
|
throw error;
|
|
|
|
}
|
2020-04-28 01:08:00 +02:00
|
|
|
|
2020-04-29 14:16:57 +02:00
|
|
|
if (stdout) {
|
|
|
|
console.log(`stdout: ${stdout}`);
|
|
|
|
}
|
2020-04-28 01:08:00 +02:00
|
|
|
|
2020-04-29 14:16:57 +02:00
|
|
|
if (stderr) {
|
|
|
|
console.log(`stderr: ${stderr}`);
|
|
|
|
}
|
2020-04-28 01:08:00 +02:00
|
|
|
|
2020-04-29 14:16:57 +02:00
|
|
|
console.log(`✔ created detached signature: ${path.basename(filePath)}.asc`.green);
|
2020-04-28 01:08:00 +02:00
|
|
|
|
2020-04-29 14:16:57 +02:00
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
});
|
2020-04-28 01:08:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
async function validateXml(xmlDoc) {
|
2020-04-29 14:16:57 +02:00
|
|
|
const schemaLocation = xmlDoc.root().attr('noNamespaceSchemaLocation').value();
|
2020-04-28 01:08:00 +02:00
|
|
|
|
2020-04-29 14:16:57 +02:00
|
|
|
if (!schemaLocation) {
|
|
|
|
throw 'Found no schema location';
|
|
|
|
}
|
2020-04-28 01:08:00 +02:00
|
|
|
|
2021-02-24 15:45:55 +01:00
|
|
|
let schemaString;
|
2020-04-29 14:16:57 +02:00
|
|
|
try {
|
2021-01-31 18:40:35 +01:00
|
|
|
console.log('Downloading schema file...'.verbose);
|
|
|
|
|
2020-04-29 14:16:57 +02:00
|
|
|
schemaString = await wget(schemaLocation);
|
|
|
|
} catch (err) {
|
|
|
|
console.log('Could not download schema. Skip validation.'.warn);
|
2020-04-28 01:08:00 +02:00
|
|
|
|
2020-04-29 14:16:57 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
const xsdDoc = libxml.parseXml(schemaString);
|
2020-04-28 01:08:00 +02:00
|
|
|
|
2020-04-29 14:16:57 +02:00
|
|
|
if (xmlDoc.validate(xsdDoc)) {
|
|
|
|
console.log('✔ Document valid'.green);
|
|
|
|
} else {
|
|
|
|
console.log('✘ Document INVALID'.error);
|
2020-04-28 01:08:00 +02:00
|
|
|
|
2020-04-29 14:16:57 +02:00
|
|
|
xmlDoc.validationErrors.forEach((error, index) => {
|
|
|
|
console.log(`#${index + 1}\t${error.toString().trim()}`.warn);
|
|
|
|
console.log(`\tLine ${error.line}:${error.column} (level ${error.level})`.verbose);
|
|
|
|
});
|
2020-04-28 01:08:00 +02:00
|
|
|
|
2020-04-29 14:16:57 +02:00
|
|
|
throw 'Abort';
|
|
|
|
}
|
2020-04-28 01:08:00 +02:00
|
|
|
}
|
|
|
|
|
2021-02-24 15:45:55 +01:00
|
|
|
function wget(url) {
|
|
|
|
return new Promise((resolve, reject) => {
|
2020-04-29 14:16:57 +02:00
|
|
|
https.get(url, (resp) => {
|
|
|
|
let data = '';
|
|
|
|
|
|
|
|
resp.on('data', (chunk) => {
|
|
|
|
data += chunk;
|
|
|
|
});
|
|
|
|
|
|
|
|
resp.on('end', () => {
|
|
|
|
resolve(data);
|
|
|
|
});
|
|
|
|
|
|
|
|
}).on('error', (err) => {
|
|
|
|
reject(err);
|
|
|
|
});
|
|
|
|
});
|
2021-02-24 15:45:55 +01:00
|
|
|
}
|