figroll-cli
Version:
CLI to upload to the Figroll Static Site hosting service
795 lines (686 loc) • 23.1 kB
JavaScript
;
let toml = require('toml-js');
let fs = require('fs');
let https = require('https');
let request = require('request');
let read = require('read');
let userHome = require('user-home');
let yazl = require("yazl");
let glob = require("glob");
let numeral = require("numeral");
let color = require("bash-color");
var open = require("open");
let displayList = require('./displayList');
var argv = require('yargs')
.usage('Usage: $0 <command> [options]')
// .option("h", {
// alias: "h",
// description: "Help"
// })
.command('login', 'login to Figroll', function(yargs) {
argv = yargs.demand(1)
.example('$0 login', 'login to Figroll')
.argv;
})
.command('list', 'list your sites', function(yargs) {
argv = yargs.demand(1, 1)
.example("$0 list")
.argv;
})
.command('create', 'Create a new free site', function(yargs) {
argv = yargs.demand(1, 1)
.example("$0 create")
.argv;
})
.command('connect', 'Connect to your site', function(yargs) {
argv = yargs.demand(3)
.usage('Usage: $0 <command> dist-path')
.example('$0 connect site-name.com dist/')
.argv;
})
.command('deploy', 'Deploy to staging', function(yargs) {
argv = yargs.demand(1)
.usage('Usage: $0 <command>')
.example('$0 deploy', 'deploy directory to staging')
.argv;
})
.command('activate', 'Activate site to Production', function(yargs) {
argv = yargs.demand(1)
.usage('Usage: $0 <command>')
.example('$0 activate', 'Activate site to production')
.argv;
})
.demand(1, "")
.argv;
let dir = userHome + "/.figroll";
let path = dir + "/config.toml";
const API_URL = "https://app.figroll.io/api";
function _getConfig(cfgPath) {
return new Promise(function(resolve, reject) {
try {
return resolve(toml.parse(fs.readFileSync(cfgPath).toString()));
} catch (e) {
return reject(e);
}
});
}
function getGlobalConfig() {
return new Promise(function(resolve, reject) {
_getConfig(userHome + "/.figroll/config.toml").then(function(config) {
config.token = process.env.FIGROLL_APITOKEN || config.token
resolve(config)
}, function(e) {
reject(e);
})
});
}
function getLocalConfig() {
return new Promise(function(resolve, reject) {
_getConfig("figroll.toml").then(function(config) {
config.siteId = process.env.FIGROLL_SITEID || config.siteId
config.distPath = process.env.FIGROLL_DISTPATH || config.distPath
return resolve(config)
}, function(e) {
if (process.env.FIGROLL_SITEID && process.env.FIGROLL_DISTPATH) {
var config = {};
config.siteId = process.env.FIGROLL_SITEID;
config.distPath = process.env.FIGROLL_DISTPATH;
return resolve(config)
}
return reject(e);
});
});
}
function getConfig() {
return Promise.all([getGlobalConfig(), getLocalConfig()]);
}
function testGlobalConfig(cfg) {
return new Promise(function(resolve, reject) {
if (!cfg.token) {
return reject("token not found in config");
}
testToken(cfg.token).then(function() {
resolve(cfg);
}, reject);
});
}
function testLocalConfig(cfg) {
return new Promise(function(resolve, reject) {
if (!cfg.siteId) {
return reject("siteId not found in config");
}
if (!cfg.distPath) {
return reject("distPath not found in config");
}
return resolve(cfg);
});
}
function showLoginError(e) {
console.log(color.red("Please login to Figroll!"));
console.log("");
console.log("Figroll Login:");
console.log(' (use "figroll login")');
process.exit(1);
}
function login() {
function getEmail() {
return new Promise(function(resolve, reject) {
read({
"prompt": "Email: "
}, function(err, res, isDefault) {
if (err) {
reject();
return;
}
resolve(res);
});
});
}
function getPassword() {
return new Promise(function(resolve, reject) {
read({
"prompt": "Password: ",
"silent": true,
"replace": "*"
}, function(err, res, isDefault) {
if (err) {
reject();
return;
}
resolve(res);
});
});
}
let credentialPromise = new Promise(function(resolve, reject) {
getEmail().then(function(email) {
getPassword().then(function(password) {
resolve({
"email": email,
"password": password
});
}, reject)
}, reject)
});
let promise = new Promise(function(resolve, reject) {
credentialPromise.then(function(credentials) {
request.post({
url: API_URL + '/auth/login',
json: credentials
}, function(err, res, body) {
if (err || res.statusCode !== 200) {
reject(err);
return;
}
resolve(body);
});
});
// TODO: Request long lived token here
});
return promise;
}
function getApiToken(user) {
return new Promise(function(resolve, reject) {
request.post({
url: API_URL + '/tokens?type=api',
headers: {
Authorization: user.token
},
json: true
}, function(err, res, body) {
if (err || res.statusCode !== 201) {
reject(err);
return;
}
user.token = body.value;
resolve(user);
});
})
}
function saveToken(user) {
return new Promise(function(resolve, reject) {
if (!fs.existsSync(dir)) {
try {
fs.mkdirSync(dir);
} catch (e) {
reject(e);
return;
}
}
let parsed;
try {
parsed = toml.parse(fs.readFileSync(path).toString());
} catch (e) {
parsed = {};
}
parsed.userId = user.user.id;
parsed.token = user.token;
try {
fs.writeFileSync(path, toml.dump(parsed));
} catch (e) {
reject(e);
return;
}
resolve();
})
}
function testToken(token) {
return new Promise(function(resolve, reject) {
request.get({
url: API_URL + '/users/me',
headers: {
"Authorization": token
},
json: true
}, function(err, res, body) {
if (err || res.statusCode !== 200) {
return reject();
}
return resolve(body);
});
});
}
function create(globalConfig) {
let getFreeDomain = new Promise(function(resolve, reject) {
request.get({
url: API_URL + '/domains/next',
json: true
}, function(err, res, body) {
if (err || res.statusCode !== 200) {
return reject();
}
return resolve(body);
});
});
let createFreeSite = new Promise(function(resolve, reject) {
getFreeDomain.then(function(body) {
request.post({
url: API_URL + '/sites',
headers: {
"Authorization": globalConfig.token
},
json: {
fqdn: body.fqdn,
plan: "free"
}
}, function(err, res, body) {
if (err || res.statusCode !== 201) {
return reject();
}
return resolve(body);
});
})
});
return createFreeSite;
}
function connect(cfg) {
return new Promise(function(resolve, reject) {
list(cfg).then(function(body) {
let sites = body.filter(function(site) {
return site.fqdn == argv._[1];
});
if (sites.length === 0) {
return reject("You have not created site: " + argv._[1]);
}
let site = sites[0];
var fd = fs.openSync("figroll.toml", "w");
try {
fs.writeFileSync(fd, toml.dump({
siteId: site.id,
fqdn: site.fqdn,
cname: site.cname,
distPath: argv._[2]
}));
} catch (e) {
return reject(e);
}
return resolve(site);
}).catch(reject);
});
}
function list(globalConfig) {
return new Promise(function(resolve, reject) {
request.get({
url: API_URL + '/sites',
json: true,
headers: {
"Authorization": globalConfig.token
}
}, function(err, res, body) {
if (err || res.statusCode !== 200) {
return reject();
}
return resolve(body);
});
});
}
function upload(cfgs) {
let globalConfig = cfgs[0];
let localConfig = cfgs[1];
var getStream = new Promise(function(resolve, reject) {
let stream;
try {
fs.lstatSync(localConfig.distPath)
} catch (e) {
return reject(e);
}
if (!fs.lstatSync(localConfig.distPath).isFile()) {
let outFn = "/tmp/" + "figroll_zip_" + process.pid + "_output.zip";
let ws = fs.createWriteStream(outFn);
var zipfile = new yazl.ZipFile();
glob(localConfig.distPath + "/**", function(er, files) {
files.forEach(function(filename) {
if (fs.lstatSync(filename).isFile()) {
var zippedFileName = filename.replace(localConfig.distPath, "")
if (zippedFileName.substring(0, 1) === "/") {
zippedFileName = zippedFileName.substring(1, zippedFileName.length);
}
console.log(color.green("Adding file to zip..."),
" " + filename, " => ", zippedFileName);
zipfile.addFile(filename, zippedFileName);
}
});
zipfile.outputStream.pipe(ws).on("close", function() {
// console.log(numeral(fs.statSync(outFn).size).format("0 b"));
resolve([outFn, true]);
});
zipfile.end();
});
} else {
resolve([localConfig.distPath, false]);
}
});
let result = new Promise(function(resolve, reject) {
getStream.then(function(details) {
let zfn = details[0];
let unlinkAfter = details[1];
request.post({
url: API_URL + '/sites/' + localConfig.siteId + "/upload",
formData: {
file: {
value: fs.createReadStream(zfn),
options: {
filename: "public.zip",
contentType: "application/zip"
}
}
},
headers: {
"Authorization": globalConfig.token
},
json: true
}, function(err, res, body) {
if (err) {
return reject(err);
}
return resolve(body);
});
})
.catch(function(e) {
return reject(e);
});
});
getStream.then(function(details) {
let zfn = details[0];
let unlinkAfter = details[1];
result.then(function() {
if (unlinkAfter) {
fs.unlinkSync(zfn);
}
});
})
return result;
}
function activate(cfgs) {
let globalConfig = cfgs[0];
let localConfig = cfgs[1];
let versionsPromise = new Promise(function(resolve, reject) {
request.get({
url: API_URL + '/sites/' + localConfig.siteId + "/versions",
headers: {
"Authorization": globalConfig.token,
"Accept": "application/json"
}
}, function(err, resp, body) {
if (body.length === 0) {
return reject("No versions exist");
}
body = JSON.parse(body)
if (err) {
return reject(err);
}
resolve(body[0]);
});
})
let activateVersion = function(version) {
version.isLive = true;
return new Promise(function(resolve, reject) {
request.put({
url: API_URL + '/sites/' + localConfig.siteId + "/versions/" + version.id,
headers: {
"Authorization": globalConfig.token
},
json: version
}, function optionalCallback(err, res, body) {
if (err) {
return reject(err);
}
return resolve(body);
});
});
};
let enableLetsEncrypt = function(site) {
return new Promise(function(resolve, reject) {
request.post({
url: API_URL + "/sites/" + site.siteId + "/letsencrypt",
headers: {
"Authorization": globalConfig.token
},
}, function optionalCallback(err, res, body) {
if (err || res.statusCode !== 201) {
return reject(err);
}
return resolve(body);
});
});
}
return versionsPromise
.then(activateVersion)
.then(enableLetsEncrypt)
};
function getSite(cfgs) {
let globalConfig = cfgs[0];
let localConfig = cfgs[1];
return new Promise(function(resolve, reject) {
request.get({
url: API_URL + '/sites/' + localConfig.siteId,
json: true,
headers: {
"Authorization": globalConfig.token
}
}, function(err, res, body) {
if (err || res.statusCode !== 200) {
reject();
}
resolve(body);
});
});
}
function configure() {
return new Promise(function(resolve, reject) {
resolve();
});
}
function doLogin() {
login()
.then(getApiToken)
.then(function(user) {
return saveToken(user)
.then(function(response) {
console.log(color.green("You are now logged in!"));
console.log("")
console.log("List yours sites:");
console.log(' (use "figroll list")');
console.log("")
console.log("Connect to a site:");
console.log(' (use "figroll connect <site-domain> dist-path")');
})
.catch(function(error) {
console.log(color.red("Could not save token."));
})
})
.catch(function(error) {
console.log(color.red("Could not login, Please check your username and password."));
})
}
function doCreate() {
getGlobalConfig(path)
.then(testGlobalConfig)
.catch(showLoginError)
.then(create)
.then(function(res) {
console.log("");
console.log("You created site:");
console.log(color.green(" site: " + res.fqdn));
console.log("")
console.log("Connect to a site:");
console.log(' (use "figroll connect ' + res.fqdn + ' dist-path")');
})
.catch(function(e) {
console.log("Site not created")
switch (e) {
case 400:
console.log("Incorrect Domain name specified");
process.exit(1);
break;
case 409:
console.log("Site already exists");
process.exit(1);
break;
default:
console.log(e);
process.exit(1);
break;
}
});
}
function doConnect() {
getGlobalConfig(path)
.then(testGlobalConfig)
.catch(showLoginError)
.then(connect)
.then(function(site) {
console.log("");
console.log("Connected to:");
console.log(color.green(" site: " + site.fqdn));
console.log("");
console.log("Deploy your site:");
console.log(' (use "figroll deploy")');
console.log("");
console.log("Read config:");
console.log(' (use "cat figroll.toml")');
})
.catch(function(e) {
console.log("");
console.log(color.red(e));
console.log("");
console.log("List yours sites:");
console.log(' (use "figroll list")');
});
}
function doList() {
getGlobalConfig(path)
.catch(showLoginError)
.then(testGlobalConfig)
.then(list)
.then(displayList)
.catch(function(e) {
console.log("Config could not be read or your token has expired.");
return;
})
}
function doDeploy() {
getConfig(path)
.then(function(cfgs) {
let globalConfig = cfgs[0];
let localConfig = cfgs[1];
return new Promise(function(resolve, reject) {
testGlobalConfig(globalConfig)
.then(testLocalConfig)
.then(resolve([globalConfig, localConfig]))
.catch(reject);
});
})
.catch(function(e) {
console.log(e);
console.log(color.red("Config could not be read"));
console.log("");
console.log("Try logging in:");
console.log(' (use "figroll login")');
process.exit(1);
})
.then(function(cfgs) {
return new Promise(function(resolve, reject) {
configure(cfgs).then(function() {
console.log(color.green("Uploading..."))
upload(cfgs)
.then(function(res) {
console.log("");
console.log("Your site had been deployed to staging:");
console.log("");
console.log(color.green(" Staging URL: " + res.stagingUrl));
open(res.stagingUrl);
console.log("");
console.log("Now activate your site to make it live live:");
console.log(' (use "figroll activate")');
})
.catch(function(e) {
console.log(color.red('Can\'t find "' + cfgs[1].distPath + '" in current folder'))
console.log("");
console.log("You can fix this:");
console.log(' (figroll.toml)');
process.exit(1);
});
}, function() {
process.exit(1);
});
});
})
}
function doActivate() {
getConfig(path)
.then(function(cfgs) {
let globalConfig = cfgs[0];
let localConfig = cfgs[1];
return new Promise(function(resolve, reject) {
testGlobalConfig(globalConfig)
.then(testLocalConfig)
.then(resolve([globalConfig, localConfig]))
.catch(reject);
});
})
.catch(function(e) {
console.log(e);
console.log(color.red("Config could not be read"));
console.log("");
console.log("Try logging in:");
console.log(' (use "figroll login")');
process.exit(1);
})
.then(function(cfgs) {
return new Promise(function(resolve, reject) {
configure(cfgs)
.then(function() {
activate(cfgs)
.then(function(res) {
getSite(cfgs)
.then(function(site) {
let productionUrl = "http://" + site.fqdn;
console.log("");
console.log("Your site had been deployed to production:");
console.log("");
console.log(color.green(" Production URL: " + productionUrl));
open(productionUrl);
}).catch(function(e) {
console.log(e)
})
})
.catch(function(e) {
console.log("Please make sure you are logged in and connected to a site.");
console.log("")
console.log("See commands:");
console.log(' (use "figroll")');
console.log("")
console.log("Connect to a site:");
console.log(' (use "figroll connect <site-domain> dist-path")');
console.log("")
console.log("List yours sites:");
console.log(' (use "figroll list")');
});
});
});
})
}
switch (argv._[0]) {
case "login":
doLogin();
break;
case "create":
doCreate();
break;
case "connect":
doConnect();
break;
case "list":
doList();
break;
case "deploy":
doDeploy();
break;
case "activate":
doActivate();
break;
default:
console.error("`" + argv._[0] + "` is not a valid command");
break;
}