Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 59 additions & 43 deletions lib/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand All @@ -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 ||
Expand Down Expand Up @@ -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
Expand All @@ -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`,
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
Expand All @@ -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));

Expand Down Expand Up @@ -287,7 +303,7 @@ function sortByInheritance(instructions) {
});
}

function verifyModelDefinitions(rootDir, modelDefinitions) {
function verifyModelDefinitions(rootDir, modelDefinitions, scriptExtensions) {
if (!modelDefinitions || modelDefinitions.length < 1) {
return undefined;
}
Expand All @@ -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);
}
Expand All @@ -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) {
Expand All @@ -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',
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
}
}
Expand All @@ -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);
Expand All @@ -732,28 +749,28 @@ 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);
if (!path) {
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);
var ext = path.extname(filepath);
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)) {
Expand Down Expand Up @@ -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;
Expand Down
17 changes: 17 additions & 0 deletions test/executor.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
6 changes: 6 additions & 0 deletions test/fixtures/simple-app/boot/custom.customjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
'use strict';

module.exports = function(app, callback) {
process.bootFlags.push('customjs');
callback();
};
6 changes: 6 additions & 0 deletions test/fixtures/simple-app/boot/custom.customjs2
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
'use strict';

module.exports = function(app, callback) {
process.bootFlags.push('customjs2');
callback();
};