diff --git a/.gitignore b/.gitignore index db441cf..7241766 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ node_modules/ -old/ \ No newline at end of file +old/ +typings/ +app/templates/src/app-ts/**/*.js +templates/typescript/*.js +app/templates/test-ts/**/*.js diff --git a/README.md b/README.md index e80760a..e90e7aa 100644 --- a/README.md +++ b/README.md @@ -34,18 +34,19 @@ ``` # Get Started -1. Install generator-hirsch +1. Check out this fork to some local folder +2. Install the generator from the local folder ``` - npm install -g generator-hirsch + npm link ``` -2. Create a new folder and change directory to it +3. Create a new folder and change directory to it ``` mkdir myApp cd myApp ``` -3. Run the generator +4. Run the generator ``` yo hirsch appname ``` @@ -74,7 +75,7 @@ # Styleguide -I use this style guid for my apps: +I use this style guide for my apps: [johnpapa/angular-styleguide](https://github.com/johnpapa/angular-styleguide) # App @@ -97,30 +98,47 @@ projectRoot/ | | | | | + core/ | | | | - | | | + app.config.js ( 3rd party modules configurations ) - | | | + app.core.js ( Defines the AngularJS modules and configures them ) - | | | + app.router.js ( ui.router middleware ) - | | | + app.run.js - | | | + app.util.js ( Global object to generates the server url for the current environment ) - | | | + app.events.js ( Global event bus ) + | | | + config/ + | | | | + thirdParty.js ( 3rd party modules configurations ) + | | | | + angular.js ( Defines the AngularJS modules and configures them ) + | | | | + run.js (Startup code) + | | | | + module.ts + | | | | + | | | + routing/ + | | | | + router.js ( ui.router middleware ) + | | | | + module.ts + | | | | + | | | + util/ + | | | | + appUtil.js ( service that generates the server url for the current environment ) + | | | | + appEvents.js ( event bus service ) + | | | | + module.ts + | | | | + | | | + module.ts | | | | | + / | | | | - | | | + common/ + | | | + directives/ | | | | | - | | | | + directives/ - | | | | + filters/ - | | | | + services/ - | | | | + templates/ - | | | | + | | | | + .directive.ts + | | | | + .directive.html + | | | | + module.ts + | | | | + | | | + filters/ + | | | + services/ + | | | | | + | | | | + .service.ts + | | | | + module.ts + | | | | + | | | + templates/ | | | + views/ | | | | | - | | | | + .js + | | | | + .ts | | | | + .html + | | | | + module.ts | | | | - | | | + .module.js + | | | + module.ts | | | - | | + app.js + | | + app.ts | | | +-- assets/ | | | @@ -151,6 +169,7 @@ projectRoot/ +-- package.json +-- project.config.js ( Path definitions for gulp ) +-- README.md ( describes the project and how to set it up ) + +-- tsd.json (contains type definition dependencies) ``` ## Task Runner diff --git a/app/index.js b/app/index.js index 5cb01e8..b396636 100644 --- a/app/index.js +++ b/app/index.js @@ -20,7 +20,7 @@ var HirschGenerator = yeoman.generators.Base.extend({ if (!this.askAppname) { if (this.appname !== path.basename(process.cwd())) { - this.destinationRoot(this.appname) + this.destinationRoot(this.appname); } } }, @@ -42,7 +42,6 @@ var HirschGenerator = yeoman.generators.Base.extend({ if (this.askAppname) { prompts.push({ - type: 'string', name: 'appname', message: 'What would you like to name the app?', default: this.appname || path.basename(process.cwd()) @@ -50,46 +49,116 @@ var HirschGenerator = yeoman.generators.Base.extend({ } prompts.push({ - type: 'string', name: 'prefix', message: 'Angular app prefix (2 chars):', default: 'my' }); prompts.push({ - type: 'string', name: 'description', message: 'Describe your application:' }); prompts.push({ - type: 'string', - name: 'author', - message: 'How is the author?' + name: 'author', + message: 'Who is the author?' + }); + + prompts.push({ + type: 'confirm', + name: 'useTypescript', + message: 'Do you want to use TypeScript?', + default: false + }); + + prompts.push({ + when: function (props) { + return props.useTypescript; + }, + name: 'typingsPath', + message: 'Where do you want to store the type definitions (path relative to root)?', + default: 'typings' }); this.prompt(prompts, function (props) { if (this.askAppname) { this.appname = props.appname; if (this.appname !== path.basename(process.cwd())) { - this.destinationRoot(this.appname) + this.destinationRoot(this.appname); } } this.prefix = props.prefix; this.description = props.description; this.author = props.author; + this.useTypescript = props.useTypescript; + this.typingsPath = props.typingsPath; done(); }.bind(this)); }, + /** + * INIT + * Declare some helper functions + */ + init: function() { + var _this = this; + var copyBase = function(copyFunc) { + var rest = Array.prototype.slice.call(arguments, 1); + return function () { + var args = Array.prototype.slice.call(arguments); + var newRest = rest.slice(0); + if (typeof args[0] === 'object') { + newRest.push(args.shift()); + } + + var tgtSegs = args.map(function (s) { + return (_this.useTypescript ? s.replace(/\.js$/, '.ts') : s).replace(/\.js!$/, '.js'); + }); + + var srcSegs = tgtSegs.map(function (s) { + var replaces = [ + { p: /^app$/, r: 'app-ts' }, + { p: /^app\//, r: 'app-ts/' }, + { p: /^test$/, r: 'test-ts' }, + { p: /^test\//, r: 'test-ts/' } + ]; + + var copiesScripts = args.some(function(s) { + return /\.js$/.test(s); + }); + + return _this.useTypescript && copiesScripts + ? replaces.reduce(function (prev, curr) { return prev.replace(curr.p, curr.r); }, s) + : s; + }).map(function (s) { return s.replace(/!$/, ''); }); + + // filter out globs from target path + tgtSegs = tgtSegs.filter(function(s) { + return s.indexOf('*') < 0 && !/!$/.test(s); + }); + + var srcPath = path.join.apply(null, srcSegs); + var tgtPath = path.join.apply(null, tgtSegs); + console.log(srcPath + ' -> ' + tgtPath); + copyFunc.apply(null, [_this.templatePath(srcPath), _this.destinationPath(tgtPath)].concat(newRest)); + }; + } + + this.copyFile = copyBase(this.fs.copy.bind(this.fs)); + this.copyTpl = copyBase(this.fs.copyTpl.bind(this.fs), this.projectConfig); + this.copyDir = copyBase(this.directory.bind(this)); + }, + addToProjectConfig: function () { this.projectConfig.prompts = {}; this.projectConfig.prompts.appname = this.appname; this.projectConfig.prompts.prefix = this.prefix; this.projectConfig.prompts.description = this.description; this.projectConfig.prompts.author = this.author; + this.projectConfig.prompts.useTypescript = this.useTypescript; + this.projectConfig.prompts.typingsPath = this.typingsPath; }, displayName: function () { @@ -104,6 +173,7 @@ var HirschGenerator = yeoman.generators.Base.extend({ this.mkdir('src/app/common/templates'); this.mkdir('src/app/common/decorators'); this.mkdir('src/app/common/filters'); + this.mkdir('src/app/common/views'); this.mkdir('src/app/core'); this.mkdir('src/assets'); this.mkdir('src/assets/medias'); @@ -126,153 +196,96 @@ var HirschGenerator = yeoman.generators.Base.extend({ taskRunner: function () { this.template('_gulpfile.js', 'gulpfile.js', this.projectConfig); - this.directory( - this.templatePath(this.projectConfig.path.taskDir), - this.destinationPath(this.projectConfig.path.taskDir) - ); + this.copyTpl(this.projectConfig.path.taskDir, '*.js!'); + + if (this.projectConfig.prompts.useTypescript) { + this.copyTpl(this.projectConfig.path.taskDir, 'ts!', '*.js!'); + } else { + this.copyTpl(this.projectConfig.path.taskDir, 'js!', '*.js!'); + } }, projectfiles: function () { this.copy('_project.config.js', 'project.config.js'); this.copy('_editorconfig', '.editorconfig'); this.copy('_gitignore', '.gitignore'); - this.copy('_jshintrc', '.jshintrc'); + + if (this.projectConfig.prompts.useTypescript) { + this.fs.copyTpl(this.templatePath('_tsd.json'), this.destinationPath('tsd.json'), this.projectConfig); + this.fs.copy(this.templatePath('_tsconfig.json'), this.destinationPath('tsconfig.json')); + this.fs.copy(this.templatePath('_tslint.json'), this.destinationPath('tslint.json')); + } else { + this.fs.copy(this.templatePath('_jshintrc'), this.destinationPath('.jshintrc')); + } }, assets: function () { - this.directory( - this.templatePath(path.join(this.projectConfig.path.srcDir, this.projectConfig.path.assetsDir)), - this.destinationPath(path.join(this.projectConfig.path.srcDir, this.projectConfig.path.assetsDir)) - ); + this.copyDir(this.projectConfig.path.srcDir, this.projectConfig.path.assetsDir); }, testRunnerFiles: function () { this.template('_karma-midway.config.js', 'karma-midway.config.js', this.projectConfig); this.template('_karma-shared.config.js', 'karma-shared.config.js', this.projectConfig); this.template('_karma-unit.config.js', 'karma-unit.config.js', this.projectConfig); - this.template(this.projectConfig.path.testDir + '/midway/app.spec.js', this.projectConfig.path.testDir + '/midway/app.spec.js', this.projectConfig); - this.directory( - this.templatePath(this.projectConfig.path.testDir + '/lib'), - this.destinationPath(this.projectConfig.path.testDir + '/lib') - ); + + this.copyTpl(this.projectConfig.path.testDir, '**', '*.js'); }, appFiles: function () { // Index - this.template( - this.templatePath(path.join(this.projectConfig.path.srcDir, this.projectConfig.path.main)), - this.destinationPath(path.join(this.projectConfig.path.srcDir, this.projectConfig.path.main)), this.projectConfig - ); - - // App - this.template( - this.templatePath(path.join(this.projectConfig.path.srcDir, this.projectConfig.path.app.main)), - this.destinationPath(path.join(this.projectConfig.path.srcDir, this.projectConfig.path.app.main)), this.projectConfig - ); + this.copyTpl(this.projectConfig.path.srcDir, this.projectConfig.path.main); + + this.copyTpl(this.projectConfig.path.srcDir, this.projectConfig.path.app.main); }, coreFiles: function () { - var corePath = path.join(this.projectConfig.path.srcDir, this.projectConfig.path.app.coreDir); - this.template(this.templatePath(path.join(corePath, 'core.module.js')), this.destinationPath(path.join(corePath, 'core.module.js')), this.projectConfig); - - this.template(this.templatePath(path.join(corePath, 'config/config.module.js')), this.destinationPath(path.join(corePath, 'config/config.module.js')), this.projectConfig); - this.template(this.templatePath(path.join(corePath, 'config/angular.config.js')), this.destinationPath(path.join(corePath, 'config/angular.config.js')), this.projectConfig); - this.template(this.templatePath(path.join(corePath, 'config/thirdParty.config.js')), this.destinationPath(path.join(corePath, 'config/thirdParty.config.js')), this.projectConfig); - - this.template(this.templatePath(path.join(corePath, 'constants/constants.module.js')), this.destinationPath(path.join(corePath, 'constants/constants.module.js')), this.projectConfig); - this.template(this.templatePath(path.join(corePath, 'constants/global.constants.js')), this.destinationPath(path.join(corePath, 'constants/global.constants.js')), this.projectConfig); - - this.template(this.templatePath(path.join(corePath, 'router/router.module.js')), this.destinationPath(path.join(corePath, 'router/router.module.js')), this.projectConfig); - this.template(this.templatePath(path.join(corePath, 'router/router.constants.js')), this.destinationPath(path.join(corePath, 'router/router.constants.js')), this.projectConfig); - this.template(this.templatePath(path.join(corePath, 'router/router.config.js')), this.destinationPath(path.join(corePath, 'router/router.config.js')), this.projectConfig); - this.template(this.templatePath(path.join(corePath, 'router/router.service.js')), this.destinationPath(path.join(corePath, 'router/router.service.js')), this.projectConfig); - this.template(this.templatePath(path.join(corePath, 'router/router.js')), this.destinationPath(path.join(corePath, 'router/router.js')), this.projectConfig); - - this.template(this.templatePath(path.join(corePath, 'util/util.module.js')), this.destinationPath(path.join(corePath, 'util/util.module.js')), this.projectConfig); - this.template(this.templatePath(path.join(corePath, 'util/events.js')), this.destinationPath(path.join(corePath, 'util/events.js')), this.projectConfig); - this.template(this.templatePath(path.join(corePath, 'util/util.js')), this.destinationPath(path.join(corePath, 'util/util.js')), this.projectConfig); - this.template(this.templatePath(path.join(corePath, 'util/logger.js')), this.destinationPath(path.join(corePath, 'util/logger.js')), this.projectConfig); + this.copyTpl(this.projectConfig.path.srcDir, this.projectConfig.path.app.coreDir, '**', '*.js'); }, - layoutFiles: function () { - var layoutPath = path.join(this.projectConfig.path.srcDir, this.projectConfig.path.app.layoutDir); - - this.template( - this.templatePath(path.join(layoutPath, 'views', 'admin.html')), - this.destinationPath(path.join(layoutPath, 'views', 'admin.html')), this.projectConfig - ); - this.template(this.templatePath(path.join(layoutPath, 'views', 'admin.js')), this.destinationPath(path.join(layoutPath, 'views', 'admin.js')), this.projectConfig); - - this.copy(this.templatePath(path.join(layoutPath, 'views', 'public.html')), this.destinationPath(path.join(layoutPath, 'views', 'public.html'))); - this.template(this.templatePath(path.join(layoutPath, 'views', 'public.js')), this.destinationPath(path.join(layoutPath, 'views', 'public.js')), this.projectConfig); - - this.copy( - this.templatePath(path.join(layoutPath, 'directives', 'header.directive.html')), - this.destinationPath(path.join(layoutPath, 'directives', 'header.directive.html')) - ); - - this.template( - this.templatePath(path.join(layoutPath, 'directives', 'header.directive.js')), - this.destinationPath(path.join(layoutPath, 'directives', 'header.directive.js')), this.projectConfig - ); - - this.template( - this.templatePath(path.join(layoutPath, 'layout.module.js')), - this.destinationPath(path.join(layoutPath, 'layout.module.js')), this.projectConfig - ); - - this.template( - this.templatePath(path.join(layoutPath, 'views/views.module.js')), - this.destinationPath(path.join(layoutPath, 'views/views.module.js')), this.projectConfig - ); - - this.template( - this.templatePath(path.join(layoutPath, 'directives/directives.module.js')), - this.destinationPath(path.join(layoutPath, 'directives/directives.module.js')), this.projectConfig - ); + commonFiles: function () { + this.copyTpl(this.projectConfig.path.srcDir, this.projectConfig.path.app.commonDir, '**', '*.js'); + }, + layoutFiles: function () { + this.copyTpl(this.projectConfig.path.srcDir, this.projectConfig.path.app.layoutDir, '**', '*.js'); + this.copyTpl(this.projectConfig.path.srcDir, this.projectConfig.path.app.layoutDir, '**', '*.html'); }, homeExample: function () { - var homePath = path.join( - this.projectConfig.path.srcDir, - this.projectConfig.path.appDir, - 'home' - ); - - this.template( - this.templatePath(path.join(homePath, 'home.module.js')), - this.destinationPath(path.join(homePath, 'home.module.js')), this.projectConfig - ); - - this.template( - this.templatePath(path.join(homePath, 'views/views.module.js')), - this.destinationPath(path.join(homePath, 'views/views.module.js')), this.projectConfig - ); - - this.template( - this.templatePath(path.join(homePath, '/views/home.js')), - this.destinationPath(path.join(homePath, '/views/home.js')), this.projectConfig - ); - - this.copy( - this.templatePath(path.join(homePath, '/views/home.html')), - this.destinationPath(path.join(homePath, '/views/home.html')) - ); - + this.copyTpl(this.projectConfig.path.srcDir, this.projectConfig.path.appDir, 'home', '**', '*.js'); + this.copyTpl(this.projectConfig.path.srcDir, this.projectConfig.path.appDir, 'home', '**', '*.html'); }, runNpm: function () { - this.npmInstall(); + this.npmInstall(); this.bowerInstall(); }, + installTypeDefs: function () { + // download type definitions if TypeScript was enabled + if (this.useTypescript) { + this.env.runLoop.add('install', function (done) { + this.emit('tsdReinstall'); + + this.log('Running ' + chalk.yellow.bold('tsd reinstall --save') + '. If this fails run the commands yourself after running `npm install -g tsd`.'); + + this.spawnCommand('node', ['node_modules/tsd/build/cli.js', 'reinstall', '--save']) + .on('exit', function (err) { + if (err === 127) { + this.log.error('Could not find tsd. Please install with `npm install -g tsd`.'); + } + this.emit('tsdReinstall:end'); + done(); + }.bind(this)); + }.bind(this), { once: 'tsd reinstall', run: false }); + } + }, end: function () { this.log(''); this.log(hirschUtils.hirschSay()); this.log('Go to your project folder and run ' + chalk.bold.yellow('gulp serve')); - this.log('Than visit your app on ' + chalk.bold.yellow('http://localhost:3000')); + this.log('Then visit your app on ' + chalk.bold.yellow('http://localhost:3000')); this.log(''); } }); diff --git a/app/templates/_gitignore b/app/templates/_gitignore index 7740491..fb3d3a5 100644 --- a/app/templates/_gitignore +++ b/app/templates/_gitignore @@ -9,3 +9,4 @@ old/ bower_components/ .DS_Store npm-debug.log +<% if (prompts.useTypescript) { %><%= prompts.typingsPath %>/<% } %> diff --git a/app/templates/_package.json b/app/templates/_package.json index 52dd8de..281d767 100644 --- a/app/templates/_package.json +++ b/app/templates/_package.json @@ -4,7 +4,7 @@ "description": "<%= prompts.description %>", "prefix": "<%= prompts.prefix %>", "main": "<%= path.distDir %>/<%= path.main %>", - "language": "JavaScript", + "language": <% if(prompts.useTypescript) { %> "TypeScript" <% } else { %> "JavaScript" <% } %>, "private": true, "dependencies": {}, "devDependencies": { @@ -22,6 +22,7 @@ "gulp-minify-html": "^0.1.7", "gulp-ng-annotate": "^0.5.2", "gulp-rename": "^1.2.0", + "gulp-sourcemaps": "^1.5.2", "gulp-task-listing": "^1.0.0", "gulp-uglify": "^1.1.0", "gulp-util": "^3.0.4", @@ -41,7 +42,11 @@ "lodash": "^3.5.0", "bower-files": "^3.3.0", "main-bower-files": "^2.5.0", - "wiredep": "^2.2.2" + "wiredep": "^2.2.2"<% if (prompts.useTypescript) { %>, + "tsd": "^0.6.0", + "typescript": "~1.4.0", + "gulp-typescript": "^2.7.5", + "gulp-tslint": "^2.0.0"<% } %> }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" diff --git a/app/templates/_project.config.js b/app/templates/_project.config.js index 03d5aa4..0513d28 100644 --- a/app/templates/_project.config.js +++ b/app/templates/_project.config.js @@ -9,9 +9,18 @@ module.exports = function (isGenerator) { var wiredep = require('wiredep'); var bowerFilesJs = []; var bowerFilesCss = []; + var bowerFilesFonts = []; try { bowerFilesJs = (!isGenerator) ? wiredep({})['js'] : []; bowerFilesCss = (!isGenerator) ? wiredep({})['css'] : []; + if (!isGenerator) { + bowerFilesFonts = ['font-awesome', 'bootstrap'] + .map(function (s) { return wiredep({}).packages[s].main; }) + .reduce(function (a, b) { return a.concat(b); }) + .filter(function (p) { + return new RegExp('\\' + path.sep + 'fonts\\' + path.sep).test(p); + }); + } } catch (e) { } @@ -67,7 +76,7 @@ module.exports = function (isGenerator) { }, app: { main: 'app/app.js', - modules: 'app/**/*module.js', + modules: 'app/**/*.module.js', services: 'app/**/*.service.js', directives: 'app/**/*.directive.js', scripts: 'app/**/*.js', @@ -170,6 +179,7 @@ module.exports = function (isGenerator) { ), unit: [ 'src/lib/angular-mocks/angular-mocks.js', + 'src/lib/angular-ui-router/release/angular-ui-router.js', 'test/unit/**/*.spec.js' ], midway: [ @@ -187,6 +197,9 @@ module.exports = function (isGenerator) { css: [].concat( bowerFilesCss ), + fonts: [].concat( + bowerFilesFonts + ), main: bowerFiles } }; diff --git a/app/templates/_tsconfig.json b/app/templates/_tsconfig.json new file mode 100644 index 0000000..f143a28 --- /dev/null +++ b/app/templates/_tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "noImplicitAny": false, + "removeComments": false, + "preserveConstEnums": true, + "sourceMap": false, + "declaration": false, + "noResolve": true, + "target": "es5" + } +} diff --git a/app/templates/_tsd.json b/app/templates/_tsd.json new file mode 100644 index 0000000..ff041fb --- /dev/null +++ b/app/templates/_tsd.json @@ -0,0 +1,45 @@ +{ + "version": "v4", + "repo": "borisyankov/DefinitelyTyped", + "ref": "master", + "path": "<%= prompts.typingsPath %>", + "bundle": "<%= prompts.typingsPath %>/tsd.d.ts", + "installed": { + "angularjs/angular.d.ts": { + "commit": "c50b111ded0a3a18b87b5abffea3150d6aca94da" + }, + "angularjs/angular-mocks.d.ts": { + "commit": "c50b111ded0a3a18b87b5abffea3150d6aca94da" + }, + "angular-ui-router/angular-ui-router.d.ts": { + "commit": "c50b111ded0a3a18b87b5abffea3150d6aca94da" + }, + "angular-translate/angular-translate.d.ts": { + "commit": "c50b111ded0a3a18b87b5abffea3150d6aca94da" + }, + "angularjs/angular-sanitize.d.ts": { + "commit": "c50b111ded0a3a18b87b5abffea3150d6aca94da" + }, + "angularjs/angular-cookies.d.ts": { + "commit": "c50b111ded0a3a18b87b5abffea3150d6aca94da" + }, + "restangular/restangular.d.ts": { + "commit": "c50b111ded0a3a18b87b5abffea3150d6aca94da" + }, + "jquery/jquery.d.ts": { + "commit": "c50b111ded0a3a18b87b5abffea3150d6aca94da" + }, + "lodash/lodash.d.ts": { + "commit": "c50b111ded0a3a18b87b5abffea3150d6aca94da" + }, + "moment/moment.d.ts": { + "commit": "7d3a939fbe55576fad2e074d007f7cc671aa0e78" + }, + "chai/chai.d.ts": { + "commit": "c50b111ded0a3a18b87b5abffea3150d6aca94da" + }, + "mocha/mocha.d.ts": { + "commit": "c50b111ded0a3a18b87b5abffea3150d6aca94da" + } + } +} diff --git a/app/templates/_tslint.json b/app/templates/_tslint.json new file mode 100644 index 0000000..630484d --- /dev/null +++ b/app/templates/_tslint.json @@ -0,0 +1,53 @@ +{ + "rules": { + "class-name": true, + "curly": true, + "eofline": true, + "forin": true, + "indent": [true, 2], + "label-position": true, + "label-undefined": true, + "max-line-length": [true, 140], + "no-arg": true, + "no-bitwise": true, + "no-console": [true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-construct": true, + "no-debugger": true, + "no-duplicate-key": true, + "no-duplicate-variable": true, + "no-empty": true, + "no-eval": true, + "no-string-literal": false, + "no-switch-case-fall-through": true, + "no-trailing-comma": true, + "no-trailing-whitespace": false, + "no-unused-expression": true, + "no-unused-variable": false, + "no-unreachable": true, + "no-use-before-declare": true, + "one-line": [true, + "check-open-brace", + "check-catch", + "check-else", + "check-whitespace" + ], + "quotemark": [true, "single"], + "radix": true, + "semicolon": true, + "triple-equals": [true, "allow-null-check"], + "variable-name": false, + "whitespace": [true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ] + } +} \ No newline at end of file diff --git a/app/templates/src/app-ts/app.ts b/app/templates/src/app-ts/app.ts new file mode 100644 index 0000000..f921855 --- /dev/null +++ b/app/templates/src/app-ts/app.ts @@ -0,0 +1,24 @@ +/// + +module <%= prompts.prefix %> { + 'use strict'; + + angular + .module('<%= prompts.prefix %>', [ + // AngularJS Libs + 'ngSanitize', + 'ngMessages', + + // Third-Party Libs + 'pascalprecht.translate', + + // Configs, middleware, run... + '<%= prompts.prefix %>.core', + + // Common components, services, filters... + + // App modules with business logic + '<%= prompts.prefix %>.layout', + '<%= prompts.prefix %>.home' + ]); +} diff --git a/app/templates/src/app-ts/common/views/abstractController.ts b/app/templates/src/app-ts/common/views/abstractController.ts new file mode 100644 index 0000000..4867c36 --- /dev/null +++ b/app/templates/src/app-ts/common/views/abstractController.ts @@ -0,0 +1,13 @@ +/// + +module <%= prompts.prefix %>.common.views { + export class AbstractController { + constructor($state: ng.ui.IStateService) { + $state.current.onExit = this.dispose.bind(this); + } + + protected dispose() { + // nothing to do here + } + } +} diff --git a/app/templates/src/app-ts/core/config/angular.config.ts b/app/templates/src/app-ts/core/config/angular.config.ts new file mode 100644 index 0000000..dbb805d --- /dev/null +++ b/app/templates/src/app-ts/core/config/angular.config.ts @@ -0,0 +1,47 @@ +/// + +/** + * Defines the AngularJS Modules and configures them + */ +module <%= prompts.prefix %>.core.config { + 'use strict'; + + /** + * Enable debug level messages + */ + var logConfig = ($logProvider: ng.ILogProvider) => { + if ($logProvider.debugEnabled) { + $logProvider.debugEnabled(true); + } + }; + + logConfig.$inject = ['$logProvider']; + + /** + * Allows the framework to store the sails cookie from the backend and disable IE ajax request caching + */ + var httpConfig = ($httpProvider: ng.IHttpProvider) => { + $httpProvider.defaults.withCredentials = true; + if ($httpProvider.defaults.headers['get']) { + $httpProvider.defaults.headers['get']['If-Modified-Since'] = '0'; + } + }; + + httpConfig.$inject = ['$httpProvider']; + + /** + * Tools like Protractor and Batarang need this information to run, but you can disable + * this in production for a significant performance boost with + */ + var compileConfig = ($compileProvider: ng.ICompileProvider) => { + $compileProvider.debugInfoEnabled(false); + }; + + compileConfig.$inject = ['$compileProvider']; + + angular + .module('<%= prompts.prefix %>.core.config.Angular', []) + .config(logConfig) + .config(httpConfig) + .config(compileConfig); +} diff --git a/app/templates/src/app-ts/core/config/config.module.ts b/app/templates/src/app-ts/core/config/config.module.ts new file mode 100644 index 0000000..01e27cc --- /dev/null +++ b/app/templates/src/app-ts/core/config/config.module.ts @@ -0,0 +1,11 @@ +/// + +module <%= prompts.prefix %>.core.config { + 'use strict'; + + angular + .module('<%= prompts.prefix %>.core.config', [ + '<%= prompts.prefix %>.core.config.Angular', + '<%= prompts.prefix %>.core.config.ThirdParty' + ]); +} diff --git a/app/templates/src/app-ts/core/config/thirdParty.config.ts b/app/templates/src/app-ts/core/config/thirdParty.config.ts new file mode 100644 index 0000000..25c5247 --- /dev/null +++ b/app/templates/src/app-ts/core/config/thirdParty.config.ts @@ -0,0 +1,20 @@ +/// + +module <%= prompts.prefix %>.core.config { + 'use strict'; + + var translateConfig = ($translateProvider: ng.translate.ITranslateProvider) => { + $translateProvider.useStaticFilesLoader({ + prefix: './assets/i18n/', + suffix: '.json' + }); + + $translateProvider.preferredLanguage('en'); + }; + + translateConfig.$inject = ['$translateProvider']; + + angular + .module('<%= prompts.prefix %>.core.config.ThirdParty', []) + .config(translateConfig); +} diff --git a/app/templates/src/app-ts/core/constants/constants.module.ts b/app/templates/src/app-ts/core/constants/constants.module.ts new file mode 100644 index 0000000..56daf02 --- /dev/null +++ b/app/templates/src/app-ts/core/constants/constants.module.ts @@ -0,0 +1,15 @@ +/// + +module <%= prompts.prefix %>.core.constants { + 'use strict'; + + angular + .module('<%= prompts.prefix %>.core.constants', [ + '<%= prompts.prefix %>.core.constants.Global' + ]); + + export var ID = { + lodash: '<%= prompts.prefix %>.core.constants.lodash', + moment: '<%= prompts.prefix %>.core.constants.moment' + }; +} diff --git a/app/templates/src/app-ts/core/constants/global.constants.ts b/app/templates/src/app-ts/core/constants/global.constants.ts new file mode 100644 index 0000000..5c7de8f --- /dev/null +++ b/app/templates/src/app-ts/core/constants/global.constants.ts @@ -0,0 +1,11 @@ +/// + +module <%= prompts.prefix %>.core.constants { + 'use strict'; + + angular + .module('<%= prompts.prefix %>.core.constants.Global', []) + .constant(ID.lodash, _) + .constant(ID.moment, moment); + +} diff --git a/app/templates/src/app-ts/core/core.module.ts b/app/templates/src/app-ts/core/core.module.ts new file mode 100644 index 0000000..d0049d2 --- /dev/null +++ b/app/templates/src/app-ts/core/core.module.ts @@ -0,0 +1,13 @@ +/// + +module <%= prompts.prefix %>.core { + 'use strict'; + + angular + .module('<%= prompts.prefix %>.core', [ + '<%= prompts.prefix %>.core.constants', + '<%= prompts.prefix %>.core.config', + '<%= prompts.prefix %>.core.router', + '<%= prompts.prefix %>.core.util' + ]); +} diff --git a/app/templates/src/app-ts/core/router/router.config.ts b/app/templates/src/app-ts/core/router/router.config.ts new file mode 100644 index 0000000..abea3c0 --- /dev/null +++ b/app/templates/src/app-ts/core/router/router.config.ts @@ -0,0 +1,14 @@ +/// + +module <%= prompts.prefix %>.core.router { + + var routerConfig = ($urlRouterProvider: ng.ui.IUrlRouterProvider) => { + $urlRouterProvider.otherwise('/home'); + }; + + routerConfig.$inject = ['$urlRouterProvider']; + + angular + .module('<%= prompts.prefix %>.core.router.RouterConfig', []) + .config(routerConfig); +} diff --git a/app/templates/src/app-ts/core/router/router.constants.ts b/app/templates/src/app-ts/core/router/router.constants.ts new file mode 100644 index 0000000..e379ca4 --- /dev/null +++ b/app/templates/src/app-ts/core/router/router.constants.ts @@ -0,0 +1,12 @@ +/// + +module <%= prompts.prefix %>.core.router { + + var getSecuredRoutes = () => [ + '/private/*' + ]; + + angular + .module('<%= prompts.prefix %>.core.router.RouterConstants', []) + .constant(ID.APP_ROUTER_PRIVATE_ROUTES, getSecuredRoutes()); +} diff --git a/app/templates/src/app-ts/core/router/router.module.ts b/app/templates/src/app-ts/core/router/router.module.ts new file mode 100644 index 0000000..d366492 --- /dev/null +++ b/app/templates/src/app-ts/core/router/router.module.ts @@ -0,0 +1,21 @@ +/// + +module <%= prompts.prefix %>.core.router { + + angular + .module('<%= prompts.prefix %>.core.router', [ + 'ui.router', + 'ui.router.router', + 'ui.router.state', + + '<%= prompts.prefix %>.core.router.RouterConstants', + '<%= prompts.prefix %>.core.router.RouterConfig', + '<%= prompts.prefix %>.core.router.RouterService', + '<%= prompts.prefix %>.core.router.Router' + ]); + + export var ID = { + RouterService: '<%= prompts.prefix %>.core.routing.RouterService', + APP_ROUTER_PRIVATE_ROUTES: '<%= prompts.prefix %>.core.routing.APP_ROUTER_PRIVATE_ROUTES' + }; +} diff --git a/app/templates/src/app-ts/core/router/router.service.ts b/app/templates/src/app-ts/core/router/router.service.ts new file mode 100644 index 0000000..d0b9e5f --- /dev/null +++ b/app/templates/src/app-ts/core/router/router.service.ts @@ -0,0 +1,49 @@ +/// + +module <%= prompts.prefix %>.core.router { + + export interface IRouterService { + isInitialized: boolean; + initialize(): ng.IPromise; + } + + class RouterService implements IRouterService { + private initializing = false; + private initialized = false; + private deferredInit: ng.IDeferred; + private log: util.Logger; + + static $inject = ['$q', util.ID.LoggerFactory]; + constructor($q: ng.IQService, loggerFactory: util.ILoggerFactory) { + this.log = loggerFactory('AppRouterService'); + this.deferredInit = $q.defer(); + this.deferredInit.promise.then(() => this.initialized = true); + } + + get isInitialized() { + return this.initialized; + } + + /** + * Here we will load some initial data for the application like the active event, but + * only at the first run. + */ + initialize = (): ng.IPromise => { + if (this.initializing || this.initialized) { + return this.deferredInit.promise; + } + + this.initializing = true; + + // TODO: load initial data + this.log.info('loadInitialData'); + this.deferredInit.resolve(); + + return this.deferredInit.promise; + }; + } + + angular + .module('<%= prompts.prefix %>.core.router.RouterService', []) + .service(ID.RouterService, RouterService); +} diff --git a/app/templates/src/app-ts/core/router/router.ts b/app/templates/src/app-ts/core/router/router.ts new file mode 100644 index 0000000..d4e3626 --- /dev/null +++ b/app/templates/src/app-ts/core/router/router.ts @@ -0,0 +1,109 @@ +/// + +/** + * Fix for 3rd party type definition file. + */ +declare module angular.ui { + interface IUrlRouterService { + listen(): void; + } +} + +module <%= prompts.prefix %>.core.router { + 'use strict'; + + angular + .module('<%= prompts.prefix %>.core.router.Router', []) + .run(AppRouter); + + AppRouter.$inject = [ + '$q', + '$rootScope', + '$urlRouter', + util.ID.LoggerFactory, + '$state', + ID.RouterService, + ID.APP_ROUTER_PRIVATE_ROUTES + ]; + + function AppRouter( + $q: ng.IQService, + $rootScope: ng.IRootScopeService, + $urlRouter: ng.ui.IUrlRouterService, + logger: util.ILoggerFactory, + $state: ng.ui.IStateService, + routerService: IRouterService, + privateRoutes: string[]) { + var log = logger('AppRouter'); + log.info('start'); + + $rootScope.$on('$locationChangeSuccess', onLocationChange); + + $urlRouter.listen(); + + function onLocationChange(event, toUrl, fromUrl) { + log.info('onLocationChange', { toUrl, fromUrl }); + + // Halt state change from even starting + event.preventDefault(); + + routerService.initialize() + .then(() => ensurePrivateRoute(toUrl)) + .then(() => hasValidSession()) + .catch(err => { + log.warn('catch', err); + if (err.redirct) { + log.info('redirect to login'); + $state.go('authLogin'); + } + + if (err.sync) { + log.info('Browser sync'); + $urlRouter.sync(); + } + }); + } + + function ensurePrivateRoute(toUrl: string) { + var deferred = $q.defer(); + toUrl = parseRoute(toUrl); + + var isPrivate = privateRoutes.some(r => doesUrlMatchPattern(r, toUrl)); + + if (isPrivate) { + deferred.resolve(); + } else { + deferred.reject({ + sync: true + }); + } + + return deferred.promise; + } + + function hasValidSession() { + var deferred = $q.defer(); + + // TODO: Check if the user has a valid session + if (true) { + deferred.resolve(); + } else { + deferred.reject({ + session: true, + redirct: true + }); + } + + return deferred.promise; + } + + function parseRoute(route: string) { + return route.split('#')[1] || route || ''; + } + + function doesUrlMatchPattern(pattern: string, route: string) { + var exp = new RegExp(pattern); + return exp.test(route); + } + } +} diff --git a/app/templates/src/app-ts/core/util/events.ts b/app/templates/src/app-ts/core/util/events.ts new file mode 100644 index 0000000..ade9615 --- /dev/null +++ b/app/templates/src/app-ts/core/util/events.ts @@ -0,0 +1,70 @@ +/// + +/** + * Event bus. Use this class to register events and trigger them from anywhere. + */ +module <%= prompts.prefix %>.core.util { + 'use strict'; + + export interface IAppEvents { + /** + * Returns all registered event callbacks. + */ + list(): { [event: string]: IEventCallback[] }; + + /** + * Register a callback for the given events. Returns + * a disposal function that unregisters the callback + * when called. + */ + on(event: string, callback: IEventCallback): () => boolean; + + /** + * Invoke all callbacks registered for the given event with + * the given data. Returns the number of callbacks invoked. + */ + trigger(event: string, eventObject?: any): number; + } + + export interface IEventCallback { + (eventObj?: any): void; + } + + class AppEvents implements IAppEvents { + private eventCallbacks: { [event: string]: IEventCallback[] }; + + constructor() { + this.eventCallbacks = {}; + } + + list = () => { + return this.eventCallbacks; + }; + + on = (event: string, callback: IEventCallback) => { + var callbacks = this.eventCallbacks[event] || (this.eventCallbacks[event] = []); + callbacks.push(callback); + + return () => { + var idx = callbacks.indexOf(callback); + if (idx >= 0) { + callbacks.splice(idx, 1); + } + + if (callbacks.length === 0) { + delete this.eventCallbacks[event]; + } + + return idx >= 0; + }; + }; + + trigger = (event: string, eventObject?: any) => { + var callbacks = this.eventCallbacks[event] || []; + callbacks.forEach(cb => cb(eventObject)); + return callbacks.length; + }; + } + + angular.module('<%= prompts.prefix %>.core.util.Events', []).service(ID.AppEvents, AppEvents); +} diff --git a/app/templates/src/app-ts/core/util/logger.ts b/app/templates/src/app-ts/core/util/logger.ts new file mode 100644 index 0000000..6ed70ad --- /dev/null +++ b/app/templates/src/app-ts/core/util/logger.ts @@ -0,0 +1,66 @@ +/// + +module <%= prompts.prefix %>.core.util { + 'use strict'; + + var loggerService = ($log, _, moment, appUtil): ILoggerFactory => { + return name => new Logger($log, _, moment, appUtil, name); + }; + + loggerService.$inject = ['$log', constants.ID.lodash, constants.ID.moment, util.ID.AppUtil]; + + export interface ILoggerFactory { + /** + * Get the logger for the given name. + */ + (name: string): Logger; + } + + // TODO: rewrite as angular decorator on top of $log + export class Logger { + constructor( + private $log: angular.ILogService, + private _: _.LoDashStatic, + private moment: moment.MomentStatic, + private appUtil: util.IAppUtil, + public name: string) { + } + + info(text: string | Object | any[], object?: any) { + this.log('info', text, object); + } + + warn(text: string | Object | any[], object?: any) { + this.log('warn', text, object); + } + + error(text: string | Object | any[], object?: any) { + this.log('error', text, object); + } + + private log(type: string, text: string | Object | any[], object?: any) { + if (this.appUtil.getEnvironment() !== 'prod') { + + if (this._.isObject(text) || this._.isArray(text)) { + object = text; + text = undefined; + } + + text = text || ''; + + if (this._.isBoolean(object)) { + object = (object) ? 'YES' : 'NO'; + } + + object = object || ''; + + var arrow = (text !== '' || object !== '') ? '=> ' : ''; + this.$log[type]('[' + this.moment().format('HH:mm:ss.ms') + ' - ' + this.name + '] ' + arrow + text, object); + } + } + } + + angular + .module('<%= prompts.prefix %>.core.util.Logger', []) + .factory(ID.LoggerFactory, loggerService); +} diff --git a/app/templates/src/app-ts/core/util/util.module.ts b/app/templates/src/app-ts/core/util/util.module.ts new file mode 100644 index 0000000..95096db --- /dev/null +++ b/app/templates/src/app-ts/core/util/util.module.ts @@ -0,0 +1,18 @@ +/// + +module <%= prompts.prefix %>.core.util { + 'use strict'; + + angular + .module('<%= prompts.prefix %>.core.util', [ + '<%= prompts.prefix %>.core.util.Logger', + '<%= prompts.prefix %>.core.util.Events', + '<%= prompts.prefix %>.core.util.Util' + ]); + + export var ID = { + AppUtil: '<%= prompts.prefix %>.core.util.Util', + AppEvents: '<%= prompts.prefix %>.core.util.Events', + LoggerFactory: '<%= prompts.prefix %>.core.util.Logger' + }; +} diff --git a/app/templates/src/app-ts/core/util/util.ts b/app/templates/src/app-ts/core/util/util.ts new file mode 100644 index 0000000..b74dfcb --- /dev/null +++ b/app/templates/src/app-ts/core/util/util.ts @@ -0,0 +1,78 @@ +/// + +module <%= prompts.prefix %>.core.util { + 'use strict'; + + export interface IAppUtil { + /** + * The title of the app. + */ + title: string; + server: { local: string; prod: string; }; + + /** + * Returns the backend url for the right environemnt. + * To get the environment, we anlyse the url from our borwser. + */ + getServerUrl(): string; + + /** + * Returns which environment the app is currently running in. + */ + getEnvironment(): string; + + /** + * Builds the path to the view templates + * + * @example AppUtil.buildTemplateUrl('foo') // -> 'app/views/foo' + */ + buildTemplateUrl(url: string): string; + + /** + * Joins the array to url path + * + * @example AppUtil.joinPath(['events','abos']) // -> 'events/abos' + */ + joinPath(segments: string[]): string; + } + + /** + * This Object has some necessary information and methods. Configuration Object + * It is also global, so we can use this outside of the AngularJS Framework + */ + class AppUtil implements IAppUtil { + title = 'admin'; + server = { + local: 'http://localhost:1337/api/', + prod: 'http://localhost:1337/api/' + }; + + getServerUrl = () => { + switch (window.location.hostname) { + case 'localhost': + return this.server.local; + default: + return this.server.prod; + } + }; + + getEnvironment = () => { + switch (window.location.hostname) { + case 'localhost': + return 'dev'; + default: + return 'prod'; + } + }; + + buildTemplateUrl = (url: string): string => { + return 'app/views/' + url; + }; + + joinPath = (segments: string[]): string => { + return segments.join('/'); + }; + } + + angular.module('<%= prompts.prefix %>.core.util.Util', []).service(ID.AppUtil, AppUtil); +} diff --git a/app/templates/src/app-ts/home/home.module.ts b/app/templates/src/app-ts/home/home.module.ts new file mode 100644 index 0000000..3430445 --- /dev/null +++ b/app/templates/src/app-ts/home/home.module.ts @@ -0,0 +1,10 @@ +/// + +module <%= prompts.prefix %>.home { + 'use strict'; + + angular + .module('<%= prompts.prefix %>.home', [ + '<%= prompts.prefix %>.home.views' + ]); +} diff --git a/app/templates/src/app-ts/home/views/home.ts b/app/templates/src/app-ts/home/views/home.ts new file mode 100644 index 0000000..17b8f71 --- /dev/null +++ b/app/templates/src/app-ts/home/views/home.ts @@ -0,0 +1,60 @@ +/// + +module <%= prompts.prefix %>.home.views { + 'use strict'; + + var stateConfig = ($stateProvider: ng.ui.IStateProvider) => { + $stateProvider + .state('admin.home', { + url: '/home', + session: true, + navigationKey: 'home', + views: { + 'content': { + templateUrl: 'app/home/views/home.html', + controller: ID.HomeController, + controllerAs: 'home' + } + } + }); + }; + + stateConfig.$inject = ['$stateProvider']; + + export interface IHomeController { + title: string; + } + + class HomeController extends common.views.AbstractController implements IHomeController { + private offs: Function[] = []; + + title = 'Hirsch says hi!'; + + static $inject = ['$state', core.util.ID.AppEvents]; + constructor($state, events: core.util.IAppEvents) { + super($state); + + this.offs.push(events.on('someEvent', this.onSomeEvent)); + + this.activate(); + } + + private onSomeEvent = (eventObj: any) => { + // TODO: handle event + }; + + private activate = () => { + // run initialization logic + }; + + protected dispose() { + super.dispose(); + this.offs.forEach(off => off()); + } + } + + angular + .module('<%= prompts.prefix %>.home.views.Home', []) + .config(stateConfig) + .controller(ID.HomeController, HomeController); +} diff --git a/app/templates/src/app-ts/home/views/views.module.ts b/app/templates/src/app-ts/home/views/views.module.ts new file mode 100644 index 0000000..809eb0c --- /dev/null +++ b/app/templates/src/app-ts/home/views/views.module.ts @@ -0,0 +1,14 @@ +/// + +module <%= prompts.prefix %>.home.views { + 'use strict'; + + angular + .module('<%= prompts.prefix %>.home.views', [ + '<%= prompts.prefix %>.home.views.Home' + ]); + + export var ID = { + HomeController: '<%= prompts.prefix %>.home.views.HomeController' + }; +} diff --git a/app/templates/src/app-ts/layout/directives/directives.module.ts b/app/templates/src/app-ts/layout/directives/directives.module.ts new file mode 100644 index 0000000..051a494 --- /dev/null +++ b/app/templates/src/app-ts/layout/directives/directives.module.ts @@ -0,0 +1,10 @@ +/// + +module <%= prompts.prefix %>.layout.directives { + 'use strict'; + + angular + .module('<%= prompts.prefix %>.layout.directives', [ + '<%= prompts.prefix %>.layout.directives.Header' + ]); +} diff --git a/app/templates/src/app-ts/layout/directives/header.directive.ts b/app/templates/src/app-ts/layout/directives/header.directive.ts new file mode 100644 index 0000000..3853524 --- /dev/null +++ b/app/templates/src/app-ts/layout/directives/header.directive.ts @@ -0,0 +1,33 @@ +/// + +module <%= prompts.prefix %>.layout.directives { + 'use strict'; + + /** + * Header element outside of the ngView area + */ + class HeaderDirective implements ng.IDirective { + restrict = 'EA'; + templateUrl = 'app/layout/directives/header.directive.html'; + controller = HeaderController; + controllerAs = 'header'; + bindToController = true; // because the scope is isolated + } + + export interface IHeaderController { + title: string; + } + + class HeaderController implements IHeaderController { + title: string; + + static $inject = [core.util.ID.AppUtil]; + constructor(appUtil: core.util.IAppUtil) { + this.title = appUtil.title; + } + } + + angular + .module('<%= prompts.prefix %>.layout.directives.Header', []) + .directive('<%= prompts.prefix %>Header', () => new HeaderDirective()); +} diff --git a/app/templates/src/app-ts/layout/layout.module.ts b/app/templates/src/app-ts/layout/layout.module.ts new file mode 100644 index 0000000..6da65d4 --- /dev/null +++ b/app/templates/src/app-ts/layout/layout.module.ts @@ -0,0 +1,11 @@ +/// + +module <%= prompts.prefix %>.layout { + 'use strict'; + + angular + .module('<%= prompts.prefix %>.layout', [ + '<%= prompts.prefix %>.layout.directives', + '<%= prompts.prefix %>.layout.views' + ]); +} diff --git a/app/templates/src/app-ts/layout/views/admin.ts b/app/templates/src/app-ts/layout/views/admin.ts new file mode 100644 index 0000000..e05751a --- /dev/null +++ b/app/templates/src/app-ts/layout/views/admin.ts @@ -0,0 +1,23 @@ +/// + +module <%= prompts.prefix %>.layout.views { + 'use strict'; + + var stateConfig = ($stateProvider: ng.ui.IStateProvider) => { + $stateProvider.state('admin', { + abstract: true, + session: true, + views: { + 'root': { + templateUrl: 'app/layout/views/admin.html' + } + } + }); + }; + + stateConfig.$inject = ['$stateProvider']; + + angular + .module('<%= prompts.prefix %>.layout.views.Admin', []) + .config(stateConfig); +} diff --git a/app/templates/src/app-ts/layout/views/public.ts b/app/templates/src/app-ts/layout/views/public.ts new file mode 100644 index 0000000..9c98268 --- /dev/null +++ b/app/templates/src/app-ts/layout/views/public.ts @@ -0,0 +1,22 @@ +/// + +module <%= prompts.prefix %>.layout.views { + 'use strict'; + + var stateConfig = ($stateProvider: ng.ui.IStateProvider) => { + $stateProvider.state('public', { + abstract: true, + views: { + 'root': { + templateUrl: 'app/layout/views/public.html' + } + } + }); + }; + + stateConfig.$inject = ['$stateProvider']; + + angular + .module('<%= prompts.prefix %>.layout.views.Public', []) + .config(stateConfig); +} diff --git a/app/templates/src/app-ts/layout/views/views.module.ts b/app/templates/src/app-ts/layout/views/views.module.ts new file mode 100644 index 0000000..4f653a5 --- /dev/null +++ b/app/templates/src/app-ts/layout/views/views.module.ts @@ -0,0 +1,11 @@ +/// + +module <%= prompts.prefix %>.layout.views { + 'use strict'; + + angular + .module('<%= prompts.prefix %>.layout.views', [ + '<%= prompts.prefix %>.layout.views.Admin', + '<%= prompts.prefix %>.layout.views.Public' + ]); +} diff --git a/app/templates/src/app/home/views/Home.html b/app/templates/src/app/home/views/home.html similarity index 100% rename from app/templates/src/app/home/views/Home.html rename to app/templates/src/app/home/views/home.html diff --git a/app/templates/tasks/build.js b/app/templates/tasks/build.js index 4b2d2d9..727d008 100644 --- a/app/templates/tasks/build.js +++ b/app/templates/tasks/build.js @@ -5,4 +5,4 @@ var gulp = require('gulp'); /** * BUILD */ -gulp.task('build', ['jshint', 'inject']); \ No newline at end of file +gulp.task('build', [<% if(!prompts.useTypescript) { %>'jshint', <% } %>'inject']); diff --git a/app/templates/tasks/dist-bower-fonts.js b/app/templates/tasks/dist-bower-fonts.js new file mode 100644 index 0000000..205aa3a --- /dev/null +++ b/app/templates/tasks/dist-bower-fonts.js @@ -0,0 +1,17 @@ +'use strict'; + +var gulp = require('gulp'); +var projectConfig = require(process.cwd() + '/project.config.js')(); +var $ = require('gulp-load-plugins')({ lazy: true }); +var header = require('gulp-header'); +var path = require('path'); + +/** + * COPY BOWER FONT FILES + */ +gulp.task('dist-bower-fonts', function () { + return gulp + .src(projectConfig.bower.files.fonts) + .pipe(header(projectConfig.banner, { pkg: projectConfig.pkg })) + .pipe(gulp.dest(path.join(projectConfig.path.distDir, projectConfig.path.asset.fontDir))); +}); diff --git a/app/templates/tasks/dist-minify-app-js.js b/app/templates/tasks/dist-minify-app-js.js index 280a084..9a82e86 100644 --- a/app/templates/tasks/dist-minify-app-js.js +++ b/app/templates/tasks/dist-minify-app-js.js @@ -9,7 +9,7 @@ var path = require('path'); /** * MINIFY APP JS-FILES */ -gulp.task('dist-minify-app-js', ['jshint'], function () { +gulp.task('dist-minify-app-js'<% if(!prompts.useTypescript) { %>, ['jshint'] <% } %>, function () { var source = projectConfig.angular.files; var destination = path.join(projectConfig.path.distDir, projectConfig.path.appDir); var fileName = projectConfig.buildDistFileName(projectConfig.pkg.name, 'js'); diff --git a/app/templates/tasks/dist-minify-bower-css.js b/app/templates/tasks/dist-minify-bower-css.js index d9085a1..e3b6b4b 100644 --- a/app/templates/tasks/dist-minify-bower-css.js +++ b/app/templates/tasks/dist-minify-bower-css.js @@ -22,7 +22,7 @@ gulp.task('dist-minify-bower-css', function () { return gulp .src(cssFiles, {base: './'}) .pipe($.concat(newCssFileName)) - .pipe(minifyCSS({keepBreaks: true})) + .pipe(minifyCSS({keepBreaks: true, relativeTo: '../assets'})) .pipe(header(projectConfig.banner, {pkg: projectConfig.pkg})) .pipe(gulp.dest(projectConfig.path.distDir)); }); diff --git a/app/templates/tasks/inject.js b/app/templates/tasks/inject.js index fe61717..b1cba9d 100644 --- a/app/templates/tasks/inject.js +++ b/app/templates/tasks/inject.js @@ -10,7 +10,7 @@ var _ = require('lodash'); * INJECT * Injects all bower and application scripts into the main index.html file */ -gulp.task('inject', ['less'], function () { +gulp.task('inject', ['less'<% if(prompts.useTypescript) { %>, 'ts' <% } %>], function () { var source = []; source.push(path.join(projectConfig.path.srcDir, projectConfig.path.asset.css)); diff --git a/app/templates/tasks/jshint.js b/app/templates/tasks/js/jshint.js similarity index 100% rename from app/templates/tasks/jshint.js rename to app/templates/tasks/js/jshint.js diff --git a/app/templates/tasks/serve.js b/app/templates/tasks/serve.js index 0cef272..cbd578c 100644 --- a/app/templates/tasks/serve.js +++ b/app/templates/tasks/serve.js @@ -11,7 +11,7 @@ var path = require('path'); * SERVE * Creates a webserver and adds some watchers to automatically refresh your browser */ -gulp.task('serve', ['inject'], function () { +gulp.task('serve', ['build'], function () { browserSync({ server: { @@ -24,7 +24,7 @@ gulp.task('serve', ['inject'], function () { gulp.watch(path.join(projectConfig.path.srcDir, projectConfig.path.app.templates), ['inject', browserSync.reload]); gulp.watch(path.join(projectConfig.path.srcDir, projectConfig.path.asset.less), ['less', browserSync.reload]); - gulp.watch(path.join(projectConfig.path.srcDir, projectConfig.path.app.scripts), ['inject', browserSync.reload]); + gulp.watch(path.join(projectConfig.path.srcDir, projectConfig.path.app.scripts<% if(prompts.useTypescript) { %>.replace(/\.js$/, '.ts')<% } %>), ['inject', browserSync.reload]); gulp.watch('./bower.json', ['inject', browserSync.reload]); }); diff --git a/app/templates/tasks/test-e2e.js b/app/templates/tasks/test-e2e.js index a1cdef8..ca3092e 100644 --- a/app/templates/tasks/test-e2e.js +++ b/app/templates/tasks/test-e2e.js @@ -2,12 +2,13 @@ var gulp = require('gulp'); var projectConfig = require(process.cwd() + '/project.config.js')(); -var $ = require('gulp-load-plugins')({lazy: true}); +var $ = require('gulp-load-plugins')({ lazy: true }); +var path = require('path'); /** * TEST E2E */ -gulp.task('test-e2e', function () { +gulp.task('test-e2e'<% if(prompts.useTypescript) { %>, ['ts'] <% } %>, function () { var testFiles = projectConfig.karma.files; testFiles.push(projectConfig.path.test.e2e.specs); @@ -15,7 +16,7 @@ gulp.task('test-e2e', function () { return gulp .src(testFiles) .pipe($.karma({ - configFile: './../' + projectConfig.path.test.e2e.config, + configFile: path.join(process.cwd(), projectConfig.path.test.e2e.config), action: 'run' })) .on('error', function (err) { diff --git a/app/templates/tasks/test-midway.js b/app/templates/tasks/test-midway.js index 85d8b4d..8ccd829 100644 --- a/app/templates/tasks/test-midway.js +++ b/app/templates/tasks/test-midway.js @@ -8,7 +8,7 @@ var $ = require('gulp-load-plugins')({lazy: true}); * TEST MIDWAY * Description */ -gulp.task('test-midway', function () { +gulp.task('test-midway'<% if(prompts.useTypescript) { %>, ['ts'] <% } %>, function () { var testFiles = projectConfig.karma.files; testFiles.push(projectConfig.path.test.midway.specs); diff --git a/app/templates/tasks/test-unit.js b/app/templates/tasks/test-unit.js index 5f5f2a0..6b85095 100644 --- a/app/templates/tasks/test-unit.js +++ b/app/templates/tasks/test-unit.js @@ -8,7 +8,7 @@ var $ = require('gulp-load-plugins')({lazy: true}); * TEST UNIT * Description */ -gulp.task('test-unit', function () { +gulp.task('test-unit'<% if(prompts.useTypescript) { %>, ['ts'] <% } %>, function () { var testFiles = projectConfig.karma.files; testFiles.push('src/lib/angular-mocks/angular-mocks.js'); diff --git a/app/templates/tasks/ts/ts-watch.js b/app/templates/tasks/ts/ts-watch.js new file mode 100644 index 0000000..db1e609 --- /dev/null +++ b/app/templates/tasks/ts/ts-watch.js @@ -0,0 +1,14 @@ +'use strict'; + +var gulp = require('gulp'); +var projectConfig = require(process.cwd() + '/project.config.js')(); +var $ = require('gulp-load-plugins')({ lazy: true }); + +/** + * TS WATCH + * Watches source .ts files for changes and recompiles them. + */ +gulp.task('ts-watch', function () { + gulp.watch(projectConfig.path.srcDir + '/' + projectConfig.path.app.scripts.replace(/\.js$/, '.ts'), ['ts']); + gulp.watch(projectConfig.path.testDir + '/' + projectConfig.path.test.specs.replace(/\.js$/, '.ts'), ['ts']); +}); diff --git a/app/templates/tasks/ts/ts.js b/app/templates/tasks/ts/ts.js new file mode 100644 index 0000000..4f83872 --- /dev/null +++ b/app/templates/tasks/ts/ts.js @@ -0,0 +1,30 @@ +'use strict'; + +var gulp = require('gulp'); +var projectConfig = require(process.cwd() + '/project.config.js')(); +var $ = require('gulp-load-plugins')({ lazy: true }); +var typescript = require('typescript'); + +var tsProject = $.typescript.createProject(process.cwd() + '/tsconfig.json', { + typescript: typescript +}); + +/** + * TS + * Lints and compiles all .ts source files in the app. + */ +gulp.task('ts', ['tslint'], function() { + return gulp.src([ + projectConfig.path.srcDir + '/' + projectConfig.path.app.scripts.replace(/\.js$/, '.ts'), + projectConfig.path.testDir + '/' + projectConfig.path.test.specs.replace(/\.js$/, '.ts'), + projectConfig.path.testDir + '/' + projectConfig.path.libDir + '/**/*.ts', + '<%= prompts.typingsPath %>/**/*.d.ts' + ], { + base: '.' + }) + .pipe($.sourcemaps.init()) + .pipe($.typescript(tsProject)) + .js + .pipe($.sourcemaps.write()) + .pipe(gulp.dest('.')); +}); diff --git a/app/templates/tasks/ts/tslint.js b/app/templates/tasks/ts/tslint.js new file mode 100644 index 0000000..eb0b80f --- /dev/null +++ b/app/templates/tasks/ts/tslint.js @@ -0,0 +1,19 @@ +'use strict'; + +var gulp = require('gulp'); +var projectConfig = require(process.cwd() + '/project.config.js')(); +var $ = require('gulp-load-plugins')({ lazy: true }); + +/** + * TSLINT + * Lints all .ts source files in the app. + */ +gulp.task('tslint', function() { + return gulp.src([ + projectConfig.path.srcDir + '/' + projectConfig.path.app.scripts.replace(/\.js$/, '.ts'), + projectConfig.path.testDir + '/' + projectConfig.path.test.specs.replace(/\.js$/, '.ts'), + projectConfig.path.testDir + '/' + projectConfig.path.libDir + '/**/*.ts' + ]) + .pipe($.tslint()) + .pipe($.tslint.report('verbose')); +}); diff --git a/app/templates/test-ts/lib/chai-expect.ts b/app/templates/test-ts/lib/chai-expect.ts new file mode 100644 index 0000000..35da615 --- /dev/null +++ b/app/templates/test-ts/lib/chai-expect.ts @@ -0,0 +1 @@ +var expect = chai.expect; diff --git a/app/templates/test-ts/lib/chai-should.ts b/app/templates/test-ts/lib/chai-should.ts new file mode 100644 index 0000000..71dd9f3 --- /dev/null +++ b/app/templates/test-ts/lib/chai-should.ts @@ -0,0 +1 @@ +var should = chai.should(); diff --git a/app/templates/test-ts/midway/app.spec.ts b/app/templates/test-ts/midway/app.spec.ts new file mode 100644 index 0000000..b65ce2c --- /dev/null +++ b/app/templates/test-ts/midway/app.spec.ts @@ -0,0 +1,26 @@ +/// + +module <%= prompts.prefix %>.test { + 'use strict'; + + describe('Midway: Testing Modules', () => { + + describe('App Module:', () => { + var module: ng.IModule; + before(() => module = angular.module('<%= prompts.prefix %>')); + + it('should be registered', () => should.exist(module)); + + describe('Dependencies:', () => { + var deps: string[]; + var hasModule = m => deps.indexOf(m) >= 0; + before(() => deps = module.value('appName').requires); + + // you can also test the module's dependencies + it('should have <%= prompts.prefix %>.core as a dependency', () => expect(hasModule('<%= prompts.prefix %>.core')).to.equal(true)); + it('should have <%= prompts.prefix %>.home as a dependency', () => expect(hasModule('<%= prompts.prefix %>.home')).to.equal(true)); + it('should have <%= prompts.prefix %>.layout as a dependency', () => expect(hasModule('<%= prompts.prefix %>.layout')).to.equal(true)); + }); + }); + }); +} diff --git a/app/templates/test-ts/unit/core/util/util.spec.ts b/app/templates/test-ts/unit/core/util/util.spec.ts new file mode 100644 index 0000000..8cf78c8 --- /dev/null +++ b/app/templates/test-ts/unit/core/util/util.spec.ts @@ -0,0 +1,20 @@ +/// + +module <%= prompts.prefix %>.core.util.test { + 'use strict'; + + describe('Unit: AppUtil', () => { + + beforeEach(module('<%= prompts.prefix %>.core.util')); + + it('should contain an appUtil service', + angular.mock.inject([ID.AppUtil, appUtil => should.exist(appUtil)]) + ); + + it('should have a getServerUrl function', + angular.mock.inject([ID.AppUtil, appUtil => { + expect(appUtil.getServerUrl()).to.be.a('string'); + }]) + ); + }); +} diff --git a/directive/index.js b/directive/index.js index 6b29076..4b20fbf 100644 --- a/directive/index.js +++ b/directive/index.js @@ -57,19 +57,29 @@ Generator.prototype.options = function () { var relModulePath = path.join(this.module, this.dirName, this.name + '.' + this.generatorName + '.html').toLowerCase(); var relAppPath = path.join(this.env.options.appPath, relModulePath).replace(/^src/, '').replace(/\\/g, '/').replace(/^\//, ''); this.templateUrl = relAppPath; - done(); }.bind(this)); -}; +} + +Generator.prototype.initComponents = function () { + this.readComponents(this.module, this.generatorName, function() { + this.components = this.components.map(function(c) { + return c.replace(/Directive$/, ''); + }); + }.bind(this)); +} Generator.prototype.createFiles = function createFiles() { this.appTemplate(this.generatorName, path.join(this.module, this.dirName, this.name + '.' + this.generatorName)); + if (this.env.options.typescript) { + this.appTemplate(this.dirName + '.module', path.join(this.module, this.dirName, this.dirName + '.module')); + } if(this.hasTemplate){ this.htmlTemplate(this.generatorName, path.join(this.module, this.dirName, this.name + '.' + this.generatorName)); } this.testTemplate('unit', this.generatorName, path.join(this.module, this.dirName, this.name + '.' + this.generatorName + '.spec')); }; -Generator.prototype.end = function createFiles() { +Generator.prototype.end = function () { this.say(this.generatorName); }; diff --git a/filter/index.js b/filter/index.js index a14f8bf..1196fb7 100644 --- a/filter/index.js +++ b/filter/index.js @@ -18,13 +18,20 @@ Generator.prototype.init = function () { Generator.prototype.prompting = function () { this.modulePrompt(); -}; +} + +Generator.prototype.initComponents = function () { + this.readComponents(this.module, this.generatorName); +} Generator.prototype.createFiles = function createFiles() { this.appTemplate(this.generatorName, path.join(this.module, this.dirName, this.name + '.' + this.generatorName)); + if (this.env.options.typescript) { + this.appTemplate(this.dirName + '.module', path.join(this.module, this.dirName, this.dirName + '.module')); + } this.testTemplate('unit', this.generatorName, path.join(this.module, this.dirName, this.name + '.' + this.generatorName + '.spec')); }; -Generator.prototype.end = function createFiles() { +Generator.prototype.end = function () { this.say(this.generatorName); }; diff --git a/module/index.js b/module/index.js index b3e9b3b..fae8d36 100644 --- a/module/index.js +++ b/module/index.js @@ -12,7 +12,7 @@ util.inherits(Generator, ScriptBase); Generator.prototype.createFiles = function createFiles() { this.appTemplate(this.generatorName, path.join(this.name, this.name + '.' + this.generatorName)); - this.testTemplate('midway', this.generatorName, path.join(this.name, this.name + '.' + this.generatorName)); + this.testTemplate('midway', this.generatorName, path.join(this.name, this.name + '.' + this.generatorName + '.spec')); }; Generator.prototype.end = function createFiles() { diff --git a/package.json b/package.json index 75c363f..aa64ebf 100644 --- a/package.json +++ b/package.json @@ -37,9 +37,11 @@ "dependencies": { "chalk": "^0.5.0", "glob": "^4.4.0", + "lodash": "^3.9.3", "main-bower-files": "^2.6.2", "path": "^0.11.14", "require-dir": "^0.3.0", + "underscore.string": "^3.0.3", "wiredep": "^2.2.2", "yeoman-generator": "^0.18.0", "yosay": "^0.3.0" diff --git a/service/index.js b/service/index.js index 8443293..15a8493 100644 --- a/service/index.js +++ b/service/index.js @@ -18,13 +18,40 @@ Generator.prototype.init = function () { Generator.prototype.prompting = function () { this.modulePrompt(); -}; +} + +Generator.prototype.initComponents = function () { + this.readComponents(this.module, this.generatorName); +} + +Generator.prototype.promptForServiceType = function () { + if (this.env.options.typescript) { + var done = this.async(); + var prompts = [ + { + type: 'list', + name: 'serviceType', + message: 'Should the service be generated as a SERVICE or a FACTORY?', + choices: ['service', 'factory'], + default: 0 + } + ]; + + this.prompt(prompts, function(props) { + this.useFactory = props.serviceType === 'factory'; + done(); + }.bind(this)); + } +} Generator.prototype.createFiles = function createFiles() { this.appTemplate(this.generatorName, path.join(this.module, this.dirName, this.name + '.' + this.generatorName)); + if (this.env.options.typescript) { + this.appTemplate(this.dirName + '.module', path.join(this.module, this.dirName, this.dirName + '.module')); + } this.testTemplate('unit', this.generatorName, path.join(this.module, this.dirName, this.name + '.' + this.generatorName + '.spec')); }; -Generator.prototype.end = function createFiles() { +Generator.prototype.end = function () { this.say(this.generatorName); }; diff --git a/templates/typescript/directive.html b/templates/typescript/directive.html new file mode 100644 index 0000000..e69de29 diff --git a/templates/typescript/directive.ts b/templates/typescript/directive.ts new file mode 100644 index 0000000..d198741 --- /dev/null +++ b/templates/typescript/directive.ts @@ -0,0 +1,35 @@ +/// + +module <%= prefix %>.<%= module %>.directives { + 'use strict'; + + class <%= classedName %>Directive implements angular.IDirective { + restrict = '<%= restrict %>';<% if (hasTemplate) { %> + templateUrl = '<%= templateUrl %>';<% } %><% if (hasController) { %> + controller = ID.<%= classedName %>Controller; + controllerAs = '<%= cameledName %>'; + bindToController = true;<% } %><% if (hasLinkFnc) { %> + + link = (scope: angular.IScope, + instanceElement: angular.IAugmentedJQuery, + instanceAttributes: angular.IAttributes<% if (hasController) { %>, + controller: <%= classedName %>Controller<% } %>) => { + // TODO: link logic + };<% } %> + }<% if (hasController) { %> + + export interface I<%= classedName %>Controller { + } + + class <%= classedName %>Controller implements I<%= classedName %>Controller { + static $inject = []; + constructor() { + // TODO + } + }<% } %> + + angular + .module('<%= prefix %>.<%= module %>.directives.<%= classedName %>', []) + .directive('<%= prefix %><%= classedName %>', () => new <%= classedName %>Directive())<% if (hasController) { %> + .controller(ID.<%= classedName %>Controller, <%= classedName %>Controller)<% } %>; +} diff --git a/templates/typescript/directive.unit.spec.ts b/templates/typescript/directive.unit.spec.ts new file mode 100644 index 0000000..882a3b8 --- /dev/null +++ b/templates/typescript/directive.unit.spec.ts @@ -0,0 +1,23 @@ +/// + +module <%= prefix %>.<%= module %>.directives.test { + 'use strict'; + + describe('Unit: <%= prefix %>.<%= module %>.directives.<%= classedName %>Directive', () => { + var $compile, $rootScope; + + beforeEach(module('<%= prefix %>.<%= module %>.directives')); + + beforeEach(angular.mock.inject( + ['$compile', '$rootScope', ($c, $r) => { + $compile = $c; + $rootScope = $r; + }] + ));<% if (hasController) { %> + + var controller: I<%= classedName %>Controller; + beforeEach(inject($controller => controller = $controller(ID.<%= classedName %>Controller))); + + it('should contain a <%= classedName %> controller', () => should.exist(controller));<% } %> + }); +} diff --git a/templates/typescript/directives.module.ts b/templates/typescript/directives.module.ts new file mode 100644 index 0000000..413a3bc --- /dev/null +++ b/templates/typescript/directives.module.ts @@ -0,0 +1,16 @@ +/// + +module <%= prefix %>.<%= module %>.directives { + 'use strict'; + + angular + .module('<%= prefix %>.<%= module %>.directives', [<% for (var i = 0, l = components.length; i < l; i++) { %> + '<%= prefix %>.<%= module %>.directives.<%= components[i] %>',<% } %> + '<%= prefix %>.<%= module %>.directives.<%= classedName %>' + ]); + + export var ID = {<% for (var i = 0, l = components.length; i < l; i++) { %> + <%= components[i] %>Controller: '<%= prefix %>.<%= module %>.directives.<%= components[i] %>Controller', <% } %> + <%= classedName %>Controller: '<%= prefix %>.<%= module %>.directives.<%= classedName %>Controller' + }; +} diff --git a/templates/typescript/filter.ts b/templates/typescript/filter.ts new file mode 100644 index 0000000..3df5943 --- /dev/null +++ b/templates/typescript/filter.ts @@ -0,0 +1,25 @@ +/// + +module <%= prefix %>.<%= module %>.filters { + 'use strict'; + + export interface I<%= classedName %>Filter { + (input: string): string; + } + + var <%= cameledName %> = (): I<%= classedName %>Filter => { + return input => { + input = input || ''; + + // TODO: implement filter logic + + return input; + }; + }; + + <%= cameledName %>.$inject = []; + + angular + .module('<%= prefix %>.<%= module %>.filters.<%= classedName %>Filter', []) + .filter(ID.<%= classedName %>Filter, <%= cameledName %>); +} diff --git a/templates/typescript/filter.unit.spec.ts b/templates/typescript/filter.unit.spec.ts new file mode 100644 index 0000000..28ec115 --- /dev/null +++ b/templates/typescript/filter.unit.spec.ts @@ -0,0 +1,15 @@ +/// + +module <%= prefix %>.<%= module %>.filters.test { + 'use strict'; + + describe('Unit: <%= prefix %>.<%= module %>.filters.<%= classedName %>Filter', () => { + + beforeEach(module('<%= prefix %>.<%= module %>.filters')); + + var <%= cameledName %>: I<%= classedName %>Filter; + beforeEach(inject($filter => <%= cameledName %> = $filter(ID.<%= classedName %>Filter))); + + it('should contain a <%= cameledName %> filter', () => should.exist(<%= cameledName %>)); + }); +} diff --git a/templates/typescript/filters.module.ts b/templates/typescript/filters.module.ts new file mode 100644 index 0000000..da34d8c --- /dev/null +++ b/templates/typescript/filters.module.ts @@ -0,0 +1,16 @@ +/// + +module <%= prefix %>.<%= module %>.filters { + 'use strict'; + + angular + .module('<%= prefix %>.<%= module %>.filters', [<% for (var i = 0, l = components.length; i < l; i++) { %> + '<%= prefix %>.<%= module %>.filters.<%= components[i] %>', <% } %> + '<%= prefix %>.<%= module %>.filters.<%= classedName %>Filter' + ]); + + export var ID = {<% for (var i = 0, l = components.length; i < l; i++) { %> + <%= components[i] %>: '<%= components[i].charAt(0).toLowerCase() %><%= components[i].replace(/Filter$/, '').slice(1) %>', <% } %> + <%= classedName %>Filter: '<%= cameledName %>' + }; +} diff --git a/templates/typescript/module.midway.spec.ts b/templates/typescript/module.midway.spec.ts new file mode 100644 index 0000000..1cd39d0 --- /dev/null +++ b/templates/typescript/module.midway.spec.ts @@ -0,0 +1,32 @@ +/// + +module <%= prefix %>.<%= cameledName %>.test { + 'use strict'; + + describe('Midway: <%= prefix %>.<%= cameledName %>', () => { + var app: ng.IModule; + var appDeps: string[]; + var module: ng.IModule; + var appHasModule = m => appDeps.indexOf(m) >= 0; + + before(() => { + app = angular.module('<%= prefix %>'); + appDeps = app.value('<%= prefix %>').requires; + module = angular.module('<%= prefix %>.<%= cameledName %>'); + }); + + it('should be registered', () => should.exist(module)); + + it('should be registered in the app module', function () { + expect(appHasModule('<%= prefix %>.<%= cameledName %>')).to.equal(true); + }); + + describe('Dependencies: ', () => { + var deps: string[]; + var hasModule = m => deps.indexOf(m) >= 0; + before(() => deps = module.value('<%= prefix %>.<%= cameledName %>').requires); + + // it('should have <%= prefix %>.<%= cameledName %>.Nobody as a dependency', () => expect(hasModule('<%= prefix %>.<%= cameledName %>.Nobody')).to.equal(true)); + }); + }); +} diff --git a/templates/typescript/module.ts b/templates/typescript/module.ts new file mode 100644 index 0000000..feac34b --- /dev/null +++ b/templates/typescript/module.ts @@ -0,0 +1,8 @@ +/// + +module <%= prefix %>.<%= cameledName %> { + 'use strict'; + + angular + .module('<%= prefix %>.<%= cameledName %>', []); +} diff --git a/templates/typescript/service.ts b/templates/typescript/service.ts new file mode 100644 index 0000000..2fd4576 --- /dev/null +++ b/templates/typescript/service.ts @@ -0,0 +1,34 @@ +/// + +module <%= prefix %>.<%= module %>.services { + 'use strict'; + + export interface I<%= classedName %>Service { + method(param: string): string; + } + + class <%= classedName %>Service implements I<%= classedName %>Service { + private field; + + constructor() { + this.field = 'value'; + } + + method = (param: string) => { + return param; + } + }<% if(useFactory) { %> + + var factory = (): I<%= classedName %>Service => { + // TODO: private initialization code + + // TODO: pass initialization parameters to class + return new <%= classedName %>Service(); + }; + factory.$inject = [];<% } %> + + angular + .module('<%= prefix %>.<%= module %>.services.<%= classedName %>Service', [])<% if(!useFactory) { %> + .service(ID.<%= classedName %>Service, <%= classedName %>Service)<% } %><% if(useFactory) { %> + .factory(ID.<%= classedName %>Service, factory)<% } %>; +} diff --git a/templates/typescript/service.unit.spec.ts b/templates/typescript/service.unit.spec.ts new file mode 100644 index 0000000..b3f7517 --- /dev/null +++ b/templates/typescript/service.unit.spec.ts @@ -0,0 +1,17 @@ +/// + +module <%= prefix %>.<%= module %>.services.test { + 'use strict'; + + describe('Unit: <%= prefix %>.<%= module %>.services.<%= classedName %>Service', () => { + + beforeEach(module('<%= prefix %>.<%= module %>.services')); + + var service: I<%= classedName %>Service; + beforeEach(angular.mock.inject([ID.<%= classedName %>Service, s => service = s])); + + it('should contain a <%= classedName %> service', () => should.exist(service)); + + it('should have a method', () => should.exist(service.method)); + }); +} diff --git a/templates/typescript/services.module.ts b/templates/typescript/services.module.ts new file mode 100644 index 0000000..77d22fe --- /dev/null +++ b/templates/typescript/services.module.ts @@ -0,0 +1,16 @@ +/// + +module <%= prefix %>.<%= module %>.services { + 'use strict'; + + angular + .module('<%= prefix %>.<%= module %>.services', [<% for (var i = 0, l = components.length; i < l; i++) { %> + '<%= prefix %>.<%= module %>.services.<%= components[i] %>', <% } %> + '<%= prefix %>.<%= module %>.services.<%= classedName %>Service' + ]); + + export var ID = {<% for (var i = 0, l = components.length; i < l; i++) { %> + <%= components[i] %>: '<%= prefix %>.<%= module %>.services.<%= components[i] %>', <% } %> + <%= classedName %>Service: '<%= prefix %>.<%= module %>.services.<%= classedName %>Service' + }; +} diff --git a/templates/typescript/view.html b/templates/typescript/view.html new file mode 100644 index 0000000..e9f6d9c --- /dev/null +++ b/templates/typescript/view.html @@ -0,0 +1 @@ +

<%= classedName %>

diff --git a/templates/typescript/view.ts b/templates/typescript/view.ts new file mode 100644 index 0000000..87156d7 --- /dev/null +++ b/templates/typescript/view.ts @@ -0,0 +1,78 @@ +/// + +module <%= prefix %>.<%= module %>.views { + 'use strict'; + + var stateConfig = ($stateProvider: ng.ui.IStateProvider) => { + $stateProvider + .state('admin.<%= module %><%= classedName %>', { + url: '/<%= url %>', + session: true, + navigationKey: '<%= module %>', + views: { + 'content': { + templateUrl: '<%= templateUrl %>', + controller: ID.<%= classedName %>Controller, + controllerAs: '<%= cameledName %>' + } + } + }); + }; + + stateConfig.$inject = ['$stateProvider']; + + export interface I<%= classedName %>Controller { + prop: string; + asyncProp: string[]; + method(param: string): string; + action(): void; + } + + class <%= classedName %>Controller extends common.views.AbstractController implements I<%= classedName %>Controller { + private offs: Function[] = []; + + prop: string; + asyncProp: string[]; + + static $inject = ['$state', core.util.ID.AppEvents]; + constructor($state, events: core.util.IAppEvents) { + super($state); + + this.prop = ''; + this.asyncProp = []; + + this.offs.push(events.on('someEvent', this.onSomeEvent)); + + this.activate(); + } + + method = (param: string) => { + return param; + }; + + action = () => { + // TODO: perform some action + }; + + private onSomeEvent = (eventObj: any) => { + // TODO: handle event + }; + + private activate = () => { + // TODO: call some service to asynchronously return data + // this.someService.getData().then(data => this.asyncProp = data); + }; + + protected dispose() { + super.dispose(); + this.offs.forEach(off => off()); + } + } + + angular + .module('<%= prefix %>.<%= module %>.views.<%= classedName %>', [ + '<%= prefix %>.core.util' + ]) + .config(stateConfig) + .controller(ID.<%= classedName %>Controller, <%= classedName %>Controller); +} diff --git a/templates/typescript/view.unit.spec.ts b/templates/typescript/view.unit.spec.ts new file mode 100644 index 0000000..5673a46 --- /dev/null +++ b/templates/typescript/view.unit.spec.ts @@ -0,0 +1,15 @@ +/// + +module <%= prefix %>.<%= module %>.views.test { + 'use strict'; + + describe('Unit: <%= prefix %>.<%= module %>.views.<%= classedName %>Controller', () => { + + beforeEach(module('<%= prefix %>.<%= module %>.views')); + + var controller: I<%= classedName %>Controller; + beforeEach(inject($controller => controller = $controller(ID.<%= classedName %>Controller))); + + it('should contain a <%= classedName %> controller', () => should.exist(controller)); + }); +} diff --git a/templates/typescript/views.module.ts b/templates/typescript/views.module.ts new file mode 100644 index 0000000..7705870 --- /dev/null +++ b/templates/typescript/views.module.ts @@ -0,0 +1,19 @@ +/// + +module <%= prefix %>.<%= module %>.views { + 'use strict'; + + angular + .module('<%= prefix %>.<%= module %>.views', [ + 'ui.router', + 'ui.router.router', + 'ui.router.state'<% for (var i = 0, l = components.length; i < l; i++) { %>, + '<%= prefix %>.<%= module %>.views.<%= components[i] %>'<% } %>, + '<%= prefix %>.<%= module %>.views.<%= classedName %>' + ]); + + export var ID = {<% for (var i = 0, l = components.length; i < l; i++) { %> + <%= components[i] %>Controller: '<%= prefix %>.<%= module %>.views.<%= components[i] %>Controller', <% } %> + <%= classedName %>Controller: '<%= prefix %>.<%= module %>.views.<%= classedName %>Controller' + }; +} diff --git a/tsd.json b/tsd.json new file mode 100644 index 0000000..717f6ef --- /dev/null +++ b/tsd.json @@ -0,0 +1,39 @@ +{ + "version": "v4", + "repo": "borisyankov/DefinitelyTyped", + "ref": "master", + "path": "typings", + "bundle": "typings/tsd.d.ts", + "installed": { + "angularjs/angular.d.ts": { + "commit": "7d3a939fbe55576fad2e074d007f7cc671aa0e78" + }, + "angularjs/angular-mocks.d.ts": { + "commit": "7d3a939fbe55576fad2e074d007f7cc671aa0e78" + }, + "angular-ui-router/angular-ui-router.d.ts": { + "commit": "7d3a939fbe55576fad2e074d007f7cc671aa0e78" + }, + "angular-translate/angular-translate.d.ts": { + "commit": "7d3a939fbe55576fad2e074d007f7cc671aa0e78" + }, + "jquery/jquery.d.ts": { + "commit": "7d3a939fbe55576fad2e074d007f7cc671aa0e78" + }, + "lodash/lodash.d.ts": { + "commit": "7d3a939fbe55576fad2e074d007f7cc671aa0e78" + }, + "moment/moment.d.ts": { + "commit": "7d3a939fbe55576fad2e074d007f7cc671aa0e78" + }, + "chai/chai.d.ts": { + "commit": "7d3a939fbe55576fad2e074d007f7cc671aa0e78" + }, + "mocha/mocha.d.ts": { + "commit": "7d3a939fbe55576fad2e074d007f7cc671aa0e78" + }, + "node/node.d.ts": { + "commit": "2d4c4679bc2b509f27435a4f9da5e2de11258571" + } + } +} diff --git a/util.js b/util.js index d58491f..610e6ae 100644 --- a/util.js +++ b/util.js @@ -3,6 +3,8 @@ var path = require('path'); var fs = require('fs'); var chalk = require('chalk'); var glob = require('glob'); +var _s = require('underscore.string'); +var _ = require('lodash'); module.exports = { @@ -55,7 +57,7 @@ module.exports = { }, getComponentsFromFileStructure: function (scope, module, type, cb) { - fs.readdir(scope.destinationPath(path.join(scope.env.options.srcPath, scope.env.options.appDir, module, type + 's')), function (err, files) { + fs.readdir(scope.destinationPath(path.join(scope.env.options.appPath, module, type + 's')), function (err, files) { if (files) { var components = _.uniq(files.filter(function(f) { return f.indexOf('.module') === -1; diff --git a/view/index.js b/view/index.js index cd3373c..1b0d24c 100644 --- a/view/index.js +++ b/view/index.js @@ -37,14 +37,21 @@ Generator.prototype.options = function () { this.templateUrl = relAppPath; done(); }.bind(this)); +} + +Generator.prototype.initComponents = function () { + this.readComponents(this.module, this.generatorName); }; Generator.prototype.createFiles = function createFiles() { this.appTemplate(this.generatorName, path.join(this.module, this.dirName, this.name)); + if (this.env.options.typescript) { + this.appTemplate(this.dirName + '.module', path.join(this.module, this.dirName, this.dirName + '.module')); + } this.htmlTemplate(this.generatorName, path.join(this.module, this.dirName, this.name)); this.testTemplate('unit', this.generatorName, path.join(this.module, this.dirName, this.name + '.' + this.generatorName + '.spec')); }; -Generator.prototype.end = function createFiles() { +Generator.prototype.end = function () { this.say(this.generatorName); };