mmir-tooling
Version:
Build tools for MMIR projects
264 lines (215 loc) • 12.3 kB
text/typescript
import { ImplementationType , ImplementationOption , ImplementationBuildEntry , AnyImplementationOptions , DirectoriesInfo , ResourceConfig , RuntimeConfiguration , BuildAppConfig , ImplementationEntry , ViewBuildEntry } from '../index.d';
import { WebpackAppConfig } from '../index-webpack.d';
import path from 'path';
import fs from 'fs-extra';
import _ from 'lodash';
import fileUtils from '../utils/filepath-utils';
import appConfigUtils from '../utils/module-config-init';
import directoriesUtil from '../tools/directories-utils';
import optionUtils from '../tools/option-utils';
import logUtils from '../utils/log-utils';
const log = logUtils.log;
const warn = logUtils.warn;
//mode: 'controller' | 'helper' | 'model'
function readDir(mode: ImplementationType, dir: string, list: ImplementationBuildEntry[], options: ImplementationOption, generalOptions: AnyImplementationOptions): void {
var files = fs.readdirSync(dir);
var dirs = [];
// log('read dir "'+dir+'" -> ', files);
files.forEach(function(p){
var absPath = path.join(dir, p);
if(fileUtils.isDirectory(absPath)){
dirs.push(absPath);
return false;
} else {
var normalized = fileUtils.normalizePath(absPath);
var id = path.basename(absPath, '.js');
switch(mode){
case 'helper':
if(!/Helper$/.test(id)){
warn('impl-utils.addFromDirectory(): invalid file-name for helper '+id+' (must have suffix "Helper"): "'+normalized+'"!');
id += 'Helper';
}
break;
case 'controller': // intentional fall through
case 'model': // intentional fall through
default:
break;
}
log('impl-utils.addFromDirectory(): parsing '+mode+' implemenation '+id+' at "'+normalized+'"');//DEBUG
var opt = options && options[id];
if((opt && (opt.exclude || opt.file) || opt === false)){
//-> ignore/exclude this impl. file!
log('impl-utils.addFromDirectory(): excluding '+mode+' implemenation '+id+' at "'+normalized+'"!');
return;//////////////////// EARLY EXIT //////////////////
}
var modExport = opt && opt.addModuleExport;
if(typeof modExport === 'undefined'){
modExport = generalOptions.addModuleExport || false;
}
list.push({
id: id,
name: toImplName(id),
type: mode,
file: normalized,
addModuleExport: modExport
});
}
});
// log('read sub-dirs -> ', dirs);
var size = dirs.length;
if(size > 0){
for(var i = 0; i < size; ++i){
readDir(mode, dirs[i], list, options, generalOptions);
}
}
}
function addFromOptions(implMap: {[id: string]: ImplementationEntry}, list: ImplementationBuildEntry[], appRootDir: string, _generalOptions: AnyImplementationOptions): void {
var impl: ImplementationEntry, entry: ImplementationBuildEntry;
for(var id in implMap){
impl = implMap[id];
if(impl && impl.file && !impl.exclude){
entry = _.cloneDeep(impl);
if(!path.isAbsolute(entry.file)){
entry.file = path.resolve(appRootDir, entry.file);
}
entry.file = fileUtils.normalizePath(entry.file);
if(entry.id && entry.id !== id){
warn('impl-utils.addFromOptions(): entry from implOptions for ID "'+id+'" has differing field id with value "'+entry.id+'", overwritting the id field with "'+id+'"!');//FIXME proper webpack error/warning
}
entry.id = id;
if(!entry.name){
entry.name = toImplName(id);
}
if(!/^(controller|helper|model)$/.test(entry.type)){
warn('impl-utils.addFromOptions(): entry from implOptions for ID "'+id+'" has invalid type '+JSON.stringify(entry.type)+', overwritting the type field with "controller"!');//FIXME proper webpack error/warning
entry.type = 'controller';
}
//TODO verify existence of entry.file?
if(!contains(list, id, entry.type)){
log('impl-utils.addFromOptions(): adding ', entry);//DEBUG
list.push(entry)
} else {
warn('impl-utils.addFromOptions(): entry from implOptions for ID '+id+' already exists in implementation-list, ignoring entry!');//FIXME proper webpack error/warning
}
}
else {//DEBUG
log('impl-utils.addFromOptions(): entry for '+id+' has no file set -> ignore ', impl);//DEBU
}
}
}
function contains(implList: ImplementationBuildEntry[], id: string, type: ImplementationType): boolean {
return implList.findIndex(function(item){
return item.id === id && item.type === type;
}) !== -1;
}
function toImplName(id: string): string {
return id[0].toUpperCase() + id.substring(1);
}
function toAliasPath(impl: ImplementationEntry): string {
return path.normalize(impl.file).replace(/\.js$/i, '');
}
function toAliasId(impl: ImplementationBuildEntry): string {
return 'mmirf/'+impl.type+'/'+impl.id;//FIXME formalize IDs for loading views in webpack (?)
}
export = {
/**
* [description]
* @param {"controller" | "helper" | "model"} mode the kind of implementation (modules) that the directory contains
* @param {ImplOptions} options the implementation options where
* options.path: REQUIRED the directory from which to add the implementations, and has the following structure:
* <directory>/<module-id-1>.js
* <directory>/<module-id-2>.js
* ...
* options["controllers" | "helpers" | "models"]: OPTIONAL a map of impl. IDs, i.e. {[implID: string]: ImplOption} with specific options for processing the corresponding implementation:
* options[types][id].exclude {Boolean}: OPTIONAL if <code>true</code>, the corresponding implementation will be excluded
* options[types][id].addModuleExport {Boolean|String}: OPTIONAL if <code>true</code>, the implementation will be exported,
* i.e. will be made a "requireable module"; the exported variable will be the impl. name,
* or, if addModuleExport is a String, this String will be used.
* @param {String} appRootDir the root directory of the app (against which relative paths will be resolved)
* @param {Array<ImplEntry>} [implList] OPTIONAL list of ImplEntry objects, to which the new entries (read from the options.directory) will be added
* if omitted, a new list will be created and returned.
* ImplEntry.id {String}: the implementation ID
* ImplEntry.name {String}: the implementation's name (usually the ID with capitalized first letter)
* ImplEntry.type {"controller" | "helper" | "model"}: the implementation's type
* ImplEntry.file {String}: the path to the implementation's file
* ImplEntry.addModuleExport {Boolean|String}: OPTIONAL if <code>true</code>, the implementation will be exported,
* i.e. will be made a "requireable module"; the exported variable will be the impl. name,
* or, if addModuleExport is a String, this String will be used.
* @return {Array<ImplEntry>} the list of ImplEntry objects
*/
implFromDir: function(mode: ImplementationType, options: AnyImplementationOptions, appRootDir: string, implList: ImplementationBuildEntry[]): ImplementationBuildEntry[] {
var dir = options.path;
if(!path.isAbsolute(dir)){
dir = path.resolve(appRootDir, dir);
}
var list = implList || [];
readDir(mode, dir, list, options[mode + 's'], options);
return list;
},
/**
* add implementations form options[mode+'s'] map {[implID: string]: ImplOption}, if the ImplOption has a <code>file</code> field set.
* @param {"controller" | "helper" | "model"} mode the kind of implementation (modules) that the directory contains
* @param {ImplOptions} options the implementation options with field options[mode+'s'], e.g. option.controllers for mode "controller"
* @param {String} appRootDir the root directory of the app (against which relative paths will be resolved)
* @param {{Array<ImplEntry>}} [implList] OPTIONAL
* @return {{Array<ImplEntry>}}
*/
implFromOptions: function(mode: ImplementationType, options: AnyImplementationOptions, appRootDir: string, implList: ImplementationBuildEntry[]): ImplementationBuildEntry[] {
var list = implList || [];
addFromOptions(options[mode + 's'], list, appRootDir, options);
return list;
},
/**
* apply the "global" options from `options` or default values to the entries
* from `implList` if its corresponding options-field is not explicitly specified.
*
* @param {ImplOptions} options the implementation options
* @param {{Array<ImplEntry>}} implList
* @return {{Array<ImplEntry>}}
*/
applyDefaultOptions: function(options: AnyImplementationOptions, implList: ImplementationBuildEntry[] | ViewBuildEntry[]): ImplementationBuildEntry[] | ViewBuildEntry[] {
implList.forEach(function(impl: ImplementationBuildEntry | ViewBuildEntry){
[
{name: 'addModuleExport', defaultValue: false}
].forEach(function(fieldInfo){
optionUtils.applySetting(fieldInfo.name, impl, options, fieldInfo.defaultValue);
});
});
return implList;
},
/**
* add implementations to (webpack) app build configuration
*
* @param {Array<ImplEntry>} implList list of ImplEntry objects:
* impl.id {String}: the impl id (usually the language code, e.g. "en" or "de")
* impl.file {String}: the path to the JSON impl (from which the executable impl will be created)
* @param {[type]} appConfig the app configuration to which the implementations will be added
* @param {[type]} directories the directories.json representation
* @param {ResourcesConfig} _resources the resources configuration
* @param {[type]} runtimeConfiguration the configuration.json representation
*/
addImplementationsToAppConfig: function(implList: ImplementationBuildEntry[], appConfig: BuildAppConfig | WebpackAppConfig, directories: DirectoriesInfo, _resources: ResourceConfig, _runtimeConfiguration: RuntimeConfiguration): void {
if(!implList || implList.length < 1){
return;
}
implList.forEach(function(impl){
var aliasId = toAliasId(impl);
appConfigUtils.addIncludeModule(appConfig, aliasId, toAliasPath(impl));
log('impl-utils.addImplementationsToAppConfig(): adding '+impl.type+' implemenation '+impl.id+': ['+aliasId+'] -> "'+toAliasPath(impl)+'"!');
switch(impl.type){
case 'controller':
directoriesUtil.addCtrl(directories, aliasId);
break;
case 'helper':
directoriesUtil.addHelper(directories, aliasId);
break;
case 'model':
directoriesUtil.addModel(directories, aliasId);
break;
default:
warn('impl-utils.addImplementationsToAppConfig(): unknown type '+impl.type+' for implemenation '+impl.id+' at "'+impl.file+'"!');
break;
}
});
}
};