├── .coveralls.yml ├── .gitignore ├── .npmignore ├── .travis.yml ├── README.md ├── assets └── 68747470733a2f2f73332e65752d63656e7472616c2d312e616d617a6f6e6177732e636f6d2f6d657368696e2f7075626c69632f747765652e696f2e706e67.png ├── bin ├── twee-translate.js └── twee.js ├── configs └── default.js ├── index.js ├── package.json ├── templates └── application │ ├── .bowerrc │ ├── .gitignore │ ├── .npmignore │ ├── Gruntfile.js │ ├── application.js │ ├── bower.json │ ├── configs │ ├── development │ │ └── twee.js │ ├── grunt.js │ ├── modules.js │ ├── production │ │ └── twee.js │ ├── testing │ │ └── twee.js │ └── twee.js │ ├── i18n │ ├── en.json │ └── ru.json │ ├── modules │ └── _Twee-MNT_ │ │ ├── assets │ │ ├── css │ │ │ ├── .empty │ │ │ └── _Twee-MNT-LC_-module.css │ │ ├── fonts │ │ │ └── .empty │ │ ├── img │ │ │ └── .empty │ │ └── js │ │ │ ├── .empty │ │ │ └── _Twee-MNT-LC_-module.js │ │ ├── controllers │ │ └── _Twee-MNT_Controller.js │ │ ├── extensions │ │ └── _Twee-MNT_Extension.js │ │ ├── middleware │ │ ├── LanguageMiddleware.js │ │ └── _Twee-MNT_Middleware.js │ │ ├── models │ │ └── .empty │ │ ├── params │ │ ├── .empty │ │ └── RangeParam.js │ │ ├── setup │ │ ├── configs │ │ │ ├── common.js │ │ │ ├── development │ │ │ │ └── common.js │ │ │ ├── grunt.js │ │ │ ├── production │ │ │ │ └── common.js │ │ │ └── testing │ │ │ │ └── common.js │ │ └── setup.js │ │ └── views │ │ └── pages │ │ └── _Twee-MNT_ │ │ ├── bootstrap.html │ │ └── index.html │ ├── package.json │ ├── public │ ├── css │ │ ├── bootstrap.css │ │ └── custom.css │ ├── img │ │ └── favicon.ico │ └── js │ │ └── ie10-viewport-fix.js │ ├── var │ ├── log │ │ └── access.json │ └── ssl │ │ ├── localhost.crt │ │ ├── localhost.csr │ │ └── localhost.key │ └── views │ ├── common │ ├── pages │ │ └── 404.html │ └── partials │ │ ├── footer.base.html │ │ └── header.base.html │ └── layouts │ └── layout.base.html ├── tests ├── fixtures │ └── framework.json └── framework.js ├── utils ├── application-generator.js └── extend.js └── view ├── tags ├── include-language.js └── translate.js └── templates └── 404.html /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-pro 2 | repo_token: ZvrMtwCAhmHUXP0GAfkxnI5pQ40BUQic5 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | atlassian-ide-plugin.xml 3 | .idea 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | atlassian-ide-plugin.xml 3 | .idea 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: 3 | - npm 4 | node_js: 5 | - "0.10" 6 | install: 7 | - npm install 8 | - npm install -g mocha 9 | script: 10 | - npm test 11 | before_install: 12 | - "rm -rf ~/.npm" 13 | - "mkdir ~/.npm" 14 | 15 | notifications: 16 | webhooks: 17 | urls: 18 | - https://webhooks.gitter.im/e/cc6bb36f383e1af68df3 19 | - https://webhooks.gitter.im/e/d9a9f2f2bceea8a3d700 20 | on_success: change # options: [always|never|change] default: always 21 | on_failure: always # options: [always|never|change] default: always 22 | on_start: false # default: false 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Twee.io Framework - Not supported anymore. 2 | ==== 3 | 4 | *- Modern MVC Framework for Node.js and io.js based on Express.js for professionals with deadlines in enterprise* 5 | 6 | ![Twee.io Logo](https://raw.githubusercontent.com/tweeio/twee-framework/master/assets/68747470733a2f2f73332e65752d63656e7472616c2d312e616d617a6f6e6177732e636f6d2f6d657368696e2f7075626c69632f747765652e696f2e706e67.png) 7 | 8 | [![Travis Build Status](https://travis-ci.org/tweeio/twee-framework.svg)](https://travis-ci.org/tweeio/twee-framework) 9 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/tweeio/twee-framework?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 10 | [![Dependency Status](https://gemnasium.com/mesin/twee.svg)](https://github.com/tweeio/twee-framework) 11 | [![npm version](https://badge.fury.io/js/twee.svg)](http://npmjs.org/mesin/twee) 12 | [![npm](https://img.shields.io/npm/dm/localeval.svg)](https://github.com/tweeio/twee-framework) 13 | [![npm](https://img.shields.io/npm/l/express.svg)](https://github.com/tweeio/twee-framework) 14 | 15 | Framework is no logner supported 16 | ==== 17 | 18 | It means, i don't create patches, don't support twee.io site. It's over. 19 | 20 | What is Twee.IO? 21 | ==== 22 | 23 | *Big applications with strong scalable and predictable structure with fast and efficient Express.js core under the hood - is all about twee.io* 24 | 25 | Raw `Node.js` application are *very* low-level. You need to handle all the requests by your own. This is problem. 26 | 27 | Another problem is when you try to use very big frameworks with tons of agreements and conventions. Most of today's frameworks (full-stack or rails like) - are big and mostly like a black boxes. You never know how they work inside! And if you know - then you're probably simply one of it's developers :-) 28 | 29 | If you like Express.js but need a layer to get started asap with great code structure and cool predictable core and nice conventions for big application - then twee.io is probably your choice. 30 | 31 | It is not like a black box like another frameworks. It's core is pretty simple and small. But it allows you to write code in OOP and MVC maneer. It has conventions that will protect your code from chaos that is one of attributes in big projects and applications. 32 | 33 | `Express.js`. It downloads 10x times more than all the other frameworks, and it says about it's quality and strong concepts. And it could be perfect, but you still need some tricks and efforts to build structure on it that can be great for scalable big enterprise-ready applications. 34 | 35 | Enterprise-ready means not only good stable code that could be safe and could work weeks and years. It also means good predictable structure of all the application parts, how easy and understandable will be to manage big complex application in hard enterprise environment. 36 | 37 | `Twee.io` tries to solve these problems 38 | 39 | Installation 40 | ==== 41 | 42 | 43 | Installing twee to use it's global commands 44 | 45 | ``` 46 | $ npm install -g twee 47 | ``` 48 | 49 | Generating application in `application` folder 50 | 51 | ``` 52 | $ twee 53 | ``` 54 | 55 | Entering into generated application's folder 56 | 57 | ``` 58 | $ cd application 59 | ``` 60 | 61 | Installing dependencies 62 | 63 | ``` 64 | $ npm install 65 | ``` 66 | 67 | Downloading client-side JS requirements and compiling assets 68 | 69 | ``` 70 | $ npm run-script assets 71 | ``` 72 | 73 | Starting server under `http://127.0.0.1:3000` 74 | 75 | ``` 76 | $ npm start 77 | ``` 78 | 79 | Resources 80 | ==== 81 | 82 | - [Official Site](http://twee.io) 83 | - [Detailed Installation Instructions](http://twee.io/docs/installation.html) 84 | - [Installation Video](https://www.youtube.com/watch?v=3JxDQ0p0Fyg) 85 | 86 | Links 87 | ==== 88 | 89 | - [Gitter.im](https://gitter.im/tweeio/twee-framework) 90 | - [Facebook](https://www.facebook.com/pages/tweeio/1574029616142606) 91 | - [LinkedIn](https://www.linkedin.com/groups/Tweeio-6931666) 92 | - [Twitter](https://twitter.com/tweeio) 93 | - [Google+](https://plus.google.com/u/0/117917320555327401329/about) 94 | - Feel free to write to author: Dmitri Meshin 95 | 96 | 97 | Useful Extensions 98 | ==== 99 | 100 | - [PassportJS Support (not all strategies)](https://github.com/tweeio/twee-passport-extension) 101 | - [Mongoose ODM / MongoDB](https://github.com/tweeio/twee-mongoose-extension) 102 | - [Socket.IO Integration](https://github.com/tweeio/twee-socket-extension) 103 | - [XML Response](https://github.com/tweeio/twee-xml-response-extension) 104 | - [View Engines](https://github.com/tweeio/twee-view-extension) 105 | - [Redis Driven Session](https://github.com/tweeio/twee-session-extension) 106 | - [Winston Logger](https://github.com/tweeio/twee-logging-extension/) 107 | - [I18n-node Integration](https://github.com/tweeio/twee-i18n-extension) 108 | - [Cookies Support](https://github.com/tweeio/twee-cookies-extension) 109 | - [HTML 2-Step Compression](https://github.com/tweeio/twee-compressor-extension) 110 | 111 | 112 | LICENSE 113 | ==== 114 | 115 | MIT 116 | -------------------------------------------------------------------------------- /assets/68747470733a2f2f73332e65752d63656e7472616c2d312e616d617a6f6e6177732e636f6d2f6d657368696e2f7075626c69632f747765652e696f2e706e67.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tweeio/twee-framework/8aeca10b54cd01a6fbc542581c5f7f40b5dc88f2/assets/68747470733a2f2f73332e65752d63656e7472616c2d312e616d617a6f6e6177732e636f6d2f6d657368696e2f7075626c69632f747765652e696f2e706e67.png -------------------------------------------------------------------------------- /bin/twee-translate.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var extend = require('../utils/extend') 3 | , fs = require('fs') 4 | , path = require('path') 5 | , commander = require('commander') 6 | , sha1 = require('sha1'); 7 | 8 | var processLoadCharLast = ''; 9 | 10 | /** 11 | * Return progress char 12 | * @returns {string} 13 | */ 14 | function getProcessLoadChar() { 15 | processLoadCharLast = processLoadCharLast || '[-]'; 16 | if (processLoadCharLast == '[-]') { 17 | processLoadCharLast = '[\\]'; 18 | } else if (processLoadCharLast == '[\\]') { 19 | processLoadCharLast = '[|]'; 20 | } else if (processLoadCharLast == '[|]') { 21 | processLoadCharLast = '[/]'; 22 | } else if (processLoadCharLast == '[/]') { 23 | processLoadCharLast = '[-]'; 24 | } 25 | 26 | return processLoadCharLast; 27 | } 28 | 29 | /** 30 | * Translation instructions regex 31 | * @type {RegExp} 32 | */ 33 | var trRegEx = /(tr\('([^'](\\\'){0,})+'\))|(tr\("([^\"](\\\"){0,})+"\))/gi; 34 | 35 | /** 36 | * Search for all the entries of tr() in provided directories/files 37 | * @param directories 38 | * @param safe 39 | * @returns {{}} 40 | */ 41 | function findTranslations(directories, safe) { 42 | var translations = {}; 43 | 44 | if (typeof directories == 'string') { 45 | directories = [directories]; 46 | } 47 | 48 | if (!directories instanceof Array) { 49 | throw new Error('Directories should be string or array'); 50 | } 51 | 52 | try { 53 | directories.forEach(function(directory){ 54 | process.stdout.write('\rCollecting new translations from code.. ' + getProcessLoadChar()); 55 | if (!fs.existsSync(directory)) { 56 | return; 57 | } 58 | var fst = fs.statSync(directory); 59 | if (fst.isDirectory()) { 60 | var files = fs.readdirSync(directory); 61 | if (files) { 62 | files.forEach(function(_file){ 63 | var subtrans = findTranslations(path.join(directory, _file)); 64 | if (typeof subtrans == 'object') { 65 | translations = extend(true, translations, subtrans); 66 | } 67 | }); 68 | } 69 | } else if (fst.isFile()) { 70 | var contents = fs.readFileSync(directory).toString(); 71 | var matches = contents.match(trRegEx); 72 | var reserved = false; 73 | 74 | if (matches) { 75 | //console.log(matches, directory); 76 | matches.forEach(function(tr){ 77 | //console.log(tr); 78 | var openStr = '' 79 | , translation = '' 80 | , prevChr; 81 | for (var i = 0; i < tr.length; i++){ 82 | prevChr = chr; 83 | var chr = tr[i]; 84 | if (!openStr && (chr == '"' || chr == "'")) { 85 | openStr = chr; 86 | } else if (openStr 87 | && (chr == openStr) 88 | && prevChr != "\\" 89 | && tr[i+1] && tr[i+1] === ')' 90 | ) { 91 | if (translation.indexOf('TT#') === 0) { 92 | return; 93 | } 94 | if (!reserved && safe) { 95 | fs.copySync(directory, directory + '.tr.src'); 96 | reserved = true; 97 | } 98 | translation = translation.replace("\\\'", "\'").replace("\\\"", "\""); 99 | translation = translation.replace("\\\'", "\'"); 100 | var translationKey = 'TT#' + parseInt(sha1(translation), 16).toString(36).replace('.', '').replace('e+', ''); 101 | translations[translationKey] = translation; 102 | contents = contents.replace(tr, "tr('" + translationKey + "')"); 103 | return; 104 | } else if (openStr) { 105 | translation += chr; 106 | } 107 | } 108 | }); 109 | fs.writeFileSync(directory, contents); 110 | } 111 | } 112 | }); 113 | } catch (e) { 114 | console.log(e.stack || e.toString()); 115 | process.exit(1); 116 | } 117 | 118 | return translations; 119 | } 120 | 121 | /** 122 | * Returning array from comma-separated list 123 | * @param strList String 124 | * @returns {Array|*} 125 | */ 126 | function list(strList) { 127 | return String(strList).split(','); 128 | } 129 | 130 | /** 131 | * Getting all the modules translations from all the modules for all the locales 132 | * @returns {{}} 133 | */ 134 | function getMergedModulesTranslations() { 135 | var modulesDirName = path.join(process.cwd(), 'modules'); 136 | var modulesNames = fs.readdirSync(modulesDirName); 137 | var translations = {}; 138 | modulesNames.forEach(function(moduleName){ 139 | var moduleI18nFolder = path.join(process.cwd(), 'modules', moduleName, 'i18n'); 140 | if (!fs.existsSync(moduleI18nFolder)) { 141 | return; 142 | } 143 | 144 | var moduleI18nFiles = fs.readdirSync(moduleI18nFolder); 145 | if (moduleI18nFiles.length) { 146 | moduleI18nFiles.forEach(function(moduleI18nFile){ 147 | 148 | var fst = fs.statSync(moduleI18nFolder + '/' + moduleI18nFile); 149 | if (!fst.isFile()) { 150 | return; 151 | } 152 | 153 | var locale = moduleI18nFile.replace('.json', ''); 154 | 155 | moduleI18nFile = moduleI18nFolder + '/' + moduleI18nFile; 156 | 157 | translations[locale] = translations[locale] || {}; 158 | translations[locale] = extend(true, translations[locale], require(moduleI18nFile)); 159 | }); 160 | } 161 | }); 162 | 163 | return translations; 164 | } 165 | 166 | /** 167 | * Returning all the application translations as locale hash 168 | * @returns {{}} 169 | */ 170 | function getApplicationTranslations() { 171 | var applicationI18nFolder = path.join(process.cwd(), 'i18n') 172 | , applicationTranslations = {}; 173 | 174 | if (!fs.existsSync(applicationI18nFolder)) { 175 | fs.mkdirSync(applicationI18nFolder); 176 | // No translations was in application folder. Just return empty hash 177 | return {}; 178 | } 179 | 180 | // Loading translations 181 | var translationFiles = fs.readdirSync(applicationI18nFolder); 182 | if (translationFiles.length) { 183 | translationFiles.forEach(function(translationFile){ 184 | var fst = fs.statSync(applicationI18nFolder + '/' + translationFile); 185 | if (fst.isFile()) { 186 | var locale = translationFile.replace('.json', ''); 187 | translationFile = applicationI18nFolder + '/' + translationFile; 188 | applicationTranslations[locale] = require(translationFile); 189 | } 190 | }); 191 | } 192 | 193 | return applicationTranslations; 194 | } 195 | 196 | commander 197 | //.version(require('../package.json').version) 198 | .option('-d, --directory ', 'Scan list of folders for `tr(..)` instructions (default: ["modules", "views"])', ['modules', 'views']) 199 | .option('-l, --locale ', 'Default locale (default: en)', 'en') 200 | .option('-s, --safe ', 'Copy original file to *.tr.src before changing it', true) 201 | .parse(process.argv); 202 | 203 | // ---------------------------------------------------------------------------- 204 | 205 | var modulesTranslations = {}; 206 | 207 | // First of all merge all the modules translations into application translations 208 | console.log('Loading all the translates from all modules..'); 209 | modulesTranslations = getMergedModulesTranslations(); 210 | console.log('Done.'); 211 | 212 | console.log('Collecting main application translations..'); 213 | var applicationTranslations = getApplicationTranslations(); 214 | console.log('Done.'); 215 | 216 | var codeTranslations = findTranslations(list(commander.directory), commander.safe); 217 | console.log(); 218 | console.log('Done.'); 219 | 220 | console.log('Processing everything..'); 221 | var translations = {}; 222 | translations[commander.locale] = codeTranslations; 223 | 224 | translations = extend(true, translations, applicationTranslations); 225 | 226 | // Extending application translations with modules translations (which was ready long before) 227 | translations = extend(true, translations, modulesTranslations); 228 | 229 | // Extending all the other locales with main locale 230 | for (l in translations) { 231 | if (l == commander.locale) { 232 | continue; 233 | } 234 | 235 | //translations[l] = extend(true, translations[l], translations[commander.locale]); 236 | for (tr in translations[commander.locale]) { 237 | if (!translations[l][tr]) { 238 | translations[l][tr] = translations[commander.locale][tr]; 239 | } 240 | } 241 | } 242 | 243 | // Order all the hashes for all locales by ASC for hash searching optimization 244 | /*var sortedTranslations = {}; 245 | for (l in translations) { 246 | sortedTranslations[l] = {}; 247 | var sortedKeys = Object.keys(translations[l]).sort(function(a, b) {return (a < b)}); 248 | for (var i = 0; i < sortedKeys.length; i++) { 249 | sortedTranslations[l][sortedKeys[i]] = translations[l][sortedKeys[i]]; 250 | } 251 | }*/ 252 | //translations = sortedTranslations; 253 | 254 | // Saving all the translations into application folder 255 | for (var l in translations) { 256 | var applicationI18nFile = path.join(process.cwd(), 'i18n', l + '.json'); 257 | fs.writeFileSync(applicationI18nFile, JSON.stringify(translations[l], null, '\t')); 258 | } 259 | 260 | console.log('Done.'); -------------------------------------------------------------------------------- /bin/twee.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // Load required modules 4 | var fs = require('fs') 5 | , path = require('path') 6 | , colors = require('colors') 7 | , commander = require('commander') 8 | , appGenerator = require('../utils/application-generator') 9 | , generateNewApplication = appGenerator.generateApplication 10 | , moduleOrApplicationGeneration = appGenerator.moduleOrApplicationGeneration; 11 | 12 | /** 13 | * Dispatch commands from command line 14 | */ 15 | commander 16 | .version(require('../package.json').version) 17 | .option('-a, --application ', 'Generate application in specified folder (default: application)', 'application') 18 | .option('-m, --module ', 'Generate new module structure in application', 'Default') 19 | .option('-f, --folder ', 'Where to generate? (default: commander.folder)', process.cwd()) 20 | .option('-e, --engine ', 'What template engine to use in views', 'swig') 21 | .parse(process.argv); 22 | 23 | var defaultOptions = { 24 | tweeVersion: require('../package').version, 25 | tweeVersionTemplate: "__TWEE_VERSION__", 26 | tweeVersionTemplateRegEx: /__TWEE_VERSION__/gi, 27 | tweeAppNameTemplate: "_Twee-App-Name_", 28 | tweeAppNameTemplateRegEx: /_Twee-App-Name_/gi, 29 | moduleName: 'Default', 30 | moduleNameLowerCase: 'default', 31 | applicationName: 'application', 32 | applicationFolder: commander.folder + '/' + this.applicationName, 33 | tweeModuleNameTemplate: '_Twee-MNT_', 34 | tweeModuleNameTemplateLowerCased: '_Twee-MNT-LC_', 35 | tweeModuleNameTemplateRegEx: /_Twee-MNT_/gi, 36 | tweeModuleNameTemplateLowerCasedRegEx: /_Twee-MNT-LC_/gi, 37 | applicationSourceFolder: path.join(__dirname, '../templates/application/'), 38 | generateOnlyModule: false 39 | }; 40 | 41 | moduleOrApplicationGeneration(defaultOptions, commander); 42 | console.log(colors.cyan('[TWEE] ') + 'Generating Module: ' + colors.cyan(defaultOptions.moduleName)); 43 | generateNewApplication(defaultOptions); 44 | console.log(colors.cyan('[TWEE] ') + 'Done.'); 45 | 46 | -------------------------------------------------------------------------------- /configs/default.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extensions": { 3 | "HTTP Parser": { 4 | "module": "twee-http-parser-extension" 5 | }, 6 | 7 | "Winston Logger": { 8 | "module": "twee-logging-extension" 9 | }, 10 | 11 | "`Powered With` Header": { 12 | "module": "twee-powered-extension" 13 | }, 14 | 15 | "Static Files": { 16 | "module": "twee-static-extension" 17 | }, 18 | 19 | "HTML Compressor": { 20 | "module": "twee-compressor-extension" 21 | }, 22 | 23 | "View Engines": { 24 | "module": "twee-view-extension" 25 | }, 26 | 27 | "View Helpers": { 28 | "module": "twee-view-extension/helpers" 29 | }, 30 | 31 | "i18n": { 32 | "module": "twee-i18n-extension" 33 | } 34 | }, 35 | 36 | "options": { 37 | "errorPages": { 38 | "404": { 39 | "viewTemplate": __dirname + "/../templates/pages/404.html" 40 | } 41 | } 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Twee Framework Functionality 3 | */ 4 | 5 | "use strict"; 6 | 7 | var express = require('express') 8 | , debug = require('debug')('twee.io') 9 | , path = require('path') 10 | , colors = require('colors/safe') 11 | , fs = require('fs') 12 | , extend = require('./utils/extend') 13 | , events = require('events'); 14 | 15 | /** 16 | * twee Framework Class 17 | * @constructor 18 | */ 19 | 20 | function twee() { 21 | /** 22 | * Express Application Instance 23 | * @type express() 24 | * @private 25 | */ 26 | this.__app = express(); 27 | 28 | /** 29 | * Flag that shows that framework already bootstrapped 30 | * @type {boolean} 31 | * @private 32 | */ 33 | this.__bootstraped = false; 34 | 35 | /** 36 | * Base Directory for including all the modules 37 | * @type {string} 38 | * @private 39 | */ 40 | this.__baseDirectory = ''; 41 | 42 | /** 43 | * Environment 44 | * @type {string} 45 | * @private 46 | */ 47 | this.__env = 'production'; 48 | 49 | /** 50 | * Configuration object. Stores all the modules configs and core config 51 | * @type {{}} 52 | * @private 53 | */ 54 | this.__config = {}; 55 | 56 | /** 57 | * Default Module Options 58 | * @type {{disabled: boolean, prefix: string, disableViewEngine: boolean}} 59 | * @private 60 | */ 61 | this.__defaultModuleOptions = { 62 | disabled: false, 63 | prefix: '/' 64 | }; 65 | 66 | /** 67 | * Registry of extensions to avoid infinity recursion 68 | * @type {{}} 69 | * @private 70 | */ 71 | this.__extensionsRegistry = {}; 72 | 73 | /** 74 | * View helpers registry 75 | * @type {} 76 | */ 77 | this.helper = {}; 78 | 79 | /** 80 | * It allows us to call in views: 81 | * {{ helper.foo(..) }} or {{ helper['foo'](...) }} 82 | * BUT! NOT: {{ foo(...) }} because it can be overwritten by usual passed variables. 83 | * So we should protect each of them. We don't want to care about this. So we'll protect only `helper` name. 84 | * @type {*} 85 | */ 86 | this.__app.locals.helper = this.helper; 87 | 88 | /** 89 | * Extending one config from another 90 | * @type {*|exports} 91 | */ 92 | this.extend = extend; 93 | 94 | /** 95 | * HTTP Server instance 96 | * @type {null} 97 | * @private 98 | */ 99 | this.__http = null; 100 | 101 | /** 102 | * HTTPS Server instance 103 | * @type {null} 104 | * @private 105 | */ 106 | this.__https = null; 107 | 108 | /** 109 | * For recursy control 110 | * @type {number} 111 | * @private 112 | */ 113 | this.__extensionsRecursyDeepness = 0; 114 | 115 | /** 116 | * Registry of different objects 117 | * @type {{}} 118 | * @private 119 | */ 120 | this.__registry = {}; 121 | 122 | /** 123 | * Registry of middleware lists that are used in dispatch process of Express 124 | * @type {{}} 125 | * @private 126 | */ 127 | this.__middlewareListRegistry = {}; 128 | } 129 | 130 | /** 131 | * Setting prototype of framework 132 | */ 133 | twee.prototype.__proto__ = new events.EventEmitter(); 134 | 135 | /** 136 | * Getting Application Instance 137 | */ 138 | twee.prototype.getApplication = function() { 139 | return this.__app; 140 | }; 141 | 142 | /** 143 | * Logging message to console 144 | * @param message 145 | * @returns {twee} 146 | */ 147 | twee.prototype.log = function(message) { 148 | debug(colors.cyan('[WORKER:' + process.pid + '] ') + colors.yellow(message)); 149 | return this; 150 | }; 151 | 152 | /** 153 | * Logging error to console 154 | * @param message 155 | * @returns {twee} 156 | */ 157 | twee.prototype.error = function(message) { 158 | debug(colors.cyan('[WORKER:' + process.pid + '][ERROR] ') + colors.red(message.stack || message.toString())); 159 | return this; 160 | }; 161 | 162 | /** 163 | * Bootstrapping application 164 | * @param options Object 165 | * @returns {twee} 166 | */ 167 | twee.prototype.Bootstrap = function(options) { 168 | if (this.__bootstraped) { 169 | return this; 170 | } 171 | 172 | var self = this; 173 | 174 | options = options || {}; 175 | 176 | // This is default config state. It can be overwritten before running 177 | options = extend(true, { 178 | modules: 'configs/modules', 179 | tweeConfig: 'configs/twee' 180 | }, options); 181 | 182 | process.on('uncaughtException', function(err) { 183 | self.error('Caught exception: ' + err.stack || err.toString()); 184 | //console.trace(); 185 | self.emit('twee.Exception', err, self); 186 | }); 187 | 188 | process.on('SIGINT', function(){ 189 | // Generate event for all the modules to free some resources 190 | self.emit('twee.Exit'); 191 | self.log('Exiting'); 192 | self.__http && self.__http.close(); 193 | self.__https && self.__http.close(); 194 | process.exit(0); 195 | }); 196 | 197 | try { 198 | this.__bootstrap(options); 199 | } catch (err) { 200 | throw new Error('Bootstrap Error: ' + err.stack || err.toString()); 201 | } 202 | 203 | this.__bootstraped = true; 204 | return this; 205 | }; 206 | 207 | /** 208 | * Common bootstrap process is wrapped with exception catcher 209 | * @param options 210 | * @returns {twee} 211 | * @private 212 | */ 213 | twee.prototype.__bootstrap = function(options) { 214 | var self = this; 215 | 216 | this.emit('twee.Bootstrap.Start'); 217 | 218 | if (!options || !options.modules) { 219 | throw new Error('Modules field should not be empty!'); 220 | } 221 | 222 | var modules = options.modules; 223 | 224 | // If this is file path with modules configuration - then load it 225 | if (typeof modules == 'string') { 226 | modules = this.Require(modules); 227 | this.emit('twee.Bootstrap.ModulesList', modules); 228 | } 229 | 230 | if (typeof modules != 'object') { 231 | throw new Error('Modules should be file path or Object'); 232 | } 233 | 234 | // Loading default framework configuration 235 | var tweeConfig = require('./configs/default'); 236 | this.emit('twee.Bootstrap.DefaultConfig', tweeConfig); 237 | 238 | // Extending framework configuration during Bootstrapping 239 | if (options.tweeConfig) { 240 | if (typeof options.tweeConfig == 'string') { 241 | var tweeConfigFullPath = path.join(this.__baseDirectory, options.tweeConfig); 242 | try { 243 | var loadedTweeConfig = require(tweeConfigFullPath); 244 | tweeConfig = extend(true, tweeConfig, loadedTweeConfig); 245 | this.emit('twee.Bootstrap.ExtendedConfig', tweeConfig); 246 | } catch (e) { 247 | this.log('[WARNING] No valid twee main config specified! Using default values.'); 248 | } 249 | 250 | // Extending config with environment-specified configuration 251 | var directory = path.dirname(tweeConfigFullPath) 252 | , configFile = path.basename(tweeConfigFullPath) 253 | , environmentConfig = directory + '/' + this.__env + '/' + configFile; 254 | try { 255 | var envTweeConfig = require(environmentConfig); 256 | tweeConfig = extend(true, tweeConfig, envTweeConfig); 257 | this.emit('twee.Bootstrap.ExtendedEnvConfig', tweeConfig); 258 | } catch (err) { 259 | // Nothing to do here. Just no config for environment 260 | } 261 | } 262 | } 263 | 264 | // Setting up framework config 265 | this.__config.twee = tweeConfig; 266 | this.emit('twee.Bootstrap.Config', tweeConfig); 267 | 268 | // Setting package information 269 | this.__config.twee.package = this.Require('package'); 270 | this.emit('twee.Bootstrap.PackageInfo'); 271 | 272 | // Extension specific configs 273 | this.__config.twee.extension = this.__config.twee.extension || {}; 274 | 275 | // Setting framework object as global 276 | global.twee = this; 277 | 278 | // Pre-loading all the modules configs, routes, patterns and other stuff 279 | this.LoadModulesInformation(modules); 280 | this.emit('twee.Bootstrap.ModulesInformationLoaded'); 281 | 282 | // Load enabled twee core extensions 283 | this.emit('twee.Bootstrap.TweeExtensionsPreLoad'); 284 | this.LoadExtensions(this.getConfig('twee:extensions', {}), null); 285 | this.emit('twee.Bootstrap.TweeExtensionsLoaded'); 286 | 287 | // All the extensions that execute random not-standard or standard code - runs before everything 288 | this.LoadModulesExtensions(); 289 | this.emit('twee.Bootstrap.ModulesExtensionsLoaded'); 290 | 291 | // Lifting the server because some extensions could require http-server object 292 | // before all the routes has been setup. for example socket.io 293 | this.__createServer(); 294 | 295 | // Head middlewares are module-specific and used to initialize something into req or res objects 296 | this.LoadModulesMiddleware('head'); 297 | this.emit('twee.Bootstrap.ModulesHeadMiddlewareLoaded'); 298 | 299 | // Controllers is the place where all the business logic is concentrated 300 | this.LoadModulesControllers(); 301 | this.emit('twee.Bootstrap.ModulesControllersLoaded'); 302 | 303 | // Tail middleware is used for logging and doing post-calculations, post-stuff 304 | this.LoadModulesMiddleware('tail'); 305 | this.emit('twee.Bootstrap.ModulesTailMiddlewareLoaded'); 306 | 307 | // This route will be used to write user that he did not sat up any configuration file for framework 308 | this.__handle404(); 309 | 310 | this.emit('twee.Bootstrap.End'); 311 | 312 | return this; 313 | }; 314 | 315 | /** 316 | * Loading turned on twee extensions 317 | * 318 | * @param extensions Object - Extensions object where keys are the names of extensions 319 | * @param moduleName String The name of current module 320 | * @returns {twee} 321 | */ 322 | twee.prototype.LoadExtensions = function(extensions, moduleName) { 323 | for (var extension_name in extensions) { 324 | extensions[extension_name].name = extension_name; 325 | this.__resolveDependencies(extensions[extension_name], extensions, moduleName); 326 | } 327 | return this; 328 | }; 329 | 330 | /** 331 | * Generating extension unique ID for registry 332 | * @param extension 333 | * @param moduleName 334 | * @returns {string} 335 | * @private 336 | */ 337 | twee.prototype.__getExtensionUniqueID = function(extension, moduleName) { 338 | return 'module:' + (moduleName || 'twee') 339 | + (extension.file ? '|file:' + extension.file : '') 340 | + (extension.module ? '|npm-module:' + extension.module : '') 341 | + (extension.applicationModule ? '|appModule:' + extension.applicationModule : ''); 342 | }; 343 | 344 | /** 345 | * Loading all the extensions and it's dependencies tree 346 | * 347 | * @param currentExtension 348 | * @param extensions 349 | * @param moduleName 350 | * @private 351 | */ 352 | twee.prototype.__resolveDependencies = function(currentExtension, extensions, moduleName) { 353 | 354 | this.emit('twee.LoadExtensions.PreLoad', currentExtension, moduleName); 355 | 356 | var extensionID = this.__getExtensionUniqueID(currentExtension, moduleName); 357 | 358 | if (this.__extensionsRegistry[extensionID]) { 359 | return; 360 | } 361 | 362 | this.__extensionsRegistry[extensionID] = {options: currentExtension, extension: null}; 363 | 364 | var moduleLog = moduleName ? '[MODULE::' + moduleName + ']' : ''; 365 | 366 | 367 | // Dependencies are loaded only when needed by another extensions 368 | if (currentExtension.dependency || (currentExtension.disabled && !currentExtension.dependency)) { 369 | return; 370 | } 371 | 372 | var currentExtensionDependencies; 373 | currentExtensionDependencies = {}; 374 | 375 | // First of all trying to import extension and load it's internal dependencies definition 376 | if (!currentExtension.module && !currentExtension.file) { 377 | moduleLog += ('[EXTENSION' + (moduleLog ? '' : '::GLOBAL') + '] '); 378 | throw new Error(moduleLog + colors.cyan(currentExtension.name) + '` has wrong configuration. `module` AND `file` are not correct'); 379 | } 380 | 381 | // Loading extension module 382 | var extensionModule = '' 383 | , extensionModuleFolder = ''; 384 | try { 385 | // This is simply local file or module 386 | if (currentExtension.file) { 387 | if (currentExtension.applicationModule) { 388 | try { 389 | extensionModuleFolder = this.__config['__folders__'][currentExtension.applicationModule]['moduleExtensionsFolder']; 390 | extensionModule = require(extensionModuleFolder + currentExtension.file); 391 | } catch (err) { 392 | //noinspection ExceptionCaughtLocallyJS 393 | throw new Error('Module `' + currentExtension.applicationModule 394 | + '` is not installed. Needed as dependency provider for extension: ' 395 | + currentExtension.name + '. ' + err.stack || err.toString()); 396 | } 397 | } else if (moduleName) { 398 | extensionModuleFolder = this.__config['__folders__'][moduleName]['moduleExtensionsFolder']; 399 | extensionModule = require(extensionModuleFolder + currentExtension.file); 400 | } else { 401 | //noinspection ExceptionCaughtLocallyJS 402 | throw new Error('Extension is wrong configured: ' + JSON.stringify(currentExtension)); 403 | } 404 | 405 | // This is npm module 406 | } else if (currentExtension.module) { 407 | extensionModule = require(currentExtension.module); 408 | } 409 | } catch (err) { 410 | throw err; 411 | } 412 | 413 | if (!extensionModule.extension || typeof extensionModule.extension !== 'function') { 414 | moduleLog += ('[EXTENSION' + (moduleLog ? '' : '::GLOBAL') + '] '); 415 | throw new Error(moduleLog + extensionID + ' should export .extension as `function`'); 416 | } 417 | 418 | this.__extensionsRegistry[extensionID].extension = extensionModule.extension; 419 | 420 | currentExtensionDependencies = extensionModule.dependencies || {}; 421 | 422 | if (currentExtension.dependencies && typeof currentExtension.dependencies == 'object' && Object.keys(currentExtension.dependencies).length) { 423 | // Overwrite dependencies configuration if local configuration presents. It has greater priority 424 | currentExtensionDependencies = currentExtension.dependencies; 425 | } 426 | 427 | for (var dep in currentExtensionDependencies) { 428 | var dependency = currentExtensionDependencies[dep]; 429 | if (dependency.disabled) { 430 | continue; 431 | } 432 | try { 433 | if (!dependency || typeof dependency !== 'object' || !Object.keys(dependency).length) { 434 | // It means that dependency is empty object or we have only it's name 435 | // And should search in global extensions namespace 436 | if (!extensions[dep]) { 437 | //noinspection ExceptionCaughtLocallyJS 438 | throw new Error('Dependency info does not exists neither in dependency config nor in global extensions namespace'); 439 | } 440 | 441 | dependency = extensions[dep]; 442 | dependency.dependency = false; 443 | } 444 | 445 | dependency.name = dep; 446 | this.__extensionsRecursyDeepness++; 447 | if (this.__extensionsRecursyDeepness > 100) { 448 | throw new Error('It seems we have dependencies recursy infinity loop'); 449 | } 450 | this.__resolveDependencies(dependency, extensions, moduleName); 451 | this.__extensionsRecursyDeepness--; 452 | } catch (err) { 453 | throw new Error('Current Extension: `' + currentExtension.name + '`, dependency: `' + dep + '` exception: ' + err.stack || err.toString()); 454 | } 455 | } 456 | 457 | moduleLog += ('[EXTENSION::' + currentExtension.name + '] '); 458 | if (extensionModule.config && typeof extensionModule.config === 'object') { 459 | var configNamespace = extensionModule.configNamespace || ''; 460 | if (configNamespace) { 461 | // Rewrite extension's config with application 462 | this.__config['twee']['extension'][configNamespace] = this.__config['twee']['extension'][configNamespace] || {}; 463 | this.__config['twee']['extension'][configNamespace] = this.extend(true, extensionModule.config, this.__config['twee']['extension'][configNamespace]); 464 | } 465 | } 466 | extensionModule.extension(); 467 | 468 | this.log(moduleLog + 'Installed (configNamespace: ' + configNamespace + ')'); 469 | this.emit('twee.LoadExtensions.Loaded', currentExtension, moduleName); 470 | }; 471 | 472 | /** 473 | * Loading all the modules 474 | * 475 | * @returns {twee} 476 | */ 477 | twee.prototype.LoadModulesControllers = function() { 478 | for (var moduleName in this.__config['__moduleOptions__']) { 479 | this.setupRoutes(moduleName, this.__config['__moduleOptions__'][moduleName].prefix || ''); 480 | } 481 | return this; 482 | }; 483 | 484 | /** 485 | * Loading all the middlewares from all modules that should be dispatched before any constructor 486 | * Head middlewares are executed before all the controllers. It is like preDispatch. 487 | * Tail middlewares are executed after all the controllers. It is like postDispatch. 488 | * 489 | * @param placement String Placement of middleware: head or tail. 490 | * @returns {twee} 491 | */ 492 | twee.prototype.LoadModulesMiddleware = function(placement) { 493 | placement = String(placement || '').trim(); 494 | if (placement !== 'head' && placement !== 'tail') { 495 | throw new Error('Middleware type should be `head` or `tail`'); 496 | } 497 | 498 | this.emit('twee.LoadModulesMiddleware.Start', placement); 499 | 500 | for (var moduleName in this.__config['__moduleOptions__']) { 501 | this.emit('twee.LoadModulesMiddleware.OnLoad', placement, moduleName); 502 | var middlewareList = this.getConfig('__setup__:' + moduleName + ':middleware:' + placement) || [] 503 | , middlewareInstanceList = this.getMiddlewareInstanceArray(moduleName, middlewareList); 504 | 505 | if (middlewareInstanceList.length) { 506 | var prefix = String(this.__config['__moduleOptions__'][moduleName].prefix || '').trim(); 507 | if (prefix) { 508 | this.__app.use(prefix, middlewareInstanceList); 509 | } else { 510 | this.__app.use(middlewareInstanceList); 511 | } 512 | } 513 | 514 | this.emit('twee.LoadModulesMiddleware.Loaded', placement, moduleName); 515 | } 516 | 517 | this.emit('twee.LoadModulesMiddleware.End', placement); 518 | 519 | return this; 520 | }; 521 | 522 | /** 523 | * Loading all extensions from all the modules by order: 524 | * ModulesOrder -> ExtensionsOrderInEveryModule 525 | * 526 | * @returns {twee} 527 | */ 528 | twee.prototype.LoadModulesExtensions = function() { 529 | this.emit('twee.LoadModulesExtensions.Start'); 530 | 531 | for (var moduleName in this.__config['__moduleOptions__']) { 532 | if (this.__config['__setup__'][moduleName]['extensions']) { 533 | if (typeof this.__config['__setup__'][moduleName]['extensions'] != 'object') { 534 | continue; 535 | } 536 | 537 | var extensions = this.__config['__setup__'][moduleName]['extensions']; 538 | this.emit('twee.LoadModulesExtensions.LoadExtensions.Start', moduleName, extensions); 539 | this.LoadExtensions(extensions, moduleName); 540 | this.emit('twee.LoadModulesExtensions.LoadExtensions.Stop', moduleName, extensions); 541 | } 542 | } 543 | this.emit('twee.LoadModulesExtensions.Stop'); 544 | return this; 545 | }; 546 | 547 | /** 548 | * Default 404 route 549 | * @private 550 | */ 551 | twee.prototype.__handle404 = function() { 552 | var self = this; 553 | 554 | // Here we can rewrite environment with framework extending 555 | this.emit('twee.__handle404.Start'); 556 | 557 | function generate404(req, res, next) { 558 | next(new Error('Not Found!')); 559 | } 560 | 561 | function errorHandler(err, req, res, next) { 562 | var message = '404 - Not found!'; 563 | if (err) { 564 | res.status(500); 565 | if (self.__env == 'development') { 566 | message = err.toString(); 567 | } 568 | } else { 569 | res.status(404); 570 | err = new Error('The page you requested has not been found!'); 571 | } 572 | 573 | if (req.xhr) { 574 | var jsonMessage = {message: message, error_code: 404}; 575 | if (self.__env == 'development') { 576 | jsonMessage['stack'] = err.stack || err.toString(); 577 | } 578 | res.json(jsonMessage); 579 | } else { 580 | if (self.__app.get('view engine')) { 581 | res.render(path.resolve(self.getConfig('twee:options:errorPages:404:viewTemplate')), {error: err}); 582 | } else { 583 | res.send('

' + message + '

'); 584 | } 585 | } 586 | } 587 | 588 | this.__app.use(generate404, errorHandler); 589 | this.emit('twee.__handle404.End'); 590 | }; 591 | 592 | /** 593 | * Loading modules information 594 | * 595 | * @param modules 596 | * @return {twee} 597 | */ 598 | twee.prototype.LoadModulesInformation = function(modules) { 599 | this.emit('twee.LoadModulesInformation.Start'); 600 | for (var moduleName in modules) { 601 | var moduleOptions = modules[moduleName]; 602 | if (moduleOptions.disabled == true) { 603 | this.log('Module `' + moduleName + '` disabled. Skipping.'); 604 | continue; 605 | } 606 | this.__config['__moduleOptions__'] = this.__config['__moduleOptions__'] || {}; 607 | this.__config['__moduleOptions__'][moduleName] = moduleOptions; 608 | this.LoadModuleInformation(moduleName, moduleOptions); 609 | } 610 | this.emit('twee.LoadModulesInformation.End'); 611 | return this; 612 | }; 613 | 614 | /** 615 | * Loading one module, including all the configs, middlewares and controllers 616 | * @param moduleName 617 | * @param moduleOptions 618 | * @returns {twee} 619 | * @constructor 620 | */ 621 | twee.prototype.LoadModuleInformation = function(moduleName, moduleOptions) { 622 | 623 | this.emit('twee.LoadModuleInformation.Start', moduleName, moduleOptions); 624 | 625 | this.log('[MODULE] Loading: ' + colors.cyan(moduleName)); 626 | 627 | moduleName = String(moduleName || '').trim(); 628 | if (!moduleName) { 629 | throw new Error('twee::LoadModuleInformation - `moduleName` is empty'); 630 | } 631 | 632 | if (moduleName == 'twee') { 633 | throw new Error('twee::LoadModuleInformation - `twee` name for modules is deprecated. It is used for framework'); 634 | } 635 | 636 | var moduleFolder = path.join(this.__baseDirectory, 'modules', moduleName + '/') 637 | , moduleSetupFolder = path.join(moduleFolder, 'setup/') 638 | , moduleSetupFile = path.join(moduleFolder, 'setup/setup') 639 | , moduleConfigsFolder = path.join(moduleFolder, 'setup/configs/') 640 | , moduleControllersFolder = path.join(moduleFolder, 'controllers/') 641 | , moduleModelsFolder = path.join(moduleFolder, 'models/') 642 | , moduleMiddlewareFolder = path.join(moduleFolder, 'middleware/') 643 | , moduleParamsFolder = path.join(moduleFolder, 'params/') 644 | , moduleViewsFolder = path.join(moduleFolder, 'views/') 645 | , moduleExtensionsFolder = path.join(moduleFolder, 'extensions/') 646 | , moduleI18nFolder = path.join(moduleFolder, 'i18n/') 647 | , moduleAssetsFolder = path.join(moduleFolder, 'assets/'); 648 | 649 | this.__config['__folders__'] = this.__config['__folders__'] || {}; 650 | this.__config['__folders__'][moduleName] = { 651 | module: moduleFolder, 652 | moduleSetupFolder: moduleSetupFolder, 653 | moduleSetupFile: moduleSetupFile, 654 | moduleConfigsFolder: moduleConfigsFolder, 655 | moduleControllersFolder: moduleControllersFolder, 656 | moduleModelsFolder: moduleModelsFolder, 657 | moduleMiddlewareFolder: moduleMiddlewareFolder, 658 | moduleParamsFolder: moduleParamsFolder, 659 | moduleViewsFolder: moduleViewsFolder, 660 | moduleExtensionsFolder: moduleExtensionsFolder, 661 | moduleI18nFolder: moduleI18nFolder, 662 | moduleAssetsFolder: moduleAssetsFolder 663 | }; 664 | 665 | // Load base configs and overwrite them according to environment 666 | this.loadConfigs(moduleName, moduleConfigsFolder); 667 | 668 | // Loading Routes Information 669 | this.__config['__setup__'] = this.__config['__setup__'] || {}; 670 | this.__config['__setup__'][moduleName] = require(moduleSetupFile); 671 | this.emit( 672 | 'twee.LoadModuleInformation.End', 673 | moduleName, 674 | this.__config['__setup__'][moduleName], 675 | this.__config['__folders__'][moduleName] 676 | ); 677 | return this; 678 | }; 679 | 680 | /** 681 | * Loading all the bunch of configs from configuration folder according to environment 682 | * 683 | * @param configsFolder string - configurations folder 684 | * @returns {twee} 685 | * @param moduleName 686 | */ 687 | twee.prototype.loadConfigs = function(moduleName, configsFolder) { 688 | 689 | this.emit('twee.loadConfigs.Start', moduleName, configsFolder); 690 | 691 | var self = this 692 | , configs = fs.readdirSync(configsFolder) 693 | , configsObject = {}; 694 | 695 | configs.forEach(function(configFile){ 696 | var configFilePath = path.join(configsFolder, configFile) 697 | , stats = fs.statSync(configFilePath); 698 | 699 | if (stats.isFile()) { 700 | var configData = self.loadConfig(configFilePath, moduleName); 701 | var cD = {}; 702 | cD[configData["name"]] = configData.config; 703 | configsObject = extend(true, configsObject, cD); 704 | } 705 | }); 706 | 707 | configsFolder = path.join(configsFolder, this.__env); 708 | 709 | if (fs.existsSync(configsFolder)) { 710 | configs = fs.readdirSync(configsFolder); 711 | configs.forEach(function(configFile){ 712 | var configFilePath = path.join(configsFolder, configFile) 713 | , stats = fs.statSync(configFilePath); 714 | 715 | if (stats.isFile()) { 716 | var configData = self.loadConfig(configFilePath, moduleName); 717 | var cD = {}; 718 | cD[configData["name"]] = configData.config; 719 | configsObject = extend(true, configsObject, cD); 720 | } 721 | }); 722 | } else { 723 | this.log('[WARNING] No environment configs exists'); 724 | } 725 | 726 | this.__config[moduleName.toLowerCase()] = configsObject; 727 | this.emit('twee.loadConfigs.End', moduleName, this.__config[moduleName]); 728 | return this; 729 | }; 730 | 731 | /** 732 | * Loading config file and returning it's name and contents 733 | * @param configFile 734 | * @param moduleName 735 | * @returns {{name: string, config: *}} 736 | */ 737 | twee.prototype.loadConfig = function(configFile, moduleName) { 738 | 739 | this.emit('twee.loadConfig.Start', configFile, moduleName); 740 | 741 | var configName = path.basename(configFile).toLowerCase().replace('.json', '').replace('.js', '') 742 | , config = require(configFile); 743 | 744 | this.log('[MODULE::' + moduleName + '][CONFIGS::' + configName + '] Loaded: ' + configFile); 745 | config = {name: configName, config: config}; 746 | 747 | this.emit('twee.loadConfig.Start', configFile, moduleName, config); 748 | 749 | return config; 750 | }; 751 | 752 | /** 753 | * Setting base directory for including all the rest 754 | * @param directory 755 | * @returns {twee} 756 | */ 757 | twee.prototype.setBaseDirectory = function(directory) { 758 | directory = String(directory || ''); 759 | this.__baseDirectory = this.__baseDirectory || directory || process.cwd(); 760 | 761 | // Fixing environment 762 | this.__env = process.env.NODE_ENV; 763 | if (!this.__env) { 764 | this.log('No NODE_ENV sat up. Setting to `production`'); 765 | this.__env = process.env.NODE_ENV = 'production'; 766 | } 767 | this.log('NODE_ENV: ' + this.__env); 768 | this.__app.locals.env = this.__env; 769 | return this; 770 | }; 771 | 772 | /** 773 | * Returning root application directory or full subfolder 774 | * 775 | * @param postfix String Postfix to add to base directory 776 | * @returns {string} 777 | */ 778 | twee.prototype.getBaseDirectory = function(postfix) { 779 | 780 | if (typeof postfix === 'string') { 781 | postfix = String(postfix || '').trim(); 782 | return path.join(this.__baseDirectory, postfix); 783 | } 784 | 785 | return this.__baseDirectory; 786 | }; 787 | 788 | /** 789 | * Including local module 790 | * @param module 791 | * @returns {*} 792 | */ 793 | twee.prototype.Require = function(module) { 794 | return require(path.join(this.__baseDirectory, module)); 795 | }; 796 | 797 | /** 798 | * Setting up params for routes 799 | * @param params 800 | * @param router 801 | * @param moduleName 802 | */ 803 | twee.prototype.setupParams = function(params, router, moduleName) { 804 | if (!router.param) { 805 | throw new Error('Router should be instance of express.Router()'); 806 | } 807 | 808 | if (params && params instanceof Object) { 809 | for (var param in params) { 810 | // Regexp can be used too 811 | //console.log(param, typeof params[param]); 812 | if (params[param] instanceof RegExp) { 813 | var paramContents = params[param]; 814 | router.param(param, function(req, res, next, p){ 815 | if (p.match(paramContents)) { 816 | next(); 817 | } else { 818 | next('route'); 819 | } 820 | }); 821 | this.log('[MODULE::' + moduleName + '][PARAM::' + param + '] Installed as RegExp(' + params[param] + ')'); 822 | 823 | // If it is middleware function from setup.js file - it could be passed as is too 824 | } else if (typeof params[param] === 'function') { 825 | var paramContents = params[param]; 826 | router.param(param, paramContents); 827 | this.log('[MODULE::' + moduleName + '][PARAM::' + param + '] Installed as inline middleware'); 828 | 829 | // Otherwise it should be an instance or middleware function from file or module or applicationModule/params folder 830 | } else if (typeof params[param] === 'object') { 831 | // This is module 832 | var requireString = ''; 833 | if (params[param].module && typeof params[param].module === 'string') { 834 | requireString = params[param].module; 835 | } else if (params[param].applicationModule 836 | && typeof params[param].applicationModule === 'string' 837 | && this.__config['__folders__'][params[param].applicationModule]) { 838 | if (params[param].file && typeof params[param].file === 'string') { 839 | requireString = this.__config['__folders__'][params[param].applicationModule]['moduleParamsFolder']; 840 | requireString += params[param].file; 841 | } 842 | } else if (params[param].file && typeof params[param].file === 'string') { 843 | requireString = this.__config['__folders__'][moduleName]['moduleParamsFolder'] + params[param].file; 844 | } 845 | 846 | if (requireString) { 847 | var _module = require(requireString); 848 | 849 | // If method specified - try to go in needed deepness to get right object 850 | if (params[param].method) { 851 | var methodParts = params[param].method.split('.') 852 | , neededMethod = _module[methodParts[0]] 853 | , previousMethod = null; 854 | 855 | for (var i = 1; i < methodParts.length; i++) { 856 | if (typeof neededMethod === 'function') { 857 | neededMethod = neededMethod(); 858 | } 859 | previousMethod = neededMethod; 860 | if (neededMethod[methodParts[i]]) { 861 | neededMethod = neededMethod[methodParts[i]]; 862 | } 863 | } 864 | 865 | // If it is regexp - then just use it as is 866 | if (neededMethod instanceof RegExp) { 867 | router.param(param, function(req, res, next, p){ 868 | if (p.match(neededMethod)) { 869 | next(); 870 | } else { 871 | next('route'); 872 | } 873 | }); 874 | this.log('[MODULE::' + moduleName + '][PARAM::' + param + '] Installed as RegExp(' + params[param] + ')'); 875 | continue; 876 | } 877 | 878 | if (typeof neededMethod !== 'function') { 879 | throw new Error('Method for router.param() neither RegExp nor Middleware Function'); 880 | } 881 | 882 | // If we need to bind function to parent reference - then do it 883 | if (params[param].reference && previousMethod instanceof Object) { 884 | neededMethod = neededMethod.bind(previousMethod); 885 | } 886 | 887 | router.param(param, neededMethod); 888 | this.log('[MODULE::' + moduleName + '][PARAM::' + param + '] Installed as middleware'); 889 | 890 | // if we have no specified method - then in case when it is middleware or RegExp - setup it 891 | } else if (typeof _module === 'function' || _module instanceof RegExp) { 892 | router.param(param, _module); 893 | this.log('[MODULE::' + moduleName + '][PARAM::' + param + '] Installed as middleware'); 894 | } 895 | } 896 | } 897 | } 898 | } 899 | }; 900 | 901 | /** 902 | * Format for controllers in configuration: 903 | * Controller:Action: 904 | * 905 | * By default HTTP method is set to `all`. It means that all the HTTP methods are acceptable 906 | * 907 | * Example of Config: 908 | * { 909 | * "routes": [ 910 | * { 911 | * "description": "Entry point for application. Landing page", 912 | * "pattern": "/", 913 | * "controllers": ["IndexController:indexAction"] 914 | * } 915 | * } 916 | * 917 | * Bundles of middlewares can be sat as: 918 | * ["IndexController:authAction", "IndexController:indexAction"] 919 | * 920 | * @param moduleName string Module Name 921 | * @param prefix string Module request prefix 922 | * @returns {twee} 923 | */ 924 | twee.prototype.setupRoutes = function(moduleName, prefix) { 925 | var routesFile = this.__config['__folders__'][moduleName]['moduleSetupFile'] 926 | , routes = require(routesFile) 927 | 928 | // TODO: use options: http://expressjs.com/api.html#router 929 | , router = express.Router() 930 | , controllersRegistry = {}; 931 | 932 | var self = this; 933 | 934 | if (!routes.routes) { 935 | throw Error('Module: `' + moduleName + '`. No `routes` field in file: ' + colors.red(routesFile)); 936 | } 937 | 938 | self.emit('twee.setupRoutes.Start', moduleName, prefix, router, controllersRegistry); 939 | 940 | self.emit('twee.setupRoutes.GlobalModuleParams.Start', routes.params, router, moduleName); 941 | // Install route global params 942 | this.setupParams(routes.params, router, moduleName); 943 | self.emit('twee.setupRoutes.GlobalModuleParams.End', routes.params, router, moduleName); 944 | 945 | 946 | 947 | routes.routes.forEach(function(route){ 948 | var pattern = route.pattern || '' 949 | , controllers = route.controllers || [] 950 | , middleware = route.middleware || {} 951 | , params = route.params || {}; 952 | 953 | if (!pattern) { 954 | throw Error('Module: `' + moduleName + '`. No valid `pattern` sat for route'); 955 | } 956 | 957 | if (!controllers.length) { 958 | return; 959 | } 960 | 961 | // If route has been disabled - then don't process it 962 | if (route.disabled) { 963 | return; 964 | } 965 | 966 | self.emit('twee.setupRoutes.ControllerParams.Start', params, router, moduleName); 967 | // Installing params for each controller set 968 | self.setupParams(params, router, moduleName); 969 | self.emit('twee.setupRoutes.ControllerParams.End', params, router, moduleName); 970 | 971 | controllers.forEach(function(controller) { 972 | var controller_info = controller.split('.'); 973 | if (controller_info.length == 0 || !controller_info[0].trim()) { 974 | throw new Error('Controller does not have controller name, action and method'); 975 | } 976 | 977 | var controller_name = controller_info[0].trim() 978 | , action_name = '' 979 | , methods = []; 980 | 981 | if (controller_info.length === 1) { 982 | // trying indexAction 983 | action_name = 'indexAction'; 984 | methods.push('all'); 985 | } else if (controller_info.length === 2) { 986 | action_name = controller_info[1].trim(); 987 | methods.push('all'); 988 | } else if (controller_info.length === 3) { 989 | action_name = controller_info[1].trim(); 990 | var _methods = controller_info[2].trim().split(',') 991 | , at_least_one_method = false; 992 | 993 | // Iterating for all the methods and call appropriate router 994 | _methods.forEach(function(requestMethod){ 995 | if (router[requestMethod.trim()]) { 996 | methods.push(requestMethod.trim()); 997 | at_least_one_method = true; 998 | } 999 | }); 1000 | 1001 | if (!at_least_one_method) { 1002 | methods.push('all'); 1003 | } 1004 | } 1005 | 1006 | // Deprecate all the actions that are not endWith `Action` 1007 | if (action_name.indexOf('Action', action_name.length - 6) === -1) { 1008 | throw new Error( 1009 | "Action name for controller have to be in format: Action." + 1010 | ' It is used to protect all the methods from calling if they are not for Public requests' 1011 | ); 1012 | } 1013 | 1014 | if (!controllersRegistry[controller_name]) { 1015 | self.log("[MODULE::" + moduleName + "][CONTROLLER::" + controller_name + "] Loading"); 1016 | var ControllerClass = require(self.__config['__folders__'][moduleName]['moduleControllersFolder'] + controller_name); 1017 | controllersRegistry[controller_name] = new ControllerClass; 1018 | } 1019 | 1020 | // For pre-initializing controller with it's own stuff 1021 | if (!controllersRegistry[controller_name].__initCalled) { 1022 | if (controllersRegistry[controller_name]['init'] 1023 | && typeof controllersRegistry[controller_name]['init'] == 'function') 1024 | { 1025 | self.emit('twee.setupRoutes.NewController.PreInit', moduleName, route, controller_name, action_name, methods, controllersRegistry[controller_name]); 1026 | controllersRegistry[controller_name].init(); 1027 | self.log('[MODULE::' + moduleName + '][CONTROLLER][INIT] ' + 1028 | colors.cyan(controller_name + '.init()')); 1029 | self.emit('twee.setupRoutes.NewController.PostInit', moduleName, route, controller_name, action_name, methods, controllersRegistry[controller_name]); 1030 | } 1031 | // Setting parent class to Controller 1032 | controllersRegistry[controller_name].__initCalled = true; 1033 | } 1034 | 1035 | // Iterating over all collected methods and setup controllers into stack 1036 | if (!controllersRegistry[controller_name][action_name]) { 1037 | throw new Error('No action: `' + action_name + '` for Controller: `' + controller_name + '`'); 1038 | } 1039 | 1040 | var middlewareList = []; 1041 | 1042 | if (middleware && middleware.before && middleware.before.length && middleware.before instanceof Array) { 1043 | self.log('[MODULE::'+ moduleName +'][CONTROLLER:' + colors.cyan(controller_name) + '] Loading PreControllerAction Middleware List'); 1044 | self.emit('twee.setupRoutes.ControllerActionMiddleware.Before.Start', moduleName, route, controller_name, action_name, methods, controllersRegistry[controller_name], middleware.before); 1045 | middlewareList = self.getMiddlewareInstanceArray(moduleName, middleware.before); 1046 | self.emit('twee.setupRoutes.ControllerActionMiddleware.Before.End', moduleName, route, controller_name, action_name, methods, controllersRegistry[controller_name], middlewareList); 1047 | } 1048 | 1049 | // This is Controller.Action installaction after `before-middlewares` and before `after-middlewares` 1050 | middlewareList.push(controllersRegistry[controller_name][action_name] 1051 | .bind(controllersRegistry[controller_name])); 1052 | 1053 | if (middleware && middleware.after && middleware.after.length && middleware.after instanceof Array) { 1054 | self.log('[MODULE::'+ moduleName +'][CONTROLLER:' + colors.cyan(controller_name) + '] Loading PostControllerAction Middleware List'); 1055 | self.emit('twee.setupRoutes.ControllerActionMiddleware.After.Start', moduleName, route, controller_name, action_name, methods, controllersRegistry[controller_name], middleware.after); 1056 | var afterMiddlewareList = self.getMiddlewareInstanceArray(moduleName, middleware.after); 1057 | self.emit('twee.setupRoutes.ControllerActionMiddleware.After.End', moduleName, route, controller_name, action_name, methods, controllersRegistry[controller_name], afterMiddlewareList); 1058 | for (var i = 0; i < afterMiddlewareList.length; i++) { 1059 | middlewareList.push(afterMiddlewareList[i]); 1060 | } 1061 | } 1062 | 1063 | methods.forEach(function(method){ 1064 | // Setup router 1065 | router[method]( 1066 | pattern, 1067 | middlewareList 1068 | ); 1069 | 1070 | self.log('[MODULE::' + moduleName + '][ROUTE] HTTP METHOD: ' 1071 | + colors.cyan(method) 1072 | + '. ' 1073 | + 'ACTION: ' + colors.cyan(controller_name + '.' + action_name)); 1074 | }); 1075 | }); 1076 | }); 1077 | 1078 | // Install all the routes as a bunch under prefix 1079 | this.emit('twee.setupRoutes.preAppUse', prefix, router, moduleName); 1080 | this.__app.use(prefix || '/', router); 1081 | this.emit('twee.setupRoutes.postAppUse', prefix, moduleName); 1082 | return this; 1083 | }; 1084 | 1085 | /** 1086 | * Setting up middleware stack. 1087 | * If placement is `head`, then middlewares list will be loaded from config by this key, and 1088 | * will be sat up before all the routes 1089 | * If placement is `tail`, then the same, but middlewares will be sat up after all the routes dispatching 1090 | * If middlewares param has been passed - then it seems some controller wants some middleware to be 1091 | * executed before controller. 1092 | * Middleware is simple list of files or modules that should return middleware function or an object that includes it. 1093 | * 1094 | * Middleware example: 1095 | * middleware: [ 1096 | * { 1097 | * "name": "authMiddleware", // (not required) 1098 | * 1099 | * // If your middleware is simple file (first priority). It is application specified middleware (not in packages) 1100 | * "file": "myFolder/myMiddleware" 1101 | * 1102 | * // OR in module (second priority if both exists) 1103 | * "module": "express/some/middleware" 1104 | * 1105 | * // As additional you can pass field name of object that should contain needed middleware: 1106 | * "method": "myMethod" 1107 | * // Then it will be passed to router 1108 | * 1109 | * // If object is too complex with nested hierarchy, then it can be specified like this: 1110 | * "method": "mySubObject[.mySubObject2[...]].MyMethod" 1111 | * 1112 | * // If this is a class and you need to use it's method as middleware, then probably 1113 | * // you want to set up `this` reference to this class. This option will allow to do this: 1114 | * "reference": true 1115 | * // It will do something like this: 1116 | * // var ref = MyClass.MySubClass 1117 | * // middleware = ref[myMethod].bind(ref) 1118 | * 1119 | * // You can disable middleware by passing this value: 1120 | * "disabled": true 1121 | * } 1122 | * ] 1123 | * 1124 | * @param moduleName 1125 | * @param middlewareList 1126 | * @returns {Array} 1127 | */ 1128 | twee.prototype.getMiddlewareInstanceArray = function(moduleName, middlewareList) { 1129 | if (!middlewareList instanceof Array) { 1130 | throw new Error('Middleware list should be an Array'); 1131 | } 1132 | 1133 | var self = this 1134 | , middlewareInstanceArray = [] 1135 | , middlewareModule = null 1136 | , middlewareIndex 1137 | , middlewareListLength = middlewareList.length; 1138 | 1139 | for (middlewareIndex=0; middlewareIndex < middlewareListLength; middlewareIndex++) { 1140 | 1141 | var middleware = middlewareList[middlewareIndex] 1142 | , middlewareName = middleware.name || '' 1143 | , uniqueMiddlewareId = JSON.stringify({m: moduleName, md: middlewareName}); 1144 | 1145 | if (this.__middlewareListRegistry[uniqueMiddlewareId]) { 1146 | middlewareModule = this.__middlewareListRegistry[uniqueMiddlewareId]; 1147 | } else { 1148 | if (!middleware.file && !middleware.module) { 1149 | throw new Error('In module `' + moduleName + '` middleware `' + middlewareName + '` have to be specified with `file` or `module` filed'); 1150 | } 1151 | 1152 | // Check if it has been disabled 1153 | if (middleware.disabled) { 1154 | this.log('[MODULE::' + moduleName + '][MIDDLEWARE::' + middlewareName + '] Disabled.'); 1155 | continue; 1156 | } 1157 | 1158 | middlewareModule = null; 1159 | var middlewareModuleFolder = self.__config['__folders__'][moduleName]['moduleMiddlewareFolder'] 1160 | , _construct = false; 1161 | 1162 | // Instantiating middleware module 1163 | if (middleware.file) { 1164 | middlewareModule = require(middlewareModuleFolder + middleware.file); 1165 | middlewareName = middlewareName || middleware.file; 1166 | } else if (middleware.module) { 1167 | var mmLen = middleware.module.length; 1168 | 1169 | if (middleware.module[mmLen - 1] == '@') { 1170 | middleware.module = middleware.module.substr(0, mmLen - 1); 1171 | _construct = true; 1172 | middlewareName = middlewareName || middleware.module; 1173 | } 1174 | middlewareModule = require(middleware.module); 1175 | if (_construct) { 1176 | if (middleware.params) { 1177 | var currParam = null; 1178 | if (typeof middleware.params !== 'object') { 1179 | middleware.params = [middleware.params]; 1180 | } 1181 | if (middleware.params instanceof Array) { 1182 | for (var i = 0; i < middleware.params.length; i++) { 1183 | currParam = middleware.params[i]; 1184 | if (typeof currParam === 'string') { 1185 | if (currParam[0] === '@') { 1186 | middleware.params[i] = self.getConfig(currParam.replace('@', '')); 1187 | } 1188 | } 1189 | } 1190 | } else if (middleware.params instanceof Object) { 1191 | for (var paramName in middleware.params) { 1192 | currParam = middleware.params[paramName]; 1193 | if (typeof currParam === 'string') { 1194 | if (currParam[0] === '@') { 1195 | middleware.params[paramName] = self.getConfig(currParam.replace('@', '')); 1196 | } 1197 | } 1198 | } 1199 | } 1200 | if (!middleware.params instanceof Array) { 1201 | middleware.params = [middleware.params]; 1202 | } 1203 | middlewareModule = middlewareModule.apply(null, middleware.params); 1204 | } else { 1205 | middlewareModule = middlewareModule(); 1206 | } 1207 | } 1208 | } 1209 | 1210 | // If method has been sat up - then lets use it 1211 | var method = middlewareModule 1212 | , methodParent = middlewareModule 1213 | , methodParts = String(middleware.method || '').trim().split('.'); 1214 | 1215 | if (methodParts.length && !_construct) { 1216 | 1217 | // Going through hierarchy of object and finding out if the last method part is the middleware function 1218 | for (var index = 0; index < methodParts.length; index++) { 1219 | if (method[methodParts[index]]) { 1220 | methodParent = method; 1221 | method = method[methodParts[index]]; 1222 | } 1223 | 1224 | // Check if it is function and not the last - then instantiate it 1225 | if (index < methodParts.length - 1 && typeof method === 'function') { 1226 | method = new method; 1227 | } 1228 | } 1229 | 1230 | if (typeof method !== 'function') { 1231 | throw new Error('Method should be a function, got: ' + (typeof method) + '. Method: ' + middleware.method + ', middlewareId: ' + middlewareId); 1232 | } 1233 | 1234 | // Do we need to provide class reference to middleware function? 1235 | if (middleware.reference) { 1236 | middlewareModule = method.bind(methodParent); 1237 | } else { 1238 | middlewareModule = method; 1239 | } 1240 | } 1241 | 1242 | if (typeof middlewareModule !== 'function') { 1243 | throw new Error('Middleware should be a function(req, res, [next]) or another app.use() valid middleware format'); 1244 | } 1245 | } 1246 | 1247 | middlewareInstanceArray.push(middlewareModule); 1248 | this.__middlewareListRegistry[uniqueMiddlewareId] = middlewareModule; 1249 | this.log('[MODULE::' + moduleName + '][MIDDLEWARE::' + middlewareName + '] Installed'); 1250 | } 1251 | return middlewareInstanceArray; 1252 | }; 1253 | 1254 | /** 1255 | * Returning config by it's path 1256 | * Examples: 1257 | * twee.getConfig('twee:foo', 'bar') 1258 | * twee.getConfig('myModule:myConfigFile:myConfig', 'baz') 1259 | * 1260 | * @param key 1261 | * @param defaultValue 1262 | * @returns {*} 1263 | */ 1264 | twee.prototype.getConfig = function(key, defaultValue) { 1265 | 1266 | key = String(key || '').trim(); 1267 | var keyParts = key.split(':'); 1268 | 1269 | if (!keyParts.length) { 1270 | return defaultValue; 1271 | } 1272 | 1273 | for (var i = 0; i < keyParts.length; i++) { 1274 | if (!keyParts[i].trim()) { 1275 | throw new Error('Config path is not correct: ' + colors.red(key)); 1276 | } 1277 | } 1278 | 1279 | var returnedValue = this.__config; 1280 | for (i = 0; i < keyParts.length; i++) { 1281 | if (typeof returnedValue[keyParts[i]] === 'undefined') { 1282 | return defaultValue; 1283 | } else { 1284 | returnedValue = returnedValue[keyParts[i]]; 1285 | } 1286 | } 1287 | 1288 | return returnedValue; 1289 | }; 1290 | 1291 | /** 1292 | * Setting config with all the deepness. 1293 | * If key exists - then value will be replaced 1294 | * If key does not exists - then all the path will be constructed and value will be sat to final path 1295 | * 1296 | * Examples: 1297 | * twee.setConfig('module:configFile:configName', '777'); 1298 | * // It will produce: {module: {configFile: {configName: '777'}}} 1299 | * 1300 | * // Setting config for non-existing key 1301 | * twee.setConfig('module:configFile:NotExistingConfigName', '123'); 1302 | * // will produce: {module: {configFile: {NotExistingConfigName: '123', configName: '777'}}} 1303 | * 1304 | * // Setting config for non-existing path 1305 | * twee.setConfig('module:configFile:NotExistingConfigName:foo', '123'); 1306 | * // will produce: {module: {configFile: {NotExistingConfigName: {foo: '123'}, configName: '777'}}} 1307 | * 1308 | * @param key 1309 | * @param value 1310 | * @returns {twee} 1311 | */ 1312 | twee.prototype.setConfig = function(key, value) { 1313 | key = String(key || '').trim(); 1314 | if (!key) { 1315 | throw new Error('Config Key should be non-empty string!'); 1316 | } 1317 | 1318 | var keyParts = key.split(':'); 1319 | 1320 | // Check if all the parts of the key aren't empty strings 1321 | for (var i = 0; i < keyParts.length; i++) { 1322 | if (!keyParts[i].trim()) { 1323 | throw new Error('All the parts of config path should be not empty: ' + colors.red(key)); 1324 | } 1325 | } 1326 | 1327 | var configPointer = this.__config; // First time pointer shows root element 1328 | // Iterate using pointer until reach the goal 1329 | for (i = 0; i < keyParts.length; i++) { 1330 | // Existing config path AND not the last element 1331 | if (configPointer[keyParts[i]] && i < keyParts.length - 1) { 1332 | configPointer = configPointer[keyParts[i]]; 1333 | 1334 | // Not existing config path AND not the last element 1335 | } else if (!configPointer[keyParts[i]] && i < keyParts.length - 1) { 1336 | configPointer[keyParts[i]] = {}; 1337 | configPointer = configPointer[keyParts[i]]; 1338 | 1339 | // Final config path element 1340 | } else { 1341 | configPointer[keyParts[i]] = value; 1342 | } 1343 | } 1344 | 1345 | return this; 1346 | }; 1347 | 1348 | /** 1349 | * View helper registration method 1350 | * 1351 | * @param name 1352 | * @param helper 1353 | * @returns {twee} 1354 | */ 1355 | twee.prototype.registerViewHelper = function(name, helper) { 1356 | name = String(name || '').trim(); 1357 | 1358 | if (!name) { 1359 | throw new Error("Helper `" + name + "` should be not empty string"); 1360 | } 1361 | 1362 | if (typeof helper != 'function') { 1363 | throw new Error("Helper `" + name + "` should be callable"); 1364 | } 1365 | 1366 | if (this.helper[name]) { 1367 | throw new Error("Helper `" + name + "` already registered"); 1368 | } 1369 | 1370 | this.helper[name] = helper; 1371 | 1372 | return this; 1373 | }; 1374 | 1375 | /** 1376 | * Creating servers: HTTP, HTTPS, Socket, REST, JSON-RPC 1377 | * @private 1378 | */ 1379 | twee.prototype.__createServer = function(){ 1380 | this.emit('twee.createServer.Start'); 1381 | 1382 | this.__app.set('port', process.env.PORT || 3000); 1383 | 1384 | var http = require('http') 1385 | , https = require('https'); 1386 | 1387 | this.__http = http.createServer(this.__app).listen(this.__app.get('port')); 1388 | 1389 | if (this.getConfig('twee:options:useHTTPS', false)) { 1390 | var options = { 1391 | key: fs.readFileSync(process.cwd() + '/var/ssl/localhost.key'), 1392 | cert: fs.readFileSync(process.cwd() + '/var/ssl/localhost.crt') 1393 | }; 1394 | this.__https = https.createServer(options, this.__app).listen(443); 1395 | } 1396 | 1397 | this.emit('twee.createServer.End'); 1398 | 1399 | this.log('Worker ' + process.pid + ' spawned'); 1400 | }; 1401 | 1402 | /** 1403 | * Running application 1404 | */ 1405 | twee.prototype.run = function() { 1406 | this.setBaseDirectory(); 1407 | this.Bootstrap(); 1408 | return this; 1409 | }; 1410 | 1411 | /** 1412 | * Returning assets folders 1413 | * @returns {{}} 1414 | */ 1415 | twee.prototype.getModulesAssetsFolders = function() { 1416 | var modulesAssets = {}; 1417 | for (var moduleName in this.__config['__folders__']) { 1418 | modulesAssets[moduleName] = this.__config['__folders__'][moduleName]['moduleAssetsFolder']; 1419 | } 1420 | 1421 | return modulesAssets; 1422 | }; 1423 | 1424 | /** 1425 | * Returning modules folders 1426 | * @param folderName String 1427 | * @returns {{}} 1428 | */ 1429 | twee.prototype.getModulesFolders = function(folderName) { 1430 | var folders = {}; 1431 | folderName = String(folderName || '').trim(); 1432 | for (var moduleName in this.__config['__folders__']) { 1433 | folders[moduleName] = this.__config['__folders__'][moduleName]['module'] + folderName; 1434 | } 1435 | return folders; 1436 | }; 1437 | 1438 | /** 1439 | * Returning i18n folders 1440 | * @returns {{}} 1441 | */ 1442 | twee.prototype.getModulesI18nFolders = function() { 1443 | var modulesI18n = {}; 1444 | for (var moduleName in this.__config['__folders__']) { 1445 | modulesI18n[moduleName] = this.__config['__folders__'][moduleName]['moduleI18nFolder']; 1446 | } 1447 | 1448 | return modulesI18n; 1449 | }; 1450 | 1451 | /** 1452 | * Collecting all the grunt configs to manage application and modules assets 1453 | * @returns {*} 1454 | */ 1455 | twee.prototype.collectGruntConfigs = function() { 1456 | var initialConfig = this.Require('configs/grunt'); 1457 | var modulesConfig = this.Require('configs/modules'); 1458 | 1459 | if (!modulesConfig instanceof Object) { 1460 | console.log(colors.red('No modules config in application folder: config/modules')); 1461 | } else { 1462 | var moduleConfig = {}; 1463 | for (var moduleName in modulesConfig) { 1464 | var moduleConfigPath = 'modules/' + moduleName + '/setup/configs/grunt'; 1465 | try { 1466 | moduleConfig = this.Require(moduleConfigPath); 1467 | initialConfig = this.extend(true, initialConfig, moduleConfig); 1468 | } catch (err) { 1469 | console.log(colors.red('[WARN]') + ' Module `' + moduleName + '` has no config: ' + moduleConfigPath); 1470 | } 1471 | } 1472 | } 1473 | 1474 | return initialConfig; 1475 | }; 1476 | 1477 | /** 1478 | * Returning instantiated HTTP server object 1479 | * @returns {null} 1480 | */ 1481 | twee.prototype.getHttpServer = function() { 1482 | return this.__http; 1483 | }; 1484 | 1485 | /** 1486 | * Returning instantiated HTTPS server object 1487 | * @returns {null} 1488 | */ 1489 | twee.prototype.getHttpsServer = function() { 1490 | return this.__https; 1491 | }; 1492 | 1493 | /** 1494 | * Setting property into registry 1495 | * @param name 1496 | * @returns {*|null} 1497 | */ 1498 | twee.prototype.get = function(name) { 1499 | return this.__registry[name] || null; 1500 | }; 1501 | 1502 | /** 1503 | * Getting property from registry 1504 | * @param name 1505 | * @param value 1506 | * @returns {twee} 1507 | */ 1508 | twee.prototype.set = function(name, value) { 1509 | name = String(name || '').trim(); 1510 | if (!name) { 1511 | throw new Error('Object name should be non-empty string'); 1512 | } 1513 | this.__registry[name] = value; 1514 | return this; 1515 | }; 1516 | 1517 | module.exports = function() { 1518 | return (new twee); 1519 | }; 1520 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "twee", 3 | "preferGlobal": false, 4 | "version": "0.1.31", 5 | "author": "Dmitri Mesin ", 6 | "homepage": "https://github.com/tweeio/twee-framework", 7 | "description": "Twee Framework - the most powerful, elegant and extensive framework for Node.js based on Express.js", 8 | "license": "MIT", 9 | "bin": { 10 | "twee": "./bin/twee.js", 11 | "twee-translate": "./bin/twee-translate.js" 12 | }, 13 | "dependencies": { 14 | "express": "*", 15 | "path": "*", 16 | "colors": "*", 17 | "debug": "*", 18 | "events": "*", 19 | "commander": "*", 20 | "fs-extra": "*", 21 | "file": "*", 22 | "sha1": "*" 23 | }, 24 | "main": "index.js", 25 | "directories": { 26 | "test": "tests" 27 | }, 28 | "devDependencies": {}, 29 | "scripts": { 30 | "test": "NODE_PATH=/var/tmp/mochatweetestapp/node_modules mocha tests" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "https://github.com/tweeio/twee-framework.git" 35 | }, 36 | "keywords": [ 37 | "node.js", 38 | "node", 39 | "io.js", 40 | "socket", 41 | "twee", 42 | "translate", 43 | "generate", 44 | "framework", 45 | "web", 46 | "router", 47 | "app", 48 | "api", 49 | "rest", 50 | "restful", 51 | "mvc", 52 | "modules", 53 | "controllers", 54 | "views", 55 | "extensions" 56 | ], 57 | "bugs": { 58 | "url": "https://github.com/tweeio/twee-framework/issues" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /templates/application/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "public/components/vendor/", 3 | "analytics": false, 4 | "timeout": 120000 5 | } 6 | -------------------------------------------------------------------------------- /templates/application/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | atlassian-ide-plugin.xml 3 | .idea 4 | npm-debug.log -------------------------------------------------------------------------------- /templates/application/.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | atlassian-ide-plugin.xml 3 | .idea 4 | npm-debug.log -------------------------------------------------------------------------------- /templates/application/Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | require('load-grunt-tasks')(grunt); 3 | 4 | // Running notify 5 | grunt.task.run('notify_hooks'); 6 | 7 | //build scripts 8 | grunt.registerTask('default', ['assets', 'watch']); 9 | grunt.registerTask('assets', ['bower', 'build']); 10 | grunt.registerTask('build', ['concat', 'uglify', 'cssmin', 'copy']); 11 | 12 | var defaultConfig = { 13 | pkg: grunt.file.readJSON('package.json') 14 | }; 15 | 16 | var twee = require('twee')().setBaseDirectory(__dirname); 17 | defaultConfig = twee.extend(true, defaultConfig, twee.collectGruntConfigs()); 18 | 19 | grunt.initConfig(defaultConfig); 20 | }; 21 | -------------------------------------------------------------------------------- /templates/application/application.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('twee')().run(); 3 | -------------------------------------------------------------------------------- /templates/application/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "_Twee-App-Name_", 3 | "version": "0.0.1", 4 | "ignore": [ 5 | ".jshintrc", 6 | "**/*.txt" 7 | ], 8 | "dependencies": { 9 | "jquery": "*", 10 | "bootstrap": "*", 11 | "html5shiv": "*", 12 | "respond": "*", 13 | "jquery-placeholder": "*" 14 | }, 15 | "devDependencies": {} 16 | } 17 | -------------------------------------------------------------------------------- /templates/application/configs/development/twee.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extensions": {} 3 | }; -------------------------------------------------------------------------------- /templates/application/configs/grunt.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "bower": { 3 | "install": { 4 | "options": { 5 | "targetDir": "./public/components/vendor/", 6 | "layout": "byType", 7 | "install": true, 8 | "verbose": true, 9 | "cleanTargetDir": false, 10 | "cleanBowerDir": false, 11 | "bowerOptions": {} 12 | } 13 | } 14 | }, 15 | "jshint": { 16 | "modules": { 17 | "files": { 18 | "src": ["Gruntfile.js", "modules/**/*.js", "!**node_modules**"] 19 | } 20 | } 21 | }, 22 | "concat": { 23 | "options": { 24 | "sourceMap": true 25 | }, 26 | "js-core": { 27 | "src": [ 28 | "public/components/vendor/jquery/dist/jquery.js", 29 | "public/components/vendor/bootstrap/dist/js/bootstrap.js" 30 | ], 31 | "dest": "public/build/<%= pkg.version %>/js/core.<%= pkg.version %>.js" 32 | }, 33 | "js-ie-support": { 34 | "src": [ 35 | "public/components/vendor/html5shiv/dist/html5shiv.js", 36 | "public/components/vendor/respond/dest/respond.js", 37 | "public/components/vendor/jquery-placeholder/jquery.placeholder.js", 38 | "public/js/ie10-viewport-fix.js" 39 | ], 40 | "dest": "public/build/<%= pkg.version %>/js/ie-support.<%= pkg.version %>.js" 41 | }, 42 | "css-core": { 43 | "src": [ 44 | "public/components/vendor/bootstrap/dist/css/bootstrap.css", 45 | "public/css/custom.css", 46 | "public/css/bootstrap.css" 47 | ], 48 | "dest": "public/build/<%= pkg.version %>/css/core.<%= pkg.version %>.css" 49 | } 50 | }, 51 | uglify: { 52 | core: { 53 | files: { 54 | 'public/build/<%= pkg.version %>/js/core.<%= pkg.version %>.min.js': ['public/build/<%= pkg.version %>/js/core.<%= pkg.version %>.js'] 55 | } 56 | }, 57 | ie_support: { 58 | files: { 59 | "public/build/<%= pkg.version %>/js/ie-support.<%= pkg.version %>.min.js": ["public/build/<%= pkg.version %>/js/ie-support.<%= pkg.version %>.js"] 60 | } 61 | }, 62 | options: { 63 | compress: true 64 | } 65 | }, 66 | cssmin: { 67 | core: { 68 | files: { 69 | 'public/build/<%= pkg.version %>/css/core.<%= pkg.version %>.min.css': ['public/build/<%= pkg.version %>/css/core.<%= pkg.version %>.css'] 70 | } 71 | } 72 | }, 73 | copy: { 74 | bootstrap_media: { 75 | files: [ 76 | { 77 | expand: true, 78 | flatten: true, 79 | //filter: 'isFile', 80 | src: ['public/components/vendor/bootstrap/fonts/*.*'], 81 | dest: 'public/build/<%= pkg.version %>/fonts/' 82 | } 83 | ] 84 | } 85 | }, 86 | watch: { 87 | options: { 88 | // For grunt-contrib-watch v0.5.0+, "nospawn: true" for lower versions. 89 | // Without this option specified express won't be reloaded 90 | spawn: false, 91 | interrupt: true 92 | }, 93 | code: { 94 | files: [ 95 | 'modules/**/controllers/*.*', 96 | 'modules/**/controllers/**/*.*', 97 | 'modules/**/extensions/*.*', 98 | 'modules/**/extensions/**/*.*', 99 | 'modules/**/middleware/*.*', 100 | 'modules/**/middleware/**/*.*', 101 | 'modules/**/models/*.*', 102 | 'modules/**/models/**/*.*', 103 | 'modules/**/setup/*.*', 104 | 'modules/**/setup/**/*.*', 105 | 'modules/**/setup/**/**/*.*', 106 | 'configs/*.*', 107 | 'configs/**/*.*', 108 | 'configs/**/**/*.*', 109 | 'configs/**/**/**/*.*', 110 | '!public/build' 111 | //'!**node_modules**', 112 | //'!**.git**', 113 | //'!**.idea**' 114 | ], 115 | tasks: ['express:dev', 'notify:restarted'] 116 | }, 117 | assets: { 118 | files: [ 119 | 'modules/**/assets/*.*', 120 | 'modules/**/assets/**/*.*', 121 | 'modules/**/assets/**/**/*.*', 122 | 'public/*.*', 123 | 'public/**/*.*', 124 | 'public/**/**/*.*', 125 | 'public/**/**/**/*.*', 126 | '!public/build' 127 | //'!**.git**', 128 | //'!**.idea**' 129 | ], 130 | tasks: ['notify:start_assets', 'build', 'notify:assets', 'express:dev'] 131 | } 132 | }, 133 | express: { 134 | options: { 135 | script: 'application.js', 136 | background: true 137 | }, 138 | dev: { 139 | options: { 140 | port: 3000, 141 | node_env: 'development' 142 | } 143 | } 144 | }, 145 | notify: { 146 | restarted: { 147 | options: { 148 | title: 'Twee Grunt', 149 | message: 'Server Restarted' 150 | } 151 | }, 152 | assets: { 153 | options: { 154 | title: 'Twee Grunt', 155 | message: 'Assets Processed' 156 | } 157 | }, 158 | start_assets: { 159 | options: { 160 | title: 'Twee Grunt', 161 | message: 'Starting Assets Processing..' 162 | } 163 | } 164 | } 165 | }; 166 | -------------------------------------------------------------------------------- /templates/application/configs/modules.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "_Twee-MNT_": { 3 | "disabled": false, 4 | "prefix": "/" 5 | } 6 | }; -------------------------------------------------------------------------------- /templates/application/configs/production/twee.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extensions": {} 3 | }; -------------------------------------------------------------------------------- /templates/application/configs/testing/twee.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extensions": {} 3 | }; -------------------------------------------------------------------------------- /templates/application/configs/twee.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extensions": { 3 | "i18n": { 4 | "module": "twee-i18n-extension", 5 | "dependencies": { 6 | "Cookies": { 7 | "module": "twee-cookies-extension" 8 | } 9 | } 10 | }, 11 | "HTML Compressor": { 12 | dependencies: { 13 | "Session": { 14 | "disabled": true 15 | } 16 | } 17 | } 18 | }, 19 | "options": { 20 | "errorPages": { 21 | "404": { 22 | "viewTemplate": __dirname + "/../views/common/pages/404.html" 23 | } 24 | } 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /templates/application/i18n/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "Module": "Module", 3 | "Toggle navigation": "Toggle navigation", 4 | "Try URL for Extension: ": "Try URL for Extension: ", 5 | "View": "View", 6 | "Twee Project": "Twee.io Project", 7 | "Twee Framework": "Twee.io Framework", 8 | "Welcome to Twee Starter Page": "Welcome to Twee Starter Page", 9 | "Use this document as a way to quickly start any new project.
All you get is this text and a mostly barebones HTML document.": "Use this document as a way to quickly start any new project.
All you get is this text and a mostly barebones HTML document.", 10 | "Your Project Name": "Your Project Name", 11 | "Home": "Home", 12 | "Hello {{name}}": "Hello {{name}}", 13 | "Starter Template for Bootstrap": "Starter Template for Bootstrap", 14 | "Helper Usage: ": "Helper Usage: ", 15 | "In View: ": "In View: ", 16 | "Middleware Message": "Middleware Message", 17 | "Middleware": "Middleware", 18 | "From Controller: ": "From Controller: ", 19 | "Extension": "Extension", 20 | "Documentation": "Documentation", 21 | "Controller": "Controller", 22 | "Bootstrap starter template": "Bootstrap starter template", 23 | "Action": "Action", 24 | "This is simple extension!": "This is simple extension!", 25 | "This is simple middleware": "This is simple middleware", 26 | "Page Not Found!": "Page Not Found!", 27 | "The page you are looking for is not found!": "The page you are looking for is not found!", 28 | "All configuration:": "All config dump:" 29 | } -------------------------------------------------------------------------------- /templates/application/i18n/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "Use this document as a way to quickly start any new project.
All you get is this text and a mostly barebones HTML document.": "Используйте этот шаблон как быстрый способ началь новый проект", 3 | "Middleware Message": "Сообщение из обработчика", 4 | "Your Project Name": "Назвние проекта", 5 | "View": "Вьюшка", 6 | "Welcome to Twee Starter Page": "Добро пожаловать на стартовую страницу проекта", 7 | "Toggle navigation": "Переключить навигацию", 8 | "Module": "Модуль", 9 | "Twee Framework": "Фреймворк Twee.io", 10 | "Starter Template for Bootstrap": "Начальный шаблон на Bootstrap", 11 | "Twee Project": "Проект Twee.io", 12 | "Try URL for Extension: ": "Попробовать по URL: ", 13 | "Extension": "Расширение", 14 | "Hello {{name}}": "Привет {{name}}", 15 | "In View: ": "Из вьюшки: ", 16 | "Home": "Домой", 17 | "Helper Usage: ": "Использование хелпера: ", 18 | "Middleware": "Обработчик", 19 | "From Controller: ": "Из контроллера: ", 20 | "Documentation": "Документация", 21 | "Controller": "Контроллер", 22 | "Bootstrap starter template": "Стартовый шаблон на Bootstrap", 23 | "Action": "Экшн", 24 | "This is simple extension!": "Это простейшее расширение!", 25 | "This is simple middleware": "Это простой обработчик", 26 | "Page Not Found!": "Страница не найдена!", 27 | "The page you are looking for is not found!": "Страница, которую вы ищете, не найдена!", 28 | "All configuration:": "Дамп всего конфига:" 29 | } 30 | -------------------------------------------------------------------------------- /templates/application/modules/_Twee-MNT_/assets/css/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tweeio/twee-framework/8aeca10b54cd01a6fbc542581c5f7f40b5dc88f2/templates/application/modules/_Twee-MNT_/assets/css/.empty -------------------------------------------------------------------------------- /templates/application/modules/_Twee-MNT_/assets/css/_Twee-MNT-LC_-module.css: -------------------------------------------------------------------------------- 1 | body{ 2 | background-color: white; 3 | } 4 | -------------------------------------------------------------------------------- /templates/application/modules/_Twee-MNT_/assets/fonts/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tweeio/twee-framework/8aeca10b54cd01a6fbc542581c5f7f40b5dc88f2/templates/application/modules/_Twee-MNT_/assets/fonts/.empty -------------------------------------------------------------------------------- /templates/application/modules/_Twee-MNT_/assets/img/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tweeio/twee-framework/8aeca10b54cd01a6fbc542581c5f7f40b5dc88f2/templates/application/modules/_Twee-MNT_/assets/img/.empty -------------------------------------------------------------------------------- /templates/application/modules/_Twee-MNT_/assets/js/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tweeio/twee-framework/8aeca10b54cd01a6fbc542581c5f7f40b5dc88f2/templates/application/modules/_Twee-MNT_/assets/js/.empty -------------------------------------------------------------------------------- /templates/application/modules/_Twee-MNT_/assets/js/_Twee-MNT-LC_-module.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | // Place your module code here. 3 | // It will run after application-scope code 4 | }); -------------------------------------------------------------------------------- /templates/application/modules/_Twee-MNT_/controllers/_Twee-MNT_Controller.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * @type Object _Twee-MNT_ Controller 5 | */ 6 | module.exports = function () { 7 | /** 8 | * Main Page 9 | * 10 | * @param req 11 | * @param res 12 | */ 13 | this.indexAction = function (req, res) { 14 | var self = this; 15 | res.render('_Twee-MNT_/views/pages/_Twee-MNT_/index', { 16 | message: res.defaultMiddlewareMessage || '', 17 | variable: twee.getConfig('_Twee-MNT-LC_:common:variable') 18 | }); 19 | }; 20 | 21 | /** 22 | * Bootstrap Styles Page 23 | * @param req 24 | * @param res 25 | */ 26 | this.bootstrapAction = function (req, res) { 27 | var self = this; 28 | res.render('_Twee-MNT_/views/pages/_Twee-MNT_/bootstrap'); 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /templates/application/modules/_Twee-MNT_/extensions/_Twee-MNT_Extension.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * Extension that shows how it works 5 | */ 6 | module.exports.extension = function(){ 7 | twee.getApplication().all('/extension', function(req, res){ 8 | res.json({response: tr('This is simple extension!')}); 9 | }); 10 | 11 | if (!twee.helper.hello) { 12 | twee.registerViewHelper('hello', function(name){ 13 | return tr("Hello {{name}}", {name: name}); 14 | }); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /templates/application/modules/_Twee-MNT_/middleware/LanguageMiddleware.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Switching language 3 | * 4 | * @param request 5 | * @param response 6 | * @param next 7 | */ 8 | module.exports.switchLanguage = function(request, response, next) { 9 | if (request.query.lang) { 10 | response.cookie('locale', request.query.lang, { maxAge: 9999999999999, path: '*.' }); 11 | request.setLocale(request.query.lang); 12 | } 13 | 14 | next(); 15 | } -------------------------------------------------------------------------------- /templates/application/modules/_Twee-MNT_/middleware/_Twee-MNT_Middleware.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Example of simple middleware 3 | * 4 | * @param request 5 | * @param response 6 | * @param next 7 | */ 8 | module.exports._Twee-MNT-LC_Middleware = function(request, response, next) { 9 | response.defaultMiddlewareMessage = tr('This is simple middleware'); 10 | next(); 11 | }; 12 | -------------------------------------------------------------------------------- /templates/application/modules/_Twee-MNT_/models/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tweeio/twee-framework/8aeca10b54cd01a6fbc542581c5f7f40b5dc88f2/templates/application/modules/_Twee-MNT_/models/.empty -------------------------------------------------------------------------------- /templates/application/modules/_Twee-MNT_/params/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tweeio/twee-framework/8aeca10b54cd01a6fbc542581c5f7f40b5dc88f2/templates/application/modules/_Twee-MNT_/params/.empty -------------------------------------------------------------------------------- /templates/application/modules/_Twee-MNT_/params/RangeParam.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Simplest Range Param Example 3 | * @type {RegExp} 4 | */ 5 | module.exports = function(req, res, next, range) { 6 | if (range.match(/^(\d+)-(\d+)$/)) { 7 | return next(); 8 | } 9 | 10 | next('route') 11 | } -------------------------------------------------------------------------------- /templates/application/modules/_Twee-MNT_/setup/configs/common.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "variable": "This value from common config" 3 | }; 4 | -------------------------------------------------------------------------------- /templates/application/modules/_Twee-MNT_/setup/configs/development/common.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "variable": "This dev value from common config" 3 | }; 4 | -------------------------------------------------------------------------------- /templates/application/modules/_Twee-MNT_/setup/configs/grunt.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "concat": { 3 | "_Twee-MNT-LC_-js": { 4 | "src": [ 5 | "modules/_Twee-MNT-LC_/assets/js/*.js", 6 | "modules/_Twee-MNT-LC_/assets/js/**/*.js" 7 | ], 8 | "dest": "public/build/<%= pkg.version %>/js/_Twee-MNT-LC_-module.<%= pkg.version %>.js" 9 | }, 10 | "_Twee-MNT-LC_-css": { 11 | "src": [ 12 | "modules/_Twee-MNT-LC_/assets/css/*.css", 13 | "modules/_Twee-MNT-LC_/assets/css/**/*.css" 14 | ], 15 | "dest": "public/build/<%= pkg.version %>/css/_Twee-MNT-LC_-module.<%= pkg.version %>.css" 16 | } 17 | }, 18 | uglify: { 19 | "_Twee-MNT-LC_": { 20 | files: { 21 | 'public/build/<%= pkg.version %>/js/_Twee-MNT-LC_-module.<%= pkg.version %>.min.js': ['public/build/<%= pkg.version %>/js/_Twee-MNT-LC_-module.<%= pkg.version %>.js'] 22 | } 23 | }, 24 | options: { 25 | compress: true 26 | } 27 | }, 28 | cssmin: { 29 | "_Twee-MNT-LC_": { 30 | files: { 31 | 'public/build/<%= pkg.version %>/css/_Twee-MNT-LC_-module.<%= pkg.version %>.min.css': ['public/build/<%= pkg.version %>/css/_Twee-MNT-LC_-module.<%= pkg.version %>.css'] 32 | } 33 | } 34 | }, 35 | copy: { 36 | "_Twee-MNT-LC_-img": { 37 | files: [ 38 | { 39 | expand: true, 40 | flatten: true, 41 | //filter: 'isFile', 42 | src: ["modules/_Twee-MNT-LC_/assets/img/*.*"], 43 | dest: 'public/build/<%= pkg.version %>/img/_Twee-MNT-LC_' 44 | } 45 | ] 46 | }, 47 | "_Twee-MNT-LC_-fonts": { 48 | files: [ 49 | { 50 | expand: true, 51 | flatten: true, 52 | //filter: 'isFile', 53 | src: ["modules/_Twee-MNT-LC_/assets/fonts/*.*"], 54 | dest: 'public/build/<%= pkg.version %>/fonts/_Twee-MNT-LC_' 55 | } 56 | ] 57 | } 58 | } 59 | }; 60 | -------------------------------------------------------------------------------- /templates/application/modules/_Twee-MNT_/setup/configs/production/common.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "variable": "This prod value from common config" 3 | }; 4 | -------------------------------------------------------------------------------- /templates/application/modules/_Twee-MNT_/setup/configs/testing/common.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "variable": "This test value from common config" 3 | }; 4 | -------------------------------------------------------------------------------- /templates/application/modules/_Twee-MNT_/setup/setup.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "params": { 3 | "id": /^\d+$/, 4 | "range": { 5 | "file": "RangeParam" 6 | } 7 | }, 8 | "middleware": { 9 | "description": "These are globally installed middleware functions", 10 | "head": [ 11 | // Middleware Example 12 | { 13 | "name": "_Twee-MNT_ Middleware", 14 | "file": "_Twee-MNT_Middleware", 15 | "method": "_Twee-MNT-LC_Middleware" 16 | }, 17 | // Middleware for UI translating 18 | { 19 | "name": "SwitchLanguage", 20 | "file": "LanguageMiddleware", 21 | "method": "switchLanguage" 22 | } 23 | ], 24 | "tail": [] 25 | }, 26 | "extensions": { 27 | // Dummy Extension Example 28 | "_Twee-MNT_ Extensions": { 29 | "file": "_Twee-MNT_Extension" 30 | } 31 | }, 32 | "routes": [ 33 | { 34 | "description": "Entry point for application. Landing page", 35 | // Example of pattern with not-required `range` param 36 | // Try URLs: /123-456 and /123-a456 37 | "pattern": "/:range?", 38 | "controllers": ["_Twee-MNT_Controller.indexAction"], 39 | "middleware": { 40 | "before": [], 41 | "after": [] 42 | } 43 | }, 44 | { 45 | "description": "Bootstrap Styles Page", 46 | // Check in URL without ID and with different ID (INT and not INT): 47 | // /bootstrap/a123 and /bootstrap/123 48 | "pattern": "/bootstrap/:id?", 49 | "controllers": ["_Twee-MNT_Controller.bootstrapAction"], 50 | "middleware": { 51 | "before": [], 52 | "after": [] 53 | } 54 | } 55 | ] 56 | }; 57 | -------------------------------------------------------------------------------- /templates/application/modules/_Twee-MNT_/views/pages/_Twee-MNT_/bootstrap.html: -------------------------------------------------------------------------------- 1 | {% extends '../../../../../views/layouts/layout.base.html' %} 2 | 3 | {% block content %} 4 |
5 |
6 |
7 | 10 | 11 |
12 | 56 |
57 | 58 |
59 | 103 |
104 | 105 |
106 |
107 |
108 | 109 |
110 | 117 | 118 |
119 |
120 | 121 |

122 | Default 123 | Primary 124 | Success 125 | Info 126 | Warning 127 | Danger 128 | Link 129 |

130 | 131 |

132 | Default 133 | Primary 134 | Success 135 | Info 136 | Warning 137 | Danger 138 | Link 139 |

140 | 141 | 142 |
143 |
144 |
145 | Default 146 | 147 | 154 |
155 | 156 |
157 | Primary 158 | 159 | 166 |
167 | 168 |
169 | Success 170 | 171 | 178 |
179 | 180 |
181 | Info 182 | 183 | 190 |
191 | 192 |
193 | Warning 194 | 195 | 202 |
203 |
204 |
205 | 206 |

207 | Large button 208 | Default button 209 | Small button 210 | Mini button 211 |

212 | 213 |
214 |
215 | 216 |

217 | Block level button 218 |

219 | 220 | 221 |
222 |
223 | Left 224 | Middle 225 | Right 226 |
227 |
228 | 229 |
230 |
231 |
232 | 1 233 | 2 234 | 3 235 | 4 236 |
237 | 238 |
239 | 5 240 | 6 241 | 7 242 |
243 | 244 |
245 | 8 246 |
247 | 248 | Dropdown 249 | 250 | 251 | 256 |
257 |
258 |
259 |
260 | 261 |
262 |
263 | Button 264 | Button 265 | Button 266 | Button 267 |
268 |
269 | 270 |
271 |
272 |
273 | 274 |
275 |
276 |
277 | 280 |
281 |
282 | 283 | 284 | 285 |
286 |
287 |
288 |

Heading 1

289 |

Heading 2

290 |

Heading 3

291 |

Heading 4

292 |
Heading 5
293 |
Heading 6
294 |

Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.

295 |
296 |
297 |
298 |
299 |

Example body text

300 |

Nullam quis risus eget urna mollis ornare vel eu leo. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam id dolor id nibh ultricies vehicula.

301 |

This line of text is meant to be treated as fine print.

302 |

The following snippet of text is rendered as bold text.

303 |

The following snippet of text is rendered as italicized text.

304 |

An abbreviation of the word attribute is attr.

305 |
306 | 307 |
308 |
309 |
310 |

Emphasis classes

311 |

Fusce dapibus, tellus ac cursus commodo, tortor mauris nibh.

312 |

Nullam id dolor id nibh ultricies vehicula ut id elit.

313 |

Etiam porta sem malesuada magna mollis euismod.

314 |

Donec ullamcorper nulla non metus auctor fringilla.

315 |

Duis mollis, est non commodo luctus, nisi erat porttitor ligula.

316 |

Maecenas sed diam eget risus varius blandit sit amet non magna.

317 |
318 | 319 |
320 |
321 | 322 | 323 | 324 |
325 |
326 |

Blockquotes

327 |
328 |
329 |
330 |
331 |
332 |
333 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

334 | Someone famous in Source Title 335 |
336 |
337 |
338 |
339 |
340 |
341 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

342 | Someone famous in Source Title 343 |
344 |
345 |
346 |
347 |
348 | 349 |
350 | 351 |
352 |
353 | 356 | 357 |
358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 |
#Column headingColumn headingColumn heading
1Column contentColumn contentColumn content
2Column contentColumn contentColumn content
3Column contentColumn contentColumn content
4Column contentColumn contentColumn content
5Column contentColumn contentColumn content
6Column contentColumn contentColumn content
7Column contentColumn contentColumn content
412 |
413 |
414 |
415 |
416 | 417 |
418 |
419 |
420 | 423 |
424 |
425 | 426 |
427 |
428 |
429 |
430 |
431 | Legend 432 |
433 | 434 |
435 | 436 |
437 |
438 |
439 | 440 |
441 | 442 |
443 | 446 |
447 |
448 |
449 |
450 | 451 |
452 | 453 | A longer block of help text that breaks onto a new line and may extend beyond one line. 454 |
455 |
456 |
457 | 458 |
459 |
460 | 464 |
465 |
466 | 470 |
471 |
472 |
473 |
474 | 475 |
476 | 483 |
484 | 491 |
492 |
493 |
494 |
495 | 496 | 497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 | 505 |
506 |
507 | 508 | 509 |
510 | 511 |
512 | 513 | 514 |
515 | 516 |
517 | 518 | 519 |
520 | 521 |
522 | 523 | 524 |
525 | 526 |
527 | 528 | 529 |
530 | 531 |
532 | 533 | 534 |
535 | 536 |
537 | 538 | 539 |
540 | 541 |
542 | 543 | 544 |
545 | 546 |
547 | 548 |
549 | $ 550 | 551 | 552 | 553 | 554 |
555 |
556 |
557 | 558 |
559 |
560 |
561 | 562 |
563 | 564 |
565 |
566 | 569 |
570 |
571 | 572 |
573 |
574 | 575 |
576 | 591 |
592 |
593 |

Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.

594 |
595 |
596 |

Food truck fixie locavore, accusamus mcsweeney's marfa nulla single-origin coffee squid. Exercitation +1 labore velit, blog sartorial PBR leggings next level wes anderson artisan four loko farm-to-table craft beer twee. Qui photo booth letterpress, commodo enim craft beer mlkshk aliquip jean shorts ullamco ad vinyl cillum PBR. Homo nostrud organic, assumenda labore aesthetic magna delectus mollit.

597 |
598 | 601 | 604 |
605 |
606 |
607 |
608 | 609 |
610 | 627 |
628 |
629 |
630 | 647 |
648 |
649 |
650 | 651 |
652 | 655 | 656 | 660 | 661 | 666 |
667 | 668 |
669 |
670 | 671 | 672 |
673 |
674 |

Pagination

675 |
676 |
    677 |
  • «
  • 678 |
  • 1
  • 679 |
  • 2
  • 680 |
  • 3
  • 681 |
  • 4
  • 682 |
  • 5
  • 683 |
  • »
  • 684 |
685 | 686 |
    687 |
  • «
  • 688 |
  • 1
  • 689 |
  • 2
  • 690 |
  • 3
  • 691 |
  • »
  • 692 |
693 | 694 |
    695 |
  • «
  • 696 |
  • 1
  • 697 |
  • 2
  • 698 |
  • 3
  • 699 |
  • 4
  • 700 |
  • 5
  • 701 |
  • »
  • 702 |
703 |
704 |
705 |
706 |

Pager

707 |
708 | 712 | 713 | 717 |
718 |
719 |
720 | 721 |
722 |
723 |
724 | 725 |
726 | 727 |
728 |
729 | 732 |
733 |
734 | 735 |
736 |
737 |

Alerts

738 |
739 |
740 | 741 |

Warning!

742 |

Best check yo self, you're not looking too good. Nulla vitae elit libero, a pharetra augue. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

743 |
744 |
745 |
746 |
747 |
748 |
749 |
750 |
751 | 752 | Oh snap! Change a few things up and try submitting again. 753 |
754 |
755 |
756 |
757 |
758 |
759 | 760 | Well done! You successfully read this important alert message. 761 |
762 |
763 |
764 |
765 |
766 |
767 | 768 | Heads up! This alert needs your attention, but it's not super important. 769 |
770 |
771 |
772 |
773 |
774 |
775 |

Labels

776 |
777 | Default 778 | Primary 779 | Success 780 | Warning 781 | Danger 782 | Info 783 |
784 |
785 |
786 |

Badges

787 |
788 | 793 |
794 |
795 |
796 |
797 | 798 |
799 | 800 |
801 |
802 | 805 | 806 |

Basic

807 |
808 |
809 |
810 |
811 |
812 | 813 |

Contextual alternatives

814 |
815 |
816 |
817 |
818 | 819 |
820 |
821 |
822 | 823 |
824 |
825 |
826 | 827 |
828 |
829 |
830 |
831 | 832 |

Striped

833 |
834 |
835 |
836 |
837 | 838 |
839 |
840 |
841 | 842 |
843 |
844 |
845 | 846 |
847 |
848 |
849 |
850 | 851 |

Animated

852 |
853 |
854 |
855 |
856 |
857 | 858 |

Stacked

859 |
860 |
861 |
862 |
863 |
864 |
865 |
866 |
867 |
868 |
869 | 870 |
871 | 872 |
873 |
874 | 877 |
878 |
879 |

Jumbotron

880 |

This is a simple hero unit, a simple jumbotron-style component for calling extra attention to featured content or information.

881 |

Learn more

882 |
883 |
884 |
885 |
886 | 887 | 888 |
889 |
890 |

List groups

891 |
892 |
893 |
894 |
895 |
896 |
    897 |
  • 898 | 14 899 | Cras justo odio 900 |
  • 901 |
  • 902 | 2 903 | Dapibus ac facilisis in 904 |
  • 905 |
  • 906 | 1 907 | Morbi leo risus 908 |
  • 909 |
910 |
911 |
912 | 925 | 939 |
940 | 941 | 942 |
943 |
944 |

Panels

945 |
946 |
947 |
948 |
949 |
950 |
951 |
952 | Basic panel 953 |
954 |
955 | 956 |
957 |
Panel heading
958 |
959 | Panel content 960 |
961 |
962 | 963 |
964 |
965 | Panel content 966 |
967 | 968 |
969 |
970 |
971 |
972 |
973 |
974 |
975 |

Panel primary

976 |
977 |
978 | Panel content 979 |
980 |
981 | 982 |
983 |
984 |

Panel success

985 |
986 |
987 | Panel content 988 |
989 |
990 | 991 |
992 |
993 |

Panel warning

994 |
995 |
996 | Panel content 997 |
998 |
999 |
1000 |
1001 |
1002 |
1003 |
1004 |
1005 |

Panel danger

1006 |
1007 |
1008 | Panel content 1009 |
1010 |
1011 | 1012 |
1013 |
1014 |

Panel info

1015 |
1016 |
1017 | Panel content 1018 |
1019 |
1020 |
1021 |
1022 |
1023 | 1024 |
1025 |
1026 |

Wells

1027 |
1028 |
1029 |
1030 |
1031 |
1032 |
1033 | Look, I'm in a well! 1034 |
1035 |
1036 |
1037 |
1038 |
1039 |
1040 | Look, I'm in a small well! 1041 |
1042 |
1043 |
1044 |
1045 |
1046 |
1047 | Look, I'm in a large well! 1048 |
1049 |
1050 |
1051 |
1052 |
1053 | 1054 |
1055 | 1056 |
1057 |
1058 | 1061 |
1062 |
1063 |

Jumbotron

1064 |

This is a simple hero unit, a simple jumbotron-style component for calling extra attention to featured content or information.

1065 |

Learn more

1066 |
1067 |
1068 |
1069 |
1070 | 1071 | 1072 |
1073 |
1074 |

List groups

1075 |
1076 |
1077 |
1078 |
1079 |
1080 |
    1081 |
  • 1082 | 14 1083 | Cras justo odio 1084 |
  • 1085 |
  • 1086 | 2 1087 | Dapibus ac facilisis in 1088 |
  • 1089 |
  • 1090 | 1 1091 | Morbi leo risus 1092 |
  • 1093 |
1094 |
1095 |
1096 | 1109 | 1123 |
1124 | 1125 | 1126 |
1127 |
1128 |

Panels

1129 |
1130 |
1131 |
1132 |
1133 |
1134 |
1135 |
1136 | Basic panel 1137 |
1138 |
1139 | 1140 |
1141 |
Panel heading
1142 |
1143 | Panel content 1144 |
1145 |
1146 | 1147 |
1148 |
1149 | Panel content 1150 |
1151 | 1152 |
1153 |
1154 |
1155 |
1156 |
1157 |
1158 |
1159 |

Panel primary

1160 |
1161 |
1162 | Panel content 1163 |
1164 |
1165 | 1166 |
1167 |
1168 |

Panel success

1169 |
1170 |
1171 | Panel content 1172 |
1173 |
1174 | 1175 |
1176 |
1177 |

Panel warning

1178 |
1179 |
1180 | Panel content 1181 |
1182 |
1183 |
1184 |
1185 |
1186 |
1187 |
1188 |
1189 |

Panel danger

1190 |
1191 |
1192 | Panel content 1193 |
1194 |
1195 | 1196 |
1197 |
1198 |

Panel info

1199 |
1200 |
1201 | Panel content 1202 |
1203 |
1204 |
1205 |
1206 |
1207 | 1208 |
1209 |
1210 |

Wells

1211 |
1212 |
1213 |
1214 |
1215 |
1216 |
1217 | Look, I'm in a well! 1218 |
1219 |
1220 |
1221 |
1222 |
1223 |
1224 | Look, I'm in a small well! 1225 |
1226 |
1227 |
1228 |
1229 |
1230 |
1231 | Look, I'm in a large well! 1232 |
1233 |
1234 |
1235 |
1236 |
1237 | 1238 | 1251 | {% endblock %} 1252 | 1253 | {% block js %} 1254 | {% parent %} 1255 | {% if settings.env == 'production' %} 1256 | 1257 | {% else %} 1258 | 1259 | {% endif %} 1260 | {% endblock js %} 1261 | 1262 | {% block css %} 1263 | {% parent %} 1264 | {% if settings.env == 'production' %} 1265 | 1266 | {% else %} 1267 | 1268 | {% endif %} 1269 | {% endblock css %} 1270 | -------------------------------------------------------------------------------- /templates/application/modules/_Twee-MNT_/views/pages/_Twee-MNT_/index.html: -------------------------------------------------------------------------------- 1 | {% extends '../../../../../views/layouts/layout.base.html' %} 2 | 3 | {% block content %} 4 |
5 |
6 |

{{ tr('Welcome to Twee Starter Page') }}

7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
{{ tr('Module') }}
modules/_Twee-MNT_
{{ tr('Controller') }}
modules/_Twee-MNT_/controllers/_Twee-MNT_Controller.js
{{ tr('Action') }}
_Twee-MNT_Controller.indexAction
{{ tr('View') }}
modules/_Twee-MNT_/views/pages/_Twee-MNT_/index.html
{{ tr('Extension') }}
39 | modules/_Twee-MNT_/extensions/_Twee-MNT_Extension.js 40 |
{{ tr('Try URL for Extension: ') }}extension 41 |
{{ tr('Middleware') }}
modules/_Twee-MNT_/middleware/_Twee-MNT_Middleware.js
{{ tr('Middleware Message') }}
{{ message }}
{{ tr('In View: ') }}
{{ twee.getConfig('default:common:variable') }}
{{ twee.getConfig('default:common:variable') }}
{{ tr('From Controller: ') }}{{ variable }}
{{ variable }}
{{ tr('Helper Usage: ') }}{{ helper.hello('Twee') }}
{{ helper.hello('Twee') }}
75 | 76 | 77 |

{{ tr('All configuration:') }}

78 |
{{ JSON.stringify(twee.__config, null, '    ') }}
79 |
80 |
81 |
82 | {% endblock %} 83 | 84 | {% block js %} 85 | {% parent %} 86 | {% if settings.env == 'production' %} 87 | 88 | {% else %} 89 | 90 | {% endif %} 91 | {% endblock js %} 92 | 93 | {% block css %} 94 | {% parent %} 95 | {% if settings.env == 'production' %} 96 | 97 | {% else %} 98 | 99 | {% endif %} 100 | {% endblock css %} -------------------------------------------------------------------------------- /templates/application/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "_Twee-App-Name_", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "pstart": "NODE_PATH=./node_modules NODE_ENV=production PORT=80 node ./application.js", 7 | "ssl-start": "NODE_PATH=./node_modules NODE_ENV=production PORT=80 sudo node ./application.js", 8 | "start": "NODE_PATH=./node_modules NODE_ENV=development DEBUG=twee.io node ./application.js", 9 | "assets": "NODE_PATH=./node_modules grunt assets", 10 | "watch": "NODE_PATH=./node_modules grunt watch" 11 | }, 12 | "dependencies": { 13 | "twee": ">= __TWEE_VERSION__", 14 | "twee-http-parser-extension": "*", 15 | "twee-logging-extension": "*", 16 | "twee-powered-extension": "*", 17 | "twee-static-extension": "*", 18 | "twee-compressor-extension": "*", 19 | "twee-view-extension": "*", 20 | "twee-i18n-extension": "*", 21 | "twee-cookies-extension": "*", 22 | "swig": "*" 23 | }, 24 | "devDependencies": { 25 | "grunt-contrib-jshint": "*", 26 | "grunt-contrib-concat": "*", 27 | "grunt-contrib-uglify": "*", 28 | "grunt-contrib-copy": "*", 29 | "grunt-contrib-clean": "*", 30 | "grunt-contrib-connect": "*", 31 | "grunt-contrib-watch": "*", 32 | "load-grunt-tasks": "*", 33 | "grunt-contrib-cssmin": "*", 34 | "grunt-aws-s3": "*", 35 | "grunt-notify": "*", 36 | "grunt-bower-task": "*", 37 | "grunt-express-server": "*" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /templates/application/public/css/custom.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | } 4 | .starter-template { 5 | padding: 40px 15px; 6 | text-align: center; 7 | } -------------------------------------------------------------------------------- /templates/application/public/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tweeio/twee-framework/8aeca10b54cd01a6fbc542581c5f7f40b5dc88f2/templates/application/public/img/favicon.ico -------------------------------------------------------------------------------- /templates/application/public/js/ie10-viewport-fix.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * IE10 viewport hack for Surface/desktop Windows 8 bug 3 | * Copyright 2014 Twitter, Inc. 4 | * Licensed under the Creative Commons Attribution 3.0 Unported License. For 5 | * details, see http://creativecommons.org/licenses/by/3.0/. 6 | */ 7 | 8 | // See the Getting Started docs for more information: 9 | // http://getbootstrap.com/getting-started/#support-ie10-width 10 | 11 | (function () { 12 | "use strict"; 13 | if (navigator.userAgent.match(/IEMobile\/10\.0/)) { 14 | var msViewportStyle = document.createElement('style') 15 | msViewportStyle.appendChild( 16 | document.createTextNode( 17 | '@-ms-viewport{width:auto!important}' 18 | ) 19 | ); 20 | document.querySelector('head').appendChild(msViewportStyle) 21 | } 22 | })(); 23 | -------------------------------------------------------------------------------- /templates/application/var/log/access.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tweeio/twee-framework/8aeca10b54cd01a6fbc542581c5f7f40b5dc88f2/templates/application/var/log/access.json -------------------------------------------------------------------------------- /templates/application/var/ssl/localhost.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICeTCCAeICCQDnjZs7AHc26zANBgkqhkiG9w0BAQUFADCBgDELMAkGA1UEBhMC 3 | VVMxEzARBgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAcMCE5ldyBZb3JrMRAwDgYD 4 | VQQKDAd0d2VlLmlvMRAwDgYDVQQDDAd0d2VlLmlvMSUwIwYJKoZIhvcNAQkBFhZk 5 | bWl0cmkubWVzaW5AZ21haWwuY29tMB4XDTE1MDEyMDIxNTEzOFoXDTE2MDEyMDIx 6 | NTEzOFowgYAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApTb21lLVN0YXRlMREwDwYD 7 | VQQHDAhOZXcgWW9yazEQMA4GA1UECgwHdHdlZS5pbzEQMA4GA1UEAwwHdHdlZS5p 8 | bzElMCMGCSqGSIb3DQEJARYWZG1pdHJpLm1lc2luQGdtYWlsLmNvbTCBnzANBgkq 9 | hkiG9w0BAQEFAAOBjQAwgYkCgYEAwpEdykPWUt+eUfxQ8AogxT61h4l2DAiXbwO/ 10 | IHvfE3F34qhjFKCdpG0nLGBL54jbJ5h8Iu1wBNYrIhwD+KqCxfdy+pSUYLeEvqwX 11 | H7S18M4+JX5E3VEA7QVPAlrbBMF4az9+IP3gd5EiRorPjLAyc2dkuIJjeO8ebeyU 12 | cGP2LpcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQAobeb8clCvIDg3KRVQ65+1yOiG 13 | YkHcTttRdgQuN4M7cbPM7qjMAdeLLuSoekP83JlRPJt3VECR/vzS/6HXp8FpVVYj 14 | k4NNEMFbE4sj56II06FCNeDA4tly/g6/T16PHrQUwngt2mhpIw1XPSe4uHf7jyBn 15 | Dmiub3tW0jhiu5RE7Q== 16 | -----END CERTIFICATE----- 17 | -------------------------------------------------------------------------------- /templates/application/var/ssl/localhost.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIB7jCCAVcCAQAwgYAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApTb21lLVN0YXRl 3 | MREwDwYDVQQHDAhOZXcgWW9yazEQMA4GA1UECgwHdHdlZS5pbzEQMA4GA1UEAwwH 4 | dHdlZS5pbzElMCMGCSqGSIb3DQEJARYWZG1pdHJpLm1lc2luQGdtYWlsLmNvbTCB 5 | nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwpEdykPWUt+eUfxQ8AogxT61h4l2 6 | DAiXbwO/IHvfE3F34qhjFKCdpG0nLGBL54jbJ5h8Iu1wBNYrIhwD+KqCxfdy+pSU 7 | YLeEvqwXH7S18M4+JX5E3VEA7QVPAlrbBMF4az9+IP3gd5EiRorPjLAyc2dkuIJj 8 | eO8ebeyUcGP2LpcCAwEAAaAtMBMGCSqGSIb3DQEJBzEGDAQxMjM0MBYGCSqGSIb3 9 | DQEJAjEJDAd0d2VlLmlvMA0GCSqGSIb3DQEBBQUAA4GBACFMLetgRNOElaJNuB84 10 | bQojop/Qu1P9KvG9ET9lDNrRx2Nsc312mSgEDjM5B9x0y+53IuWMJEcsFlq+0/rc 11 | 3O/KY4xkA5ma8Wrt5TY8oGZwFMVB9uIFyf/aV0W2DSja1DnRChQ9nOapCrDvAMsV 12 | ZwiMj1neq4KqPEiXOtPBKogc 13 | -----END CERTIFICATE REQUEST----- 14 | -------------------------------------------------------------------------------- /templates/application/var/ssl/localhost.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: DES-EDE3-CBC,4DEA5F2570883D00 4 | 5 | V6haAQbyCpONb6U5CoweWT8KBwA1a5jE2iaHYmTwrPGMfboy+TlgN2vzKUebQjY3 6 | FfUbld702e1cxgVj8isZhJ+rPfkYNVZWbBSy6T/QSrJYe9lfpPDn+DRlPOgQlmI4 7 | pBoiq6vmH8b2/S8PjLx2xOFRuUSXJ5AIfXYmZlctHXGiqZHOylI8sPATPPau51ka 8 | WXlR1GMzZ/wr7LnlQFfRlfKwVkJKqHiuAfNl6vBkatAmvF5I9yCHKCC+q4/IuZnV 9 | tMo7+TMwrtQSwY9MiVm+FPWL3KLAzJge4wREbrHqENSGA5qhsjsu0i6WuBUzkyMh 10 | bbMoPu/KBuQXuOvxzJkkX7AySj/PnVZOy8cDOVPpG2ELIxelYYgRjdf7m3Bq9XOi 11 | V4pEnWo17YqteGpDBsZmuE0vwmZHuvKJXmHtHjCu7Shsp0syDsin9xCh2XurvCAW 12 | HPlAzm7dJ6Fh643bfNp5fwjyrpkyphAKxQyF/jurcjbubIVw7kH81e914KC9zHMw 13 | DNMf0wwZXyxmTPCJRjNabjkIMbTSwDN83TdvuVnCZjSRLjHV9c8Qdmkey1TH+d9m 14 | IB9qUct+Rk2YqtrLfjTc6Hc4bBCyGfEkKUBsjTl3z/KXnrO5gbWGNJe3fTcD5nhC 15 | vnK4P3NwHLhdcIaIzEOYmWlppeiKNLO9CMmMTO95CAJAWwUgmYYfVZ8rA0wlKpjT 16 | cz5OlK2qndq5g+I5BmNrs4ekrShqJagWwa7DTY/yC2laTxQyKMCSyqmF8+RrfPLO 17 | lNfjgIr2h3OOhBIPnFE49m4SVi8FmRd2EB3GhYcvLsJMr03oSb2Xfg== 18 | -----END RSA PRIVATE KEY----- 19 | -------------------------------------------------------------------------------- /templates/application/views/common/pages/404.html: -------------------------------------------------------------------------------- 1 | {% extends '../../../views/layouts/layout.base.html' %} 2 | 3 | {% block content %} 4 |
5 |
6 |

{{ tr('Page Not Found!') }}

7 |
8 |

{{ tr('The page you are looking for is not found!') }}

9 | {% if settings.env == 'development' %} 10 |
11 |                     {{ error.stack }}
12 |                 
13 | {% endif %} 14 |
15 |
16 |
17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /templates/application/views/common/partials/footer.base.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

© {{ tr('Your Project Name') }}

5 |
6 |
-------------------------------------------------------------------------------- /templates/application/views/common/partials/header.base.html: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /templates/application/views/layouts/layout.base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% set package_version = twee.getConfig('twee:package:version') %} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {{ meta.title|default(tr('Starter Template for Bootstrap')) }} 15 | 16 | {% block css %} 17 | {% if settings.env == 'production' %} 18 | 19 | 22 | {% else %} 23 | 24 | 27 | {% endif %} 28 | {% endblock css %} 29 | 30 | 31 | 32 | {% block header %} 33 | {% include '../common/partials/header.base.html' %} 34 | {% endblock header %} 35 | 36 |
37 | {% block content %} 38 |
39 |

{{ tr('Bootstrap starter template') }}

40 |

{{ tr('Use this document as a way to quickly start any new project.
All you get is this text and a mostly barebones HTML document.') }}

41 |
42 | {% endblock content %} 43 |
44 | 45 | {% block footer %} 46 | {% include '../common/partials/footer.base.html' %} 47 | {% endblock footer %} 48 | 49 | {% block js %} 50 | {% if settings.env == 'production' %} 51 | 52 | 55 | {% else %} 56 | 57 | 60 | {% endif %} 61 | {% endblock js %} 62 | 63 | 64 | -------------------------------------------------------------------------------- /tests/fixtures/framework.json: -------------------------------------------------------------------------------- 1 | { 2 | "twee": { 3 | "extensions": { 4 | "Twee Basic HTTP Parsers": { 5 | "module": "twee-extensions/http/parsers", 6 | "name": "Twee Basic HTTP Parsers" 7 | }, 8 | "Twee Response Formats": { 9 | "module": "twee-extensions/http/responses", 10 | "name": "Twee Response Formats" 11 | }, 12 | "Twee Winston Logger": { 13 | "module": "twee-extensions/logging/winston", 14 | "name": "Twee Winston Logger" 15 | }, 16 | "Twee Custom Headers": { 17 | "module": "twee-extensions/http/headers/requested-with", 18 | "name": "Twee Custom Headers" 19 | }, 20 | "Twee Static Files Serving": { 21 | "module": "twee-extensions/http/static", 22 | "name": "Twee Static Files Serving" 23 | }, 24 | "Twee Session": { 25 | "module": "twee-extensions/http/session", 26 | "disabled": true, 27 | "name": "Twee Session" 28 | }, 29 | "Twee Compressor": { 30 | "module": "twee-extensions/http/compressor", 31 | "name": "Twee Compressor" 32 | }, 33 | "Twee View Engine": { 34 | "module": "twee-extensions/view/engine/swig", 35 | "name": "Twee View Engine" 36 | }, 37 | "Twee View Helpers": { 38 | "module": "twee-extensions/view/helpers", 39 | "name": "Twee View Helpers" 40 | }, 41 | "Twee Passport": { 42 | "module": "twee-extensions/http/session/passport", 43 | "disabled": true, 44 | "name": "Twee Passport" 45 | }, 46 | "Twee i18n": { 47 | "module": "twee-extensions/i18n", 48 | "name": "Twee i18n" 49 | } 50 | }, 51 | "options": { 52 | "i18n": { 53 | "init": { 54 | "locales": [ 55 | "en", 56 | "ru" 57 | ], 58 | "defaultLocale": "en", 59 | "cookie": "locale", 60 | "directory": "i18n" 61 | }, 62 | "autoUpdate": false 63 | }, 64 | "compress": { 65 | "html": true, 66 | "gzip": true 67 | }, 68 | "passport": { 69 | "enabled": true 70 | }, 71 | "staticFiles": { 72 | "directory": "public" 73 | }, 74 | "logging": { 75 | "winston": { 76 | "accessFile": "var/log/access.json", 77 | "exceptionsFile": "var/log/exceptions.json", 78 | "exitOnError": false, 79 | "consoleLogging": false, 80 | "consoleLoggingOptions": { 81 | "colorize": true, 82 | "meta": true, 83 | "msg": "HTTP {{res.statusCode}} {{req.method}} {{res.responseTime}}ms {{req.url}}", 84 | "expressFormat": true, 85 | "colorStatus": true 86 | } 87 | } 88 | }, 89 | "favicon": { 90 | "file": "public/favicon.ico" 91 | }, 92 | "bodyParser": { 93 | "urlencoded": { 94 | "extended": false 95 | } 96 | }, 97 | "session": { 98 | "enabled": true, 99 | "options": { 100 | "secret": "expR3ssS3cR3TASD:Fwfk%$^$%&*&", 101 | "cookie": { 102 | "secure": false, 103 | "maxAge": 999999999999, 104 | "signed": true, 105 | "path": "/" 106 | }, 107 | "resave": true, 108 | "saveUninitialized": true 109 | } 110 | }, 111 | "viewEngine": { 112 | "swig": { 113 | "engineExtension": "html", 114 | "options": { 115 | "cache": null 116 | } 117 | }, 118 | "jade": { 119 | "engineExtension": "jade", 120 | "options": { 121 | "pretty": true, 122 | "compileDebug": false 123 | } 124 | }, 125 | "disabled": false 126 | }, 127 | "cache": { 128 | "redis": { 129 | "host": "127.0.0.1", 130 | "port": 6379 131 | }, 132 | "memcache": {} 133 | }, 134 | "errorPages": { 135 | "404": { 136 | "viewTemplate": "/private/var/tmp/mochatweetestapp/configs/../views/common/pages/404.html" 137 | } 138 | } 139 | }, 140 | "package": { 141 | "name": "mochatweetestapp", 142 | "version": "0.0.1", 143 | "private": true, 144 | "scripts": { 145 | "pstart": "NODE_ENV=production PORT=80 node ./application.js", 146 | "start": "NODE_ENV=development DEBUG=twee node ./application.js" 147 | }, 148 | "dependencies": { 149 | "twee": ">= 0.1.4", 150 | "grunt-contrib-jshint": "*", 151 | "grunt-contrib-concat": "*", 152 | "grunt-contrib-uglify": "*", 153 | "grunt-contrib-copy": "*", 154 | "grunt-contrib-clean": "*", 155 | "grunt-contrib-connect": "*", 156 | "grunt-contrib-watch": "*", 157 | "load-grunt-tasks": "*", 158 | "grunt-contrib-cssmin": "*", 159 | "grunt-aws-s3": "*", 160 | "grunt-notify": "*", 161 | "grunt-bower-task": "*", 162 | "grunt-express-server": "*" 163 | } 164 | } 165 | }, 166 | "__moduleOptions__": { 167 | "Default": { 168 | "disabled": false, 169 | "prefix": "/" 170 | } 171 | }, 172 | "__folders__": { 173 | "Default": { 174 | "module": "/var/tmp/mochatweetestapp/modules/Default", 175 | "moduleSetupFolder": "/var/tmp/mochatweetestapp/modules/Default/setup/", 176 | "moduleSetupFile": "/var/tmp/mochatweetestapp/modules/Default/setup/setup", 177 | "moduleConfigsFolder": "/var/tmp/mochatweetestapp/modules/Default/setup/configs/", 178 | "moduleControllersFolder": "/var/tmp/mochatweetestapp/modules/Default/controllers/", 179 | "moduleModelsFolder": "/var/tmp/mochatweetestapp/modules/Default/models/", 180 | "moduleMiddlewareFolder": "/var/tmp/mochatweetestapp/modules/Default/middleware/", 181 | "moduleViewsFolder": "/var/tmp/mochatweetestapp/modules/Default/views/", 182 | "moduleExtensionsFolder": "/var/tmp/mochatweetestapp/modules/Default/extensions/", 183 | "moduleI18nFolder": "/var/tmp/mochatweetestapp/modules/Default/i18n/", 184 | "moduleAssetsFolder": "/var/tmp/mochatweetestapp/modules/Default/assets/" 185 | } 186 | }, 187 | "default": { 188 | "common": { 189 | "variable": "This prod value from common config" 190 | }, 191 | "grunt": { 192 | "concat": { 193 | "default-js": { 194 | "src": [ 195 | "modules/default/assets/js/*.js", 196 | "modules/default/assets/js/**/*.js" 197 | ], 198 | "dest": "public/build/<%= pkg.version %>/js/default-module.<%= pkg.version %>.js" 199 | }, 200 | "default-css": { 201 | "src": [ 202 | "modules/default/assets/css/*.css", 203 | "modules/default/assets/css/**/*.css" 204 | ], 205 | "dest": "public/build/<%= pkg.version %>/css/default-module.<%= pkg.version %>.css" 206 | } 207 | }, 208 | "uglify": { 209 | "default": { 210 | "files": { 211 | "public/build/<%= pkg.version %>/js/default-module.<%= pkg.version %>.min.js": [ 212 | "public/build/<%= pkg.version %>/js/default-module.<%= pkg.version %>.js" 213 | ] 214 | } 215 | }, 216 | "options": { 217 | "compress": true 218 | } 219 | }, 220 | "cssmin": { 221 | "default": { 222 | "files": { 223 | "public/build/<%= pkg.version %>/css/default-module.<%= pkg.version %>.min.css": [ 224 | "public/build/<%= pkg.version %>/css/default-module.<%= pkg.version %>.css" 225 | ] 226 | } 227 | } 228 | }, 229 | "copy": { 230 | "default-img": { 231 | "files": [ 232 | { 233 | "expand": true, 234 | "flatten": true, 235 | "src": [ 236 | "modules/default/assets/img/*.*" 237 | ], 238 | "dest": "public/build/<%= pkg.version %>/img/default" 239 | } 240 | ] 241 | }, 242 | "default-fonts": { 243 | "files": [ 244 | { 245 | "expand": true, 246 | "flatten": true, 247 | "src": [ 248 | "modules/default/assets/fonts/*.*" 249 | ], 250 | "dest": "public/build/<%= pkg.version %>/fonts/default" 251 | } 252 | ] 253 | } 254 | } 255 | } 256 | }, 257 | "__setup__": { 258 | "Default": { 259 | "params": [], 260 | "middleware": { 261 | "description": "These are globally installed middleware functions", 262 | "head": { 263 | "Default Middleware": { 264 | "file": "DefaultMiddleware", 265 | "method": "defaultMiddleware" 266 | } 267 | }, 268 | "tail": {} 269 | }, 270 | "extensions": { 271 | "Default Extensions": { 272 | "file": "DefaultExtension", 273 | "name": "Default Extensions" 274 | } 275 | }, 276 | "routes": [ 277 | { 278 | "description": "Entry point for application. Landing page", 279 | "pattern": "/", 280 | "controllers": [ 281 | "DefaultController.indexAction" 282 | ], 283 | "middleware": { 284 | "before": {}, 285 | "after": {} 286 | } 287 | } 288 | ] 289 | } 290 | } 291 | } -------------------------------------------------------------------------------- /tests/framework.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert") 2 | , exec = require('child_process').exec 3 | , fs = require('fs') 4 | , cwd = process.cwd() 5 | , appName = 'mochatweetestapp' 6 | , tweeBaseDir = '/var/tmp/' + appName; 7 | 8 | var twee = require('../')(); 9 | 10 | describe('Twee Application Generator', function(){ 11 | 12 | 13 | it('should instantiate Twee Instance', function(){ 14 | assert(typeof twee, 'object'); 15 | }); 16 | 17 | it('should inherit from event emitter', function(done){ 18 | twee.on('foo', done); 19 | twee.emit('foo'); 20 | }); 21 | 22 | it('should normally generate application and install deps', function(done){ 23 | this.timeout(120000); 24 | process.chdir('/var/tmp/'); 25 | exec('rm -rf ' + tweeBaseDir, function(error, stdout, stderr){ 26 | process.chdir('/var/tmp/'); 27 | exec('node ' + cwd + '/bin/twee.js -a ' + appName, function(error, stdout, stderr){ 28 | process.chdir(tweeBaseDir); 29 | exec('npm install', function(error, stdout, stderr){ 30 | done(error); 31 | }); 32 | }); 33 | }); 34 | }); 35 | 36 | it('should normally bootstrap', function(done){ 37 | process.chdir(tweeBaseDir); 38 | twee.setBaseDirectory(tweeBaseDir); 39 | twee.Bootstrap(); 40 | done(); 41 | //fs.writeFileSync(cwd + '/tests/fixtures/framework.json', JSON.stringify(twee.__config, null, '\t')); 42 | }); 43 | }); 44 | 45 | describe('Twee Configs', function(){ 46 | it('core configs presents', function(){ 47 | assert(typeof twee.__config['twee'], 'object'); 48 | assert(typeof twee.__config['twee']['extensions'], 'object'); 49 | assert(typeof twee.__config['twee']['options'], 'object'); 50 | assert(typeof twee.__config['twee']['package'], 'object'); 51 | assert(typeof twee.__config['__moduleOptions__'], 'object'); 52 | assert(typeof twee.__config['__moduleOptions__']['Default'], 'object'); 53 | assert(typeof twee.__config['__folders__'], 'object'); 54 | assert(typeof twee.__config['__folders__']['Default'], 'object'); 55 | assert(typeof twee.__config['__setup__'], 'object'); 56 | assert(typeof twee.__config['__setup__']['Default'], 'object'); 57 | assert(typeof twee.__config['default'], 'object'); 58 | }); 59 | 60 | it('__moduleOptions__ configs presents', function(){ 61 | assert(typeof twee.__config['__moduleOptions__'], 'object'); 62 | assert(typeof twee.__config['__moduleOptions__']['Default'], 'object'); 63 | }); 64 | 65 | it('__folders__ configs presents', function(){ 66 | assert(typeof twee.__config['__folders__'], 'object'); 67 | assert(typeof twee.__config['__folders__']['Default'], 'object'); 68 | }); 69 | 70 | it('__setup__ configs presents', function(){ 71 | assert(typeof twee.__config['__setup__'], 'object'); 72 | assert(typeof twee.__config['__setup__']['Default'], 'object'); 73 | }); 74 | 75 | it('`default` application module configs presents', function(){ 76 | assert(typeof twee.__config['default'], 'object'); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /utils/application-generator.js: -------------------------------------------------------------------------------- 1 | var fse = require('fs-extra') 2 | , fs = require('fs') 3 | , path = require('path') 4 | , colors = require('colors') 5 | , commander = require('commander') 6 | , file = require('file'); 7 | 8 | 9 | module.exports.moduleOrApplicationGeneration = function (defaultOptions, commander) { 10 | // User generates new application 11 | defaultOptions.applicationName = commander.application.replace(/\s\t/gi, ''); 12 | defaultOptions.applicationFolder = commander.folder + '/' + defaultOptions.applicationName; 13 | defaultOptions.moduleName = commander.module.replace(/[^a-zA-Z-_.]+/gi, '').capitalize(); 14 | defaultOptions.moduleNameLowerCase = defaultOptions.moduleName.toLowerCase(); 15 | 16 | console.log(colors.cyan('[TWEE] ') + 'Generating New Twee Application: ' + colors.cyan(defaultOptions.applicationName)); 17 | 18 | if (fs.existsSync(defaultOptions.applicationFolder)) { 19 | 20 | console.log(colors.cyan('[TWEE] ') + 'Application Folder Exists'); 21 | 22 | defaultOptions.applicationSourceFolder = defaultOptions.applicationSourceFolder + '/modules/' + defaultOptions.tweeModuleNameTemplate; 23 | defaultOptions.applicationFolder = defaultOptions.applicationFolder + '/modules/' + defaultOptions.tweeModuleNameTemplate; 24 | var newModuleFolder = defaultOptions.applicationFolder.replace(defaultOptions.tweeModuleNameTemplateRegEx, defaultOptions.moduleName); 25 | 26 | if (fs.existsSync(newModuleFolder)) { 27 | console.error(colors.cyan('[TWEE] ') + colors.red('Module folder exists: ' + newModuleFolder)); 28 | process.exit(1); 29 | } 30 | 31 | defaultOptions.generateOnlyModule = true; 32 | } else { 33 | fs.mkdirSync(defaultOptions.applicationFolder); 34 | } 35 | }; 36 | 37 | /** 38 | * Receiving all needed options and generating ready to use new application 39 | * @param options 40 | */ 41 | module.exports.generateApplication = function(options) { 42 | // Copying raw template structure 43 | fse.copySync( 44 | options.applicationSourceFolder, 45 | options.applicationFolder 46 | ); 47 | 48 | // If we generate only module - then it won't be touch. Do it in special case 49 | if (options.generateOnlyModule) { 50 | var newApplicationFolder = options.applicationFolder.replace(options.tweeModuleNameTemplate, options.moduleName); 51 | fs.renameSync(options.applicationFolder, newApplicationFolder); 52 | options.applicationFolder = newApplicationFolder; 53 | } 54 | 55 | var allDirs = [], i = 0, j = 0, d = ''; 56 | 57 | // Collect all the dir paths to rename directories before renaming files and chaning it's content 58 | file.walkSync(options.applicationFolder, function(dirPath, dirs, files){ 59 | for (var i = 0; i < dirs.length; i++) { 60 | allDirs.push(path.join(dirPath, dirs[i])); 61 | } 62 | }); 63 | 64 | // Bubble sort the directories by it's length 65 | for (i = 0; i < allDirs.length - 1; i++){ 66 | for (j = i + 1; j < allDirs.length; j++){ 67 | if (allDirs[i].length < allDirs[j].length) { 68 | d = allDirs[i]; 69 | allDirs[i] = allDirs[j]; 70 | allDirs[j] = d; 71 | } 72 | } 73 | } 74 | 75 | // Bubble sort the directories by it's length 76 | for (i = 0; i < allDirs.length - 1; i++){ 77 | for (j = i + 1; j < allDirs.length; j++){ 78 | if (howManyTimesInPath(allDirs[i], options.tweeModuleNameTemplate) < howManyTimesInPath(allDirs[j], options.tweeModuleNameTemplate)) { 79 | d = allDirs[i]; 80 | allDirs[i] = allDirs[j]; 81 | allDirs[j] = d; 82 | } 83 | } 84 | } 85 | 86 | // Renaming folders very accurate 87 | for (i = 0; i < allDirs.length; i++) { 88 | if (allDirs[i].endsWith(options.tweeModuleNameTemplate)) { 89 | var offsetStart = allDirs[i].indexOf(options.tweeModuleNameTemplate, allDirs[i].length - options.tweeModuleNameTemplate.length); 90 | var originalFolder = allDirs[i]; 91 | allDirs[i] = allDirs[i].substr(0, offsetStart) + options.moduleName; 92 | fs.renameSync(originalFolder, allDirs[i]); 93 | for (j = 0; j < allDirs.length; j++){ 94 | allDirs[j] = allDirs[j].replace(originalFolder, allDirs[i]); 95 | } 96 | } 97 | } 98 | 99 | //console.log(allDirs); 100 | // Now we renamed all the folders. Lets read structure once more to rename files 101 | var allFiles = []; 102 | file.walkSync(options.applicationFolder, function(dirPath, dirs, files){ 103 | for (i = 0; i < files.length; i++) { 104 | allFiles.push(path.join(dirPath, files[i])); 105 | } 106 | }); 107 | 108 | // Rename Patterned Files to new ones 109 | // And also at the same time with renamed files - replace patterns inside them. 110 | for (i = 0; i < allFiles.length; i++){ 111 | var newName = allFiles[i].replace(options.tweeModuleNameTemplate, options.moduleName) 112 | .replace(options.tweeModuleNameTemplateLowerCased, options.moduleNameLowerCase); 113 | fs.renameSync(allFiles[i], newName); 114 | allFiles[i] = newName; 115 | var fileContents = fs.readFileSync(newName); 116 | fileContents = fileContents.toString() 117 | .replace(options.tweeAppNameTemplateRegEx, options.applicationName) 118 | .replace(options.tweeModuleNameTemplateRegEx, options.moduleName) 119 | .replace(options.tweeModuleNameTemplateLowerCasedRegEx, options.moduleNameLowerCase) 120 | .replace(options.tweeVersionTemplateRegEx, options.tweeVersion); 121 | 122 | fs.writeFileSync(newName, fileContents); 123 | } 124 | 125 | // Turning module ON 126 | var modulesAppConfig = path.join(process.cwd(), options.applicationName, 'configs/modules.js') 127 | , modulesConfig = require(modulesAppConfig); 128 | 129 | modulesConfig[options.moduleName] = { 130 | "disabled": false, 131 | "prefix": (options.generateOnlyModule ? "/" + options.moduleNameLowerCase : "/") 132 | }; 133 | modulesConfig = JSON.stringify(modulesConfig, null, "\t"); 134 | modulesConfig = "module.exports = " + modulesConfig + ";"; 135 | fs.writeFileSync(modulesAppConfig, modulesConfig); 136 | } 137 | 138 | 139 | /** 140 | * Count how many times needed string occurs in source folder path 141 | * @param source 142 | * @param needed 143 | * @returns {number} 144 | */ 145 | function howManyTimesInPath(source, needed) { 146 | var sourceParts = source.split('/') 147 | , numberOfNeeded = 0; 148 | for (var i = 0; i < source.length; i++) { 149 | if (sourceParts[i] == needed) { 150 | numberOfNeeded++; 151 | } 152 | } 153 | 154 | return numberOfNeeded; 155 | } 156 | 157 | /** 158 | * Check if ends with 159 | * @param suffix 160 | * @returns {boolean} 161 | */ 162 | String.prototype.endsWith = function(suffix) { 163 | return this.indexOf(suffix, this.length - suffix.length) !== -1; 164 | }; 165 | 166 | /** 167 | * Capitalize 168 | * @returns {string} 169 | */ 170 | String.prototype.capitalize = function () 171 | { 172 | return this.charAt(0).toUpperCase() + this.slice(1); 173 | }; -------------------------------------------------------------------------------- /utils/extend.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery.extend extracted from the jQuery source & optimised for NodeJS 3 | Twitter: @FGRibreau / fgribreau.com 4 | 5 | Usage: 6 | var Extend = require('./Extend'); 7 | 8 | 9 | // Extend 10 | var obj = Extend({opt1:true, opt2:true}, {opt1:false}); 11 | 12 | // Deep Copy 13 | var clonedObject = Extend(true, {}, myObject); 14 | var clonedArray = Extend(true, [], ['a',['b','c',['d']]]); 15 | */ 16 | var toString = Object.prototype.toString, 17 | hasOwn = Object.prototype.hasOwnProperty, 18 | push = Array.prototype.push, 19 | slice = Array.prototype.slice, 20 | trim = String.prototype.trim, 21 | indexOf = Array.prototype.indexOf, 22 | 23 | // [[Class]] -> type pairs 24 | class2type = {}; 25 | 26 | // Populate the class2type map 27 | "Boolean Number String Function Array Date RegExp Object".split(" ").forEach(function(name) { 28 | class2type[ "[object " + name + "]" ] = name.toLowerCase(); 29 | }); 30 | 31 | function type(obj){ 32 | return obj == null ? 33 | String( obj ) : 34 | class2type[ toString.call(obj) ] || "object"; 35 | } 36 | 37 | function isPlainObject( obj ) { 38 | if ( !obj || type(obj) !== "object") { 39 | return false; 40 | } 41 | 42 | // Not own constructor property must be Object 43 | if ( obj.constructor && 44 | !hasOwn.call(obj, "constructor") && 45 | !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { 46 | return false; 47 | } 48 | 49 | // Own properties are enumerated firstly, so to speed up, 50 | // if last one is own, then all properties are own. 51 | 52 | var key; 53 | for ( key in obj ) {} 54 | 55 | return key === undefined || hasOwn.call( obj, key ); 56 | } 57 | 58 | module.exports = function extend(){ 59 | var options, name, src, copy, copyIsArray, clone, 60 | target = arguments[0] || {}, 61 | i = 1, 62 | length = arguments.length, 63 | deep = false; 64 | 65 | // Handle a deep copy situation 66 | if ( typeof target === "boolean" ) { 67 | deep = target; 68 | target = arguments[1] || {}; 69 | // skip the boolean and the target 70 | i = 2; 71 | } 72 | 73 | // Handle case when target is a string or something (possible in deep copy) 74 | if ( typeof target !== "object" && type(target) !== "function") { 75 | target = {}; 76 | } 77 | 78 | // extend jQuery itself if only one argument is passed 79 | if ( length === i ) { 80 | target = this; 81 | --i; 82 | } 83 | 84 | for ( ; i < length; i++ ) { 85 | // Only deal with non-null/undefined values 86 | if ( (options = arguments[ i ]) != null ) { 87 | // Extend the base object 88 | for ( name in options ) { 89 | src = target[ name ]; 90 | copy = options[ name ]; 91 | 92 | // Prevent never-ending loop 93 | if ( target === copy ) { 94 | continue; 95 | } 96 | 97 | // Recurse if we're merging plain objects or arrays 98 | if ( deep && copy && ( isPlainObject(copy) || (copyIsArray = type(copy) === "array") ) ) { 99 | if ( copyIsArray ) { 100 | copyIsArray = false; 101 | clone = src && type(src) === "array" ? src : []; 102 | 103 | } else { 104 | clone = src && isPlainObject(src) ? src : {}; 105 | } 106 | 107 | // Never move original objects, clone them 108 | target[ name ] = extend( deep, clone, copy ); 109 | 110 | // Don't bring in undefined values 111 | } else if ( copy !== undefined ) { 112 | target[ name ] = copy; 113 | } 114 | } 115 | } 116 | } 117 | 118 | // Return the modified object 119 | return target; 120 | }; -------------------------------------------------------------------------------- /view/tags/include-language.js: -------------------------------------------------------------------------------- 1 | var ignore = 'ignore', 2 | missing = 'missing', 3 | only = 'only', 4 | swig_parse = require('swig/lib/tags/include').parse; 5 | 6 | /** 7 | * Includes a template partial in place. The template is rendered within the current locals variable context. 8 | * 9 | * @alias include 10 | * 11 | * @example 12 | * // food = 'burritos'; 13 | * // drink = 'lemonade'; 14 | * {% include "./partial.html" %} 15 | * // => I like burritos and lemonade. 16 | * 17 | * @example 18 | * // my_obj = { food: 'tacos', drink: 'horchata' }; 19 | * {% include "./partial.html" with my_obj only %} 20 | * // => I like tacos and horchata. 21 | * 22 | * @example 23 | * {% include "/this/file/does/not/exist" ignore missing %} 24 | * // => (Nothing! empty string) 25 | * 26 | * @param {string|var} file The path, relative to the template root, to render into the current context. 27 | * @param {literal} [with] Literally, "with". 28 | * @param {object} [context] Local variable key-value object context to provide to the included file. 29 | * @param {literal} [only] Restricts to only passing the with context as local variables–the included template will not be aware of any other local variables in the parent template. For best performance, usage of this option is recommended if possible. 30 | * @param {literal} [ignore missing] Will output empty string if not found instead of throwing an error. 31 | */ 32 | exports.compile = function (compiler, args) { 33 | var file = args.shift(), 34 | onlyIdx = args.indexOf(only), 35 | onlyCtx = onlyIdx !== -1 ? args.splice(onlyIdx, 1) : false, 36 | parentFile = (args.pop() || '').replace(/\\/g, '\\\\'), 37 | ignore = args[args.length - 1] === missing ? (args.pop()) : false, 38 | w = args.join(''), 39 | locale = String(global.locale).toLowerCase() || 'en'; 40 | 41 | return (ignore ? ' try {\n' : '') + 42 | '_output += _swig.compileFile(' + file.replace('.lang.', '.' + locale + '.') + ', {' + 43 | 'resolveFrom: "' + parentFile + '"' + 44 | '})(' + 45 | ((onlyCtx && w) ? w : (!w ? '_ctx' : '_utils.extend({}, _ctx, ' + w + ')')) + 46 | ');\n' + 47 | (ignore ? '} catch (e) {}\n' : ''); 48 | }; 49 | 50 | exports.parse = swig_parse; 51 | -------------------------------------------------------------------------------- /view/tags/translate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Includes a template partial in place. The template is rendered within the current locals variable context. 3 | * 4 | * @example 5 | * {% include "/this/file/does/not/exist" ignore missing %} 6 | * // => (Nothing! empty string) 7 | * 8 | * @param {string|var} class Class of translation 9 | * @param {string|var} entity Entity to translate 10 | */ 11 | exports.compile = function (compiler, args) { 12 | var locale = String(global.locale).toLowerCase() || 'en'; 13 | 14 | console.log(args); 15 | 16 | if (args.length < 1) { 17 | return '_output += "NO TRANSLATION DETAILS SPECIFIED entity_class::entity";'; 18 | } 19 | 20 | return ''; 21 | 22 | return db.models.translates.qAll({entity_class: 'home'}) 23 | .then(function(translates){ 24 | //console.dir(translates[0].translation); 25 | //_output += translates[0].translation; 26 | return translates[0].translation; 27 | }); 28 | 29 | return '_output += db.models.translates.qAll({entity_class: \'home\'})' + 30 | '.then(function(translates){' + 31 | 'console.dir(translates[0].translation); ' + 32 | '_output += translates[0].translation;' + 33 | 'return translates[0].translation;' + 34 | '});'; 35 | }; 36 | 37 | /** 38 | * Simple parse function 39 | * @param str 40 | * @param line 41 | * @param parser 42 | * @param types 43 | * @param stack 44 | * @param opts 45 | */ 46 | exports.parse = function (str, line, parser, types, stack, opts) { 47 | var _class, w; 48 | parser.on(types.STRING, function (token) { 49 | if (!_class) { 50 | _class = token.match; 51 | this.out.push(_class); 52 | return; 53 | } 54 | 55 | return true; 56 | }); 57 | 58 | return true; 59 | }; 60 | -------------------------------------------------------------------------------- /view/templates/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Twee Framework 5 | 6 | 7 | 8 | 14 | 15 | 16 | --------------------------------------------------------------------------------