diff --git a/lib/compiler.js b/lib/compiler.js index 614d76e..8fe1e4b 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -17,6 +17,13 @@ var g = require('strong-globalize')(); var FILE_EXTENSION_JSON = '.json'; +function arrayToObject(array) { + return array.reduce(function(obj, val) { + obj[val] = val; + return obj; + }, {}); +} + /** * Gather all bootstrap-related configuration data and compile it into * a single object containing instruction for `boot.execute`. @@ -36,8 +43,14 @@ module.exports = function compile(options) { options = { appRootDir: options }; } + // For setting properties without modifying the original object + options = Object.create(options); + var appRootDir = options.appRootDir = options.appRootDir || process.cwd(); var env = options.env || process.env.NODE_ENV || 'development'; + var scriptExtensions = options.scriptExtensions ? + arrayToObject(options.scriptExtensions) : + require.extensions; var appConfigRootDir = options.appConfigRootDir || appRootDir; var appConfig = options.config || @@ -76,9 +89,9 @@ module.exports = function compile(options) { resolveRelativePaths(bootScripts, appRootDir); bootDirs.forEach(function(dir) { - bootScripts = bootScripts.concat(findScripts(dir)); + bootScripts = bootScripts.concat(findScripts(dir, scriptExtensions)); var envdir = dir + '/' + env; - bootScripts = bootScripts.concat(findScripts(envdir)); + bootScripts = bootScripts.concat(findScripts(envdir, scriptExtensions)); }); // de-dedup boot scripts -ERS @@ -90,12 +103,12 @@ module.exports = function compile(options) { var modelSources = options.modelSources || modelsMeta.sources || ['./models']; var modelInstructions = buildAllModelInstructions( - modelsRootDir, modelsConfig, modelSources, options.modelDefinitions); + modelsRootDir, modelsConfig, modelSources, options.modelDefinitions, + scriptExtensions); - var mixinDirs = options.mixinDirs || []; var mixinSources = options.mixinSources || modelsMeta.mixins || ['./mixins']; var mixinInstructions = buildAllMixinInstructions( - appRootDir, mixinDirs, mixinSources, options, modelInstructions); + appRootDir, options, mixinSources, scriptExtensions, modelInstructions); // When executor passes the instruction to loopback methods, // loopback modifies the data. Since we are loading the data using `require`, @@ -151,11 +164,11 @@ function assertIsValidModelConfig(config) { * @private */ -function findScripts(dir, extensions) { +function findScripts(dir, scriptExtensions) { assert(dir, g.f('cannot require directory contents without directory name')); var files = tryReadDir(dir); - extensions = extensions || _.keys(require.extensions); + scriptExtensions = scriptExtensions || require.extensions; // sort files in lowercase alpha for linux files.sort(function(a, b) { @@ -181,9 +194,9 @@ function findScripts(dir, extensions) { var filepath = path.resolve(path.join(dir, filename)); var stats = fs.statSync(filepath); - // only require files supported by require.extensions (.txt .md etc.) + // only require files supported by specified extensions if (stats.isFile()) { - if (isPreferredExtension(filename)) + if (scriptExtensions && isPreferredExtension(filename, scriptExtensions)) results.push(filepath); else debug('Skipping file %s - unknown extension', filepath); @@ -204,9 +217,12 @@ function tryReadDir() { } function buildAllModelInstructions(rootDir, modelsConfig, sources, - modelDefinitions) { - var registry = verifyModelDefinitions(rootDir, modelDefinitions) || - findModelDefinitions(rootDir, sources); + modelDefinitions, scriptExtensions) { + var registry = verifyModelDefinitions(rootDir, modelDefinitions, + scriptExtensions); + if (!registry) { + registry = findModelDefinitions(rootDir, sources, scriptExtensions); + } var modelNamesToBuild = addAllBaseModels(registry, Object.keys(modelsConfig)); @@ -287,7 +303,7 @@ function sortByInheritance(instructions) { }); } -function verifyModelDefinitions(rootDir, modelDefinitions) { +function verifyModelDefinitions(rootDir, modelDefinitions, scriptExtensions) { if (!modelDefinitions || modelDefinitions.length < 1) { return undefined; } @@ -299,7 +315,7 @@ function verifyModelDefinitions(rootDir, modelDefinitions) { definition.sourceFile = fixFileExtension( fullPath, tryReadDir(path.dirname(fullPath)), - true); + scriptExtensions); if (!definition.sourceFile) { debug('Model source code not found: %s - %s', definition.sourceFile); } @@ -325,7 +341,7 @@ function verifyModelDefinitions(rootDir, modelDefinitions) { return registry; } -function findModelDefinitions(rootDir, sources) { +function findModelDefinitions(rootDir, sources, scriptExtensions) { var registry = {}; sources.forEach(function(src) { @@ -343,7 +359,8 @@ function findModelDefinitions(rootDir, sources) { }) .forEach(function(f) { var fullPath = path.resolve(srcDir, f); - var entry = loadModelDefinition(rootDir, fullPath, files); + var entry = loadModelDefinition(rootDir, fullPath, files, + scriptExtensions); var modelName = entry.definition.name; if (!modelName) { debug('Skipping model definition without Model name: %s', @@ -445,13 +462,13 @@ function tryResolveAppPath(rootDir, relativePath, resolveOptions) { return undefined; } -function loadModelDefinition(rootDir, jsonFile, allFiles) { +function loadModelDefinition(rootDir, jsonFile, allFiles, scriptExtensions) { var definition = require(jsonFile); var basename = path.basename(jsonFile, path.extname(jsonFile)); definition.name = definition.name || _.capitalize(_.camelCase(basename)); // find a matching file with a supported extension like `.js` or `.coffee` - var sourceFile = fixFileExtension(jsonFile, allFiles, true); + var sourceFile = fixFileExtension(jsonFile, allFiles, scriptExtensions); if (sourceFile === undefined) { debug('Model source code not found: %s', sourceFile); @@ -644,19 +661,21 @@ function getExcludedExtensions() { }; } -function isPreferredExtension(filename) { - var includeExtensions = require.extensions; +function isPreferredExtension(filename, includeExtensions) { + assert(!!includeExtensions, '"includeExtensions" argument is required'); var ext = path.extname(filename); return (ext in includeExtensions) && !(ext in getExcludedExtensions()); } -function fixFileExtension(filepath, files, onlyScriptsExportingFunction) { +function fixFileExtension(filepath, files, scriptExtensions) { var results = []; var otherFile; /* Prefer coffee scripts over json */ - if (isPreferredExtension(filepath)) return filepath; + if (scriptExtensions && isPreferredExtension(filepath, scriptExtensions)) { + return filepath; + } var basename = path.basename(filepath, FILE_EXTENSION_JSON); var sourceDir = path.dirname(filepath); @@ -670,10 +689,7 @@ function fixFileExtension(filepath, files, onlyScriptsExportingFunction) { if (!(otherFileExtension in getExcludedExtensions()) && path.basename(f, otherFileExtension) == basename) { - if (!onlyScriptsExportingFunction) - results.push(otherFile); - else if (onlyScriptsExportingFunction && - (typeof require.extensions[otherFileExtension]) === 'function') { + if (!scriptExtensions || otherFileExtension in scriptExtensions) { results.push(otherFile); } } @@ -689,32 +705,33 @@ function resolveAppScriptPath(rootDir, relativePath, resolveOptions) { } var sourceDir = path.dirname(resolvedPath); var files = tryReadDir(sourceDir); - var fixedFile = fixFileExtension(resolvedPath, files, false); + var fixedFile = fixFileExtension(resolvedPath, files); return (fixedFile === undefined ? resolvedPath : fixedFile); } -function buildAllMixinInstructions(appRootDir, mixinDirs, mixinSources, options, - modelInstructions) { - var extensions = _.without(_.keys(require.extensions), - _.keys(getExcludedExtensions())); - +function buildAllMixinInstructions(appRootDir, options, mixinSources, + scriptExtensions, modelInstructions) { // load mixins from `options.mixins` var sourceFiles = options.mixins || []; - var instructionsFromMixins = loadMixins(sourceFiles, options); + var mixinDirs = options.mixinDirs || []; + var instructionsFromMixins = loadMixins(sourceFiles, options.normalization); // load mixins from `options.mixinDirs` - sourceFiles = findMixinDefinitions(appRootDir, mixinDirs, extensions); + sourceFiles = findMixinDefinitions(appRootDir, mixinDirs, scriptExtensions); if (sourceFiles === undefined) return; - var instructionsFromMixinDirs = loadMixins(sourceFiles, options); + var instructionsFromMixinDirs = loadMixins(sourceFiles, + options.normalization); /* If `mixinDirs` and `mixinSources` have any directories in common, * then remove the common directories from `mixinSources` */ mixinSources = _.difference(mixinSources, mixinDirs); // load mixins from `options.mixinSources` - sourceFiles = findMixinDefinitions(appRootDir, mixinSources, extensions); + sourceFiles = findMixinDefinitions(appRootDir, mixinSources, + scriptExtensions); if (sourceFiles === undefined) return; - var instructionsFromMixinSources = loadMixins(sourceFiles, options); + var instructionsFromMixinSources = loadMixins(sourceFiles, + options.normalization); // Fetch unique list of mixin names, used in models var modelMixins = fetchMixinNamesUsedInModelInstructions(modelInstructions); @@ -732,7 +749,7 @@ function buildAllMixinInstructions(appRootDir, mixinDirs, mixinSources, options, return _.values(mixins); } -function findMixinDefinitions(appRootDir, sourceDirs, extensions) { +function findMixinDefinitions(appRootDir, sourceDirs, scriptExtensions) { var files = []; sourceDirs.forEach(function(dir) { var path = tryResolveAppPath(appRootDir, dir); @@ -740,12 +757,12 @@ function findMixinDefinitions(appRootDir, sourceDirs, extensions) { debug('Skipping unknown module source dir %j', dir); return; } - files = files.concat(findScripts(path, extensions)); + files = files.concat(findScripts(path, scriptExtensions)); }); return files; } -function loadMixins(sourceFiles, options) { +function loadMixins(sourceFiles, normalization) { var mixinInstructions = {}; sourceFiles.forEach(function(filepath) { var dir = path.dirname(filepath); @@ -753,7 +770,7 @@ function loadMixins(sourceFiles, options) { var name = path.basename(filepath, ext); var metafile = path.join(dir, name + FILE_EXTENSION_JSON); - name = normalizeMixinName(name, options); + name = normalizeMixinName(name, normalization); var meta = {}; meta.name = name; if (utils.fileExistsSync(metafile)) { @@ -788,8 +805,7 @@ function filterMixinInstructionsUsingWhitelist(instructions, includeMixins) { return filteredInstructions; } -function normalizeMixinName(str, options) { - var normalization = options.normalization; +function normalizeMixinName(str, normalization) { switch (normalization) { case false: case 'none': return str; diff --git a/test/executor.test.js b/test/executor.test.js index 3a5e66c..7d00b10 100644 --- a/test/executor.test.js +++ b/test/executor.test.js @@ -299,6 +299,23 @@ describe('executor', function() { }); }); + it('searches boot file extensions specified in options.scriptExtensions', + function(done) { + var options = { + app: app, + appRootDir: path.join(__dirname, './fixtures/simple-app'), + scriptExtensions: ['.customjs', '.customjs2'], + }; + boot.execute(app, boot.compile(options), function(err) { + if (err) return done(err); + expect(process.bootFlags, 'process: bootFlags').to.eql([ + 'customjs', + 'customjs2', + ]); + done(); + }); + }); + describe('for mixins', function() { var options; beforeEach(function() { diff --git a/test/fixtures/simple-app/boot/custom.customjs b/test/fixtures/simple-app/boot/custom.customjs new file mode 100644 index 0000000..8fde947 --- /dev/null +++ b/test/fixtures/simple-app/boot/custom.customjs @@ -0,0 +1,6 @@ +'use strict'; + +module.exports = function(app, callback) { + process.bootFlags.push('customjs'); + callback(); +}; diff --git a/test/fixtures/simple-app/boot/custom.customjs2 b/test/fixtures/simple-app/boot/custom.customjs2 new file mode 100644 index 0000000..14a5de2 --- /dev/null +++ b/test/fixtures/simple-app/boot/custom.customjs2 @@ -0,0 +1,6 @@ +'use strict'; + +module.exports = function(app, callback) { + process.bootFlags.push('customjs2'); + callback(); +};