removed all old apps, compacted repo

This commit is contained in:
Aly 2024-05-26 04:20:24 +10:00
commit c6def505cf
14 changed files with 1659 additions and 0 deletions

View file

@ -0,0 +1,168 @@
/*jshint esversion: 6 */
const path = require('path');
const yaml = require('yaml');
const fs = require('fs-extra');
// Next, for V4:
// ============================================================================
// ============================================================================
// *********** THIS IS ONLY TO BE DONE AFTER CAPROVER 1.8 RELEASE *************
// ============================================================================
// ============================================================================
//
// 1- DUPLICATE this script. The new script is to ONLY read from /public/v4/*.yaml
// 2- Test with a new YAML file
// 3- Write script to convert all v2 JSON to V4 yaml and place them in /public/v4/*.yaml
// 4- Update readme!!!!
// 5- Push all 3 steps above at the same time to GITHUB
const pathOfPublic = path.join(__dirname, '..', `public`);
const pathOfDist = path.join(__dirname, '..', `dist`);
const pathOfDistV2 = path.join(pathOfDist, 'v2');
const pathOfDistV3 = path.join(pathOfDist, 'v3');
const pathOfDistV4 = path.join(pathOfDist, 'v4');
const pathOfSourceDirectory = path.join(pathOfPublic, 'v2');
const pathOfSourceDirectoryApps = path.join(pathOfSourceDirectory, 'apps');
const pathOfSourceDirectoryLogos = path.join(pathOfSourceDirectory, 'logos');
function createAppList(appsList, pathOfApps) {
const apps = appsList.filter(v => v.includes('.json'));
const appDetails = [];
for (var i = 0; i < apps.length; i++) {
const contentString = fs.readFileSync(path.join(pathOfApps, apps[i]));
const content = JSON.parse(contentString);
const captainVersion = (content.captainVersion + '');
apps[i] = apps[i].replace('.json', '');
if (captainVersion + '' === '2') {
if (!content.displayName) {
content.displayName = apps[i];
content.displayName = content.displayName.substr(0, 1).toUpperCase() + content.displayName.substring(1, content.displayName.length);
}
if (!content.description) content.description = '';
appDetails[i] = {
name: apps[i],
displayName: content.displayName,
description: content.description,
isOfficial: `${content.isOfficial}`.toLowerCase() === 'true',
logoUrl: apps[i] + '.png'
};
} else {
throw new Error('Unknown captain-version: ' + captainVersion);
}
}
return {
appList: apps,
appDetails: appDetails
};
}
function convertV2toV4(v2String) {
const parsed = JSON.parse(v2String);
if (`${parsed.captainVersion}` !== '2') {
throw new Error('CaptainVersion must be 2 for this conversion');
}
function moveProperty(propertyName) {
parsed.caproverOneClickApp[propertyName] = parsed[propertyName];
parsed[propertyName] = undefined;
}
parsed.services = parsed.dockerCompose.services;
parsed.dockerCompose = undefined;
parsed.captainVersion = 4;
parsed.caproverOneClickApp = {};
moveProperty('variables');
moveProperty('instructions');
moveProperty('displayName');
moveProperty('isOfficial');
moveProperty('description');
moveProperty('documentation');
Object.keys(parsed.services).forEach(serviceName => {
const service = parsed.services[serviceName];
if (service.containerHttpPort) {
service.caproverExtra = service.caproverExtra || {};
service.caproverExtra.containerHttpPort = service.containerHttpPort;
}
if (service.dockerfileLines) {
service.caproverExtra = service.caproverExtra || {};
service.caproverExtra.dockerfileLines = service.dockerfileLines;
}
if (service.notExposeAsWebApp) {
service.caproverExtra = service.caproverExtra || {};
service.caproverExtra.notExposeAsWebApp = service.notExposeAsWebApp;
}
service.containerHttpPort = undefined;
service.dockerfileLines = undefined;
service.notExposeAsWebApp = undefined;
});
return parsed;
}
function buildDist() {
return Promise.resolve()
.then(function () {
if (!fs.existsSync(pathOfSourceDirectoryApps)) {
return [];
}
return fs.readdir(pathOfSourceDirectoryApps);
})
.then(function (appsFileNames) { // [ app1.json app2.json .... ]
if (appsFileNames.length === 0) {
return;
}
appsFileNames.forEach(appFileName => {
const pathOfAppFileInSource = path.join(pathOfSourceDirectoryApps, appFileName);
//v2
fs.copySync(pathOfAppFileInSource, path.join(pathOfDistV2, `apps`, appFileName));
//v3
fs.copySync(pathOfAppFileInSource, path.join(pathOfDistV3, `apps`, appFileName.split('.')[0]));
//v4
const contentString = fs.readFileSync(pathOfAppFileInSource);
fs.outputJsonSync(path.join(pathOfDistV4, `apps`, appFileName.split('.')[0]), convertV2toV4(contentString));
});
fs.copySync(pathOfSourceDirectoryLogos, path.join(pathOfDistV2, `logos`));
fs.copySync(pathOfSourceDirectoryLogos, path.join(pathOfDistV3, `logos`));
fs.copySync(pathOfSourceDirectoryLogos, path.join(pathOfDistV4, `logos`));
const allAppsList = createAppList(appsFileNames, pathOfSourceDirectoryApps);
const v3List = {
oneClickApps: allAppsList.appDetails
};
fs.outputJsonSync(path.join(pathOfDistV2, 'autoGeneratedList.json'), allAppsList);
fs.outputJsonSync(path.join(pathOfDistV2, 'list'), v3List); // TODO delete oneClickApps:
fs.outputJsonSync(path.join(pathOfDistV3, 'list'), v3List);
fs.outputJsonSync(path.join(pathOfDistV4, 'list'), v3List);
return fs.copySync(path.join(pathOfPublic, 'CNAME'), path.join(pathOfDist, 'CNAME'));
});
}
Promise.resolve()
.then(function () {
return buildDist();
})
.catch(function (err) {
console.error(err);
process.exit(127);
});

View file

@ -0,0 +1,195 @@
/*jshint esversion: 6 */
const path = require('path');
const yaml = require('yaml');
const fs = require('fs-extra');
const pathOfPublic = path.join(__dirname, '..', `public`);
const pathOfDist = path.join(__dirname, '..', `dist`);
const pathOfDistV2 = path.join(pathOfDist, 'v2');
const pathOfDistV3 = path.join(pathOfDist, 'v3');
const pathOfDistV4 = path.join(pathOfDist, 'v4');
const pathOfSourceDirectory = path.join(pathOfPublic, 'v4');
const pathOfSourceDirectoryApps = path.join(pathOfSourceDirectory, 'apps');
const pathOfSourceDirectoryLogos = path.join(pathOfSourceDirectory, 'logos');
/**
* Creates a listing of apps for GET http://oneclickapps.caprover.com/v4
* {
"oneClickApps": [
{
"name": "adminer",
"displayName": "Adminer",
"description": "Adminer (formerly phpMinAdmin) is a full-featured database management tool written in PHP",
"isOfficial": true,
"logoUrl": "adminer.png"
},.....]}
*/
function createAppList(appsFileNames, pathOfApps) {
const apps = appsFileNames.filter(v => `${v}`.endsWith('.yml'));
if (apps.length !== appsFileNames.length) {
throw new Error('All files in v4 must end with .yml extension!');
}
const appDetails = [];
for (var i = 0; i < apps.length; i++) {
const contentString = fs.readFileSync(path.join(pathOfApps, apps[i]), 'utf-8');
const content = yaml.parse(contentString);
const captainVersion = `${content.captainVersion}`;
apps[i] = apps[i].replace('.yml', '');
const caproverOneClickApp = content.caproverOneClickApp;
if (captainVersion === '4') {
if (!caproverOneClickApp.displayName) {
caproverOneClickApp.displayName = apps[i];
caproverOneClickApp.displayName = caproverOneClickApp.displayName.substr(0, 1).toUpperCase() +
caproverOneClickApp.displayName.substring(1, caproverOneClickApp.displayName.length);
}
if (!caproverOneClickApp.description) caproverOneClickApp.description = '';
appDetails[i] = {
name: apps[i],
displayName: caproverOneClickApp.displayName,
description: caproverOneClickApp.description,
isOfficial: `${caproverOneClickApp.isOfficial}`.toLowerCase().trim() === 'true',
logoUrl: apps[i] + '.png'
};
} else {
throw new Error('Unknown captain-version: ' + captainVersion);
}
}
return {
appList: apps,
appDetails: appDetails
};
}
function convertV4toV2(v4String) {
const parsed = JSON.parse(v4String);
if (`${parsed.captainVersion}` !== '4') {
throw new Error('CaptainVersion must be 4 for this conversion');
}
function moveProperty(propertyName) {
parsed[propertyName] = parsed.caproverOneClickApp[propertyName];
}
parsed.dockerCompose = {
services: parsed.services
};
parsed.services = undefined;
parsed.captainVersion = 2;
moveProperty('variables');
moveProperty('instructions');
moveProperty('displayName');
moveProperty('isOfficial');
moveProperty('description');
moveProperty('documentation');
Object.keys(parsed.dockerCompose.services).forEach(serviceName => {
const service = parsed.dockerCompose.services[serviceName];
if (!service.caproverExtra) {
return;
}
if (service.caproverExtra.containerHttpPort) {
service.containerHttpPort = service.caproverExtra.containerHttpPort;
}
if (service.caproverExtra.dockerfileLines) {
service.dockerfileLines = service.caproverExtra.dockerfileLines;
}
if (service.caproverExtra.notExposeAsWebApp) {
service.notExposeAsWebApp = service.caproverExtra.notExposeAsWebApp;
}
service.caproverExtra = undefined;
});
parsed.caproverOneClickApp = undefined;
return parsed;
}
function buildDist() {
return fs.readdir(pathOfSourceDirectoryApps)
.then(function (appsFileNames) { // [ app1.yml app2.yml .... ]
appsFileNames.forEach(appFileName => {
console.log('Building dist for ' + appFileName);
const pathOfAppFileInSource = path.join(pathOfSourceDirectoryApps, appFileName);
const contentParsed = yaml.parse(fs.readFileSync(pathOfAppFileInSource, 'utf-8'));
//v4
fs.outputJsonSync(path.join(pathOfDistV4, `apps`, appFileName.split('.')[0]), contentParsed);
//v3
fs.outputJsonSync(path.join(pathOfDistV3, `apps`, appFileName.split('.')[0]), convertV4toV2(JSON.stringify(contentParsed)));
//v2
fs.outputJsonSync(path.join(pathOfDistV2, `apps`, appFileName.split('.')[0] + '.json'), convertV4toV2(JSON.stringify(contentParsed)));
});
fs.copySync(pathOfSourceDirectoryLogos, path.join(pathOfDistV2, `logos`));
fs.copySync(pathOfSourceDirectoryLogos, path.join(pathOfDistV3, `logos`));
fs.copySync(pathOfSourceDirectoryLogos, path.join(pathOfDistV4, `logos`));
const allAppsList = createAppList(appsFileNames, pathOfSourceDirectoryApps);
const v3List = {
oneClickApps: allAppsList.appDetails
};
// Remove once we are fully on V4
if (fs.existsSync(path.join(pathOfDistV3, 'list'))) {
const v3ListExisting = fs.readFileSync(path.join(pathOfDistV3, 'list'), 'utf-8');
if (v3ListExisting && JSON.parse(v3ListExisting).oneClickApps) {
v3List.oneClickApps = [...v3List.oneClickApps, ...JSON.parse(v3ListExisting).oneClickApps];
const names = {};
const list = [];
v3List.oneClickApps.forEach(a => {
if (!names[a.name]) {
list.push(a);
names[a.name] = true;
}
});
v3List.oneClickApps = list.sort(function (a, b) {
return `${a.name}`.localeCompare(b.name);
});
allAppsList.appList = list.map(l => l.name);
allAppsList.appDetails = v3List.oneClickApps;
}
}
fs.outputJsonSync(path.join(pathOfDistV2, 'autoGeneratedList.json'), allAppsList);
fs.outputJsonSync(path.join(pathOfDistV2, 'list'), v3List);
fs.outputJsonSync(path.join(pathOfDistV3, 'list'), v3List);
fs.outputJsonSync(path.join(pathOfDistV4, 'list'), v3List);
})
.then(function () {
return fs.copySync(path.join(pathOfPublic, 'CNAME'), path.join(pathOfDist, 'CNAME'));
});
}
Promise.resolve()
.then(function () {
return buildDist();
})
.catch(function (err) {
console.error(err);
process.exit(127);
});

111
scripts/migrate_v2_to_v4.js Normal file
View file

@ -0,0 +1,111 @@
/*jshint esversion: 6 */
const path = require('path');
const yaml = require('yaml');
const types = require('yaml/types');
const fs = require('fs-extra');
types.strOptions.fold.lineWidth = 0;
// Next, for V4:
// ============================================================================
// ============================================================================
// *********** THIS IS ONLY TO BE DONE AFTER CAPROVER 1.8 RELEASE *************
// ============================================================================
// ============================================================================
//
// 1- DUPLICATE this script. The new script is to ONLY read from /public/v4/*.yaml
// 2- Test with a new YAML file
// 3- Write script to convert all v2 JSON to V4 yaml and place them in /public/v4/*.yaml
// 4- Update readme!!!!
// 5- Push all 3 steps above at the same time to GITHUB
const pathOfPublic = path.join(__dirname, '..', `public`);
const pathOfDist = path.join(__dirname, '..', `dist`);
const pathOfDistV2 = path.join(pathOfDist, 'v2');
const pathOfDistV3 = path.join(pathOfDist, 'v3');
const pathOfDistV4 = path.join(pathOfDist, 'v4');
const pathOfSourceDirectoryV2 = path.join(pathOfPublic, 'v2');
const pathOfSourceDirectoryAppsV2 = path.join(pathOfSourceDirectoryV2, 'apps');
const pathOfSourceDirectoryLogosV2 = path.join(pathOfSourceDirectoryV2, 'logos');
function convertV2toV4(v2String) {
const parsed = JSON.parse(v2String);
if (`${parsed.captainVersion}` !== '2') {
throw new Error('CaptainVersion must be 2 for this conversion');
}
function moveProperty(propertyName) {
parsed.caproverOneClickApp[propertyName] = parsed[propertyName];
parsed[propertyName] = undefined;
}
parsed.services = parsed.dockerCompose.services;
parsed.dockerCompose = undefined;
parsed.captainVersion = 4;
parsed.caproverOneClickApp = {};
moveProperty('variables');
moveProperty('instructions');
moveProperty('displayName');
moveProperty('isOfficial');
moveProperty('description');
moveProperty('documentation');
Object.keys(parsed.services).forEach(serviceName => {
const service = parsed.services[serviceName];
if (service.containerHttpPort) {
service.caproverExtra = service.caproverExtra || {};
service.caproverExtra.containerHttpPort = service.containerHttpPort;
}
if (service.dockerfileLines) {
service.caproverExtra = service.caproverExtra || {};
service.caproverExtra.dockerfileLines = service.dockerfileLines;
}
if (service.notExposeAsWebApp) {
service.caproverExtra = service.caproverExtra || {};
service.caproverExtra.notExposeAsWebApp = service.notExposeAsWebApp;
}
service.containerHttpPort = undefined;
service.dockerfileLines = undefined;
service.notExposeAsWebApp = undefined;
});
return JSON.parse(JSON.stringify(parsed));
}
function buildDist() {
return fs.readdir(pathOfSourceDirectoryAppsV2)
.then(function (appsFileNames) { // [ app1.json app2.json .... ]
appsFileNames.forEach(appFileName => {
const pathOfAppFileInSource = path.join(pathOfSourceDirectoryAppsV2, appFileName);
//v4
const pathOfSourceDirectoryV4 = path.join(pathOfPublic, 'v4');
const contentString = fs.readFileSync(pathOfAppFileInSource);
fs.outputFileSync(path.join(pathOfSourceDirectoryV4, `apps`, appFileName.split('.')[0] + '.yml'), yaml.stringify(convertV2toV4(contentString)));
fs.moveSync(path.join(pathOfSourceDirectoryV2, `logos`, appFileName.split('.')[0] + '.png'),
path.join(pathOfSourceDirectoryV4, `logos`, appFileName.split('.')[0] + '.png'));
fs.removeSync(path.join(pathOfSourceDirectoryV2, `apps`, appFileName.split('.')[0] + '.json'));
});
});
}
Promise.resolve()
.then(function () {
return buildDist();
})
.catch(function (err) {
console.error(err);
process.exit(127);
});

90
scripts/publish-from-actions.sh Executable file
View file

@ -0,0 +1,90 @@
#!/bin/bash
# FROM: https://raw.githubusercontent.com/maxheld83/ghpages/master/LICENSE
# MIT License
# Copyright (c) 2019 Maximilian Held
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
set -e
BUILD_DIR=dist
SOURCE_DIRECTORY_DEPLOY_GH=~/temp-gh-deploy-src
CLONED_DIRECTORY_DEPLOY_GH=~/temp-gh-deploy-cloned
echo "#############################################"
echo "######### making directories"
echo "######### $SOURCE_DIRECTORY_DEPLOY_GH"
echo "######### $CLONED_DIRECTORY_DEPLOY_GH"
echo "#############################################"
mkdir -p $SOURCE_DIRECTORY_DEPLOY_GH
mkdir -p $CLONED_DIRECTORY_DEPLOY_GH
echo "#############################################"
echo "######### Setting env vars"
echo "#############################################"
REMOTE_REPO="https://${GITHUB_PERSONAL_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
REPONAME="$(echo $GITHUB_REPOSITORY| cut -d'/' -f 2)"
OWNER="$(echo $GITHUB_REPOSITORY| cut -d'/' -f 1)"
GHIO="${OWNER}.github.io"
if [[ "$REPONAME" == "$GHIO" ]]; then
REMOTE_BRANCH="master"
else
REMOTE_BRANCH="gh-pages"
fi
sleep 1s
echo "#############################################"
echo "######### CLONING REMOTE_BRANCH: $REMOTE_BRANCH"
echo "#############################################"
cp -r $BUILD_DIR $SOURCE_DIRECTORY_DEPLOY_GH/
git clone --single-branch --branch=$REMOTE_BRANCH $REMOTE_REPO $CLONED_DIRECTORY_DEPLOY_GH
sleep 1s
echo "#############################################"
echo "######### Removing old files"
echo "#############################################"
cd $CLONED_DIRECTORY_DEPLOY_GH && git rm -rf . && git clean -fdx
sleep 1s
echo "#############################################"
echo "######### Copying files"
echo "#############################################"
cp -r $SOURCE_DIRECTORY_DEPLOY_GH/$BUILD_DIR $CLONED_DIRECTORY_DEPLOY_GH/$BUILD_DIR
mv $CLONED_DIRECTORY_DEPLOY_GH/.git $CLONED_DIRECTORY_DEPLOY_GH/$BUILD_DIR/
cd $CLONED_DIRECTORY_DEPLOY_GH/$BUILD_DIR/
sleep 1s
echo "#############################################"
echo "######### Content pre-commit ###"
echo "#############################################"
ls -la
echo "#############################################"
echo "######### Commit and push ###"
echo "#############################################"
sleep 1s
git config user.name "${GITHUB_ACTOR}"
git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
echo `date` >> forcebuild.date
git add -A
git commit -m 'Deploy to GitHub Pages'
git push $REMOTE_REPO $REMOTE_BRANCH:$REMOTE_BRANCH

149
scripts/validate_apps.js Normal file
View file

@ -0,0 +1,149 @@
/*jshint esversion: 6 */
const path = require('path');
const yaml = require('yaml');
const fs = require('fs-extra');
const PUBLIC = `public`;
const pathOfPublic = path.join(__dirname, '..', PUBLIC);
// validating version 4
function validateV4() {
const version = '4';
const pathOfVersion = path.join(pathOfPublic, 'v' + version);
const pathOfApps = path.join(pathOfVersion, 'apps');
return fs.readdir(pathOfApps)
.then(function (items) {
const apps = items.filter(v => v.includes('.yml'));
if (items.length !== apps.length) {
throw new Error('All files in v4 must end with .yml');
}
for (var i = 0; i < apps.length; i++) {
const contentString = fs.readFileSync(path.join(pathOfApps, apps[i]), 'utf-8');
const content = yaml.parse(contentString);
const captainVersion = (content.captainVersion + '');
const versionString = (version + '');
if (versionString !== captainVersion)
throw new Error(`unmatched versions ${versionString} ${captainVersion} for ${apps[i]}`);
apps[i] = apps[i].replace('.yml', '');
if (!content.caproverOneClickApp) {
throw new Error(`Cannot find caproverOneClickApp for ${apps[i]}`);
}
if (!content.caproverOneClickApp.description) {
throw new Error(`Cannot find description for ${apps[i]}`);
}
if (content.caproverOneClickApp.description.length > 200) {
throw new Error(`Description too long for ${apps[i]} - keep it below 200 chars`);
}
if (!content.caproverOneClickApp.instructions ||
!content.caproverOneClickApp.instructions.start ||
!content.caproverOneClickApp.instructions.end) {
throw new Error(`Cannot find instructions.start or instructions.end for ${apps[i]}`);
}
if (!content.services) {
throw new Error(`Cannot find services for ${apps[i]}`);
}
Object.keys(content.services).forEach(
(serviceName) => { // jshint ignore:line
const s = content.services[serviceName];
if (s.image && s.image.endsWith(':latest')) {
// throw new Error(`"latest" tag is not allowed as it can change and break the setup, see ${apps[i]}`);
}
});
const logoFileName = apps[i] + '.png';
const logoFullPath = path.join(pathOfVersion, 'logos', logoFileName);
if (!fs.existsSync(logoFullPath) ||
!fs.statSync(logoFullPath).isFile()) {
let printablePath = logoFullPath;
printablePath = printablePath.substr(printablePath.indexOf(`/${PUBLIC}`));
throw new Error(`Cannot find logo for ${apps[i]} ${printablePath}`);
}
console.log(`Validated ${apps[i]}`);
}
});
}
// validating version 2
function validateV2() {
const version = '2';
const pathOfVersion = path.join(pathOfPublic, 'v' + version);
const pathOfApps = path.join(pathOfVersion, 'apps');
if (!fs.existsSync(pathOfApps)) {
return;
}
return fs.readdir(pathOfApps)
.then(function (items) {
const apps = items.filter(v => v.includes('.json'));
if (items.length !== apps.length) {
throw new Error('All files in v2 must end with .json');
}
for (var i = 0; i < apps.length; i++) {
const contentString = fs.readFileSync(path.join(pathOfApps, apps[i]));
const content = JSON.parse(contentString);
const captainVersion = (content.captainVersion + '');
const versionString = (version + '');
if (versionString !== captainVersion)
throw new Error(`unmatched versions ${versionString} ${captainVersion} for ${apps[i]}`);
apps[i] = apps[i].replace('.json', '');
if (!content.description) {
throw new Error(`Cannot find description for ${apps[i]}`);
}
if (content.description.length > 200) {
throw new Error(`Description too long for ${apps[i]} - keep it below 200 chars`);
}
const logoFileName = apps[i] + '.png';
const logoFullPath = path.join(pathOfVersion, 'logos', logoFileName);
if (!fs.existsSync(logoFullPath) ||
!fs.statSync(logoFullPath).isFile()) {
let printablePath = logoFullPath;
printablePath = printablePath.substr(printablePath.indexOf(`/${PUBLIC}`));
throw new Error(`Cannot find logo for ${apps[i]} ${printablePath}`);
}
console.log(`Validated ${apps[i]}`);
}
});
}
Promise.resolve()
.then(function () {
return validateV2();
})
.then(function () {
return validateV4();
})
.catch(function (err) {
console.error(err);
process.exit(127);
});