├── .gitignore ├── README.md ├── bin └── fastack ├── docs └── img │ ├── auto-reload.gif │ └── install.gif ├── lib ├── .fastack │ ├── config.base.js │ ├── fastack-packages │ │ ├── dev │ │ │ └── live-reload.js │ │ └── main.js │ └── package.base.json ├── autocomplete.js ├── builder.js ├── commands │ ├── add_host.js │ ├── cat.js │ ├── cd.js │ ├── cp.js │ ├── deploy.js │ ├── emacs.js │ ├── export.js │ ├── init.js │ ├── install.js │ ├── list.js │ ├── login.js │ ├── logout.js │ ├── ls.js │ ├── mkdir.js │ ├── mv.js │ ├── new_app.js │ ├── open.js │ ├── pwd.js │ ├── register.js │ ├── rm.js │ ├── touch.js │ └── uninstall.js ├── directory.js ├── fastack.js ├── helpers.js ├── htmlBuild.js ├── keys.js └── localServer.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directory 2 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 3 | node_modules 4 | .idea 5 | 6 | *.log 7 | *.DS_Store 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fastack ![npm version](https://img.shields.io/npm/v/fastack.svg) 2 | Fastack is a zero-configuration development tool that makes developing client-side-only apps easy. Run `$ fastack` in your app directory to automatically enable the following features. 3 | 4 | ## Features 5 | ### Easy module loading 6 | - Every file in your app directory is implicitly loaded into your Fastack application. Just drop `main.coffee`, `styles.sass` or `my-code.js` somewhere into your app directory and Fastack will load it. 7 | - Need to install a dependency? `fastack:my-app $ install jquery` will install modules from JSPM 8 | 9 | ![Package management](docs/img/install.gif) 10 | 11 | 12 | ### Pre-compiler support 13 | - SASS 14 | - CoffeeScript 15 | 16 | ### Auto reload on file changes 17 | Hot module replacement implementation coming soon. See below. 18 | 19 | ![Package management](docs/img/auto-reload.gif) 20 | 21 | ## Quickstart 22 | ``` 23 | $ npm install -g fastack 24 | ``` 25 | 26 | Fastack is an immersive command line application. Running `$ fastack` will drop you into a new prompt with a number of sub-commands available. Run `help` in the Fastack prompt for more information. 27 | 28 | ``` 29 | $ mkdir my-app 30 | $ cd my-app 31 | $ fastack 32 | fastack:my-app $ init 33 | ``` 34 | 35 | ## Coming Soon 36 | - Hot code reload 37 | - Application export 38 | - Fastack deploy 39 | - LESS support 40 | - TypeScript support -------------------------------------------------------------------------------- /bin/fastack: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ":" //# http://sambal.org/?p=1014 ; exec /usr/bin/env node --harmony "$0" "$@" 3 | 4 | var fastack = require('../lib/fastack'); 5 | 6 | process.title = 'fastack'; 7 | -------------------------------------------------------------------------------- /docs/img/auto-reload.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fastack/cli/faa932670cbd5a5c6ddaf4510c5d11aad7c76c52/docs/img/auto-reload.gif -------------------------------------------------------------------------------- /docs/img/install.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fastack/cli/faa932670cbd5a5c6ddaf4510c5d11aad7c76c52/docs/img/install.gif -------------------------------------------------------------------------------- /lib/.fastack/config.base.js: -------------------------------------------------------------------------------- 1 | System.config({ 2 | defaultJSExtensions: false, 3 | buildCSS: true, 4 | transpiler: "babel", 5 | babelOptions: { 6 | "optional": [ 7 | "runtime", 8 | "optimisation.modules.system" 9 | ] 10 | }, 11 | paths: { 12 | "github:*": ".fastack/jspm_packages/github/*", 13 | "npm:*": ".fastack/jspm_packages/npm/*" 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /lib/.fastack/fastack-packages/dev/live-reload.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | // Inject socket.io client script (served by local server) 3 | var liveReload = document.createElement('script'); 4 | liveReload.setAttribute('src','/socket.io/socket.io.js'); 5 | document.head.appendChild(liveReload); 6 | 7 | liveReload.onload = function() { 8 | var socket = io(window.location.origin); 9 | 10 | socket.on('reload', function() { 11 | location.reload(); 12 | }); 13 | 14 | socket.on('reload-css', function() { 15 | // https://github.com/dbashford/mimosa-live-reload/blob/master/lib/assets/reload-client.js 16 | setTimeout(function(){ 17 | var links = document.getElementsByTagName("link"); 18 | for (var i = 0; i < links.length; i++) { 19 | var tag = links[i]; 20 | if (tag.rel.toLowerCase().indexOf("stylesheet") >= 0 && tag.href) { 21 | var newHref = tag.href.replace(/(&|%5C?)\d+/, ""); 22 | tag.href = newHref + (newHref.indexOf("?") >= 0 ? "&" : "?") + (new Date().valueOf()); 23 | } 24 | } 25 | }, 150) 26 | }) 27 | } 28 | 29 | })(); -------------------------------------------------------------------------------- /lib/.fastack/fastack-packages/main.js: -------------------------------------------------------------------------------- 1 | require("../../main.css!"); 2 | require("../../test.coffee!"); 3 | -------------------------------------------------------------------------------- /lib/.fastack/package.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "jspm": { 3 | "directories": { 4 | "baseURL": "..", 5 | "packages": "jspm_packages" 6 | }, 7 | "configFile": "config.js", 8 | "dependencies": { 9 | }, 10 | "devDependencies": { 11 | "babel": "npm:babel-core@^5.8.24", 12 | "babel-runtime": "npm:babel-runtime@^5.8.24", 13 | "coffee": "github:forresto/system-coffee@^0.1.2", 14 | "core-js": "npm:core-js@^1.1.4", 15 | "clean-css": "npm:clean-css@^3.4.8", 16 | "plugin-css": "github:systemjs/plugin-css@^0.1.20", 17 | "plugin-json": "github:systemjs/plugin-json@^0.1.0", 18 | "plugin-text": "github:systemjs/plugin-text@^0.0.4", 19 | "plugin-sass": "github:mobilexag/plugin-sass@^0.1.0" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/autocomplete.js: -------------------------------------------------------------------------------- 1 | const Path = require('path') 2 | , fs = require('fs') 3 | , walkSync = require('walk-sync') 4 | , _ = require('lodash') 5 | ; 6 | 7 | module.exports = function(FASTACK, COMMAND) { 8 | return function (text, iteration, cb) { 9 | var opts = {directories: true, globs: ["**"]}; 10 | 11 | switch (COMMAND) { 12 | case 'cd': 13 | //opts.globs = ["(**/*.*)"]; 14 | opts.dot = false; 15 | break; 16 | case 'cat': 17 | opts.directories = false; 18 | break; 19 | } 20 | 21 | var list = walkSync(FASTACK.dir, opts); 22 | 23 | if (iteration > 1) { 24 | cb(void 0, list); 25 | } else { 26 | var match = this.match(text, list); 27 | if (match) { 28 | cb(void 0, COMMAND + " " + match); 29 | } else { 30 | cb(void 0, void 0); 31 | } 32 | } 33 | } 34 | }; -------------------------------------------------------------------------------- /lib/builder.js: -------------------------------------------------------------------------------- 1 | const Path = require('path') 2 | , fs = require('fs-extra') 3 | , crypto = require('crypto') 4 | , Promise = require('bluebird') 5 | , jspm = require('jspm') 6 | , _ = require('lodash') 7 | ; 8 | 9 | module.exports = function(FASTACK) { 10 | 11 | FASTACK.getLocalDeps = function() { 12 | var installedDeps = fs.readJsonSync(Path.resolve(FASTACK.localDir, 'package.json'))['jspm']['dependencies']; 13 | installedDeps = _.keys(installedDeps); 14 | 15 | return new Promise(function(resolve, reject) { 16 | var allowable = [ 17 | 'css', 18 | 'coffee', 19 | 'sass', 20 | 'scss', 21 | 'less', 22 | 'js' 23 | ]; 24 | 25 | function getSuffix(ext) { 26 | switch(ext) { 27 | case ".css": return "!plugin-css"; 28 | case ".sass": return "!plugin-sass"; 29 | case ".scss": return "!plugin-sass"; 30 | case ".js": return ""; 31 | default: return "!"; 32 | } 33 | } 34 | 35 | //var out = installedDeps; 36 | var out = []; 37 | 38 | FASTACK.traverse(FASTACK.cwd, allowable, function(file) { 39 | out.push(file.path + getSuffix(Path.extname(file.path))); 40 | }).then(function() { 41 | resolve(out); 42 | }); 43 | }); 44 | }; 45 | 46 | 47 | FASTACK.build = function(production) { 48 | production = !!production; 49 | FASTACK.hash = crypto.createHash('md5').update((new Date()).toString()).digest('hex'); 50 | 51 | jspm.setPackagePath(FASTACK.localDir); 52 | var configFile = Path.resolve(FASTACK.localDir, 'config.js'); 53 | var builder = new jspm.Builder(); 54 | builder.loadConfigSync(configFile, true, true); 55 | builder.config({ 56 | buildCSS: true, 57 | separateCSS: true 58 | }); 59 | 60 | if (!production) { 61 | builder.config({ 62 | paths: { 63 | 'fastack:*': Path.resolve(FASTACK.localDir, 'fastack-packages/*') 64 | } 65 | }) 66 | } 67 | 68 | return new Promise(function(resolve, reject) { 69 | if (!production) resolve(); 70 | else { 71 | var hash = FASTACK.hash; 72 | FASTACK.getLocalDeps() 73 | .then((str) => { 74 | str = str.map((path) => Path.relative(FASTACK.cwd, path)); 75 | str = str.join(' + '); 76 | fs.ensureDirSync(Path.join(FASTACK.localDir, 'builds/'+hash+'/')); 77 | builder.buildStatic(str, Path.join(FASTACK.localDir, 'builds/'+hash+'/'+hash+'.js'), { 78 | minify: false, 79 | sourceMaps: true, 80 | //mangle: production, 81 | //lowResSourceMaps: !production 82 | format: 'global' 83 | }) 84 | .then(function () { 85 | //FASTACK.logger.info(hash); 86 | resolve(FASTACK.hash); 87 | }).catch(function (e) { 88 | console.log(e); 89 | reject(e); 90 | }) 91 | }) 92 | } 93 | }); 94 | }; 95 | 96 | if (FASTACK.app) return FASTACK.build(); 97 | else 98 | return new Promise(function(resolve, reject) { 99 | resolve(); 100 | }); 101 | }; -------------------------------------------------------------------------------- /lib/commands/add_host.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | , Syncano = require('syncano') 3 | , validator = require('validator') 4 | , request = require('request') 5 | , clui = require('clui') 6 | , Spinner = clui.Spinner 7 | , jspm = require('jspm') 8 | , chalk = require('chalk') 9 | , Promise = require('bluebird') 10 | ; 11 | 12 | module.exports = function(FASTACK) { 13 | 14 | return function(args, callback){ 15 | 16 | var hostName = args["host-name"]; 17 | 18 | function addHost(url, info) { 19 | return new Promise(function(resolve, reject) { 20 | request.post(url, {json:{info}} , function (err, res) { 21 | if (err) { 22 | reject(err); 23 | return; 24 | } 25 | resolve( res.body.result.stdout ); 26 | }); 27 | }); 28 | } 29 | 30 | var url = "https://api.syncano.io/v1/instances/fastack/webhooks/p/0acce994a35c9512e9c0a4960853d00483f95209/create_host/"; 31 | var spinner = new Spinner('Adding host'); 32 | var info = { hostName: hostName, apiKey: FASTACK.apiKey, userKey: FASTACK.userKey }; 33 | spinner.start(); 34 | addHost(url, info).then( (result) => { 35 | spinner.stop(); 36 | FASTACK.logger.log(result); 37 | callback(); 38 | }).catch((e) => { 39 | spinner.stop(); 40 | FASTACK.logger.error(e); 41 | callback(); 42 | }); 43 | 44 | } 45 | }; -------------------------------------------------------------------------------- /lib/commands/cat.js: -------------------------------------------------------------------------------- 1 | const Path = require('path') 2 | , fs = require('fs') 3 | ; 4 | 5 | module.exports = function(FASTACK) { 6 | return function(args, callback) { 7 | 8 | try { 9 | var file = fs.readFileSync(Path.resolve(FASTACK.dir, args["file"]), 'utf8'); 10 | console.log(file); 11 | } catch(e) { 12 | if (e.code == 'ENOENT') FASTACK.logger.error('File not found!'); 13 | else if (e.code == 'EISDIR') FASTACK.logger.error('That\'s a directory!'); 14 | else { 15 | FASTACK.logger.error(e); 16 | } 17 | } 18 | callback(); 19 | } 20 | }; -------------------------------------------------------------------------------- /lib/commands/cd.js: -------------------------------------------------------------------------------- 1 | const Path = require('path') 2 | , fs = require('fs') 3 | ; 4 | 5 | module.exports = function(FASTACK) { 6 | return function(args, callback) { 7 | var newDir = Path.resolve(FASTACK.dir, args["dir-name"]); 8 | 9 | try { 10 | var newDirStat = fs.statSync(newDir); 11 | if (!newDirStat.isDirectory()) { 12 | FASTACK.logger.error(FASTACK.logger.error('That\'s not a directory!')); 13 | callback(); 14 | } 15 | else if (Path.relative(FASTACK.cwd, newDir).indexOf('..') != -1) { 16 | FASTACK.logger.error('Cannot leave your application directory!'); 17 | callback(); 18 | } 19 | else { 20 | FASTACK.dir = newDir; 21 | FASTACK.updateDelimiter(); 22 | callback(); 23 | } 24 | } catch(e) { 25 | if (e.code == 'ENOENT') FASTACK.logger.error(FASTACK.logger.error('Directory does not exist!')); 26 | 27 | callback(); 28 | } 29 | } 30 | }; -------------------------------------------------------------------------------- /lib/commands/cp.js: -------------------------------------------------------------------------------- 1 | const child_process = require('child_process') 2 | , fs = require('fs-extra') 3 | , Path = require('path') 4 | ; 5 | 6 | // could add options if we wanted (e.g. a '-l') 7 | module.exports = function(FASTACK) { 8 | return function(args, callback) { 9 | 10 | // if a file already exists, this command will just overwrite it right now 11 | 12 | var src = args["src"]; 13 | var dst = args["dst"]; 14 | 15 | var dataToCopy = fs.readFile(src, function(err, data){ 16 | if (err){ 17 | FASTACK.logger.log(err); 18 | } 19 | }); 20 | 21 | var fileName = Path.basename(src); 22 | if( fs.existsSync(dst) && fs.lstatSync(dst).isDirectory() ){ 23 | var fileName = Path.basename(src); 24 | dst = dst + "/" + fileName; 25 | } 26 | var fd = fs.openSync(dst, 'w'); 27 | 28 | fs.write(fd, dataToCopy, function(err) { 29 | if(err) { 30 | FASTACK.logger.log(err); 31 | } 32 | FASTACK.logger.log("File copied."); 33 | }); 34 | 35 | callback(); 36 | } 37 | }; -------------------------------------------------------------------------------- /lib/commands/deploy.js: -------------------------------------------------------------------------------- 1 | const Syncano = require('syncano') 2 | , Path = require('path') 3 | , fs = require('fs-extra') 4 | , Promise = require('bluebird') 5 | , walkSync = require('walk-sync') 6 | , clui = require('clui') 7 | , Spinner = clui.Spinner 8 | , request = require('request') 9 | ; 10 | 11 | module.exports = function(FASTACK) { 12 | 13 | return function(args, callback){ 14 | 15 | var fastack = new Syncano({instance: 'fastack', apiKey: FASTACK.apiKey, userKey: FASTACK.userKey}); 16 | 17 | function getAppId(name) { 18 | var appId; 19 | return new Promise(function(resolve, reject) { 20 | fastack.class('apps').dataobject().list({"query": {"name": {"_eq": name}}}) 21 | .then((apps) => { 22 | if (apps.objects && apps.objects[0]) { 23 | appId = apps.objects[0].id; 24 | resolve(appId); 25 | } 26 | else { 27 | throw new Error('App doesn\'t exist!'); 28 | } 29 | }).catch((e) => { 30 | reject(e); 31 | }); 32 | }); 33 | } 34 | 35 | function deployFile(fileObject) { 36 | return new Promise(function(resolve, reject) { 37 | fastack.class('files').dataobject().add(fileObject, function(err, res){ 38 | if (err) reject(err); 39 | resolve(res); 40 | }); 41 | }); 42 | 43 | } 44 | 45 | function fetchFilesToClean(appId) { 46 | return new Promise((resolve, reject) => { 47 | fastack.class('files').dataobject().list({ 48 | "query": {"app": {"_eq": appId}} 49 | }).then((files) => { 50 | if (files.objects) resolve(files.objects.map((f) => f.id)) 51 | else resolve([]); 52 | }) 53 | }); 54 | } 55 | 56 | function cleanFiles(fileIdList) { 57 | var promises = []; 58 | for (var i in fileIdList) { 59 | var id = fileIdList[i]; 60 | promises.push(fastack.class('files').dataobject(id).delete()); 61 | } 62 | return Promise.all(promises); 63 | } 64 | 65 | 66 | var spinner = new Spinner('Starting deploy...'); 67 | spinner.start(); 68 | fs.emptyDirSync(Path.resolve(FASTACK.localDir, 'deploy/')); 69 | 70 | var appId, dir, filesToClean; 71 | FASTACK.export('.fastack/deploy') 72 | .then((d) => {dir = d; return getAppId(args["app-name"])}) 73 | .then((id) => {appId = id; return fetchFilesToClean(appId)}) 74 | .then((ids) => {filesToClean = ids;}) 75 | .then(() => { 76 | var promises = []; 77 | var files = walkSync(dir, {directories: false}); 78 | for (var f in files) { 79 | var file = Path.resolve(dir, files[f]); 80 | var fileObject = { 81 | path: Path.relative(dir, file), 82 | app: appId, 83 | contents: { 84 | filename: Path.basename(file), 85 | data: fs.readFileSync(file, 'utf8') 86 | }, 87 | owner_permissions: "full" 88 | }; 89 | spinner.message('Uploading: ' + Path.basename(file)); 90 | promises.push(deployFile(fileObject)); 91 | } 92 | return Promise.all(promises); 93 | }) 94 | .then((deployed) => { 95 | spinner.stop(); 96 | FASTACK.logger.confirm(deployed.length + ' files deployed.'); 97 | }) 98 | .catch((e) => { 99 | spinner.stop(); 100 | FASTACK.logger.error(e); 101 | callback(); 102 | }) 103 | .then(() => cleanFiles(filesToClean)) 104 | .finally(() => { 105 | callback(); 106 | }); 107 | } 108 | }; -------------------------------------------------------------------------------- /lib/commands/emacs.js: -------------------------------------------------------------------------------- 1 | const child_process = require('child_process') 2 | ; 3 | 4 | // could add options if we wanted (e.g. a '-l') 5 | module.exports = function(FASTACK) { 6 | return function(args, callback) { 7 | // child_process.exec(command[, options], callback) 8 | var command = "emacs " + args["file-name"]; 9 | child_process.exec(command, function(error, stdout, stderr){ 10 | if(error) FASTACK.logger.error(stderr); 11 | FASTACK.logger.log(stdout); 12 | }); 13 | callback(); 14 | } 15 | }; -------------------------------------------------------------------------------- /lib/commands/export.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra') 2 | , clui = require('clui') 3 | , Spinner = clui.Spinner 4 | , Path = require('path') 5 | , Promise = require('bluebird') 6 | ; 7 | 8 | module.exports = function(FASTACK) { 9 | 10 | return function(args, callback) { 11 | var spinner = new Spinner('Exporting...'); 12 | spinner.start(); 13 | 14 | FASTACK.export(args['dir']).then((files) => { 15 | spinner.stop(); 16 | FASTACK.logger.confirm(files.length + ' files exported.'); 17 | callback(); 18 | }) 19 | 20 | } 21 | }; -------------------------------------------------------------------------------- /lib/commands/init.js: -------------------------------------------------------------------------------- 1 | const clui = require('clui') 2 | , Spinner = clui.Spinner 3 | , os = require('os') 4 | , Path = require('path') 5 | , fs = require('fs-extra') 6 | , Promise = require('bluebird') 7 | , jspm = require('jspm') 8 | , chalk = require('chalk') 9 | ; 10 | 11 | 12 | module.exports = function(FASTACK) { 13 | return function(args, callback) { 14 | 15 | var spinner = new Spinner('Initializing Fastack app in ' + chalk.cyan(FASTACK.cwd)); 16 | spinner.start(); 17 | fs.ensureDirSync(FASTACK.localDir); 18 | fs.ensureDirSync(Path.resolve(FASTACK.localDir, 'jspm_packages')); 19 | fs.ensureDirSync(Path.resolve(FASTACK.localDir, 'builds')); 20 | 21 | var toCopy = Path.resolve(__dirname, '../.fastack/'); 22 | 23 | try { 24 | fs.copySync(Path.resolve(toCopy, 'package.base.json'), Path.resolve(FASTACK.localDir, 'package.json')); 25 | fs.copySync(Path.resolve(toCopy, 'config.base.js'), Path.resolve(FASTACK.localDir, 'config.js')); 26 | fs.copySync(Path.resolve(toCopy, 'fastack-packages/dev'), Path.resolve(FASTACK.localDir, 'fastack-packages/dev')); 27 | fs.copySync(Path.resolve(require.resolve('systemjs'), '../dist'), Path.resolve(FASTACK.localDir, 'system-js')); 28 | } catch(e) { 29 | FASTACK.logger.error(e) 30 | } 31 | 32 | try { 33 | 34 | jspm.setPackagePath(FASTACK.localDir); 35 | jspm.install(true, { lock: true }) 36 | .then(function() { 37 | spinner.stop(); 38 | FASTACK.logger.info('Fastack app initialized.'); 39 | FASTACK.app = true; 40 | 41 | FASTACK.localServer.stop(); 42 | FASTACK.localServer.start(); 43 | 44 | callback(); 45 | }).catch(function(e) { 46 | spinner.stop(); 47 | FASTACK.logger.error('Problem initializing: '+ e); 48 | callback(); 49 | }); 50 | 51 | } catch(e) { 52 | FASTACK.logger.error(e); 53 | callback(); 54 | } 55 | }; 56 | }; -------------------------------------------------------------------------------- /lib/commands/install.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | , clui = require('clui') 3 | , Spinner = clui.Spinner 4 | , jspm = require('jspm') 5 | , chalk = require('chalk') 6 | ; 7 | 8 | module.exports = function(FASTACK) { 9 | return function(args, callback) { 10 | var spinner = new Spinner('Installing ' + chalk.cyan(args.module || 'dependencies')); 11 | spinner.start(); 12 | 13 | var updateSpinner = function(type, msg) { 14 | spinner.message(msg); 15 | }; 16 | 17 | jspm.on('log', updateSpinner); 18 | 19 | var install = () => { 20 | if (args.target) return jspm.install(args.module, String(args.target)); 21 | else return jspm.install(args.module || true); 22 | }; 23 | 24 | install().then(function() { 25 | spinner.stop(); 26 | FASTACK.vorpal.exec('list'); 27 | FASTACK.build().then(function() { 28 | jspm.removeListener('log', updateSpinner); 29 | callback(); 30 | }); 31 | }).catch((e) => { 32 | spinner.stop(); 33 | FASTACK.logger.error(e); 34 | callback(); 35 | }); 36 | } 37 | }; -------------------------------------------------------------------------------- /lib/commands/list.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | , Path = require('path') 3 | , clui = require('clui') 4 | , Spinner = clui.Spinner 5 | , jspm = require('jspm') 6 | , chalk = require('chalk') 7 | ; 8 | 9 | module.exports = function(FASTACK) { 10 | FASTACK.getInstalled = () => { 11 | var package = JSON.parse(fs.readFileSync(Path.join(FASTACK.localDir, 'package.json'), 'utf8')); 12 | var deps = package.jspm.dependencies; 13 | return deps; 14 | }; 15 | return function(args, callback) { 16 | try { 17 | var deps = FASTACK.getInstalled(); 18 | for (var module in deps) { 19 | FASTACK.logger.info(chalk.cyan(module) + ': ' + deps[module]); 20 | } 21 | callback(); 22 | } catch (e) { 23 | FASTACK.logger.error(e); 24 | } 25 | } 26 | }; -------------------------------------------------------------------------------- /lib/commands/login.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | , Syncano = require('syncano') 3 | , clui = require('clui') 4 | , Spinner = clui.Spinner 5 | , chalk = require('chalk') 6 | ; 7 | 8 | module.exports = function(FASTACK) { 9 | return function(args, callback) { 10 | var spinner = new Spinner('Logging in...'); 11 | this.prompt([ 12 | { 13 | type: 'input', 14 | name: 'username', 15 | message: 'Fastack username: ' 16 | }, 17 | { 18 | type: 'password', 19 | name: 'password', 20 | message: 'Fastack password: ', 21 | hidden: true 22 | } 23 | ], function(answers) { 24 | spinner.start(); 25 | var fastack = new Syncano({instance: 'fastack', apiKey: FASTACK.apiKey}); 26 | fastack.user().login({username: answers.username, password: answers.password}) 27 | .then((data) => { 28 | var config = { 29 | userKey: data.user_key, 30 | apiKey: FASTACK.apiKey 31 | }; 32 | 33 | if (data && data.user_key) fs.writeFileSync(FASTACK.globalConfig, JSON.stringify(config), 'utf8'); 34 | FASTACK.userKey = data.user_key; 35 | 36 | spinner.stop(); 37 | FASTACK.logger.info('Successful authentication. Key: ' + chalk.cyan(data.user_key)); 38 | callback(); 39 | }) 40 | .catch((e) => { 41 | spinner.stop(); 42 | var msg = JSON.parse(JSON.parse(e.message)); 43 | FASTACK.logger.error(msg.detail); 44 | callback(); 45 | }) 46 | }); 47 | } 48 | }; -------------------------------------------------------------------------------- /lib/commands/logout.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | , Syncano = require('syncano') 3 | ; 4 | 5 | module.exports = function(FASTACK) { 6 | return function(args, callback) { 7 | fs.unlinkSync(FASTACK.globalConfig); 8 | FASTACK.userKey = null; 9 | FASTACK.syncano = new Syncano({ 10 | instance: 'fastack', 11 | apiKey: FASTACK.apiKey 12 | }); 13 | FASTACK.logger.info('Successful logout.'); 14 | callback(); 15 | } 16 | }; -------------------------------------------------------------------------------- /lib/commands/ls.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra') 2 | , Path = require('path') 3 | , chalk = require('chalk') 4 | ; 5 | 6 | // could add options if we wanted (e.g. a '-l') 7 | module.exports = function(FASTACK) { 8 | 9 | return function(args, callback) { 10 | 11 | var files = fs.readdirSync(FASTACK.dir); 12 | for (var f in files) { 13 | var file = files[f]; 14 | var stat = fs.lstatSync(Path.resolve(FASTACK.dir, file)); 15 | 16 | if (stat.isDirectory()) FASTACK.logger.info(chalk.cyan(file)); 17 | else FASTACK.logger.info(file); 18 | 19 | } 20 | 21 | callback(); 22 | 23 | }; 24 | 25 | }; -------------------------------------------------------------------------------- /lib/commands/mkdir.js: -------------------------------------------------------------------------------- 1 | const child_process = require('child_process') 2 | , fs = require('fs-extra') 3 | ; 4 | 5 | module.exports = function(FASTACK) { 6 | return function(args, callback) { 7 | 8 | var dir = args["dir-name"]; 9 | fs.mkdirs(dir, function (err) { 10 | if (err) return FASTACK.logger.error(err) 11 | FASTACK.logger.log("directory created.") 12 | }); 13 | 14 | callback(); 15 | } 16 | }; -------------------------------------------------------------------------------- /lib/commands/mv.js: -------------------------------------------------------------------------------- 1 | const child_process = require('child_process') 2 | , fs = require('fs-extra') 3 | , Path = require('path') 4 | ; 5 | 6 | // could add options if we wanted (e.g. a '-l') 7 | module.exports = function(FASTACK) { 8 | return function(args, callback) { 9 | 10 | var src = args["src"]; 11 | 12 | // read contents of file to copy 13 | var dataToCopy = fs.readFile(src, function(err, data){ 14 | if (err){ 15 | FASTACK.logger.log(err); 16 | } 17 | }); 18 | 19 | // remove old file 20 | fs.remove(src, function (err) { 21 | if (err) return console.error(err) 22 | }); 23 | 24 | var dst = args["dst"]; 25 | 26 | if( fs.existsSync(dst) && fs.lstatSync(dst).isDirectory() ){ 27 | var fileName = Path.basename(src); 28 | dst = dst + "/" + fileName; 29 | } 30 | // create file 31 | var fd = fs.openSync( dst, 'w'); 32 | // copy data into new file 33 | fs.write(fd, dataToCopy, function(err) { 34 | if(err) { 35 | FASTACK.logger.log(err); 36 | } 37 | FASTACK.logger.log("File moved."); 38 | }); 39 | 40 | callback(); 41 | 42 | } 43 | }; -------------------------------------------------------------------------------- /lib/commands/new_app.js: -------------------------------------------------------------------------------- 1 | const Syncano = require('syncano') 2 | , fs = require('fs') 3 | , request = require('request') 4 | ; 5 | 6 | module.exports = function(FASTACK) { 7 | 8 | return function(args, callback) { 9 | 10 | var fastack = new Syncano({instance: 'fastack', apiKey: FASTACK.apiKey, userKey: FASTACK.userKey}); 11 | 12 | var createAppEndpoint = 'http://api.fastack.io/create-app'; 13 | var userId; 14 | var appName = args['app_name']; 15 | 16 | FASTACK.getUserId() 17 | .then((id) => new Promise((resolve, reject) => { 18 | userId = id; 19 | request.post(createAppEndpoint, (error, response) => { 20 | if (error) reject(error); 21 | resolve(response.headers['location']); 22 | }) 23 | })) 24 | .then((redirected) => new Promise((resolve, reject) => { 25 | request.post(redirected, { 26 | json: true, 27 | body: {userId, appName} 28 | }, (error, response, body) => { 29 | if (error) reject(error); 30 | else resolve(body); 31 | }) 32 | })) 33 | .finally(() => { 34 | callback(); 35 | }) 36 | 37 | } 38 | 39 | }; -------------------------------------------------------------------------------- /lib/commands/open.js: -------------------------------------------------------------------------------- 1 | const open = require('open') 2 | ; 3 | 4 | module.exports = function(FASTACK) { 5 | return function(args, callback) { 6 | open(args["path-or-url"]); 7 | callback(); 8 | } 9 | }; -------------------------------------------------------------------------------- /lib/commands/pwd.js: -------------------------------------------------------------------------------- 1 | const Path = require('path') 2 | , fs = require('fs') 3 | ; 4 | 5 | module.exports = function(FASTACK) { 6 | return function(args, callback) { 7 | FASTACK.logger.info(Path.relative(FASTACK.cwd, FASTACK.dir) || '/'); 8 | callback(); 9 | } 10 | }; -------------------------------------------------------------------------------- /lib/commands/register.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | , Syncano = require('syncano') 3 | , validator = require('validator') 4 | , request = require('request') 5 | , clui = require('clui') 6 | , Spinner = clui.Spinner 7 | , jspm = require('jspm') 8 | , chalk = require('chalk') 9 | , Promise = require('bluebird') 10 | ; 11 | 12 | module.exports = function(FASTACK) { 13 | return function(args, callback) { 14 | const self = this; 15 | 16 | function registerUser(url, answers) { 17 | return new Promise(function(resolve, reject) { 18 | request.post(url, {json:{answers}} , function (err, res) { 19 | if (err) { 20 | reject(err); 21 | return; 22 | } 23 | resolve( res.body.result.stdout ); 24 | }); 25 | }); 26 | } 27 | 28 | this.prompt([ 29 | { 30 | type: 'input', 31 | name: 'username', 32 | message: 'Fastack username: ' 33 | }, 34 | { 35 | type: 'input', 36 | name: 'email', 37 | message: 'Email: ' 38 | }, 39 | { 40 | type: 'password', 41 | name: 'password', 42 | message: 'Fastack password: ' 43 | } 44 | ], function(answers) { 45 | // var url = "http://api.fastack.io/register?username="+answers.username+"&password="+answers.password+"&email="+answers.email+"&apiKey="+FASTACK.apiKey; 46 | // var url = "https://api.syncano.io/v1/instances/fastack/webhooks/p/e0069d7708abccb1bcf09b0b8bc98bec71096378/register/?username="+answers.username+"&password="+answers.password+"&email="+answers.email+"&apiKey="+FASTACK.apiKey; 47 | var url = "https://api.syncano.io/v1/instances/fastack/webhooks/p/e0069d7708abccb1bcf09b0b8bc98bec71096378/register/"; 48 | var spinner = new Spinner('Registering user'); 49 | answers.apiKey = FASTACK.apiKey; 50 | spinner.start(); 51 | registerUser(url, answers).then( (result) => { 52 | spinner.stop(); 53 | FASTACK.logger.log(result); 54 | callback(); 55 | }).catch((e) => { 56 | spinner.stop(); 57 | FASTACK.logger.error(e); 58 | callback(); 59 | }); 60 | 61 | }); 62 | 63 | } 64 | }; -------------------------------------------------------------------------------- /lib/commands/rm.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra') 2 | , Path = require('path') 3 | ; 4 | 5 | module.exports = function(FASTACK) { 6 | return function(args, callback) { 7 | 8 | var file = Path.relative(FASTACK.dir, args["file"]); 9 | if (args.options && args.options.force) fs.removeSync(file); 10 | else FASTACK.shell.rm(file); 11 | 12 | callback(); 13 | } 14 | }; -------------------------------------------------------------------------------- /lib/commands/touch.js: -------------------------------------------------------------------------------- 1 | const Path = require('path') 2 | , fs = require('fs-extra') 3 | ; 4 | 5 | module.exports = function(FASTACK) { 6 | return function(args, callback) { 7 | 8 | try { 9 | fs.closeSync(fs.openSync(Path.resolve(FASTACK.dir, args["file-name"]), 'w')); 10 | FASTACK.vorpal.exec('ls'); 11 | } catch (e) { 12 | FASTACK.logger.error('Could not create file!'); 13 | } 14 | 15 | callback(); 16 | } 17 | }; -------------------------------------------------------------------------------- /lib/commands/uninstall.js: -------------------------------------------------------------------------------- 1 | const clui = require('clui') 2 | , Spinner = clui.Spinner 3 | , jspm = require('jspm') 4 | , chalk = require('chalk') 5 | ; 6 | 7 | module.exports = function(FASTACK) { 8 | return function(args, callback) { 9 | var spinner = new Spinner('Uninstalling ' + chalk.cyan(args.module)); 10 | spinner.start(); 11 | 12 | var updateSpinner = function(type, msg) { 13 | spinner.message(msg); 14 | }; 15 | 16 | jspm.on('log', updateSpinner); 17 | 18 | jspm.uninstall(args.module).then(function() { 19 | spinner.stop(); 20 | FASTACK.vorpal.exec('list'); 21 | FASTACK.build().then(function() { 22 | jspm.removeListener('log', updateSpinner); 23 | callback(); 24 | }).catch((e) => { 25 | spinner.stop(); 26 | FASTACK.logger.error(e); 27 | callback(); 28 | }); 29 | }).catch((e) => { 30 | spinner.stop(); 31 | FASTACK.logger.error(e); 32 | callback(); 33 | }); 34 | } 35 | }; -------------------------------------------------------------------------------- /lib/directory.js: -------------------------------------------------------------------------------- 1 | const clui = require('clui') 2 | , Spinner = clui.Spinner 3 | , os = require('os') 4 | , Path = require('path') 5 | , fs = require('fs-extra') 6 | , through2 = require('through2') 7 | , Promise = require('bluebird') 8 | , _ = require('lodash') 9 | , chalk = require('chalk') 10 | ; 11 | 12 | module.exports = function(FASTACK) { 13 | 14 | FASTACK.cwd = process.cwd(); 15 | FASTACK.dir = FASTACK.cwd; 16 | FASTACK.globalConfig = Path.join(os.homedir(), '.fastack'); 17 | FASTACK.localDir = Path.join(FASTACK.cwd, '.fastack'); 18 | 19 | FASTACK.updateDelimiter = function() { 20 | var path = Path.relative(FASTACK.cwd, FASTACK.dir); 21 | var name = Path.basename(path) || Path.basename(FASTACK.cwd); 22 | FASTACK.vorpal 23 | .delimiter('fastack:' + chalk.cyan(name) + ' $'); 24 | }; 25 | 26 | FASTACK.updateDelimiter(); 27 | 28 | FASTACK.traverse = function(dir, allowable, onData, completed) { 29 | if (!dir) throw new Error("Specify a directory to traverse"); 30 | if (!allowable) allowable = true; 31 | if (!onData) onData = function() {}; 32 | if (!completed) completed = function() {}; 33 | 34 | var excludeHiddenDirs = through2.obj(function (item, enc, next) { 35 | var relPath = Path.relative(FASTACK.cwd, item.path); 36 | if (!(/(^|\/)\.[^\/\.]/g).test(relPath)) this.push(item); 37 | next(); 38 | }); 39 | 40 | var excludeNodeModules = through2.obj(function (item, enc, next) { 41 | var relPath = Path.relative(FASTACK.cwd, item.path); 42 | if (relPath.indexOf('node_modules') == -1) this.push(item); 43 | next(); 44 | }); 45 | 46 | var excludeIrrelevantFiles = through2.obj(function (item, enc, next) { 47 | var ext = Path.extname(item.path).substring(1); 48 | if (allowable == true) this.push(item); 49 | else if(_.contains(allowable, ext)) this.push(item); 50 | next(); 51 | }); 52 | 53 | return new Promise(function(resolve, reject) { 54 | var items = []; 55 | fs.walk(dir) 56 | .pipe(excludeHiddenDirs) 57 | .pipe(excludeNodeModules) 58 | .pipe(excludeIrrelevantFiles) 59 | .on('data', function(item) { 60 | items.push(item); 61 | onData(item); 62 | }) 63 | .on('end', function() { 64 | completed(items); 65 | resolve(items); 66 | }); 67 | }); 68 | }; 69 | 70 | try { 71 | var config = fs.readFileSync(Path.join(FASTACK.cwd, 'fastack.json'), 'utf8'); 72 | config = JSON.parse(config); 73 | FASTACK.config = config; 74 | } catch(e) { 75 | FASTACK.config = {}; 76 | } 77 | 78 | 79 | return new Promise(function(resolve, reject) { 80 | var localDir; 81 | try { 82 | localDir = fs.lstatSync(FASTACK.localDir); 83 | FASTACK.app = true; 84 | } catch (e) { 85 | if (e.code == 'ENOENT') { 86 | FASTACK.app = false; 87 | } 88 | } 89 | resolve(); 90 | }); 91 | }; -------------------------------------------------------------------------------- /lib/fastack.js: -------------------------------------------------------------------------------- 1 | const vorpal = require('vorpal')() 2 | , chalk = require('chalk') 3 | , jspm = require('jspm') 4 | , clui = require('clui') 5 | , Spinner = clui.Spinner 6 | , _ = require('lodash') 7 | , parseArgs = require('minimist') 8 | , fs = require('fs') 9 | ; 10 | 11 | var FASTACK = { 12 | argv: parseArgs(process.argv.slice(2), { 13 | alias: { 14 | 'p': 'port' 15 | } 16 | }) 17 | }; 18 | 19 | 20 | FASTACK.vorpal = vorpal; 21 | 22 | var spinner = new Spinner('Starting Fastack...'); 23 | spinner.start(); 24 | require('./directory.js')(FASTACK) 25 | .then(() => { 26 | spinner.message('Fetching Fastack API keys...'); 27 | return require('./keys.js')(FASTACK)}) 28 | .then(() => { 29 | spinner.message('Loading helpers...'); 30 | return require('./helpers.js')(FASTACK)}) 31 | .then(() => { 32 | spinner.message('Loading builder...'); 33 | return require('./builder.js')(FASTACK)}) 34 | .then(() => { 35 | spinner.message('Starting local server...'); 36 | return require('./localServer.js')(FASTACK) 37 | }) 38 | .finally(() => { 39 | spinner.stop(); 40 | 41 | 42 | if (!FASTACK.app) { 43 | FASTACK.logger.warn('The current directory is not a Fastack application'); 44 | FASTACK.logger.info('To initialize a Fastack app in this directory, run \'init\''); 45 | } 46 | 47 | if (!FASTACK.userKey) { 48 | FASTACK.logger.info('Not logged into Fastack. Authenticate by running \'login\''); 49 | } 50 | 51 | if (FASTACK.localServer && FASTACK.localServer.server) { 52 | var port = FASTACK.localServer.server.address().port; 53 | FASTACK.logger.info('Local server now running on port ' + chalk.red(port) + ': ' + chalk.cyan('http://localhost:'+port) + '/'); 54 | } 55 | 56 | vorpal.show(); 57 | }).catch((e) => { 58 | spinner.stop(); 59 | FASTACK.logger.error(e); 60 | vorpal.show(); 61 | }) 62 | ; 63 | 64 | 65 | vorpal 66 | .command('login', 'Authenticate to Fastack') 67 | .action(require('./commands/login.js')(FASTACK)); 68 | 69 | vorpal 70 | .command('logout', 'De-authenticate to Fastack') 71 | .action(require('./commands/logout.js')(FASTACK)); 72 | 73 | vorpal 74 | .command('register', 'Register an account with Fastack') 75 | .action(require('./commands/register.js')(FASTACK)); 76 | 77 | vorpal 78 | .command('init', 'Initialize the current directory as a Fastack app') 79 | .action(require('./commands/init.js')(FASTACK)); 80 | 81 | vorpal 82 | .command('start', 'start server') 83 | .action(function(args, callback) { 84 | FASTACK.localServer.start(); 85 | callback() 86 | }); 87 | 88 | vorpal 89 | .command('stop', 'stop server') 90 | .action(function(args, callback) { 91 | FASTACK.localServer.stop(); 92 | callback() 93 | }); 94 | 95 | vorpal 96 | .command('install [module] [target]', 'install a module via JSPM') 97 | .action(require('./commands/install.js')(FASTACK)); 98 | 99 | vorpal 100 | .command('uninstall [module]', 'uninstall a module via JSPM') 101 | .autocompletion(function(text, iteration, cb) { 102 | var installed = _.keys(FASTACK.getInstalled()); 103 | if (iteration > 1) { 104 | cb(void 0, installed); 105 | } else { 106 | var match = this.match(text, installed); 107 | if (match) { 108 | cb(void 0, 'uninstall ' + match); 109 | } else { 110 | cb(void 0, void 0); 111 | } 112 | } 113 | }) 114 | .action(require('./commands/uninstall.js')(FASTACK)); 115 | 116 | vorpal 117 | .command('list', 'list installed modules') 118 | .action(require('./commands/list.js')(FASTACK)); 119 | 120 | vorpal 121 | .command('build', 'trigger a build') 122 | .action(function(args, callback) { 123 | var spinner = new Spinner('Building application...'); 124 | spinner.start(); 125 | var start = new Date(); 126 | FASTACK.build(true).then(function() { 127 | spinner.stop(); 128 | var t = new Date() - start; 129 | FASTACK.logger.info('Build time: ' + t + ' ms.'); 130 | callback(); 131 | }).catch((e) => { 132 | spinner.stop(); 133 | FASTACK.logger.error(e); 134 | callback(); 135 | }); 136 | }); 137 | 138 | vorpal 139 | .command('export ', 'export application') 140 | .action(require('./commands/export.js')(FASTACK)); 141 | 142 | vorpal 143 | .command('ls', 'list contents of current dir') 144 | // .option('-l, --long', 'Long output.') 145 | .action(require('./commands/ls.js')(FASTACK)); 146 | 147 | vorpal 148 | .command('mkdir ', 'create a new directory') 149 | .action(require('./commands/mkdir.js')(FASTACK)); 150 | 151 | vorpal 152 | .command('touch ', 'create a new file') 153 | .action(require('./commands/touch.js')(FASTACK)); 154 | 155 | vorpal 156 | .command('cp ', 'copy a file') 157 | .action(require('./commands/cp.js')(FASTACK)); 158 | 159 | vorpal 160 | .command('rm ', 'remove a file') 161 | .option('-f, --force', 'remove a directory and its contents') 162 | .autocompletion(require('./autocomplete.js')(FASTACK,'rm')) 163 | .action(require('./commands/rm.js')(FASTACK)); 164 | 165 | vorpal 166 | .command('cd ', 'change directory') 167 | .autocompletion(require('./autocomplete.js')(FASTACK,'cd')) 168 | .action(require('./commands/cd.js')(FASTACK)); 169 | 170 | vorpal 171 | .command('pwd', 'list current path within app') 172 | .action(require('./commands/pwd.js')(FASTACK)); 173 | 174 | vorpal 175 | .command('cat ', 'show the contents of a file') 176 | .autocompletion(require('./autocomplete.js')(FASTACK,'cat')) 177 | .action(require('./commands/cat.js')(FASTACK)); 178 | 179 | vorpal 180 | .command('open ', 'open a path or url') 181 | .autocompletion(require('./autocomplete.js')(FASTACK,'open')) 182 | .action(require('./commands/open.js')(FASTACK)); 183 | 184 | vorpal 185 | .command('mv ', 'move a file') 186 | .action(require('./commands/mv.js')(FASTACK)); 187 | 188 | vorpal 189 | .command('add_host ', 'add a new host') 190 | .action(require('./commands/add_host.js')(FASTACK)); 191 | 192 | vorpal 193 | .command('deploy ', 'deploy to Syncano') 194 | .action(require('./commands/deploy.js')(FASTACK)); 195 | 196 | 197 | vorpal 198 | .command('new_app ', 'create a new fastack app') 199 | .action(require('./commands/new_app.js')(FASTACK)); 200 | 201 | 202 | 203 | vorpal.find('exit').remove(); 204 | 205 | vorpal.ui._sigint = function() { 206 | process.exit(0); 207 | }; 208 | 209 | process.on('exit', function(code) { 210 | FASTACK.logger.confirm('Exiting Fastack.'); 211 | }); 212 | 213 | process.on('uncaughtException', function(err) { 214 | console.error(err); 215 | //FASTACK.logger.error(err); 216 | }); -------------------------------------------------------------------------------- /lib/helpers.js: -------------------------------------------------------------------------------- 1 | const shell = require('shelljs') 2 | , console2 = require('console2')({disableWelcome: true, override: false}) 3 | , Promise = require('bluebird') 4 | , Path = require('path') 5 | , fs = require('fs-extra') 6 | , walkSync = require('walk-sync') 7 | , Syncano = require('syncano') 8 | ; 9 | 10 | shell.log = function() { 11 | FASTACK.logger.info.apply(this, arguments); 12 | }; 13 | 14 | module.exports = function(FASTACK) { 15 | 16 | FASTACK.shell = shell; 17 | FASTACK.console2 = console2; 18 | 19 | FASTACK.logger = { 20 | log: function(msg) { 21 | console2.log(msg); 22 | }, 23 | info: function(msg) { 24 | if (typeof msg === 'string') msg = msg.replace(/\n+$/, ""); 25 | console2.log(msg); 26 | }, 27 | error: function(msg) { 28 | if (typeof msg === 'string') msg = msg.replace(/\n+$/, ""); 29 | if (typeof msg === 'object') msg = msg.toString(); 30 | console2.error(msg); 31 | }, 32 | warn: function(msg) { 33 | if (typeof msg === 'string') msg = msg.replace(/\n+$/, ""); 34 | console2.warn(msg); 35 | }, 36 | confirm: function(msg) { 37 | if (typeof msg === 'string') msg = msg.replace(/\n+$/, ""); 38 | console2.info(msg) 39 | }, 40 | object: function(obj) { 41 | var root = console2.box(); 42 | 43 | function traverse(obj, prev) { 44 | var box = prev.box(); 45 | for (var key in obj) { 46 | if (typeof obj[key] === 'object') { 47 | box.line(chalk.cyan(key)); 48 | traverse(obj[key], box); 49 | } 50 | else if (typeof obj[key] === 'array') { 51 | for (var i in obj[key]) traverse(obj[key], box) 52 | } 53 | else box.line(key + ': ' + obj[key]) 54 | } 55 | box.over(); 56 | return prev; 57 | } 58 | 59 | traverse(obj, root).out(); 60 | } 61 | }; 62 | 63 | console.log = function(msg) { 64 | FASTACK.logger.info(msg); 65 | }; 66 | 67 | FASTACK.export = function(dir) { 68 | return new Promise((resolve, reject) => { 69 | FASTACK.build(true) 70 | .then((hash) => { 71 | var dest = Path.resolve(FASTACK.localDir, 'builds/', hash); 72 | var ultDest = Path.relative(FASTACK.cwd, dir); 73 | 74 | var appFiles = walkSync(FASTACK.cwd, { 75 | globs: ["**"], 76 | directories: false 77 | }); 78 | 79 | for (var f in appFiles) { 80 | var file = appFiles[f]; 81 | fs.copySync(file, Path.resolve(dest, Path.relative(FASTACK.cwd, file))); 82 | } 83 | fs.copySync(dest, ultDest); 84 | 85 | var files = walkSync(ultDest, {directories: false}); 86 | 87 | for (var f in files) { 88 | var file = Path.resolve(ultDest, files[f]); 89 | if (Path.extname(file) == '.html') { 90 | var newHtml = require('./htmlBuild')(FASTACK).prod(fs.readFileSync(file, 'utf8'), hash); 91 | fs.outputFileSync(file, newHtml, 'utf8'); 92 | } 93 | } 94 | 95 | resolve(ultDest); 96 | }) 97 | }) 98 | }; 99 | 100 | FASTACK.getUserId = () => new Promise((resolve, reject) => { 101 | var fastack = new Syncano({instance: 'fastack', apiKey: FASTACK.apiKey, userKey: FASTACK.userKey}); 102 | fastack.class('user_profile').dataobject().list() 103 | .then((u) => resolve(u.objects[0].owner)) 104 | }); 105 | 106 | 107 | return new Promise(function(resolve, reject) { 108 | resolve(); 109 | }); 110 | }; -------------------------------------------------------------------------------- /lib/htmlBuild.js: -------------------------------------------------------------------------------- 1 | const cheerio = require('cheerio') 2 | , fs = require('fs-extra') 3 | , Path = require('path') 4 | , Promise = require('bluebird') 5 | ; 6 | 7 | module.exports = function(FASTACK) { 8 | var configString = "System.config({paths: {'fastack:*': '.fastack/fastack-packages/*'}});"; 9 | 10 | return { 11 | dev: function(html, hash) { 12 | var $ = cheerio.load(html); 13 | 14 | $('head').append(''); 15 | $('head').append(''); 16 | $('head').append(''); 17 | 18 | return new Promise((resolve, reject) => { 19 | var str = ''; 20 | FASTACK.getLocalDeps().then((deps) => { 21 | deps.unshift('fastack:dev/live-reload'); 22 | for (var i in deps) { 23 | str += "System.import('"+Path.relative(FASTACK.cwd, deps[i])+"');\n"; 24 | } 25 | $('head').append(''); 26 | resolve($.html()); 27 | }); 28 | }) 29 | }, 30 | prod: function(html, hash) { 31 | var $ = cheerio.load(html); 32 | 33 | $('head').append(''); 34 | $('head').append(''); 35 | 36 | //return new Promise((resolve, reject) => { 37 | // resolve($.html()); 38 | //}) 39 | return $.html() 40 | } 41 | } 42 | }; -------------------------------------------------------------------------------- /lib/keys.js: -------------------------------------------------------------------------------- 1 | const Syncano = require('syncano') 2 | , clui = require('clui') 3 | , Spinner = clui.Spinner 4 | , os = require('os') 5 | , Path = require('path') 6 | , fs = require('fs') 7 | , request = require('request') 8 | , Promise = require('bluebird') 9 | ; 10 | var syncanoKey = function() { 11 | return new Promise(function(resolve, reject) { 12 | request('http://api.fastack.io/cli-api-key', function(error, response, body) { 13 | if (error) reject(error); 14 | var key = JSON.parse(body).result.stdout; 15 | resolve(key); 16 | }); 17 | }); 18 | }; 19 | 20 | module.exports = function(FASTACK) { 21 | return new Promise(function(resolve, reject) { 22 | 23 | var config; 24 | try { 25 | config = fs.readFileSync(FASTACK.globalConfig, 'utf8'); 26 | config = JSON.parse(config); 27 | } catch(e) { 28 | if (e.code && e.code == 'ENOENT') { // file not found 29 | config = {}; 30 | } 31 | } 32 | 33 | FASTACK.userKey = config.userKey; 34 | FASTACK.apiKey = config.apiKey; 35 | 36 | 37 | if (!FASTACK.apiKey) { 38 | syncanoKey().then(function(key) { 39 | FASTACK.apiKey = key; 40 | resolve(); 41 | }) 42 | } else resolve(); 43 | 44 | }); 45 | }; -------------------------------------------------------------------------------- /lib/localServer.js: -------------------------------------------------------------------------------- 1 | const clui = require('clui') 2 | , Spinner = clui.Spinner 3 | , FastackDelivery = require('fastack-deliver-middleware') 4 | , Promise = require('bluebird') 5 | , connect = require('connect') 6 | , http = require('http') 7 | , chalk = require('chalk') 8 | , Path = require('path') 9 | , fs = require('fs-extra') 10 | , mime = require('mime') 11 | , chokidar = require('chokidar') 12 | , crypto = require('crypto') 13 | ; 14 | 15 | function LocalServer(FASTACK) { 16 | this.FASTACK = FASTACK; 17 | } 18 | 19 | var sendFile = function(FASTACK){ 20 | return function(req, res, next) { 21 | var routed = req.FASTACK.routed; 22 | 23 | if (routed && routed.type == 'redirect') { 24 | res.statusCode = routed.code; 25 | res.setHeader('Location', routed.path); 26 | res.end(); 27 | next(); 28 | } 29 | 30 | else if (routed && routed.type == 'file') { 31 | try { 32 | var filePath = Path.join(FASTACK.cwd, routed.path); 33 | res.setHeader('Content-Type', mime.lookup(routed.path)); 34 | var contents = fs.readFileSync(filePath); 35 | if (FASTACK.app && Path.extname(filePath) == '.html') 36 | require('./htmlBuild.js')(FASTACK).dev(contents, FASTACK.hash).then((contents) => { 37 | res.end(contents, 'binary'); 38 | }); 39 | else res.end(contents, 'binary'); 40 | } catch(e) { 41 | //FASTACK.logger.error(e); 42 | next(); 43 | } 44 | } 45 | 46 | else next(); 47 | 48 | } 49 | }; 50 | 51 | LocalServer.prototype.start = function() { 52 | var FASTACK = this.FASTACK; 53 | var req = {FASTACK: {}}; 54 | FastackDelivery.configure('development')(req, null, function() {}); 55 | FASTACK.config = req.FASTACK.config; 56 | 57 | var fastackServer = connect(); 58 | 59 | fastackServer.use(function(req, res, next) { 60 | req.FASTACK = {}; 61 | req.FASTACK.config = FASTACK.config; 62 | next(); 63 | }); 64 | 65 | fastackServer.use(FastackDelivery.configure('development')); 66 | fastackServer.use(FastackDelivery.route()); 67 | fastackServer.use(sendFile(FASTACK)); 68 | fastackServer.use(FastackDelivery.notFound()); 69 | 70 | 71 | var port = FASTACK.argv.port || FASTACK.config.local.port; 72 | var server = http.createServer(fastackServer).listen(port); 73 | 74 | this.server = server; 75 | 76 | if (FASTACK.app) { 77 | var io = require('socket.io')(server); 78 | 79 | var watcher = chokidar.watch(FASTACK.cwd, { 80 | //ignored: /[\/\\]\./, 81 | persistent: true, 82 | ignoreInitial: true 83 | }); 84 | 85 | this.watcher = watcher; 86 | 87 | var spinner = new Spinner('Reloading clients'); 88 | watcher.on('all', function(event, path) { 89 | var socket = io; 90 | spinner.start(); 91 | var css = Path.extname(path) == '.css'; 92 | if (css) socket.emit('reload-css'); 93 | else socket.emit('reload'); 94 | 95 | spinner.stop(); 96 | FASTACK.vorpal.ui.redraw.done(); 97 | }); 98 | 99 | } 100 | 101 | }; 102 | 103 | LocalServer.prototype.stop = function() { 104 | this.server.close(); 105 | if (this.watcher) this.watcher.close(); 106 | }; 107 | 108 | module.exports = function(FASTACK) { 109 | return new Promise(function(resolve, reject) { 110 | FASTACK.localServer = new LocalServer(FASTACK); 111 | 112 | if (FASTACK.app) { 113 | FASTACK.build() 114 | .then(function() { 115 | FASTACK.localServer.start(); 116 | resolve() 117 | }) 118 | .catch(function(e) { 119 | reject(e); 120 | }) 121 | } else { 122 | FASTACK.localServer.start(); 123 | resolve(); 124 | } 125 | 126 | }); 127 | 128 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fastack", 3 | "description": "Command line interface for deploying and interacting with Fastack", 4 | "keywords": [ 5 | "cli", 6 | "fastack", 7 | "static app web host platform", 8 | "platform-as-a-service", 9 | "deployment" 10 | ], 11 | "version": "0.1.3", 12 | "author": "Fastack", 13 | "maintainers": [ 14 | "Patrick DeVivo " 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/fastack/cli.git" 19 | }, 20 | "preferGlobal": true, 21 | "dependencies": { 22 | "bluebird": "^3.1.1", 23 | "chalk": "^1.0.0", 24 | "cheerio": "^0.19.0", 25 | "chokidar": "^1.4.1", 26 | "clean-css": "^3.4.8", 27 | "cli-table2": "^0.1.9", 28 | "clui": "^0.3.1", 29 | "connect": "^3.4.0", 30 | "console-highlight": "^0.1.0", 31 | "console2": "^2.2.0", 32 | "fastack-deliver-middleware": "0.0.10", 33 | "fs-extra": "^0.26.3", 34 | "jspm": "^0.16.19", 35 | "less": "^2.5.3", 36 | "lodash": "^3.10.1", 37 | "mime": "^1.3.4", 38 | "minimist": "^1.2.0", 39 | "open": "0.0.5", 40 | "request": "^2.67.0", 41 | "shelljs": "^0.5.3", 42 | "socket.io": "^1.3.7", 43 | "syncano": "^0.4.5", 44 | "systemjs": "^0.19.9", 45 | "through2": "^2.0.0", 46 | "validator": "*", 47 | "vorpal": "^1.4.1", 48 | "walk-sync": "^0.2.6" 49 | }, 50 | "bin": { 51 | "fastack": "./bin/fastack" 52 | }, 53 | "main": "./lib/fastack", 54 | "license": "MIT" 55 | } 56 | --------------------------------------------------------------------------------