this.assets = function(grunt) { if(fs.existsSync('.whdist')) { wrench.rmdirSyncRecursive('.whdist'); } mkdirp.sync('.whdist'); var files = wrench.readdirSyncRecursive('pages'); files.forEach(function(file) { var originalFile = 'pages/' + file; var destFile = '.whdist/pages/' + file; if(!fs.lstatSync(originalFile).isDirectory()) { var content = fs.readFileSync(originalFile); if(path.extname(originalFile) === '.html') { content = content.toString(); content = content.replace('\r\n', '\n').replace('\r', '\n'); } mkdirp.sync(path.dirname(destFile)); fs.writeFileSync(destFile, content); } }); files = wrench.readdirSyncRecursive('templates'); files.forEach(function(file) { var originalFile = 'templates/' + file; var destFile = '.whdist/templates/' + file; if(!fs.lstatSync(originalFile).isDirectory()) { var content = fs.readFileSync(originalFile); if(path.extname(originalFile) === '.html') { content = content.toString(); content = content.replace('\r\n', '\n').replace('\r', '\n'); } mkdirp.sync(path.dirname(destFile)); fs.writeFileSync(destFile, content); } }); files = wrench.readdirSyncRecursive('static'); files.forEach(function(file) { var originalFile = 'static/' + file; var destFile = '.whdist/static/' + file; if(!fs.lstatSync(originalFile).isDirectory()) { var content = fs.readFileSync(originalFile); if(path.extname(originalFile) === '.html') { content = content.toString(); content = content.replace('\r\n', '\n').replace('\r', '\n'); } mkdirp.sync(path.dirname(destFile)); fs.writeFileSync(destFile, content); } }); grunt.task.run('useminPrepare'); grunt.task.run('assetsMiddle'); }
import wrench from 'wrench'; import path from 'path'; // Load all gulp plugins based on their names // EX: gulp-copy -> copy const plugins = gulpLoadPlugins(); let config = pjson.config; let taskName = process.argv[2]; let dirs = config.directories; console.log(path.resolve(dirs.destination)); // This will grab all js in the `gulp` directory // in order to load all gulp tasks wrench.readdirSyncRecursive('./gulp').filter((file) => { return (/\.(js)$/i).test(file); }).map(function(file) { require('./gulp/' + file)(gulp, plugins, taskName, config, path.resolve(dirs.destination)); }); // Default task gulp.task('default', [], () => { gulp.start('build'); }); // Build production-ready code gulp.task('build', [ 'sass', 'browserify' ]);
setup(function(){ lib = wrench.readdirSyncRecursive(path.join(__dirname,'..','..','lib')); require(path.join(__dirname,'..','..')); });
function remove(opts) { opts = opts || {}; var folder = opts.folder; var exceptions = opts.exceptions || []; var types = _.isString(opts.types) ? [opts.types] : opts.types; var locations = _.isString(opts.locations) ? [opts.locations] : opts.locations; var runtimePath = opts.runtimePath; // Set the base runtime search path if (!runtimePath) { if (!folder) { U.die([ 'You must specify either "runtimePath" or "folder" when calling Orphanage.remove()', new Error().stack ]); } else { if (opts.widgetId) { runtimePath = path.join(dirs.runtime, CONST.DIR.WIDGET, opts.widgetId, folder); } else { runtimePath = path.join(dirs.runtime, folder); } } } // skip if the target runtime folder doesn't exist if (!fs.existsSync(runtimePath)) { return; } // Let's see if we need to delete any orphan files... _.each(wrench.readdirSyncRecursive(runtimePath), function(file) { var runtimeFullpath = path.join(runtimePath, file); var found = false; var checks, i; // skip if file no longer exists, or if it's an exception if (!fs.existsSync(runtimeFullpath) || (/*!opts.widgetId && */ isException(file, exceptions))) { return; } // Get a list of app folder locations to check for a match against the runtime file checks = getChecks(file, runtimeFullpath, _.extend({ widgetId: opts.widgetId }, types ? { types: types } : { locations: locations })); // if checks is null, we already know we can skip it if (checks === null) { return; } // If we find the corresponding app folder file(s), skip this file for (i = 0; i < checks.length; i++) { if (!opts.widgetId && fs.existsSync(checks[i])) { found = true; return; } } // It's an orphan, delete it if (!found) { // already deleted, perhaps a file in a deleted directory if (!fs.existsSync(runtimeFullpath)) { return; } logger.trace('* ' + file); // delete the directory or file var targetStat = fs.statSync(runtimeFullpath); if (targetStat.isDirectory()) { if(opts.widgetId) { // remove the widget's folder wrench.rmdirSyncRecursive(path.resolve(runtimeFullpath, '..'), true); } else { wrench.rmdirSyncRecursive(runtimeFullpath, true); } } else { fs.unlinkSync(runtimeFullpath); } } }); }
function packageApp() { var source = path.resolve(builder.buildDir), destination = path.resolve(source, '..', 'mobileweb-' + target), version = tiapp.version, templateData = { // general projectName: tiapp.id, projectDisplayName: tiapp.name, projectGUID: tiapp.guid || uuid.v4(), projectDescription: tiapp.description || 'No description', author: tiapp.publisher || config.get('user.name') || 'Titanium', appFiles: [], // windows phone specific assemblyGUID: uuid.v4(), publisherGUID: cli.argv['wp8-publisher-guid'], company: 'not specified', // Hopefully we can support this some day copyright: tiapp.copyright || ('Copyright © ' + new Date().getFullYear()), logToken: builder.logToken, targetSDK: cli.tiapp['windows-phone'] && cli.tiapp['windows-phone']['target-sdk'] || '', // windows store specific visualStudioVersion: env.visualStudioVersion, certificatePath: certificatePathRoot + '.pfx' }, templateDir = path.join(__dirname, '..', '..', 'templates', 'packages', target), filenameReplacementRegex = /\{\{ProjectName\}\}/g, templateFiles, appFiles = templateData.appFiles, versionFormatRegex = /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/; if (target == 'wp8') { templateFiles = [ '{{ProjectName}}.sln', path.join('{{ProjectName}}', '{{ProjectName}}.csproj'), path.join('{{ProjectName}}', 'titanium_settings.ini'), path.join('{{ProjectName}}', 'Resources', 'AppResources.Designer.cs'), path.join('{{ProjectName}}', 'Properties', 'AssemblyInfo.cs'), path.join('{{ProjectName}}', 'Properties', 'WMAppManifest.xml') ]; } else { templateFiles = [ '{{ProjectName}}.sln', path.join('{{ProjectName}}', '{{ProjectName}}.jsproj'), path.join('{{ProjectName}}', 'package.appxmanifest') ]; } // validate and format the version to Major.Minor.Build.Revision format necessary for MS build systems if (!version) { version = '0.1.0.0'; } else if (!versionFormatRegex.test(version)) { version = version.match(/^[0-9]+(\.[0-9]+)*/); if (!version) { logger.warn(__('Invalid project version number %s, setting to 0.1.0.0', tiapp.version)); version = '0.1.0.0'; } else { version = version[0]; while (!versionFormatRegex.test(version)) { version = version + '.0'; } logger.info(__('Project version number will be converted to %s for compatibility with Visual Studio', version)); } } templateData.projectVersion = tiapp._windowsVersion = version; // Create the destination folder if it doesn't exist if (!fs.existsSync(destination)) { wrench.mkdirSyncRecursive(destination); } // Copy the built app over logger.info(__('Copying Mobile Web output to the Visual Studio project')); wrench.mkdirSyncRecursive(path.join(destination, templateData.projectName, 'App')); wrench.readdirSyncRecursive(source).forEach(function (file) { var sourcePath = path.join(source, file), destinationPath = path.join(destination, templateData.projectName, 'App', file), fileStats = fs.statSync(sourcePath); if (fileStats.isDirectory()) { logger.debug(__('Creating directory %s', destinationPath.cyan)); wrench.mkdirSyncRecursive(destinationPath); } else if (fileStats.size === 0) { logger.warn(__('%s is empty and will not be copied over', sourcePath.cyan)); } else { logger.debug(__('Copying file %s => %s', sourcePath.cyan, destinationPath.cyan)); fs.writeFileSync(destinationPath, fs.readFileSync(sourcePath)); // Store the file for inclusion in the csproj file. appFiles.push(path.join('App', file)); } }); // Copy the template files over logger.info(__('Generating %s Visual Studio project', displayName)); wrench.readdirSyncRecursive(templateDir).forEach(function (file) { var sourcePath = path.join(templateDir, file), sourceData, destinationPath = path.join(destination, file.replace(filenameReplacementRegex, templateData.projectName || 'Project')); // If this is a folder, just create the destination folder directly if (fs.statSync(sourcePath).isDirectory()) { logger.debug(__('Creating directory %s', destinationPath.cyan)); wrench.mkdirSyncRecursive(destinationPath); } else { // Otherwise, run the file through EJS if it needs to be templated, else just copy it sourceData = fs.readFileSync(sourcePath); if (templateFiles.indexOf(file) != -1) { logger.debug(__('Generating file %s', destinationPath.cyan)); fs.writeFileSync(destinationPath, ejs.render(sourceData.toString(), templateData)); } else { logger.debug(__('Copying file %s => %s', sourcePath.cyan, destinationPath.cyan)); fs.writeFileSync(destinationPath, sourceData); } } }); // copy the tile icons logger.info(__('Copying tile icons')); var buildDir = builder.buildDir, m = builder.tiapp.icon.match(/^(.*)(?:\.(?:png|jpg|gif))$/), iconPrefix = m && m[1] != 'appicon' && m[1]; function copyTile(suffix, destFilename) { var file = path.join(builder.projectResDir, 'mobileweb', iconPrefix + suffix); if (!fs.existsSync(file)) { file = path.join(builder.projectResDir, iconPrefix + suffix); } if (!fs.existsSync(file)) { file = path.join(builder.projectResDir, 'mobileweb', 'appicon' + suffix); } if (!fs.existsSync(file)) { file = path.join(builder.projectResDir, 'appicon' + suffix); } if (fs.existsSync(file)) { appc.fs.copyFileSync(file, path.join(destination, tiapp.id, 'Assets', destFilename), { logger: logger.debug }); } } copyTile('.png', 'ApplicationIcon.png'); copyTile('-tile-small.png', 'Tiles\\FlipCycleTileSmall.png'); copyTile('-tile-medium.png', 'Tiles\\FlipCycleTileMedium.png'); copyTile('-tile-large.png', 'Tiles\\FlipCycleTileLarge.png'); // Compile the app var cmd = [ 'MSBuild', '/m', '/p:configuration=' + (cli.argv['deploy-type'] == 'production' ? 'Release' : 'Debug'), path.join(destination, tiapp.id + '.sln') ]; logger.info(__('Building the %s Visual Studio project', displayName)); logger.debug(__('Running: %s', cmd.join(' ').cyan)); run(cmd, function (code) { if (!code) { logger.info(__('Finished building the application')); } finished(code); }); }
assembleTitaniumCSS: function (callback) { var tiCSS = [ HEADER, '\n' ]; if (this.tiapp.mobileweb.splash.enabled) { var splashDir = this.projectResDir + '/mobileweb/splash', splashHtmlFile = splashDir + '/splash.html', splashCssFile = splashDir + '/splash.css'; if (afs.exists(splashDir)) { this.logger.info(__('Processing splash screen')); afs.exists(splashHtmlFile) && (this.splashHtml = fs.readFileSync(splashHtmlFile)); if (afs.exists(splashCssFile)) { var css = fs.readFileSync(splashCssFile).toString(); if (this.tiapp.mobileweb.splash['inline-css-images']) { var parts = css.split('url('), i = 1, p, img, imgPath, imgType, len = parts.length; for (; i < len; i++) { p = parts[i].indexOf(')'); if (p != -1) { img = parts[i].substring(0, p).replace(/["']/g, '').trim(); if (!/^data\:/.test(img)) { imgPath = img.charAt(0) == '/' ? this.projectResDir + img : splashDir + '/' + img; imgType = imageMimeTypes[imgPath.match(/(\.[a-zA-Z]{3})$/)[1]]; if (afs.exists(imgPath) && imgType) { parts[i] = 'data:' + imgType + ';base64,' + fs.readFileSync(imgPath).toString('base64') + parts[i].substring(p); } } } } css = parts.join('url('); } tiCSS.push(css); } } } this.logger.info(__('Assembling titanium.css')); var commonCss = this.mobilewebThemeDir + '/common.css'; afs.exists(commonCss) && tiCSS.push(fs.readFileSync(commonCss).toString()); // TODO: need to rewrite absolute paths for urls // TODO: code below does NOT inline imports, nor remove them... do NOT use imports until themes are fleshed out var themePath = this.projectResDir + '/themes/' + this.theme; afs.exists(themePath) || (themePath = this.projectResDir + '/' + this.theme); afs.exists(themePath) || (themePath = this.mobilewebSdkPath + '/themes/' + this.theme); if (!afs.exists(themePath)) { this.logger.error(__('Unable to locate theme "%s"', this.theme) + '\n'); process.exit(1); } wrench.readdirSyncRecursive(themePath).forEach(function (file) { /\.css$/.test(file) && tiCSS.push(fs.readFileSync(themePath + '/' + file).toString() + '\n'); }); // detect any fonts and add font face rules to the css file var fonts = {}; wrench.readdirSyncRecursive(this.projectResDir).forEach(function (file) { var match = file.match(/^(.+)(\.otf|\.woff)$/); if (match) { fonts[match[0]] || (fonts[match[0]] = []); fonts[match[0]].push(file); } }); Object.keys(fonts).forEach(function (name) { tiCSS.push('@font-face{font-family:' + name + ';src:url(' + fonts[name] + ');\n'); }); // TODO: minify the css // write the titanium.css fs.writeFileSync(this.buildDir + '/titanium.css', cleanCSS.process(tiCSS.join(''))); callback(); },
function parse(node, state, args) { var code = ''; var nodeName = node.nodeName; var isSingleton = node.getAttribute('instance') !== 'true'; var id = node.getAttribute('id'); var src = node.getAttribute('src'); var backboneVar; var manifest = CU.currentManifest; var root = manifest ? CONST.WIDGET_OBJECT : 'Alloy'; // Make sure the parent is <Alloy> if (!node.parentNode || node.parentNode.nodeName !== 'Alloy') { U.dieWithNode(node, 'All <' + nodeName + '> elements must be a direct child of <Alloy>'); } // only perform compile time checks on nodes in non-widget markup if (!manifest) { // Make sure there's models to be used as the "src" of the Backbone class var modelsPath = path.join(CU.getCompilerConfig().dir.home,CONST.DIR.MODEL); var validModels; if (!path.existsSync(modelsPath) || !(validModels = wrench.readdirSyncRecursive(modelsPath)).length) { U.dieWithNode(node, [ 'You must have a valid model in your app/' + CONST.DIR.MODEL + ' folder to create a <' + nodeName + '>', 'Once you have a valid model, assign it like this for a singleton:', ' <' + nodeName + ' src="yourModelName"/>', 'Or like this for an instance:', ' <' + nodeName + ' src="yourModelName" instance="true" id="someId"/>' ]); } // Make sure we have a valid model src var validModelsPrint = '[' + _.map(validModels, function(s) { return s.replace(/\.js$/,''); }).join(',') + ']'; if (!src) { U.dieWithNode(node, [ 'All ' + U.lcfirst(nodeName) + 's must have a "src" attribute which identifies its base model', '"src" should be the name of a model in your app/' + CONST.DIR.MODEL + ' folder', 'Valid models: ' + validModelsPrint ]); } else { var modelPath = path.join(modelsPath,src + '.' + CONST.FILE_EXT.MODEL); if (!path.existsSync(modelPath)) { U.dieWithNode(node, [ '"src" attribute\'s model "' + src + '" does not exist in app/' + CONST.DIR.MODEL, 'Valid models: ' + validModelsPrint ]); } } } var createCall = root + '.create' + nodeName + '(\'' + src + '\')'; // create code based on whether the collection is a singleton or instance if (isSingleton) { if (id) { logger.warn([ 'Warning with <' + nodeName + '> at line ' + node.lineNumber, 'id="' + id + '" will be ignored, as only ' + nodeName + ' instances can have ids, not singletons', 'To create an instance of the ' + nodeName + ', add instance="true"', 'This instance will be accessible in your controller as $.' + id, 'Example: ', ' <' + nodeName + ' src="' + src + '" id="' + id + '" instance="true"/>' ]); } code += root + '.' + nodeName + 's.instance(\'' + src + '\');'; // backboneVar = 'Alloy.' + nodeName + 's[\'' + src + '\']'; // code += backboneVar + ' || (' + backboneVar + ' = ' + createCall + ');'; } else { id || (id = args.id); backboneVar = '$.' + id; code += backboneVar + ' = ' + createCall + ';'; } return { code: '', modelCode: code, // modelCode will add this before the UI code args: { symbol: backboneVar } }; };
ExpressApi.prototype.init = function () { // Variables var self = this; // Here we are console.log('ExpressAPI: Configuring API endpoints'); // Routes directory var routesDir = ((this.ExpressConfig["endpoints"]["routes"]["relative"] === true ? process.cwd() : '') + this.ExpressConfig["endpoints"]["routes"]["path"]) .replace(/\/?$/, '/'); // Trailing slash; console.log('. Routes directory: ' + routesDir); // Either use provided or default endpoint directory var endpointDir = ((this.ExpressConfig["endpoints"]["configurations"]["relative"] === true ? process.cwd() : '') + this.ExpressConfig["endpoints"]["configurations"]["path"]) .replace(/\/?$/, '/'); // Trailing slash; console.log('. Endpoints directory: ' + endpointDir); // Receive all andpoint definition filenames (recursively) var endpointFilenames = wrench.readdirSyncRecursive(endpointDir); // Iterate through each endpoint definition for (var i = 0; i < endpointFilenames.length; i++) { var fileName = endpointDir + endpointFilenames[i]; // Skip directories if (fs.lstatSync(fileName).isDirectory()) { continue; } // Read one by one var endpointDefinitions = require(fileName); // Iterate through each route (ie /foobar/) for (var routeName in endpointDefinitions) { // Skip built-in meta properties if (!endpointDefinitions.hasOwnProperty(routeName)) { continue; } // Log what we are doing console.log('. Processing "' + routeName + '"'); // Persist on application layer this.ExpressApp.set(routeName, endpointDefinitions[routeName]); // Iterate through each method types (ie GET, POST) for (var routeVerb in endpointDefinitions[routeName]) { // Skip built-in meta information if (!endpointDefinitions[routeName].hasOwnProperty(routeVerb)) { continue; } // Contains the detailled configuration of a particular endpoint (ie GET:/foobar/) var configuration = endpointDefinitions[routeName][routeVerb]; // Skip if disabled if(endpointDefinitions[routeName][routeVerb]["disabled"] === true) { console.log('. . Skipped "' + routeVerb + ':' + routeName + '". Endpoint is disabled'); continue; } // Skip if not supported in current environment if (configuration["env"] != "*" && configuration["env"].indexOf(this.ExpressApp.get('env')) == -1) { console.log('. . Skipped "' + routeVerb + ':' + routeName + '". Environment "' + this.ExpressApp.get('env') + '" not supported'); continue; } // Require route (ie foobar-route) var route; try { route = require(routesDir + configuration['route']['file']); } catch(err) { console.log('. . Skipped "' + routeVerb + ':' + routeName + '". Route "' + configuration['route']['file'] + '" does not exist'); continue; } // If target action does not exist skjp this andpoint if (typeof route[configuration['route']['action']] !== 'function') { console.log('. . Skipped "' + routeVerb + ':' + routeName + '". Route "' + configuration['route']['file'] + '.' + configuration['route']['action'] + '" does not exist'); continue; } // Point to action // Thanks to Nicolas Traeder<*****@*****.**> // TODO Anonymous wrapper for onEndpointRequested may be redundant this.ExpressApp[routeVerb.toLowerCase()](routeName, function (req, res, next) { return self.onEndpointRequested(req, res, next); }, route[configuration['route']['action']]); // Log that we are done console.log('. . Published "' + routeVerb + ':' + routeName + '". Route "' + configuration['route']['file'] + '.' + configuration['route']['action'] + '"'); } } } // Configuration finished console.log('ExpressAPI: Up and running'); };
describe('alloy compile', function() { TU.addMatchers(); // Iterate through each test app and make sure it compiles for all platforms _.each(wrench.readdirSyncRecursive(paths.apps), function(file) { // are we testing only a specific app? if (process.env.app && file !== process.env.app) { return; } // TODO: Stop skipping the ui/navwindow test when TiSDK 3.1.3 is in the HarnessTemplate // tiapp.xml. We skip it now because it purposely fails compilation on any SDK below // TiSDK 3.1.3, where Ti.UI.iOS.NavigationWindow was introduced. if (file === 'ui'+sep+'navwindow' || file === 'testing'+sep+'ALOY-818' || file === 'testing'+sep+'ALOY-840') { return; } describe(file.yellow, function() { var indexJs = path.join(paths.apps,file,'controllers','index.js'); if (!path.existsSync(indexJs) || indexJs.indexOf(GEN_FOLDER) !== -1) { return; } it('preparing test app', function() { TU.asyncExecTest('jake app:setup dir=' + file + ' quiet=1', { timeout: TIMEOUT_PREP }); }); _.each(platforms, function(platform,k) { if (process.platform === 'darwin' && platform.platform === 'blackberry') { return; } describe(('[' + platform.platform + ']').cyan, function () { it('compiles without critical error', function() { TU.asyncExecTest( 'alloy compile ' + paths.harness + ' --config platform=' + platform.platform, { test: function() { // Make sure there were no compile errors if (file === 'testing'+sep+'ALOY-887') { // this test specifically tests a compiler error expect(this.output.error).toBeTruthy(); } else { expect(this.output.error).toBeFalsy(); } }, timeout: TIMEOUT_COMPILE } ); }); it('leaves no compiler directives in generated code', function() { var hrDir = path.join(paths.harness,'Resources'); var cPaths = [ path.join(hrDir,'alloy'), path.join(hrDir,platform.titaniumFolder,'alloy') ]; _.each(cPaths, function(cPath) { if (file === 'testing'+sep+'ALOY-887') { // skip this test since this app forces a compile error return; } if (!fs.existsSync(cPath)) { return; } var files = wrench.readdirSyncRecursive(cPath); _.each(files, function(file) { var fullpath = path.join(cPath,file); if (!fs.statSync(fullpath).isFile() || !/\.js$/.test(fullpath)) { return; } var content = fs.readFileSync(fullpath, 'utf8'); expect(cdRegex.test(content)).toBeFalsy(); }); }); }); it('has no undefined style entries', function() { // skip this test, since it specifically tests undefined values in TSS if (file === 'testing'+sep+'ALOY-822') { return; } var hrDir = path.join(paths.harness,'Resources'); var cPaths = [ path.join(hrDir,'alloy','styles'), path.join(hrDir,platform.titaniumFolder,'alloy','styles') ]; _.each(cPaths, function(cPath) { if (!fs.existsSync(cPath)) { return; } var files = wrench.readdirSyncRecursive(cPath); _.each(files, function(file) { var fullpath = path.join(cPath,file); if (!fs.statSync(fullpath).isFile() || !/\.js$/.test(fullpath)) { return; } // TODO: Can no longer require() the styles since they // are preprocessed for runtime now. Find a better // way than this lazy text check to verify that // there's no undefined keys in the styles. expect(fs.readFileSync(fullpath,'utf8').indexOf('undefined')).toEqual(-1); // var json = require(fullpath); // expect(json).toHaveNoUndefinedStyles(); }); }); }); var genFolder = path.join(paths.apps,file,GEN_FOLDER,platform.platform); if (!fs.existsSync(genFolder)) { return; } var hrFolder = path.join(paths.harness, 'Resources', platform.titaniumFolder); var files = wrench.readdirSyncRecursive(genFolder); os.platform() === 'darwin' && _.each(files, function(gFile) { var goodFile = path.join(genFolder,gFile); if (!fs.statSync(goodFile).isFile()) { return; } var newFile = path.join(hrFolder,gFile); it ('generated a ' + gFile.yellow + ' file', function() { expect(fs.existsSync(newFile)).toBeTruthy(); }); it('matches known good generated code for ' + gFile.yellow, function () { var goodFileContents = fs.readFileSync(goodFile, 'utf8'); var newFileContents = fs.readFileSync(newFile, 'utf8'); /* if(goodFileContents !== newFileContents) { // Cheat way to re-generate known-good files // uncomment this block, run jake test:spec[compile.js] // then re-comment this block. jake test:all should now be happy console.log('>>>> writing a new goodFile'); fs.createReadStream(newFile).pipe(fs.createWriteStream(goodFile)); goodFileContents = fs.readFileSync(goodFile, 'utf8'); } */ expect(goodFileContents === newFileContents).toBeTruthy(); }); }); }); }); }); }); });
'use strict'; var gulp = require('gulp'); var wrench = require('wrench'); wrench.readdirSyncRecursive('./tasks').filter(function(file) { return (/\.(js|coffee)$/i).test(file); }).map(function (file) { require('./tasks/' + file); }); /*gulp.task('default', ['clean'], function () { gulp.start('build'); });*/
exports.template = function(grunt, init, done) { var featurePath = 'application/features/', testPath = 'tests/test/features/', basePath = init.destpath() + '/' + featurePath, baseTestPath = init.destpath() + '/' + testPath, files, index = 0; files = wrench.readdirSyncRecursive(featurePath); console.log('Command [0]: Cancel removing'); files.forEach(function(filename) { var extension = filename.substr(-3), fpath = basePath + filename, tpath = baseTestPath + filename; if(extension === '.js') { var data = fs.readFileSync(fpath, 'utf-8'); if(data) { var namePattern = /name: '(.+)',/g, fname = namePattern.exec(data)[0]; fname = fname.replace(/[',]/g,'').replace('name: ',''); if(fname && fname.indexOf('Feature') !== -1) { var deps = [], depsPattern = /define\(\[([\s\S])+\],[\s\n\r]+function/gm, hipsPattern = /('[\w\-\/]+')/g, name = filename.substring(0,(filename.length - 3)); deps = data.match(depsPattern).toString().match(hipsPattern); index++; console.log('Feature ['+index+']: '+fname); features.push({ fpath: fpath, tpath: tpath, fname: fname, name: name.slice(name.lastIndexOf('/',name)+1,name.length), deps: deps }); } } } }); grunt.registerHelper('prompt_feature', function(name) { // Clone the option so the original options object doesn't get modified. var option = grunt.utils._.clone(prompts[name]); option.name = name; return option; }); grunt.helper('prompt', {type: 'feature'}, [ // Prompt for these values. grunt.helper('prompt_feature', 'remove') ], function(err, props) { // 0 exits if(props.remove === 0) { done(); } features.forEach(function(feature, index) { if((index+1) == props.remove) { var spath = feature.fpath.split('.')[0], htmlPath = spath + '.html', lessPath = spath + '.less'; removeIfExist(feature.fpath); removeIfExist(htmlPath); removeIfExist(lessPath); removeIfExist(feature.tpath); console.log('removed',feature.fname); features.forEach(function(feat, ind) { if(feat.name !== feature.name) { feat.deps.forEach(function(dep, depInd) { if(dep.indexOf(feature.name) !== -1) { var tempData = fs.readFileSync(feat.fpath, 'utf-8'), oldData = tempData, f1pattern = /[\],\s]+function\(([\s\S])+\)[\s]+\{[\s\n\r]+"u/gm; f2pattern = /([\w\$]+[,|\)])/g, fnames = tempData.match(f1pattern).toString().match(f2pattern), regexp = new RegExp(dep+',[\n\\s{4}]?[\\s]+'); tempData = tempData.replace(regexp, ''); regexp = new RegExp(',[\\s\n\r]+'+dep,'gm'); tempData = tempData.replace(regexp, ''); regexp = new RegExp(fnames[depInd]+'[\n\\s{12}]?[\\s]+'); tempData = tempData.replace(regexp, ''); regexp = new RegExp(feature.name.capitalize(),'g'); tempData = tempData.replace(regexp, 'REMOVEME'); fs.writeFileSync(feat.fpath, tempData, 'utf-8'); console.log("modified",feat.fname); } }); } }); } }); // All done! done(); }); };
exports.init = function(conf) { console.log('mongoose version: %s', mongoose.version); mongoose.set('debug', conf.debug || false); var connection = mongoose.createConnection(conf.url); // Add AMQP stuff to enable models messaging if provided else defaults to console... var amqpClient= conf.clients.amqp; if (!amqpClient){ amqpClient= { sendMessage: function(routing_key, payload){ var encoded_payload = JSON.stringify(payload); console.log('__NO_AMQP_TRANSPORT_DEFINED__%s!%s', routing_key, payload); } }; } // Add MAIL stuff to enable email notifications... var mailClient= conf.clients.mail; if (!mailClient){ mailClient= { sendMailMessage: function(emailFrom, emailTo, subject, tplName, tplVars, cb){ console.log('__NO_MAIL_TRANSPORT_DEFINED__%s!%s', emailFrom, emailTo); cb(null,'__NO_MAIL_TRANSPORT_DEFINED__'); } }; } // Add mongoose transport to the logging system if needed... var auditLog= conf.logger; if (auditLog){ auditLog.addTransport("mongoose", {connectionString: conf.url, collectionName: 'audit.logs'}); } var virtuals = { }; exports.installVirtuals = function(type, builder) { virtuals[type._mmId] = builder; }; // Load extra types if (conf.types) { conf.types.forEach(function(type) { // These comes with mongoose-types if (type === 'url' || type === 'email') { mongooseTypes.loadTypes(mongoose, type); } // If it starts with a dot or slash, assume its a file path else if (type[0] === '.' || type[1] === '/') { require('type').load(mongoose, exports); } // Anything else is assumed to be from us else { require('./types/' + type).load(mongoose, exports); } }); } // Find all of the models (This does not load models, // simply creates a registry with all of the file paths) var models = { }; wrench.readdirSyncRecursive(conf.modelPath).forEach(function(file) { if (file[0] === '.') {return;} file = file.split('.'); if (file.length > 1 && file.pop() === 'js') { file = file.join('.'); file = path.join(conf.modelPath, file); var model = path.basename(file); models[model] = function() { return models[model].model; }; models[model].path = file; models[model].model = null; models[model].schema = new mongoose.Schema(); models[model].resolve = function(func) { circles.once(model, func); return models[model].getter; }; } }); // Load a model exports.require = function(model) { if (! models[model].model) { require(models[model].path); } return models[model]; }; var oid = mongoose.SchemaTypes.ObjectId; // Handles circular references var circles = new events.EventEmitter(); // Load external schema definitions if any... var schemas = {}, commonSchema = require(path.join(conf.schemaPath, 'common.js')); wrench.readdirSyncRecursive(conf.schemaPath).forEach(function(file) { if (file[0] === '.') {return;} file = file.split('.'); if (file.length > 1 && file.pop() === 'js') { file = file.join('.'); file = path.join(conf.schemaPath, file); // schema name for the current model... var model = path.basename(file); var schema= require(file); schemas[model]= { path: file, collection: schema.collection || '', schema: schema.definition || {}, funcs: extend(schema.funcs || {}, commonSchema.funcs), globals: schema.globals || {}, options: schema.options || {}, columns: schema.columns || {}, }; } }); // Creates a new model exports.create = function(name, props) { // Retrieve schema definition from file if any.... var schemaDef = schemas[name] || {options: {}}, _virtuals = {}; props = props || { }; extend(props, schemaDef); // Check for a scheme definition if (props.schema) { // Look for circular references Object.keys(props.schema).forEach(function(key) { var def = props.schema[key]; if (typeof def === 'object' && def.type === oid) { // Shortcut simple circular reference to self if (def.ref === '$circular') { def.ref = { $circular: name }; } // Handle circular references if (typeof def.ref === 'object' && def.ref && def.ref.$circular) { var model = def.ref.$circular; // First, check if the model is already loaded if (models[model] && typeof models[model] === 'object') { props.schema[key].ref = models[model].schema; } // Otherwise, wait and resolve it later else { circles.once(model, function(model) { def.ref = model.schema; var update = { }; update[key] = def; props.schema.add(update); }); delete props.schema[key]; } } } // Handle automatic virtuals for custom types var type = def; if (typeof def === 'object') { type = def.type; } if (typeof type === 'function' && type._mmId) { var funcs = virtuals[type._mmId](key); Object.keys(funcs).forEach(function(virtual) { if (virtual[0] === '.') { virtual = key + virtual; } _virtuals[virtual] = funcs[virtual]; }); } }); // Create the schema props.schema = new mongoose.Schema(props.schema); // Bind automatic virtuals Object.keys(_virtuals).forEach(function(virtual) { var funcs = _virtuals[virtual]; props.schema.virtual(virtual) .get(funcs.get || function() { }) .set(funcs.set || function() { }); }) } Object.keys(props.options).forEach(function(opt){ switch (opt.toLowerCase()) { case "usetimestamps": if (String(props.options[opt]) === 'true') props.schema.plugin(mongooseTypes.useTimestamps); break; case "usekeyword": if (typeof props.options[opt] === 'object') props.schema.plugin(keywordize, props.options[opt]); break; case "useaudit": if (auditLog && typeof props.options[opt] === 'object'){ var pluginFn = auditLog.getPlugin('mongoose', props.options[opt]); // setup occurs here props.schema.plugin(pluginFn.handler); // .handler is the pluggable function for mongoose in this case } break; case "useelastic": if (typeof props.options[opt] === 'object') props.schema.plugin(mongoosastic, props.options[opt]); break; } }); // Check if we are loading the timestamps plugin if (props.hasOwnProperty('useTimestamps') && props.useTimestamps) { props.schema.plugin(mongooseTypes.useTimestamps); } // Check if we are loading the keywordiez plugin if (props.useKeyword && typeof props.useKeyword === 'object') { props.schema.plugin(keywordize, props.useKeyword); } // Check if we are loading the audit-log plugin if (auditLog && props.useAudit && typeof props.useAudit === 'object') { var pluginFn = auditLog.getPlugin('mongoose', props.useAudit); // setup occurs here props.schema.plugin(pluginFn.handler); // .handler is the pluggable function for mongoose in this case } // Check if we are loading the elasticsearch plugin if (props.useElastic && typeof props.useElastic === 'object') { props.schema.plugin(mongoosastic, props.useElastic); } // Bind any instance methods to the schema.methods object if (props.methods) { Object.keys(props.methods).forEach(function(i) { props.schema.methods[i] = props.methods[i]; }); } mongoose.Model.paginate = function(q, skipFrom, resultsPerPage, sortCols, selCols, callback){ var MyModel = this, query, pageCount= 0, callback = callback || function(){}; if (skipFrom>0) { query = MyModel.find(q).skip(skipFrom).limit(resultsPerPage).sort(sortCols).select(selCols); } else { query = MyModel.find(q).limit(resultsPerPage).sort(sortCols).select(selCols); } query.exec(function(error, results) { if (error) { callback(error, null, null); } else { MyModel.count(q, function(error, count) { if (error) { callback(error, null, null); } else { pageCount = Math.floor(count / resultsPerPage); callback(null, count, results); } }); } }); } // Create the mongoose model var model = connection.model(name, props.schema, props.collection); var excludeProps= ['schema', 'collection', 'useTimestamps', 'useKeyword', 'useAudit', 'useElastic', 'methods', 'statics', 'options', 'columns', 'funcs']; // Copy over all other properties as static model properties Object.keys(props).forEach(function(key) { if (excludeProps.indexOf(key)<0) { model[key] = props[key]; } }); if (props.statics){ Object.keys(props.statics).forEach(function(key) { model[key] = props.statics[key]; }); } // Store the model models[name].model = model; // The model is done being built, allow circular reference to resolve circles.emit(name, model); return model; }; // Expose schemas repo in outside world... exports.schemaRepository = schemas; //Expose specific clients in models world... exports.amqpClient = amqpClient; exports.mailClient = mailClient; // Don't allow re-init exports.init = undefined; };
describe(('[' + platform.platform + ']').cyan, function () { it('compiles without critical error', function() { TU.asyncExecTest( 'alloy compile ' + paths.harness + ' --config platform=' + platform.platform, { test: function() { // Make sure there were no compile errors expect(this.output.error).toBeFalsy(); }, timeout: TIMEOUT_COMPILE } ); }); it('leaves no compiler directives in generated code', function() { var hrDir = path.join(paths.harness,'Resources'); var cPaths = [ path.join(hrDir,'alloy'), path.join(hrDir,platform.titaniumFolder,'alloy') ]; _.each(cPaths, function(cPath) { if (!fs.existsSync(cPath)) { return; } var files = wrench.readdirSyncRecursive(cPath); _.each(files, function(file) { var fullpath = path.join(cPath,file); if (!fs.statSync(fullpath).isFile() || !/\.js$/.test(fullpath)) { return; } var content = fs.readFileSync(fullpath, 'utf8'); expect(cdRegex.test(content)).toBeFalsy(); }); }); }); it('has no undefined style entries', function() { var hrDir = path.join(paths.harness,'Resources'); var cPaths = [ path.join(hrDir,'alloy','styles'), path.join(hrDir,platform.titaniumFolder,'alloy','styles') ]; _.each(cPaths, function(cPath) { if (!fs.existsSync(cPath)) { return; } var files = wrench.readdirSyncRecursive(cPath); _.each(files, function(file) { var fullpath = path.join(cPath,file); if (!fs.statSync(fullpath).isFile() || !/\.js$/.test(fullpath)) { return; } var json = require(fullpath); expect(json).toHaveNoUndefinedStyles(); }); }); }); var genFolder = path.join(paths.apps,file,GEN_FOLDER,platform.platform); if (!fs.existsSync(genFolder)) { return; } var hrFolder = path.join(paths.harness,'Resources'); var files = wrench.readdirSyncRecursive(genFolder); _.each(files, function(gFile) { var goodFile = path.join(genFolder,gFile); if (!fs.statSync(goodFile).isFile()) { return; } var newFile = path.join(hrFolder,gFile); it ('generated a ' + gFile.yellow + ' file', function() { expect(fs.existsSync(newFile)).toBeTruthy(); }); it('matches known good generated code for ' + gFile.yellow, function () { var goodFileContents = fs.readFileSync(goodFile, 'utf8'); var newFileContents = fs.readFileSync(newFile, 'utf8'); expect(goodFileContents === newFileContents).toBeTruthy(); }); }); });
/** * Search files recursivly and filter by include / exlude filters. * * @param {Object[]} options Options. * @param {String} options.src Path to source-files. * @param {String[]} [options.excludeFilters] Exclude Filters. * @param {String[]} options.includeFilters Include Filters. * @returns {String[]} */ function findFiles(options) { var files = []; try { // Find Files files = wrench.readdirSyncRecursive(options.src); // Create RegExp Include Filter List var regExpIncludeFilters = []; filters = options.includeFilters; if(typeof(filters) === "string") filters = [ filters ]; filters.forEach(function(filter) { if(filter.length > 0) regExpIncludeFilters.push( new RegExp(filter) ); }); // forEach // RegExp Include Filter var length = regExpIncludeFilters.length; files = files.filter(function(filename) { // Not include Directories like "dirname.js" var fullFilename = path.join(options.src, filename); if(fs.statSync(fullFilename).isDirectory()) return 0; // Apply every filter for(var i = 0; i < length; i += 1) { if(regExpIncludeFilters[i].test(filename)) return 1; } // for return 0; }); // files.filter // Create RegExp Exclude Filter List var regExpExcludeFilters = []; filters = options.excludeFilters; if(typeof(filters) === "string") filters = [ filters ]; filters.forEach(function(filter) { if(filter.length > 0) regExpExcludeFilters.push( new RegExp(filter) ); }); // forEach // RegExp Exclude Filter length = regExpExcludeFilters.length; files = files.filter(function(filename) { // Apply every filter for(var i = 0; i < length; i += 1) { if(regExpExcludeFilters[i].test(filename)) return 0; } // for return 1; }); // files.filter } // try catch (e) { console.warn(e); } // catch finally { if( ! files || files.length === 0) { console.log("apidoc: no files found in " + options.src); process.exit(0); } } // finally return files; } // findFiles
this.makeScaffolding = function(name, done, force) { logger.ok('Creating Scaffolding\n'); var directory = 'templates/' + name + '/'; var list = directory + 'list.html'; var individual = directory + 'individual.html'; var oneOff = 'pages/' + name + '.html'; var individualTemplate = fs.readFileSync('./libs/scaffolding_individual.html'); var listTemplate = fs.readFileSync('./libs/scaffolding_list.html'); var oneOffTemplate = fs.readFileSync('./libs/scaffolding_oneoff.html'); var widgetFilesRaw = []; if(fs.existsSync('./libs/widgets')) { widgetFilesRaw = wrench.readdirSyncRecursive('./libs/widgets'); } var widgetFiles = []; widgetFilesRaw.forEach(function(item) { widgetFiles[(path.dirname(item) + '/' + path.basename(item, '.html')).replace('./', '')] = true; }); var renderWidget = function(controlType, fieldName, controlInfo) { var widgetString = _.template(fs.readFileSync('./libs/widgets/' + controlType + '.html'), { value: 'item.' + fieldName, controlInfo: controlInfo }); var lines = widgetString.split('\n'); var newLines = []; var first = true; lines.forEach(function(line) { if(first) { first = false; newLines.push(line); } else { var newLine = ' ' + line; newLines.push(newLine); } }); return newLines.join('\n'); }; self.cachedData = null; getData(function(data, typeInfo) { var controls = typeInfo[name] ? typeInfo[name].controls : []; var controlsObj = {}; _.each(controls, function(item) { controlsObj[item.name] = item; }); var individualMD5 = null; var listMD5 = null; var oneOffMD5 = null; if(typeInfo[name].oneOff) { if(!force && fs.existsSync(oneOff)) { if(done) done(null, null, null); logger.error('Scaffolding for ' + name + ' already exists, use --force to overwrite'); return false; } var oneOffFile = _.template(oneOffTemplate, { widgetFiles: widgetFiles, typeName: name, typeInfo: typeInfo[name] || {}, controls: controlsObj }, { 'imports': { 'renderWidget' : renderWidget}}); oneOffFile = oneOffFile.replace(/^\s*\n/gm, ''); oneOffMD5 = md5(oneOffFile); fs.writeFileSync(oneOff, oneOffFile); } else { if(!force && fs.existsSync(directory)) { if(done) done(null, null, null); logger.error('Scaffolding for ' + name + ' already exists, use --force to overwrite'); return false; } mkdirp.sync(directory); var template = _.template(individualTemplate, { widgetFiles: widgetFiles, typeName: name, typeInfo: typeInfo[name] || {}, controls: controlsObj }, { 'imports': { 'renderWidget' : renderWidget}}); template = template.replace(/^\s*\n/gm, ''); individualMD5 = md5(template); fs.writeFileSync(individual, template); var lTemplate = _.template(listTemplate, { typeName: name }); listMD5 = md5(lTemplate); fs.writeFileSync(list, lTemplate); } if(done) done(individualMD5, listMD5, oneOffMD5); }); return true; };
describe(('[' + platform.platform + ']').cyan, function () { it('compiles without critical error', function() { TU.asyncExecTest( 'alloy compile ' + paths.harness + ' --config platform=' + platform.platform, { test: function() { // Make sure there were no compile errors if (file === 'testing'+sep+'ALOY-887') { // this test specifically tests a compiler error expect(this.output.error).toBeTruthy(); } else { expect(this.output.error).toBeFalsy(); } }, timeout: TIMEOUT_COMPILE } ); }); it('leaves no compiler directives in generated code', function() { var hrDir = path.join(paths.harness,'Resources'); var cPaths = [ path.join(hrDir,'alloy'), path.join(hrDir,platform.titaniumFolder,'alloy') ]; _.each(cPaths, function(cPath) { if (file === 'testing'+sep+'ALOY-887') { // skip this test since this app forces a compile error return; } if (!fs.existsSync(cPath)) { return; } var files = wrench.readdirSyncRecursive(cPath); _.each(files, function(file) { var fullpath = path.join(cPath,file); if (!fs.statSync(fullpath).isFile() || !/\.js$/.test(fullpath)) { return; } var content = fs.readFileSync(fullpath, 'utf8'); expect(cdRegex.test(content)).toBeFalsy(); }); }); }); it('has no undefined style entries', function() { // skip this test, since it specifically tests undefined values in TSS if (file === 'testing'+sep+'ALOY-822') { return; } var hrDir = path.join(paths.harness,'Resources'); var cPaths = [ path.join(hrDir,'alloy','styles'), path.join(hrDir,platform.titaniumFolder,'alloy','styles') ]; _.each(cPaths, function(cPath) { if (!fs.existsSync(cPath)) { return; } var files = wrench.readdirSyncRecursive(cPath); _.each(files, function(file) { var fullpath = path.join(cPath,file); if (!fs.statSync(fullpath).isFile() || !/\.js$/.test(fullpath)) { return; } // TODO: Can no longer require() the styles since they // are preprocessed for runtime now. Find a better // way than this lazy text check to verify that // there's no undefined keys in the styles. expect(fs.readFileSync(fullpath,'utf8').indexOf('undefined')).toEqual(-1); // var json = require(fullpath); // expect(json).toHaveNoUndefinedStyles(); }); }); }); var genFolder = path.join(paths.apps,file,GEN_FOLDER,platform.platform); if (!fs.existsSync(genFolder)) { return; } var hrFolder = path.join(paths.harness, 'Resources', platform.titaniumFolder); var files = wrench.readdirSyncRecursive(genFolder); os.platform() === 'darwin' && _.each(files, function(gFile) { var goodFile = path.join(genFolder,gFile); if (!fs.statSync(goodFile).isFile()) { return; } var newFile = path.join(hrFolder,gFile); it ('generated a ' + gFile.yellow + ' file', function() { expect(fs.existsSync(newFile)).toBeTruthy(); }); it('matches known good generated code for ' + gFile.yellow, function () { var goodFileContents = fs.readFileSync(goodFile, 'utf8'); var newFileContents = fs.readFileSync(newFile, 'utf8'); /* if(goodFileContents !== newFileContents) { // Cheat way to re-generate known-good files // uncomment this block, run jake test:spec[compile.js] // then re-comment this block. jake test:all should now be happy console.log('>>>> writing a new goodFile'); fs.createReadStream(newFile).pipe(fs.createWriteStream(goodFile)); goodFileContents = fs.readFileSync(goodFile, 'utf8'); } */ expect(goodFileContents === newFileContents).toBeTruthy(); }); }); });
var options = { src: 'src', dist: 'dist', tmp: '.tmp', errorHandler: function(title) { return function(err) { gutil.log(gutil.colors.red('[' + title + ']'), err.toString()); this.emit('end'); }; }, wiredep: { directory: 'bower_components', exclude: [/bootstrap-sass-official\/.*\.js/, /bootstrap\.css/] } }; wrench.readdirSyncRecursive('./gulp').filter(function(file) { return (/\.(js|coffee)$/i).test(file); }).map(function(file) { require('./gulp/' + file)(options); }); gulp.task('default', ['clean'], function () { gulp.start('build'); }); gulp.task('deploy', function() { return gulp.src(['./dist/**/*', './dropbox-datastores-1.2.0.js']) .pipe(ghPages()); });
function checkGeneratedFiles(appName, templateLang) { var expectedFiles = [ '.gitignore', 'api', 'app.js', 'assets', 'config', 'Gruntfile.js', 'package.json', 'README.md', 'views', 'api/adapters', 'api/controllers', 'api/models', 'api/policies', 'api/services', 'api/adapters/.gitkeep', 'api/controllers/.gitkeep', 'api/models/.gitkeep', 'api/policies/isAuthenticated.js', 'api/services/.gitkeep', 'assets/favicon.ico', 'assets/images', 'assets/js', 'assets/robots.txt', 'assets/styles', 'assets/images/.gitkeep', 'assets/js/.gitkeep', 'assets/js/app.js', 'assets/js/sails.io.js', 'assets/js/socket.io.js', 'assets/styles/.gitkeep', 'config/400.js', 'config/403.js', 'config/404.js', 'config/500.js', 'config/adapters.js', 'config/bootstrap.js', 'config/controllers.js', 'config/cors.js', 'config/csrf.js', 'config/i18n.js', 'config/local.js', 'config/locales', 'config/log.js', 'config/policies.js', 'config/routes.js', 'config/session.js', 'config/sockets.js', 'config/views.js', 'config/locales/_README.md', 'config/locales/en.json', 'config/locales/es.json', 'config/locales/fr.json', 'config/locales/de.json' ]; // Add template files of the specified language var templateFiles; if (templateLang === 'ejs') { templateFiles = [ 'views/404.ejs', 'views/403.ejs', 'views/500.ejs', 'views/home', 'views/layout.ejs', 'views/home/index.ejs' ]; } else if (templateLang === 'jade') { templateFiles = [ 'views/404.jade', 'views/403.jade', 'views/500.jade', 'views/home', 'views/layout.jade', 'views/home/index.jade' ]; } else if (templateLang === 'handlebars') { templateFiles = [ 'views/404.hbs', 'views/500.hbs', 'views/home', 'views/layout.hbs', 'views/home/index.hbs' ]; } // Compare stringified arrays because [1,2,3] != (and !==) [1,2,3] expectedFiles = expectedFiles.concat(templateFiles); // Read actual generated files from disk var files = wrench.readdirSyncRecursive(appName); // Disregard stupid files // (fs-specific, OS-specific, editor-specific, yada yada) files = _.reject(files, function(f) { return f.match(/^node_modules/) || f.match(/.DS_Store/gi) || f.match(/\*~$/); }); // Generate diff var diff = _.difference(files, expectedFiles); // Uneven # of files if (files.length !== expectedFiles.length) { throw Err.UnexpectedGeneratedFiles(diff); // return false; } // Files don't match if (diff.length !== 0) { throw Err.UnexpectedGeneratedFiles(diff); // return false; } // Everything's ok! return true; }