├── .gitignore ├── README.md ├── bin ├── swan-configure.js ├── swan-install.js ├── swan-new.js ├── swan-setup.js ├── swan-start.js └── swan.js ├── book.json ├── docs └── getting-started.md ├── lib ├── cli-utils.js ├── config.js ├── generator │ └── new │ │ ├── index.js │ │ ├── new-client.js │ │ ├── new-project.js │ │ └── new-server.js ├── new.js └── setup.js ├── package.json └── templates └── server ├── .sailsrc ├── api └── controllers │ └── CountController.js └── config ├── cors.js ├── models.js └── routes.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | jspm_packages 3 | bower_components 4 | .idea 5 | .DS_STORE 6 | build/reports -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swan CLI 2 | *The command line interface for SWAN.* 3 | 4 | **Unmaintained! This setup requires an overhaul, which won't happen any time soon.** 5 | 6 | SWAN as a stack runs [**S**ails](http://sailsjs.org/), [**W**aterline](https://github.com/balderdashy/waterline), [**A**urelia](http://aurelia.io) and [**N**ode](https://nodejs.org). 7 | 8 | **Note:** This module is work in progress. 9 | It already provides some useful methods, but isn't _done_ yet. 10 | Contributions are more than welcome. 11 | 12 | ## What? 13 | The SWAN stack makes developing applications a lot easier and faster. It follows the following principles: 14 | 15 | - **Separate repositories:** The project, the API and the client live in separate repositories. 16 | - **API-driven development:** Separation of client (static files) and server (API). 17 | - **Single entity definition:** Entities (and thus API endpoints) are the same on both the server and the client, including validation. 18 | 19 | ## Installation 20 | 21 | 1. Install swan and the other dependencies: `npm i -g sails jspm gulp swan-cli` 22 | 2. Create a [personal access token](https://github.com/settings/tokens) on github and set permissions for `repo`. 23 | 3. Type `swan configure` and follow the steps. 24 | 4. Enjoy! 25 | 26 | ## Usage 27 | Usage is described in the `swan --help` output. 28 | 29 | #### New project 30 | To create a new project. 31 | 32 | ``` 33 | swan new projectname 34 | ``` 35 | 36 | #### Configuring project 37 | Configure default settings such as personal access token and protocols (ssh/https). Switching protocols in a swan project will update all the remotes. 38 | 39 | ``` 40 | swan configure 41 | ``` 42 | 43 | #### Collaborate on existing project 44 | To start working on, or check out a project. 45 | 46 | ``` 47 | swan setup owner/repository 48 | ``` 49 | 50 | For instance `swan setup spoonx/aurelia-todo`. 51 | 52 | #### Start your project 53 | Start your project. This starts both the server, and the client in one convenient command. 54 | 55 | ``` 56 | swan start --v 57 | ``` 58 | 59 | ## Configuration 60 | Swan can be configured by changing the contents of the `swan.json` file. 61 | 62 | ## Coming soon(ish) 63 | * Getting started 64 | * Swan install (to quickly set up your project after cloning). 65 | * Support for mobile. 66 | 67 | ## Disclaimer 68 | This project is a little bit messy. It's relatively easy to get into, but it could be structured much better. 69 | This is the first version, meant as a PoC to figure out what works best for SWAN. 70 | This means it _will_ be refactored at some point, but the usage will most likely stay the same. 71 | -------------------------------------------------------------------------------- /bin/swan-configure.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | "use strict"; 4 | 5 | const fs = require('fs'); 6 | const program = require('commander'); 7 | const colors = require('colors'); 8 | const utils = require('../lib/cli-utils'); 9 | const config = require('../lib/config'); 10 | const heart = '<3 '.bold.blue; 11 | const heartBroken = ' string.trim(), 28 | validate: function(input) { 29 | if (input && input.length === 40) { 30 | return true; 31 | } 32 | 33 | return 'Expected the argument to be a personal access token.'; 34 | } 35 | }, 36 | protocol: { 37 | message: 'Which protocol do you want to use:', 38 | type : 'list', 39 | choices: ['https', 'ssh'] 40 | } 41 | } 42 | 43 | program.parse(process.argv); 44 | 45 | if (!config.system) { 46 | return initConfiguration(); 47 | } 48 | 49 | editConfiguration(); 50 | 51 | function initConfiguration() { 52 | inquirer.prompt(utils.questions(questions), saveConfiguration); 53 | } 54 | 55 | function editConfiguration() { 56 | var subQuestion = {}; 57 | 58 | inquirer.prompt(utils.questions({ 59 | edit: { 60 | message: 'What do you want to reconfigure?', 61 | type : 'list', 62 | choices: Object.keys(questions) 63 | } 64 | }), answer => { 65 | subQuestion[answer['edit']] = questions[answer['edit']]; 66 | 67 | // set previous given answer to default 68 | if (subQuestion[answer['edit']].choices) { 69 | subQuestion[answer['edit']].default = config.system[answer['edit']]; 70 | } 71 | 72 | inquirer.prompt(utils.questions(subQuestion), saveConfiguration); 73 | }); 74 | } 75 | 76 | function saveConfiguration (settings) { 77 | let home = process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME']; 78 | 79 | // easiest way to display `personal access token` in the reconfigure option 80 | if (settings['personal access token']) { 81 | settings['token'] = settings['personal access token']; 82 | delete settings['personal access token']; 83 | } 84 | 85 | if (settings.username) { 86 | utils.setUsername(settings.username); 87 | 88 | delete settings.username; // don't save in swan 89 | } 90 | 91 | // merge with existing settings 92 | if (config.system) { 93 | for (var attr in settings) { 94 | if (attr === 'protocol' && config.system[attr] !== settings[attr]) { 95 | utils.updateProjectProtocol(attr); 96 | } 97 | 98 | config.system[attr] = settings[attr]; 99 | } 100 | 101 | settings = config.system; 102 | } 103 | 104 | fs.writeFile(`${home}/.swan`, JSON.stringify(settings), error => { 105 | if (error) { 106 | console.log(`${heartBroken}Something went wrong!`, error); 107 | } else { 108 | console.log(`${heart}Saved configuration!`); 109 | } 110 | }); 111 | } 112 | -------------------------------------------------------------------------------- /bin/swan-install.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | "use strict"; 4 | 5 | /** 6 | * Install a swan module. 7 | */ 8 | -------------------------------------------------------------------------------- /bin/swan-new.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const program = require('commander'); 3 | const colors = require('colors'); 4 | const utils = require('../lib/cli-utils'); 5 | const path = require('path'); 6 | const createNew = require('../lib/generator/new'); 7 | const heart = '<3 '.bold.blue; 8 | const username = require('../lib/config').git.user.name; 9 | const inquirer = require('inquirer'); 10 | const questions = utils.questions({ 11 | projectName: { 12 | message: 'Enter the name of your new project:', 13 | when : function(optionsSoFar) { 14 | var firstArg = program.args[0]; 15 | var cwd, isCwd; 16 | 17 | if (!firstArg) { 18 | return true; 19 | } 20 | 21 | cwd = process.cwd(); 22 | isCwd = ['.', './'].indexOf(firstArg) > -1; 23 | optionsSoFar.projectName = isCwd ? cwd.substr(cwd.lastIndexOf('/') + 1) : firstArg; 24 | optionsSoFar.path = isCwd ? cwd : path.join(cwd, firstArg); 25 | } 26 | }, 27 | source : { 28 | type : 'list', 29 | choices: ['Github', 'Bitbucket'], 30 | message: 'Where is your project hosted:', 31 | default: 'Github' 32 | }, 33 | description: 'Enter a nice description for the project:', 34 | owner : { 35 | message: 'Enter the name of the owner (upstream, e.g. google, spoonx):', 36 | default: username 37 | }, 38 | username : { 39 | when : function(optionsSoFar) { 40 | var userIsOwner = optionsSoFar.owner.toLowerCase() === username.toLowerCase(); 41 | 42 | if (userIsOwner) { 43 | optionsSoFar.username = username; 44 | optionsSoFar.owner = null; 45 | } 46 | 47 | return !userIsOwner; 48 | }, 49 | message: 'Enter your username:', 50 | default: username 51 | } 52 | }); 53 | 54 | program.parse(process.argv); 55 | 56 | inquirer.prompt(questions, function(answers) { 57 | createNew(answers) 58 | .then(() => { 59 | console.log(`\n${heart}Created project` + ` ${answers.projectName}!`.bold); 60 | console.log(`${heart}Make sure to verify the existence of the repositories on ${answers.source}.`); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /bin/swan-setup.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | "use strict"; 4 | 5 | // swan setup github:spoonx/todo-example 6 | 7 | const program = require('commander'); 8 | const colors = require('colors'); 9 | const utils = require('../lib/cli-utils'); 10 | const path = require('path'); 11 | const setup = require('../lib/setup'); 12 | const heart = '<3 '.bold.blue; 13 | const heartBroken = ' { 49 | console.log(`\n${heart}Setup has been completed for` + ` ${answers.slug}`.bold); 50 | process.exit(); 51 | }) 52 | .catch(error => { 53 | console.log(`\n${heartBroken}` + error); 54 | process.exit(); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /bin/swan-start.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | "use strict"; 4 | 5 | const colors = require('colors'); 6 | const utils = require('../lib/cli-utils'); 7 | const spawn = require('cross-spawn-async'); 8 | const program = require('commander'); 9 | 10 | program 11 | .option('-v, --verbose', 'Detailed logging', null, null) 12 | .parse(process.argv); 13 | 14 | run('client'); 15 | run('server'); 16 | 17 | function run(side) { 18 | let runner = utils.config.runners[side]; 19 | let bin = runner.bin; 20 | let parameters = runner.parameters; 21 | let child; 22 | 23 | if (program.verbose) { 24 | parameters.push('--verbose'); 25 | } 26 | 27 | child = spawn(bin, parameters, {cwd: utils.path(side)}); 28 | console.log('Program:'.underline.bold.green + ` started ${side}!`); 29 | 30 | redirectOutput(side, child); 31 | } 32 | 33 | function redirectOutput(prefix, child) { 34 | if (program.verbose) { 35 | child.stdout.pipe(process.stdout); 36 | } 37 | 38 | child.stderr.pipe(process.stderr); 39 | 40 | child.on('close', code => { 41 | process.stdout.write(`"${prefix}" process exited with code ${code}`); 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /bin/swan.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var program = require('commander'); 3 | 4 | program 5 | .command('new [projectname]', 'Create a new project') 6 | .command('start', 'Start project for dev') 7 | .command('configure', 'Configure github settings') 8 | .command('setup', 'Setup a project') 9 | .parse(process.argv); 10 | -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | ## Going deep 2 | SWAN follows some best practises and beliefs. These are relatively simple, and explained below. 3 | 4 | ### Project overview 5 | Every project is split up in three repositories: 6 | 7 | * Project 8 | * Client 9 | * Server 10 | 11 | ### Project 12 | The project holds meta-data for your project. It holds the swan.json, documentation and perhaps a wiki. This is the goto repository when starting work on this project. This is also the repository you use to get started, as it is responsible for also cloning your other repositories. 13 | 14 | By default, the project repository has a `.gitignore` for the client and the server repository. The reason for this is because swan works contained in a single directory. This means you will initially start out with the following directory structure: 15 | 16 | ``` 17 | ./coolname 18 | ./coolname/coolname-client 19 | ./coolname/coolname-server 20 | ``` 21 | 22 | ### Client 23 | This repository holds the client side code for your project. This will be an aurelia application based on our skeleton, that allows you to work on the project with manual (boilerplate) code. 24 | -------------------------------------------------------------------------------- /lib/cli-utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const config = require('./config'); 4 | const path = require('path'); 5 | const SimpleGit = require('simple-git'); 6 | const fs = require('fs'); 7 | const spawn = require('cross-spawn-async'); 8 | const exec = require('child_process').exec; 9 | 10 | module.exports.questions = function(questionsObject) { 11 | var questions = []; 12 | 13 | Object.getOwnPropertyNames(questionsObject).forEach(function(questionKey) { 14 | var question = questionsObject[questionKey]; 15 | 16 | if (typeof question === 'string') { 17 | question = { 18 | message: question, 19 | type : question.trim().slice(-1) === '?' ? 'confirm' : 'input' 20 | }; 21 | } 22 | 23 | question.name = questionKey; 24 | 25 | questions.push(question); 26 | }); 27 | 28 | return questions; 29 | }; 30 | 31 | module.exports.path = function(side, dir) { 32 | return path.join(process.cwd(), config.project.repositories[side].directory, dir || ''); 33 | }; 34 | 35 | module.exports.bin = function(side) { 36 | return module.exports.path(side, config.runners[side].bin); 37 | }; 38 | 39 | module.exports.getOrigin = function(options, side) { 40 | if (typeof options === 'string') { 41 | side = options; 42 | options = {}; 43 | } 44 | 45 | options = options || {}; 46 | let protocol = this.getProtocol(options.source || config.project.source); 47 | let username = options.username || config.git.user.name; 48 | let project = options.projectName || config.project.name; 49 | side = (side === 'project' || !side) ? '' : `-${side}`; 50 | 51 | return `${protocol}${username}/${project}${side}.git`; 52 | }; 53 | 54 | module.exports.setOrigin = function(side) { 55 | return new Promise(function(resolve) { 56 | let simpleGit = SimpleGit(module.exports.path(side)); 57 | 58 | simpleGit.addRemote('origin', module.exports.getOrigin(side)).then(() => { 59 | resolve(); 60 | }); 61 | }); 62 | }; 63 | 64 | module.exports.setRemotes = function(options) { 65 | return new Promise(function(resolve) { 66 | let simpleGit = SimpleGit(path.join(process.cwd(), options.directory)); 67 | 68 | if (options.upstream) { 69 | return simpleGit.addRemote('upstream', options.upstream).then(function() { 70 | resolve(); 71 | }); 72 | } 73 | 74 | return simpleGit.addRemote('origin', options.origin).then(() => { 75 | resolve(); 76 | }); 77 | }); 78 | }; 79 | 80 | module.exports.getProtocol = function (source) { 81 | return (!config.system || config.system.protocol === 'https') ? config.sources[source].https : config.sources[source].ssh; 82 | } 83 | 84 | module.exports.convertProtocol = function(str, source) { 85 | var source = config.sources[source]; 86 | var protocol = config.system.protocol; 87 | 88 | if (protocol === 'https' && str.search(source.ssh) > -1) { 89 | return str.replace(source.ssh, source.https); 90 | } 91 | 92 | if (protocol === 'ssh' && str.search(source.https) > -1) { 93 | return str.replace(source.https, source.ssh); 94 | } 95 | 96 | return str; 97 | } 98 | 99 | // update the existing protocol with the new one e.g ssh to https 100 | module.exports.updateProjectProtocol = function(protocol) { 101 | let self = this; 102 | let repositories; 103 | let swanConfig; 104 | 105 | try { 106 | swanConfig = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'swan.json'), "utf8")); 107 | repositories = swanConfig.repositories; 108 | } catch(error) { 109 | } 110 | 111 | if (!repositories) { 112 | return; 113 | } 114 | 115 | function renameRemote(simpleGit, remote) { 116 | simpleGit.removeRemote(remote.name) 117 | .then(() => simpleGit.addRemote(remote.name, self.convertProtocol(remote.refs.fetch, swanConfig.source))); 118 | } 119 | 120 | for (var i in repositories) { 121 | let simpleGit = SimpleGit(path.join(process.cwd(), repositories[i].directory)); 122 | 123 | simpleGit.getRemotes(true, (error, remotes) => { 124 | if (error) { 125 | console.log(`Unable to set remotes for ${repositories[i].directory}!`, error); 126 | return; 127 | } 128 | 129 | for (var j in remotes) { 130 | renameRemote(simpleGit, remotes[j]) 131 | } 132 | }); 133 | } 134 | } 135 | 136 | module.exports.setUsername = function (username) { 137 | exec('git config --global user.name "' + username + '"'); 138 | } 139 | 140 | module.exports.cloneRepo = function(src, dest, prefixUser) { 141 | let simpleGit = SimpleGit(process.cwd()); 142 | var protocol = this.getProtocol('Github'); 143 | 144 | if (prefixUser) { 145 | src = `${config.git.user.name}/${src}`; 146 | } 147 | 148 | return simpleGit.clone(`${protocol}${src}.git`, dest); 149 | } 150 | 151 | module.exports.installDependencies = function(directory) { 152 | let promises = []; 153 | let packageConfig; 154 | 155 | function install(type) { 156 | promises.push(new Promise((resolve, reject) => { 157 | let install = spawn(type, ['install'], {cwd: directory}).on('close', code => { 158 | if (code === 0) { 159 | return resolve(); 160 | } 161 | 162 | return reject(code); 163 | }); 164 | 165 | install.stdout.on('data', data => {}); 166 | install.stderr.on('data', data => {}); 167 | })); 168 | } 169 | 170 | try { 171 | packageConfig = JSON.parse(fs.readFileSync(`${directory}/package.json`)); 172 | } catch(error) { 173 | } 174 | 175 | if (packageConfig.dependencies || packageConfig.devDependencies) { 176 | install('npm'); 177 | } 178 | 179 | if (packageConfig.jspm) { 180 | install('jspm'); 181 | } 182 | 183 | return Promise.all(promises); 184 | } 185 | 186 | module.exports.config = config; 187 | -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | var ini = require('ini'); 4 | var home = process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME']; 5 | var configPath = path.join(home, '.gitconfig'); 6 | var projectConfig = null; 7 | 8 | try { 9 | fs.statSync(process.cwd() + '/swan.json'); 10 | 11 | projectConfig = require(process.cwd() + '/swan.json'); 12 | } catch (error) { 13 | } 14 | 15 | module.exports = { 16 | sources : { 17 | Bitbucket: { 18 | ssh : 'git@bitbucket.org:', 19 | https: 'https://bitbucket.org/' 20 | }, 21 | Github: { 22 | ssh : 'git@github.com:', 23 | https: 'https://github.com/', 24 | } 25 | }, 26 | git : ini.parse(fs.readFileSync(configPath, 'utf-8')), 27 | sails : { 28 | bin: path.resolve(__dirname, '../node_modules/.bin/sails') 29 | }, 30 | runners : { 31 | server: {bin : 'sails', parameters: ['lift']}, 32 | client: {bin : 'gulp', parameters: ['watch']} 33 | }, 34 | templatePath : path.resolve(__dirname, '../templates'), 35 | clientSkeleton : { 36 | ssh : 'git@github.com:spoonx/aurelia-skeleton.git', 37 | https: 'https://github.com/SpoonX/aurelia-skeleton.git' 38 | }, 39 | project : projectConfig, 40 | defaultSlug : 'spoonx/swan-example', 41 | serverDependencies: [ 42 | 'sails-hook-authorization' 43 | ], 44 | serverDevDependencies: [ 45 | 'sails-hook-autoreload' 46 | ], 47 | templateMapper : [ 48 | {'server/.sailsrc' : '{server}/.sailsrc'}, 49 | {'server/api/controllers/CountController.js' : '{server}/api/controllers/CountController.js'}, 50 | {'server/config/routes.js' : '{server}/config/routes.js'}, 51 | {'server/config/models.js' : '{server}/config/models.js'}, 52 | {'server/config/cors.js' : '{server}/config/cors.js'} 53 | ] 54 | }; 55 | 56 | try { 57 | module.exports.system = JSON.parse(fs.readFileSync(path.join(home, '.swan'), "utf8")); 58 | } catch (error) { 59 | } 60 | -------------------------------------------------------------------------------- /lib/generator/new/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const newProject = require('./new-project'); 4 | const newServer = require('./new-server'); 5 | const readline = require('readline'); 6 | const newClient = require('./new-client'); 7 | const path = require('path'); 8 | const config = require('../../config'); 9 | const utils = require('../../cli-utils'); 10 | const mkdirp = require('mkdirp'); 11 | 12 | module.exports = function executeNew(options) { 13 | options.path = options.path || path.join(process.cwd(), options.projectName); 14 | mkdirp.sync(options.path); 15 | process.chdir(options.path); 16 | 17 | let protocol = utils.getProtocol(options.source); 18 | let repositoryBase = `${protocol}${options.username}/${options.projectName}`; 19 | 20 | options.repositories = { 21 | project: { 22 | directory: '.' 23 | }, 24 | server : { 25 | directory: options.projectName + '-server' 26 | }, 27 | client : { 28 | directory: options.projectName + '-client' 29 | } 30 | }; 31 | 32 | if (options.owner) { 33 | let ownerBase = `${protocol}${options.owner}/${options.projectName}`; 34 | options.repositories.project.upstream = `${ownerBase}.git`; 35 | options.repositories.server.upstream = `${ownerBase}-server.git`; 36 | options.repositories.client.upstream = `${ownerBase}-client.git`; 37 | } else { 38 | options.repositories.project.origin = `${repositoryBase}.git`; 39 | options.repositories.server.origin = `${repositoryBase}-server.git`; 40 | options.repositories.client.origin = `${repositoryBase}-client.git`; 41 | } 42 | 43 | let rl = readline.createInterface({ 44 | input : process.stdin, 45 | output: process.stdout 46 | }); 47 | 48 | rl.setPrompt(''); 49 | 50 | // Simulate ctrl+u to delete the line written previously 51 | rl.write(null, {ctrl: true, name: 'u'}); 52 | rl.write('\n- Setting up project. '); 53 | return newProject(options) 54 | .then(() => { 55 | rl.write('Done!'.bold.green + '\n'); 56 | rl.write('- Setting up server and client, this may take a while:\n'); 57 | rl.write(' - Installing server. (1/2) '); 58 | 59 | return newServer(options); 60 | }) 61 | .then(() => { 62 | rl.write('Done!'.bold.green + '\n'); 63 | rl.write(' - Installing client. (2/2) '); 64 | return newClient(options); 65 | }) 66 | .then(() => { 67 | if (!options.owner) { 68 | return; 69 | } 70 | 71 | rl.write('Done!'.bold.green + '\n'); 72 | rl.write(' - Setting origins.'); 73 | 74 | return utils.setOrigin('project') 75 | .then(() => utils.setOrigin('server')) 76 | .then(() => utils.setOrigin('client')); 77 | }) 78 | .then(() => { 79 | rl.write('Done!'.bold.green + '\n'); 80 | rl.close(); 81 | }) 82 | .catch((error) => { 83 | rl.write('Error!'.bold.red + '\n'); 84 | console.error(error); 85 | rl.close(); 86 | }); 87 | }; 88 | -------------------------------------------------------------------------------- /lib/generator/new/new-client.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const config = require('../../config'); 4 | const utils = require('../../cli-utils'); 5 | const SimpleGit = require('simple-git'); 6 | const path = require('path'); 7 | const fs = require('fs'); 8 | 9 | module.exports = function(options) { 10 | var projectName = options.projectName + '-client'; 11 | var projectPath = path.join(process.cwd(), projectName); 12 | var simpleGit = SimpleGit(projectPath); 13 | var clientSkeletonUri = (config.system.protocol === 'https') ? config.clientSkeleton.https : config.clientSkeleton.ssh; 14 | 15 | fs.mkdirSync(projectPath); 16 | 17 | return new Promise((resolve, reject) => { 18 | simpleGit.clone(clientSkeletonUri, to(projectName)).then(() => { 19 | simpleGit.removeRemote('origin').then(() => { 20 | utils.setRemotes(options.repositories.client) 21 | .then(() => utils.installDependencies(options.repositories.client.directory)) 22 | .then(resolve).catch(reject); 23 | }); 24 | }); 25 | }); 26 | 27 | function to(file) { 28 | return path.join(options.path, file); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /lib/generator/new/new-project.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const fs = require('fs'); 4 | const SimpleGit = require('simple-git'); 5 | const config = require('../../config'); 6 | const path = require('path'); 7 | const utils = require('../../cli-utils'); 8 | 9 | /** 10 | * @param {{projectRepository: ''}} options 11 | * @return {Promise} 12 | */ 13 | module.exports = function(options) { 14 | function to(file) { 15 | return path.join(options.path, file); 16 | } 17 | 18 | return initRepository() 19 | .then(() => { 20 | fs.writeFileSync(to('swan.json'), makeConfig(options)); 21 | fs.writeFileSync(to('.gitignore'), makeGitIgnore(options)); 22 | }) 23 | .then(() => utils.setRemotes(options.repositories.project)); 24 | }; 25 | 26 | function initRepository() { 27 | return new Promise(resolve => { 28 | SimpleGit().init().then(resolve); 29 | }); 30 | } 31 | 32 | function makeGitIgnore(options) { 33 | return [ 34 | options.repositories.client.directory, 35 | options.repositories.server.directory, 36 | '.idea', 37 | '.DS_STORE' 38 | ].join('\n'); 39 | } 40 | 41 | function makeConfig(options) { 42 | let configuration = { 43 | name : options.projectName, 44 | source : options.source, 45 | repositories: options.repositories 46 | }; 47 | 48 | config.project = configuration; 49 | 50 | return JSON.stringify(configuration, null, 2); 51 | } 52 | -------------------------------------------------------------------------------- /lib/generator/new/new-server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const spawn = require('cross-spawn-async'); 6 | const config = require('../../config'); 7 | const utils = require('../../cli-utils'); 8 | const SimpleGit = require('simple-git'); 9 | const cp = require('cp'); 10 | 11 | module.exports = function(options) { 12 | return new Promise(resolve => { 13 | createServer(options, () => { 14 | resolve(); 15 | }); 16 | }); 17 | }; 18 | 19 | function createServer(options, done) { 20 | let projectName = options.repositories.server.directory; 21 | let user = config.git.user; 22 | let projectOptions = ['new', projectName, '--no-frontend', `--author=${user.name}`, `--description=${user.description}`]; 23 | 24 | spawn(config.sails.bin, projectOptions).on('close', () => { 25 | initRepository(path.join(process.cwd(), projectName)) 26 | .then(() => overrideDefaultFiles(options.repositories.server.directory)) 27 | .then(() => utils.setRemotes(options.repositories.server)) 28 | .then(() => addDependencies(options, config.serverDependencies)) 29 | .then(() => addDependencies(options, config.serverDevDependencies, true)) 30 | .then(done); 31 | }); 32 | } 33 | 34 | function initRepository(location) { 35 | return new Promise(resolve => { 36 | SimpleGit(location).init().then(resolve); 37 | }); 38 | } 39 | 40 | function addDependencies(options, dependencies, forDev) { 41 | var directory = options.repositories.server.directory; 42 | var installParam = (forDev) ? '--save-dev' : '--save'; 43 | 44 | return new Promise(resolve => { 45 | spawn('npm', ['install'].concat(dependencies, [installParam]), {cwd: directory}).on('close', resolve); 46 | }); 47 | } 48 | 49 | function overrideDefaultFiles(serverDirectory) { 50 | var mapper = config.templateMapper; 51 | var promises = []; 52 | 53 | for (var i in mapper) { 54 | var src = Object.keys(mapper[i])[0]; 55 | var dest = mapper[i][src]; 56 | 57 | if (dest.indexOf('{client}') > -1) { 58 | continue; 59 | } 60 | 61 | if (dest.indexOf('{server}') > -1) { 62 | dest = dest.replace('{server}', serverDirectory); 63 | } 64 | 65 | promises.push(new Promise((resolve, reject) => { 66 | cp(path.join(config.templatePath, src), path.join(process.cwd(), dest), error => { 67 | if (error) { 68 | return reject(); 69 | } 70 | 71 | resolve(); 72 | }); 73 | })); 74 | } 75 | 76 | return Promise.all(promises); 77 | } 78 | -------------------------------------------------------------------------------- /lib/new.js: -------------------------------------------------------------------------------- 1 | var username = require('./config').git.user.name; 2 | var path = require('path'); 3 | var fs = require('fs'); 4 | var mkdirp = require('mkdirp'); 5 | var SimpleGit = require('simple-git'); 6 | 7 | module.exports = function executeNew(options) { 8 | mkdirp.sync(options.path); 9 | process.chdir(options.path); 10 | fs.mkdirSync('project'); 11 | 12 | var simpleGit = SimpleGit(process.cwd); 13 | 14 | simpleGit.init(); 15 | 16 | fs.mkdirSync('server'); 17 | fs.mkdirSync('client'); 18 | 19 | mkdirp.sync(options.path); 20 | }; 21 | -------------------------------------------------------------------------------- /lib/setup.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const config = require('./config'); 4 | const utils = require('./cli-utils'); 5 | const Github = require('api-github'); 6 | const fs = require('fs'); 7 | const readline = require('readline'); 8 | 9 | let github = new Github({auth: {token: config.system.token}}); 10 | let repositoryName; 11 | 12 | module.exports = function(options) { 13 | repositoryName = options.slug.split('/')[1]; 14 | 15 | if (fs.readdirSync('./').indexOf(repositoryName) > -1) { 16 | return Promise.reject(`A directory named ` + `${repositoryName}`.bold + ` already exists, rename or remove the directory and try again.`); 17 | } 18 | 19 | return setupRepo(options); 20 | }; 21 | 22 | function setupRepo(options, isForked) { 23 | let repositoryPath = `./${repositoryName}`; 24 | let done = ` Done!`.bold.green + `\n`; 25 | let repositoryOwner = options.slug.split('/')[0]; 26 | let retryCount = 0; 27 | let swanConfig; 28 | let serverDirectory; 29 | let clientDirectory; 30 | 31 | let rl = readline.createInterface({ 32 | input : process.stdin, 33 | output: process.stdout 34 | }); 35 | 36 | function checkForkedRepo (forkedRepo, ownerRepo) { 37 | return new Promise((resolve, reject) => { 38 | function checkRepository() { 39 | setTimeout(() => { 40 | if (++retryCount === 30) { 41 | return reject('Repository "' + ownerRepo + '" not forked to "' + forkedRepo + '" after 30 retries. Please try running the same command again.'); 42 | } 43 | 44 | github.repos.find(forkedRepo).then(resolve).catch(checkRepository); 45 | }, 500); 46 | } 47 | 48 | checkRepository(); 49 | }); 50 | } 51 | 52 | function forkRepo(repository, slug) { 53 | var repo = `${config.git.user.name}/${repository}`; 54 | 55 | return github.repos.find(repo) 56 | .then(() => { 57 | return Promise.resolve(); 58 | }) 59 | .catch(error => { 60 | if (error.statusCode !== 404) { 61 | throw error; 62 | } 63 | 64 | return github.repos.fork(slug).then(() => checkForkedRepo(repo, slug)); 65 | }); 66 | } 67 | 68 | rl.setPrompt(''); 69 | rl.write('- Forking project.'); 70 | return forkRepo(repositoryName, options.slug) 71 | .then(() => rl.write(done + '- Cloning project.')) 72 | .then(() => utils.cloneRepo(`${repositoryName}`, repositoryPath, true)) 73 | .then(() => { 74 | try { 75 | swanConfig = JSON.parse(fs.readFileSync(`${repositoryPath}/swan.json`)).repositories; 76 | serverDirectory = swanConfig.server.directory; 77 | clientDirectory = swanConfig.client.directory; 78 | } catch (error) { 79 | throw `Couldn't find swan.json in ${repositoryPath}`; 80 | } 81 | }) 82 | .then(() => rl.write(done + '- Forking server and client.')) 83 | .then(() => forkRepo(serverDirectory, `${repositoryOwner}/${serverDirectory}`)) 84 | .then(() => forkRepo(clientDirectory, `${repositoryOwner}/${clientDirectory}`)) 85 | .then(() => rl.write(done + '- Cloning server and client.')) 86 | .then(() => utils.cloneRepo(`${serverDirectory}`, `${repositoryPath}/${serverDirectory}`, true)) 87 | .then(() => utils.cloneRepo(`${clientDirectory}`, `${repositoryPath}/${clientDirectory}`, true)) 88 | .then(() => rl.write(done + '- Setting remotes.')) 89 | .then(() => utils.setRemotes({upstream: utils.convertProtocol(swanConfig.project.upstream, options.source), directory: repositoryPath})) 90 | .then(() => utils.setRemotes({upstream: utils.convertProtocol(swanConfig.server.upstream, options.source), directory: `${repositoryPath}/${serverDirectory}`})) 91 | .then(() => utils.setRemotes({upstream: utils.convertProtocol(swanConfig.client.upstream, options.source), directory: `${repositoryPath}/${clientDirectory}`})) 92 | .then(() => rl.write(done + '- Installing dependencies, this may take a while:' + `\n` )) 93 | .then(() => rl.write(' - Installing server. (1/2)')) 94 | .then(() => utils.installDependencies(`${repositoryPath}/${serverDirectory}`)) 95 | .then(() => rl.write(done + ' - Installing client. (2/2)')) 96 | .then(() => utils.installDependencies(`${repositoryPath}/${clientDirectory}`)) 97 | .then(() => rl.write(done)) 98 | .catch(error => { 99 | console.log(error); // show original error to figure out what went wrong 100 | throw `Unable to fork ` + `${options.slug}`.bold + `.\nDoes the personal access token have full access to repo?`; 101 | }); 102 | } 103 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "swan-cli", 3 | "version": "0.0.12", 4 | "description": "The CLI for the SWAN stack", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "gulp test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/swanstack/cli.git" 12 | }, 13 | "bin": { 14 | "swan": "./bin/swan.js" 15 | }, 16 | "keywords": [ 17 | "swan", 18 | "cli", 19 | "sails", 20 | "waterline", 21 | "aurelia", 22 | "node", 23 | "spoonx" 24 | ], 25 | "author": "RWOverdijk", 26 | "license": "ISC", 27 | "bugs": { 28 | "url": "https://github.com/swanstack/cli/issues" 29 | }, 30 | "homepage": "https://github.com/swanstack/cli#readme", 31 | "dependencies": { 32 | "api-github": "^1.0.0", 33 | "colors": "^1.1.2", 34 | "commander": "^2.9.0", 35 | "cp": "^0.2.0", 36 | "cross-spawn-async": "^2.2.2", 37 | "ini": "^1.3.4", 38 | "inquirer": "^0.11.2", 39 | "lodash": "^4.0.0", 40 | "mkdirp": "^0.5.1", 41 | "rc": "^1.1.6", 42 | "sails": "^0.11.4", 43 | "simple-git": "^1.21.0", 44 | "validator": "^4.5.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /templates/server/.sailsrc: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "grunt" : false, 4 | "views" : false, 5 | "session": false, 6 | "csrf" : false 7 | }, 8 | "generators": { 9 | "modules": {} 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /templates/server/api/controllers/CountController.js: -------------------------------------------------------------------------------- 1 | var actionUtil = require('sails/lib/hooks/blueprints/actionUtil'); 2 | 3 | module.exports = { 4 | count : function (req, res) { 5 | var Model = sails.models[req.param('modelName')], 6 | criteria = actionUtil.parseCriteria(req); 7 | 8 | if (!Model) { 9 | return res.badRequest('invalid_parameter'); 10 | } 11 | 12 | delete criteria.modelName; 13 | 14 | Model.count(criteria, function (error, response) { 15 | if (error) { 16 | return res.serverError('database_error', error); 17 | } 18 | 19 | res.ok({count: response}); 20 | }); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /templates/server/config/cors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Cross-Origin Resource Sharing (CORS) Settings 3 | * (sails.config.cors) 4 | * 5 | * CORS is like a more modern version of JSONP-- it allows your server/API 6 | * to successfully respond to requests from client-side JavaScript code 7 | * running on some other domain (e.g. google.com) 8 | * Unlike JSONP, it works with POST, PUT, and DELETE requests 9 | * 10 | * For more information on CORS, check out: 11 | * http://en.wikipedia.org/wiki/Cross-origin_resource_sharing 12 | * 13 | * Note that any of these settings (besides 'allRoutes') can be changed on a per-route basis 14 | * by adding a "cors" object to the route configuration: 15 | * 16 | * '/get foo': { 17 | * controller: 'foo', 18 | * action: 'bar', 19 | * cors: { 20 | * origin: 'http://foobar.com,https://owlhoot.com' 21 | * } 22 | * } 23 | * 24 | * For more information on this configuration file, see: 25 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.cors.html 26 | * 27 | */ 28 | 29 | module.exports.cors = { 30 | 31 | /*************************************************************************** 32 | * * 33 | * Allow CORS on all routes by default? If not, you must enable CORS on a * 34 | * per-route basis by either adding a "cors" configuration object to the * 35 | * route config, or setting "cors:true" in the route config to use the * 36 | * default settings below. * 37 | * * 38 | ***************************************************************************/ 39 | 40 | allRoutes: true, 41 | 42 | /*************************************************************************** 43 | * * 44 | * Which domains which are allowed CORS access? This can be a * 45 | * comma-delimited list of hosts (beginning with http:// or https://) or * 46 | * "*" to allow all domains CORS access. * 47 | * * 48 | ***************************************************************************/ 49 | 50 | origin: '*', 51 | 52 | /*************************************************************************** 53 | * * 54 | * Allow cookies to be shared for CORS requests? * 55 | * * 56 | ***************************************************************************/ 57 | 58 | // credentials: true, 59 | 60 | /*************************************************************************** 61 | * * 62 | * Which methods should be allowed for CORS requests? This is only used in * 63 | * response to preflight requests (see article linked above for more info) * 64 | * * 65 | ***************************************************************************/ 66 | 67 | methods: 'GET, POST, PUT, DELETE, OPTIONS, HEAD', 68 | 69 | /*************************************************************************** 70 | * * 71 | * Which headers should be allowed for CORS requests? This is only used in * 72 | * response to preflight requests. * 73 | * * 74 | ***************************************************************************/ 75 | 76 | headers: 'content-type,Authorization' 77 | 78 | }; 79 | -------------------------------------------------------------------------------- /templates/server/config/models.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Default model configuration 3 | * (sails.config.models) 4 | * 5 | * Unless you override them, the following properties will be included 6 | * in each of your models. 7 | * 8 | * For more info on Sails models, see: 9 | * http://sailsjs.org/#!/documentation/concepts/ORM 10 | */ 11 | 12 | module.exports.models = { 13 | 14 | /*************************************************************************** 15 | * * 16 | * Your app's default connection. i.e. the name of one of your app's * 17 | * connections (see `config/connections.js`) * 18 | * * 19 | ***************************************************************************/ 20 | // connection: 'localDiskDb', 21 | 22 | /*************************************************************************** 23 | * * 24 | * How and whether Sails will attempt to automatically rebuild the * 25 | * tables/collections/etc. in your schema. * 26 | * * 27 | * See http://sailsjs.org/#!/documentation/concepts/ORM/model-settings.html * 28 | * * 29 | ***************************************************************************/ 30 | 31 | migrate: 'alter' 32 | }; 33 | -------------------------------------------------------------------------------- /templates/server/config/routes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Route Mappings 3 | * (sails.config.routes) 4 | * 5 | * Your routes map URLs to views and controllers. 6 | * 7 | * If Sails receives a URL that doesn't match any of the routes below, 8 | * it will check for matching files (images, scripts, stylesheets, etc.) 9 | * in your assets directory. e.g. `http://localhost:1337/images/foo.jpg` 10 | * might match an image file: `/assets/images/foo.jpg` 11 | * 12 | * Finally, if those don't match either, the default 404 handler is triggered. 13 | * See `api/responses/notFound.js` to adjust your app's 404 logic. 14 | * 15 | * Note: Sails doesn't ACTUALLY serve stuff from `assets`-- the default Gruntfile in Sails copies 16 | * flat files from `assets` to `.tmp/public`. This allows you to do things like compile LESS or 17 | * CoffeeScript for the front-end. 18 | * 19 | * For more information on configuring custom routes, check out: 20 | * http://sailsjs.org/#!/documentation/concepts/Routes/RouteTargetSyntax.html 21 | */ 22 | 23 | module.exports.routes = { 24 | 25 | /*************************************************************************** 26 | * * 27 | * If a request to a URL doesn't match any of the custom routes above, it * 28 | * is matched against Sails route blueprints. See `config/blueprints.js` * 29 | * for configuration options and examples. * 30 | * * 31 | ***************************************************************************/ 32 | 33 | 'GET /:modelName/count': 'CountController.count' 34 | }; 35 | --------------------------------------------------------------------------------