2014-06-02 16:47:46 +00:00
|
|
|
var assert = require('assert');
|
2014-12-03 06:10:50 +00:00
|
|
|
var cloneDeep = require('lodash').cloneDeep;
|
2014-06-02 16:47:46 +00:00
|
|
|
var fs = require('fs');
|
|
|
|
var path = require('path');
|
2014-06-16 13:07:21 +00:00
|
|
|
var toposort = require('toposort');
|
2014-06-02 16:47:46 +00:00
|
|
|
var ConfigLoader = require('./config-loader');
|
|
|
|
var debug = require('debug')('loopback:boot:compiler');
|
2014-10-21 14:51:17 +00:00
|
|
|
var Module = require('module');
|
2014-11-19 17:54:36 +00:00
|
|
|
var _ = require('lodash');
|
2014-06-02 16:47:46 +00:00
|
|
|
|
2015-03-23 08:19:11 +00:00
|
|
|
var FILE_EXTENSION_JSON = '.json';
|
|
|
|
|
2014-06-02 16:47:46 +00:00
|
|
|
/**
|
|
|
|
* Gather all bootstrap-related configuration data and compile it into
|
|
|
|
* a single object containing instruction for `boot.execute`.
|
|
|
|
*
|
|
|
|
* @options {String|Object} options Boot options; If String, this is
|
|
|
|
* the application root directory; if object, has the properties
|
|
|
|
* described in `bootLoopBackApp` options above.
|
|
|
|
* @return {Object}
|
|
|
|
*
|
|
|
|
* @header boot.compile(options)
|
|
|
|
*/
|
|
|
|
|
|
|
|
module.exports = function compile(options) {
|
|
|
|
options = options || {};
|
|
|
|
|
2014-11-13 14:54:59 +00:00
|
|
|
if (typeof options === 'string') {
|
2014-06-02 16:47:46 +00:00
|
|
|
options = { appRootDir: options };
|
|
|
|
}
|
|
|
|
|
|
|
|
var appRootDir = options.appRootDir = options.appRootDir || process.cwd();
|
|
|
|
var env = options.env || process.env.NODE_ENV || 'development';
|
|
|
|
|
2014-09-29 14:46:13 +00:00
|
|
|
var appConfigRootDir = options.appConfigRootDir || appRootDir;
|
|
|
|
var appConfig = options.config ||
|
|
|
|
ConfigLoader.loadAppConfig(appConfigRootDir, env);
|
2014-06-02 16:47:46 +00:00
|
|
|
assertIsValidConfig('app', appConfig);
|
|
|
|
|
|
|
|
var modelsRootDir = options.modelsRootDir || appRootDir;
|
|
|
|
var modelsConfig = options.models ||
|
|
|
|
ConfigLoader.loadModels(modelsRootDir, env);
|
2014-06-09 12:43:44 +00:00
|
|
|
assertIsValidModelConfig(modelsConfig);
|
2014-06-02 16:47:46 +00:00
|
|
|
|
|
|
|
var dsRootDir = options.dsRootDir || appRootDir;
|
|
|
|
var dataSourcesConfig = options.dataSources ||
|
|
|
|
ConfigLoader.loadDataSources(dsRootDir, env);
|
|
|
|
assertIsValidConfig('data source', dataSourcesConfig);
|
|
|
|
|
2014-11-12 15:56:01 +00:00
|
|
|
// not configurable yet
|
|
|
|
var middlewareRootDir = appRootDir;
|
|
|
|
|
|
|
|
var middlewareConfig = options.middleware ||
|
|
|
|
ConfigLoader.loadMiddleware(middlewareRootDir, env);
|
|
|
|
var middlewareInstructions =
|
|
|
|
buildMiddlewareInstructions(middlewareRootDir, middlewareConfig);
|
|
|
|
|
2015-01-06 13:00:26 +00:00
|
|
|
var componentRootDir = appRootDir; // not configurable yet
|
|
|
|
var componentConfig = options.components ||
|
|
|
|
ConfigLoader.loadComponents(componentRootDir, env);
|
|
|
|
var componentInstructions =
|
|
|
|
buildComponentInstructions(componentRootDir, componentConfig);
|
|
|
|
|
2014-06-02 16:47:46 +00:00
|
|
|
// require directories
|
2014-08-04 08:33:03 +00:00
|
|
|
var bootDirs = options.bootDirs || []; // precedence
|
|
|
|
bootDirs = bootDirs.concat(path.join(appRootDir, 'boot'));
|
2015-03-10 10:35:50 +00:00
|
|
|
resolveRelativePaths(bootDirs, appRootDir);
|
2014-10-27 10:10:57 +00:00
|
|
|
|
2014-08-04 08:33:03 +00:00
|
|
|
var bootScripts = options.bootScripts || [];
|
2015-03-10 10:35:50 +00:00
|
|
|
resolveRelativePaths(bootScripts, appRootDir);
|
|
|
|
|
2014-08-04 08:33:03 +00:00
|
|
|
bootDirs.forEach(function(dir) {
|
|
|
|
bootScripts = bootScripts.concat(findScripts(dir));
|
|
|
|
});
|
2014-06-02 16:47:46 +00:00
|
|
|
|
2014-11-19 17:54:36 +00:00
|
|
|
// de-dedup boot scripts -ERS
|
|
|
|
// https://github.com/strongloop/loopback-boot/issues/64
|
|
|
|
bootScripts = _.uniq(bootScripts);
|
|
|
|
|
2014-06-16 08:39:59 +00:00
|
|
|
var modelsMeta = modelsConfig._meta || {};
|
|
|
|
delete modelsConfig._meta;
|
|
|
|
|
2014-08-04 08:33:03 +00:00
|
|
|
var modelSources = options.modelSources || modelsMeta.sources || ['./models'];
|
2014-06-13 11:14:43 +00:00
|
|
|
var modelInstructions = buildAllModelInstructions(
|
2014-06-16 08:39:59 +00:00
|
|
|
modelsRootDir, modelsConfig, modelSources);
|
2014-06-13 11:14:43 +00:00
|
|
|
|
2014-07-16 18:44:30 +00:00
|
|
|
// When executor passes the instruction to loopback methods,
|
|
|
|
// loopback modifies the data. Since we are loading the data using `require`,
|
|
|
|
// such change affects also code that calls `require` for the same file.
|
2015-03-13 01:03:35 +00:00
|
|
|
var instructions = {
|
2014-06-25 06:09:24 +00:00
|
|
|
config: appConfig,
|
2014-06-02 16:47:46 +00:00
|
|
|
dataSources: dataSourcesConfig,
|
2014-06-13 11:14:43 +00:00
|
|
|
models: modelInstructions,
|
2014-11-12 15:56:01 +00:00
|
|
|
middleware: middlewareInstructions,
|
2015-01-06 13:00:26 +00:00
|
|
|
components: componentInstructions,
|
2014-06-02 16:47:46 +00:00
|
|
|
files: {
|
|
|
|
boot: bootScripts
|
|
|
|
}
|
2015-03-13 01:03:35 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
if (options.appId)
|
|
|
|
instructions.appId = options.appId;
|
|
|
|
|
|
|
|
return cloneDeep(instructions);
|
2014-06-02 16:47:46 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
function assertIsValidConfig(name, config) {
|
2014-11-13 14:54:59 +00:00
|
|
|
if (config) {
|
2014-06-02 16:47:46 +00:00
|
|
|
assert(typeof config === 'object',
|
|
|
|
name + ' config must be a valid JSON object');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-09 12:43:44 +00:00
|
|
|
function assertIsValidModelConfig(config) {
|
|
|
|
assertIsValidConfig('model', config);
|
|
|
|
for (var name in config) {
|
|
|
|
var entry = config[name];
|
|
|
|
var options = entry.options || {};
|
|
|
|
var unsupported = entry.properties ||
|
|
|
|
entry.base || options.base ||
|
|
|
|
entry.plural || options.plural;
|
|
|
|
|
|
|
|
if (unsupported) {
|
|
|
|
throw new Error(
|
2014-07-15 09:05:42 +00:00
|
|
|
'The data in model-config.json is in the unsupported 1.x format.');
|
2014-06-09 12:43:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-02 16:47:46 +00:00
|
|
|
/**
|
|
|
|
* Find all javascript files (except for those prefixed with _)
|
|
|
|
* and all directories.
|
|
|
|
* @param {String} dir Full path of the directory to enumerate.
|
|
|
|
* @return {Array.<String>} A list of absolute paths to pass to `require()`.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
|
|
|
|
function findScripts(dir) {
|
|
|
|
assert(dir, 'cannot require directory contents without directory name');
|
|
|
|
|
|
|
|
var files = tryReadDir(dir);
|
|
|
|
|
|
|
|
// sort files in lowercase alpha for linux
|
|
|
|
files.sort(function(a, b) {
|
|
|
|
a = a.toLowerCase();
|
|
|
|
b = b.toLowerCase();
|
|
|
|
|
|
|
|
if (a < b) {
|
|
|
|
return -1;
|
|
|
|
} else if (b < a) {
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
var results = [];
|
|
|
|
files.forEach(function(filename) {
|
|
|
|
// ignore index.js and files prefixed with underscore
|
2015-03-13 07:13:35 +00:00
|
|
|
if (filename === 'index.js' || filename[0] === '_') {
|
2014-06-02 16:47:46 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var filepath = path.resolve(path.join(dir, filename));
|
|
|
|
var stats = fs.statSync(filepath);
|
|
|
|
|
|
|
|
// only require files supported by require.extensions (.txt .md etc.)
|
|
|
|
if (stats.isFile()) {
|
2015-03-23 08:19:11 +00:00
|
|
|
if (isPreferredExtension(filename))
|
2014-06-02 16:47:46 +00:00
|
|
|
results.push(filepath);
|
|
|
|
else
|
|
|
|
debug('Skipping file %s - unknown extension', filepath);
|
|
|
|
} else {
|
2015-03-13 07:13:35 +00:00
|
|
|
debug('Skipping directory %s', filepath);
|
2014-06-02 16:47:46 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
|
|
|
function tryReadDir() {
|
|
|
|
try {
|
|
|
|
return fs.readdirSync.apply(fs, arguments);
|
2014-11-13 14:54:59 +00:00
|
|
|
} catch (e) {
|
2014-06-02 16:47:46 +00:00
|
|
|
return [];
|
|
|
|
}
|
|
|
|
}
|
2014-06-13 11:14:43 +00:00
|
|
|
|
|
|
|
function buildAllModelInstructions(rootDir, modelsConfig, sources) {
|
|
|
|
var registry = findModelDefinitions(rootDir, sources);
|
|
|
|
|
|
|
|
var modelNamesToBuild = addAllBaseModels(registry, Object.keys(modelsConfig));
|
|
|
|
|
|
|
|
var instructions = modelNamesToBuild
|
|
|
|
.map(function createModelInstructions(name) {
|
|
|
|
var config = modelsConfig[name];
|
|
|
|
var definition = registry[name] || {};
|
|
|
|
|
|
|
|
debug('Using model "%s"\nConfiguration: %j\nDefinition %j',
|
|
|
|
name, config, definition.definition);
|
|
|
|
|
|
|
|
return {
|
|
|
|
name: name,
|
|
|
|
config: config,
|
|
|
|
definition: definition.definition,
|
|
|
|
sourceFile: definition.sourceFile
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
return sortByInheritance(instructions);
|
|
|
|
}
|
|
|
|
|
|
|
|
function addAllBaseModels(registry, modelNames) {
|
|
|
|
var result = [];
|
|
|
|
var visited = {};
|
|
|
|
|
|
|
|
while (modelNames.length) {
|
|
|
|
var name = modelNames.shift();
|
2014-06-16 13:07:21 +00:00
|
|
|
|
|
|
|
if (visited[name]) continue;
|
|
|
|
visited[name] = true;
|
2014-06-13 11:14:43 +00:00
|
|
|
result.push(name);
|
|
|
|
|
|
|
|
var definition = registry[name] && registry[name].definition;
|
|
|
|
if (!definition) continue;
|
|
|
|
|
2014-06-16 13:07:21 +00:00
|
|
|
var base = getBaseModelName(definition);
|
2014-06-13 11:14:43 +00:00
|
|
|
|
|
|
|
// ignore built-in models like User
|
|
|
|
if (!registry[base]) continue;
|
|
|
|
|
|
|
|
modelNames.push(base);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2014-06-16 13:07:21 +00:00
|
|
|
function getBaseModelName(modelDefinition) {
|
|
|
|
if (!modelDefinition)
|
|
|
|
return undefined;
|
|
|
|
|
|
|
|
return modelDefinition.base ||
|
|
|
|
modelDefinition.options && modelDefinition.options.base;
|
|
|
|
}
|
|
|
|
|
2014-06-13 11:14:43 +00:00
|
|
|
function sortByInheritance(instructions) {
|
2014-06-16 13:07:21 +00:00
|
|
|
// create edges Base name -> Model name
|
|
|
|
var edges = instructions
|
|
|
|
.map(function(inst) {
|
|
|
|
return [getBaseModelName(inst.definition), inst.name];
|
|
|
|
});
|
|
|
|
|
|
|
|
var sortedNames = toposort(edges);
|
|
|
|
|
|
|
|
var instructionsByModelName = {};
|
|
|
|
instructions.forEach(function(inst) {
|
|
|
|
instructionsByModelName[inst.name] = inst;
|
|
|
|
});
|
|
|
|
|
|
|
|
return sortedNames
|
|
|
|
// convert to instructions
|
|
|
|
.map(function(name) {
|
|
|
|
return instructionsByModelName[name];
|
|
|
|
})
|
|
|
|
// remove built-in models
|
|
|
|
.filter(function(inst) {
|
|
|
|
return !!inst;
|
|
|
|
});
|
2014-06-13 11:14:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function findModelDefinitions(rootDir, sources) {
|
|
|
|
var registry = {};
|
|
|
|
|
|
|
|
sources.forEach(function(src) {
|
2015-04-22 07:52:06 +00:00
|
|
|
var srcDir = tryResolveAppPath(rootDir, src, { strict: false });
|
2014-10-21 14:51:17 +00:00
|
|
|
if (!srcDir) {
|
|
|
|
debug('Skipping unknown module source dir %j', src);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-06-13 11:14:43 +00:00
|
|
|
var files = tryReadDir(srcDir);
|
2014-10-21 06:21:40 +00:00
|
|
|
|
2014-06-13 11:14:43 +00:00
|
|
|
files
|
|
|
|
.filter(function(f) {
|
|
|
|
return f[0] !== '_' && path.extname(f) === '.json';
|
|
|
|
})
|
|
|
|
.forEach(function(f) {
|
|
|
|
var fullPath = path.resolve(srcDir, f);
|
2014-10-21 06:21:40 +00:00
|
|
|
var entry = loadModelDefinition(rootDir, fullPath, files);
|
2014-06-13 11:14:43 +00:00
|
|
|
var modelName = entry.definition.name;
|
|
|
|
if (!modelName) {
|
|
|
|
debug('Skipping model definition without Model name: %s',
|
|
|
|
path.relative(srcDir, fullPath));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
registry[modelName] = entry;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return registry;
|
|
|
|
}
|
|
|
|
|
2015-03-23 08:19:11 +00:00
|
|
|
function resolveAppPath(rootDir, relativePath, resolveOptions) {
|
|
|
|
var resolvedPath = tryResolveAppPath(rootDir, relativePath, resolveOptions);
|
2015-03-19 11:23:35 +00:00
|
|
|
if (resolvedPath === undefined) {
|
|
|
|
var err = new Error('Cannot resolve path "' + relativePath + '"');
|
|
|
|
err.code = 'PATH_NOT_FOUND';
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
return resolvedPath;
|
|
|
|
}
|
|
|
|
|
2015-03-23 08:19:11 +00:00
|
|
|
function tryResolveAppPath(rootDir, relativePath, resolveOptions) {
|
|
|
|
var fullPath;
|
|
|
|
var start = relativePath.substring(0, 2);
|
|
|
|
|
2015-04-22 07:52:06 +00:00
|
|
|
/* In order to retain backward compatibility, we need to support
|
|
|
|
* two ways how to treat values that are not relative nor absolute
|
|
|
|
* path (e.g. `relativePath = 'foobar'`)
|
|
|
|
* - `resolveOptions.strict = true` searches in `node_modules` only
|
|
|
|
* - `resolveOptions.strict = false` attempts to resolve the value
|
|
|
|
* as a relative path first before searching `node_modules`
|
|
|
|
*/
|
|
|
|
resolveOptions = resolveOptions || { strict: true };
|
2015-03-23 08:19:11 +00:00
|
|
|
|
2015-04-07 08:42:49 +00:00
|
|
|
var isModuleRelative = false;
|
2015-03-23 08:19:11 +00:00
|
|
|
if (relativePath[0] === '/') {
|
|
|
|
fullPath = relativePath;
|
2015-04-07 08:42:49 +00:00
|
|
|
} else if (start === './' || start === '..') {
|
|
|
|
fullPath = path.resolve(rootDir, relativePath);
|
|
|
|
} else if (!resolveOptions.strict) {
|
|
|
|
isModuleRelative = true;
|
2015-03-23 08:19:11 +00:00
|
|
|
fullPath = path.resolve(rootDir, relativePath);
|
|
|
|
}
|
|
|
|
|
2015-04-07 08:42:49 +00:00
|
|
|
if (fullPath) {
|
|
|
|
// This check is needed to support paths pointing to a directory
|
|
|
|
if (fs.existsSync(fullPath)) {
|
2015-03-13 07:13:35 +00:00
|
|
|
return fullPath;
|
2015-04-07 08:42:49 +00:00
|
|
|
}
|
|
|
|
|
2015-03-13 07:13:35 +00:00
|
|
|
try {
|
|
|
|
fullPath = require.resolve(fullPath);
|
|
|
|
return fullPath;
|
|
|
|
} catch (err) {
|
2015-04-07 08:42:49 +00:00
|
|
|
if (!isModuleRelative) {
|
|
|
|
debug ('Skipping %s - %s', fullPath, err);
|
|
|
|
return undefined;
|
|
|
|
}
|
2015-03-13 07:13:35 +00:00
|
|
|
}
|
2014-10-21 14:51:17 +00:00
|
|
|
}
|
|
|
|
|
2015-04-07 08:42:49 +00:00
|
|
|
// Handle module-relative path, e.g. `loopback/common/models`
|
|
|
|
|
|
|
|
// Module.globalPaths is a list of globally configured paths like
|
|
|
|
// [ env.NODE_PATH values, $HOME/.node_modules, etc. ]
|
|
|
|
// Module._nodeModulePaths(rootDir) returns a list of paths like
|
|
|
|
// [ rootDir/node_modules, rootDir/../node_modules, etc. ]
|
|
|
|
var modulePaths = Module.globalPaths
|
|
|
|
.concat(Module._nodeModulePaths(rootDir));
|
|
|
|
|
|
|
|
fullPath = modulePaths
|
|
|
|
.map(function(candidateDir) {
|
|
|
|
try {
|
|
|
|
var filePath = path.join(candidateDir, relativePath);
|
|
|
|
filePath = require.resolve(filePath);
|
|
|
|
return filePath;
|
|
|
|
} catch (err) {
|
|
|
|
return filePath;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.filter(function(candidate) {
|
|
|
|
return fs.existsSync(candidate);
|
|
|
|
})
|
|
|
|
[0];
|
|
|
|
|
|
|
|
if (fullPath)
|
|
|
|
return fullPath;
|
|
|
|
|
|
|
|
debug ('Skipping %s - module not found', fullPath);
|
2014-10-21 14:51:17 +00:00
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
2014-10-21 06:21:40 +00:00
|
|
|
function loadModelDefinition(rootDir, jsonFile, allFiles) {
|
2014-06-13 11:14:43 +00:00
|
|
|
var definition = require(jsonFile);
|
2015-04-07 12:21:05 +00:00
|
|
|
var basename = path.basename(jsonFile, path.extname(jsonFile));
|
2015-04-10 11:38:06 +00:00
|
|
|
definition.name = definition.name || _.capitalize(_.camelCase(basename));
|
2014-10-21 06:21:40 +00:00
|
|
|
|
2014-10-27 10:10:57 +00:00
|
|
|
// find a matching file with a supported extension like `.js` or `.coffee`
|
2015-03-23 08:19:11 +00:00
|
|
|
var sourceFile = fixFileExtension(jsonFile, allFiles, true);
|
2014-06-13 11:14:43 +00:00
|
|
|
|
2015-03-23 08:19:11 +00:00
|
|
|
if (sourceFile === undefined) {
|
|
|
|
debug('Model source code not found: %s', sourceFile);
|
2014-06-13 11:14:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
debug('Found model "%s" - %s %s', definition.name,
|
|
|
|
path.relative(rootDir, jsonFile),
|
|
|
|
sourceFile ? path.relative(rootDir, sourceFile) : '(no source file)');
|
|
|
|
|
|
|
|
return {
|
|
|
|
definition: definition,
|
|
|
|
sourceFile: sourceFile
|
|
|
|
};
|
|
|
|
}
|
2014-11-12 15:56:01 +00:00
|
|
|
|
|
|
|
function buildMiddlewareInstructions(rootDir, config) {
|
|
|
|
var phasesNames = Object.keys(config);
|
|
|
|
var middlewareList = [];
|
|
|
|
phasesNames.forEach(function(phase) {
|
|
|
|
var phaseConfig = config[phase];
|
|
|
|
Object.keys(phaseConfig).forEach(function(middleware) {
|
|
|
|
var allConfigs = phaseConfig[middleware];
|
|
|
|
if (!Array.isArray(allConfigs))
|
|
|
|
allConfigs = [allConfigs];
|
|
|
|
|
|
|
|
allConfigs.forEach(function(config) {
|
2014-11-21 21:56:28 +00:00
|
|
|
var resolved = resolveMiddlewarePath(rootDir, middleware);
|
|
|
|
|
2014-11-12 15:56:01 +00:00
|
|
|
var middlewareConfig = cloneDeep(config);
|
|
|
|
middlewareConfig.phase = phase;
|
|
|
|
|
2014-11-28 11:03:10 +00:00
|
|
|
if (middlewareConfig.params) {
|
|
|
|
middlewareConfig.params = resolveMiddlewareParams(
|
|
|
|
rootDir, middlewareConfig.params);
|
|
|
|
}
|
|
|
|
|
2014-11-21 21:56:28 +00:00
|
|
|
var item = {
|
|
|
|
sourceFile: resolved.sourceFile,
|
2014-11-12 15:56:01 +00:00
|
|
|
config: middlewareConfig
|
2014-11-21 21:56:28 +00:00
|
|
|
};
|
|
|
|
if (resolved.fragment) {
|
|
|
|
item.fragment = resolved.fragment;
|
|
|
|
}
|
|
|
|
middlewareList.push(item);
|
2014-11-12 15:56:01 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
var flattenedPhaseNames = phasesNames
|
|
|
|
.map(function getBaseName(name) {
|
|
|
|
return name.replace(/:[^:]+$/, '');
|
|
|
|
})
|
|
|
|
.filter(function differsFromPreviousItem(value, ix, source) {
|
|
|
|
// Skip duplicate entries. That happens when
|
|
|
|
// `name:before` and `name:after` are both translated to `name`
|
|
|
|
return ix === 0 || value !== source[ix - 1];
|
|
|
|
});
|
|
|
|
|
|
|
|
return {
|
|
|
|
phases: flattenedPhaseNames,
|
|
|
|
middleware: middlewareList
|
|
|
|
};
|
|
|
|
}
|
2014-11-21 21:56:28 +00:00
|
|
|
|
|
|
|
function resolveMiddlewarePath(rootDir, middleware) {
|
|
|
|
var resolved = {};
|
|
|
|
|
|
|
|
var segments = middleware.split('#');
|
|
|
|
var pathName = segments[0];
|
|
|
|
var fragment = segments[1];
|
2015-03-19 11:23:35 +00:00
|
|
|
var middlewarePath = pathName;
|
2015-04-07 08:42:49 +00:00
|
|
|
var opts = { strict: true };
|
2014-11-21 21:56:28 +00:00
|
|
|
|
|
|
|
if (fragment) {
|
|
|
|
resolved.fragment = fragment;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pathName.indexOf('./') === 0 || pathName.indexOf('../') === 0) {
|
|
|
|
// Relative path
|
|
|
|
pathName = path.resolve(rootDir, pathName);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!fragment) {
|
2015-04-07 08:42:49 +00:00
|
|
|
resolved.sourceFile = resolveAppScriptPath(rootDir, middlewarePath, opts);
|
2014-11-21 21:56:28 +00:00
|
|
|
return resolved;
|
|
|
|
}
|
|
|
|
|
|
|
|
var err;
|
|
|
|
|
|
|
|
// Try to require the module and check if <module>.<fragment> is a valid
|
|
|
|
// function
|
|
|
|
var m = require(pathName);
|
|
|
|
if (typeof m[fragment] === 'function') {
|
2015-04-07 08:42:49 +00:00
|
|
|
resolved.sourceFile = resolveAppScriptPath(rootDir, middlewarePath, opts);
|
2014-11-21 21:56:28 +00:00
|
|
|
return resolved;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* module/server/middleware/fragment
|
|
|
|
* module/middleware/fragment
|
|
|
|
*/
|
|
|
|
var candidates = [
|
|
|
|
pathName + '/server/middleware/' + fragment,
|
|
|
|
pathName + '/middleware/' + fragment,
|
|
|
|
// TODO: [rfeng] Should we support the following flavors?
|
|
|
|
// pathName + '/lib/' + fragment,
|
|
|
|
// pathName + '/' + fragment
|
|
|
|
];
|
|
|
|
|
|
|
|
for (var ix in candidates) {
|
|
|
|
try {
|
2015-04-07 08:42:49 +00:00
|
|
|
resolved.sourceFile = resolveAppScriptPath(rootDir, candidates[ix], opts);
|
2014-11-21 21:56:28 +00:00
|
|
|
delete resolved.fragment;
|
|
|
|
return resolved;
|
|
|
|
}
|
|
|
|
catch (e) {
|
|
|
|
// Report the error for the first candidate when no candidate matches
|
|
|
|
if (!err) err = e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
throw err;
|
|
|
|
}
|
2014-11-28 11:03:10 +00:00
|
|
|
|
|
|
|
// Match values starting with `$!./` or `$!../`
|
|
|
|
var MIDDLEWARE_PATH_PARAM_REGEX = /^\$!(\.\/|\.\.\/)/;
|
|
|
|
|
|
|
|
function resolveMiddlewareParams(rootDir, params) {
|
|
|
|
return cloneDeep(params, function resolvePathParam(value) {
|
|
|
|
if (typeof value === 'string' && MIDDLEWARE_PATH_PARAM_REGEX.test(value)) {
|
|
|
|
return path.resolve(rootDir, value.slice(2));
|
|
|
|
} else {
|
|
|
|
return undefined; // no change
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2015-01-06 13:00:26 +00:00
|
|
|
|
|
|
|
function buildComponentInstructions(rootDir, componentConfig) {
|
2015-03-05 09:38:27 +00:00
|
|
|
return Object.keys(componentConfig)
|
|
|
|
.filter(function(name) { return !!componentConfig[name]; })
|
|
|
|
.map(function(name) {
|
|
|
|
return {
|
2015-04-07 08:42:49 +00:00
|
|
|
sourceFile: resolveAppScriptPath(rootDir, name, { strict: true }),
|
2015-03-05 09:38:27 +00:00
|
|
|
config: componentConfig[name]
|
|
|
|
};
|
|
|
|
});
|
2015-01-06 13:00:26 +00:00
|
|
|
}
|
2015-03-10 10:35:50 +00:00
|
|
|
|
|
|
|
function resolveRelativePaths(relativePaths, appRootDir) {
|
2015-04-22 07:52:06 +00:00
|
|
|
var resolveOpts = { strict: false };
|
2015-03-10 10:35:50 +00:00
|
|
|
relativePaths.forEach(function(relativePath, k) {
|
2015-04-22 07:52:06 +00:00
|
|
|
var resolvedPath = tryResolveAppPath(appRootDir, relativePath, resolveOpts);
|
2015-03-13 07:13:35 +00:00
|
|
|
if (resolvedPath !== undefined) {
|
|
|
|
relativePaths[k] = resolvedPath;
|
|
|
|
} else {
|
|
|
|
debug ('skipping boot script %s - unknown file', relativePath);
|
2015-03-10 10:35:50 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2015-03-23 08:19:11 +00:00
|
|
|
|
|
|
|
function getExcludedExtensions() {
|
|
|
|
return {
|
|
|
|
'.json': '.json',
|
|
|
|
'.node': 'node'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function isPreferredExtension (filename) {
|
|
|
|
var includeExtensions = require.extensions;
|
|
|
|
|
|
|
|
var ext = path.extname(filename);
|
|
|
|
return (ext in includeExtensions) && !(ext in getExcludedExtensions());
|
|
|
|
}
|
|
|
|
|
2015-04-07 08:42:49 +00:00
|
|
|
function fixFileExtension(filepath, files, onlyScriptsExportingFunction) {
|
2015-03-23 08:19:11 +00:00
|
|
|
var results = [];
|
|
|
|
var otherFile;
|
|
|
|
|
|
|
|
/* Prefer coffee scripts over json */
|
|
|
|
if (isPreferredExtension(filepath)) return filepath;
|
|
|
|
|
|
|
|
var basename = path.basename(filepath, FILE_EXTENSION_JSON);
|
|
|
|
var sourceDir = path.dirname(filepath);
|
|
|
|
|
|
|
|
files.forEach(function(f) {
|
|
|
|
otherFile = path.resolve(sourceDir, f);
|
|
|
|
|
|
|
|
var stats = fs.statSync(otherFile);
|
|
|
|
if (stats.isFile()) {
|
|
|
|
var otherFileExtension = path.extname(f);
|
|
|
|
|
|
|
|
if (!(otherFileExtension in getExcludedExtensions()) &&
|
|
|
|
path.basename(f, otherFileExtension) == basename) {
|
|
|
|
if (!onlyScriptsExportingFunction)
|
|
|
|
results.push(otherFile);
|
|
|
|
else if (onlyScriptsExportingFunction &&
|
|
|
|
(typeof require.extensions[otherFileExtension]) === 'function') {
|
|
|
|
results.push(otherFile);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return (results.length > 0 ? results[0] : undefined);
|
|
|
|
}
|
|
|
|
|
2015-04-07 08:42:49 +00:00
|
|
|
function resolveAppScriptPath(rootDir, relativePath, resolveOptions) {
|
|
|
|
var resolvedPath = resolveAppPath(rootDir, relativePath, resolveOptions);
|
2015-03-23 08:19:11 +00:00
|
|
|
var sourceDir = path.dirname(resolvedPath);
|
|
|
|
var files = tryReadDir(sourceDir);
|
2015-04-07 08:42:49 +00:00
|
|
|
var fixedFile = fixFileExtension(resolvedPath, files, false);
|
2015-03-23 08:19:11 +00:00
|
|
|
return (fixedFile === undefined ? resolvedPath : fixedFile);
|
|
|
|
}
|