├── templates └── default │ ├── package │ ├── both.js │ ├── client.js │ ├── server.js │ ├── .gitignore │ └── package.js │ ├── init │ ├── server │ │ └── .gitkeep │ ├── .gitignore │ ├── both │ │ ├── lib │ │ │ ├── collections.js │ │ │ └── _namespaces.js │ │ └── routes.js │ ├── client │ │ ├── styles │ │ │ ├── _pages.scss │ │ │ ├── _components.scss │ │ │ └── main.scss │ │ ├── _startup.js │ │ └── main.html │ ├── .jshintignore │ ├── makefile │ └── .jshintrc │ ├── page │ ├── _edit.scss │ ├── _index.scss │ ├── _new.scss │ ├── _show.scss │ ├── edit.js │ ├── index.js │ ├── new.js │ ├── show.js │ ├── new.html │ ├── edit.html │ ├── index.html │ └── show.html │ ├── component │ ├── comp_name.scss │ ├── comp_name.html │ └── comp_name.js │ ├── lib │ └── namespaces.js │ ├── controller │ ├── base.js │ ├── _app.js │ ├── new.js │ ├── create.js │ ├── destroy.js │ ├── update.js │ ├── index.js │ ├── show.js │ └── edit.js │ ├── publish │ └── publish.js │ ├── router │ └── routes.js │ └── collection │ └── permission.js ├── bin └── mgen ├── .gitignore ├── tests ├── spec_helper.coffee ├── package_spec.coffee ├── rename_spec.coffee └── parse_name_spec.coffee ├── Makefile ├── package.json ├── LICENSE ├── lib ├── collection.coffee ├── package.coffee ├── namespace.coffee ├── publish.coffee ├── rename.js ├── component.coffee ├── parse_name.js ├── router.js ├── init.js ├── controller.js ├── page.js └── commands.coffee └── README.md /templates/default/package/both.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/default/init/server/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/default/package/client.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/default/package/server.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/default/init/.gitignore: -------------------------------------------------------------------------------- 1 | .meteor 2 | -------------------------------------------------------------------------------- /templates/default/init/both/lib/collections.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/default/init/client/styles/_pages.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/default/init/client/styles/_components.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/default/page/_edit.scss: -------------------------------------------------------------------------------- 1 | .comp-name { 2 | 3 | } 4 | 5 | -------------------------------------------------------------------------------- /templates/default/page/_index.scss: -------------------------------------------------------------------------------- 1 | .comp-name { 2 | 3 | } 4 | 5 | -------------------------------------------------------------------------------- /templates/default/page/_new.scss: -------------------------------------------------------------------------------- 1 | .comp-name { 2 | 3 | } 4 | 5 | -------------------------------------------------------------------------------- /templates/default/page/_show.scss: -------------------------------------------------------------------------------- 1 | .comp-name { 2 | 3 | } 4 | 5 | -------------------------------------------------------------------------------- /templates/default/init/.jshintignore: -------------------------------------------------------------------------------- 1 | client/compatibility 2 | packages 3 | -------------------------------------------------------------------------------- /templates/default/package/.gitignore: -------------------------------------------------------------------------------- 1 | .build* 2 | .build/ 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /templates/default/init/makefile: -------------------------------------------------------------------------------- 1 | dev: 2 | meteor deploy yourproject.meteor.com 3 | -------------------------------------------------------------------------------- /templates/default/component/comp_name.scss: -------------------------------------------------------------------------------- 1 | .__name-singular-hyphen__ { 2 | 3 | } 4 | 5 | -------------------------------------------------------------------------------- /templates/default/init/client/_startup.js: -------------------------------------------------------------------------------- 1 | Meteor.startup(function() { 2 | 3 | }); 4 | 5 | -------------------------------------------------------------------------------- /templates/default/init/client/styles/main.scss: -------------------------------------------------------------------------------- 1 | @import 'components'; 2 | 3 | @import 'pages'; 4 | 5 | -------------------------------------------------------------------------------- /templates/default/init/both/lib/_namespaces.js: -------------------------------------------------------------------------------- 1 | /*global db:true */ 2 | 3 | // mini-mongo collection namespace 4 | db = {}; 5 | 6 | -------------------------------------------------------------------------------- /templates/default/lib/namespaces.js: -------------------------------------------------------------------------------- 1 | /*global db:true */ 2 | 3 | // Collections namespace 4 | db = {}; 5 | 6 | // Resource namespaces 7 | -------------------------------------------------------------------------------- /templates/default/controller/base.js: -------------------------------------------------------------------------------- 1 | /*global __name-plural-pascal__Controller:true */ 2 | 3 | __name-plural-pascal__Controller = {}; 4 | 5 | -------------------------------------------------------------------------------- /bin/mgen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // fire up Commander and parse params 4 | require('coffee-script/register'); 5 | require('../lib/commands'); 6 | 7 | -------------------------------------------------------------------------------- /templates/default/component/comp_name.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .meteor 2 | lib-cov 3 | *.seed 4 | *.log 5 | *.csv 6 | *.dat 7 | *.out 8 | *.pid 9 | *.gz 10 | 11 | pids 12 | logs 13 | results 14 | npm-debug.log 15 | node_modules 16 | 17 | tags 18 | -------------------------------------------------------------------------------- /templates/default/page/edit.js: -------------------------------------------------------------------------------- 1 | Template.compName.helpers({ 2 | 3 | }); 4 | 5 | Template.compName.events({ 6 | 7 | }); 8 | 9 | Template.compName.rendered = function() { 10 | 11 | }; 12 | -------------------------------------------------------------------------------- /templates/default/page/index.js: -------------------------------------------------------------------------------- 1 | Template.compName.helpers({ 2 | 3 | }); 4 | 5 | Template.compName.events({ 6 | 7 | }); 8 | 9 | Template.compName.rendered = function() { 10 | 11 | }; 12 | -------------------------------------------------------------------------------- /templates/default/page/new.js: -------------------------------------------------------------------------------- 1 | Template.compName.helpers({ 2 | 3 | }); 4 | 5 | Template.compName.events({ 6 | 7 | }); 8 | 9 | Template.compName.rendered = function() { 10 | 11 | }; 12 | -------------------------------------------------------------------------------- /templates/default/page/show.js: -------------------------------------------------------------------------------- 1 | Template.compName.helpers({ 2 | 3 | }); 4 | 5 | Template.compName.events({ 6 | 7 | }); 8 | 9 | Template.compName.rendered = function() { 10 | 11 | }; 12 | -------------------------------------------------------------------------------- /templates/default/publish/publish.js: -------------------------------------------------------------------------------- 1 | Meteor.publish('__pub-name__', function() { 2 | // TODO index query 3 | // XXX bad performance no limit 4 | return db.__name-plural-camel__.find({}); 5 | }); 6 | 7 | 8 | -------------------------------------------------------------------------------- /templates/default/page/new.html: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /templates/default/page/edit.html: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /templates/default/page/index.html: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /templates/default/page/show.html: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /templates/default/router/routes.js: -------------------------------------------------------------------------------- 1 | // Iron Router - for useage see https://github.com/EventedMind/iron-router 2 | 3 | Router.configure({ 4 | layoutTemplate: 'mainLayout' 5 | }); 6 | 7 | Router.map(function () { 8 | 9 | });// 10 | -------------------------------------------------------------------------------- /templates/default/component/comp_name.js: -------------------------------------------------------------------------------- 1 | Template.__name-singular-camel__.helpers({ 2 | 3 | }); 4 | 5 | Template.__name-singular-camel__.events({ 6 | 7 | }); 8 | 9 | Template.__name-singular-camel__.rendered = function() { 10 | 11 | }; 12 | -------------------------------------------------------------------------------- /tests/spec_helper.coffee: -------------------------------------------------------------------------------- 1 | global.chai = require "chai" 2 | global.sinon = require "sinon" 3 | global.sinonChai = require "sinon-chai" 4 | global.rewire = require "rewire" 5 | 6 | global.expect = chai.expect 7 | chai.should() 8 | chai.use(sinonChai) 9 | 10 | -------------------------------------------------------------------------------- /templates/default/controller/_app.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------ 2 | // Application Controller 3 | //------------------------------------------------ 4 | 5 | /*global AppController:true, RouteController */ 6 | 7 | AppController = RouteController.extend({ 8 | }); 9 | 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: 2 | make test 3 | 4 | test: 5 | ./node_modules/mocha/bin/mocha --compilers coffee:coffee-script/register -R spec tests/ 6 | 7 | test_watch: 8 | ./node_modules/mocha/bin/mocha -w --compilers coffee:coffee-script/register -R min tests/ 9 | 10 | ctags: 11 | ctags -R --exclude=node_modules 12 | -------------------------------------------------------------------------------- /templates/default/init/both/routes.js: -------------------------------------------------------------------------------- 1 | // Iron Router - for useage see https://github.com/EventedMind/iron-router 2 | 3 | Router.configure({ 4 | layoutTemplate: 'mainLayout' 5 | }); 6 | 7 | Router.map(function () { 8 | 9 | // delete me and replace with your homepage 10 | this.route('homeTemp', {path: '/'}) 11 | 12 | });// 13 | 14 | -------------------------------------------------------------------------------- /templates/default/controller/new.js: -------------------------------------------------------------------------------- 1 | // page for creating a single __name-singular-pascal__ - /__name-plural-hyphen__/new/:id 2 | __name-plural-pascal__Controller.New = AppController.extend({ 3 | template: '__name-plural-camel__New', 4 | 5 | onBeforeAction: function() { 6 | console.log(" [__name-plural-pascal__Controller.New]: loading", this.url); 7 | this.next(); 8 | } 9 | }); 10 | 11 | 12 | -------------------------------------------------------------------------------- /templates/default/controller/create.js: -------------------------------------------------------------------------------- 1 | // create a __name-singular-pascal__ 2 | __name-plural-pascal__Controller.create = function(data, callback) { 3 | console.log('Fired Create __name-singular-pascal__'); 4 | 5 | __name-singular-pascal__.create(data, function(err, docId) { 6 | if (callback) { 7 | callback(err, docId); 8 | } 9 | 10 | if (err) { 11 | console.log(err); 12 | } 13 | }); 14 | }; 15 | 16 | 17 | -------------------------------------------------------------------------------- /templates/default/controller/destroy.js: -------------------------------------------------------------------------------- 1 | // destroy a __name-singular-pascal__ 2 | __name-plural-pascal__Controller.destroy = function(data, callback) { 3 | console.log('Fired Destroy __name-singular-pascal__'); 4 | 5 | __name-singular-pascal__.destroy(data, function(err, count) { 6 | if (callback) { 7 | callback(err, count); 8 | } 9 | 10 | if (err) { 11 | console.log(err); 12 | } 13 | }); 14 | }; 15 | 16 | -------------------------------------------------------------------------------- /templates/default/controller/update.js: -------------------------------------------------------------------------------- 1 | // update a __name-singular-pascal__ 2 | __name-plural-pascal__Controller.update = function(data, callback) { 3 | console.log('Fired Update __name-singular-pascal__'); 4 | 5 | __name-singular-pascal__.update(data, function(err, count) { 6 | if (callback){ 7 | callback(err, count); 8 | } 9 | 10 | if (err) { 11 | console.log(err); 12 | } 13 | }); 14 | }; 15 | 16 | 17 | -------------------------------------------------------------------------------- /templates/default/controller/index.js: -------------------------------------------------------------------------------- 1 | // page for a list of __name-plural-pascal__ - /__name-plural-hyphen__ 2 | __name-plural-pascal__Controller.Index = AppController.extend({ 3 | template: '__name-plural-camel__', 4 | 5 | waitOn: function() { 6 | //return Meteor.subscribe('__name-plural-camel__'); 7 | }, 8 | 9 | data: function() { 10 | //return db.__name-plural-camel__.find(); 11 | }, 12 | 13 | onBeforeAction: function() { 14 | console.log(" [__name-plural-pascal__Controller.Index]: loading", this.url); 15 | this.next(); 16 | } 17 | }); 18 | 19 | 20 | -------------------------------------------------------------------------------- /templates/default/controller/show.js: -------------------------------------------------------------------------------- 1 | // page for showing a single __name-singular-pascal__ - /__name-plural-hyphen__/:id 2 | __name-plural-pascal__Controller.Show = AppController.extend({ 3 | template: '__name-plural-camel__Show', 4 | 5 | waitOn: function() { 6 | //return Meteor.subscribe('__name-singular-camel__', this.params.id); 7 | }, 8 | 9 | data: function() { 10 | //return db.__name-plural-camel__.findOne(this.params.id); 11 | }, 12 | 13 | onBeforeAction: function() { 14 | console.log(" [__name-plural-pascal__Controller.Show]: loading", this.url); 15 | this.next(); 16 | } 17 | }); 18 | 19 | 20 | -------------------------------------------------------------------------------- /templates/default/controller/edit.js: -------------------------------------------------------------------------------- 1 | // show edit page for single __name-singular-pascal__ : /__name-plural-hyphen__/edit/:id 2 | __name-plural-pascal__Controller.Edit = AppController.extend({ 3 | template: '__name-plural-camel__Edit', 4 | 5 | waitOn: function() { 6 | //return Meteor.subscribe('__name-singular-camel__', this.params.id); 7 | }, 8 | 9 | data: function() { 10 | //return db.__name-plural-camel__.findOne(this.params.id); 11 | }, 12 | 13 | onBeforeAction: function() { 14 | console.log(" [__name-plural-pascal__Controller.Edit]: loading", this.url); 15 | this.next(); 16 | } 17 | }); 18 | 19 | 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "meteor-generate", 3 | "version": "0.12.1", 4 | "description": "Generate Meteor scaffolding on demand", 5 | "keywords": [ "meteor", "scaffold"], 6 | "author": "Adam Brodzinski", 7 | "license": "MIT", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/AdamBrodzinski/meteor-generate.git" 11 | }, 12 | 13 | "bin": { "mgen": "./bin/mgen" }, 14 | 15 | "dependencies": { 16 | "coffee-script": "*", 17 | "commander": "*", 18 | "change-case": "*", 19 | "fs-extra": "0.13.0", 20 | "pluralize": "*" 21 | }, 22 | "devDependencies": { 23 | "mocha": "*", 24 | "chai": "*" 25 | }, 26 | "scripts" : { 27 | "test" : "make test" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /templates/default/init/client/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 28 | 29 | -------------------------------------------------------------------------------- /templates/default/collection/permission.js: -------------------------------------------------------------------------------- 1 | // __name-pascal__ Permissions 2 | // see docs for more info - http://docs.meteor.com/#allow 3 | 4 | db.__name-camel__.allow({ 5 | insert: function() { 6 | console.log("\n*** db.__name-camel__ insert not secure ***\n"); 7 | return true; 8 | }, 9 | 10 | update: function() { 11 | console.log("\n*** db.__name-camel__ update not secure ***\n"); 12 | return true; 13 | }, 14 | 15 | remove: function() { 16 | console.log("\n*** db.__name-camel__ remove not secure ***\n"); 17 | return true; 18 | }, 19 | 20 | //fetch: ['owner'], 21 | 22 | // perform a type check to ensure correct data is getting saved 23 | transform: function(doc) { 24 | check(doc, Match.Optional({ 25 | _id: String 26 | })); 27 | 28 | return doc; 29 | } 30 | }); 31 | 32 | 33 | db.__name-camel__.deny({ 34 | update: doesNotOwnDocument, 35 | remove: doesNotOwnDocument, 36 | //fetch: ['owner'] 37 | }); 38 | 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Adam Brodzinski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /tests/package_spec.coffee: -------------------------------------------------------------------------------- 1 | require './spec_helper' 2 | Package = rewire '../lib/package' 3 | 4 | describe 'Package Module', -> 5 | 6 | before -> 7 | global.templatePath = 'test_path' 8 | 9 | Package.__set__("fs", { 10 | copySync: -> "copy success" 11 | existsSync: -> false 12 | appendFileSync: -> "append success" 13 | }) 14 | 15 | it 'should create a new instance', -> 16 | pack = new Package() 17 | create = sinon.spy(pack, 'create') 18 | copyTemp = sinon.spy(pack, 'copyTemplate') 19 | rename = sinon.spy(pack, 'renamePackage') 20 | fsp = sinon.spy(fs, 'appendFileSync') 21 | pack.create('myTemplate') 22 | 23 | copyTemp.should.have.been.calledWith 'myTemplate' 24 | rename.should.have.been.calledOnce 25 | fsp.should.have.been.calledWithExactly './.meteor/packages', 'myTemplate' 26 | 27 | it "should copy the package template folder to clients root folder", -> 28 | pack = new Package() 29 | copy = sinon.spy(fs, 'copySync') 30 | pack.copyTemplate('packageDeluxe') 31 | path = 'test_path/package' 32 | dest = './packages/packageDeluxe' 33 | 34 | copy.should.have.been.calledWith(path, dest) 35 | copy.should.have.returned 'copy success' 36 | 37 | -------------------------------------------------------------------------------- /lib/collection.coffee: -------------------------------------------------------------------------------- 1 | # Meteor Generate 2 | # Copyright(c) 2014 Adam Brodzinski 3 | # MIT Licensed 4 | 5 | fs = require 'fs-extra' 6 | transformVariables = require './rename' 7 | parseName = require './parse_name' 8 | 9 | 10 | # Public: Creates a collection string and inserts it into the user's 11 | # collections file. File extension can be changed in the future when 12 | # more langs are supported. 13 | # 14 | # collName - The {String} name of the mongo collection to create/connect 15 | # opts - The options 16 | # :extension - {String} file extension 17 | # 18 | class Collection 19 | constructor: (collName, opts) -> 20 | @fileExt = opts.extension || '.js' 21 | @nameCamel = parseName(collName).camel 22 | @insertCollection() 23 | 24 | # private 25 | 26 | insertCollection: () -> 27 | collectionLine = @generateCollectionString() 28 | @appendToCollectionsFile(collectionLine) 29 | 30 | generateCollectionString: -> 31 | return "db.#{@nameCamel} = new Meteor.Collection('#{@nameCamel}');\n" 32 | 33 | appendToCollectionsFile: (line) -> 34 | filename = "./both/lib/collections#{@fileExt}" 35 | fs.appendFileSync(filename, line) 36 | puts(" Inserting #{@nameCamel} collection into #{filename}") 37 | 38 | module.exports = Collection 39 | 40 | -------------------------------------------------------------------------------- /templates/default/package/package.js: -------------------------------------------------------------------------------- 1 | // See Meteor docs for API use - https://docs.meteor.com/#/full/packagejs 2 | 3 | Package.describe({ 4 | // Short two-sentence summary. 5 | summary: "What your app does" 6 | 7 | // Version number. 8 | version: "1.0.0", 9 | 10 | // Optional. Default is package directory name. 11 | name: "username:package-name", 12 | 13 | // Optional github URL to your source repository. 14 | git: "https://github.com/something/something.git", 15 | }); 16 | 17 | 18 | Package.onUse(function (api) { 19 | // If no version is specified for an 'api.use' dependency, use the one defined in Meteor 0.9.0. 20 | api.versionsFrom('0.9.0'); 21 | 22 | // Use Underscore package, but only on the server. 23 | // Version not specified, so it will be as of Meteor 0.9.0. 24 | //api.use('underscore', 'server'); 25 | 26 | // Use application-configuration package, version 1.0.0 or newer. 27 | //api.use('accounts@1.0.0'); 28 | 29 | // Give users of this package access to the Templating package. 30 | //api.imply('templating') 31 | 32 | // Specify the source code for the package. 33 | api.add_files('client.js', 'client'); 34 | api.add_files('server.js', 'server'); 35 | api.add_files('both.js', ['client', 'server']); 36 | 37 | // Export the variable 'MyGlobal' in the server (use 'client' for browser) 38 | api.export('MyGlobal', 'server'); 39 | }); 40 | 41 | -------------------------------------------------------------------------------- /lib/package.coffee: -------------------------------------------------------------------------------- 1 | # Meteor Generate 2 | # Copyright(c) 2014 Adam Brodzinski 3 | # MIT Licensed 4 | 5 | global.fs = require 'fs-extra' 6 | 7 | class Package 8 | constructor: -> 9 | @dest = './packages/' 10 | @template = "#{templatePath}/package" 11 | 12 | 13 | # Public Creates a package by copying template folder to users 14 | # folder, then renames any variables found inside. Appends the 15 | # package name to `.meteor/packages` file. 16 | # 17 | # Examples: 18 | # require('./package').create('mixpanel') 19 | # 20 | # packageName - The {String} name of the package 21 | # 22 | create: (packageName) -> 23 | @copyTemplate(packageName) 24 | @renamePackage() 25 | # Append package to Meteor packages 26 | fs.appendFileSync('./.meteor/packages', packageName) 27 | 28 | 29 | # Private: Copy the package template folder to client root folder. 30 | # Renames it to packageName 31 | # 32 | # packageName - The {String} name of the package 33 | # 34 | copyTemplate: (packageName) -> 35 | folderAlreadyExists = fs.existsSync(@dest + packageName) 36 | 37 | unless folderAlreadyExists 38 | fs.copySync(@template, @dest + packageName) 39 | 40 | 41 | # Private: Renames any template variables found inside of the 42 | # template files 43 | # 44 | renamePackage: () -> 45 | 46 | 47 | module.exports = Package 48 | 49 | -------------------------------------------------------------------------------- /tests/rename_spec.coffee: -------------------------------------------------------------------------------- 1 | require('chai').should() 2 | rename = require '../lib/rename' 3 | 4 | describe 'Change Case Module', -> 5 | 6 | it 'should transform a camelCased singular variable', -> 7 | newText = rename "bars", "Hello __name-singular-camel__ End" 8 | newText.should.equal "Hello bar End" 9 | 10 | it 'should transform a camelCased plural variable', -> 11 | newText = rename "bars", "Hello __name-plural-camel__ End" 12 | newText.should.equal "Hello bars End" 13 | 14 | it 'should transform a hyphenated singular variable', -> 15 | newText = rename "foo_bars", "Hello __name-singular-hyphen__ End" 16 | newText.should.equal "Hello foo-bar End" 17 | 18 | it 'should transform a hyphenated plural variable', -> 19 | newText = rename "foo_bars", "Hello __name-plural-hyphen__ End" 20 | newText.should.equal "Hello foo-bars End" 21 | 22 | it 'should transform a snake_case singular variable', -> 23 | newText = rename "foo_bars", "Hello __name-singular-snake__ End" 24 | newText.should.equal "Hello foo_bar End" 25 | 26 | it 'should transform a snake_case plural variable', -> 27 | newText = rename "foo_bars", "Hello __name-plural-snake__ End" 28 | newText.should.equal "Hello foo_bars End" 29 | 30 | it 'should transform a PascalCase singular variable', -> 31 | newText = rename "foo_bars", "Hello __name-singular-pascal__ End" 32 | newText.should.equal "Hello FooBar End" 33 | 34 | it 'should transform a PascalCase plural variable', -> 35 | newText = rename "foo_bar", "Hello __name-plural-pascal__ End" 36 | newText.should.equal "Hello FooBars End" 37 | 38 | -------------------------------------------------------------------------------- /lib/namespace.coffee: -------------------------------------------------------------------------------- 1 | # Meteor Generate 2 | # Copyright(c) 2014 Adam Brodzinski 3 | # MIT Licensed 4 | # 5 | # Namespaces are used for each resource in a Meteor project. For examples the 6 | # Posts resource would have a namespace of `Posts`. Controllers and helpers 7 | # can be inserted in them such as `Posts.destroy()` 8 | 9 | fs = require "fs-extra" 10 | parseName = require './parse_name' 11 | 12 | class Namespace 13 | nsFile: "both/lib/_namespaces.js", 14 | 15 | # Public: Create a namespaces file. Copies over from templates if it 16 | # doesn"t yet exist. 17 | # 18 | createFile: -> 19 | namespaceExists = fs.existsSync("both/lib/_namespaces.js") 20 | unless namespaceExists 21 | fs.copySync("#{templatePath}lib/_namespaces.js", "both/lib/_namespaces.js") 22 | puts " Created: both/lib/_namespaces.js" 23 | 24 | 25 | # Public: Add a namespace to the namespaces file. Allows user to stuff 26 | # things related to a namespace in one area. Checks to make sure 27 | # namespace isn"t already inside file. 28 | # 29 | # resName - The {String} name of the resource, eg `posts` 30 | # 31 | add: (resName) -> 32 | resName = parseName(resName).pascalPlural 33 | namespace = "#{resName} = {}\n" 34 | contrNamespace = "#{resName}Controller = {}\n" 35 | nsFileStr = fs.readFileSync(@nsFile, {encoding: "utf-8"}) 36 | namespaceExists = nsFileStr.match(namespace) 37 | 38 | unless namespaceExists 39 | fs.appendFileSync(this.nsFile, namespace) 40 | puts " Added: #{resName} namespace" 41 | fs.appendFileSync(this.nsFile, contrNamespace) 42 | puts " Added: #{resName}Controller namespace" 43 | 44 | 45 | module.exports = Namespace 46 | 47 | -------------------------------------------------------------------------------- /lib/publish.coffee: -------------------------------------------------------------------------------- 1 | # Meteor Generate 2 | # Copyright(c) 2014 Adam Brodzinski 3 | # MIT Licensed 4 | 5 | fs = require 'fs-extra' 6 | transformVariables = require './rename' 7 | parseName = require './parse_name' 8 | 9 | 10 | class Publish 11 | constructor:(pubParams, opts) -> 12 | @opts = opts 13 | @ext = '.js' 14 | params = @parseParams(pubParams) 15 | @filename = params.resourceName + @ext 16 | @pubName = params.pubName 17 | @resName = params.resourceName 18 | @pubsPath = "server/publications/" 19 | @fullFilePath = @pubsPath + @filename 20 | @templatePath = "#{templatePath}publish/publish" + @ext 21 | 22 | @createPublicationFile() 23 | @appendTemplateIntoPubFile() 24 | puts "Inserted '#{@pubName}' into #{@fullFilePath}" 25 | 26 | # private 27 | 28 | createPublicationFile: -> 29 | fs.ensureFileSync(@fullFilePath) 30 | 31 | appendTemplateIntoPubFile: -> 32 | template = fs.readFileSync(@templatePath, {encoding: "utf-8"}) 33 | template = @renameTemplateVariables(template) 34 | fs.appendFileSync(@fullFilePath, template) 35 | 36 | # returns transformed {String} 37 | renameTemplateVariables: (tmpl) -> 38 | tmplString = transformVariables(@resName, tmpl) 39 | tmplString = tmplString.replace('__pub-name__', @pubName) 40 | 41 | # find the filename and publication name 42 | parseParams:(params) -> 43 | # splits `posts:userPosts` into ['posts:userPosts','posts','userPosts'] 44 | matches = params.match(/^([^:]+):([^:]+)$/) 45 | if !matches || !matches[1] || !matches[2] 46 | @printBadSynaxHelp() 47 | process.exit(1) 48 | else 49 | return {resourceName: matches[1], pubName: matches[2]} 50 | 51 | printBadSynaxHelp: -> 52 | puts "\nBad Publish Name Syntax, see mgen publish --help" 53 | puts '\n Example:' 54 | puts ' $ mgen publish collectionName:publishName' 55 | puts ' $ mgen publish posts:userPost\n' 56 | 57 | module.exports = Publish 58 | 59 | -------------------------------------------------------------------------------- /lib/rename.js: -------------------------------------------------------------------------------- 1 | // Meteor Generate 2 | // Copyright(c) 2014 Adam Brodzinski 3 | // MIT Licensed 4 | 5 | 6 | // Renames variables in a stream of text. Used for converting generic templates 7 | // into custom ones by renaming __name-pascal__ into `PostsThing`. Converts 8 | // hyphen, camel, pascal, and original case (as passed in). See spec for all 9 | // possible variable cases. 10 | // 11 | // Examples: 12 | // 13 | // oldText = "Greetings __name-pascal__"; 14 | // var newText = require('./rename').transform('hello_world', oldText); 15 | // newText === "Greetings HelloWorld"; 16 | // 17 | // resName - a {String} name, preferably snake_cased 18 | // tmplStr - a UTF-8 {String} template, normally streamed from file 19 | // 20 | // Returns a {String} with transformed variables 21 | // 22 | module.exports = function(resName, tmplStr) { 23 | var res = require('./parse_name')(resName); 24 | var newTmpl = tmplStr; 25 | 26 | // transform camelCase template variables 27 | newTmpl = newTmpl.replace(/__name-singular-camel__/g, res.camelSingular); 28 | newTmpl = newTmpl.replace(/__name-plural-camel__/g, res.camelPlural); 29 | newTmpl = newTmpl.replace(/__name-camel__/g, res.camel); 30 | 31 | // transform hyphenated template variables 32 | newTmpl = newTmpl.replace(/__name-singular-hyphen__/g, res.hyphenSingular); 33 | newTmpl = newTmpl.replace(/__name-plural-hyphen__/g, res.hyphenPlural); 34 | newTmpl = newTmpl.replace(/__name-hyphen__/g, res.hyphen); 35 | 36 | // transform snakeCase template variables 37 | newTmpl = newTmpl.replace(/__name-singular-snake__/g, res.snakeSingular); 38 | newTmpl = newTmpl.replace(/__name-plural-snake__/g, res.snakePlural); 39 | newTmpl = newTmpl.replace(/__name-snake__/g, res.snake); 40 | 41 | // transform PascalCase template variables 42 | newTmpl = newTmpl.replace(/__name-singular-pascal__/g, res.pascalSingular); 43 | newTmpl = newTmpl.replace(/__name-plural-pascal__/g, res.pascalPlural); 44 | newTmpl = newTmpl.replace(/__name-pascal__/g, res.pascal); 45 | 46 | return newTmpl; 47 | }; 48 | 49 | -------------------------------------------------------------------------------- /lib/component.coffee: -------------------------------------------------------------------------------- 1 | # Meteor Generate 2 | # Copyright(c) 2014 Adam Brodzinski 3 | # MIT Licensed 4 | # 5 | # Generate a component folder containing an HTML template, JavaScript file 6 | # and a Sass file. The Sass file import is also appended to /client/sass/comps 7 | 8 | 9 | fs = require 'fs-extra' 10 | transformVariables = require './rename' 11 | parseName = require './parse_name' 12 | 13 | 14 | class Component 15 | constructor: (compName, opts) -> 16 | @compName = parseName(compName) 17 | @compName.original = compName 18 | @templatePath = templatePath + 'component/' 19 | @destPath = opts.directory + @compName.snake + '/' 20 | 21 | @copyAllTemplates() 22 | @renameAllTemplates() 23 | @appendStylesheetImport(@compName.snake) 24 | 25 | # private 26 | 27 | copyAllTemplates: () -> 28 | fs.copySync(@templatePath, @destPath) 29 | puts "\nComponent" 30 | puts " Created: #{@compName.snake}.html" 31 | puts " Created: #{@compName.snake}.js" 32 | puts " Created: _#{@compName.snake}.scss" 33 | 34 | 35 | renameAllTemplates: () -> 36 | for extension in [".html", ".js", ".scss"] 37 | filePath = @destPath + "comp_name" + extension 38 | @renameTempateVariables(filePath, extension) 39 | 40 | 41 | renameTempateVariables: (filePath, extension) -> 42 | oldFileStr = fs.readFileSync(filePath, {encoding: 'utf-8'}) 43 | newFileStr = transformVariables(@compName.original, oldFileStr) 44 | fs.writeFileSync(filePath, newFileStr) 45 | @renameFile(filePath, extension) 46 | 47 | 48 | renameFile: (oldFilePath, ext) -> 49 | needsUnder = (ext == '.scss') 50 | fileName = if needsUnder then "_#{@compName.snake}" else @compName.snake 51 | newFilePath = @destPath + fileName + ext 52 | fs.renameSync(oldFilePath, newFilePath) 53 | 54 | 55 | appendStylesheetImport: (fileName) -> 56 | importStr = "@import '../components/#{fileName}/#{fileName}';\n" 57 | fs.appendFileSync('client/styles/_components.scss', importStr) 58 | 59 | 60 | module.exports = Component 61 | 62 | -------------------------------------------------------------------------------- /lib/parse_name.js: -------------------------------------------------------------------------------- 1 | // Meteor Generate 2 | // Copyright(c) 2014 Adam Brodzinski 3 | // MIT Licensed 4 | // 5 | // Parse a root name and return it's original, singular and plural 6 | // names in various cases. 7 | 8 | /** It returns an object with singular 9 | * and plural names. Access them with .camelS for singular, 10 | * .camelP for pluralized case, and .camel for original pluralization. 11 | * camelCase, snake_case, and hyphen-case are available in return object. 12 | * 13 | * return example passing in 'foo_posts': 14 | * { 15 | camel: 'fooPosts' 16 | snake: 'foo_posts', 17 | hyphen: 'foo-posts', 18 | camelS: 'fooPost' 19 | snakeS: 'foo_post', 20 | hyphenS: 'foo-post', 21 | camelP: 'fooPosts', 22 | snakeP: 'foo_posts', 23 | hyphenP: 'foo-posts' 24 | * } 25 | * 26 | * @param rootName {String} - root name to convert from 27 | * @return {Object} 28 | * @api public 29 | */ 30 | module.exports = function(rootName) { 31 | var changeCase, pluralize, singularRoot, pluralRoot; 32 | 33 | changeCase = require('change-case'); 34 | pluralize = require('pluralize'); 35 | 36 | singularRoot = pluralize.singular(rootName); 37 | pluralRoot = pluralize.plural(rootName); 38 | 39 | return { 40 | camel: changeCase.camel(rootName), 41 | snake: changeCase.snake(rootName), 42 | hyphen: changeCase.param(rootName), 43 | pascal: changeCase.title(rootName)[0] + 44 | changeCase.camel(rootName).slice(1), 45 | 46 | camelSingular: changeCase.camel(singularRoot), 47 | snakeSingular: changeCase.snake(singularRoot), 48 | hyphenSingular: changeCase.param(singularRoot), 49 | pascalSingular: changeCase.title(singularRoot)[0] + 50 | changeCase.camel(singularRoot).slice(1), 51 | 52 | camelPlural: changeCase.camel(pluralRoot), 53 | snakePlural: changeCase.snake(pluralRoot), 54 | hyphenPlural: changeCase.param(pluralRoot), 55 | pascalPlural: changeCase.title(pluralRoot)[0] + 56 | changeCase.camel(pluralRoot).slice(1) 57 | }; 58 | }; 59 | 60 | -------------------------------------------------------------------------------- /lib/router.js: -------------------------------------------------------------------------------- 1 | // Meteor Generate 2 | // Copyright(c) 2014 Adam Brodzinski 3 | // MIT Licensed 4 | 5 | /*global templatePath */ 6 | var fs = require('fs-extra'); 7 | 8 | module.exports = { 9 | destRoot: 'both/', 10 | routeSrc: templatePath + 'router/routes.js', 11 | routeDest: 'both/routes.js', 12 | terminateRoute: '});//', 13 | 14 | // \}\); - }); 15 | // \/\/ - // 16 | beforeEndOfRouter: /\}\);\/\//, 17 | 18 | 19 | // Public: Copy routes.js from templates folder into users `both/` folder. 20 | // If file already exists it will not be overwritten. 21 | // 22 | create: function(projPath) { 23 | // handle calling create outside of project and inside 24 | var rootPath = (projPath) ? projPath : './'; 25 | 26 | // create both folder if it doesn't already exisit 27 | fs.mkdirsSync(rootPath); 28 | 29 | // bail if router already exists 30 | if (fs.existsSync(rootPath + 'both/routes.js')) return; 31 | 32 | // copy router.js from templates/ to project/both/controllers 33 | fs.copySync(this.routeSrc, rootPath + 'both/routes.js'); 34 | console.log(' Created: both/routes.js'); 35 | }, 36 | 37 | 38 | // Public: Append the correct route into routes.js. The routes file is 39 | // examined for the `end-routes` marker and it is removed. The new route 40 | // is inserted onto the end of the file and the marker is re-appended. 41 | // 42 | // resName - The {String} name of the controller action, eg index, show 43 | // 44 | appendRoute: function(resName, action) { 45 | var route, newContent, oldFileStr, newFileStr, res; 46 | resName = resName || ''; 47 | 48 | res = require('./parse_name')(resName) 49 | 50 | // set correct route for the required action 51 | // TODO Pascal case namespaces, camelcase routes 52 | switch (action) { 53 | case 'index': 54 | route = " this.route('" + res.camelPlural + "', { path: '/"+resName+"', controller: "+res.pascalPlural+"Controller.Index });"; 55 | break; 56 | case 'new': 57 | route = " this.route('" + res.camelPlural + "New', { path: '/"+resName+"/new', controller: "+res.pascalPlural+"Controller.New });"; 58 | break; 59 | case 'show': 60 | route = " this.route('" + res.camelPlural + "Show', { path: '/"+resName+"/:id', controller: "+res.pascalPlural+"Controller.Show });"; 61 | break; 62 | case 'edit': 63 | route = " this.route('" + res.camelPlural + "Edit', { path: '/"+resName+"/edit/:id', controller: "+res.pascalPlural+"Controller.Edit });"; 64 | break; 65 | case 'comment_line': 66 | route = " // " + resName + " routes"; 67 | break; 68 | case 'blank_line': 69 | route = ""; 70 | break; 71 | default: 72 | route = " this.route('UNKNOWN', { path: '/', controller: UNKNOWN.index });"; 73 | break; 74 | } 75 | 76 | // read routes.js from users folder and save to string 77 | // concat new route together 78 | oldFileStr = fs.readFileSync(this.routeDest, {encoding: 'utf-8'}); 79 | newContent = route + "\n" + this.terminateRoute; 80 | 81 | // insert new content into current routes.js string 82 | // write new content to file `routes/controllers/resource_name.js` 83 | newFileStr = oldFileStr.replace(this.beforeEndOfRouter, newContent); 84 | fs.writeFileSync(this.routeDest, newFileStr); 85 | } 86 | }; 87 | 88 | -------------------------------------------------------------------------------- /lib/init.js: -------------------------------------------------------------------------------- 1 | // Meteor Generate 2 | // Copyright(c) 2014 Adam Brodzinski 3 | // MIT Licensed 4 | 5 | /*global templatePath, puts */ 6 | var fs = require('fs-extra'), 7 | router = require('./router.js'); 8 | 9 | module.exports = { 10 | initSource: templatePath + 'init/', 11 | 12 | // Public: Copy the boilerplate in the templates init folder to 13 | // the user's root project folder. Creates a router and client 14 | // folder. The project name is used for removing original boilerplate. 15 | // 16 | // Examples: 17 | // require('./init').init('myProjectName'); 18 | // 19 | // projectName - the {String} name of the user's meteor project 20 | // 21 | init: function(projectName) { 22 | var self = this; 23 | this.projectName = projectName; 24 | this.projDir = './' + projectName + '/'; 25 | this.packages = this.projDir + '.meteor/packages'; 26 | 27 | this._runTerminalCommand('meteor create '+ projectName, function() { 28 | // after command is finished 29 | router.create(self.projDir); 30 | self._copyTemplate(); 31 | self._removeOriginalMeteorFiles(projectName); 32 | self._removeUnwantedPackages(); 33 | self._addPackagesToPackagesFile(); 34 | 35 | puts('\n-------------------------------------------'); 36 | puts(' type cd %s to navigate to project', projectName); 37 | puts(' then the meteor command to start a server'); 38 | puts('-------------------------------------------\n'); 39 | }); 40 | }, 41 | 42 | 43 | // Private: Run a command on the command line. 44 | // 45 | // command - the {String} command to run 46 | // callback - a {Function} to call when complete 47 | // 48 | _runTerminalCommand: function(command, callback) { 49 | var exec = require('child_process').exec; 50 | 51 | exec(command, function (err) { 52 | if (err) puts('exec error: ' + err); 53 | callback.call(this); 54 | }); 55 | }, 56 | 57 | 58 | // Private: Add packages into Meteor's `.meteor/packages` file so that they 59 | // can get installed on first bootup. Appends the packages to the end of file 60 | // 61 | _addPackagesToPackagesFile: function() { 62 | fs.appendFileSync(this.projDir + '.meteor/packages', 'iron:router\n'); 63 | puts(" Added Package: iron:router"); 64 | }, 65 | 66 | 67 | // Private: Copy the boilerplate from templates directory and place it 68 | // in the user's project root directory. Remove .gitkeep when done 69 | // 70 | _copyTemplate: function() { 71 | fs.copySync(this.initSource, this.projDir); 72 | fs.removeSync(this.projDir + 'client/.gitkeep'); 73 | fs.removeSync(this.projDir + 'server/.gitkeep'); 74 | 75 | puts(' Created: .jshintrc'); 76 | puts(' Created: .jshintignore'); 77 | puts(' Created: makefile'); 78 | }, 79 | 80 | 81 | // Private: Removes the original foo.html, foo.js, and foo.css files 82 | // that Meteor creates after a `meteor create foo` command 83 | // 84 | _removeOriginalMeteorFiles: function() { 85 | fs.removeSync(this.projDir + this.projectName + '.js'); 86 | fs.removeSync(this.projDir + this.projectName + '.html'); 87 | fs.removeSync(this.projDir + this.projectName + '.css'); 88 | puts(' Removed: original boilerplate'); 89 | }, 90 | 91 | 92 | // Private: Removes packages that are not wanted for a production app 93 | // 94 | _removeUnwantedPackages: function() { 95 | this._removePackage('insecure'); 96 | this._removePackage('autopublish'); 97 | puts("Updating Packages"); 98 | puts(" Removed Package: insecure"); 99 | puts(" Removed Package: autopublish"); 100 | }, 101 | 102 | 103 | // Private: Look at packages file and remove package if present. Stream 104 | // packages file, replace, then overwrite original file. 105 | // 106 | // packageName - The {String} name of the package 107 | // 108 | _removePackage: function(packageName) { 109 | var oldPackages, newPackages; 110 | 111 | oldPackages = fs.readFileSync(this.packages, {encoding: 'utf-8'}); 112 | newPackages = oldPackages.replace(packageName + '\n', ''); 113 | fs.writeFileSync(this.packages, newPackages); 114 | } 115 | 116 | }; 117 | 118 | -------------------------------------------------------------------------------- /tests/parse_name_spec.coffee: -------------------------------------------------------------------------------- 1 | require('chai').should() 2 | parseName = require '../lib/parse_name' 3 | 4 | 5 | describe '#parseName', -> 6 | 7 | it 'should not change a one word singular root name', -> 8 | name = parseName 'foo' 9 | name.camelSingular.should.equal 'foo' 10 | name.snakeSingular.should.equal 'foo' 11 | name.hyphenSingular.should.equal 'foo' 12 | name.camel.should.equal 'foo' 13 | name.snake.should.equal 'foo' 14 | name.hyphen.should.equal 'foo' 15 | 16 | it 'should not change a one word plural root name', -> 17 | name = parseName 'bars' 18 | name.camelPlural.should.equal 'bars' 19 | name.snakePlural.should.equal 'bars' 20 | name.hyphenPlural.should.equal 'bars' 21 | name.camel.should.equal 'bars' 22 | name.snake.should.equal 'bars' 23 | name.hyphen.should.equal 'bars' 24 | 25 | # ----------- Convert to CamelCase ------------- 26 | 27 | it 'should convert singular root name to singular camel case', -> 28 | name = parseName 'foo_bar' 29 | name.camelSingular.should.equal 'fooBar' 30 | name.camel.should.equal 'fooBar' 31 | 32 | it 'should convert plural root name to singular camel case', -> 33 | name = parseName 'foo_bars' 34 | name.camelSingular.should.equal 'fooBar' 35 | 36 | it 'should convert plural root name to plural camel case', -> 37 | name = parseName 'bar_posts' 38 | name.camelPlural.should.equal 'barPosts' 39 | name.camel.should.equal 'barPosts' 40 | 41 | it 'should convert singular root name to plural camel case', -> 42 | name = parseName 'bar_post' 43 | name.camelPlural.should.equal 'barPosts' 44 | 45 | 46 | # ----------- Convert to snake_case ------------- 47 | 48 | it 'should convert singular root name to snake case', -> 49 | name = parseName 'foo_bar' 50 | name.snake.should.equal 'foo_bar' 51 | name.snakeSingular.should.equal 'foo_bar' 52 | name = parseName 'foo-bar' 53 | name.snake.should.equal 'foo_bar' 54 | 55 | it 'should convert plural root name to singular snake case', -> 56 | name = parseName 'foo_bars' 57 | name.snakeSingular.should.equal 'foo_bar' 58 | name = parseName 'foo-bars' 59 | name.snakeSingular.should.equal 'foo_bar' 60 | 61 | it 'should convert plural root name to plural snake case', -> 62 | name = parseName 'bar_posts' 63 | name.snakePlural.should.equal 'bar_posts' 64 | name.snake.should.equal 'bar_posts' 65 | name = parseName 'bar-posts' 66 | name.snakePlural.should.equal 'bar_posts' 67 | 68 | it 'should convert singular root name to plural snake case', -> 69 | name = parseName 'foo_post' 70 | name.snakePlural.should.equal 'foo_posts' 71 | 72 | # ----------- Convert to snake_case ------------- 73 | 74 | it 'should convert singular root name to singular hyphen case', -> 75 | name = parseName 'foo_bar' 76 | name.hyphenSingular.should.equal 'foo-bar' 77 | name.hyphen.should.equal 'foo-bar' 78 | 79 | it 'should convert plural root name to singular hyphen case', -> 80 | name = parseName 'foo_bars' 81 | name.hyphenSingular.should.equal 'foo-bar' 82 | 83 | it 'should convert plural root name to plural hyphen case', -> 84 | name = parseName 'bar_posts' 85 | name.hyphenPlural.should.equal 'bar-posts' 86 | name.hyphen.should.equal 'bar-posts' 87 | 88 | it 'should convert singular root name to plural hyphen case', -> 89 | name = parseName 'bar_post' 90 | name.hyphenPlural.should.equal 'bar-posts' 91 | 92 | # ----------- Convert to PascalCase ------------- 93 | 94 | it 'should convert singular root name to singular pascal case', -> 95 | name = parseName 'foo_bar' 96 | name.pascalSingular.should.equal 'FooBar' 97 | name.pascal.should.equal 'FooBar' 98 | 99 | it 'should convert plural root name to singular pascal case', -> 100 | name = parseName 'foo_bars' 101 | name.pascalSingular.should.equal 'FooBar' 102 | 103 | it 'should convert plural root name to plural pascal case', -> 104 | name = parseName 'bar_posts' 105 | name.pascalPlural.should.equal 'BarPosts' 106 | name.pascal.should.equal 'BarPosts' 107 | 108 | it 'should convert singular root name to plural pascal case', -> 109 | name = parseName 'bar_post' 110 | name.pascalPlural.should.equal 'BarPosts' 111 | 112 | -------------------------------------------------------------------------------- /lib/controller.js: -------------------------------------------------------------------------------- 1 | // Meteor Generate 2 | // Copyright(c) 2014 Adam Brodzinski 3 | // MIT Licensed 4 | // 5 | // Creates a Meteor controller for Iron Router and custom CRUD methods. 6 | // Saves controller actions to it's resource namespace. File is generated 7 | // in `both/controllers` folder and the filename takes on the resource name. 8 | // When file already exists, the new controllers are appended to the bottom. 9 | 10 | /*global templatePath */ 11 | var fs = require('fs-extra'); 12 | 13 | module.exports = { 14 | controllerPath : 'both/controllers/', 15 | contrTemplates: templatePath + 'controller/', 16 | 17 | // Public: Creates a controller file & appends any type of controller needed. 18 | // If no options are passed in it will create all router and CRUD controllers 19 | // 20 | // resName - The {String} name of the resource passed in from command line 21 | // opts - The options generated from a Commander command entry. 22 | // 23 | init: function(resName, opts) { 24 | 25 | // TODO ensure resName is always snake case 26 | this.contrFile = this.controllerPath + resName + '.js'; 27 | this.resName = resName; 28 | this.opts = opts; 29 | 30 | // if no args passed in, created all routes 31 | if (!opts.index && !opts.new && !opts.show && !opts.edit && 32 | !opts.create && !opts.update && !opts.destroy) { 33 | this.opts.all = true; 34 | } 35 | 36 | this._createBaseController(); 37 | 38 | // add a comment line before adding new routes 39 | require('./router').appendRoute(resName, 'comment_line'); 40 | 41 | // print 'created' if file doesn't exist yet (created on first append) 42 | if (!fs.existsSync(this.contrFile)) { 43 | console.log(' Created: ' + this.contrFile); 44 | } 45 | 46 | // Append Iron Router Controllers 47 | 48 | if (opts.index || opts.all) { 49 | this._appendController('index'); 50 | require('./page').run(this.resName, {index: true}); 51 | } 52 | 53 | if (opts.new || opts.all) { 54 | this._appendController('new'); 55 | require('./page').run(resName, {'new': true}); 56 | } 57 | 58 | if (opts.show || opts.all) { 59 | this._appendController('show'); 60 | require('./page').run(resName, {show: true}); 61 | } 62 | 63 | if (opts.edit || opts.all) { 64 | this._appendController('edit'); 65 | require('./page').run(resName, {edit: true}); 66 | } 67 | 68 | // Append data Controllers 69 | 70 | if (opts.create || opts.all) { 71 | this._appendController('create'); 72 | } 73 | 74 | if (opts.update || opts.all) { 75 | this._appendController('update'); 76 | } 77 | 78 | if (opts.destroy || opts.all) { 79 | this._appendController('destroy'); 80 | } 81 | 82 | // add blank line to routes.js after controller routes 83 | require('./router').appendRoute(null, 'blank_line'); 84 | 85 | // instantiate and add namespace for this resource if needed 86 | new (require('./namespace'))().add(resName); 87 | }, 88 | 89 | 90 | // Private: Streams controller template from templates folder, renames any 91 | // template variables with `lib/rename.js`and appends the new processed 92 | // template into user's controller folder `both/controller/posts.js`. If this 93 | // file does not exist, it will be created. A route is appended to `routes.js`. 94 | // 95 | // action - The {String} name of the controller action, eg index, show 96 | // 97 | _appendController: function(action) { 98 | var templateStr = fs.readFileSync(this.contrTemplates + action + '.js', 99 | {encoding: 'utf-8'}); 100 | 101 | // rename template variables and append to controller file 102 | templateStr = require('./rename')(this.resName, templateStr); 103 | fs.appendFileSync(this.contrFile, templateStr); 104 | 105 | // add a route for new controller 106 | if (action !== 'create' && action !== 'update' && action !== 'destroy') { 107 | require('./router').appendRoute(this.resName, action); 108 | console.log(' Added Route: ' + this.resName + " " + action); 109 | } 110 | }, 111 | 112 | 113 | // Private: Copy base app controller from templates dir to user project 114 | // folder `both/controllers/app.js` Will not overwrite existing file. 115 | // 116 | _createBaseController: function() { 117 | if (fs.existsSync(this.controllerPath + '_app.js')) return; 118 | 119 | fs.copySync(this.contrTemplates+'_app.js', this.controllerPath+'_app.js'); 120 | console.log(' Created: '+ this.controllerPath +'_app.js'); 121 | } 122 | }; 123 | 124 | -------------------------------------------------------------------------------- /lib/page.js: -------------------------------------------------------------------------------- 1 | // Meteor Generate 2 | // Copyright(c) 2014 Adam Brodzinski 3 | // MIT Licensed 4 | // 5 | // Generate a page folder containing an HTML template, JavaScript file 6 | // and a Sass file. The Sass file include is also appended to /client/sass/comps 7 | 8 | 9 | // XXX dirty dirty dirty (>_<) 10 | // TODO refactor into clean module 11 | 12 | /*global templatePath */ 13 | var fs = require('fs-extra'); 14 | var changeCase = require('change-case'); 15 | var pageTemplates = templatePath + 'page/'; 16 | 17 | exports.run = function(pageName, opts) { 18 | var nameCase, destPath; 19 | 20 | nameCase = { 21 | snake: pageName, 22 | camel: changeCase.camelCase(pageName), 23 | hyphen: changeCase.paramCase(pageName) 24 | }; 25 | 26 | 27 | // set destination, e.g. client/pages/posts/ 28 | opts.directory = opts.directory || 'client/pages/'; 29 | destPath = opts.directory + pageName + '/'; 30 | 31 | 32 | // create a directory in components folder 33 | fs.mkdirs(destPath, function (err) { 34 | var importStr; 35 | 36 | if (err) { 37 | console.error('\nError creating directory', pageName); 38 | console.error(err); 39 | } else { 40 | copyTemplates(); 41 | } 42 | }); 43 | 44 | 45 | var templatePath = pageTemplates; 46 | var pagePath = destPath; 47 | 48 | 49 | function copyTemplates() { 50 | // if no action flags are passed, scaffold everything 51 | if (!opts.index && !opts.new && !opts.show && !opts.edit) { 52 | opts.index = true; 53 | opts.new = true; 54 | opts.show = true; 55 | opts.edit = true; 56 | } 57 | 58 | if (opts.index) { 59 | copyFile({ from: templatePath, to: pagePath, pageName: pageName, 60 | filename: 'index', ext: ['.html', '.js', '.scss'], 61 | replaceWith: pageName 62 | }); 63 | } 64 | 65 | if (opts.new) { 66 | copyFile({ from: templatePath, to: pagePath, pageName: pageName, 67 | filename: 'new', ext: ['.html', '.js', '.scss'], 68 | replaceWith: pageName + 'New' 69 | }); 70 | } 71 | 72 | if (opts.show) { 73 | copyFile({ from: templatePath, to: pagePath, pageName: pageName, 74 | filename: 'show', ext: ['.html', '.js', '.scss'], 75 | replaceWith: pageName + 'Show' 76 | }); 77 | } 78 | 79 | if (opts.edit) { 80 | copyFile({ from: templatePath, to: pagePath, pageName: pageName, 81 | filename: 'edit', ext: ['.html', '.js', '.scss'], 82 | replaceWith: pageName + 'Edit' 83 | }); 84 | } 85 | } 86 | 87 | 88 | // Refactor into module ------------------------------------------ 89 | function copyFile(opts) { 90 | var importStr, ext; 91 | 92 | if (!opts) return console.log('incomplete options'); 93 | ext = opts.ext; 94 | if (typeof ext === 'string') { 95 | ext = [ext]; // turn string ext into array 96 | } 97 | 98 | // itterate through each file and process it 99 | ext.forEach(function(extension) { 100 | var filename; 101 | 102 | filename = opts.filename; 103 | 104 | // handle sass paritals by prepending underscore 105 | if (extension === '.scss') { 106 | importStr = "@import '../pages/" + opts.pageName + "/" + filename + "';\n" 107 | fs.appendFileSync('client/styles/_pages.scss', importStr) 108 | filename = "_" + filename; 109 | } 110 | 111 | // copy from default folder to destination folder and rename any 112 | // variables that may be in each file 113 | fs.copy(opts.from + filename + extension, 114 | opts.to + filename + extension, function (err) { 115 | 116 | handleError(err); 117 | 118 | replace(opts.to + filename + extension, function(data) { 119 | var res, repl; 120 | if (opts.replaceWith) { 121 | repl = opts.replaceWith; 122 | res = data.replace(/compName/g, changeCase.camelCase(repl)); 123 | return res.replace(/comp-name/g, changeCase.paramCase(repl)); 124 | } else { 125 | res = data.replace(/compName/g, nameCase.camel); 126 | return res.replace(/comp-name/g, nameCase.hyphen); 127 | } 128 | }); 129 | 130 | console.log(' Created:', opts.to+opts.filename+extension); 131 | }); 132 | 133 | }); 134 | 135 | } 136 | 137 | 138 | }; 139 | 140 | // TODO refactor these into a module 141 | 142 | 143 | 144 | function handleError(err) { 145 | if (err) { 146 | console.log('\nError Creating File'); 147 | console.log(err.stack); 148 | process.exit(1); 149 | } 150 | } 151 | 152 | 153 | function replace(file, callback) { 154 | fs.readFile(file, 'utf8', function (err,data) { 155 | if (err) { return console.log(err); } 156 | 157 | var result = callback(data); 158 | 159 | fs.writeFile(file, result, 'utf8', function (err) { 160 | if (err) return console.log(err); 161 | }); 162 | }); 163 | } 164 | 165 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Generate Scaffolding for Meteor 2 | 3 | ### Notice 4 | The layout in master will be changing soon. This new structure is geared towards promoting components and moving away from Iron Router's controllers. Instead that logic will be in the component/page scripts. 5 | 6 | Checkout the [Yo generator](https://github.com/AdamBrodzinski/generator-mgen) for the new progress. Eventually mgen will run this beind the scenes to give you a cleaner CLI API than Yo. 7 | 8 | In the future you can pin the release or run a local copy for legacy apps (I have it alised until I can migrate them). 9 | 10 | Automate the common tasks used when building a Meteor app. Create 'pages', components, controllers, collections & more. 11 | User customizable templates and configuration. Meteor Generate borrows a few conventions from Rails but it still stays true to Meteor. 12 | 13 | ### Installation 14 | `sudo npm install meteor-generate --global` 15 | 16 | ### Create 17 | `mgen create ` Runs Meteor create command and scaffolds out a basic app structure. 18 | `mgen create myblog` creates the following files: 19 | 20 | ``` 21 | └── myblog 22 | ├── both 23 | │   ├── lib 24 | │   │   ├── _namespaces.js 25 | │   │   └── collections.js 26 | │   └── routes.js 27 | │ 28 | ├── client/ 29 | │   └── _startup.js 30 | │   └── main.html 31 | │   └── styles/ 32 | │   ├── _components.scss 33 | │   ├── _pages.scss 34 | │   └── main.scss 35 | │ 36 | ├── server/ 37 | │ 38 | ├── .jshintrc 39 | ├── .jshintignore 40 | ├── makefile 41 | └── smart.json 42 | ``` 43 | 44 | 45 | ### Components 46 | `mgen component ` Re-useable widgets that are not tied to a page. These should be able to be included in any page on the app. These are typically things like the header, footer, etc... Appends the Sass import into the `styles/_components.scss` file. 47 | `mgen comp header` outputs the follwing: 48 | 49 | ``` 50 | └── client 51 | └── components 52 | └── header 53 | ├── _header.scss 54 | ├── header.html 55 | └── header.js 56 | ``` 57 | 58 | 59 | 60 | ### Pages 61 | `mgen page ` Create conceptual 'pages'. Creates template, script, and stylesheet. No controllers are generated. 62 | If no action flag is passed in, all actions will be created. Possible actions: index, show, new, and edit. 63 | [See Example Pages][3] - `mgen page posts --index` creates the following: 64 | 65 | ``` 66 | └── client 67 | └── pages 68 | └── posts 69 | ├── index.html 70 | ├── index.js 71 | └── _index.scss 72 | ``` 73 | 74 | 75 | ### Controllers 76 | `mgen controller ` Creates controllers for Iron-Router, adds routes, creates pages for controllers. If only `create`, `update`, or `destroy` actions are passed, no pages are created. These are data only controllers. A namespace file is created if it doesn't exist which adds a `db` namespace for collections. This allows for a natural `db.posts.find(...)` synatax. Creating a posts controller adds a `PostsController` namespace. For example, `PostsController.create()` Possible actions: index, new, show, edit, create, update, and destroy. If no actions are passed, all will be created. 77 | See [Example Controller][4] - `mgen controller posts --show` generates the following: 78 | 79 | ``` 80 | ├── both 81 | │   ├── controllers 82 | │   │   ├── _app.js 83 | │   │   └── posts.js 84 | │   ├── lib 85 | │   │   └── namespaces.js // adds a `db` namespace and `Posts` namespace 86 | │   └── routes.js 87 | │ 88 | └── client 89 | └── pages 90 | └── posts 91 | ├── show.html 92 | ├── show.js 93 | └── _show.scss 94 | ``` 95 | 96 | ```javascript 97 | // show edit page for single Post : /posts/edit/:id 98 | PostsController.Edit = AppController.extend({ 99 | template: 'postsEdit', 100 | 101 | waitOn: function() { 102 | //return Meteor.subscribe('post', this.params.id); 103 | }, 104 | 105 | data: function() { 106 | //return Post.findOne(this.params.id); 107 | }, 108 | 109 | onBeforeAction: function() { 110 | console.log(" [PostsController.Edit]: loading", this.url); 111 | this.next(); 112 | } 113 | }); 114 | 115 | // controller to do something when complete 116 | PostsController.create = function(data, callback) { 117 | console.log('Fired Create Post'); 118 | 119 | // call Post model here... 120 | Post.create(data, function(err, id){ 121 | if (callback) callback(err, id); 122 | if (err) return alert(err.reason); 123 | 124 | Router.go('/somewhere'); 125 | }); 126 | }; 127 | ``` 128 | 129 | 130 | ### Packages 131 | 132 | `mgen package ` Creates a Meteor smart package. Flags for client/server/both coming soon. 133 | `mgen package mixpanel` Generates the following: 134 | 135 | ``` 136 | └── packages 137 | └── mixpanel 138 | ├── both.js 139 | ├── client.js 140 | ├── package.js 141 | ├── server.js 142 | └── smart.json 143 | ``` 144 | 145 | 146 | ### Collections 147 | 148 | Creates a Meteor collection in the `both/lib/collections.js` file. Collections 149 | are inserted into the `db` namespace, access them with `db.posts.find(...)` Passing in `mgen collection posts` generates the following: 150 | 151 | `db.posts = new Meteor.Collection('posts');` 152 | 153 | 154 | ### Models 155 | Coming Soon 156 | 157 | 158 | ### Publications 159 | 160 | Creates a publication on the server. Publications are stored in the `server/publications` directory inside of a script file file with the same name as the resource. Passing in 161 | `mgen publish post:userPost` will generate a 'userPost' publication for the resource 'post'. 162 | 163 | ``` 164 | Meteor.publish('userPost', function() { 165 | // TODO index query 166 | // XXX bad performance no limit 167 | return db.posts.find({}); 168 | }); 169 | ``` 170 | 171 | ### Tests 172 | Coming Soon. Meteor Generate will create tests for Velocity for you by default. 173 | 174 | 175 | ### Custom Templates / Config 176 | Generators are only helpful if they save you time. 177 | 178 | Soon, you will be able to define your own templates folder so that you can include things like form helpers, test helpers... anything you want. A project level mgen config file will help team members sync settings and reduce command line flags. 179 | 180 | 181 | 182 | 183 | ***[Example blog][1] fire it up to see the general structure it generates.*** **note this has extra logic added in** 184 | 185 | [1]: https://github.com/AdamBrodzinski/meteor-generate/tree/master/examples/blog 186 | [2]: https://github.com/AdamBrodzinski/meteor-generate/tree/master/examples/blog/client/pages/posts 187 | [3]: https://github.com/AdamBrodzinski/meteor-generate/tree/master/examples/blog/client/pages 188 | [4]: https://github.com/AdamBrodzinski/meteor-generate/blob/master/examples/blog/both/controllers/posts.js 189 | -------------------------------------------------------------------------------- /lib/commands.coffee: -------------------------------------------------------------------------------- 1 | # Meteor Generate 2 | # Copyright(c) 2014 Adam Brodzinski 3 | # MIT Licensed 4 | 5 | # boot CLI 6 | prog = require 'commander' 7 | prog.version('0.11.0') 8 | 9 | 10 | # hardcode set template path 11 | # TODO add a config to use your own templates 12 | path = require 'path' 13 | global.templatePath = path.resolve("#{__dirname}/../templates/default/") + "/" 14 | 15 | 16 | # helper to make printing look a bit nicer 17 | global.puts = (msg, param1) -> 18 | unless param1 19 | console.log(msg || '') 20 | else 21 | console.log(msg, param1) 22 | 23 | 24 | # require mgen modules 25 | Package = require './package' 26 | Namespace = require './namespace' 27 | Component = require './component' 28 | Collection = require './collection' 29 | Publish = require './publish' 30 | 31 | 32 | 33 | # ------------------- CLI Commands ------------------- 34 | 35 | 36 | 37 | # Create Command 38 | prog.command('create ') 39 | .description('Create a basic running app with a router.') 40 | 41 | .action((projectName, options) -> 42 | puts('\nCreating basic app structure') 43 | require('./init').init(projectName, options) 44 | ) 45 | 46 | .on('--help', -> 47 | puts 'Scaffold out common files like .jshintrc, smart.json, and a makefile' 48 | puts '' 49 | puts ' Example:' 50 | puts ' $ mgen create mybook' 51 | puts ' $ cd mybook' 52 | puts ' $ meteor' 53 | puts '' 54 | ) 55 | 56 | 57 | # Collections 58 | prog.command('collection ') 59 | .description('Add a Meteor Collection"') 60 | 61 | .action((collectionName, options) -> 62 | new Collection(collectionName, options) 63 | ) 64 | 65 | .on('--help', -> 66 | puts ' Inserts a collection into the both/lib/collections file.' 67 | puts ' Multi words should be camel cased like `postThings`' 68 | puts '' 69 | puts ' Examples:' 70 | puts ' $ mgen collection posts' 71 | puts '' 72 | puts ' Creates:' 73 | puts ' db.posts = new Meteor.Collection("posts");' 74 | puts '' 75 | ) 76 | 77 | 78 | 79 | # Components Command 80 | # copies a Meteor component boilerplate folder and moves it into component directory. 81 | # 82 | prog.command('component ') 83 | .description('Create a component template, JS, and Sass files') 84 | 85 | .option("-d, --directory ", "Change the default component directory 'client/components'", "client/components/") 86 | 87 | .action((compName, options) -> 88 | component = new Component(compName, options) 89 | ) 90 | 91 | .on('--help', -> 92 | puts ' Create a component boilerplate folder including a HTML template, JS, & Sass file.' 93 | puts " If a component name has multiple words use 'snake_case'" 94 | puts '' 95 | puts ' Examples:' 96 | puts ' $ mgen component header' 97 | puts ' $ mgen component stock_ticker' 98 | puts '' 99 | ) 100 | 101 | 102 | # Page Command 103 | # copies a Meteor page boilerplate folder and moves it into client/page directory. 104 | prog.command('page ') 105 | .description('Create a page folder with HTML, JS, and Sass files') 106 | 107 | .option("-d, --directory ", "Change the default component directory 'client/pages'", "client/pages/") 108 | .option("-i, --index", "Only create an 'index' page") 109 | .option("-s, --show", "Only create a 'show' page") 110 | .option("-n, --new", "Only create a 'new' page") 111 | .option("-e, --edit", "Only create an 'edit' page") 112 | 113 | .action((pageName, options) -> 114 | require('./page').run(pageName, options) 115 | puts('Creating Pages') 116 | ) 117 | 118 | .on('--help', -> 119 | puts ' Create a page boilerplate folder including a HTML template, JS, & Sass file.' 120 | puts " By default all crud actions will be generated, e.g., new.js, show.js, edit.js, index.js" 121 | puts " Pass --show flag to only generate show.js, show.html, etc.." 122 | puts " If a page name has multiple words use 'snake_case'" 123 | puts '' 124 | puts ' Examples:' 125 | puts ' $ mgen page users' 126 | puts '' 127 | ) 128 | 129 | 130 | # Controller Command 131 | # create a controller and page template 132 | prog.command('controller ') 133 | .description("Create a controller with it's associated pages. Routes are appended to both/routes.js") 134 | .option("-D, --directory ", "Change the default controller directory 'both/controllers/'\n", "both/controllers/") 135 | .option("-i, --index", "Create an index controller - Page of 'posts'") 136 | .option("-n, --new", "Create a new controller - Page to create 'a post'") 137 | .option("-s, --show", "Create a show controller - Page to show 'a post'") 138 | .option("-e, --edit", "Create an edit controller - Page to edit 'a post'") 139 | .option("-c, --create", "Create a create controller - No page, No Route") 140 | .option("-u, --update", "Create an update controller - No page, No Route") 141 | .option("-d, --destroy", "Create a delete controller - No page, No Route") 142 | 143 | .action((resourceName, opts) -> 144 | puts('\nCreating Routes') 145 | namespace = new Namespace 146 | namespace.createFile() 147 | require('./router').create() 148 | require('./controller').init(resourceName, opts) 149 | puts('Creating Pages') 150 | ) 151 | 152 | .on('--help', -> 153 | puts ' Create Iron Router controllers & their page templates. Routes are automatically appended' 154 | puts " to the bottom of routes.js. To create a single page/controller, pass in it's action flag" 155 | puts '' 156 | puts ' Data only controllers (create, update, destroy) do not create routes, but' 157 | puts ' allow you to organize data calls. Optional Meteor Methods in the future.' 158 | puts ' Run the `mrt` command to install Iron Router if this is the first time.' 159 | puts '' 160 | puts ' Examples:' 161 | puts ' $ mgen controller posts ( generate all controllers )' 162 | puts ' $ mgen controller posts --edit --new' 163 | puts ' $ mgen controller posts -en' 164 | puts ' $ mrt #run mrt first time to install iron router' 165 | puts '' 166 | ) 167 | 168 | 169 | # Package Command 170 | prog.command('package ') 171 | .description('Create a basic Meteor/Atmosphere package') 172 | 173 | .action((packageName, options) -> 174 | puts('\nCreating package ' + packageName) 175 | _package = new Package() 176 | _package.create(packageName, options) 177 | ) 178 | 179 | .on('--help', -> 180 | puts 'Create a basic Meteor/Atmosphere package. Includes a smart.json file' 181 | puts 'if you wish to publish it to atmosphere.' 182 | puts '' 183 | puts ' Example:' 184 | puts ' $ mgen package form-checker' 185 | puts '' 186 | ) 187 | 188 | # Package Command 189 | prog.command('publish ') 190 | .description('Insert a Publish Method') 191 | 192 | .action((pubName, options) -> 193 | new Publish(pubName, options) 194 | ) 195 | 196 | .on('--help', -> 197 | puts ' Insert a publish method into /server/publications/ ' 198 | puts ' To insert a "userPost" publication into the ' 199 | puts ' /server/publications/posts.js file we would specify' 200 | puts ' the file name and publication name such as:' 201 | puts '' 202 | puts ' Example:' 203 | puts ' $ mgen publish posts:userPost' 204 | puts '' 205 | ) 206 | 207 | # fire up Commander 208 | prog.parse(process.argv) 209 | 210 | -------------------------------------------------------------------------------- /templates/default/init/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // JSHint Meteor Configuration File 3 | // Match the Meteor Style Guide 4 | // 5 | // By @raix with contributions from @aldeed and @awatson1978 6 | // Source https://github.com/raix/Meteor-jshintrc 7 | // 8 | // See http://jshint.com/docs/ for more details 9 | 10 | "maxerr" : 50, // {int} Maximum error before stopping 11 | 12 | // Enforcing 13 | "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) 14 | "camelcase" : true, // true: Identifiers must be in camelCase 15 | "curly" : true, // true: Require {} for every new block or scope 16 | "eqeqeq" : true, // true: Require triple equals (===) for comparison 17 | "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() 18 | "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` 19 | "indent" : 2, // {int} Number of spaces to use for indentation 20 | "latedef" : false, // true: Require variables/functions to be defined before being used 21 | "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()` 22 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` 23 | "noempty" : true, // true: Prohibit use of empty blocks 24 | "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) 25 | "plusplus" : false, // true: Prohibit use of `++` & `--` 26 | "quotmark" : false, // Quotation mark consistency: 27 | // false : do nothing (default) 28 | // true : ensure whatever is used is consistent 29 | // "single" : require single quotes 30 | // "double" : require double quotes 31 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) 32 | "unused" : true, // true: Require all defined variables be used 33 | "strict" : false, // true: Requires all functions run in ES5 Strict Mode 34 | "trailing" : true, // true: Prohibit trailing whitespaces 35 | "maxparams" : false, // {int} Max number of formal params allowed per function 36 | "maxdepth" : false, // {int} Max depth of nested blocks (within functions) 37 | "maxstatements" : false, // {int} Max number statements per function 38 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function 39 | "maxlen" : 80, // {int} Max number of characters per line 40 | 41 | // Relaxing 42 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) 43 | "boss" : false, // true: Tolerate assignments where comparisons would be expected 44 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. 45 | "eqnull" : false, // true: Tolerate use of `== null` 46 | "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) 47 | "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`) 48 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) 49 | // (ex: `for each`, multiple try/catch, function expression…) 50 | "evil" : false, // true: Tolerate use of `eval` and `new Function()` 51 | "expr" : false, // true: Tolerate `ExpressionStatement` as Programs 52 | "funcscope" : false, // true: Tolerate defining variables inside control statements" 53 | "globalstrict" : true, // true: Allow global "use strict" (also enables 'strict') 54 | "iterator" : false, // true: Tolerate using the `__iterator__` property 55 | "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block 56 | "laxbreak" : false, // true: Tolerate possibly unsafe line breakings 57 | "laxcomma" : false, // true: Tolerate comma-first style coding 58 | "loopfunc" : false, // true: Tolerate functions being defined in loops 59 | "multistr" : false, // true: Tolerate multi-line strings 60 | "proto" : false, // true: Tolerate using the `__proto__` property 61 | "scripturl" : false, // true: Tolerate script-targeted URLs 62 | "smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment 63 | "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` 64 | "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation 65 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` 66 | "validthis" : false, // true: Tolerate using this in a non-constructor function 67 | 68 | // Environments 69 | "browser" : true, // Web Browser (window, document, etc) 70 | "couch" : false, // CouchDB 71 | "devel" : true, // Development/debugging (alert, confirm, etc) 72 | "dojo" : false, // Dojo Toolkit 73 | "jquery" : false, // jQuery 74 | "mootools" : false, // MooTools 75 | "node" : false, // Node.js 76 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) 77 | "prototypejs" : false, // Prototype and Scriptaculous 78 | "rhino" : false, // Rhino 79 | "worker" : false, // Web Workers 80 | "wsh" : false, // Windows Scripting Host 81 | "yui" : false, // Yahoo User Interface 82 | //"meteor" : false, // Meteor.js 83 | 84 | // Legacy 85 | "nomen" : false, // true: Prohibit dangling `_` in variables 86 | "onevar" : false, // true: Allow only one `var` statement per function 87 | "passfail" : false, // true: Stop on first error 88 | "white" : false, // true: Check against strict whitespace and indentation rules 89 | 90 | // Custom globals, from http://docs.meteor.com, in the order they appear there 91 | "globals" : { 92 | "Meteor": false, 93 | "DDP": false, 94 | "Session": false, 95 | "Accounts": false, 96 | "Template": false, 97 | "Match": false, 98 | "check": false, 99 | "Deps": false, 100 | "EJSON": false, 101 | "HTTP": false, 102 | "Email": false, 103 | "Assets": false, 104 | "Handlebars": false, // https://github.com/meteor/meteor/wiki/Handlebars 105 | 106 | // Meteor internals 107 | "DDPServer": false, 108 | "global": false, 109 | "Log": false, 110 | "MongoInternals": false, 111 | "process": false, 112 | "WebApp": false, 113 | "WebAppInternals": false, 114 | 115 | // globals useful when creating Meteor packages 116 | "Package": false, 117 | "Npm": false, 118 | "Tinytest": false, 119 | 120 | // common Meteor packages 121 | "Random": false, 122 | "_": false, // Underscore.js 123 | "$": false, // jQuery 124 | "Router": false, // iron-router 125 | "RouteController": false, // iron-router 126 | 127 | // Meteor Generate 128 | "AppController": false, 129 | "db": false 130 | } 131 | } 132 | 133 | --------------------------------------------------------------------------------