Merge pull request #240 from supasate/provide-script-extensions-option

Provide scriptExtensions option for 3.x
This commit is contained in:
Raymond Feng 2017-04-03 10:02:51 -07:00 committed by GitHub
commit 480380224d
10 changed files with 113 additions and 50 deletions

10
lib/bootstrapper.js vendored
View File

@ -3,6 +3,7 @@
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
var _ = require('lodash');
var assert = require('assert'); var assert = require('assert');
var async = require('async'); var async = require('async');
var utils = require('./utils'); var utils = require('./utils');
@ -10,6 +11,7 @@ var path = require('path');
var pluginLoader = require('./plugin-loader'); var pluginLoader = require('./plugin-loader');
var debug = require('debug')('loopback:boot:bootstrapper'); var debug = require('debug')('loopback:boot:bootstrapper');
var Promise = require('bluebird'); var Promise = require('bluebird');
var arrayToObject = require('./utils').arrayToObject;
module.exports = Bootstrapper; module.exports = Bootstrapper;
@ -58,13 +60,20 @@ function Bootstrapper(options) {
options = { appRootDir: 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 appRootDir = options.appRootDir = options.appRootDir || process.cwd();
var env = options.env || process.env.NODE_ENV || 'development'; var env = options.env || process.env.NODE_ENV || 'development';
var scriptExtensions = options.scriptExtensions ?
arrayToObject(options.scriptExtensions) :
require.extensions;
var appConfigRootDir = options.appConfigRootDir || appRootDir; var appConfigRootDir = options.appConfigRootDir || appRootDir;
options.rootDir = appConfigRootDir; options.rootDir = appConfigRootDir;
options.env = env; options.env = env;
options.scriptExtensions = scriptExtensions;
this.options = options; this.options = options;
this.phases = options.phases || builtinPhases; this.phases = options.phases || builtinPhases;
@ -200,4 +209,3 @@ Bootstrapper.prototype.run = function(context, done) {
}); });
return done.promise; return done.promise;
}; };

View File

@ -33,9 +33,13 @@ PluginScript.prototype.load = function(context) {
utils.resolveRelativePaths(pluginScripts, appRootDir); utils.resolveRelativePaths(pluginScripts, appRootDir);
pluginDirs.forEach(function(dir) { pluginDirs.forEach(function(dir) {
pluginScripts = pluginScripts.concat(utils.findScripts(dir)); pluginScripts = pluginScripts.concat(
utils.findScripts(dir, options.scriptExtensions)
);
var envdir = dir + '/' + options.env; var envdir = dir + '/' + options.env;
pluginScripts = pluginScripts.concat(utils.findScripts(envdir)); pluginScripts = pluginScripts.concat(
utils.findScripts(envdir, options.scriptExtensions)
);
}); });
pluginScripts = _.uniq(pluginScripts); pluginScripts = _.uniq(pluginScripts);
@ -58,5 +62,3 @@ PluginScript.prototype.compile = function(context) {
plugins[name] = handler; plugins[name] = handler;
}); });
}; };

View File

@ -34,9 +34,13 @@ Script.prototype.load = function(context) {
utils.resolveRelativePaths(bootScripts, appRootDir); utils.resolveRelativePaths(bootScripts, appRootDir);
bootDirs.forEach(function(dir) { bootDirs.forEach(function(dir) {
bootScripts = bootScripts.concat(utils.findScripts(dir)); bootScripts = bootScripts.concat(
utils.findScripts(dir, options.scriptExtensions)
);
var envdir = dir + '/' + options.env; var envdir = dir + '/' + options.env;
bootScripts = bootScripts.concat(utils.findScripts(envdir)); bootScripts = bootScripts.concat(
utils.findScripts(envdir, options.scriptExtensions)
);
}); });
// de-dedup boot scripts -ERS // de-dedup boot scripts -ERS

View File

@ -83,6 +83,7 @@ Middleware.prototype.mergePhaseConfig = function(target, config, phase) {
Middleware.prototype.buildInstructions = function(context, rootDir, config) { Middleware.prototype.buildInstructions = function(context, rootDir, config) {
var phasesNames = Object.keys(config); var phasesNames = Object.keys(config);
var middlewareList = []; var middlewareList = [];
phasesNames.forEach(function(phase) { phasesNames.forEach(function(phase) {
var phaseConfig = config[phase]; var phaseConfig = config[phase];
Object.keys(phaseConfig).forEach(function(middleware) { Object.keys(phaseConfig).forEach(function(middleware) {

View File

@ -30,38 +30,39 @@ util.inherits(Mixin, PluginBase);
Mixin.prototype.buildInstructions = function(context, rootDir, config) { Mixin.prototype.buildInstructions = function(context, rootDir, config) {
var modelsMeta = context.configurations.mixins._meta || {}; var modelsMeta = context.configurations.mixins._meta || {};
var modelInstructions = context.instructions.models; var modelInstructions = context.instructions.models;
var mixinDirs = this.options.mixinDirs || [];
var mixinSources = this.options.mixinSources || modelsMeta.mixins || var mixinSources = this.options.mixinSources || modelsMeta.mixins ||
['./mixins']; ['./mixins'];
var scriptExtensions = this.options.scriptExtensions || require.extensions;
var mixinInstructions = buildAllMixinInstructions( var mixinInstructions = buildAllMixinInstructions(
rootDir, mixinDirs, mixinSources, this.options, modelInstructions); rootDir, this.options, mixinSources, scriptExtensions, modelInstructions);
return mixinInstructions; return mixinInstructions;
}; };
function buildAllMixinInstructions(appRootDir, mixinDirs, mixinSources, options, function buildAllMixinInstructions(appRootDir, options, mixinSources,
modelInstructions) { scriptExtensions, modelInstructions) {
var extensions = _.without(_.keys(require.extensions),
_.keys(getExcludedExtensions()));
// load mixins from `options.mixins` // load mixins from `options.mixins`
var sourceFiles = 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` // load mixins from `options.mixinDirs`
sourceFiles = findMixinDefinitions(appRootDir, mixinDirs, extensions); sourceFiles = findMixinDefinitions(appRootDir, mixinDirs, scriptExtensions);
if (sourceFiles === undefined) return; if (sourceFiles === undefined) return;
var instructionsFromMixinDirs = loadMixins(sourceFiles, options); var instructionsFromMixinDirs = loadMixins(sourceFiles,
options.normalization);
/* If `mixinDirs` and `mixinSources` have any directories in common, /* If `mixinDirs` and `mixinSources` have any directories in common,
* then remove the common directories from `mixinSources` */ * then remove the common directories from `mixinSources` */
mixinSources = _.difference(mixinSources, mixinDirs); mixinSources = _.difference(mixinSources, mixinDirs);
// load mixins from `options.mixinSources` // load mixins from `options.mixinSources`
sourceFiles = findMixinDefinitions(appRootDir, mixinSources, extensions); sourceFiles = findMixinDefinitions(appRootDir, mixinSources,
scriptExtensions);
if (sourceFiles === undefined) return; if (sourceFiles === undefined) return;
var instructionsFromMixinSources = loadMixins(sourceFiles, options); var instructionsFromMixinSources = loadMixins(sourceFiles,
options.normalization);
// Fetch unique list of mixin names, used in models // Fetch unique list of mixin names, used in models
var modelMixins = fetchMixinNamesUsedInModelInstructions(modelInstructions); var modelMixins = fetchMixinNamesUsedInModelInstructions(modelInstructions);
@ -79,7 +80,7 @@ function buildAllMixinInstructions(appRootDir, mixinDirs, mixinSources, options,
return _.values(mixins); return _.values(mixins);
} }
function findMixinDefinitions(appRootDir, sourceDirs, extensions) { function findMixinDefinitions(appRootDir, sourceDirs, scriptExtensions) {
var files = []; var files = [];
sourceDirs.forEach(function(dir) { sourceDirs.forEach(function(dir) {
var path = tryResolveAppPath(appRootDir, dir); var path = tryResolveAppPath(appRootDir, dir);
@ -87,12 +88,12 @@ function findMixinDefinitions(appRootDir, sourceDirs, extensions) {
debug('Skipping unknown module source dir %j', dir); debug('Skipping unknown module source dir %j', dir);
return; return;
} }
files = files.concat(findScripts(path, extensions)); files = files.concat(findScripts(path, scriptExtensions));
}); });
return files; return files;
} }
function loadMixins(sourceFiles, options) { function loadMixins(sourceFiles, normalization) {
var mixinInstructions = {}; var mixinInstructions = {};
sourceFiles.forEach(function(filepath) { sourceFiles.forEach(function(filepath) {
var dir = path.dirname(filepath); var dir = path.dirname(filepath);
@ -100,7 +101,7 @@ function loadMixins(sourceFiles, options) {
var name = path.basename(filepath, ext); var name = path.basename(filepath, ext);
var metafile = path.join(dir, name + FILE_EXTENSION_JSON); var metafile = path.join(dir, name + FILE_EXTENSION_JSON);
name = normalizeMixinName(name, options); name = normalizeMixinName(name, normalization);
var meta = {}; var meta = {};
meta.name = name; meta.name = name;
if (utils.fileExistsSync(metafile)) { if (utils.fileExistsSync(metafile)) {
@ -135,8 +136,7 @@ function filterMixinInstructionsUsingWhitelist(instructions, includeMixins) {
return filteredInstructions; return filteredInstructions;
} }
function normalizeMixinName(str, options) { function normalizeMixinName(str, normalization) {
var normalization = options.normalization;
switch (normalization) { switch (normalization) {
case false: case false:
case 'none': case 'none':

View File

@ -46,14 +46,18 @@ Model.prototype.buildInstructions = function(context, rootDir, modelsConfig) {
var modelSources = this.options.modelSources || modelsMeta.sources || var modelSources = this.options.modelSources || modelsMeta.sources ||
['./models']; ['./models'];
var modelInstructions = buildAllModelInstructions( var modelInstructions = buildAllModelInstructions(
rootDir, modelsConfig, modelSources, this.options.modelDefinitions); rootDir, modelsConfig, modelSources, this.options.modelDefinitions,
this.options.scriptExtensions);
return modelInstructions; return modelInstructions;
}; };
function buildAllModelInstructions(rootDir, modelsConfig, sources, function buildAllModelInstructions(rootDir, modelsConfig, sources,
modelDefinitions) { modelDefinitions, scriptExtensions) {
var registry = verifyModelDefinitions(rootDir, modelDefinitions) || var registry = verifyModelDefinitions(rootDir, modelDefinitions,
findModelDefinitions(rootDir, sources); scriptExtensions);
if (!registry) {
registry = findModelDefinitions(rootDir, sources, scriptExtensions);
}
var modelNamesToBuild = addAllBaseModels(registry, Object.keys(modelsConfig)); var modelNamesToBuild = addAllBaseModels(registry, Object.keys(modelsConfig));
@ -134,7 +138,7 @@ function sortByInheritance(instructions) {
}); });
} }
function verifyModelDefinitions(rootDir, modelDefinitions) { function verifyModelDefinitions(rootDir, modelDefinitions, scriptExtensions) {
if (!modelDefinitions || modelDefinitions.length < 1) { if (!modelDefinitions || modelDefinitions.length < 1) {
return undefined; return undefined;
} }
@ -146,7 +150,8 @@ function verifyModelDefinitions(rootDir, modelDefinitions) {
definition.sourceFile = fixFileExtension( definition.sourceFile = fixFileExtension(
fullPath, fullPath,
tryReadDir(path.dirname(fullPath)), tryReadDir(path.dirname(fullPath)),
true); scriptExtensions);
if (!definition.sourceFile) { if (!definition.sourceFile) {
debug('Model source code not found: %s - %s', definition.sourceFile); debug('Model source code not found: %s - %s', definition.sourceFile);
} }
@ -172,7 +177,7 @@ function verifyModelDefinitions(rootDir, modelDefinitions) {
return registry; return registry;
} }
function findModelDefinitions(rootDir, sources) { function findModelDefinitions(rootDir, sources, scriptExtensions) {
var registry = {}; var registry = {};
sources.forEach(function(src) { sources.forEach(function(src) {
@ -190,7 +195,8 @@ function findModelDefinitions(rootDir, sources) {
}) })
.forEach(function(f) { .forEach(function(f) {
var fullPath = path.resolve(srcDir, 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; var modelName = entry.definition.name;
if (!modelName) { if (!modelName) {
debug('Skipping model definition without Model name: %s', debug('Skipping model definition without Model name: %s',
@ -204,13 +210,14 @@ function findModelDefinitions(rootDir, sources) {
return registry; return registry;
} }
function loadModelDefinition(rootDir, jsonFile, allFiles) { function loadModelDefinition(rootDir, jsonFile, allFiles, scriptExtensions) {
var definition = require(jsonFile); var definition = require(jsonFile);
var basename = path.basename(jsonFile, path.extname(jsonFile)); var basename = path.basename(jsonFile, path.extname(jsonFile));
definition.name = definition.name || _.upperFirst(_.camelCase(basename)); definition.name = definition.name || _.upperFirst(_.camelCase(basename));
// find a matching file with a supported extension like `.js` or `.coffee` // 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) { if (sourceFile === undefined) {
debug('Model source code not found: %s', sourceFile); debug('Model source code not found: %s', sourceFile);
} }
@ -301,4 +308,3 @@ Model.prototype.start = function(context) {
app.model(data._model, data.config); app.model(data._model, data.config);
}); });
}; };

View File

@ -11,6 +11,7 @@ var assert = require('assert');
var _ = require('lodash'); var _ = require('lodash');
var g = require('./globalize'); var g = require('./globalize');
exports.arrayToObject = arrayToObject;
exports.tryReadDir = tryReadDir; exports.tryReadDir = tryReadDir;
exports.resolveRelativePaths = resolveRelativePaths; exports.resolveRelativePaths = resolveRelativePaths;
exports.assertIsValidConfig = assertIsValidConfig; exports.assertIsValidConfig = assertIsValidConfig;
@ -31,11 +32,11 @@ var FILE_EXTENSION_JSON = exports.FILE_EXTENSION_JSON = '.json';
* @return {Array.<String>} A list of absolute paths to pass to `require()`. * @return {Array.<String>} A list of absolute paths to pass to `require()`.
*/ */
function findScripts(dir, extensions) { function findScripts(dir, scriptExtensions) {
assert(dir, 'cannot require directory contents without directory name'); assert(dir, 'cannot require directory contents without directory name');
var files = tryReadDir(dir); var files = tryReadDir(dir);
extensions = extensions || _.keys(require.extensions); scriptExtensions = scriptExtensions || require.extensions;
// sort files in lowercase alpha for linux // sort files in lowercase alpha for linux
files.sort(function(a, b) { files.sort(function(a, b) {
@ -61,9 +62,9 @@ function findScripts(dir, extensions) {
var filepath = path.resolve(path.join(dir, filename)); var filepath = path.resolve(path.join(dir, filename));
var stats = fs.statSync(filepath); 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 (stats.isFile()) {
if (isPreferredExtension(filename)) if (scriptExtensions && isPreferredExtension(filename, scriptExtensions))
results.push(filepath); results.push(filepath);
else else
debug('Skipping file %s - unknown extension', filepath); debug('Skipping file %s - unknown extension', filepath);
@ -102,19 +103,28 @@ function getExcludedExtensions() {
}; };
} }
function isPreferredExtension(filename) { function arrayToObject(array) {
var includeExtensions = require.extensions; return array.reduce(function(obj, val) {
obj[val] = val;
return obj;
}, {});
}
function isPreferredExtension(filename, includeExtensions) {
assert(!!includeExtensions, '"includeExtensions" argument is required');
var ext = path.extname(filename); var ext = path.extname(filename);
return (ext in includeExtensions) && !(ext in getExcludedExtensions()); return (ext in includeExtensions) && !(ext in getExcludedExtensions());
} }
function fixFileExtension(filepath, files, onlyScriptsExportingFunction) { function fixFileExtension(filepath, files, scriptExtensions) {
var results = []; var results = [];
var otherFile; var otherFile;
/* Prefer coffee scripts over json */ /* 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 basename = path.basename(filepath, FILE_EXTENSION_JSON);
var sourceDir = path.dirname(filepath); var sourceDir = path.dirname(filepath);
@ -128,10 +138,7 @@ function fixFileExtension(filepath, files, onlyScriptsExportingFunction) {
if (!(otherFileExtension in getExcludedExtensions()) && if (!(otherFileExtension in getExcludedExtensions()) &&
path.basename(f, otherFileExtension) == basename) { path.basename(f, otherFileExtension) == basename) {
if (!onlyScriptsExportingFunction) if (!scriptExtensions || otherFileExtension in scriptExtensions) {
results.push(otherFile);
else if (onlyScriptsExportingFunction &&
(typeof require.extensions[otherFileExtension]) === 'function') {
results.push(otherFile); results.push(otherFile);
} }
} }
@ -157,7 +164,7 @@ function resolveAppScriptPath(rootDir, relativePath, resolveOptions) {
} }
var sourceDir = path.dirname(resolvedPath); var sourceDir = path.dirname(resolvedPath);
var files = tryReadDir(sourceDir); var files = tryReadDir(sourceDir);
var fixedFile = fixFileExtension(resolvedPath, files, false); var fixedFile = fixFileExtension(resolvedPath, files);
return (fixedFile === undefined ? resolvedPath : fixedFile); return (fixedFile === undefined ? resolvedPath : fixedFile);
} }
@ -347,4 +354,3 @@ function fileExistsSync(filepath) {
return false; return false;
} }
} }

View File

@ -77,6 +77,30 @@ describe('Bootstrapper', 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'],
};
var bootstrapper = new Bootstrapper(options);
var context = {
app: app,
};
bootstrapper.run(context, function(err) {
if (err) return done(err);
expect(process.bootFlags, 'process: bootFlags').to.eql([
'customjs',
'customjs2',
]);
done();
});
});
afterEach(function() { afterEach(function() {
delete process.bootFlags; delete process.bootFlags;
}); });

View File

@ -0,0 +1,6 @@
'use strict';
module.exports = function(app, callback) {
process.bootFlags.push('customjs');
callback();
};

View File

@ -0,0 +1,6 @@
'use strict';
module.exports = function(app, callback) {
process.bootFlags.push('customjs2');
callback();
};