├── .gitignore ├── .travis.yml ├── bin ├── mimosa └── _mimosa ├── src ├── modules │ ├── file │ │ ├── index.coffee │ │ ├── init.coffee │ │ ├── delete.coffee │ │ ├── read.coffee │ │ ├── clean.coffee │ │ ├── write.coffee │ │ └── beforeRead.coffee │ ├── compilers │ │ ├── misc.coffee │ │ ├── javascript.coffee │ │ ├── index.coffee │ │ ├── css.coffee │ │ └── template.coffee │ └── index.coffee ├── util │ ├── cleaner.coffee │ ├── watcher.coffee │ ├── file.coffee │ ├── config-builder.coffee │ └── workflow.coffee ├── command │ ├── module │ │ ├── config.coffee │ │ ├── uninstall.coffee │ │ ├── list.coffee │ │ └── install.coffee │ ├── external.coffee │ ├── config.coffee │ ├── clean.coffee │ ├── build.coffee │ └── watch.coffee └── index.js ├── mimosa-config.coffee ├── lib ├── modules │ ├── file │ │ ├── index.js │ │ ├── init.js │ │ ├── delete.js │ │ ├── read.js │ │ ├── clean.js │ │ ├── write.js │ │ └── beforeRead.js │ ├── compilers │ │ ├── misc.js │ │ ├── javascript.js │ │ └── index.js │ └── index.js ├── command │ ├── module │ │ ├── config.js │ │ ├── uninstall.js │ │ ├── list.js │ │ └── install.js │ ├── external.js │ ├── config.js │ ├── clean.js │ ├── build.js │ └── watch.js ├── util │ ├── cleaner.js │ ├── config-builder.js │ ├── watcher.js │ └── file.js └── index.js ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | -------------------------------------------------------------------------------- /bin/mimosa: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require('path'), 4 | fs = require('fs'); 5 | 6 | var lib = path.join(path.dirname(fs.realpathSync(__filename)), '..', 'lib'); 7 | require(lib); -------------------------------------------------------------------------------- /src/modules/file/index.coffee: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | init = require './init' 4 | beforeRead = require './beforeRead' 5 | read = require './read' 6 | write = require './write' 7 | del = require './delete' 8 | clean = require './clean' 9 | 10 | modules = [init, beforeRead, read, write, del, clean] 11 | 12 | exports.registration = (config, register) -> 13 | modules.forEach (module) -> 14 | module.registration(config, register) -------------------------------------------------------------------------------- /mimosa-config.coffee: -------------------------------------------------------------------------------- 1 | exports.config = 2 | modules: ["jshint", "coffeescript@1.1.0", "copy"] 3 | watch: 4 | sourceDir: "src" 5 | compiledDir: "lib" 6 | javascriptDir: null 7 | coffeescript: 8 | options: 9 | bare: true 10 | sourceMap: false 11 | compilers: 12 | extensionOverrides: 13 | typescript: null 14 | copy: 15 | extensions: ["js", "ts", "json"] 16 | jshint: 17 | rules: 18 | node: true 19 | laxcomma: true -------------------------------------------------------------------------------- /lib/modules/file/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var beforeRead, clean, del, init, modules, read, write; 3 | 4 | init = require('./init'); 5 | 6 | beforeRead = require('./beforeRead'); 7 | 8 | read = require('./read'); 9 | 10 | write = require('./write'); 11 | 12 | del = require('./delete'); 13 | 14 | clean = require('./clean'); 15 | 16 | modules = [init, beforeRead, read, write, del, clean]; 17 | 18 | exports.registration = function(config, register) { 19 | return modules.forEach(function(module) { 20 | return module.registration(config, register); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /src/modules/file/init.coffee: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | _initSingleAsset = (config, options, next) -> 4 | fileUtils = require '../../util/file' 5 | fileUtils.setFileFlags( config, options ) 6 | 7 | options.files = [{ 8 | inputFileName:options.inputFile 9 | outputFileName:null 10 | inputFileText:null 11 | outputFileText:null 12 | }] 13 | 14 | next() 15 | 16 | _initMultiAsset = (config, options, next) -> 17 | fileUtils = require '../../util/file' 18 | fileUtils.setFileFlags( config, options ) 19 | options.files = [] 20 | next() 21 | 22 | exports.registration = (config, register) -> 23 | e = config.extensions 24 | register ['add','update','remove','cleanFile','buildExtension'], 'init', _initMultiAsset, [e.template..., e.css...] 25 | register ['add','update','remove','cleanFile','buildFile'], 'init', _initSingleAsset, [e.javascript..., e.copy..., e.misc...] -------------------------------------------------------------------------------- /src/modules/file/delete.coffee: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | _delete = (config, options, next) -> 4 | fs = require 'fs' 5 | 6 | # has no discernable output file 7 | unless options.destinationFile 8 | return next() 9 | 10 | fileName = options.destinationFile(options.inputFile) 11 | fs.exists fileName, (exists) -> 12 | unless exists 13 | if config.log.isDebug() 14 | config.log.debug "File does not exist? [[ #{fileName} ]]" 15 | return next() 16 | 17 | if config.log.isDebug() 18 | config.log.debug "Removing file [[ #{fileName} ]]" 19 | fs.unlink fileName, (err) -> 20 | if err 21 | config.log.error "Failed to delete file [[ #{fileName} ]]" 22 | else 23 | config.log.success "Deleted file [[ #{fileName} ]]", options 24 | next() 25 | 26 | exports.registration = (config, register) -> 27 | e = config.extensions 28 | register ['remove','cleanFile'], 'delete', _delete, [e.javascript..., e.css..., e.copy..., e.misc...] -------------------------------------------------------------------------------- /src/modules/file/read.coffee: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | _read = (config, options, next) -> 4 | fs = require 'fs' 5 | 6 | hasFiles = options.files?.length > 0 7 | return next() unless hasFiles 8 | 9 | i = 0 10 | done = -> 11 | next() if ++i is options.files.length 12 | 13 | options.files.forEach (file) -> 14 | return done() unless file.inputFileName? 15 | fs.readFile file.inputFileName, (err, text) -> 16 | if err? 17 | config.log.error "Failed to read file [[ #{file.inputFileName} ]], #{err}", {exitIfBuild:true} 18 | else 19 | if options.isJavascript or options.isCSS or options.isTemplate 20 | text = text.toString() 21 | file.inputFileText = text 22 | done() 23 | 24 | exports.registration = (config, register) -> 25 | e = config.extensions 26 | register ['add','update','buildFile'], 'read', _read, [e.javascript..., e.copy..., e.misc...] 27 | register ['add','update','remove','buildExtension'], 'read', _read, [e.css..., e.template...] -------------------------------------------------------------------------------- /lib/modules/file/init.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var _initMultiAsset, _initSingleAsset, 3 | __slice = [].slice; 4 | 5 | _initSingleAsset = function(config, options, next) { 6 | var fileUtils; 7 | fileUtils = require('../../util/file'); 8 | fileUtils.setFileFlags(config, options); 9 | options.files = [ 10 | { 11 | inputFileName: options.inputFile, 12 | outputFileName: null, 13 | inputFileText: null, 14 | outputFileText: null 15 | } 16 | ]; 17 | return next(); 18 | }; 19 | 20 | _initMultiAsset = function(config, options, next) { 21 | var fileUtils; 22 | fileUtils = require('../../util/file'); 23 | fileUtils.setFileFlags(config, options); 24 | options.files = []; 25 | return next(); 26 | }; 27 | 28 | exports.registration = function(config, register) { 29 | var e; 30 | e = config.extensions; 31 | register(['add', 'update', 'remove', 'cleanFile', 'buildExtension'], 'init', _initMultiAsset, __slice.call(e.template).concat(__slice.call(e.css))); 32 | return register(['add', 'update', 'remove', 'cleanFile', 'buildFile'], 'init', _initSingleAsset, __slice.call(e.javascript).concat(__slice.call(e.copy), __slice.call(e.misc))); 33 | }; 34 | -------------------------------------------------------------------------------- /lib/modules/file/delete.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var _delete, 3 | __slice = [].slice; 4 | 5 | _delete = function(config, options, next) { 6 | var fileName, fs; 7 | fs = require('fs'); 8 | if (!options.destinationFile) { 9 | return next(); 10 | } 11 | fileName = options.destinationFile(options.inputFile); 12 | return fs.exists(fileName, function(exists) { 13 | if (!exists) { 14 | if (config.log.isDebug()) { 15 | config.log.debug("File does not exist? [[ " + fileName + " ]]"); 16 | } 17 | return next(); 18 | } 19 | if (config.log.isDebug()) { 20 | config.log.debug("Removing file [[ " + fileName + " ]]"); 21 | } 22 | return fs.unlink(fileName, function(err) { 23 | if (err) { 24 | config.log.error("Failed to delete file [[ " + fileName + " ]]"); 25 | } else { 26 | config.log.success("Deleted file [[ " + fileName + " ]]", options); 27 | } 28 | return next(); 29 | }); 30 | }); 31 | }; 32 | 33 | exports.registration = function(config, register) { 34 | var e; 35 | e = config.extensions; 36 | return register(['remove', 'cleanFile'], 'delete', _delete, __slice.call(e.javascript).concat(__slice.call(e.css), __slice.call(e.copy), __slice.call(e.misc))); 37 | }; 38 | -------------------------------------------------------------------------------- /src/modules/file/clean.coffee: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | _clean = (config, options, next) -> 4 | fs = require 'fs' 5 | path = require 'path' 6 | _ = require 'lodash' 7 | wrench = require 'wrench' 8 | 9 | dir = config.watch.compiledDir 10 | directories = wrench.readdirSyncRecursive(dir).filter (f) -> fs.statSync(path.join(dir, f)).isDirectory() 11 | 12 | return next() if directories.length is 0 13 | 14 | i = 0 15 | done = -> 16 | next() if ++i is directories.length 17 | 18 | _.sortBy(directories, 'length').reverse().forEach (dir) -> 19 | dirPath = path.join(config.watch.compiledDir, dir) 20 | if fs.existsSync dirPath 21 | if config.log.isDebug() 22 | config.log.debug "Deleting directory [[ #{dirPath} ]]" 23 | try 24 | fs.rmdirSync dirPath 25 | config.log.success "Deleted empty directory [[ #{dirPath} ]]" 26 | catch err 27 | if err.code is 'ENOTEMPTY' 28 | config.log.info "Unable to delete directory [[ #{dirPath} ]] because directory not empty" 29 | else 30 | config.log.error "Unable to delete directory, [[ #{dirPath} ]]" 31 | config.log.error err 32 | done() 33 | 34 | exports.registration = (config, register) -> 35 | register ['postClean'], 'complete', _clean -------------------------------------------------------------------------------- /src/modules/file/write.coffee: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | 4 | _write = (config, options, next) -> 5 | fileUtils = require '../../util/file' 6 | 7 | hasFiles = options.files?.length > 0 8 | return next() unless hasFiles 9 | 10 | i = 0 11 | done = -> 12 | next() if ++i is options.files.length 13 | 14 | options.files.forEach (file) -> 15 | return done() if (file.outputFileText isnt "" and not file.outputFileText) or not file.outputFileName 16 | 17 | if file.outputFileText is "" 18 | config.log.warn "Compile of file [[ #{file.inputFileName} ]] resulted in empty output." 19 | 20 | if config.log.isDebug() 21 | config.log.debug "Writing file [[ #{file.outputFileName} ]]" 22 | 23 | fileUtils.writeFile file.outputFileName, file.outputFileText, (err) -> 24 | if err? 25 | config.log.error "Failed to write new file [[ #{file.outputFileName} ]], Error: #{err}", {exitIfBuild:true} 26 | else 27 | config.log.success "Wrote file [[ #{file.outputFileName} ]]", options 28 | done() 29 | 30 | exports.registration = (config, register) -> 31 | e = config.extensions 32 | register ['add','update','remove','buildExtension'], 'write', _write, [e.template..., e.css...] 33 | register ['add','update','buildFile'], 'write', _write, [e.javascript..., e.copy..., e.misc...] -------------------------------------------------------------------------------- /src/util/cleaner.coffee: -------------------------------------------------------------------------------- 1 | watch = require 'chokidar' 2 | logger = require 'logmimosa' 3 | _ = require 'lodash' 4 | 5 | Workflow = require './workflow' 6 | 7 | class Cleaner 8 | 9 | constructor: (@config, modules, @initCallback) -> 10 | @workflow = new Workflow _.clone(@config, true), modules, @_cleanDone 11 | @workflow.initClean @_startWatcher 12 | 13 | _startWatcher: => 14 | watchConfig = 15 | ignored:@_ignoreFunct 16 | persistent:false 17 | interval: @config.watch.interval 18 | binaryInterval: @config.watch.binaryInterval 19 | usePolling: @config.watch.usePolling 20 | 21 | @watcher = watch.watch @config.watch.sourceDir, watchConfig 22 | @watcher.on "add", @workflow.clean 23 | @watcher.on "ready", @workflow.ready 24 | 25 | 26 | _cleanDone: => 27 | @workflow.postClean => 28 | @watcher.close() 29 | @initCallback() 30 | 31 | _ignoreFunct: (name) => 32 | if @config.watch.excludeRegex? 33 | if name.match(@config.watch.excludeRegex) 34 | logger.debug "Ignoring file [[ #{name} ]], matches exclude regex" 35 | return true 36 | if @config.watch.exclude? 37 | if @config.watch.exclude.indexOf(name) > -1 38 | logger.debug "Ignoring file [[ #{name} ]], matches exclude string path" 39 | return true 40 | false 41 | 42 | module.exports = Cleaner 43 | -------------------------------------------------------------------------------- /bin/_mimosa: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * 5 | * Taken from Mocha: https://github.com/visionmedia/mocha/blob/master/bin/mocha 6 | * 7 | * This tiny wrapper file checks for known node flags and appends them 8 | * when found, before invoking the "real" mimosa executable. 9 | */ 10 | 11 | var spawn = require('child_process').spawn 12 | , args = [ __dirname + '/mimosa' ]; 13 | 14 | process.argv.slice(2).forEach(function(arg){ 15 | var flag = arg.split('=')[0]; 16 | 17 | switch (flag) { 18 | case '--debug': 19 | case '--debug-brk': 20 | args.unshift(arg); 21 | break; 22 | case '-gc': 23 | case '--expose-gc': 24 | args.unshift('--expose-gc'); 25 | break; 26 | case '--nolazy': 27 | case '--gc-global': 28 | case '--harmony': 29 | case '--harmony-proxies': 30 | case '--harmony-collections': 31 | case '--harmony-generators': 32 | case '--prof': 33 | args.unshift(arg); 34 | break; 35 | default: 36 | if (0 == arg.indexOf('--trace')) args.unshift(arg); 37 | else args.push(arg); 38 | break; 39 | } 40 | }); 41 | 42 | var proc = spawn(process.argv[0], args, { stdio: 'inherit' }); 43 | proc.on('exit', function (code, signal) { 44 | process.on('exit', function(){ 45 | if (signal) { 46 | process.kill(process.pid, signal); 47 | } else { 48 | process.exit(code); 49 | } 50 | }); 51 | }); -------------------------------------------------------------------------------- /lib/modules/file/read.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var _read, 3 | __slice = [].slice; 4 | 5 | _read = function(config, options, next) { 6 | var done, fs, hasFiles, i, _ref; 7 | fs = require('fs'); 8 | hasFiles = ((_ref = options.files) != null ? _ref.length : void 0) > 0; 9 | if (!hasFiles) { 10 | return next(); 11 | } 12 | i = 0; 13 | done = function() { 14 | if (++i === options.files.length) { 15 | return next(); 16 | } 17 | }; 18 | return options.files.forEach(function(file) { 19 | if (file.inputFileName == null) { 20 | return done(); 21 | } 22 | return fs.readFile(file.inputFileName, function(err, text) { 23 | if (err != null) { 24 | config.log.error("Failed to read file [[ " + file.inputFileName + " ]], " + err, { 25 | exitIfBuild: true 26 | }); 27 | } else { 28 | if (options.isJavascript || options.isCSS || options.isTemplate) { 29 | text = text.toString(); 30 | } 31 | file.inputFileText = text; 32 | } 33 | return done(); 34 | }); 35 | }); 36 | }; 37 | 38 | exports.registration = function(config, register) { 39 | var e; 40 | e = config.extensions; 41 | register(['add', 'update', 'buildFile'], 'read', _read, __slice.call(e.javascript).concat(__slice.call(e.copy), __slice.call(e.misc))); 42 | return register(['add', 'update', 'remove', 'buildExtension'], 'read', _read, __slice.call(e.css).concat(__slice.call(e.template))); 43 | }; 44 | -------------------------------------------------------------------------------- /src/modules/compilers/misc.coffee: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | logger = require 'logmimosa' 4 | 5 | module.exports = class MiscCompiler 6 | 7 | constructor: (config, @compiler) -> 8 | @extensions = @compiler.extensions(config) 9 | 10 | registration: (config, register) -> 11 | 12 | register( 13 | ['add','update','remove','cleanFile','buildFile'], 14 | 'init', 15 | @_determineOutputFile, 16 | @extensions) 17 | 18 | register( 19 | ['add','update','buildFile'], 20 | 'compile', 21 | @compiler.compile, 22 | @extensions) 23 | 24 | _determineOutputFile: (config, options, next) => 25 | # if destinationFile is already there, 26 | # ignore all this, don't want these compilers 27 | # overwriting compiler with same extension 28 | if options.files and options.files.length and !options.destinationFile 29 | 30 | if @compiler.compilerType is "copy" 31 | options.destinationFile = (fileName) -> 32 | fileName.replace(config.watch.sourceDir, config.watch.compiledDir) 33 | 34 | options.files.forEach (file) -> 35 | file.outputFileName = options.destinationFile( file.inputFileName ) 36 | else 37 | if @compiler.determineOutputFile 38 | @compiler.determineOutputFile( config, options ) 39 | else 40 | if logger.isDebug() 41 | logger.debug "compiler [[ " + @compiler.name + " ]] does not have determineOutputFile function." 42 | 43 | next() -------------------------------------------------------------------------------- /src/command/module/config.coffee: -------------------------------------------------------------------------------- 1 | logger = require 'logmimosa' 2 | _ = require 'lodash' 3 | 4 | modules = require('../../modules/').installedMetadata 5 | 6 | config = (name, opts) -> 7 | if opts.mdebug 8 | opts.debug = true 9 | logger.setDebug() 10 | process.env.DEBUG = true 11 | 12 | unless name? 13 | return logger.error "Must provide a module name, ex: mimosa mod:config mimosa-moduleX" 14 | 15 | modMeta = _.findWhere(modules, {name:name}) 16 | if modMeta? 17 | mod = modMeta.mod 18 | if mod.placeholder 19 | text = mod.placeholder() 20 | logger.green "#{text}\n\n" 21 | else 22 | logger.info "Module [[ #{name} ]] has no configuration" 23 | else 24 | return logger.error "Could not find module named [[ #{name} ]]" 25 | 26 | process.exit 0 27 | 28 | register = (program, callback) -> 29 | program 30 | .command('mod:config [name]') 31 | .option("-D, --mdebug", "run in debug mode") 32 | .description("Print out the configuration snippet for a module to the console") 33 | .action(callback) 34 | .on '--help', => 35 | logger.green(' The mod:config command will print out the default CoffeeScript snippet for the') 36 | logger.green(' given named Mimosa module. If there is already a mimosa-config.coffee in the') 37 | logger.green(' current directory, Mimosa will not copy the file in.') 38 | logger.blue( '\n $ mimosa mod:config [nameOfModule]\n') 39 | 40 | module.exports = (program) -> 41 | register program, config 42 | -------------------------------------------------------------------------------- /lib/modules/file/clean.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var _clean; 3 | 4 | _clean = function(config, options, next) { 5 | var dir, directories, done, fs, i, path, wrench, _; 6 | fs = require('fs'); 7 | path = require('path'); 8 | _ = require('lodash'); 9 | wrench = require('wrench'); 10 | dir = config.watch.compiledDir; 11 | directories = wrench.readdirSyncRecursive(dir).filter(function(f) { 12 | return fs.statSync(path.join(dir, f)).isDirectory(); 13 | }); 14 | if (directories.length === 0) { 15 | return next(); 16 | } 17 | i = 0; 18 | done = function() { 19 | if (++i === directories.length) { 20 | return next(); 21 | } 22 | }; 23 | return _.sortBy(directories, 'length').reverse().forEach(function(dir) { 24 | var dirPath, err; 25 | dirPath = path.join(config.watch.compiledDir, dir); 26 | if (fs.existsSync(dirPath)) { 27 | if (config.log.isDebug()) { 28 | config.log.debug("Deleting directory [[ " + dirPath + " ]]"); 29 | } 30 | try { 31 | fs.rmdirSync(dirPath); 32 | config.log.success("Deleted empty directory [[ " + dirPath + " ]]"); 33 | } catch (_error) { 34 | err = _error; 35 | if (err.code === 'ENOTEMPTY') { 36 | config.log.info("Unable to delete directory [[ " + dirPath + " ]] because directory not empty"); 37 | } else { 38 | config.log.error("Unable to delete directory, [[ " + dirPath + " ]]"); 39 | config.log.error(err); 40 | } 41 | } 42 | } 43 | return done(); 44 | }); 45 | }; 46 | 47 | exports.registration = function(config, register) { 48 | return register(['postClean'], 'complete', _clean); 49 | }; 50 | -------------------------------------------------------------------------------- /src/command/external.coffee: -------------------------------------------------------------------------------- 1 | 2 | registerCommand = (buildFirst, isDebug, callback) -> 3 | logger = require 'logmimosa' 4 | 5 | # manage multiple formats 6 | opts = {} 7 | 8 | if callback 9 | # full (buildFirst, isDebug, callback) 10 | opts.mdebug = isDebug 11 | opts.buildFirst = buildFirst 12 | else 13 | # is 2 parameters, can be (opts, callback) or 14 | # older (buildFirst, callback) 15 | 16 | callback = isDebug 17 | if typeof buildFirst is "boolean" 18 | # is (buildFirst, callback) 19 | opts.mdebug = false 20 | opts.buildFirst = buildFirst 21 | else 22 | # is (opts, callback) 23 | opts = buildFirst 24 | 25 | if opts.mdebug 26 | logger.setDebug() 27 | process.env.DEBUG = true 28 | 29 | configurer = require '../util/configurer' 30 | configurer opts, (config, mods) -> 31 | if opts.buildFirst 32 | Cleaner = require '../util/cleaner' 33 | Watcher = require '../util/watcher' 34 | 35 | config.isClean = true 36 | new Cleaner config, mods, -> 37 | config.isClean = false 38 | 39 | new Watcher config, mods, false, -> 40 | logger.success "Finished build" 41 | callback config 42 | else 43 | callback config 44 | 45 | module.exports = (program) -> 46 | modules = require '../modules' 47 | for mod in modules.modulesWithCommands() 48 | # older API just took two commands, newer API, 49 | # as of 2.1.10 allows passing of logger 50 | if mod.registerCommand.length is 2 51 | mod.registerCommand program, registerCommand 52 | else 53 | logger = require 'logmimosa' 54 | mod.registerCommand program, logger, registerCommand 55 | -------------------------------------------------------------------------------- /lib/command/module/config.js: -------------------------------------------------------------------------------- 1 | var config, logger, modules, register, _; 2 | 3 | logger = require('logmimosa'); 4 | 5 | _ = require('lodash'); 6 | 7 | modules = require('../../modules/').installedMetadata; 8 | 9 | config = function(name, opts) { 10 | var mod, modMeta, text; 11 | if (opts.mdebug) { 12 | opts.debug = true; 13 | logger.setDebug(); 14 | process.env.DEBUG = true; 15 | } 16 | if (name == null) { 17 | return logger.error("Must provide a module name, ex: mimosa mod:config mimosa-moduleX"); 18 | } 19 | modMeta = _.findWhere(modules, { 20 | name: name 21 | }); 22 | if (modMeta != null) { 23 | mod = modMeta.mod; 24 | if (mod.placeholder) { 25 | text = mod.placeholder(); 26 | logger.green("" + text + "\n\n"); 27 | } else { 28 | logger.info("Module [[ " + name + " ]] has no configuration"); 29 | } 30 | } else { 31 | return logger.error("Could not find module named [[ " + name + " ]]"); 32 | } 33 | return process.exit(0); 34 | }; 35 | 36 | register = function(program, callback) { 37 | var _this = this; 38 | return program.command('mod:config [name]').option("-D, --mdebug", "run in debug mode").description("Print out the configuration snippet for a module to the console").action(callback).on('--help', function() { 39 | logger.green(' The mod:config command will print out the default CoffeeScript snippet for the'); 40 | logger.green(' given named Mimosa module. If there is already a mimosa-config.coffee in the'); 41 | logger.green(' current directory, Mimosa will not copy the file in.'); 42 | return logger.blue('\n $ mimosa mod:config [nameOfModule]\n'); 43 | }); 44 | }; 45 | 46 | module.exports = function(program) { 47 | return register(program, config); 48 | }; 49 | -------------------------------------------------------------------------------- /lib/modules/file/write.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var _write, 3 | __slice = [].slice; 4 | 5 | _write = function(config, options, next) { 6 | var done, fileUtils, hasFiles, i, _ref; 7 | fileUtils = require('../../util/file'); 8 | hasFiles = ((_ref = options.files) != null ? _ref.length : void 0) > 0; 9 | if (!hasFiles) { 10 | return next(); 11 | } 12 | i = 0; 13 | done = function() { 14 | if (++i === options.files.length) { 15 | return next(); 16 | } 17 | }; 18 | return options.files.forEach(function(file) { 19 | if ((file.outputFileText !== "" && !file.outputFileText) || !file.outputFileName) { 20 | return done(); 21 | } 22 | if (file.outputFileText === "") { 23 | config.log.warn("Compile of file [[ " + file.inputFileName + " ]] resulted in empty output."); 24 | } 25 | if (config.log.isDebug()) { 26 | config.log.debug("Writing file [[ " + file.outputFileName + " ]]"); 27 | } 28 | return fileUtils.writeFile(file.outputFileName, file.outputFileText, function(err) { 29 | if (err != null) { 30 | config.log.error("Failed to write new file [[ " + file.outputFileName + " ]], Error: " + err, { 31 | exitIfBuild: true 32 | }); 33 | } else { 34 | config.log.success("Wrote file [[ " + file.outputFileName + " ]]", options); 35 | } 36 | return done(); 37 | }); 38 | }); 39 | }; 40 | 41 | exports.registration = function(config, register) { 42 | var e; 43 | e = config.extensions; 44 | register(['add', 'update', 'remove', 'buildExtension'], 'write', _write, __slice.call(e.template).concat(__slice.call(e.css))); 45 | return register(['add', 'update', 'buildFile'], 'write', _write, __slice.call(e.javascript).concat(__slice.call(e.copy), __slice.call(e.misc))); 46 | }; 47 | -------------------------------------------------------------------------------- /lib/modules/compilers/misc.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var MiscCompiler, logger, 3 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; 4 | 5 | logger = require('logmimosa'); 6 | 7 | module.exports = MiscCompiler = (function() { 8 | function MiscCompiler(config, compiler) { 9 | this.compiler = compiler; 10 | this._determineOutputFile = __bind(this._determineOutputFile, this); 11 | this.extensions = this.compiler.extensions(config); 12 | } 13 | 14 | MiscCompiler.prototype.registration = function(config, register) { 15 | register(['add', 'update', 'remove', 'cleanFile', 'buildFile'], 'init', this._determineOutputFile, this.extensions); 16 | return register(['add', 'update', 'buildFile'], 'compile', this.compiler.compile, this.extensions); 17 | }; 18 | 19 | MiscCompiler.prototype._determineOutputFile = function(config, options, next) { 20 | if (options.files && options.files.length && !options.destinationFile) { 21 | if (this.compiler.compilerType === "copy") { 22 | options.destinationFile = function(fileName) { 23 | return fileName.replace(config.watch.sourceDir, config.watch.compiledDir); 24 | }; 25 | options.files.forEach(function(file) { 26 | return file.outputFileName = options.destinationFile(file.inputFileName); 27 | }); 28 | } else { 29 | if (this.compiler.determineOutputFile) { 30 | this.compiler.determineOutputFile(config, options); 31 | } else { 32 | if (logger.isDebug()) { 33 | logger.debug("compiler [[ " + this.compiler.name + " ]] does not have determineOutputFile function."); 34 | } 35 | } 36 | } 37 | } 38 | return next(); 39 | }; 40 | 41 | return MiscCompiler; 42 | 43 | })(); 44 | -------------------------------------------------------------------------------- /lib/command/external.js: -------------------------------------------------------------------------------- 1 | var registerCommand; 2 | 3 | registerCommand = function(buildFirst, isDebug, callback) { 4 | var configurer, logger, opts; 5 | logger = require('logmimosa'); 6 | opts = {}; 7 | if (callback) { 8 | opts.mdebug = isDebug; 9 | opts.buildFirst = buildFirst; 10 | } else { 11 | callback = isDebug; 12 | if (typeof buildFirst === "boolean") { 13 | opts.mdebug = false; 14 | opts.buildFirst = buildFirst; 15 | } else { 16 | opts = buildFirst; 17 | } 18 | } 19 | if (opts.mdebug) { 20 | logger.setDebug(); 21 | process.env.DEBUG = true; 22 | } 23 | configurer = require('../util/configurer'); 24 | return configurer(opts, function(config, mods) { 25 | var Cleaner, Watcher; 26 | if (opts.buildFirst) { 27 | Cleaner = require('../util/cleaner'); 28 | Watcher = require('../util/watcher'); 29 | config.isClean = true; 30 | return new Cleaner(config, mods, function() { 31 | config.isClean = false; 32 | return new Watcher(config, mods, false, function() { 33 | logger.success("Finished build"); 34 | return callback(config); 35 | }); 36 | }); 37 | } else { 38 | return callback(config); 39 | } 40 | }); 41 | }; 42 | 43 | module.exports = function(program) { 44 | var logger, mod, modules, _i, _len, _ref, _results; 45 | modules = require('../modules'); 46 | _ref = modules.modulesWithCommands(); 47 | _results = []; 48 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 49 | mod = _ref[_i]; 50 | if (mod.registerCommand.length === 2) { 51 | _results.push(mod.registerCommand(program, registerCommand)); 52 | } else { 53 | logger = require('logmimosa'); 54 | _results.push(mod.registerCommand(program, logger, registerCommand)); 55 | } 56 | } 57 | return _results; 58 | }; 59 | -------------------------------------------------------------------------------- /src/modules/file/beforeRead.coffee: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | path = require 'path' 4 | 5 | allExtensions = null 6 | 7 | _notValidExtension = (file) -> 8 | ext = path.extname(file.inputFileName).replace(/\./,'') 9 | allExtensions.indexOf(ext) is -1 10 | 11 | _fileNeedsCompiling = (config, options, next) -> 12 | hasFiles = options.files?.length > 0 13 | return next() unless hasFiles 14 | 15 | i = 0 16 | newFiles = [] 17 | done = -> 18 | if ++i is options.files.length 19 | options.files = newFiles 20 | next() 21 | 22 | options.files.forEach (file) -> 23 | # if using require verification, forcing compile to assemble require information 24 | # or if extension is for file that was placed here, not that originated the workflow 25 | # like with .css files and CSS proprocessors 26 | if (options.isJavascript and config.__forceJavaScriptRecompile) or _notValidExtension(file) 27 | newFiles.push file 28 | done() 29 | else 30 | fileUtils = require '../../util/file' 31 | fileUtils.isFirstFileNewer file.inputFileName, file.outputFileName, (isNewer) -> 32 | if isNewer 33 | newFiles.push file 34 | else 35 | if config.log.isDebug() 36 | config.log.debug "Not processing [[ #{file.inputFileName} ]] as it is not newer than destination file." 37 | done() 38 | 39 | _fileNeedsCompilingStartup = (config, options, next) -> 40 | # force compiling on startup to build require dependency tree 41 | # but not for vendor javascript 42 | if config.__forceJavaScriptRecompile and options.isJSNotVendor 43 | if config.log.isDebug() 44 | config.log.debug "File [[ #{options.inputFile} ]] NEEDS compiling/copying" 45 | next() 46 | else 47 | _fileNeedsCompiling(config, options, next) 48 | 49 | exports.registration = (config, register) -> 50 | allExtensions = [config.extensions.javascript..., config.extensions.copy...] 51 | register ['buildFile'], 'beforeRead', _fileNeedsCompilingStartup, allExtensions 52 | register ['add','update'], 'beforeRead', _fileNeedsCompiling, allExtensions -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mimosa", 3 | "preferGlobal": "true", 4 | "version": "2.3.32", 5 | "homepage": "http://www.mimosa.io", 6 | "author": "David Bashford", 7 | "description": "A lightning-fast, modular, next generation browser development tool.", 8 | "contributors": [ 9 | { 10 | "name": "David Bashford", 11 | "email": "dbashford@hotmail.com" 12 | } 13 | ], 14 | "bin": { 15 | "mimosa": "./bin/_mimosa" 16 | }, 17 | "main": "./lib/index", 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/dbashford/mimosa" 21 | }, 22 | "keywords": [ 23 | "framework", 24 | "boilerplate", 25 | "toolkit", 26 | "compiler", 27 | "coffeescript", 28 | "livescript", 29 | "typescript", 30 | "sass", 31 | "less", 32 | "stylus", 33 | "dust", 34 | "handlebars", 35 | "ember", 36 | "emblem", 37 | "jade", 38 | "underscore", 39 | "lodash", 40 | "hogan", 41 | "ractive", 42 | "nunjucks", 43 | "amd", 44 | "require", 45 | "requirejs", 46 | "mimosa", 47 | "mmodule", 48 | "lint", 49 | "hint", 50 | "brunch", 51 | "grunt", 52 | "yeoman", 53 | "server", 54 | "express", 55 | "source", 56 | "maps", 57 | "node-sass", 58 | "bower" 59 | ], 60 | "dependencies": { 61 | "lodash": "2.4.1", 62 | "coffee-script": "1.7.1", 63 | "ansi-color": "0.2.1", 64 | "wrench": "1.5.8", 65 | "commander": "1.3.2", 66 | "chokidar": "0.12.6", 67 | "request": "2.33.0", 68 | "logmimosa": "1.0.4", 69 | "skelmimosa": "1.3.0", 70 | "newmimosa": "2.0.0", 71 | "validatemimosa": "1.3.5", 72 | "mimosa-minify-js": "1.1.5", 73 | "mimosa-minify-css": "1.3.2", 74 | "mimosa-jshint": "1.1.6", 75 | "mimosa-csslint": "1.2.0", 76 | "mimosa-bower": "1.6.2", 77 | "mimosa-copy": "1.3.3", 78 | "mimosa-server": "1.6.1", 79 | "mimosa-live-reload": "1.4.0", 80 | "mimosa-require": "2.3.2" 81 | }, 82 | "license": "MIT", 83 | "engines": { 84 | "node": ">=0.10" 85 | }, 86 | "scripts": { 87 | "prepublish": "node build.js" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/util/cleaner.js: -------------------------------------------------------------------------------- 1 | var Cleaner, Workflow, logger, watch, _, 2 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; 3 | 4 | watch = require('chokidar'); 5 | 6 | logger = require('logmimosa'); 7 | 8 | _ = require('lodash'); 9 | 10 | Workflow = require('./workflow'); 11 | 12 | Cleaner = (function() { 13 | function Cleaner(config, modules, initCallback) { 14 | this.config = config; 15 | this.initCallback = initCallback; 16 | this._ignoreFunct = __bind(this._ignoreFunct, this); 17 | this._cleanDone = __bind(this._cleanDone, this); 18 | this._startWatcher = __bind(this._startWatcher, this); 19 | this.workflow = new Workflow(_.clone(this.config, true), modules, this._cleanDone); 20 | this.workflow.initClean(this._startWatcher); 21 | } 22 | 23 | Cleaner.prototype._startWatcher = function() { 24 | var watchConfig; 25 | watchConfig = { 26 | ignored: this._ignoreFunct, 27 | persistent: false, 28 | interval: this.config.watch.interval, 29 | binaryInterval: this.config.watch.binaryInterval, 30 | usePolling: this.config.watch.usePolling 31 | }; 32 | this.watcher = watch.watch(this.config.watch.sourceDir, watchConfig); 33 | this.watcher.on("add", this.workflow.clean); 34 | return this.watcher.on("ready", this.workflow.ready); 35 | }; 36 | 37 | Cleaner.prototype._cleanDone = function() { 38 | var _this = this; 39 | return this.workflow.postClean(function() { 40 | _this.watcher.close(); 41 | return _this.initCallback(); 42 | }); 43 | }; 44 | 45 | Cleaner.prototype._ignoreFunct = function(name) { 46 | if (this.config.watch.excludeRegex != null) { 47 | if (name.match(this.config.watch.excludeRegex)) { 48 | logger.debug("Ignoring file [[ " + name + " ]], matches exclude regex"); 49 | return true; 50 | } 51 | } 52 | if (this.config.watch.exclude != null) { 53 | if (this.config.watch.exclude.indexOf(name) > -1) { 54 | logger.debug("Ignoring file [[ " + name + " ]], matches exclude string path"); 55 | return true; 56 | } 57 | } 58 | return false; 59 | }; 60 | 61 | return Cleaner; 62 | 63 | })(); 64 | 65 | module.exports = Cleaner; 66 | -------------------------------------------------------------------------------- /lib/modules/file/beforeRead.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var allExtensions, path, _fileNeedsCompiling, _fileNeedsCompilingStartup, _notValidExtension, 3 | __slice = [].slice; 4 | 5 | path = require('path'); 6 | 7 | allExtensions = null; 8 | 9 | _notValidExtension = function(file) { 10 | var ext; 11 | ext = path.extname(file.inputFileName).replace(/\./, ''); 12 | return allExtensions.indexOf(ext) === -1; 13 | }; 14 | 15 | _fileNeedsCompiling = function(config, options, next) { 16 | var done, hasFiles, i, newFiles, _ref; 17 | hasFiles = ((_ref = options.files) != null ? _ref.length : void 0) > 0; 18 | if (!hasFiles) { 19 | return next(); 20 | } 21 | i = 0; 22 | newFiles = []; 23 | done = function() { 24 | if (++i === options.files.length) { 25 | options.files = newFiles; 26 | return next(); 27 | } 28 | }; 29 | return options.files.forEach(function(file) { 30 | var fileUtils; 31 | if ((options.isJavascript && config.__forceJavaScriptRecompile) || _notValidExtension(file)) { 32 | newFiles.push(file); 33 | return done(); 34 | } else { 35 | fileUtils = require('../../util/file'); 36 | return fileUtils.isFirstFileNewer(file.inputFileName, file.outputFileName, function(isNewer) { 37 | if (isNewer) { 38 | newFiles.push(file); 39 | } else { 40 | if (config.log.isDebug()) { 41 | config.log.debug("Not processing [[ " + file.inputFileName + " ]] as it is not newer than destination file."); 42 | } 43 | } 44 | return done(); 45 | }); 46 | } 47 | }); 48 | }; 49 | 50 | _fileNeedsCompilingStartup = function(config, options, next) { 51 | if (config.__forceJavaScriptRecompile && options.isJSNotVendor) { 52 | if (config.log.isDebug()) { 53 | config.log.debug("File [[ " + options.inputFile + " ]] NEEDS compiling/copying"); 54 | } 55 | return next(); 56 | } else { 57 | return _fileNeedsCompiling(config, options, next); 58 | } 59 | }; 60 | 61 | exports.registration = function(config, register) { 62 | allExtensions = __slice.call(config.extensions.javascript).concat(__slice.call(config.extensions.copy)); 63 | register(['buildFile'], 'beforeRead', _fileNeedsCompilingStartup, allExtensions); 64 | return register(['add', 'update'], 'beforeRead', _fileNeedsCompiling, allExtensions); 65 | }; 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/dbashford/mimosa.png?branch=master)](https://travis-ci.org/dbashford/mimosa) 2 | 3 | [Visit the Mimosa Website](http://www.mimosa.io) for all sorts of Mimosa documentation goodness. 4 | 5 | ### Questions? 6 | 7 | * [![Gitter chat](https://badges.gitter.im/dbashford/mimosa.png)](https://gitter.im/dbashford/mimosa) 8 | * [Google Group](https://groups.google.com/forum/#!forum/mimosajs) 9 | * [Twitter @mimosajs](https://twitter.com/mimosajs/) 10 | 11 | ## Quick Start 12 | 13 | There are a ton of docs on [the site](http://www.mimosa.io) to get you started, but here's a quick start. 14 | 15 | * Install [node.js](http://nodejs.org/). A `.10` version. 16 | * `npm install -g mimosa` 17 | * `mimosa new testproject` 18 | * Follow the prompts to choose some assets 19 | * `cd testproject` 20 | * `mimosa watch -s` 21 | * http://localhost:3000 22 | 23 | Now you are ready to rock! 24 | 25 | Maybe you want to start with something other than an empty app? Maybe you want to start with a preconfigured Ember app for instance? 26 | 27 | `mimosa skel:list` 28 | 29 | That will get you a list of the skeletons currently available for Mimosa that might help jumpstart your app. 30 | 31 | ## License 32 | 33 | (The MIT License) 34 | 35 | Copyright (c) 2014 David Bashford 36 | 37 | Permission is hereby granted, free of charge, to any person obtaining 38 | a copy of this software and associated documentation files (the 39 | 'Software'), to deal in the Software without restriction, including 40 | without limitation the rights to use, copy, modify, merge, publish, 41 | distribute, sublicense, and/or sell copies of the Software, and to 42 | permit persons to whom the Software is furnished to do so, subject to 43 | the following conditions: 44 | 45 | The above copyright notice and this permission notice shall be 46 | included in all copies or substantial portions of the Software. 47 | 48 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 49 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 50 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 51 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 52 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 53 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 54 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 55 | -------------------------------------------------------------------------------- /src/command/module/uninstall.coffee: -------------------------------------------------------------------------------- 1 | path = require 'path' 2 | fs = require 'fs' 3 | {exec} = require 'child_process' 4 | 5 | logger = require 'logmimosa' 6 | moduleMetadata = require('../../modules').installedMetadata 7 | 8 | deleteMod = (name, opts) -> 9 | if opts.mdebug 10 | opts.debug = true 11 | logger.setDebug() 12 | process.env.DEBUG = true 13 | 14 | unless name? 15 | try 16 | pack = require path.join process.cwd(), 'package.json' 17 | catch err 18 | return logger.error "Unable to find package.json, or badly formatted: #{err}" 19 | 20 | unless pack.name? 21 | return logger.error "package.json missing either name or version" 22 | 23 | name = pack.name 24 | 25 | unless name.indexOf('mimosa-') is 0 26 | return logger.error "Can only delete 'mimosa-' prefixed modules with mod:delete (ex: mimosa-server)." 27 | 28 | found = false 29 | for mod in moduleMetadata 30 | if mod.name is name 31 | found = true 32 | break 33 | 34 | unless found 35 | return logger.error "Module named [[ #{name} ]] is not currently installed so it cannot be uninstalled." 36 | 37 | currentDir = process.cwd() 38 | mimosaPath = path.join __dirname, '..', '..' 39 | process.chdir mimosaPath 40 | 41 | uninstallString = "npm uninstall #{name} --save" 42 | exec uninstallString, (err, sout, serr) => 43 | if err 44 | logger.error err 45 | else 46 | if serr 47 | logger.error serr 48 | logger.success "Uninstall of [[ #{name} ]] successful" 49 | 50 | logger.debug "NPM UNINSTALL standard out\n#{sout}" 51 | logger.debug "NPM UNINSTALL standard err\n#{serr}" 52 | process.chdir currentDir 53 | process.exit 0 54 | 55 | register = (program, callback) -> 56 | program 57 | .command('mod:uninstall [name]') 58 | .option("-D, --mdebug", "run in debug mode") 59 | .description("uninstall a Mimosa module from your installed Mimosa") 60 | .action(callback) 61 | .on '--help', => 62 | logger.green(' The \'mod:uninstall\' command will delete a Mimosa module from your Mimosa install. This does') 63 | logger.green(' not delete anything from any of your projects, but it removes the ability for all projects') 64 | logger.green(' using Mimosa to utilize the removed module. You can retrieve the list of installed modules ') 65 | logger.green(' using \'mod:list\'.') 66 | logger.blue( '\n $ mimosa mod:uninstall mimosa-server\n') 67 | 68 | module.exports = (program) -> 69 | register program, deleteMod -------------------------------------------------------------------------------- /src/util/watcher.coffee: -------------------------------------------------------------------------------- 1 | watch = require 'chokidar' 2 | logger = require 'logmimosa' 3 | 4 | Workflow = require './workflow' 5 | 6 | class Watcher 7 | 8 | adds:[] 9 | 10 | constructor: (@config, modules, @persist, @initCallback) -> 11 | @throttle = @config.watch.throttle 12 | @workflow = new Workflow @config, modules, @_buildDoneCallback 13 | @workflow.initBuild @_startWatcher 14 | 15 | _startWatcher: => 16 | 17 | watchConfig = 18 | ignored:@_ignoreFunct 19 | persistent:@persist 20 | interval: @config.watch.interval 21 | binaryInterval: @config.watch.binaryInterval 22 | usePolling: @config.watch.usePolling 23 | 24 | watcher = watch.watch @config.watch.sourceDir, watchConfig 25 | 26 | process.on 'STOPMIMOSA', -> 27 | watcher.close() 28 | 29 | watcher.on "error", (error) -> logger.warn "File watching error: #{error}" 30 | watcher.on "change", (f) => @_fileUpdated('update', f) 31 | watcher.on "unlink", @workflow.remove 32 | watcher.on "ready", @workflow.ready 33 | watcher.on "add", (f) => 34 | if @throttle > 0 35 | @adds.push(f) 36 | else 37 | @_fileUpdated('add', f) 38 | 39 | if @persist 40 | logger.info "Watching [[ #{@config.watch.sourceDir} ]]" 41 | 42 | if @throttle > 0 43 | logger.debug "Throttle is set, setting interval at 100 milliseconds" 44 | @intervalId = setInterval(@_pullFiles, 100) 45 | @_pullFiles() 46 | 47 | _fileUpdated: (eventType, f) => 48 | # sometimes events can be sent before 49 | # file isn't finished being written 50 | if @config.watch.delay > 0 51 | setTimeout => 52 | @workflow[eventType](f) 53 | , @config.watch.delay 54 | else 55 | @workflow[eventType](f) 56 | 57 | _buildDoneCallback: => 58 | logger.buildDone() 59 | clearInterval(@intervalId) if @intervalId? and !@persist 60 | @initCallback(@config) if @initCallback? 61 | 62 | _pullFiles: => 63 | return if @adds.length is 0 64 | filesToAdd = if @adds.length <= @throttle 65 | @adds.splice(0, @adds.length) 66 | else 67 | @adds.splice(0, @throttle) 68 | @workflow.add(f) for f in filesToAdd 69 | 70 | _ignoreFunct: (name) => 71 | if @config.watch.excludeRegex? 72 | if name.match(@config.watch.excludeRegex) 73 | logger.debug "Ignoring file [[ #{name} ]], matches exclude regex" 74 | return true 75 | if @config.watch.exclude? 76 | for exclude in @config.watch.exclude 77 | if name.indexOf(exclude) is 0 78 | logger.debug "Ignoring file [[ #{name} ]], matches exclude string path" 79 | return true 80 | false 81 | 82 | module.exports = Watcher 83 | -------------------------------------------------------------------------------- /src/command/config.coffee: -------------------------------------------------------------------------------- 1 | copyConfig = (opts) -> 2 | path = require 'path' 3 | fs = require 'fs' 4 | logger = require 'logmimosa' 5 | buildConfig = require '../util/config-builder' 6 | moduleManager = require '../modules' 7 | 8 | if opts.mdebug 9 | opts.debug = true 10 | logger.setDebug() 11 | process.env.DEBUG = true 12 | 13 | conf = buildConfig() 14 | 15 | currDefaultsPath = path.join path.resolve(''), "mimosa-config-documented.coffee" 16 | logger.debug "Writing config defaults file to #{currDefaultsPath}" 17 | defaultsConf = """ 18 | 19 | # The following is a version of the mimosa-config with all of 20 | # the defaults listed. This file is meant for reference only. 21 | 22 | #{conf} 23 | """ 24 | 25 | fs.writeFileSync currDefaultsPath, defaultsConf, 'ascii' 26 | 27 | unless opts.suppress 28 | logger.success "Copied [[ mimosa-config-documented.coffee ]] into current directory." 29 | 30 | mimosaConfigPath = path.join path.resolve(''), "mimosa-config.js" 31 | mimosaConfigPathCoffee = path.join path.resolve(''), "mimosa-config.coffee" 32 | 33 | if fs.existsSync(mimosaConfigPath) or fs.existsSync(mimosaConfigPathCoffee) 34 | unless opts.suppress 35 | logger.info "Not writing mimosa-config file as one exists already." 36 | else 37 | logger.debug "Writing config file to #{mimosaConfigPath}" 38 | outConfigText = if moduleManager.configModuleString 39 | modArray = JSON.parse(moduleManager.configModuleString) 40 | modObj = modules:modArray 41 | "exports.config = " + JSON.stringify( modObj, null, 2 ) 42 | else 43 | modObj = modules: ['copy', 'jshint', 'csslint', 'server', 'require', 'minify-js', 'minify-css', 'live-reload', 'bower'] 44 | "exports.config = " + JSON.stringify( modObj, null, 2 ) 45 | 46 | fs.writeFileSync mimosaConfigPath, outConfigText, 'ascii' 47 | 48 | unless opts.suppress 49 | logger.success "Copied [[ mimosa-config.js ]] into current directory." 50 | 51 | process.exit 0 52 | 53 | register = (program, callback) -> 54 | program 55 | .command('config') 56 | .option("-D, --mdebug", "run in debug mode") 57 | .option("-s, --suppress", "suppress info message") 58 | .description("copy the default Mimosa config into the current folder") 59 | .action(callback) 60 | .on '--help', -> 61 | logger = require 'logmimosa' 62 | logger.green(' The config command will create a mimosa-config.js in the current directory. It will') 63 | logger.green(' also create a mimosa-config-documented.coffee which contains all of the various') 64 | logger.green(' configuration documentation for each module that is a part of your project.') 65 | logger.blue( '\n $ mimosa config\n') 66 | 67 | module.exports = (program) -> 68 | register(program, copyConfig) 69 | -------------------------------------------------------------------------------- /lib/command/config.js: -------------------------------------------------------------------------------- 1 | var copyConfig, register; 2 | 3 | copyConfig = function(opts) { 4 | var buildConfig, conf, currDefaultsPath, defaultsConf, fs, logger, mimosaConfigPath, mimosaConfigPathCoffee, modArray, modObj, moduleManager, outConfigText, path; 5 | path = require('path'); 6 | fs = require('fs'); 7 | logger = require('logmimosa'); 8 | buildConfig = require('../util/config-builder'); 9 | moduleManager = require('../modules'); 10 | if (opts.mdebug) { 11 | opts.debug = true; 12 | logger.setDebug(); 13 | process.env.DEBUG = true; 14 | } 15 | conf = buildConfig(); 16 | currDefaultsPath = path.join(path.resolve(''), "mimosa-config-documented.coffee"); 17 | logger.debug("Writing config defaults file to " + currDefaultsPath); 18 | defaultsConf = "# The following is a version of the mimosa-config with all of\n# the defaults listed. This file is meant for reference only.\n\n" + conf; 19 | fs.writeFileSync(currDefaultsPath, defaultsConf, 'ascii'); 20 | if (!opts.suppress) { 21 | logger.success("Copied [[ mimosa-config-documented.coffee ]] into current directory."); 22 | } 23 | mimosaConfigPath = path.join(path.resolve(''), "mimosa-config.js"); 24 | mimosaConfigPathCoffee = path.join(path.resolve(''), "mimosa-config.coffee"); 25 | if (fs.existsSync(mimosaConfigPath) || fs.existsSync(mimosaConfigPathCoffee)) { 26 | if (!opts.suppress) { 27 | logger.info("Not writing mimosa-config file as one exists already."); 28 | } 29 | } else { 30 | logger.debug("Writing config file to " + mimosaConfigPath); 31 | outConfigText = moduleManager.configModuleString ? (modArray = JSON.parse(moduleManager.configModuleString), modObj = { 32 | modules: modArray 33 | }, "exports.config = " + JSON.stringify(modObj, null, 2)) : (modObj = { 34 | modules: ['copy', 'jshint', 'csslint', 'server', 'require', 'minify-js', 'minify-css', 'live-reload', 'bower'] 35 | }, "exports.config = " + JSON.stringify(modObj, null, 2)); 36 | fs.writeFileSync(mimosaConfigPath, outConfigText, 'ascii'); 37 | if (!opts.suppress) { 38 | logger.success("Copied [[ mimosa-config.js ]] into current directory."); 39 | } 40 | } 41 | return process.exit(0); 42 | }; 43 | 44 | register = function(program, callback) { 45 | return program.command('config').option("-D, --mdebug", "run in debug mode").option("-s, --suppress", "suppress info message").description("copy the default Mimosa config into the current folder").action(callback).on('--help', function() { 46 | var logger; 47 | logger = require('logmimosa'); 48 | logger.green(' The config command will create a mimosa-config.js in the current directory. It will'); 49 | logger.green(' also create a mimosa-config-documented.coffee which contains all of the various'); 50 | logger.green(' configuration documentation for each module that is a part of your project.'); 51 | return logger.blue('\n $ mimosa config\n'); 52 | }); 53 | }; 54 | 55 | module.exports = function(program) { 56 | return register(program, copyConfig); 57 | }; 58 | -------------------------------------------------------------------------------- /lib/command/module/uninstall.js: -------------------------------------------------------------------------------- 1 | var deleteMod, exec, fs, logger, moduleMetadata, path, register; 2 | 3 | path = require('path'); 4 | 5 | fs = require('fs'); 6 | 7 | exec = require('child_process').exec; 8 | 9 | logger = require('logmimosa'); 10 | 11 | moduleMetadata = require('../../modules').installedMetadata; 12 | 13 | deleteMod = function(name, opts) { 14 | var currentDir, err, found, mimosaPath, mod, pack, uninstallString, _i, _len, 15 | _this = this; 16 | if (opts.mdebug) { 17 | opts.debug = true; 18 | logger.setDebug(); 19 | process.env.DEBUG = true; 20 | } 21 | if (name == null) { 22 | try { 23 | pack = require(path.join(process.cwd(), 'package.json')); 24 | } catch (_error) { 25 | err = _error; 26 | return logger.error("Unable to find package.json, or badly formatted: " + err); 27 | } 28 | if (pack.name == null) { 29 | return logger.error("package.json missing either name or version"); 30 | } 31 | name = pack.name; 32 | } 33 | if (name.indexOf('mimosa-') !== 0) { 34 | return logger.error("Can only delete 'mimosa-' prefixed modules with mod:delete (ex: mimosa-server)."); 35 | } 36 | found = false; 37 | for (_i = 0, _len = moduleMetadata.length; _i < _len; _i++) { 38 | mod = moduleMetadata[_i]; 39 | if (mod.name === name) { 40 | found = true; 41 | break; 42 | } 43 | } 44 | if (!found) { 45 | return logger.error("Module named [[ " + name + " ]] is not currently installed so it cannot be uninstalled."); 46 | } 47 | currentDir = process.cwd(); 48 | mimosaPath = path.join(__dirname, '..', '..'); 49 | process.chdir(mimosaPath); 50 | uninstallString = "npm uninstall " + name + " --save"; 51 | return exec(uninstallString, function(err, sout, serr) { 52 | if (err) { 53 | logger.error(err); 54 | } else { 55 | if (serr) { 56 | logger.error(serr); 57 | } 58 | logger.success("Uninstall of [[ " + name + " ]] successful"); 59 | } 60 | logger.debug("NPM UNINSTALL standard out\n" + sout); 61 | logger.debug("NPM UNINSTALL standard err\n" + serr); 62 | process.chdir(currentDir); 63 | return process.exit(0); 64 | }); 65 | }; 66 | 67 | register = function(program, callback) { 68 | var _this = this; 69 | return program.command('mod:uninstall [name]').option("-D, --mdebug", "run in debug mode").description("uninstall a Mimosa module from your installed Mimosa").action(callback).on('--help', function() { 70 | logger.green(' The \'mod:uninstall\' command will delete a Mimosa module from your Mimosa install. This does'); 71 | logger.green(' not delete anything from any of your projects, but it removes the ability for all projects'); 72 | logger.green(' using Mimosa to utilize the removed module. You can retrieve the list of installed modules '); 73 | logger.green(' using \'mod:list\'.'); 74 | return logger.blue('\n $ mimosa mod:uninstall mimosa-server\n'); 75 | }); 76 | }; 77 | 78 | module.exports = function(program) { 79 | return register(program, deleteMod); 80 | }; 81 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | // If just looking for version, spit it out and be done 2 | if ( process.argv.indexOf( "--version" ) === 2 ) { 3 | var version = require( '../package.json' ).version; 4 | console.log( version ); 5 | return; 6 | } 7 | 8 | var logger = require( 'logmimosa' ) 9 | , program = require( 'commander' ) 10 | , modCommandsAdded = false; 11 | 12 | var modCommands = function() { 13 | if ( !modCommandsAdded ) { 14 | require( './command/module/install' )( program ); 15 | require( './command/module/uninstall' )( program ); 16 | require( './command/module/list') ( program ); 17 | require( './command/module/config' )( program ); 18 | } 19 | modCommandsAdded = true; 20 | }; 21 | 22 | var makeTopLevelHelp = function() { 23 | process.argv[2] = '--help'; 24 | modCommands(); 25 | program.on( '--help', function() { 26 | console.log( " Node Options (these will be passed through to the node process that runs Mimosa):"); 27 | console.log( ' --nolazy, turns off lazy compilation, forcing v8 to do a full compile of the code.'); 28 | console.log( ' --debug, useful when you are not going to debug node.js right now, but you want to debug it later.'); 29 | console.log( ' --debug-brk, allows you to debug the code executed on start'); 30 | console.log( ' --expose-gc, expose gc extension'); 31 | console.log( ' --gc, expose gc extension'); 32 | console.log( ' --gc-global, gc forced by flags'); 33 | console.log( ' --harmony, enable all harmony features (except typeof)'); 34 | console.log( ' --harmony-collections, enable harmony collections (sets, maps, and weak maps)'); 35 | console.log( ' --harmony-generators, enable harmony proxies'); 36 | console.log( ' --harmony-proxies, enable harmony proxies'); 37 | console.log( ' --prof, Log statistical profiling information (implies --log-code)'); 38 | console.log( "\n" ); 39 | }); 40 | }; 41 | 42 | require( './command/watch' )( program ); 43 | require( './command/config' )( program ); 44 | require( './command/build' )( program ); 45 | require( './command/clean' )( program ); 46 | require( './command/external' )( program ); 47 | 48 | // if someone just types "mimosa", treat it as --help. 49 | if ( process.argv.length === 2 || ( process.argv.length > 2 && ( process.argv[2] === '--help' || process.argv[2] === '-h') ) ) { 50 | makeTopLevelHelp(); 51 | } else { 52 | program.command( '*' ).action( function ( arg ) { 53 | if ( arg ) { 54 | logger.red( " " + arg + " is not a valid command." ); 55 | } 56 | makeTopLevelHelp(); 57 | program.parse( process.argv ); 58 | }); 59 | 60 | if ( !modCommandsAdded ) { 61 | process.argv.forEach( function( arg ) { 62 | if ( arg.indexOf( "mod:" ) === 0 ) { 63 | modCommands(); 64 | } 65 | }); 66 | } 67 | } 68 | 69 | // var cleanUpProcesses = function() { 70 | // var psTree = require( 'ps-tree' ); 71 | // psTree( process.pid, function( err, children ) { 72 | // children.forEach( function( c ) { 73 | // process.kill( c.PID ); 74 | // }); 75 | // }); 76 | // }; 77 | // 78 | // // ensure cleanup of mimosa related stuff on sigterm 79 | // process.on( 'SIGTERM', cleanUpProcesses).on( 'SIGINT', cleanUpProcesses ); 80 | 81 | program.parse( process.argv ); -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // If just looking for version, spit it out and be done 2 | if ( process.argv.indexOf( "--version" ) === 2 ) { 3 | var version = require( '../package.json' ).version; 4 | console.log( version ); 5 | return; 6 | } 7 | 8 | var logger = require( 'logmimosa' ) 9 | , program = require( 'commander' ) 10 | , modCommandsAdded = false; 11 | 12 | var modCommands = function() { 13 | if ( !modCommandsAdded ) { 14 | require( './command/module/install' )( program ); 15 | require( './command/module/uninstall' )( program ); 16 | require( './command/module/list') ( program ); 17 | require( './command/module/config' )( program ); 18 | } 19 | modCommandsAdded = true; 20 | }; 21 | 22 | var makeTopLevelHelp = function() { 23 | process.argv[2] = '--help'; 24 | modCommands(); 25 | program.on( '--help', function() { 26 | console.log( " Node Options (these will be passed through to the node process that runs Mimosa):"); 27 | console.log( ' --nolazy, turns off lazy compilation, forcing v8 to do a full compile of the code.'); 28 | console.log( ' --debug, useful when you are not going to debug node.js right now, but you want to debug it later.'); 29 | console.log( ' --debug-brk, allows you to debug the code executed on start'); 30 | console.log( ' --expose-gc, expose gc extension'); 31 | console.log( ' --gc, expose gc extension'); 32 | console.log( ' --gc-global, gc forced by flags'); 33 | console.log( ' --harmony, enable all harmony features (except typeof)'); 34 | console.log( ' --harmony-collections, enable harmony collections (sets, maps, and weak maps)'); 35 | console.log( ' --harmony-generators, enable harmony proxies'); 36 | console.log( ' --harmony-proxies, enable harmony proxies'); 37 | console.log( ' --prof, Log statistical profiling information (implies --log-code)'); 38 | console.log( "\n" ); 39 | }); 40 | }; 41 | 42 | require( './command/watch' )( program ); 43 | require( './command/config' )( program ); 44 | require( './command/build' )( program ); 45 | require( './command/clean' )( program ); 46 | require( './command/external' )( program ); 47 | 48 | // if someone just types "mimosa", treat it as --help. 49 | if ( process.argv.length === 2 || ( process.argv.length > 2 && ( process.argv[2] === '--help' || process.argv[2] === '-h') ) ) { 50 | makeTopLevelHelp(); 51 | } else { 52 | program.command( '*' ).action( function ( arg ) { 53 | if ( arg ) { 54 | logger.red( " " + arg + " is not a valid command." ); 55 | } 56 | makeTopLevelHelp(); 57 | program.parse( process.argv ); 58 | }); 59 | 60 | if ( !modCommandsAdded ) { 61 | process.argv.forEach( function( arg ) { 62 | if ( arg.indexOf( "mod:" ) === 0 ) { 63 | modCommands(); 64 | } 65 | }); 66 | } 67 | } 68 | 69 | // var cleanUpProcesses = function() { 70 | // var psTree = require( 'ps-tree' ); 71 | // psTree( process.pid, function( err, children ) { 72 | // children.forEach( function( c ) { 73 | // process.kill( c.PID ); 74 | // }); 75 | // }); 76 | // }; 77 | // 78 | // // ensure cleanup of mimosa related stuff on sigterm 79 | // process.on( 'SIGTERM', cleanUpProcesses).on( 'SIGINT', cleanUpProcesses ); 80 | 81 | program.parse( process.argv ); -------------------------------------------------------------------------------- /src/command/clean.coffee: -------------------------------------------------------------------------------- 1 | clean = (opts) -> 2 | fs = require 'fs' 3 | wrench = require 'wrench' 4 | logger = require 'logmimosa' 5 | configurer = require '../util/configurer' 6 | Cleaner = require '../util/cleaner' 7 | 8 | if opts.mdebug 9 | opts.debug = true 10 | logger.setDebug() 11 | process.env.DEBUG = true 12 | 13 | opts.clean = true 14 | 15 | if opts.cleanall 16 | fileUtils = require '../util/file' 17 | fileUtils.removeDotMimosa() 18 | logger.info("Removed .mimosa directory.") 19 | 20 | configurer opts, (config, modules) => 21 | if opts.force 22 | if fs.existsSync config.watch.compiledDir 23 | logger.info "Forcibly removing the entire directory [[ #{config.watch.compiledDir} ]]" 24 | wrench.rmdirSyncRecursive(config.watch.compiledDir) 25 | logger.success "[[ #{config.watch.compiledDir} ]] has been removed" 26 | else 27 | logger.success "Compiled directory already deleted" 28 | else 29 | config.isClean = true 30 | new Cleaner config, modules, -> 31 | logger.success "[[ #{config.watch.compiledDir} ]] has been cleaned." 32 | process.exit 0 33 | 34 | register = (program, callback) => 35 | program 36 | .command('clean') 37 | .option("-f, --force", "completely delete your compiledDir") 38 | .option("-C, --cleanall", "deletes the .mimosa directory as part of the clean") 39 | .option("-P, --profile ", "select a mimosa profile") 40 | .option("-D, --mdebug", "run in debug mode") 41 | .description("clean out all of the compiled assets from the compiled directory") 42 | .action(callback) 43 | .on '--help', -> 44 | logger = require 'logmimosa' 45 | logger.green(' The clean command will remove all of the compiled assets from the configured compiledDir. After') 46 | logger.green(' the assets are deleted, any empty directories left over are also removed. Mimosa will also remove any') 47 | logger.green(' Mimosa copied assets, like template libraries. It will not remove anything that did not originate') 48 | logger.green(' from the source directory.') 49 | logger.blue( '\n $ mimosa clean\n') 50 | logger.green(' In the course of development, some files get left behind that no longer have counterparts in your') 51 | logger.green(' source directories. If you have \'mimosa watch\' turned off when you delete a file, for instance,') 52 | logger.green(' the compiled version of that file will not get removed from your compiledDir, and \'mimosa clean\'') 53 | logger.green(' will leave it alone because it does not recognize it as coming from mimosa. If you want to ') 54 | logger.green(' forcibly remove the entire compiledDir, use the \'force\' flag.') 55 | logger.blue( '\n $ mimosa clean -force') 56 | logger.blue( ' $ mimosa clean -f\n') 57 | logger.green(' Pass a \'cleanall\' flag and Mimosa remove the .mimosa directory as part of the clean.') 58 | logger.blue( '\n $ mimosa clean --cleanall') 59 | logger.blue( ' $ mimosa clean -C\n') 60 | logger.green(' Pass a \'profile\' flag and the name of a Mimosa profile to run with.') 61 | logger.blue( '\n $ mimosa clean --profile build') 62 | logger.blue( ' $ mimosa clean -P build') 63 | 64 | module.exports = (program) -> 65 | register(program, clean) -------------------------------------------------------------------------------- /lib/command/clean.js: -------------------------------------------------------------------------------- 1 | var clean, register, 2 | _this = this; 3 | 4 | clean = function(opts) { 5 | var Cleaner, configurer, fileUtils, fs, logger, wrench, 6 | _this = this; 7 | fs = require('fs'); 8 | wrench = require('wrench'); 9 | logger = require('logmimosa'); 10 | configurer = require('../util/configurer'); 11 | Cleaner = require('../util/cleaner'); 12 | if (opts.mdebug) { 13 | opts.debug = true; 14 | logger.setDebug(); 15 | process.env.DEBUG = true; 16 | } 17 | opts.clean = true; 18 | if (opts.cleanall) { 19 | fileUtils = require('../util/file'); 20 | fileUtils.removeDotMimosa(); 21 | logger.info("Removed .mimosa directory."); 22 | } 23 | return configurer(opts, function(config, modules) { 24 | if (opts.force) { 25 | if (fs.existsSync(config.watch.compiledDir)) { 26 | logger.info("Forcibly removing the entire directory [[ " + config.watch.compiledDir + " ]]"); 27 | wrench.rmdirSyncRecursive(config.watch.compiledDir); 28 | return logger.success("[[ " + config.watch.compiledDir + " ]] has been removed"); 29 | } else { 30 | return logger.success("Compiled directory already deleted"); 31 | } 32 | } else { 33 | config.isClean = true; 34 | return new Cleaner(config, modules, function() { 35 | logger.success("[[ " + config.watch.compiledDir + " ]] has been cleaned."); 36 | return process.exit(0); 37 | }); 38 | } 39 | }); 40 | }; 41 | 42 | register = function(program, callback) { 43 | return program.command('clean').option("-f, --force", "completely delete your compiledDir").option("-C, --cleanall", "deletes the .mimosa directory as part of the clean").option("-P, --profile ", "select a mimosa profile").option("-D, --mdebug", "run in debug mode").description("clean out all of the compiled assets from the compiled directory").action(callback).on('--help', function() { 44 | var logger; 45 | logger = require('logmimosa'); 46 | logger.green(' The clean command will remove all of the compiled assets from the configured compiledDir. After'); 47 | logger.green(' the assets are deleted, any empty directories left over are also removed. Mimosa will also remove any'); 48 | logger.green(' Mimosa copied assets, like template libraries. It will not remove anything that did not originate'); 49 | logger.green(' from the source directory.'); 50 | logger.blue('\n $ mimosa clean\n'); 51 | logger.green(' In the course of development, some files get left behind that no longer have counterparts in your'); 52 | logger.green(' source directories. If you have \'mimosa watch\' turned off when you delete a file, for instance,'); 53 | logger.green(' the compiled version of that file will not get removed from your compiledDir, and \'mimosa clean\''); 54 | logger.green(' will leave it alone because it does not recognize it as coming from mimosa. If you want to '); 55 | logger.green(' forcibly remove the entire compiledDir, use the \'force\' flag.'); 56 | logger.blue('\n $ mimosa clean -force'); 57 | logger.blue(' $ mimosa clean -f\n'); 58 | logger.green(' Pass a \'cleanall\' flag and Mimosa remove the .mimosa directory as part of the clean.'); 59 | logger.blue('\n $ mimosa clean --cleanall'); 60 | logger.blue(' $ mimosa clean -C\n'); 61 | logger.green(' Pass a \'profile\' flag and the name of a Mimosa profile to run with.'); 62 | logger.blue('\n $ mimosa clean --profile build'); 63 | return logger.blue(' $ mimosa clean -P build'); 64 | }); 65 | }; 66 | 67 | module.exports = function(program) { 68 | return register(program, clean); 69 | }; 70 | -------------------------------------------------------------------------------- /src/util/file.coffee: -------------------------------------------------------------------------------- 1 | path = require 'path' 2 | fs = require 'fs' 3 | logger = require 'logmimosa' 4 | 5 | exports.removeDotMimosa = -> 6 | dotMimosaDir = path.join(process.cwd(), ".mimosa") 7 | if fs.existsSync(dotMimosaDir) 8 | wrench = require 'wrench' 9 | wrench.rmdirSyncRecursive(dotMimosaDir) 10 | 11 | exports.isCSS = isCSS = (fileName) -> 12 | path.extname(fileName) is ".css" 13 | 14 | exports.isJavascript = isJavascript = (fileName) -> 15 | path.extname(fileName) is ".js" 16 | 17 | exports.isVendorCSS = isVendorCSS = (config, fileName) -> 18 | fileName.indexOf(config.vendor.stylesheets) is 0 19 | 20 | exports.isVendorJS = isVendorJS = (config, fileName) -> 21 | fileName.indexOf(config.vendor.javascripts) is 0 22 | 23 | exports.mkdirRecursive = mkdirRecursive = (p, made) -> 24 | if !made then made = null 25 | p = path.resolve(p) 26 | 27 | try 28 | fs.mkdirSync p 29 | made = made || p 30 | catch err 31 | if err.code is 'ENOENT' 32 | made = mkdirRecursive path.dirname(p), made 33 | mkdirRecursive p, made 34 | else if err.code is 'EEXIST' 35 | try 36 | stat = fs.statSync(p); 37 | catch err2 38 | throw err 39 | if !stat.isDirectory() then throw err 40 | else throw err 41 | made 42 | 43 | exports.writeFile = (fileName, content, callback) -> 44 | dirname = path.dirname(fileName) 45 | mkdirRecursive dirname unless fs.existsSync dirname 46 | fs.writeFile fileName, content, "utf8", (err) -> 47 | error = if err? then "Failed to write file: #{fileName}, #{err}" 48 | callback(error) 49 | 50 | exports.isFirstFileNewer = (file1, file2, cb) -> 51 | return cb(false) unless file1? 52 | return cb(true) unless file2? 53 | 54 | fs.exists file1, (exists1) -> 55 | unless exists1 56 | logger.warn "Detected change with file [[ #{file1} ]] but is no longer present." 57 | return cb(false) 58 | 59 | fs.exists file2, (exists2) -> 60 | unless exists2 61 | logger.debug "File missing, so is new file [[ #{file2} ]]" 62 | return cb(true) 63 | 64 | fs.stat file2, (err, stats2) -> 65 | fs.stat file1, (err, stats1) -> 66 | unless stats1? and stats2? 67 | logger.debug "Somehow a file went missing [[ #{stats1} ]], [[ #{stats2} ]] " 68 | return cb(false) 69 | 70 | if stats1.mtime > stats2.mtime then cb(true) else cb(false) 71 | 72 | exports.readdirSyncRecursive = (baseDir, excludes = [], excludeRegex, ignoreDirectories = false) -> 73 | baseDir = baseDir.replace /\/$/, '' 74 | 75 | readdirSyncRecursive = (baseDir) -> 76 | curFiles = fs.readdirSync(baseDir).map (f) -> 77 | path.join(baseDir, f) 78 | 79 | if excludes.length > 0 80 | curFiles = curFiles.filter (f) -> 81 | for exclude in excludes 82 | if f is exclude or f.indexOf(exclude) is 0 83 | return false 84 | true 85 | 86 | if excludeRegex 87 | curFiles = curFiles.filter (f) -> not f.match(excludeRegex) 88 | 89 | nextDirs = curFiles.filter (fname) -> 90 | fs.statSync(fname).isDirectory() 91 | 92 | if ignoreDirectories 93 | curFiles = curFiles.filter (fname) -> 94 | fs.statSync(fname).isFile() 95 | 96 | files = curFiles 97 | while (nextDirs.length) 98 | files = files.concat(readdirSyncRecursive(nextDirs.shift())) 99 | files 100 | 101 | readdirSyncRecursive(baseDir) 102 | 103 | exports.setFileFlags = (config, options) -> 104 | exts = config.extensions 105 | ext = options.extension 106 | 107 | options.isJavascript = false 108 | options.isCSS = false 109 | options.isVendor = false 110 | options.isJSNotVendor = false 111 | options.isCopy = false 112 | 113 | if exts.template.indexOf(ext) > -1 114 | options.isTemplate = true 115 | options.isJavascript = true 116 | options.isJSNotVendor = true 117 | 118 | if exts.copy.indexOf(ext) > -1 119 | options.isCopy = true 120 | 121 | if exts.javascript.indexOf(ext) > -1 or (options.inputFile and isJavascript(options.inputFile)) 122 | options.isJavascript = true 123 | if options.inputFile 124 | options.isVendor = isVendorJS(config, options.inputFile) 125 | options.isJSNotVendor = not options.isVendor 126 | 127 | if exts.css.indexOf(ext) > -1 or (options.inputFile and isCSS(options.inputFile)) 128 | options.isCSS = true 129 | if options.inputFile 130 | options.isVendor = isVendorCSS(config, options.inputFile) -------------------------------------------------------------------------------- /src/util/config-builder.coffee: -------------------------------------------------------------------------------- 1 | moduleManager = require '../modules' 2 | 3 | _configTop = -> 4 | """ 5 | exports.config = { 6 | 7 | minMimosaVersion:null # The minimum Mimosa version that must be installed to use the project. 8 | requiredMimosaVersion:null # The Mimosa version that must be installed to use the project. 9 | 10 | ### 11 | The list of Mimosa modules to use for this application. The defaults come bundled with Mimosa 12 | and do not need to be installed. If a module is listed here that Mimosa is unaware of, Mimosa 13 | will attempt to install it. 14 | ### 15 | modules: ['copy', 'jshint', 'csslint', 'server', 'require', 'minify-js', 'minify-css', 'live-reload', 'bower'] 16 | 17 | watch: 18 | sourceDir: "assets" # directory location of web assets, can be relative to 19 | # the project root, or absolute 20 | compiledDir: "public" # directory location of compiled web assets, can be 21 | # relative to the project root, or absolute 22 | javascriptDir: "javascripts" # Location of precompiled javascript (i.e. 23 | # coffeescript), must be relative to sourceDir 24 | exclude: [/[/\\\\](\\.|~)[^/\\\\]+$/] # regexes or strings matching the files to be 25 | # ignored by mimosa, the default matches all sorts of 26 | # dot files and temp files. Strings are paths and can 27 | # be relative to sourceDir or absolute. 28 | throttle: 0 # number of file adds the watcher handles before 29 | # taking a 100 millisecond pause to let those files 30 | # finish their processing. This helps avoid EMFILE 31 | # issues for projects containing large numbers of 32 | # files that all get copied at once. If the throttle 33 | # is set to 0, no throttling is performed. Recommended 34 | # to leave this set at 0, thedefault, until you start 35 | # encountering EMFILE problems. throttle has no effect 36 | # if usePolling is set to false. 37 | usePolling: true # WARNING: Do not change this default if you are on 38 | # *Nix. Windows users, read on. 39 | # Whether or not to poll for system file changes. 40 | # Unless you have a lot files and your CPU starts 41 | # running hot, it is best to leave this setting alone. 42 | interval: 100 # Interval of file system polling. 43 | binaryInterval: 300 # Interval of file system polling for binary files 44 | delay: 0 # For file adds/updates, a forced delay before Mimosa 45 | # begins processing a file. This helps solve cases when 46 | # a file system event is created before the file system 47 | # is actually finished writing the file. Delay is in millis. 48 | 49 | vendor: # settings for vendor assets 50 | javascripts: "javascripts/vendor" # location, relative to the watch.sourceDir, of vendor 51 | # javascript assets. Unix style slashes please. 52 | stylesheets: "stylesheets/vendor" # location, relative to the watch.sourceDir, of vendor 53 | # stylesheet assets. Unix style slashes please. 54 | 55 | """ 56 | 57 | buildConfigText = -> 58 | modules = moduleManager.all 59 | configText = _configTop() 60 | for mod in modules 61 | if mod.placeholder? 62 | ph = mod.placeholder() 63 | configText += ph if ph? 64 | configText += "\n}" 65 | 66 | if moduleManager.configModuleString? 67 | configText = configText.replace(" modules: ['copy', 'jshint', 'csslint', 'server', 'require', 'minify-js', 'minify-css', 'live-reload', 'bower']", " modules: " + moduleManager.configModuleString) 68 | 69 | configText 70 | 71 | module.exports = buildConfigText 72 | -------------------------------------------------------------------------------- /lib/util/config-builder.js: -------------------------------------------------------------------------------- 1 | var buildConfigText, moduleManager, _configTop; 2 | 3 | moduleManager = require('../modules'); 4 | 5 | _configTop = function() { 6 | return "exports.config = {\n\n minMimosaVersion:null # The minimum Mimosa version that must be installed to use the project.\n requiredMimosaVersion:null # The Mimosa version that must be installed to use the project.\n\n ###\n The list of Mimosa modules to use for this application. The defaults come bundled with Mimosa\n and do not need to be installed. If a module is listed here that Mimosa is unaware of, Mimosa\n will attempt to install it.\n ###\n modules: ['copy', 'jshint', 'csslint', 'server', 'require', 'minify-js', 'minify-css', 'live-reload', 'bower']\n\n watch:\n sourceDir: \"assets\" # directory location of web assets, can be relative to\n # the project root, or absolute\n compiledDir: \"public\" # directory location of compiled web assets, can be\n # relative to the project root, or absolute\n javascriptDir: \"javascripts\" # Location of precompiled javascript (i.e.\n # coffeescript), must be relative to sourceDir\n exclude: [/[/\\\\](\\.|~)[^/\\\\]+$/] # regexes or strings matching the files to be\n # ignored by mimosa, the default matches all sorts of\n # dot files and temp files. Strings are paths and can\n # be relative to sourceDir or absolute.\n throttle: 0 # number of file adds the watcher handles before\n # taking a 100 millisecond pause to let those files\n # finish their processing. This helps avoid EMFILE\n # issues for projects containing large numbers of\n # files that all get copied at once. If the throttle\n # is set to 0, no throttling is performed. Recommended\n # to leave this set at 0, thedefault, until you start\n # encountering EMFILE problems. throttle has no effect\n # if usePolling is set to false.\n usePolling: true # WARNING: Do not change this default if you are on\n # *Nix. Windows users, read on.\n # Whether or not to poll for system file changes.\n # Unless you have a lot files and your CPU starts\n # running hot, it is best to leave this setting alone.\n interval: 100 # Interval of file system polling.\n binaryInterval: 300 # Interval of file system polling for binary files\n delay: 0 # For file adds/updates, a forced delay before Mimosa\n # begins processing a file. This helps solve cases when\n # a file system event is created before the file system\n # is actually finished writing the file. Delay is in millis.\n\n vendor: # settings for vendor assets\n javascripts: \"javascripts/vendor\" # location, relative to the watch.sourceDir, of vendor\n # javascript assets. Unix style slashes please.\n stylesheets: \"stylesheets/vendor\" # location, relative to the watch.sourceDir, of vendor\n # stylesheet assets. Unix style slashes please.\n"; 7 | }; 8 | 9 | buildConfigText = function() { 10 | var configText, mod, modules, ph, _i, _len; 11 | modules = moduleManager.all; 12 | configText = _configTop(); 13 | for (_i = 0, _len = modules.length; _i < _len; _i++) { 14 | mod = modules[_i]; 15 | if (mod.placeholder != null) { 16 | ph = mod.placeholder(); 17 | if (ph != null) { 18 | configText += ph; 19 | } 20 | } 21 | } 22 | configText += "\n}"; 23 | if (moduleManager.configModuleString != null) { 24 | configText = configText.replace(" modules: ['copy', 'jshint', 'csslint', 'server', 'require', 'minify-js', 'minify-css', 'live-reload', 'bower']", " modules: " + moduleManager.configModuleString); 25 | } 26 | return configText; 27 | }; 28 | 29 | module.exports = buildConfigText; 30 | -------------------------------------------------------------------------------- /lib/util/watcher.js: -------------------------------------------------------------------------------- 1 | var Watcher, Workflow, logger, watch, 2 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; 3 | 4 | watch = require('chokidar'); 5 | 6 | logger = require('logmimosa'); 7 | 8 | Workflow = require('./workflow'); 9 | 10 | Watcher = (function() { 11 | Watcher.prototype.adds = []; 12 | 13 | function Watcher(config, modules, persist, initCallback) { 14 | this.config = config; 15 | this.persist = persist; 16 | this.initCallback = initCallback; 17 | this._ignoreFunct = __bind(this._ignoreFunct, this); 18 | this._pullFiles = __bind(this._pullFiles, this); 19 | this._buildDoneCallback = __bind(this._buildDoneCallback, this); 20 | this._fileUpdated = __bind(this._fileUpdated, this); 21 | this._startWatcher = __bind(this._startWatcher, this); 22 | this.throttle = this.config.watch.throttle; 23 | this.workflow = new Workflow(this.config, modules, this._buildDoneCallback); 24 | this.workflow.initBuild(this._startWatcher); 25 | } 26 | 27 | Watcher.prototype._startWatcher = function() { 28 | var watchConfig, watcher, 29 | _this = this; 30 | watchConfig = { 31 | ignored: this._ignoreFunct, 32 | persistent: this.persist, 33 | interval: this.config.watch.interval, 34 | binaryInterval: this.config.watch.binaryInterval, 35 | usePolling: this.config.watch.usePolling 36 | }; 37 | watcher = watch.watch(this.config.watch.sourceDir, watchConfig); 38 | process.on('STOPMIMOSA', function() { 39 | return watcher.close(); 40 | }); 41 | watcher.on("error", function(error) { 42 | return logger.warn("File watching error: " + error); 43 | }); 44 | watcher.on("change", function(f) { 45 | return _this._fileUpdated('update', f); 46 | }); 47 | watcher.on("unlink", this.workflow.remove); 48 | watcher.on("ready", this.workflow.ready); 49 | watcher.on("add", function(f) { 50 | if (_this.throttle > 0) { 51 | return _this.adds.push(f); 52 | } else { 53 | return _this._fileUpdated('add', f); 54 | } 55 | }); 56 | if (this.persist) { 57 | logger.info("Watching [[ " + this.config.watch.sourceDir + " ]]"); 58 | } 59 | if (this.throttle > 0) { 60 | logger.debug("Throttle is set, setting interval at 100 milliseconds"); 61 | this.intervalId = setInterval(this._pullFiles, 100); 62 | return this._pullFiles(); 63 | } 64 | }; 65 | 66 | Watcher.prototype._fileUpdated = function(eventType, f) { 67 | var _this = this; 68 | if (this.config.watch.delay > 0) { 69 | return setTimeout(function() { 70 | return _this.workflow[eventType](f); 71 | }, this.config.watch.delay); 72 | } else { 73 | return this.workflow[eventType](f); 74 | } 75 | }; 76 | 77 | Watcher.prototype._buildDoneCallback = function() { 78 | logger.buildDone(); 79 | if ((this.intervalId != null) && !this.persist) { 80 | clearInterval(this.intervalId); 81 | } 82 | if (this.initCallback != null) { 83 | return this.initCallback(this.config); 84 | } 85 | }; 86 | 87 | Watcher.prototype._pullFiles = function() { 88 | var f, filesToAdd, _i, _len, _results; 89 | if (this.adds.length === 0) { 90 | return; 91 | } 92 | filesToAdd = this.adds.length <= this.throttle ? this.adds.splice(0, this.adds.length) : this.adds.splice(0, this.throttle); 93 | _results = []; 94 | for (_i = 0, _len = filesToAdd.length; _i < _len; _i++) { 95 | f = filesToAdd[_i]; 96 | _results.push(this.workflow.add(f)); 97 | } 98 | return _results; 99 | }; 100 | 101 | Watcher.prototype._ignoreFunct = function(name) { 102 | var exclude, _i, _len, _ref; 103 | if (this.config.watch.excludeRegex != null) { 104 | if (name.match(this.config.watch.excludeRegex)) { 105 | logger.debug("Ignoring file [[ " + name + " ]], matches exclude regex"); 106 | return true; 107 | } 108 | } 109 | if (this.config.watch.exclude != null) { 110 | _ref = this.config.watch.exclude; 111 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 112 | exclude = _ref[_i]; 113 | if (name.indexOf(exclude) === 0) { 114 | logger.debug("Ignoring file [[ " + name + " ]], matches exclude string path"); 115 | return true; 116 | } 117 | } 118 | } 119 | return false; 120 | }; 121 | 122 | return Watcher; 123 | 124 | })(); 125 | 126 | module.exports = Watcher; 127 | -------------------------------------------------------------------------------- /src/command/build.coffee: -------------------------------------------------------------------------------- 1 | build = (opts) -> 2 | logger = require 'logmimosa' 3 | configurer = require '../util/configurer' 4 | Watcher = require '../util/watcher' 5 | Cleaner = require '../util/cleaner' 6 | 7 | if opts.mdebug 8 | opts.debug = true 9 | logger.setDebug() 10 | process.env.DEBUG = true 11 | 12 | logger.info "Beginning build" 13 | opts.build = true 14 | 15 | if opts.cleanall 16 | fileUtils = require '../util/file' 17 | fileUtils.removeDotMimosa() 18 | logger.info("Removed .mimosa directory.") 19 | 20 | configurer opts, (config, modules) -> 21 | 22 | doBuild = -> 23 | config.isClean = false 24 | new Watcher config, modules, false, -> 25 | logger.success "Finished build" 26 | process.exit 0 27 | 28 | config.isClean = true 29 | new Cleaner(config, modules, doBuild) 30 | 31 | register = (program, callback) -> 32 | program 33 | .command('build') 34 | .description("make a single pass through assets, compile them, and optionally package them") 35 | .option("-C, --cleanall", "deletes the .mimosa directory before building") 36 | .option("-o, --optimize", "run r.js optimization after building") 37 | .option("-m, --minify", "minify each asset as it is compiled using uglify") 38 | .option("-p, --package", "use modules that perform packaging") 39 | .option("-i --install", "use modules that perform installation") 40 | .option("-e --errorout", "cease processing and error out if the build encounters critical errors") 41 | .option("-P, --profile ", "select a mimosa profile") 42 | .option("-D, --mdebug", "run in debug mode") 43 | .action(callback) 44 | .on '--help', -> 45 | logger = require 'logmimosa' 46 | logger.green(' The build command will make a single pass through your assets and bulid any that need building') 47 | logger.green(' and then exit.') 48 | logger.blue( '\n $ mimosa build\n') 49 | logger.green(' Pass a \'cleanall\' flag and Mimosa remove the .mimosa directory before building. The \'build\' command') 50 | logger.green(' already runs a code clean.') 51 | logger.blue( '\n $ mimosa build --cleanall') 52 | logger.blue( ' $ mimosa build -C\n') 53 | logger.green(' Pass an \'optimize\' flag and Mimosa will use requirejs to optimize your assets and provide you') 54 | logger.green(' with single files for the named requirejs modules. ') 55 | logger.blue( '\n $ mimosa build --optimize') 56 | logger.blue( ' $ mimosa build -o\n') 57 | logger.green(' Pass an \'minify\' flag and Mimosa will use uglify to minify/compress your assets when they are compiled.') 58 | logger.green(' You can provide exclude, files you do not want to minify, in the mimosa-config. If you run \'minify\' ') 59 | logger.green(' and \'optimize\' at the same time, optimize will not run the uglify portion of its processing which occurs as') 60 | logger.green(' a separate step after everything has compiled and does not allow control of what gets uglified. Use \'optimize\'') 61 | logger.green(' and \'minify\' together if you need to control which files get mangled by uglify (because sometimes uglify') 62 | logger.green(' can break them) but you still want everything together in a single file.') 63 | logger.blue( '\n $ mimosa watch --minify') 64 | logger.blue( ' $ mimosa watch -m\n') 65 | logger.green(' Pass a \'package\' flag if you have installed a module (like mimosa-web-package) that is capable of') 66 | logger.green(' executing packaging functionality for you after the building of assets is complete.') 67 | logger.blue( '\n $ mimosa build --package') 68 | logger.blue( ' $ mimosa build -p\n') 69 | logger.green(' Pass a \'install\' flag if you have installed a module that is capable of executing') 70 | logger.green(' installation functionality for you after the building/packaging of assets is complete.') 71 | logger.blue( '\n $ mimosa build --install') 72 | logger.blue( ' $ mimosa build -i\n') 73 | logger.green(' Pass a \'errorout\' flag if you want the build to stop as soon as a critical error occurs.') 74 | logger.blue( '\n $ mimosa build --errorout') 75 | logger.blue( ' $ mimosa build -e\n') 76 | logger.green(' Pass a \'profile\' flag and the name of a Mimosa profile to run with.') 77 | logger.blue( '\n $ mimosa build --profile build') 78 | logger.blue( ' $ mimosa build -P build') 79 | 80 | module.exports = (program) -> 81 | register(program, build) -------------------------------------------------------------------------------- /src/command/module/list.coffee: -------------------------------------------------------------------------------- 1 | http = require 'http' 2 | 3 | color = require('ansi-color').set 4 | logger = require 'logmimosa' 5 | childProcess = require 'child_process' 6 | 7 | moduleMetadata = require('../../modules').installedMetadata 8 | 9 | printResults = (mods, opts) -> 10 | verbose = opts.verbose 11 | installed = opts.installed 12 | 13 | longestModName = 0 14 | ownedMods = [] 15 | for mod in mods 16 | if mod.name.length > longestModName 17 | longestModName = mod.name.length 18 | mod.installed= '' 19 | for m in moduleMetadata 20 | if m.name is mod.name 21 | if mod.version is m.version 22 | mod.installed = m.version 23 | else 24 | mod.installed = color m.version, "red" 25 | mod.site = color " "+mod.site, "green+bold" 26 | ownedMods.push mod 27 | 28 | mods = mods.filter (mod) -> 29 | for owned in ownedMods 30 | if owned.name is mod.name 31 | return false 32 | true 33 | 34 | mods = if installed 35 | logger.green " The following is a list of the Mimosa modules currently installed.\n" 36 | ownedMods 37 | else 38 | logger.green " The following is a list of the Mimosa modules in NPM.\n" 39 | ownedMods.concat mods 40 | 41 | gap = new Array(longestModName-2).join(' ') 42 | 43 | logger.blue " Name" + gap + "Version Updated Have? Website" 44 | fields = [ 45 | ['name',longestModName + 2], 46 | ['version',13], 47 | ['updated',22], 48 | ['installed',13], 49 | ['site',65], 50 | ] 51 | for mod in mods 52 | headline = " " 53 | for field in fields 54 | name = field[0] 55 | spacing = field[1] 56 | data = mod[name] 57 | headline += data 58 | spaces = spacing - (data + "").length 59 | if spaces < 1 then spaces = 2 60 | headline += new Array(spaces).join(' ') 61 | 62 | logger.green headline 63 | 64 | if verbose 65 | console.log " Description: #{mod.desc}" 66 | if mod.dependencies? 67 | asArray = for dep, version of mod.dependencies 68 | "#{dep}@#{version}" 69 | console.log " Dependencies: #{asArray.join(', ')}" 70 | console.log "" 71 | 72 | unless verbose 73 | logger.green "\n To view more module details, execute \'mimosa mod:search -v\' for \'verbose\' logging." 74 | 75 | unless installed 76 | logger.green "\n To view only the installed Mimosa modules, add the [-i/--installed] flag: \'mimosa mod:list -i\'" 77 | 78 | logger.green " \n Install modules by executing \'mimosa mod:install <>\' \n\n" 79 | 80 | process.exit 0 81 | 82 | list = (opts) -> 83 | if opts.mdebug 84 | opts.debug = true 85 | logger.setDebug() 86 | process.env.DEBUG = true 87 | 88 | logger.green "\n Searching Mimosa modules...\n" 89 | 90 | childProcess.exec 'npm config get proxy', (error, stdout, stderr) -> 91 | options = { 92 | 'uri': 'http://mimosa-data.herokuapp.com/modules' 93 | } 94 | proxy = stdout.replace /(\r\n|\n|\r)/gm, '' 95 | if !error && proxy != 'null' 96 | options.proxy = proxy 97 | request = require 'request' 98 | request options, (error, client, response) -> 99 | if error != null 100 | console.log(error) 101 | return 102 | mods = JSON.parse response 103 | printResults mods, opts 104 | 105 | register = (program, callback) -> 106 | program 107 | .command('mod:list') 108 | .option("-D, --mdebug", "run in debug mode") 109 | .option("-v, --verbose", "list more details about each module") 110 | .option("-i, --installed", "Show just those modules that are currently installed.") 111 | .description("get list of all mimosa modules in NPM") 112 | .action(callback) 113 | .on '--help', => 114 | logger.green(' The mod:list command will search npm for all packages and return a list') 115 | logger.green(' of Mimosa modules that are available for install. This command will also') 116 | logger.green(' inform you if your project has out of date modules.') 117 | logger.blue( '\n $ mimosa mod:list\n') 118 | logger.green(' Pass an \'installed\' flag to only see the modules you have installed.') 119 | logger.blue( '\n $ mimosa mod:list --installed\n') 120 | logger.blue( '\n $ mimosa mod:list -i\n') 121 | logger.green(' Pass a \'verbose\' flag to get additional information about each module') 122 | logger.blue( '\n $ mimosa mod:list --verbose\n') 123 | logger.blue( '\n $ mimosa mod:list -v\n') 124 | 125 | module.exports = (program) -> 126 | register program, list -------------------------------------------------------------------------------- /lib/command/build.js: -------------------------------------------------------------------------------- 1 | var build, register; 2 | 3 | build = function(opts) { 4 | var Cleaner, Watcher, configurer, fileUtils, logger; 5 | logger = require('logmimosa'); 6 | configurer = require('../util/configurer'); 7 | Watcher = require('../util/watcher'); 8 | Cleaner = require('../util/cleaner'); 9 | if (opts.mdebug) { 10 | opts.debug = true; 11 | logger.setDebug(); 12 | process.env.DEBUG = true; 13 | } 14 | logger.info("Beginning build"); 15 | opts.build = true; 16 | if (opts.cleanall) { 17 | fileUtils = require('../util/file'); 18 | fileUtils.removeDotMimosa(); 19 | logger.info("Removed .mimosa directory."); 20 | } 21 | return configurer(opts, function(config, modules) { 22 | var doBuild; 23 | doBuild = function() { 24 | config.isClean = false; 25 | return new Watcher(config, modules, false, function() { 26 | logger.success("Finished build"); 27 | return process.exit(0); 28 | }); 29 | }; 30 | config.isClean = true; 31 | return new Cleaner(config, modules, doBuild); 32 | }); 33 | }; 34 | 35 | register = function(program, callback) { 36 | return program.command('build').description("make a single pass through assets, compile them, and optionally package them").option("-C, --cleanall", "deletes the .mimosa directory before building").option("-o, --optimize", "run r.js optimization after building").option("-m, --minify", "minify each asset as it is compiled using uglify").option("-p, --package", "use modules that perform packaging").option("-i --install", "use modules that perform installation").option("-e --errorout", "cease processing and error out if the build encounters critical errors").option("-P, --profile ", "select a mimosa profile").option("-D, --mdebug", "run in debug mode").action(callback).on('--help', function() { 37 | var logger; 38 | logger = require('logmimosa'); 39 | logger.green(' The build command will make a single pass through your assets and bulid any that need building'); 40 | logger.green(' and then exit.'); 41 | logger.blue('\n $ mimosa build\n'); 42 | logger.green(' Pass a \'cleanall\' flag and Mimosa remove the .mimosa directory before building. The \'build\' command'); 43 | logger.green(' already runs a code clean.'); 44 | logger.blue('\n $ mimosa build --cleanall'); 45 | logger.blue(' $ mimosa build -C\n'); 46 | logger.green(' Pass an \'optimize\' flag and Mimosa will use requirejs to optimize your assets and provide you'); 47 | logger.green(' with single files for the named requirejs modules. '); 48 | logger.blue('\n $ mimosa build --optimize'); 49 | logger.blue(' $ mimosa build -o\n'); 50 | logger.green(' Pass an \'minify\' flag and Mimosa will use uglify to minify/compress your assets when they are compiled.'); 51 | logger.green(' You can provide exclude, files you do not want to minify, in the mimosa-config. If you run \'minify\' '); 52 | logger.green(' and \'optimize\' at the same time, optimize will not run the uglify portion of its processing which occurs as'); 53 | logger.green(' a separate step after everything has compiled and does not allow control of what gets uglified. Use \'optimize\''); 54 | logger.green(' and \'minify\' together if you need to control which files get mangled by uglify (because sometimes uglify'); 55 | logger.green(' can break them) but you still want everything together in a single file.'); 56 | logger.blue('\n $ mimosa watch --minify'); 57 | logger.blue(' $ mimosa watch -m\n'); 58 | logger.green(' Pass a \'package\' flag if you have installed a module (like mimosa-web-package) that is capable of'); 59 | logger.green(' executing packaging functionality for you after the building of assets is complete.'); 60 | logger.blue('\n $ mimosa build --package'); 61 | logger.blue(' $ mimosa build -p\n'); 62 | logger.green(' Pass a \'install\' flag if you have installed a module that is capable of executing'); 63 | logger.green(' installation functionality for you after the building/packaging of assets is complete.'); 64 | logger.blue('\n $ mimosa build --install'); 65 | logger.blue(' $ mimosa build -i\n'); 66 | logger.green(' Pass a \'errorout\' flag if you want the build to stop as soon as a critical error occurs.'); 67 | logger.blue('\n $ mimosa build --errorout'); 68 | logger.blue(' $ mimosa build -e\n'); 69 | logger.green(' Pass a \'profile\' flag and the name of a Mimosa profile to run with.'); 70 | logger.blue('\n $ mimosa build --profile build'); 71 | return logger.blue(' $ mimosa build -P build'); 72 | }); 73 | }; 74 | 75 | module.exports = function(program) { 76 | return register(program, build); 77 | }; 78 | -------------------------------------------------------------------------------- /src/modules/compilers/javascript.coffee: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | path = require 'path' 4 | fs = require 'fs' 5 | 6 | logger = require 'logmimosa' 7 | 8 | fileUtils = require '../../util/file' 9 | 10 | _genMapFileName = (config, file) -> 11 | extName = path.extname file.inputFileName 12 | file.inputFileName.replace(extName, ".js.map").replace(config.watch.sourceDir, config.watch.compiledDir) 13 | 14 | _genSourceName = (config, file) -> 15 | file.inputFileName.replace(config.watch.sourceDir, config.watch.compiledDir) + ".src" 16 | 17 | _cleanUpSourceMaps = (config, options, next) -> 18 | i = 0 19 | done = -> 20 | next() if ++i is 2 21 | 22 | options.files.forEach (file) -> 23 | mapFileName = _genMapFileName(config, file) 24 | sourceName = _genSourceName(config, file) 25 | [mapFileName, sourceName].forEach (f) -> 26 | fs.exists f, (exists) -> 27 | if exists 28 | fs.unlink f, (err) -> 29 | if err 30 | logger.error "Error deleting file [[ #{f} ]], #{err}" 31 | else 32 | if logger.isDebug() 33 | logger.debug "Deleted file [[ #{f} ]]" 34 | done() 35 | else 36 | done() 37 | 38 | _cleanUpSourceMapsRegister = (register, extensions) -> 39 | # register remove only if sourcemap as remove is watch workflow 40 | register ['remove'], 'delete', _cleanUpSourceMaps, extensions 41 | 42 | # register clean regardless to ensure any existing source maps are removed during build/clean 43 | register ['cleanFile'], 'delete', _cleanUpSourceMaps, extensions 44 | 45 | 46 | module.exports = class JSCompiler 47 | 48 | constructor: (config, @compiler) -> 49 | 50 | registration: (config, register) -> 51 | exts = @compiler.extensions(config) 52 | 53 | register( 54 | ['add','update','remove','cleanFile','buildFile'], 55 | 'init', 56 | @_determineOutputFile, 57 | exts) 58 | 59 | register( 60 | ['add','update','buildFile'], 61 | 'compile', 62 | @_compile, 63 | exts) 64 | 65 | if @compiler.cleanUpSourceMaps 66 | _cleanUpSourceMapsRegister register, exts 67 | 68 | _determineOutputFile: (config, options, next) -> 69 | if options.files and options.files.length 70 | options.destinationFile = (fileName) -> 71 | baseCompDir = fileName.replace(config.watch.sourceDir, config.watch.compiledDir) 72 | baseCompDir.substring(0, baseCompDir.lastIndexOf(".")) + ".js" 73 | 74 | options.files.forEach (file) -> 75 | file.outputFileName = options.destinationFile( file.inputFileName ) 76 | 77 | next() 78 | 79 | _compile: (config, options, next) => 80 | return next() unless options.files?.length 81 | 82 | i = 0 83 | newFiles = [] 84 | 85 | whenDone = options.files.length 86 | done = -> 87 | if ++i is whenDone 88 | options.files = newFiles 89 | next() 90 | 91 | options.files.forEach (file) => 92 | 93 | if logger.isDebug() 94 | logger.debug "Calling compiler function for compiler [[ " + @compiler.name + " ]]" 95 | 96 | file.isVendor = options.isVendor 97 | 98 | @compiler.compile config, file, (err, output, compilerConfig, sourceMap) => 99 | if err 100 | logger.error "File [[ #{file.inputFileName} ]] failed compile. Reason: #{err}", {exitIfBuild:true} 101 | else 102 | if sourceMap 103 | 104 | if compilerConfig.sourceMapDynamic 105 | sourceMap = JSON.parse(sourceMap) 106 | sourceMap.sources[0] = file.inputFileName 107 | sourceMap.sourcesContent = [file.inputFileText]; 108 | sourceMap.file = file.outputFileName; 109 | 110 | base64SourceMap = new Buffer(JSON.stringify(sourceMap)).toString('base64') 111 | datauri = 'data:application/json;base64,' + base64SourceMap 112 | if compilerConfig.sourceMapConditional 113 | output = "#{output}\n//@ sourceMappingURL=#{datauri}\n" 114 | else 115 | output = "#{output}\n//# sourceMappingURL=#{datauri}\n" 116 | 117 | 118 | else 119 | whenDone += 2 120 | # writing source 121 | sourceName = _genSourceName(config, file) 122 | fileUtils.writeFile sourceName, file.inputFileText, (err) -> 123 | if err 124 | logger.error "Error writing source file [[ #{sourceName} ]], #{err}" 125 | done() 126 | 127 | # writing map 128 | file.sourceMap = sourceMap 129 | file.sourceMapName = _genMapFileName(config, file) 130 | fileUtils.writeFile file.sourceMapName, sourceMap, (err) -> 131 | if err 132 | logger.error "Error writing map file [[ #{file.sourceMapName} ]], #{err}" 133 | done() 134 | 135 | if compilerConfig.sourceMapConditional 136 | output = "#{output}\n//@ sourceMappingURL=#{path.basename(file.sourceMapName)}\n" 137 | else 138 | output = "#{output}\n//# sourceMappingURL=#{path.basename(file.sourceMapName)}\n" 139 | 140 | file.outputFileText = output 141 | newFiles.push file 142 | 143 | done() -------------------------------------------------------------------------------- /src/command/watch.coffee: -------------------------------------------------------------------------------- 1 | watch = (opts) -> 2 | fs = require 'fs' 3 | logger = require 'logmimosa' 4 | configurer = require '../util/configurer' 5 | 6 | if opts.mdebug 7 | opts.debug = true 8 | logger.setDebug() 9 | process.env.DEBUG = true 10 | 11 | opts.watch = true 12 | 13 | if opts.cleanall 14 | opts.clean = true 15 | fileUtils = require '../util/file' 16 | fileUtils.removeDotMimosa() 17 | logger.info("Removed .mimosa directory.") 18 | 19 | configurer opts, (config, modules) -> 20 | instWatcher = -> 21 | config.isClean = false 22 | Watcher = require '../util/watcher' 23 | new Watcher(config, modules, true) 24 | 25 | if opts.delete 26 | if fs.existsSync config.watch.compiledDir 27 | wrench = require 'wrench' 28 | wrench.rmdirSyncRecursive config.watch.compiledDir 29 | logger.success "[[ #{config.watch.compiledDir} ]] has been removed" 30 | 31 | instWatcher() 32 | else if opts.clean or config.needsClean 33 | config.isClean = true 34 | Cleaner = require '../util/cleaner' 35 | new Cleaner(config, modules, instWatcher) 36 | else 37 | instWatcher() 38 | 39 | register = (program, callback) -> 40 | program 41 | .command('watch') 42 | .description("watch the filesystem and compile assets") 43 | .option("-s, --server", "run a server that will serve up the assets in the compiled directory") 44 | .option("-o, --optimize", "run require.js optimization after each js file compile") 45 | .option("-m, --minify", "minify each asset as it is compiled using uglify") 46 | .option("-c, --clean", "clean the compiled directory before the watch begins, this forces a recompile of all your assets") 47 | .option("-C, --cleanall", "clean the compiled directory and the .mimosa directory before the watch begins") 48 | .option("-d, --delete", "remove the compiled directory entirely before starting") 49 | .option("-P, --profile ", "select a mimosa profile") 50 | .option("-D, --mdebug", "run in debug mode") 51 | .action(callback) 52 | .on '--help', -> 53 | logger = require 'logmimosa' 54 | logger.green(' The watch command will observe your source directory and compile or copy your assets when they change.') 55 | logger.green(' When the watch command starts up, it will make an initial pass through your assets and compile or copy') 56 | logger.green(' any assets that are newer then their companion compiled assets in the compiled directory. The watch') 57 | logger.green(' command will remain running when executed, and must be terminated with CNTL-C.') 58 | logger.blue( '\n $ mimosa watch\n') 59 | logger.green(' Pass a \'server\' flag and Mimosa will start-up a server that will serve up the assets Mimosa compiles.') 60 | logger.green(' You have the opportunity, via Mimosa\'s config, to provide Mimosa a hook to your own server if you have') 61 | logger.green(' one. If you do not have a server, Mimosa will use an embedded server to serve up the assets. Server') 62 | logger.green(' configuration options and explanations can be found in the \'server\' settings in the mimosa-config.') 63 | logger.blue( '\n $ mimosa watch --server') 64 | logger.blue( ' $ mimosa watch -s\n') 65 | logger.green(' Pass a \'clean\' flag and Mimosa will first clean out all your assets before starting the watch. This') 66 | logger.green(' has the effect of forcing a recompile of all of your assets.') 67 | logger.blue( '\n $ mimosa watch --clean') 68 | logger.blue( ' $ mimosa watch -c\n') 69 | logger.green(' Pass a \'cleanall\' flag and Mimosa will first clean out all your assets before starting the watch. This') 70 | logger.green(' has the effect of forcing a recompile of all of your assets. This clean will also remove the .mimosa directory.') 71 | logger.blue( '\n $ mimosa watch --cleanall') 72 | logger.blue( ' $ mimosa watch -C\n') 73 | logger.green(' Pass a \'delete\' flag and Mimosa will first remove the compiled directory before starting the watch.') 74 | logger.blue( '\n $ mimosa watch --delete') 75 | logger.blue( ' $ mimosa watch -d\n') 76 | logger.green(' Pass an \'optimize\' flag and Mimosa will use requirejs to optimize your assets and provide you with') 77 | logger.green(' single files for the named requirejs modules. It will do this any time a JavaScript asset is changed.') 78 | logger.blue( '\n $ mimosa watch --optimize') 79 | logger.blue( ' $ mimosa watch -o\n') 80 | logger.green(' Pass an \'minify\' flag and Mimosa will use uglify to minify/compress your assets as they are compiled.') 81 | logger.green(' You can provide exclude, files you do not want to minify, in the mimosa-config. If you run \'minify\' ') 82 | logger.green(' and \'optimize\' at the same time, optimize will not run the uglify portion of its processing which occurs as') 83 | logger.green(' a separate step after everything has compiled and does not allow control of what gets uglified. Use \'optimize\'') 84 | logger.green(' and \'minify\' together if you need to control which files get mangled by uglify (because sometimes uglify') 85 | logger.green(' can break them) but you still want everything together in a single file.') 86 | logger.blue( '\n $ mimosa watch --minify') 87 | logger.blue( ' $ mimosa watch -m\n') 88 | logger.green(' Pass a \'profile\' flag and the name of a Mimosa profile to run with.') 89 | logger.blue( '\n $ mimosa clean --profile build') 90 | logger.blue( ' $ mimosa clean -P build') 91 | 92 | module.exports = (program) -> 93 | register(program, watch) -------------------------------------------------------------------------------- /lib/command/watch.js: -------------------------------------------------------------------------------- 1 | var register, watch; 2 | 3 | watch = function(opts) { 4 | var configurer, fileUtils, fs, logger; 5 | fs = require('fs'); 6 | logger = require('logmimosa'); 7 | configurer = require('../util/configurer'); 8 | if (opts.mdebug) { 9 | opts.debug = true; 10 | logger.setDebug(); 11 | process.env.DEBUG = true; 12 | } 13 | opts.watch = true; 14 | if (opts.cleanall) { 15 | opts.clean = true; 16 | fileUtils = require('../util/file'); 17 | fileUtils.removeDotMimosa(); 18 | logger.info("Removed .mimosa directory."); 19 | } 20 | return configurer(opts, function(config, modules) { 21 | var Cleaner, instWatcher, wrench; 22 | instWatcher = function() { 23 | var Watcher; 24 | config.isClean = false; 25 | Watcher = require('../util/watcher'); 26 | return new Watcher(config, modules, true); 27 | }; 28 | if (opts["delete"]) { 29 | if (fs.existsSync(config.watch.compiledDir)) { 30 | wrench = require('wrench'); 31 | wrench.rmdirSyncRecursive(config.watch.compiledDir); 32 | logger.success("[[ " + config.watch.compiledDir + " ]] has been removed"); 33 | return instWatcher(); 34 | } 35 | } else if (opts.clean || config.needsClean) { 36 | config.isClean = true; 37 | Cleaner = require('../util/cleaner'); 38 | return new Cleaner(config, modules, instWatcher); 39 | } else { 40 | return instWatcher(); 41 | } 42 | }); 43 | }; 44 | 45 | register = function(program, callback) { 46 | return program.command('watch').description("watch the filesystem and compile assets").option("-s, --server", "run a server that will serve up the assets in the compiled directory").option("-o, --optimize", "run require.js optimization after each js file compile").option("-m, --minify", "minify each asset as it is compiled using uglify").option("-c, --clean", "clean the compiled directory before the watch begins, this forces a recompile of all your assets").option("-C, --cleanall", "clean the compiled directory and the .mimosa directory before the watch begins").option("-d, --delete", "remove the compiled directory entirely before starting").option("-P, --profile ", "select a mimosa profile").option("-D, --mdebug", "run in debug mode").action(callback).on('--help', function() { 47 | var logger; 48 | logger = require('logmimosa'); 49 | logger.green(' The watch command will observe your source directory and compile or copy your assets when they change.'); 50 | logger.green(' When the watch command starts up, it will make an initial pass through your assets and compile or copy'); 51 | logger.green(' any assets that are newer then their companion compiled assets in the compiled directory. The watch'); 52 | logger.green(' command will remain running when executed, and must be terminated with CNTL-C.'); 53 | logger.blue('\n $ mimosa watch\n'); 54 | logger.green(' Pass a \'server\' flag and Mimosa will start-up a server that will serve up the assets Mimosa compiles.'); 55 | logger.green(' You have the opportunity, via Mimosa\'s config, to provide Mimosa a hook to your own server if you have'); 56 | logger.green(' one. If you do not have a server, Mimosa will use an embedded server to serve up the assets. Server'); 57 | logger.green(' configuration options and explanations can be found in the \'server\' settings in the mimosa-config.'); 58 | logger.blue('\n $ mimosa watch --server'); 59 | logger.blue(' $ mimosa watch -s\n'); 60 | logger.green(' Pass a \'clean\' flag and Mimosa will first clean out all your assets before starting the watch. This'); 61 | logger.green(' has the effect of forcing a recompile of all of your assets.'); 62 | logger.blue('\n $ mimosa watch --clean'); 63 | logger.blue(' $ mimosa watch -c\n'); 64 | logger.green(' Pass a \'cleanall\' flag and Mimosa will first clean out all your assets before starting the watch. This'); 65 | logger.green(' has the effect of forcing a recompile of all of your assets. This clean will also remove the .mimosa directory.'); 66 | logger.blue('\n $ mimosa watch --cleanall'); 67 | logger.blue(' $ mimosa watch -C\n'); 68 | logger.green(' Pass a \'delete\' flag and Mimosa will first remove the compiled directory before starting the watch.'); 69 | logger.blue('\n $ mimosa watch --delete'); 70 | logger.blue(' $ mimosa watch -d\n'); 71 | logger.green(' Pass an \'optimize\' flag and Mimosa will use requirejs to optimize your assets and provide you with'); 72 | logger.green(' single files for the named requirejs modules. It will do this any time a JavaScript asset is changed.'); 73 | logger.blue('\n $ mimosa watch --optimize'); 74 | logger.blue(' $ mimosa watch -o\n'); 75 | logger.green(' Pass an \'minify\' flag and Mimosa will use uglify to minify/compress your assets as they are compiled.'); 76 | logger.green(' You can provide exclude, files you do not want to minify, in the mimosa-config. If you run \'minify\' '); 77 | logger.green(' and \'optimize\' at the same time, optimize will not run the uglify portion of its processing which occurs as'); 78 | logger.green(' a separate step after everything has compiled and does not allow control of what gets uglified. Use \'optimize\''); 79 | logger.green(' and \'minify\' together if you need to control which files get mangled by uglify (because sometimes uglify'); 80 | logger.green(' can break them) but you still want everything together in a single file.'); 81 | logger.blue('\n $ mimosa watch --minify'); 82 | logger.blue(' $ mimosa watch -m\n'); 83 | logger.green(' Pass a \'profile\' flag and the name of a Mimosa profile to run with.'); 84 | logger.blue('\n $ mimosa clean --profile build'); 85 | return logger.blue(' $ mimosa clean -P build'); 86 | }); 87 | }; 88 | 89 | module.exports = function(program) { 90 | return register(program, watch); 91 | }; 92 | -------------------------------------------------------------------------------- /lib/command/module/list.js: -------------------------------------------------------------------------------- 1 | var childProcess, color, http, list, logger, moduleMetadata, printResults, register; 2 | 3 | http = require('http'); 4 | 5 | color = require('ansi-color').set; 6 | 7 | logger = require('logmimosa'); 8 | 9 | childProcess = require('child_process'); 10 | 11 | moduleMetadata = require('../../modules').installedMetadata; 12 | 13 | printResults = function(mods, opts) { 14 | var asArray, data, dep, field, fields, gap, headline, installed, longestModName, m, mod, name, ownedMods, spaces, spacing, verbose, version, _i, _j, _k, _l, _len, _len1, _len2, _len3; 15 | verbose = opts.verbose; 16 | installed = opts.installed; 17 | longestModName = 0; 18 | ownedMods = []; 19 | for (_i = 0, _len = mods.length; _i < _len; _i++) { 20 | mod = mods[_i]; 21 | if (mod.name.length > longestModName) { 22 | longestModName = mod.name.length; 23 | } 24 | mod.installed = ''; 25 | for (_j = 0, _len1 = moduleMetadata.length; _j < _len1; _j++) { 26 | m = moduleMetadata[_j]; 27 | if (m.name === mod.name) { 28 | if (mod.version === m.version) { 29 | mod.installed = m.version; 30 | } else { 31 | mod.installed = color(m.version, "red"); 32 | mod.site = color(" " + mod.site, "green+bold"); 33 | } 34 | ownedMods.push(mod); 35 | } 36 | } 37 | } 38 | mods = mods.filter(function(mod) { 39 | var owned, _k, _len2; 40 | for (_k = 0, _len2 = ownedMods.length; _k < _len2; _k++) { 41 | owned = ownedMods[_k]; 42 | if (owned.name === mod.name) { 43 | return false; 44 | } 45 | } 46 | return true; 47 | }); 48 | mods = installed ? (logger.green(" The following is a list of the Mimosa modules currently installed.\n"), ownedMods) : (logger.green(" The following is a list of the Mimosa modules in NPM.\n"), ownedMods.concat(mods)); 49 | gap = new Array(longestModName - 2).join(' '); 50 | logger.blue(" Name" + gap + "Version Updated Have? Website"); 51 | fields = [['name', longestModName + 2], ['version', 13], ['updated', 22], ['installed', 13], ['site', 65]]; 52 | for (_k = 0, _len2 = mods.length; _k < _len2; _k++) { 53 | mod = mods[_k]; 54 | headline = " "; 55 | for (_l = 0, _len3 = fields.length; _l < _len3; _l++) { 56 | field = fields[_l]; 57 | name = field[0]; 58 | spacing = field[1]; 59 | data = mod[name]; 60 | headline += data; 61 | spaces = spacing - (data + "").length; 62 | if (spaces < 1) { 63 | spaces = 2; 64 | } 65 | headline += new Array(spaces).join(' '); 66 | } 67 | logger.green(headline); 68 | if (verbose) { 69 | console.log(" Description: " + mod.desc); 70 | if (mod.dependencies != null) { 71 | asArray = (function() { 72 | var _ref, _results; 73 | _ref = mod.dependencies; 74 | _results = []; 75 | for (dep in _ref) { 76 | version = _ref[dep]; 77 | _results.push("" + dep + "@" + version); 78 | } 79 | return _results; 80 | })(); 81 | console.log(" Dependencies: " + (asArray.join(', '))); 82 | } 83 | console.log(""); 84 | } 85 | } 86 | if (!verbose) { 87 | logger.green("\n To view more module details, execute \'mimosa mod:search -v\' for \'verbose\' logging."); 88 | } 89 | if (!installed) { 90 | logger.green("\n To view only the installed Mimosa modules, add the [-i/--installed] flag: \'mimosa mod:list -i\'"); 91 | } 92 | logger.green(" \n Install modules by executing \'mimosa mod:install <>\' \n\n"); 93 | return process.exit(0); 94 | }; 95 | 96 | list = function(opts) { 97 | if (opts.mdebug) { 98 | opts.debug = true; 99 | logger.setDebug(); 100 | process.env.DEBUG = true; 101 | } 102 | logger.green("\n Searching Mimosa modules...\n"); 103 | return childProcess.exec('npm config get proxy', function(error, stdout, stderr) { 104 | var options, proxy, request; 105 | options = { 106 | 'uri': 'http://mimosa-data.herokuapp.com/modules' 107 | }; 108 | proxy = stdout.replace(/(\r\n|\n|\r)/gm, ''); 109 | if (!error && proxy !== 'null') { 110 | options.proxy = proxy; 111 | } 112 | request = require('request'); 113 | return request(options, function(error, client, response) { 114 | var mods; 115 | if (error !== null) { 116 | console.log(error); 117 | return; 118 | } 119 | mods = JSON.parse(response); 120 | return printResults(mods, opts); 121 | }); 122 | }); 123 | }; 124 | 125 | register = function(program, callback) { 126 | var _this = this; 127 | return program.command('mod:list').option("-D, --mdebug", "run in debug mode").option("-v, --verbose", "list more details about each module").option("-i, --installed", "Show just those modules that are currently installed.").description("get list of all mimosa modules in NPM").action(callback).on('--help', function() { 128 | logger.green(' The mod:list command will search npm for all packages and return a list'); 129 | logger.green(' of Mimosa modules that are available for install. This command will also'); 130 | logger.green(' inform you if your project has out of date modules.'); 131 | logger.blue('\n $ mimosa mod:list\n'); 132 | logger.green(' Pass an \'installed\' flag to only see the modules you have installed.'); 133 | logger.blue('\n $ mimosa mod:list --installed\n'); 134 | logger.blue('\n $ mimosa mod:list -i\n'); 135 | logger.green(' Pass a \'verbose\' flag to get additional information about each module'); 136 | logger.blue('\n $ mimosa mod:list --verbose\n'); 137 | return logger.blue('\n $ mimosa mod:list -v\n'); 138 | }); 139 | }; 140 | 141 | module.exports = function(program) { 142 | return register(program, list); 143 | }; 144 | -------------------------------------------------------------------------------- /lib/util/file.js: -------------------------------------------------------------------------------- 1 | var fs, isCSS, isJavascript, isVendorCSS, isVendorJS, logger, mkdirRecursive, path; 2 | 3 | path = require('path'); 4 | 5 | fs = require('fs'); 6 | 7 | logger = require('logmimosa'); 8 | 9 | exports.removeDotMimosa = function() { 10 | var dotMimosaDir, wrench; 11 | dotMimosaDir = path.join(process.cwd(), ".mimosa"); 12 | if (fs.existsSync(dotMimosaDir)) { 13 | wrench = require('wrench'); 14 | return wrench.rmdirSyncRecursive(dotMimosaDir); 15 | } 16 | }; 17 | 18 | exports.isCSS = isCSS = function(fileName) { 19 | return path.extname(fileName) === ".css"; 20 | }; 21 | 22 | exports.isJavascript = isJavascript = function(fileName) { 23 | return path.extname(fileName) === ".js"; 24 | }; 25 | 26 | exports.isVendorCSS = isVendorCSS = function(config, fileName) { 27 | return fileName.indexOf(config.vendor.stylesheets) === 0; 28 | }; 29 | 30 | exports.isVendorJS = isVendorJS = function(config, fileName) { 31 | return fileName.indexOf(config.vendor.javascripts) === 0; 32 | }; 33 | 34 | exports.mkdirRecursive = mkdirRecursive = function(p, made) { 35 | var err, err2, stat; 36 | if (!made) { 37 | made = null; 38 | } 39 | p = path.resolve(p); 40 | try { 41 | fs.mkdirSync(p); 42 | made = made || p; 43 | } catch (_error) { 44 | err = _error; 45 | if (err.code === 'ENOENT') { 46 | made = mkdirRecursive(path.dirname(p), made); 47 | mkdirRecursive(p, made); 48 | } else if (err.code === 'EEXIST') { 49 | try { 50 | stat = fs.statSync(p); 51 | } catch (_error) { 52 | err2 = _error; 53 | throw err; 54 | } 55 | if (!stat.isDirectory()) { 56 | throw err; 57 | } 58 | } else { 59 | throw err; 60 | } 61 | } 62 | return made; 63 | }; 64 | 65 | exports.writeFile = function(fileName, content, callback) { 66 | var dirname; 67 | dirname = path.dirname(fileName); 68 | if (!fs.existsSync(dirname)) { 69 | mkdirRecursive(dirname); 70 | } 71 | return fs.writeFile(fileName, content, "utf8", function(err) { 72 | var error; 73 | error = err != null ? "Failed to write file: " + fileName + ", " + err : void 0; 74 | return callback(error); 75 | }); 76 | }; 77 | 78 | exports.isFirstFileNewer = function(file1, file2, cb) { 79 | if (file1 == null) { 80 | return cb(false); 81 | } 82 | if (file2 == null) { 83 | return cb(true); 84 | } 85 | return fs.exists(file1, function(exists1) { 86 | if (!exists1) { 87 | logger.warn("Detected change with file [[ " + file1 + " ]] but is no longer present."); 88 | return cb(false); 89 | } 90 | return fs.exists(file2, function(exists2) { 91 | if (!exists2) { 92 | logger.debug("File missing, so is new file [[ " + file2 + " ]]"); 93 | return cb(true); 94 | } 95 | return fs.stat(file2, function(err, stats2) { 96 | return fs.stat(file1, function(err, stats1) { 97 | if (!((stats1 != null) && (stats2 != null))) { 98 | logger.debug("Somehow a file went missing [[ " + stats1 + " ]], [[ " + stats2 + " ]] "); 99 | return cb(false); 100 | } 101 | if (stats1.mtime > stats2.mtime) { 102 | return cb(true); 103 | } else { 104 | return cb(false); 105 | } 106 | }); 107 | }); 108 | }); 109 | }); 110 | }; 111 | 112 | exports.readdirSyncRecursive = function(baseDir, excludes, excludeRegex, ignoreDirectories) { 113 | var readdirSyncRecursive; 114 | if (excludes == null) { 115 | excludes = []; 116 | } 117 | if (ignoreDirectories == null) { 118 | ignoreDirectories = false; 119 | } 120 | baseDir = baseDir.replace(/\/$/, ''); 121 | readdirSyncRecursive = function(baseDir) { 122 | var curFiles, files, nextDirs; 123 | curFiles = fs.readdirSync(baseDir).map(function(f) { 124 | return path.join(baseDir, f); 125 | }); 126 | if (excludes.length > 0) { 127 | curFiles = curFiles.filter(function(f) { 128 | var exclude, _i, _len; 129 | for (_i = 0, _len = excludes.length; _i < _len; _i++) { 130 | exclude = excludes[_i]; 131 | if (f === exclude || f.indexOf(exclude) === 0) { 132 | return false; 133 | } 134 | } 135 | return true; 136 | }); 137 | } 138 | if (excludeRegex) { 139 | curFiles = curFiles.filter(function(f) { 140 | return !f.match(excludeRegex); 141 | }); 142 | } 143 | nextDirs = curFiles.filter(function(fname) { 144 | return fs.statSync(fname).isDirectory(); 145 | }); 146 | if (ignoreDirectories) { 147 | curFiles = curFiles.filter(function(fname) { 148 | return fs.statSync(fname).isFile(); 149 | }); 150 | } 151 | files = curFiles; 152 | while (nextDirs.length) { 153 | files = files.concat(readdirSyncRecursive(nextDirs.shift())); 154 | } 155 | return files; 156 | }; 157 | return readdirSyncRecursive(baseDir); 158 | }; 159 | 160 | exports.setFileFlags = function(config, options) { 161 | var ext, exts; 162 | exts = config.extensions; 163 | ext = options.extension; 164 | options.isJavascript = false; 165 | options.isCSS = false; 166 | options.isVendor = false; 167 | options.isJSNotVendor = false; 168 | options.isCopy = false; 169 | if (exts.template.indexOf(ext) > -1) { 170 | options.isTemplate = true; 171 | options.isJavascript = true; 172 | options.isJSNotVendor = true; 173 | } 174 | if (exts.copy.indexOf(ext) > -1) { 175 | options.isCopy = true; 176 | } 177 | if (exts.javascript.indexOf(ext) > -1 || (options.inputFile && isJavascript(options.inputFile))) { 178 | options.isJavascript = true; 179 | if (options.inputFile) { 180 | options.isVendor = isVendorJS(config, options.inputFile); 181 | options.isJSNotVendor = !options.isVendor; 182 | } 183 | } 184 | if (exts.css.indexOf(ext) > -1 || (options.inputFile && isCSS(options.inputFile))) { 185 | options.isCSS = true; 186 | if (options.inputFile) { 187 | return options.isVendor = isVendorCSS(config, options.inputFile); 188 | } 189 | } 190 | }; 191 | -------------------------------------------------------------------------------- /src/command/module/install.coffee: -------------------------------------------------------------------------------- 1 | path = require 'path' 2 | fs = require 'fs' 3 | {exec} = require 'child_process' 4 | 5 | wrench = require 'wrench' 6 | logger = require 'logmimosa' 7 | 8 | mimosaPath = path.join __dirname, '..', '..', '..' 9 | currentDir = process.cwd() 10 | 11 | install = (name, opts) -> 12 | if opts.mdebug 13 | opts.debug = true 14 | logger.setDebug() 15 | process.env.DEBUG = true 16 | 17 | if name? 18 | unless name? and name.indexOf('mimosa-') is 0 19 | return logger.error "Can only install 'mimosa-' prefixed modules with mod:install (ex: mimosa-server)." 20 | 21 | dirName = if name.indexOf('@') > 7 22 | name.substring(0, name.indexOf('@')) 23 | else 24 | name 25 | 26 | _doNPMInstall name, dirName 27 | else 28 | 29 | try 30 | pack = require path.join currentDir, 'package.json' 31 | catch err 32 | return logger.error "Unable to find package.json, or badly formatted: #{err}" 33 | 34 | unless pack.name? and pack.version? 35 | return logger.error "package.json missing either name or version" 36 | 37 | unless pack.name.indexOf('mimosa-') is 0 38 | return logger.error "package.json name is [[ #{pack.name} ]]. Can only install 'mimosa-' prefixed modules with mod:install (ex: mimosa-server). " 39 | 40 | _doLocalInstall() 41 | 42 | _installModule = (name, done) -> 43 | logger.info "Installing module [[ #{name} ]] into Mimosa." 44 | installString = "npm install \"#{name}\" --save" 45 | exec installString, (err, sout, serr) => 46 | if err 47 | logger.error "Error installing module" 48 | logger.error err 49 | else 50 | console.log sout 51 | console.log serr 52 | logger.success "Install of [[ #{name} ]] successful" 53 | 54 | logger.debug "NPM INSTALL standard out\n#{sout}" 55 | logger.debug "NPM INSTALL standard err\n#{serr}" 56 | 57 | done(err) 58 | 59 | ### 60 | NPM Install 61 | ### 62 | 63 | _doNPMInstall = (name, dirName) -> 64 | process.chdir mimosaPath 65 | oldVersion = _prepareForNPMInstall dirName 66 | _installModule name, _doneNPMInstall(dirName, oldVersion) 67 | 68 | _doneNPMInstall = (name, oldVersion) -> 69 | (err) -> 70 | if err 71 | _revertInstall oldVersion, name 72 | 73 | backupPath = path.join mimosaPath, "node_modules", name + "_____backup" 74 | if fs.existsSync backupPath 75 | wrench.rmdirSyncRecursive backupPath 76 | 77 | process.chdir currentDir 78 | process.exit 0 79 | 80 | _prepareForNPMInstall = (name) -> 81 | beginPath = path.join mimosaPath, "node_modules", name 82 | oldVersion = null 83 | if fs.existsSync beginPath 84 | endPath = path.join mimosaPath, "node_modules", name + "_____backup" 85 | wrench.copyDirSyncRecursive beginPath, endPath 86 | 87 | mimosaPackagePath = path.join mimosaPath, 'package.json' 88 | mimosaPackage = require mimosaPackagePath 89 | oldVersion = mimosaPackage.dependencies[name] 90 | delete mimosaPackage.dependencies[name] 91 | logger.debug "New mimosa dependencies:\n #{JSON.stringify(mimosaPackage, null, 2)}" 92 | fs.writeFileSync mimosaPackagePath, JSON.stringify(mimosaPackage, null, 2), 'ascii' 93 | 94 | oldVersion 95 | 96 | _revertInstall = (oldVersion, name) -> 97 | backupPath = path.join mimosaPath, "node_modules", name + "_____backup" 98 | 99 | # if backup path exists, put that code back, otherwise get rid of module 100 | if fs.existsSync backupPath 101 | endPath = path.join mimosaPath, "node_modules", name 102 | wrench.copyDirSyncRecursive backupPath, endPath 103 | 104 | mimosaPackagePath = path.join mimosaPath, 'package.json' 105 | mimosaPackage = require mimosaPackagePath 106 | mimosaPackage.dependencies[name] = oldVersion 107 | logger.debug "New mimosa dependencies:\n #{JSON.stringify(mimosaPackage, null, 2)}" 108 | fs.writeFileSync mimosaPackagePath, JSON.stringify(mimosaPackage, null, 2), 'ascii' 109 | else 110 | modPath = path.join mimosaPath, "node_modules", name 111 | if fs.existsSync modPath 112 | wrench.rmdirSyncRecursive modPath 113 | 114 | ### 115 | Local Dev Install 116 | ### 117 | 118 | _doLocalInstall = -> 119 | _testLocalInstall -> 120 | dirName = currentDir.replace(path.dirname(currentDir) + path.sep, '') 121 | process.chdir mimosaPath 122 | _installModule currentDir, -> 123 | process.chdir currentDir 124 | process.exit 0 125 | 126 | _testLocalInstall = (callback) -> 127 | logger.info "Testing local install in place." 128 | exec "npm install", (err, sout, serr) => 129 | if err 130 | return logger.error "Could not install module locally: \n #{err}" 131 | 132 | logger.debug "NPM INSTALL standard out\n#{sout}" 133 | logger.debug "NPM INSTALL standard err\n#{serr}" 134 | 135 | try 136 | require currentDir 137 | logger.info "Local install successful." 138 | callback() 139 | catch err 140 | logger.error "Attempted to use installed module and module failed\n#{err}" 141 | console.log err 142 | 143 | ### 144 | Command 145 | ### 146 | 147 | register = (program, callback) -> 148 | program 149 | .command('mod:install [name]') 150 | .option("-D, --mdebug", "run in debug mode") 151 | .description("install a Mimosa module into your Mimosa") 152 | .action(callback) 153 | .on '--help', => 154 | logger.green(' The \'mod:install\' command will install a Mimosa module into Mimosa. It does not install') 155 | logger.green(' the module into your project, it just makes it available to be used by Mimosa\'s commands.') 156 | logger.green(' You can discover new modules using the \'mod:search\' command. Once you know the module you') 157 | logger.green(' would like to install, put the name of the module after the \'mod:install\' command.') 158 | logger.blue( '\n $ mimosa mod:install mimosa-server\n') 159 | logger.green(' If there is a specific version of a module you want to use, simply append \'@\' followed by') 160 | logger.green(' the version information.') 161 | logger.blue( '\n $ mimosa mod:install mimosa-server@0.1.0\n') 162 | logger.green(' If you are developing a module and would like to install your local module into your local') 163 | logger.green(' Mimosa, then execute \'mod:install\' from the root of the module, the same location as the') 164 | logger.green(' package.json, without providing a name.') 165 | logger.blue( '\n $ mimosa mod:install\n') 166 | 167 | module.exports = (program) -> 168 | register program, install -------------------------------------------------------------------------------- /src/modules/index.coffee: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | fs = require 'fs' 4 | path = require 'path' 5 | {exec} = require 'child_process' 6 | 7 | _ = require 'lodash' 8 | logger = require 'logmimosa' 9 | skels = require 'skelmimosa' 10 | newmod = require 'newmimosa' 11 | 12 | compilers = require './compilers' 13 | file = require './file' 14 | mimosaPackage = require('../../package.json') 15 | 16 | builtIns = [ 17 | 'mimosa-copy' 18 | 'mimosa-server' 19 | 'mimosa-jshint' 20 | 'mimosa-csslint' 21 | 'mimosa-require' 22 | 'mimosa-minify-js' 23 | 'mimosa-minify-css' 24 | 'mimosa-live-reload' 25 | 'mimosa-bower' 26 | ] 27 | configuredModules = null 28 | 29 | isMimosaModuleName = (str) -> str.indexOf('mimosa-') > -1 30 | 31 | projectNodeModules = path.resolve process.cwd(), 'node_modules' 32 | locallyInstalled = if fs.existsSync projectNodeModules 33 | _(fs.readdirSync projectNodeModules) 34 | .select(isMimosaModuleName) 35 | .select (dep) -> 36 | try 37 | require path.join projectNodeModules, dep 38 | true 39 | catch err 40 | logger.error "Error pulling in local Mimosa module: #{err}" 41 | process.exit 1 42 | .map (dep) -> 43 | local: true 44 | name: dep 45 | nodeModulesDir: projectNodeModules 46 | .value() 47 | else 48 | [] 49 | 50 | locallyInstalledNames = _.pluck locallyInstalled, 'name' 51 | standardlyInstalled = _(mimosaPackage.dependencies) 52 | .keys() 53 | .select (dir) -> 54 | isMimosaModuleName(dir) and dir not in locallyInstalledNames 55 | .map (dep) -> 56 | name: dep 57 | nodeModulesDir: '../../node_modules' 58 | .value() 59 | 60 | independentlyInstalled = do -> 61 | topLevelNodeModulesDir = path.resolve __dirname, '../../..' 62 | standardlyResolvedModules = _.pluck standardlyInstalled, 'name' 63 | _(fs.readdirSync topLevelNodeModulesDir) 64 | .select (dir) -> 65 | isMimosaModuleName(dir) and dir not in standardlyResolvedModules and dir not in locallyInstalledNames 66 | .map (dir) -> 67 | name: dir 68 | nodeModulesDir: topLevelNodeModulesDir 69 | .value() 70 | 71 | allInstalled = standardlyInstalled.concat(independentlyInstalled).concat(locallyInstalled) 72 | meta = _.map allInstalled, (modInfo) -> 73 | requireString = "#{modInfo.nodeModulesDir}/#{modInfo.name}/package.json" 74 | try 75 | modPack = require requireString 76 | 77 | mod: if modInfo.local then require "#{modInfo.nodeModulesDir}/#{modInfo.name}/" else require modInfo.name 78 | name: modInfo.name 79 | version: modPack.version 80 | site: modPack.homepage 81 | desc: modPack.description 82 | default: if builtIns.indexOf(modInfo.name) > -1 then "yes" else "no" 83 | dependencies: modPack.dependencies 84 | catch err 85 | resolvedPath = path.resolve requireString 86 | logger.error "Unable to read file at [[ #{resolvedPath} ]], possibly a permission issue? \nsystem error : #{err}" 87 | process.exit 1 88 | 89 | metaNames = _.pluck meta, 'name' 90 | configModuleString = if _.difference(metaNames, builtIns).length > 0 91 | names = metaNames.map (name) -> name.replace 'mimosa-', '' 92 | JSON.stringify names 93 | 94 | configured = (moduleNames, callback) -> 95 | return configuredModules if configuredModules 96 | 97 | # file must be first 98 | configuredModules = [file, compilers, logger] 99 | index = 0 100 | 101 | processModule = -> 102 | if index is moduleNames.length 103 | return callback(configuredModules) 104 | 105 | modName = moduleNames[index++] 106 | unless modName.indexOf('mimosa-') is 0 107 | modName = "mimosa-#{modName}" 108 | 109 | fullModName = modName 110 | 111 | if modName.indexOf('@') > 7 112 | modParts = modName.split('@') 113 | modName = modParts[0] 114 | modVersion = modParts[1] 115 | 116 | found = false 117 | for installed in meta when installed.name is modName 118 | unless modVersion? and modVersion isnt installed.version 119 | found = true 120 | installed.mod.__mimosaModuleName = modName 121 | configuredModules.push installed.mod 122 | break 123 | 124 | if found 125 | processModule() 126 | else 127 | logger.info "Module [[ #{fullModName} ]] cannot be found, attempting to install it from NPM into your project." 128 | 129 | nodeModules = path.join process.cwd(), "node_modules" 130 | unless fs.existsSync nodeModules 131 | logger.info "node_modules directory does not exist, creating one..." 132 | fs.mkdirSync nodeModules 133 | 134 | installString = "npm install #{fullModName}" 135 | exec installString, (err, sout, serr) => 136 | if err 137 | console.log "" 138 | logger.error "Unable to install [[ #{fullModName} ]]\n" 139 | logger.info "Does the module exist in npm (https://npmjs.org/package/#{fullModName})?\n" 140 | logger.error err 141 | 142 | process.exit 1 143 | else 144 | console.log sout 145 | logger.success "[[ #{fullModName} ]] successfully installed into your project." 146 | 147 | modPath = path.join nodeModules, modName 148 | Object.keys(require.cache).forEach (key) -> 149 | if key.indexOf(modPath) is 0 150 | delete require.cache[key] 151 | 152 | try 153 | requiredModule = require modPath 154 | requiredModule.__mimosaModuleName = modName 155 | configuredModules.push(requiredModule) 156 | catch err 157 | logger.warn "There was an error attempting to include the newly installed module in the currently running Mimosa process," + 158 | " but the install was successful. Mimosa is exiting. When it is restarted, Mimosa will use the newly installed module." 159 | logger.debug err 160 | 161 | process.exit 0 162 | 163 | #logger.debug "NPM INSTALL standard out\n#{sout}" 164 | #logger.debug "NPM INSTALL standard err\n#{serr}" 165 | 166 | processModule() 167 | 168 | processModule() 169 | 170 | all = [compilers, logger, file, skels, newmod].concat _.pluck(meta, 'mod') 171 | 172 | modulesWithCommands = -> 173 | mods = [] 174 | for mod in all 175 | if mod.registerCommand? 176 | mods.push mod 177 | mods 178 | 179 | module.exports = 180 | installedMetadata: meta 181 | getConfiguredModules: configured 182 | all: all 183 | configModuleString: configModuleString 184 | modulesWithCommands: modulesWithCommands 185 | -------------------------------------------------------------------------------- /lib/modules/compilers/javascript.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var JSCompiler, fileUtils, fs, logger, path, _cleanUpSourceMaps, _cleanUpSourceMapsRegister, _genMapFileName, _genSourceName, 3 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; 4 | 5 | path = require('path'); 6 | 7 | fs = require('fs'); 8 | 9 | logger = require('logmimosa'); 10 | 11 | fileUtils = require('../../util/file'); 12 | 13 | _genMapFileName = function(config, file) { 14 | var extName; 15 | extName = path.extname(file.inputFileName); 16 | return file.inputFileName.replace(extName, ".js.map").replace(config.watch.sourceDir, config.watch.compiledDir); 17 | }; 18 | 19 | _genSourceName = function(config, file) { 20 | return file.inputFileName.replace(config.watch.sourceDir, config.watch.compiledDir) + ".src"; 21 | }; 22 | 23 | _cleanUpSourceMaps = function(config, options, next) { 24 | var done, i; 25 | i = 0; 26 | done = function() { 27 | if (++i === 2) { 28 | return next(); 29 | } 30 | }; 31 | return options.files.forEach(function(file) { 32 | var mapFileName, sourceName; 33 | mapFileName = _genMapFileName(config, file); 34 | sourceName = _genSourceName(config, file); 35 | return [mapFileName, sourceName].forEach(function(f) { 36 | return fs.exists(f, function(exists) { 37 | if (exists) { 38 | return fs.unlink(f, function(err) { 39 | if (err) { 40 | logger.error("Error deleting file [[ " + f + " ]], " + err); 41 | } else { 42 | if (logger.isDebug()) { 43 | logger.debug("Deleted file [[ " + f + " ]]"); 44 | } 45 | } 46 | return done(); 47 | }); 48 | } else { 49 | return done(); 50 | } 51 | }); 52 | }); 53 | }); 54 | }; 55 | 56 | _cleanUpSourceMapsRegister = function(register, extensions) { 57 | register(['remove'], 'delete', _cleanUpSourceMaps, extensions); 58 | return register(['cleanFile'], 'delete', _cleanUpSourceMaps, extensions); 59 | }; 60 | 61 | module.exports = JSCompiler = (function() { 62 | function JSCompiler(config, compiler) { 63 | this.compiler = compiler; 64 | this._compile = __bind(this._compile, this); 65 | } 66 | 67 | JSCompiler.prototype.registration = function(config, register) { 68 | var exts; 69 | exts = this.compiler.extensions(config); 70 | register(['add', 'update', 'remove', 'cleanFile', 'buildFile'], 'init', this._determineOutputFile, exts); 71 | register(['add', 'update', 'buildFile'], 'compile', this._compile, exts); 72 | if (this.compiler.cleanUpSourceMaps) { 73 | return _cleanUpSourceMapsRegister(register, exts); 74 | } 75 | }; 76 | 77 | JSCompiler.prototype._determineOutputFile = function(config, options, next) { 78 | if (options.files && options.files.length) { 79 | options.destinationFile = function(fileName) { 80 | var baseCompDir; 81 | baseCompDir = fileName.replace(config.watch.sourceDir, config.watch.compiledDir); 82 | return baseCompDir.substring(0, baseCompDir.lastIndexOf(".")) + ".js"; 83 | }; 84 | options.files.forEach(function(file) { 85 | return file.outputFileName = options.destinationFile(file.inputFileName); 86 | }); 87 | } 88 | return next(); 89 | }; 90 | 91 | JSCompiler.prototype._compile = function(config, options, next) { 92 | var done, i, newFiles, whenDone, _ref, 93 | _this = this; 94 | if (!((_ref = options.files) != null ? _ref.length : void 0)) { 95 | return next(); 96 | } 97 | i = 0; 98 | newFiles = []; 99 | whenDone = options.files.length; 100 | done = function() { 101 | if (++i === whenDone) { 102 | options.files = newFiles; 103 | return next(); 104 | } 105 | }; 106 | return options.files.forEach(function(file) { 107 | if (logger.isDebug()) { 108 | logger.debug("Calling compiler function for compiler [[ " + _this.compiler.name + " ]]"); 109 | } 110 | file.isVendor = options.isVendor; 111 | return _this.compiler.compile(config, file, function(err, output, compilerConfig, sourceMap) { 112 | var base64SourceMap, datauri, sourceName; 113 | if (err) { 114 | logger.error("File [[ " + file.inputFileName + " ]] failed compile. Reason: " + err, { 115 | exitIfBuild: true 116 | }); 117 | } else { 118 | if (sourceMap) { 119 | if (compilerConfig.sourceMapDynamic) { 120 | sourceMap = JSON.parse(sourceMap); 121 | sourceMap.sources[0] = file.inputFileName; 122 | sourceMap.sourcesContent = [file.inputFileText]; 123 | sourceMap.file = file.outputFileName; 124 | base64SourceMap = new Buffer(JSON.stringify(sourceMap)).toString('base64'); 125 | datauri = 'data:application/json;base64,' + base64SourceMap; 126 | if (compilerConfig.sourceMapConditional) { 127 | output = "" + output + "\n//@ sourceMappingURL=" + datauri + "\n"; 128 | } else { 129 | output = "" + output + "\n//# sourceMappingURL=" + datauri + "\n"; 130 | } 131 | } else { 132 | whenDone += 2; 133 | sourceName = _genSourceName(config, file); 134 | fileUtils.writeFile(sourceName, file.inputFileText, function(err) { 135 | if (err) { 136 | logger.error("Error writing source file [[ " + sourceName + " ]], " + err); 137 | } 138 | return done(); 139 | }); 140 | file.sourceMap = sourceMap; 141 | file.sourceMapName = _genMapFileName(config, file); 142 | fileUtils.writeFile(file.sourceMapName, sourceMap, function(err) { 143 | if (err) { 144 | logger.error("Error writing map file [[ " + file.sourceMapName + " ]], " + err); 145 | } 146 | return done(); 147 | }); 148 | if (compilerConfig.sourceMapConditional) { 149 | output = "" + output + "\n//@ sourceMappingURL=" + (path.basename(file.sourceMapName)) + "\n"; 150 | } else { 151 | output = "" + output + "\n//# sourceMappingURL=" + (path.basename(file.sourceMapName)) + "\n"; 152 | } 153 | } 154 | } 155 | file.outputFileText = output; 156 | newFiles.push(file); 157 | } 158 | return done(); 159 | }); 160 | }); 161 | }; 162 | 163 | return JSCompiler; 164 | 165 | })(); 166 | -------------------------------------------------------------------------------- /lib/command/module/install.js: -------------------------------------------------------------------------------- 1 | var currentDir, exec, fs, install, logger, mimosaPath, path, register, wrench, _doLocalInstall, _doNPMInstall, _doneNPMInstall, _installModule, _prepareForNPMInstall, _revertInstall, _testLocalInstall; 2 | 3 | path = require('path'); 4 | 5 | fs = require('fs'); 6 | 7 | exec = require('child_process').exec; 8 | 9 | wrench = require('wrench'); 10 | 11 | logger = require('logmimosa'); 12 | 13 | mimosaPath = path.join(__dirname, '..', '..', '..'); 14 | 15 | currentDir = process.cwd(); 16 | 17 | install = function(name, opts) { 18 | var dirName, err, pack; 19 | if (opts.mdebug) { 20 | opts.debug = true; 21 | logger.setDebug(); 22 | process.env.DEBUG = true; 23 | } 24 | if (name != null) { 25 | if (!((name != null) && name.indexOf('mimosa-') === 0)) { 26 | return logger.error("Can only install 'mimosa-' prefixed modules with mod:install (ex: mimosa-server)."); 27 | } 28 | dirName = name.indexOf('@') > 7 ? name.substring(0, name.indexOf('@')) : name; 29 | return _doNPMInstall(name, dirName); 30 | } else { 31 | try { 32 | pack = require(path.join(currentDir, 'package.json')); 33 | } catch (_error) { 34 | err = _error; 35 | return logger.error("Unable to find package.json, or badly formatted: " + err); 36 | } 37 | if (!((pack.name != null) && (pack.version != null))) { 38 | return logger.error("package.json missing either name or version"); 39 | } 40 | if (pack.name.indexOf('mimosa-') !== 0) { 41 | return logger.error("package.json name is [[ " + pack.name + " ]]. Can only install 'mimosa-' prefixed modules with mod:install (ex: mimosa-server). "); 42 | } 43 | return _doLocalInstall(); 44 | } 45 | }; 46 | 47 | _installModule = function(name, done) { 48 | var installString, 49 | _this = this; 50 | logger.info("Installing module [[ " + name + " ]] into Mimosa."); 51 | installString = "npm install \"" + name + "\" --save"; 52 | return exec(installString, function(err, sout, serr) { 53 | if (err) { 54 | logger.error("Error installing module"); 55 | logger.error(err); 56 | } else { 57 | console.log(sout); 58 | console.log(serr); 59 | logger.success("Install of [[ " + name + " ]] successful"); 60 | } 61 | logger.debug("NPM INSTALL standard out\n" + sout); 62 | logger.debug("NPM INSTALL standard err\n" + serr); 63 | return done(err); 64 | }); 65 | }; 66 | 67 | /* 68 | NPM Install 69 | */ 70 | 71 | 72 | _doNPMInstall = function(name, dirName) { 73 | var oldVersion; 74 | process.chdir(mimosaPath); 75 | oldVersion = _prepareForNPMInstall(dirName); 76 | return _installModule(name, _doneNPMInstall(dirName, oldVersion)); 77 | }; 78 | 79 | _doneNPMInstall = function(name, oldVersion) { 80 | return function(err) { 81 | var backupPath; 82 | if (err) { 83 | _revertInstall(oldVersion, name); 84 | } 85 | backupPath = path.join(mimosaPath, "node_modules", name + "_____backup"); 86 | if (fs.existsSync(backupPath)) { 87 | wrench.rmdirSyncRecursive(backupPath); 88 | } 89 | process.chdir(currentDir); 90 | return process.exit(0); 91 | }; 92 | }; 93 | 94 | _prepareForNPMInstall = function(name) { 95 | var beginPath, endPath, mimosaPackage, mimosaPackagePath, oldVersion; 96 | beginPath = path.join(mimosaPath, "node_modules", name); 97 | oldVersion = null; 98 | if (fs.existsSync(beginPath)) { 99 | endPath = path.join(mimosaPath, "node_modules", name + "_____backup"); 100 | wrench.copyDirSyncRecursive(beginPath, endPath); 101 | mimosaPackagePath = path.join(mimosaPath, 'package.json'); 102 | mimosaPackage = require(mimosaPackagePath); 103 | oldVersion = mimosaPackage.dependencies[name]; 104 | delete mimosaPackage.dependencies[name]; 105 | logger.debug("New mimosa dependencies:\n " + (JSON.stringify(mimosaPackage, null, 2))); 106 | fs.writeFileSync(mimosaPackagePath, JSON.stringify(mimosaPackage, null, 2), 'ascii'); 107 | } 108 | return oldVersion; 109 | }; 110 | 111 | _revertInstall = function(oldVersion, name) { 112 | var backupPath, endPath, mimosaPackage, mimosaPackagePath, modPath; 113 | backupPath = path.join(mimosaPath, "node_modules", name + "_____backup"); 114 | if (fs.existsSync(backupPath)) { 115 | endPath = path.join(mimosaPath, "node_modules", name); 116 | wrench.copyDirSyncRecursive(backupPath, endPath); 117 | mimosaPackagePath = path.join(mimosaPath, 'package.json'); 118 | mimosaPackage = require(mimosaPackagePath); 119 | mimosaPackage.dependencies[name] = oldVersion; 120 | logger.debug("New mimosa dependencies:\n " + (JSON.stringify(mimosaPackage, null, 2))); 121 | return fs.writeFileSync(mimosaPackagePath, JSON.stringify(mimosaPackage, null, 2), 'ascii'); 122 | } else { 123 | modPath = path.join(mimosaPath, "node_modules", name); 124 | if (fs.existsSync(modPath)) { 125 | return wrench.rmdirSyncRecursive(modPath); 126 | } 127 | } 128 | }; 129 | 130 | /* 131 | Local Dev Install 132 | */ 133 | 134 | 135 | _doLocalInstall = function() { 136 | return _testLocalInstall(function() { 137 | var dirName; 138 | dirName = currentDir.replace(path.dirname(currentDir) + path.sep, ''); 139 | process.chdir(mimosaPath); 140 | return _installModule(currentDir, function() { 141 | process.chdir(currentDir); 142 | return process.exit(0); 143 | }); 144 | }); 145 | }; 146 | 147 | _testLocalInstall = function(callback) { 148 | var _this = this; 149 | logger.info("Testing local install in place."); 150 | return exec("npm install", function(err, sout, serr) { 151 | if (err) { 152 | return logger.error("Could not install module locally: \n " + err); 153 | } 154 | logger.debug("NPM INSTALL standard out\n" + sout); 155 | logger.debug("NPM INSTALL standard err\n" + serr); 156 | try { 157 | require(currentDir); 158 | logger.info("Local install successful."); 159 | return callback(); 160 | } catch (_error) { 161 | err = _error; 162 | logger.error("Attempted to use installed module and module failed\n" + err); 163 | return console.log(err); 164 | } 165 | }); 166 | }; 167 | 168 | /* 169 | Command 170 | */ 171 | 172 | 173 | register = function(program, callback) { 174 | var _this = this; 175 | return program.command('mod:install [name]').option("-D, --mdebug", "run in debug mode").description("install a Mimosa module into your Mimosa").action(callback).on('--help', function() { 176 | logger.green(' The \'mod:install\' command will install a Mimosa module into Mimosa. It does not install'); 177 | logger.green(' the module into your project, it just makes it available to be used by Mimosa\'s commands.'); 178 | logger.green(' You can discover new modules using the \'mod:search\' command. Once you know the module you'); 179 | logger.green(' would like to install, put the name of the module after the \'mod:install\' command.'); 180 | logger.blue('\n $ mimosa mod:install mimosa-server\n'); 181 | logger.green(' If there is a specific version of a module you want to use, simply append \'@\' followed by'); 182 | logger.green(' the version information.'); 183 | logger.blue('\n $ mimosa mod:install mimosa-server@0.1.0\n'); 184 | logger.green(' If you are developing a module and would like to install your local module into your local'); 185 | logger.green(' Mimosa, then execute \'mod:install\' from the root of the module, the same location as the'); 186 | logger.green(' package.json, without providing a name.'); 187 | return logger.blue('\n $ mimosa mod:install\n'); 188 | }); 189 | }; 190 | 191 | module.exports = function(program) { 192 | return register(program, install); 193 | }; 194 | -------------------------------------------------------------------------------- /lib/modules/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var all, allInstalled, builtIns, compilers, configModuleString, configured, configuredModules, exec, file, fs, independentlyInstalled, isMimosaModuleName, locallyInstalled, locallyInstalledNames, logger, meta, metaNames, mimosaPackage, modulesWithCommands, names, newmod, path, projectNodeModules, skels, standardlyInstalled, _, 3 | __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; 4 | 5 | fs = require('fs'); 6 | 7 | path = require('path'); 8 | 9 | exec = require('child_process').exec; 10 | 11 | _ = require('lodash'); 12 | 13 | logger = require('logmimosa'); 14 | 15 | skels = require('skelmimosa'); 16 | 17 | newmod = require('newmimosa'); 18 | 19 | compilers = require('./compilers'); 20 | 21 | file = require('./file'); 22 | 23 | mimosaPackage = require('../../package.json'); 24 | 25 | builtIns = ['mimosa-copy', 'mimosa-server', 'mimosa-jshint', 'mimosa-csslint', 'mimosa-require', 'mimosa-minify-js', 'mimosa-minify-css', 'mimosa-live-reload', 'mimosa-bower']; 26 | 27 | configuredModules = null; 28 | 29 | isMimosaModuleName = function(str) { 30 | return str.indexOf('mimosa-') > -1; 31 | }; 32 | 33 | projectNodeModules = path.resolve(process.cwd(), 'node_modules'); 34 | 35 | locallyInstalled = fs.existsSync(projectNodeModules) ? _(fs.readdirSync(projectNodeModules)).select(isMimosaModuleName).select(function(dep) { 36 | var err; 37 | try { 38 | require(path.join(projectNodeModules, dep)); 39 | return true; 40 | } catch (_error) { 41 | err = _error; 42 | logger.error("Error pulling in local Mimosa module: " + err); 43 | return process.exit(1); 44 | } 45 | }).map(function(dep) { 46 | return { 47 | local: true, 48 | name: dep, 49 | nodeModulesDir: projectNodeModules 50 | }; 51 | }).value() : []; 52 | 53 | locallyInstalledNames = _.pluck(locallyInstalled, 'name'); 54 | 55 | standardlyInstalled = _(mimosaPackage.dependencies).keys().select(function(dir) { 56 | return isMimosaModuleName(dir) && __indexOf.call(locallyInstalledNames, dir) < 0; 57 | }).map(function(dep) { 58 | return { 59 | name: dep, 60 | nodeModulesDir: '../../node_modules' 61 | }; 62 | }).value(); 63 | 64 | independentlyInstalled = (function() { 65 | var standardlyResolvedModules, topLevelNodeModulesDir; 66 | topLevelNodeModulesDir = path.resolve(__dirname, '../../..'); 67 | standardlyResolvedModules = _.pluck(standardlyInstalled, 'name'); 68 | return _(fs.readdirSync(topLevelNodeModulesDir)).select(function(dir) { 69 | return isMimosaModuleName(dir) && __indexOf.call(standardlyResolvedModules, dir) < 0 && __indexOf.call(locallyInstalledNames, dir) < 0; 70 | }).map(function(dir) { 71 | return { 72 | name: dir, 73 | nodeModulesDir: topLevelNodeModulesDir 74 | }; 75 | }).value(); 76 | })(); 77 | 78 | allInstalled = standardlyInstalled.concat(independentlyInstalled).concat(locallyInstalled); 79 | 80 | meta = _.map(allInstalled, function(modInfo) { 81 | var err, modPack, requireString, resolvedPath; 82 | requireString = "" + modInfo.nodeModulesDir + "/" + modInfo.name + "/package.json"; 83 | try { 84 | modPack = require(requireString); 85 | return { 86 | mod: modInfo.local ? require("" + modInfo.nodeModulesDir + "/" + modInfo.name + "/") : require(modInfo.name), 87 | name: modInfo.name, 88 | version: modPack.version, 89 | site: modPack.homepage, 90 | desc: modPack.description, 91 | "default": builtIns.indexOf(modInfo.name) > -1 ? "yes" : "no", 92 | dependencies: modPack.dependencies 93 | }; 94 | } catch (_error) { 95 | err = _error; 96 | resolvedPath = path.resolve(requireString); 97 | logger.error("Unable to read file at [[ " + resolvedPath + " ]], possibly a permission issue? \nsystem error : " + err); 98 | return process.exit(1); 99 | } 100 | }); 101 | 102 | metaNames = _.pluck(meta, 'name'); 103 | 104 | configModuleString = _.difference(metaNames, builtIns).length > 0 ? (names = metaNames.map(function(name) { 105 | return name.replace('mimosa-', ''); 106 | }), JSON.stringify(names)) : void 0; 107 | 108 | configured = function(moduleNames, callback) { 109 | var index, processModule; 110 | if (configuredModules) { 111 | return configuredModules; 112 | } 113 | configuredModules = [file, compilers, logger]; 114 | index = 0; 115 | processModule = function() { 116 | var found, fullModName, installString, installed, modName, modParts, modVersion, nodeModules, _i, _len, 117 | _this = this; 118 | if (index === moduleNames.length) { 119 | return callback(configuredModules); 120 | } 121 | modName = moduleNames[index++]; 122 | if (modName.indexOf('mimosa-') !== 0) { 123 | modName = "mimosa-" + modName; 124 | } 125 | fullModName = modName; 126 | if (modName.indexOf('@') > 7) { 127 | modParts = modName.split('@'); 128 | modName = modParts[0]; 129 | modVersion = modParts[1]; 130 | } 131 | found = false; 132 | for (_i = 0, _len = meta.length; _i < _len; _i++) { 133 | installed = meta[_i]; 134 | if (installed.name === modName) { 135 | if (!((modVersion != null) && modVersion !== installed.version)) { 136 | found = true; 137 | installed.mod.__mimosaModuleName = modName; 138 | configuredModules.push(installed.mod); 139 | break; 140 | } 141 | } 142 | } 143 | if (found) { 144 | return processModule(); 145 | } else { 146 | logger.info("Module [[ " + fullModName + " ]] cannot be found, attempting to install it from NPM into your project."); 147 | nodeModules = path.join(process.cwd(), "node_modules"); 148 | if (!fs.existsSync(nodeModules)) { 149 | logger.info("node_modules directory does not exist, creating one..."); 150 | fs.mkdirSync(nodeModules); 151 | } 152 | installString = "npm install " + fullModName; 153 | return exec(installString, function(err, sout, serr) { 154 | var modPath, requiredModule; 155 | if (err) { 156 | console.log(""); 157 | logger.error("Unable to install [[ " + fullModName + " ]]\n"); 158 | logger.info("Does the module exist in npm (https://npmjs.org/package/" + fullModName + ")?\n"); 159 | logger.error(err); 160 | process.exit(1); 161 | } else { 162 | console.log(sout); 163 | logger.success("[[ " + fullModName + " ]] successfully installed into your project."); 164 | modPath = path.join(nodeModules, modName); 165 | Object.keys(require.cache).forEach(function(key) { 166 | if (key.indexOf(modPath) === 0) { 167 | return delete require.cache[key]; 168 | } 169 | }); 170 | try { 171 | requiredModule = require(modPath); 172 | requiredModule.__mimosaModuleName = modName; 173 | configuredModules.push(requiredModule); 174 | } catch (_error) { 175 | err = _error; 176 | logger.warn("There was an error attempting to include the newly installed module in the currently running Mimosa process," + " but the install was successful. Mimosa is exiting. When it is restarted, Mimosa will use the newly installed module."); 177 | logger.debug(err); 178 | process.exit(0); 179 | } 180 | } 181 | return processModule(); 182 | }); 183 | } 184 | }; 185 | return processModule(); 186 | }; 187 | 188 | all = [compilers, logger, file, skels, newmod].concat(_.pluck(meta, 'mod')); 189 | 190 | modulesWithCommands = function() { 191 | var mod, mods, _i, _len; 192 | mods = []; 193 | for (_i = 0, _len = all.length; _i < _len; _i++) { 194 | mod = all[_i]; 195 | if (mod.registerCommand != null) { 196 | mods.push(mod); 197 | } 198 | } 199 | return mods; 200 | }; 201 | 202 | module.exports = { 203 | installedMetadata: meta, 204 | getConfiguredModules: configured, 205 | all: all, 206 | configModuleString: configModuleString, 207 | modulesWithCommands: modulesWithCommands 208 | }; 209 | -------------------------------------------------------------------------------- /src/modules/compilers/index.coffee: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | path = require 'path' 4 | fs = require 'fs' 5 | 6 | _ = require 'lodash' 7 | logger = require 'logmimosa' 8 | 9 | JavaScriptCompiler = require( "./javascript" ) 10 | CSSCompiler = require( "./css" ) 11 | TemplateCompiler = require( "./template" ) 12 | MiscCompiler = require( "./misc" ) 13 | 14 | compilers = [] 15 | 16 | templateLibrariesBeingUsed = 0 17 | 18 | _testDifferentTemplateLibraries = (config, options, next) -> 19 | hasFiles = options.files?.length > 0 20 | return next() unless hasFiles 21 | return next() unless typeof config.template.outputFileName is "string" 22 | 23 | if ++templateLibrariesBeingUsed is 2 24 | logger.error "More than one template library is being used, but multiple template.outputFileName entries not found." + 25 | " You will want to configure a map of template.outputFileName entries in your config, otherwise you will only get" + 26 | " template output for one of the libraries." 27 | 28 | next() 29 | 30 | exports.setupCompilers = (config) -> 31 | 32 | if compilers.length 33 | compilers = [] 34 | 35 | for modName, mod of config.installedModules 36 | if mod.compilerType 37 | if logger.isDebug() 38 | logger.debug( "Found compiler [[ #{mod.name} ]], adding to array of compilers"); 39 | compilers.push mod 40 | 41 | for compiler in compilers 42 | exts = compiler.extensions(config) 43 | config.extensions[compiler.compilerType].push(exts...) 44 | 45 | for type, extensions of config.extensions 46 | config.extensions[type] = _.uniq(extensions) 47 | 48 | # sort copy and misc to the end of compilers list 49 | # as they are not to override other compilers, 50 | # for instance if two compilers both register 51 | # for same extension 52 | if config.resortCompilers 53 | backloadCompilers = ["copy", "misc"] 54 | copyMisc = _.remove compilers, (comp) -> 55 | backloadCompilers.indexOf( comp.compilerType ) > -1 56 | compilers = compilers.concat copyMisc 57 | 58 | exports.registration = (config, register) -> 59 | for compiler in compilers 60 | if logger.isDebug() 61 | logger.debug "Creating compiler " + compiler.name 62 | 63 | CompilerClass = switch compiler.compilerType 64 | when "copy" then MiscCompiler 65 | when "misc" then MiscCompiler 66 | when "javascript" then JavaScriptCompiler 67 | when "template" then TemplateCompiler 68 | when "css" then CSSCompiler 69 | compilerInstance = new CompilerClass( config, compiler ) 70 | compilerInstance.name = compiler.name 71 | compilerInstance.registration(config, register) 72 | if compiler.registration 73 | compiler.registration( config, register ) 74 | 75 | if logger.isDebug() 76 | logger.debug "Done with compiler " + compiler.name 77 | 78 | if config.template 79 | register ['buildExtension'], 'complete', _testDifferentTemplateLibraries, config.extensions.template 80 | 81 | exports.defaults = -> 82 | template: 83 | writeLibrary: true 84 | wrapType: "amd" 85 | commonLibPath: null 86 | nameTransform:"fileName" 87 | outputFileName: "javascripts/templates" 88 | 89 | exports.placeholder = -> 90 | """ 91 | \t 92 | 93 | template: # overall template object can be set to null if no 94 | # templates being used 95 | writeLibrary: true # Whether or not to write a client library for 96 | # any template compilers 97 | nameTransform: "fileName" # means by which Mimosa creates the name for each 98 | # template, options: default "fileName" is name of file, 99 | # "filePath" is path of file after watch.sourceDir 100 | # with the extension dropped, a supplied regex can be 101 | # used to remove any unwanted portions of the filePath, 102 | # and a provided function will be called with the 103 | # filePath as input 104 | wrapType: "amd" # The type of module wrapping for the output templates 105 | # file. Possible values: "amd", "common", "none". 106 | commonLibPath: null # Valid when wrapType is 'common'. The path to the 107 | # client library. Some libraries do not have clients 108 | # therefore this is not strictly required when choosing 109 | # the common wrapType. 110 | outputFileName: "javascripts/templates" # the file all templates are compiled into, 111 | # is relative to watch.sourceDir. 112 | 113 | # outputFileName: # outputFileName Alternate Config 1 114 | # hogan:"hogans" # Optionally outputFileName can be provided an object of 115 | # jade:"jades" # compiler name to file name in the event you are using 116 | # multiple templating libraries. 117 | 118 | # output: [{ # output Alternate Config 2 119 | # folders:[""] # Use output instead of outputFileName if you want 120 | # outputFileName: "" # to break up your templates into multiple files, for 121 | # }] # instance, if you have a two page app and want the 122 | # templates for each page to be built separately. 123 | # For each entry, provide an array of folders that 124 | # contain the templates to combine. folders entries are 125 | # relative to watch.sourceDir and must exist. 126 | # outputFileName works identically to outputFileName 127 | # above, including the alternate config, however, no 128 | # default file name is assumed. An output name must be 129 | # provided for each output entry, and the names 130 | # must be unique. 131 | 132 | """ 133 | 134 | exports.validate = (config, validators) -> 135 | errors = [] 136 | 137 | if validators.ifExistsIsObject(errors, "template config", config.template) 138 | validators.ifExistsIsBoolean( errors, "template.writeLibrary", config.template.writeLibrary ) 139 | 140 | if config.template.output and config.template.outputFileName 141 | delete config.template.outputFileName 142 | 143 | if validators.ifExistsIsBoolean(errors, "template.amdWrap", config.template.amdWrap) 144 | logger.warn "template.amdWrap has been deprecated and support will be removed with a future release. Use template.wrapType." 145 | if config.template.amdWrap 146 | config.template.wrapType = "amd" 147 | else 148 | config.template.wrapType = "none" 149 | 150 | if validators.ifExistsIsString(errors, "template.wrapType", config.template.wrapType) 151 | if ["common", "amd", "none"].indexOf(config.template.wrapType) is -1 152 | errors.push "template.wrapType must be one of: 'common', 'amd', 'none'" 153 | 154 | if config.template.nameTransform? 155 | if typeof config.template.nameTransform is "string" 156 | if ["fileName","filePath"].indexOf(config.template.nameTransform) is -1 157 | errors.push "config.template.nameTransform valid string values are filePath or fileName" 158 | else if typeof config.template.nameTransform is "function" or config.template.nameTransform instanceof RegExp 159 | # do nothing 160 | else 161 | errors.push "config.template.nameTransform property must be a string, regex or function" 162 | 163 | if config.template.outputFileName? 164 | config.template.output = [{ 165 | folders:[""] 166 | outputFileName:config.template.outputFileName 167 | }] 168 | 169 | if validators.ifExistsIsArrayOfObjects(errors, "template.output", config.template.output) 170 | fileNames = [] 171 | for outputConfig in config.template.output 172 | if validators.isArrayOfStringsMustExist errors, "template.templateFiles.folders", outputConfig.folders 173 | 174 | if outputConfig.folders.length is 0 175 | errors.push "template.templateFiles.folders must have at least one entry" 176 | else 177 | newFolders = [] 178 | for folder in outputConfig.folders 179 | folder = path.join config.watch.sourceDir, folder 180 | unless fs.existsSync folder 181 | errors.push "template.templateFiles.folders must exist, folder resolved to [[ #{folder} ]]" 182 | newFolders.push folder 183 | outputConfig.folders = newFolders 184 | 185 | if outputConfig.outputFileName? 186 | fName = outputConfig.outputFileName 187 | if typeof fName is "string" 188 | fileNames.push fName 189 | else if typeof fName is "object" and not Array.isArray(fName) 190 | for tComp in Object.keys(fName) 191 | fileNames.push fName[tComp] 192 | else 193 | errors.push "template.outputFileName must be an object or a string." 194 | else 195 | errors.push "template.output.outputFileName must exist for each entry in array." 196 | 197 | if fileNames.length isnt _.uniq(fileNames).length 198 | errors.push "template.output.outputFileName names must be unique." 199 | 200 | errors 201 | -------------------------------------------------------------------------------- /src/modules/compilers/css.coffee: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | fs = require 'fs' 4 | path = require 'path' 5 | 6 | _ = require 'lodash' 7 | logger = require 'logmimosa' 8 | 9 | fileUtils = require '../../util/file' 10 | 11 | __buildDestinationFile = (config, fileName) -> 12 | baseCompDir = fileName.replace(config.watch.sourceDir, config.watch.compiledDir) 13 | baseCompDir.substring(0, baseCompDir.lastIndexOf(".")) + ".css" 14 | 15 | __baseOptionsObject = (config, base) -> 16 | destFile = __buildDestinationFile(config, base) 17 | 18 | inputFileName:base 19 | outputFileName:destFile 20 | inputFileText:null 21 | outputFileText:null 22 | 23 | _init = (config, options, next) -> 24 | options.destinationFile = (fileName) -> 25 | __buildDestinationFile config, fileName 26 | next() 27 | 28 | module.exports = class CSSCompiler 29 | 30 | constructor: (config, @compiler) -> 31 | @extensions = @compiler.extensions(config) 32 | 33 | registration: (config, register) -> 34 | register ['add','update','remove','cleanFile','buildExtension'], 'init', _init, @extensions 35 | 36 | register ['buildExtension'], 'init', @_processWatchedDirectories, [@extensions[0]] 37 | register ['buildExtension'], 'init', @_findBasesToCompileStartup, [@extensions[0]] 38 | register ['buildExtension'], 'compile', @_compile, [@extensions[0]] 39 | 40 | exts = @extensions 41 | if @compiler.canFullyImportCSS 42 | exts.push "css" 43 | 44 | register ['add'], 'init', @_processWatchedDirectories, exts 45 | register ['remove','cleanFile'], 'init', @_checkState, exts 46 | register ['add','update','remove','cleanFile'], 'init', @_findBasesToCompile, exts 47 | register ['add','update','remove'], 'compile', @_compile, exts 48 | register ['update','remove'], 'afterCompile', @_processWatchedDirectories, exts 49 | 50 | # for clean 51 | _checkState: (config, options, next) => 52 | if @includeToBaseHash? 53 | next() 54 | else 55 | @_processWatchedDirectories(config, options, -> next()) 56 | 57 | _findBasesToCompile: (config, options, next) => 58 | # clear out any compiler related files, leave any that are not from this compiler 59 | options.files = options.files.filter @__notCompilerFile 60 | 61 | if @_isInclude(options.inputFile, @includeToBaseHash) 62 | # check to see if also is base 63 | if @baseFiles.indexOf(options.inputFile) > -1 64 | options.files.push __baseOptionsObject(config, options.inputFile) 65 | 66 | # file is include so need to find bases to compile for it 67 | bases = @includeToBaseHash[options.inputFile] 68 | if bases? 69 | if logger.isDebug() 70 | logger.debug "Bases files for [[ #{options.inputFile} ]]\n#{bases.join('\n')}" 71 | 72 | for base in bases 73 | options.files.push __baseOptionsObject(config, base) 74 | # else 75 | # valid only for SASS which has naming convension for partials 76 | # unless options.lifeCycleType is 'remove' 77 | # logger.warn "Orphaned partial file: [[ #{options.inputFile} ]]" 78 | else 79 | # file is passing through, isn't include, is base of its own and needs to be compiled 80 | # unless it is a remove (since it is deleted) 81 | if options.lifeCycleType isnt 'remove' and path.extname(options.inputFile) isnt ".css" 82 | options.files.push __baseOptionsObject(config, options.inputFile) 83 | 84 | # protect against compiling the same file multiple times 85 | # ... circular references and such 86 | options.files = _.uniq options.files, (f) -> f.outputFileName 87 | 88 | next() 89 | 90 | _compile: (config, options, next) => 91 | hasFiles = options.files?.length > 0 92 | return next() unless hasFiles 93 | 94 | i = 0 95 | done = -> 96 | if ++i is options.files.length 97 | next() 98 | 99 | options.files.forEach (file) => 100 | if (@__notCompilerFile(file)) 101 | done() 102 | else fs.exists file.inputFileName, (exists) => 103 | if exists 104 | @compiler.compile config, file, (err, result) => 105 | if err 106 | logger.error "File [[ #{file.inputFileName} ]] failed compile. Reason: #{err}", {exitIfBuild:true} 107 | else 108 | file.outputFileText = result 109 | done() 110 | else 111 | done() 112 | 113 | __notCompilerFile: (file) => 114 | # css files are processed by all compilers 115 | # result is some compilers can add files to other compilers workflows 116 | # as css file flows through, need to be certain file belongs 117 | ext = path.extname(file.inputFileName).replace(/\./,'') 118 | @extensions.indexOf(ext) is -1 or ext is "css" 119 | 120 | _findBasesToCompileStartup: (config, options, next) => 121 | baseFilesToCompileNow = [] 122 | 123 | # Determine if any includes necessitate a base file compile 124 | for include, bases of @includeToBaseHash 125 | for base in bases 126 | basePath = __buildDestinationFile(config, base) 127 | if fs.existsSync basePath 128 | includeTime = fs.statSync(include).mtime 129 | baseTime = fs.statSync(basePath).mtime 130 | if includeTime > baseTime 131 | if logger.isDebug() 132 | logger.debug "Base [[ #{base} ]] needs compiling because [[ #{include} ]] has been changed recently" 133 | baseFilesToCompileNow.push(base) 134 | else 135 | if logger.isDebug() 136 | logger.debug "Base file [[ #{base} ]] hasn't been compiled yet, needs compiling" 137 | baseFilesToCompileNow.push(base) 138 | 139 | # Determine if any bases need to be compiled based on their own merit 140 | for base in @baseFiles 141 | baseCompiledPath = __buildDestinationFile(config, base) 142 | if fs.existsSync baseCompiledPath 143 | if fs.statSync(base).mtime > fs.statSync(baseCompiledPath).mtime 144 | if logger.isDebug() 145 | logger.debug "Base file [[ #{base} ]] needs to be compiled, it has been changed recently" 146 | baseFilesToCompileNow.push(base) 147 | else 148 | if logger.isDebug() 149 | logger.debug "Base file [[ #{base} ]] hasn't been compiled yet, needs compiling" 150 | baseFilesToCompileNow.push(base) 151 | 152 | baseFilesToCompile = _.uniq(baseFilesToCompileNow) 153 | 154 | options.files = baseFilesToCompile.map (base) -> 155 | __baseOptionsObject(config, base) 156 | 157 | if options.files.length > 0 158 | options.isVendor = fileUtils.isVendorCSS(config, options.files[0].inputFileName) 159 | options.files.forEach (f) -> 160 | f.isVendor = fileUtils.isVendorCSS(config, f.inputFileName) 161 | 162 | options.isCSS = true 163 | 164 | next() 165 | 166 | _processWatchedDirectories: (config, options, next) => 167 | @includeToBaseHash = {} 168 | allFiles = @__getAllFiles(config) 169 | 170 | oldBaseFiles = @baseFiles ?= [] 171 | @baseFiles = @compiler.determineBaseFiles(allFiles).filter (file) -> 172 | path.extname(file) isnt '.css' 173 | allBaseFiles = _.union oldBaseFiles, @baseFiles 174 | 175 | # Change in base files to be compiled, cleanup and message 176 | if (allBaseFiles.length isnt oldBaseFiles.length or allBaseFiles.length isnt @baseFiles.length) and oldBaseFiles.length > 0 177 | logger.info "The list of CSS files that Mimosa will compile has changed. Mimosa will now compile the following root files to CSS:" 178 | logger.info baseFile for baseFile in @baseFiles 179 | 180 | @__importsForFile(baseFile, baseFile, allFiles) for baseFile in @baseFiles 181 | 182 | next() 183 | 184 | _isInclude: (fileName, includeToBaseHash) -> 185 | if @compiler.isInclude 186 | @compiler.isInclude(fileName, includeToBaseHash) 187 | else 188 | includeToBaseHash[fileName]? 189 | 190 | __getAllFiles: (config) => 191 | files = fileUtils.readdirSyncRecursive(config.watch.sourceDir, config.watch.exclude, config.watch.excludeRegex, true) 192 | .filter (file) => 193 | @extensions.some (ext) -> 194 | fileExt = file.slice(-(ext.length+1)) 195 | fileExt is ".#{ext}" or (fileExt is ".css" and @compiler.canFullyImportCSS) 196 | 197 | # logger.debug "All files for extensions [[ #{@extensions} ]]:\n#{files.join('\n')}" 198 | 199 | files 200 | 201 | # get all imports for a given file, and recurse through 202 | # those imports until entire tree is built 203 | __importsForFile: (baseFile, file, allFiles) -> 204 | if fs.existsSync(file) 205 | importMatches = fs.readFileSync(file, 'utf8').match(@compiler.importRegex) 206 | 207 | return unless importMatches? 208 | 209 | if logger.isDebug() 210 | logger.debug "Imports for file [[ #{file} ]]: #{importMatches}" 211 | 212 | imports = [] 213 | for anImport in importMatches 214 | @compiler.importRegex.lastIndex = 0 215 | anImport = @compiler.importRegex.exec(anImport)[1] 216 | if @compiler.importSplitRegex 217 | imports.push.apply(imports, anImport.split(@compiler.importSplitRegex)) 218 | else 219 | imports.push(anImport) 220 | 221 | # iterate over all the import paths in the file 222 | for importPath in imports 223 | 224 | fullImportFilePaths = @compiler.getImportFilePath(file, importPath) 225 | unless Array.isArray(fullImportFilePaths) 226 | fullImportFilePaths = [fullImportFilePaths] 227 | 228 | # iterate over the all the possible forms of the full path for the import on the file system 229 | for fullImportFilePath in fullImportFilePaths 230 | 231 | includeFiles = if path.extname(fullImportFilePath) is ".css" and @compiler.canFullyImportCSS 232 | [fullImportFilePath] 233 | else 234 | allFiles.filter (f) => 235 | if path.extname( fullImportFilePath ) is '' 236 | f = f.replace(path.extname(f), '') 237 | f.slice(-fullImportFilePath.length) is fullImportFilePath 238 | 239 | # now we've found files for includes on the file system, iterate over them 240 | for includeFile in includeFiles 241 | 242 | hash = @includeToBaseHash[includeFile] 243 | 244 | if hash? 245 | if logger.isDebug() 246 | logger.debug "Adding base file [[ #{baseFile} ]] to list of base files for include [[ #{includeFile} ]]" 247 | hash.push(baseFile) if hash.indexOf(baseFile) is -1 248 | else 249 | if fs.existsSync includeFile 250 | if logger.isDebug() 251 | logger.debug "Creating base file entry for include file [[ #{includeFile} ]], adding base file [[ #{baseFile} ]]" 252 | @includeToBaseHash[includeFile] = [baseFile] 253 | 254 | if baseFile is includeFile 255 | logger.info "Circular import reference found in file [[ #{baseFile} ]]" 256 | else 257 | @__importsForFile(baseFile, includeFile, allFiles) -------------------------------------------------------------------------------- /src/util/workflow.coffee: -------------------------------------------------------------------------------- 1 | path = require 'path' 2 | fs = require 'fs' 3 | 4 | _ = require 'lodash' 5 | logger = require 'logmimosa' 6 | 7 | compilers = require '../modules/compilers' 8 | fileUtils = require './file' 9 | 10 | module.exports = class WorkflowManager 11 | 12 | startup:true 13 | initialFilesHandled:0 14 | registration: {} 15 | doneFiles: [] 16 | 17 | masterTypes: 18 | 19 | preClean: ["init", "complete"] 20 | cleanFile: ["init", "beforeRead", "read", "afterRead", "beforeDelete", "delete", "afterDelete", "complete"] 21 | postClean: ["init", "complete"] 22 | 23 | preBuild: ["init", "complete"] 24 | buildFile: ["init", "beforeRead", "read", "afterRead", "betweenReadCompile", "beforeCompile", "compile", "afterCompile", "betweenCompileWrite", "beforeWrite", "write", "afterWrite", "complete"] 25 | buildExtension: ["init", "beforeRead", "read", "afterRead", "betweenReadCompile", "beforeCompile", "compile", "afterCompile", "betweenCompileWrite", "beforeWrite", "write", "afterWrite", "complete"] 26 | postBuild: ["init", "betweenInitOptimize", "beforeOptimize", "optimize", "afterOptimize", "beforeServer", "server", "afterServer", "beforePackage", "package", "afterPackage", "beforeInstall", "install", "afterInstall", "complete"] 27 | 28 | add: ["init", "beforeRead", "read", "afterRead", "betweenReadCompile", "beforeCompile", "compile", "afterCompile", "betweenCompileWrite", "beforeWrite", "write", "afterWrite", "betweenWriteOptimize", "beforeOptimize", "optimize", "afterOptimize", "complete"] 29 | update: ["init", "beforeRead", "read", "afterRead", "betweenReadCompile", "beforeCompile", "compile", "afterCompile", "betweenCompileWrite", "beforeWrite", "write", "afterWrite", "betweenWriteOptimize", "beforeOptimize", "optimize", "afterOptimize", "complete"] 30 | remove: ["init", "beforeRead", "read", "afterRead", "beforeDelete", "delete", "afterDelete", "beforeCompile", "compile", "afterCompile", "betweenCompileWrite", "beforeWrite", "write", "afterWrite", "betweenWriteOptimize", "beforeOptimize", "optimize", "afterOptimize", "complete"] 31 | 32 | constructor: (@config, modules, @buildDoneCallback) -> 33 | compilers.setupCompilers(@config) 34 | 35 | @types = _.clone(@masterTypes, true) 36 | for type, steps of @types 37 | @registration[type] = {} 38 | for step in steps 39 | @registration[type][step] = {} 40 | 41 | @allExtensions = [] 42 | for module in modules 43 | if module.registration 44 | module.registration(@config, @register) 45 | @allExtensions = _.uniq(@allExtensions) 46 | 47 | @_cleanUpRegistration() 48 | #@_determineFileCount() 49 | 50 | initClean: (cb) => 51 | @_init "preClean", cb 52 | 53 | initBuild: (cb) => 54 | @_init "preBuild", cb 55 | 56 | postClean: (cb) => 57 | @_executeWorkflowStep {}, 'postClean', cb 58 | 59 | # ready is called when all files are initially encountered on startup 60 | # can't do anything with this because mimosa cares not when the files 61 | # are encountered, but when mimosa's intial workflows are done processing 62 | # those files. However, if ready is called and there were no files 63 | # processed, then there are no source code files. Need to info 64 | # that to users and then continue on with further workflows 65 | ready: => 66 | if @initialFileCount is 0 67 | logger.info "No files to be processed" 68 | @_initialFileProcessingDone() 69 | 70 | clean: (fileName) => 71 | @_executeWorkflowStep(@_buildAssetOptions(fileName), 'cleanFile') 72 | 73 | update: (fileName) => 74 | @_executeWorkflowStep(@_buildAssetOptions(fileName), 'update') 75 | 76 | remove: (fileName) => 77 | @_executeWorkflowStep(@_buildAssetOptions(fileName), 'remove') 78 | 79 | add: (fileName) => 80 | if @startup 81 | @_executeWorkflowStep(@_buildAssetOptions(fileName), 'buildFile') 82 | else 83 | @_executeWorkflowStep(@_buildAssetOptions(fileName), 'add') 84 | 85 | register: (types, step, callback, extensions = ['*']) => 86 | unless Array.isArray(types) 87 | return logger.warn "Workflow types not passed in as array: [[ #{types} ]], ending registration for module." 88 | 89 | unless Array.isArray(extensions) 90 | return logger.warn "Workflow extensions not passed in as array: [[ #{extensions} ]], ending registration for module." 91 | 92 | unless typeof step is "string" 93 | return logger.warn "Workflow step not passed in as string: [[ #{step} ]], ending registration for module." 94 | 95 | unless _.isFunction(callback) 96 | return logger.warn "Workflow callback not passed in as function: [[ #{callback} ]], ending registration for module." 97 | 98 | for type in types 99 | 100 | unless @types[type]? 101 | return logger.warn "Unrecognized workflow type [[ #{type} ]], valid types are [[ #{Object.keys(@types).join(',')} ]], ending registration for module." 102 | 103 | if @types[type].indexOf(step) < 0 104 | return logger.warn "Unrecognized workflow step [[ #{step} ]] for type [[ #{type} ]], valid steps are [[ #{@types[type]} ]]" 105 | 106 | # no registering the same extension twice 107 | for extension in _.uniq(extensions) 108 | 109 | extension = extension.toLowerCase() 110 | 111 | if @registration[type][step][extension]? 112 | if @registration[type][step][extension].indexOf(callback) >= 0 113 | logger.debug "Callback already registered for this extension, ignoring:", type, step, extension 114 | continue 115 | else 116 | @registration[type][step][extension] ?= [] 117 | 118 | @allExtensions.push extension 119 | @registration[type][step][extension].push callback 120 | 121 | _init: (step, cb) => 122 | @_executeWorkflowStep {}, step, => 123 | # need to redetermine initial file count 124 | @_determineFileCount() 125 | cb() 126 | 127 | _determineFileCount: => 128 | w = @config.watch 129 | files = fileUtils.readdirSyncRecursive(w.sourceDir, w.exclude, w.excludeRegex, true).filter (f) => 130 | ext = path.extname(f).substring(1) 131 | ext.length >= 1 and @allExtensions.indexOf(ext) >= 0 132 | @initialFileCount = files.length 133 | # @initialFiles = files.map (f) => path.join @config.watch.sourceDir, f 134 | 135 | _cleanUpRegistration: => 136 | #logger.debug "Cleaning up unused workflow steps" 137 | 138 | for type, typeReg of @registration 139 | for step, stepReg of typeReg 140 | if Object.keys(stepReg).length is 0 141 | i = 0 142 | for st in @types[type] 143 | if st is step 144 | @types[type].splice(i,1) 145 | break 146 | i++ 147 | delete typeReg[step] 148 | 149 | _initialFileProcessingDone: => 150 | if @config.isClean 151 | if @buildDoneCallback? then @buildDoneCallback() 152 | else 153 | @_buildExtensions() 154 | 155 | _buildAssetOptions: (fileName) -> 156 | ext = path.extname(fileName).toLowerCase() 157 | ext = if ext.length > 1 158 | ext.substring(1) 159 | else 160 | # is dot file? 161 | baseName = path.basename( fileName ) 162 | if baseName.indexOf(".") is 0 and baseName.length > 1 163 | baseName.substring(1) 164 | else 165 | # no extension found 166 | '' 167 | 168 | {inputFile:fileName, extension:ext} 169 | 170 | _executeWorkflowStep: (options, type, done = @_finishedWithFile) -> 171 | options.lifeCycleType = type 172 | 173 | if options.inputFile? 174 | if options.extension.length is 0 and fs.existsSync(options.inputFile) and fs.statSync(options.inputFile).isDirectory() 175 | return logger.debug "Not handling directory [[ #{options.inputFile} ]]" 176 | 177 | # if processing a file, and the file's extension isn't in the list, boot it 178 | if @allExtensions.indexOf(options.extension) is -1 179 | if options.extension?.length is 0 180 | return logger.debug "No extension detected [[ #{options.inputFile} ]]." 181 | else 182 | return logger.warn "No module has registered for extension: [[ #{options.extension} ]], file: [[ #{options.inputFile} ]]" 183 | 184 | if @config.timer && @config.timer.enabled 185 | options.timer = { 186 | start: process.hrtime() 187 | } 188 | 189 | i = 0 190 | next = => 191 | if i < @types[type].length 192 | @_workflowMethod type, @types[type][i++], options, cb 193 | else 194 | # finished naturally 195 | done(options) 196 | 197 | cb = (nextVal) => 198 | if _.isBoolean(nextVal) and not nextVal 199 | done(options) 200 | else 201 | next() 202 | 203 | next() 204 | 205 | _workflowMethod: (type, _step, options, done) -> 206 | tasks = [] 207 | ext = options.extension 208 | step = @registration[type][_step] 209 | if step[ext]? then tasks.push step[ext]... 210 | if step['*']? then tasks.push step['*']... 211 | 212 | i = 0 213 | next = => 214 | if i < tasks.length 215 | #logger.debug "Calling workflow: [[ #{type} ]], [[ #{_step} ]], [[ #{options.extension} ]], [[ #{options.inputFile} ]]" 216 | #logger.debug options.files?.length 217 | tasks[i++](@config, options, cb) 218 | else 219 | # natural finish to workflow step 220 | done() 221 | 222 | cb = (nextVal) -> 223 | if _.isBoolean(nextVal) and not nextVal 224 | done(false) 225 | # no error, natural stop to workflow 226 | else 227 | # go to the next one 228 | next() 229 | 230 | next() 231 | 232 | _finishedWithFile: (options) => 233 | if logger.isDebug() 234 | logger.debug "Finished with file [[ #{options.inputFile} ]]" 235 | 236 | # @_writeTime(options) 237 | 238 | #@doneFiles.push(options.inputFile) 239 | #console.log _.difference(@initialFiles, @doneFiles) 240 | #console.log "finished #{@initialFilesHandled + 1} of #{@initialFileCount}" 241 | if @startup and ++@initialFilesHandled is @initialFileCount 242 | @_initialFileProcessingDone() 243 | 244 | _buildExtensions: => 245 | @startup = false 246 | done = 0 247 | # go through startup for each extension 248 | @allExtensions.forEach (extension) => 249 | @_executeWorkflowStep {extension:extension}, 'buildExtension', => 250 | @_buildDone() if ++done is @allExtensions.length 251 | 252 | _buildDone: => 253 | # wrap up, buildDone 254 | @_executeWorkflowStep {}, 'postBuild', => 255 | if @buildDoneCallback? then @buildDoneCallback() 256 | 257 | _writeTime: (options) -> 258 | if options.timer 259 | options.timer.end = process.hrtime() 260 | timeStart = options.timer.start[0] * 1000 + options.timer.start[1] / 1000 261 | timeEnd = options.timer.end[0] * 1000 + options.timer.end[1] / 1000 262 | time = Math.round(timeEnd - timeStart) 263 | if time > 1750 264 | time = (time / 1000).toFixed(2) + " milliseconds" 265 | else 266 | time = time + " microseconds" 267 | 268 | logger.info "Finished with file [[ #{options.inputFile} ]] in [[ #{time} ]]" -------------------------------------------------------------------------------- /lib/modules/compilers/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var CSSCompiler, JavaScriptCompiler, MiscCompiler, TemplateCompiler, compilers, fs, logger, path, templateLibrariesBeingUsed, _, _testDifferentTemplateLibraries; 3 | 4 | path = require('path'); 5 | 6 | fs = require('fs'); 7 | 8 | _ = require('lodash'); 9 | 10 | logger = require('logmimosa'); 11 | 12 | JavaScriptCompiler = require("./javascript"); 13 | 14 | CSSCompiler = require("./css"); 15 | 16 | TemplateCompiler = require("./template"); 17 | 18 | MiscCompiler = require("./misc"); 19 | 20 | compilers = []; 21 | 22 | templateLibrariesBeingUsed = 0; 23 | 24 | _testDifferentTemplateLibraries = function(config, options, next) { 25 | var hasFiles, _ref; 26 | hasFiles = ((_ref = options.files) != null ? _ref.length : void 0) > 0; 27 | if (!hasFiles) { 28 | return next(); 29 | } 30 | if (typeof config.template.outputFileName !== "string") { 31 | return next(); 32 | } 33 | if (++templateLibrariesBeingUsed === 2) { 34 | logger.error("More than one template library is being used, but multiple template.outputFileName entries not found." + " You will want to configure a map of template.outputFileName entries in your config, otherwise you will only get" + " template output for one of the libraries."); 35 | } 36 | return next(); 37 | }; 38 | 39 | exports.setupCompilers = function(config) { 40 | var backloadCompilers, compiler, copyMisc, extensions, exts, mod, modName, type, _i, _len, _ref, _ref1, _ref2; 41 | if (compilers.length) { 42 | compilers = []; 43 | } 44 | _ref = config.installedModules; 45 | for (modName in _ref) { 46 | mod = _ref[modName]; 47 | if (mod.compilerType) { 48 | if (logger.isDebug()) { 49 | logger.debug("Found compiler [[ " + mod.name + " ]], adding to array of compilers"); 50 | } 51 | compilers.push(mod); 52 | } 53 | } 54 | for (_i = 0, _len = compilers.length; _i < _len; _i++) { 55 | compiler = compilers[_i]; 56 | exts = compiler.extensions(config); 57 | (_ref1 = config.extensions[compiler.compilerType]).push.apply(_ref1, exts); 58 | } 59 | _ref2 = config.extensions; 60 | for (type in _ref2) { 61 | extensions = _ref2[type]; 62 | config.extensions[type] = _.uniq(extensions); 63 | } 64 | if (config.resortCompilers) { 65 | backloadCompilers = ["copy", "misc"]; 66 | copyMisc = _.remove(compilers, function(comp) { 67 | return backloadCompilers.indexOf(comp.compilerType) > -1; 68 | }); 69 | return compilers = compilers.concat(copyMisc); 70 | } 71 | }; 72 | 73 | exports.registration = function(config, register) { 74 | var CompilerClass, compiler, compilerInstance, _i, _len; 75 | for (_i = 0, _len = compilers.length; _i < _len; _i++) { 76 | compiler = compilers[_i]; 77 | if (logger.isDebug()) { 78 | logger.debug("Creating compiler " + compiler.name); 79 | } 80 | CompilerClass = (function() { 81 | switch (compiler.compilerType) { 82 | case "copy": 83 | return MiscCompiler; 84 | case "misc": 85 | return MiscCompiler; 86 | case "javascript": 87 | return JavaScriptCompiler; 88 | case "template": 89 | return TemplateCompiler; 90 | case "css": 91 | return CSSCompiler; 92 | } 93 | })(); 94 | compilerInstance = new CompilerClass(config, compiler); 95 | compilerInstance.name = compiler.name; 96 | compilerInstance.registration(config, register); 97 | if (compiler.registration) { 98 | compiler.registration(config, register); 99 | } 100 | if (logger.isDebug()) { 101 | logger.debug("Done with compiler " + compiler.name); 102 | } 103 | } 104 | if (config.template) { 105 | return register(['buildExtension'], 'complete', _testDifferentTemplateLibraries, config.extensions.template); 106 | } 107 | }; 108 | 109 | exports.defaults = function() { 110 | return { 111 | template: { 112 | writeLibrary: true, 113 | wrapType: "amd", 114 | commonLibPath: null, 115 | nameTransform: "fileName", 116 | outputFileName: "javascripts/templates" 117 | } 118 | }; 119 | }; 120 | 121 | exports.placeholder = function() { 122 | return "\t\n\n template: # overall template object can be set to null if no\n # templates being used\n writeLibrary: true # Whether or not to write a client library for\n # any template compilers\n nameTransform: \"fileName\" # means by which Mimosa creates the name for each\n # template, options: default \"fileName\" is name of file,\n # \"filePath\" is path of file after watch.sourceDir\n # with the extension dropped, a supplied regex can be\n # used to remove any unwanted portions of the filePath,\n # and a provided function will be called with the\n # filePath as input\n wrapType: \"amd\" # The type of module wrapping for the output templates\n # file. Possible values: \"amd\", \"common\", \"none\".\n commonLibPath: null # Valid when wrapType is 'common'. The path to the\n # client library. Some libraries do not have clients\n # therefore this is not strictly required when choosing\n # the common wrapType.\n outputFileName: \"javascripts/templates\" # the file all templates are compiled into,\n # is relative to watch.sourceDir.\n\n # outputFileName: # outputFileName Alternate Config 1\n # hogan:\"hogans\" # Optionally outputFileName can be provided an object of\n # jade:\"jades\" # compiler name to file name in the event you are using\n # multiple templating libraries.\n\n # output: [{ # output Alternate Config 2\n # folders:[\"\"] # Use output instead of outputFileName if you want\n # outputFileName: \"\" # to break up your templates into multiple files, for\n # }] # instance, if you have a two page app and want the\n # templates for each page to be built separately.\n # For each entry, provide an array of folders that\n # contain the templates to combine. folders entries are\n # relative to watch.sourceDir and must exist.\n # outputFileName works identically to outputFileName\n # above, including the alternate config, however, no\n # default file name is assumed. An output name must be\n # provided for each output entry, and the names\n # must be unique.\n"; 123 | }; 124 | 125 | exports.validate = function(config, validators) { 126 | var errors, fName, fileNames, folder, newFolders, outputConfig, tComp, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2; 127 | errors = []; 128 | if (validators.ifExistsIsObject(errors, "template config", config.template)) { 129 | validators.ifExistsIsBoolean(errors, "template.writeLibrary", config.template.writeLibrary); 130 | if (config.template.output && config.template.outputFileName) { 131 | delete config.template.outputFileName; 132 | } 133 | if (validators.ifExistsIsBoolean(errors, "template.amdWrap", config.template.amdWrap)) { 134 | logger.warn("template.amdWrap has been deprecated and support will be removed with a future release. Use template.wrapType."); 135 | if (config.template.amdWrap) { 136 | config.template.wrapType = "amd"; 137 | } else { 138 | config.template.wrapType = "none"; 139 | } 140 | } 141 | if (validators.ifExistsIsString(errors, "template.wrapType", config.template.wrapType)) { 142 | if (["common", "amd", "none"].indexOf(config.template.wrapType) === -1) { 143 | errors.push("template.wrapType must be one of: 'common', 'amd', 'none'"); 144 | } 145 | } 146 | if (config.template.nameTransform != null) { 147 | if (typeof config.template.nameTransform === "string") { 148 | if (["fileName", "filePath"].indexOf(config.template.nameTransform) === -1) { 149 | errors.push("config.template.nameTransform valid string values are filePath or fileName"); 150 | } 151 | } else if (typeof config.template.nameTransform === "function" || config.template.nameTransform instanceof RegExp) { 152 | 153 | } else { 154 | errors.push("config.template.nameTransform property must be a string, regex or function"); 155 | } 156 | } 157 | if (config.template.outputFileName != null) { 158 | config.template.output = [ 159 | { 160 | folders: [""], 161 | outputFileName: config.template.outputFileName 162 | } 163 | ]; 164 | } 165 | if (validators.ifExistsIsArrayOfObjects(errors, "template.output", config.template.output)) { 166 | fileNames = []; 167 | _ref = config.template.output; 168 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 169 | outputConfig = _ref[_i]; 170 | if (validators.isArrayOfStringsMustExist(errors, "template.templateFiles.folders", outputConfig.folders)) { 171 | if (outputConfig.folders.length === 0) { 172 | errors.push("template.templateFiles.folders must have at least one entry"); 173 | } else { 174 | newFolders = []; 175 | _ref1 = outputConfig.folders; 176 | for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { 177 | folder = _ref1[_j]; 178 | folder = path.join(config.watch.sourceDir, folder); 179 | if (!fs.existsSync(folder)) { 180 | errors.push("template.templateFiles.folders must exist, folder resolved to [[ " + folder + " ]]"); 181 | } 182 | newFolders.push(folder); 183 | } 184 | outputConfig.folders = newFolders; 185 | } 186 | } 187 | if (outputConfig.outputFileName != null) { 188 | fName = outputConfig.outputFileName; 189 | if (typeof fName === "string") { 190 | fileNames.push(fName); 191 | } else if (typeof fName === "object" && !Array.isArray(fName)) { 192 | _ref2 = Object.keys(fName); 193 | for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { 194 | tComp = _ref2[_k]; 195 | fileNames.push(fName[tComp]); 196 | } 197 | } else { 198 | errors.push("template.outputFileName must be an object or a string."); 199 | } 200 | } else { 201 | errors.push("template.output.outputFileName must exist for each entry in array."); 202 | } 203 | } 204 | if (fileNames.length !== _.uniq(fileNames).length) { 205 | errors.push("template.output.outputFileName names must be unique."); 206 | } 207 | } 208 | } 209 | return errors; 210 | }; 211 | -------------------------------------------------------------------------------- /src/modules/compilers/template.coffee: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | path = require 'path' 4 | fs = require 'fs' 5 | 6 | _ = require 'lodash' 7 | logger = require 'logmimosa' 8 | 9 | fileUtils = require '../../util/file' 10 | 11 | __generateTemplateName = (fileName, config) -> 12 | nameTransform = config.template.nameTransform 13 | if nameTransform is "fileName" 14 | path.basename fileName, path.extname(fileName) 15 | else 16 | # only sourceDir forward 17 | filePath = fileName.replace config.watch.sourceDir, '' 18 | # normalize to unix file seps, slice off first one 19 | filePath = filePath.split(path.sep).join('/').substring(1) 20 | # remove ext 21 | filePath = filePath.replace(path.extname(filePath), '') 22 | if nameTransform is "filePath" 23 | filePath 24 | else 25 | returnFilepath = if nameTransform instanceof RegExp 26 | filePath.replace nameTransform, '' 27 | else 28 | nameTransform filePath 29 | 30 | if typeof returnFilepath isnt "string" 31 | logger.error "Application of template.nameTransform for file [[ #{fileName} ]] did not result in string", {exitIfBuild:true} 32 | "nameTransformFailed" 33 | else 34 | returnFilepath 35 | 36 | __removeClientLibrary = (clientPath, cb) -> 37 | if clientPath? 38 | fs.exists clientPath, (exists) -> 39 | if exists 40 | if logger.isDebug() 41 | logger.debug "Removing client library [[ #{clientPath} ]]" 42 | fs.unlink clientPath, (err) -> 43 | logger.success "Deleted file [[ #{clientPath} ]]" unless err 44 | cb() 45 | else 46 | cb() 47 | else 48 | cb() 49 | 50 | __testForSameTemplateName = (files) -> 51 | nameHash = {} 52 | files.forEach (file) -> 53 | templateName = file.tName 54 | fileName = file.fName 55 | if nameHash[templateName] 56 | logger.error "Files [[ #{nameHash[templateName]} ]] and [[ #{fileName} ]] result in templates of the same name " + 57 | "being created. You will want to change the name for one of them or they will collide." 58 | else 59 | nameHash[templateName] = fileName 60 | 61 | __templatePreamble = (file) -> 62 | """ 63 | \n// 64 | // Source file: [#{file.inputFileName}] 65 | // Template name: [#{file.templateName}] 66 | //\n 67 | """ 68 | 69 | __destFile = (config) -> 70 | (compilerName, folders) -> 71 | for outputConfig in config.template.output 72 | if outputConfig.folders is folders 73 | outputFileName = outputConfig.outputFileName 74 | if outputFileName[compilerName] 75 | return path.join(config.watch.compiledDir, outputFileName[compilerName] + ".js") 76 | else 77 | return path.join(config.watch.compiledDir, outputFileName + ".js") 78 | 79 | _init = (config, options, next) -> 80 | # if processing a file, check and see if that file 81 | # is inside a folder to be wrapped up in template file 82 | # before laying claim to that file for the template compiler 83 | if options.inputFile 84 | for outputFileConfig in config.template.output 85 | for folder in outputFileConfig.folders 86 | if options.inputFile.indexOf(path.join(folder, path.sep)) is 0 87 | options.isTemplateFile = true 88 | options.destinationFile = __destFile(config); 89 | return next() 90 | else 91 | # if not processing a file, then processing extension 92 | # in which case lay claim to the extension as it 93 | # was specifically registered 94 | options.isTemplateFile = true 95 | options.destinationFile = __destFile(config); 96 | 97 | next() 98 | 99 | module.exports = class TemplateCompiler 100 | 101 | constructor: (config, @compiler) -> 102 | @extensions = @compiler.extensions(config) 103 | 104 | if @compiler.clientLibrary and (config.template.wrapType is 'amd' or config.template.writeLibrary) 105 | @clientPath = path.basename(@compiler.clientLibrary) 106 | @clientPath = path.join config.vendor.javascripts, @clientPath 107 | @clientPath = @clientPath.replace config.watch.sourceDir, config.watch.compiledDir 108 | compiledJs = path.join config.watch.compiledDir, config.watch.javascriptDir 109 | @libPath = @clientPath.replace(compiledJs, '').substring(1).split(path.sep).join('/') 110 | @libPath = @libPath.replace(path.extname(@libPath), '') 111 | 112 | registration: (config, register) -> 113 | @requireRegister = config.installedModules['mimosa-require'] 114 | 115 | register ['add','update','remove','buildExtension','buildFile'], 'init', _init, @extensions 116 | 117 | register ['buildExtension'], 'init', @_gatherFiles, [@extensions[0]] 118 | register ['add','update','remove'], 'init', @_gatherFiles, @extensions 119 | register ['buildExtension'], 'compile', @_compile, [@extensions[0]] 120 | register ['add','update','remove'], 'compile', @_compile, @extensions 121 | 122 | register ['cleanFile'], 'init', @_removeFiles, @extensions 123 | 124 | register ['buildExtension'], 'afterCompile', @_merge, [@extensions[0]] 125 | register ['add','update','remove'], 'afterCompile', @_merge, @extensions 126 | 127 | if config.template.writeLibrary 128 | register ['remove'], 'init', @_testForRemoveClientLibrary, @extensions 129 | 130 | register ['add','update'], 'afterCompile', @_readInClientLibrary, @extensions 131 | register ['buildExtension'], 'afterCompile', @_readInClientLibrary, [@extensions[0]] 132 | 133 | _gatherFiles: (config, options, next) => 134 | return next() unless options.isTemplateFile 135 | 136 | options.files = [] 137 | 138 | # consider simplifying if init takes care of whether or not 139 | # compiler should be invoked 140 | for outputFileConfig in config.template.output 141 | if options.inputFile? 142 | for folder in outputFileConfig.folders 143 | if options.inputFile.indexOf(path.join(folder, path.sep)) is 0 144 | @__gatherFolderFilesForOutputFileConfig(config, options, outputFileConfig.folders) 145 | break 146 | else 147 | @__gatherFolderFilesForOutputFileConfig(config, options, outputFileConfig.folders) 148 | 149 | next(options.files.length > 0) 150 | 151 | __gatherFolderFilesForOutputFileConfig: (config, options, folders) => 152 | for folder in folders 153 | for folderFile in @__gatherFilesForFolder(config, options, folder) 154 | if _.pluck(options.files, 'inputFileName').indexOf(folderFile.inputFileName) is -1 155 | options.files.push folderFile 156 | 157 | __gatherFilesForFolder: (config, options, folder) => 158 | allFiles = fileUtils.readdirSyncRecursive(folder, config.watch.exclude, config.watch.excludeRegex) 159 | 160 | fileNames = [] 161 | for file in allFiles 162 | extension = path.extname(file).substring(1) 163 | if _.any(@extensions, (e) -> e is extension) 164 | fileNames.push(file) 165 | 166 | if fileNames.length is 0 167 | [] 168 | else 169 | fileNames.map (file) -> 170 | inputFileName:file 171 | inputFileText:null 172 | outputFileText:null 173 | 174 | _compile: (config, options, next) => 175 | return next() unless options.isTemplateFile 176 | return next() unless options.files?.length 177 | 178 | newFiles = [] 179 | options.files.forEach (file, i) => 180 | if logger.isDebug() 181 | logger.debug "Compiling template [[ #{file.inputFileName} ]]" 182 | file.templateName = __generateTemplateName(file.inputFileName, config) 183 | @compiler.compile config, file, (err, result) => 184 | if err 185 | logger.error "Template [[ #{file.inputFileName} ]] failed to compile. Reason: #{err}", {exitIfBuild:true} 186 | else 187 | unless @compiler.handlesNamespacing 188 | result = "templates['#{file.templateName}'] = #{result}\n" 189 | file.outputFileText = result 190 | newFiles.push file 191 | 192 | if i is options.files.length-1 193 | options.files = newFiles 194 | next() 195 | 196 | _merge: (config, options, next) => 197 | return next() unless options.isTemplateFile 198 | return next() unless options.files?.length 199 | 200 | libPath = @__libraryPath() 201 | prefix = @compiler.prefix config, libPath 202 | suffix = @compiler.suffix config 203 | 204 | for outputFileConfig in config.template.output 205 | 206 | # if post-build, need to check to see if the outputFileConfig is valid for this compile 207 | if options.inputFile 208 | found = false 209 | for folder in outputFileConfig.folders 210 | if options.inputFile.indexOf(folder) is 0 211 | found = true 212 | break 213 | continue unless found 214 | 215 | mergedText = "" 216 | mergedFiles = [] 217 | options.files.forEach (file) => 218 | for folder in outputFileConfig.folders 219 | if file.inputFileName?.indexOf(path.join(folder, path.sep)) is 0 220 | mergedFiles.push {tName: file.templateName, fName: file.inputFileName} 221 | unless config.isOptimize 222 | mergedText += __templatePreamble file 223 | mergedText += file.outputFileText 224 | break 225 | 226 | __testForSameTemplateName(mergedFiles) if mergedFiles.length > 1 227 | 228 | continue if mergedText is "" 229 | 230 | options.files.push 231 | outputFileText: prefix + mergedText + suffix 232 | outputFileName: options.destinationFile(@compiler.name, outputFileConfig.folders) 233 | isTemplate:true 234 | 235 | next() 236 | 237 | _removeFiles: (config, options, next) => 238 | total = if config.template.output 239 | config.template.output.length + 1 240 | else 241 | 2 242 | 243 | i = 0 244 | done = -> 245 | next() if ++i is total 246 | 247 | __removeClientLibrary(@clientPath, done) 248 | createDestFile = __destFile(config) 249 | for outputFileConfig in config.template.output 250 | outFile = createDestFile(@compiler.name, outputFileConfig.folders) 251 | __removeClientLibrary(outFile, done) 252 | 253 | _testForRemoveClientLibrary: (config, options, next) => 254 | return next() unless options.isTemplateFile 255 | 256 | if options.files?.length is 0 257 | logger.info "No template files left, removing template based assets" 258 | @_removeFiles(config, options, next) 259 | else 260 | next() 261 | 262 | _readInClientLibrary: (config, options, next) => 263 | return next() unless options.isTemplateFile 264 | 265 | if !@clientPath? or fs.existsSync @clientPath 266 | logger.debug "Not going to write template client library" 267 | return next() 268 | 269 | if logger.isDebug() 270 | logger.debug "Adding template client library [[ #{@compiler.clientLibrary} ]] to list of files to write" 271 | 272 | fs.readFile @compiler.clientLibrary, "utf8", (err, data) => 273 | if err 274 | logger.error("Cannot read client library [[ #{@compiler.clientLibrary} ]]") 275 | return next() 276 | 277 | options.files.push 278 | outputFileName: @clientPath 279 | outputFileText: data 280 | 281 | next() 282 | 283 | __libraryPath: => 284 | if @requireRegister 285 | @requireRegister.aliasForPath(@libPath) ? @requireRegister.aliasForPath("./" + @libPath) ? @libPath 286 | else 287 | @libPath 288 | --------------------------------------------------------------------------------