UNPKG

allexsdk

Version:
574 lines (480 loc) 20.2 kB
var REFERENCES = require('../../templates/webapps/predefined_references.json'); function createPBWebAppReader (Lib, Node) { 'use strict'; var Bower = require('allex_bowerhelperssdklib')(Lib), Fs = Node.Fs, Allex = require('allex_allexjshelperssdklib')(Lib), Path = Node.Path, Q = Lib.q, QLib = Lib.qlib; function PBWebAppReader (dir, options, starteddefer) { if (!starteddefer) { throw new Error('PBWebAppReader must get a starteddefer in ctor'); } if (!options) options = {}; this.cwd = Path.resolve(dir); var pb_path = Path.join(this.cwd, 'protoboard.json'); if (!Fs.fileExists(pb_path)) return this.error('No file: "protoboard.json" in '+this.cwd); this.pb_data = Fs.safeReadJSONFileSync(pb_path); this._references = Lib.extend({}, REFERENCES, this.pb_data.references); if (!this.pb_data) this.pb_data = {}; if (!this.pb_data.partials) this.pb_data.partials = {}; if (!this.pb_data) return this.error('No protoboard data in file: '+pb_path); if (!this.pb_data.protoboard && this.pb_data.protoboard.role !== 'web_app') return this.error('Invalid protoboard content at '+this.cwd); this.requires_connection = this.pb_data.protoboard.requires_connection; this.devel = ('devel' in options) ? options.devel : true; this.distro = ('distro' in options) ? options.distro : null; var includes = []; if (this.devel) { includes.push(Bower.native.config.storage.links); } var cwdbower = Path.resolve(dir,'bower_components'); var cwdbower_rc = Path.resolve(dir, '.bowerrc'); if (Fs.fileExists(cwdbower_rc)) { var td = Fs.readFieldFromJSONFile(cwdbower_rc, 'directory'); if (td) cwdbower = td; } this.components_dir = cwdbower; includes.push(cwdbower); if (options.includes){ if (Lib.isStrng(options.includes)) includes.push(options.includes); if (Lib.isArray(options.includes)) Array.prototype.push.apply(includes, options.includes); } this.includes = includes.map(transformIncludePath.bind(null, this, this.cwd)); this.components = {}; this.pages = {}; this.partials = []; this.jstemplates = {}; var promises = []; Lib.traverse(this.pb_data.pages, this._prepare_page.bind(this, promises)); QLib.promise2defer(Q.all(promises).then( this.buildNamespace.bind(this) ).then( this.searchComponents.bind(this) ).then( QLib.returner(this) ), starteddefer); } PBWebAppReader.prototype.destroy = function () { this._references = null; this.components_dir = null; Lib.objNullAll(this.jstemplates); this.jstemplates = null; Lib.objNullAll(this.partials); this.partials = null; Lib.objNullAll(this.pages); this.pages = null; Lib.objNullAll(this.components); this.components = null; Lib.arryNullAll(this.includes); this.includes = null; this.devel = null; this.requires_connection = null; this.cwd = null; this.defer = null; this.distro = null; }; PBWebAppReader.prototype.getSafeDistro = function () { return this.distro ? this.distro : (this.devel ? 'devel' : 'production'); }; PBWebAppReader.prototype.getDefaultConnection = function () { var version = this.devel ? 'devel' : 'production'; if (this.pb_data.protoboard && this.pb_data.protoboard.default_connections && this.pb_data.protoboard.default_connections[version]){ return this.pb_data.protoboard.default_connections[version]; }else{ return this.devel ? './connections/local' : './connections/public'; } }; function absolutizePath(root, path) { return Path.isAbsolute(path) ? path : Path.resolve(root, path); } function transformIncludePath(pbw, root, path) { if (!path) return pbw.error('Invalid path ...'+path); var ret = absolutizePath(root, path); if (!Lib.isString(ret)) return pbw.error('Invalid includes path '+ret); return ret; }; PBWebAppReader.prototype.connectionDataTemplateTarget = function () { return Path.resolve(this.cwd, 'js', '_connection.js'); }; PBWebAppReader.prototype.set_connection_data = function (connection_data) { if (this.connection_data) return this.error('Connection data already set ...'); ////TODO: what to do with this ?!?!?! //if (!this.requires_connection) return this.error('Connection data was never required'); this._prepareJSTemplate({ dest_path: this.connectionDataTemplateTarget(), src_path: Path.resolve(__dirname, '../../templates/connection.js'), data: {'connection':JSON.stringify(connection_data)} }); }; PBWebAppReader.prototype.isConnectionDataSet = function () { return this.jstemplates[this.connectionDataTemplateTarget()]; }; PBWebAppReader.prototype._prepareJSTemplate = function (rec) { this.jstemplates[rec.dest_path] = rec; }; PBWebAppReader.prototype._preparelocalPartials = function () { this._iteratePartialsSubDir(null, 'partials'); }; PBWebAppReader.prototype._iteratePartialsSubDir = function (cwd, path) { var content = { files: [], dirs: [] }; if (cwd) { path = Path.join(cwd, path); } var root = Path.resolve(this.cwd, path); if (Fs.dirExists(root)) Fs.readdirSync(root).forEach(diversify.bind(null, content, root)); if (content.files.length) { this.pb_data.partials[path] = content.files; } if (content.dirs.length) { content.dirs.forEach(this._iteratePartialsSubDir.bind(this, path)); } } function diversify (content, root, item) { var stat = Fs.lstatSync(Path.resolve(root, item)); if (stat.isSymbolicLink()){ ///will ignore symbolic links ... return; } if (stat.isFile()) content.files.push(item); if (stat.isDirectory()) content.dirs.push(item); }; PBWebAppReader.prototype._prepare_partials = function (record, root){ var component_name = extractComponentName(root); if (component_name) this._requireComponent(component_name); if (Lib.isArray(record)) { Array.prototype.push.apply(this.partials,record.map(this._preparePartialsRecord.bind(this, root, component_name))); } }; PBWebAppReader.prototype._preparePartialsRecord = function (root, component_name, rec) { var src = Path.join(root, rec); var ret = { component: component_name, resolved: false, src_path: component_name ? replaceComponentSource(component_name, src) : Path.resolve(this.cwd, src), dest_path:component_name ? src.replace(COMPONENTS_START, 'partials/') : Path.resolve(this.cwd, '_generated', src) }; return ret; }; function findhash (item, index) { if (Lib.isString(item) && item.charAt(0) === '#') return index; } function replacePage (name, item) { if (!Lib.isString(item)) return item; return item.replace('PAGE', name); } PBWebAppReader.prototype.resolveReferences = function (list, page) { try { var references_list = []; while (true) { var index = Lib.traverseConditionally(list, findhash); if (Lib.isUndef(index)) { break; } var ref = list[index]; if (references_list.indexOf(ref) > -1) { throw new Error("Circular dependency detected: "+ref); } if (!this._references[ref]) throw new Error('Missing reference '+ref); var args = [index, 1].concat(this._references[ref]); Array.prototype.splice.apply(list, args); } if (!list) return []; var ret = list.map(replacePage.bind(null, page)); }catch (e) { console.log(e, e.stack); } return ret; }; PBWebAppReader.prototype._prepare_page = function (promises, p_data, name){ promises.push(this._do_page_preparation(p_data, name)); }; PBWebAppReader.prototype._do_page_preparation = function (p_data, name) { if (this.pages[name]) return Q.reject(new Error('Duplicate page declaration '+name)); return Q.all([ Q.all(this.resolveReferences(p_data.js, name).map(this._prepareAsset.bind(this, 'js'))), Q.all(this.resolveReferences(p_data.css, name).map(this._prepareAsset.bind(this, 'css'))) ]).then(this.onJSCSS.bind(this, p_data, name)); }; PBWebAppReader.prototype.onJSCSS = function (p_data, name, jscss) { var js = jscss[0], css = jscss[1]; if (!Lib.isArray(js)) { console.error('no js', jscss); process.exit(1); } if (!Lib.isArray(css)) { console.error('no css', jscss); process.exit(1); } this.pages[name] = { 'connection' : false, 'js': js, 'css': css, 'vars': p_data.vars, 'distro_vars' : p_data.distro_vars, 'include_manifest_devel' : p_data.include_manifest_devel, }; if ( this.requires_connection ) { this.pages[name].connection = 'connection' in p_data && !p_data.connection ? false : true; } if (this.pages[name].connection) { return this._prepareSourceLessAsset('js','_connection.js').then( this.onSourceLessAsset.bind(this) ); } return Q(true); }; PBWebAppReader.prototype.onSourceLessAsset = function (asset) { this.pages[name].js.unshift(asset); return Q(true); }; var COMPONENTS_START = /^components\//; function replaceComponentSource (name, src) { return src.replace(Path.join('components', name)+Path.sep, ''); }; function extractComponentName (path) { var ret = null, temp = null; if (path.match(COMPONENTS_START)) { temp = path.split('/'); ret = temp[1]; if (!ret) return this.error('Components record must have component name '+path); } return ret; } PBWebAppReader.prototype._prepareSourceLessAsset = function (root_if_no_component, record) { var ret = this._prepareAsset(root_if_no_component, record); return ret; }; PBWebAppReader.prototype._prepareAsset = function (root_if_no_component, record) { var ret = { component:null, src_path:null, dest_path:null, resolved: false }, alternative = null,temp = null; if (!Lib.isString(record)) { //basepath field: path //production field: minified path //devel field: devel path //conditional field: conditional tag //distro field : choices for different build distro if (!record.basepath) { record.basepath = './'; } if (!record.production) record.production = record.devel; alternative = this.devel ? record.devel : record.production; if (record.distro) { if (this.distro in record.distro) { var rdd = record.distro[this.distro]; if (Lib.isString(rdd)){ alternative = rdd; }else{ if (!rdd.devel) { return Q.reject(new Error ('No devel distro asset in record: '+JSON.stringify(rdd))); } if (!rdd.production) rdd.production = rdd.devel; alternative = this.devel ? rdd.devel : rdd.production; } } } if (!alternative) return this.error('Record invalid:'+JSON.stringify(record, null, 2)); ret.dest_path = Path.join(root_if_no_component, record.basepath, alternative); ret.src_path = this.devel ? Path.join(root_if_no_component, ret.dest_path) : absolutizePath (this.cwd, ret.dest_path); ret.conditional = record.conditional; return Q(ret); }else{ return this.testForAllex(record).then(this.onTestedForAllex.bind(this, root_if_no_component, record, ret)); } }; PBWebAppReader.prototype.onSrcPathForPrepareAsset = function (root_if_no_component, ret, src_path) { ret.src_path = src_path; ret.dest_path = ret.src_path; ret.component = extractComponentName(ret.src_path); if (ret.component) { this._requireComponent(ret.component); ret.src_path = ret.src_path.replace(Path.join('components', ret.component)+Path.sep,''); }else{ var is_public = Lib.traverseConditionally (this.pb_data.public_dirs,isPublicAsset.bind(null, ret.src_path)); if (ret.src_path.match (/(\:\/\/)/)) throw new Error('Global links now allowed: '+ret.src_path); ret.src_path = Path.resolve(this.cwd, is_public ? './' : root_if_no_component, ret.src_path); ret.dest_path= Path.join(is_public ? './' : root_if_no_component, ret.dest_path); ret.resolved = true; } return Q(ret); }; function isPublicAsset (ret, public_dir) { if (ret.match (new RegExp('^'+public_dir+'\/'))) { return true; } } PBWebAppReader.prototype.testForAllex = function (record) { var spl1 = record.split('/'); return Lib.moduleRecognition(spl1.shift()).then(onRecognizedForTestForAllex.bind(null, spl1)); }; function onRecognizedForTestForAllex (spl1, component) { if (!component) return Q(null); if (Lib.isString(component)) return Q(null); return Q('components/'+component.modulename+'/'+(spl1.length ? spl1.join('/') : 'dist/browserified.js')); } PBWebAppReader.prototype.onTestedForAllex = function (root_if_no_component, record, ret, testresult) { return this.onSrcPathForPrepareAsset(root_if_no_component, ret, testresult ? testresult : record); }; PBWebAppReader.prototype._requireComponent = function (name) { if (this.components[name]) return this.components[name]; this.components[name] = null; return null; }; PBWebAppReader.prototype.buildNamespace = function () { var nsfile; try { nsfile = Fs.readJSONSync(Path.join(Node.getNamespacePath(), '.allexns.json')); Fs.ensureDirSync(Path.join(this.components_dir, 'allexns')); Fs.writeFileSync(Path.join(this.components_dir, 'allexns', 'ns.js'), "window['.allexns.js'] = "+JSON.stringify(nsfile, null, 2)+";\n"); } catch (e) { return Q.reject(new Error('Cannot read .allexns.json: '+e.message)); } return Q(true); }; PBWebAppReader.prototype.searchComponents = function () { var bower_path = Path.resolve(this.cwd, 'bower.json'), deps = null; if (Fs.fileExists(bower_path)){ deps = Fs.readFieldFromJSONFile(bower_path, 'dependencies'); } if (deps) { for (var i in deps) { if (!this.components[i]) this.components[i] = null; } } Lib.traverseConditionally (this.includes, this._searchComponents.bind(this)); return Q.all(this.getUnresolvedComponents().map (this.resolveAllexComponents.bind(this))); }; PBWebAppReader.prototype.resolveAllexComponents = function (name) { var p = Allex.paths.allexServiceWebC(name); ///global one ..., if (Fs.dirExists(p)) { this.storeComponent(name, p); return Q.resolve(true); } return Allex.paths.allexServiceWebC(name, this.cwd).then (this._checkAllexComponentExists.bind(this, name)); }; PBWebAppReader.prototype._checkAllexComponentExists = function (name, p) { if (Fs.dirExists(p)) { this.storeComponent(name, p); } return Q.resolve (true); }; PBWebAppReader.prototype._searchComponents = function (dir){ var unresolved = this.getUnresolvedComponents().length, bower_fp = Path.resolve(this.cwd, dir, 'bower.json'), pb_fp = Path.resolve(this.cwd, dir, 'protoboard.json'); if (Fs.fileExists(pb_fp) && Fs.fileExists(bower_fp)){ ///this is exact component dir ... var name = Fs.readFieldFromJSONFile(bower_fp, 'name'); if (!this.components[name]){ this.storeComponent(name, Path.resolve(this.cwd, dir)); } return; }else{ var resolved = []; if (!Fs.dirExists(dir)) return; Fs.readdirSync(dir).forEach(isComponentSuitable.bind(null, this, dir)); } return this.getUnresolvedComponents().length ? undefined : true; }; PBWebAppReader.prototype.storeComponent = function (name, path) { if (this.components[name]) throw new Error('Already loaded component '+name); var ret = { name: name, path:path, public_dirs: null }; if (!Fs.dirExists(path) ) throw new Error('Unable to locate directory: '+path); this.components[name] = ret; var pbf = Path.join(path, 'protoboard.json'); if (!Fs.fileExists(pbf)) return; //nothing to be done var pb = Fs.readFieldFromJSONFile(pbf); ret.public_dirs = pb && pb.protoboard && pb.protoboard.public_dirs ? pb.protoboard.public_dirs : null; if (pb.partials) { Lib.traverse(pb.partials, expandPBPartialsRecordWithComponentPartials.bind(null, this.pb_data.partials, name)); } ret.protoboard = true; }; function expandPBPartialsRecordWithComponentPartials (pbdata, component_name, partials, root) { var nr = Path.join('components', component_name, root); if (pbdata[nr]) return; //won't affect webapp partials data .... pbdata[nr] = partials; } function isComponentSuitable (pbw, root, dir){ var fp = absolutizePath(root, dir); if (!Fs.dirExists(fp)) return; var name = Path.basename(fp), bower = Path.resolve(fp, 'bower.json'); if (Fs.fileExists(bower)) name = Fs.readFieldFromJSONFile(bower, 'name'); if (pbw.components[name]) return; if (pbw.getUnresolvedComponents().indexOf(name) < 0) return; pbw.storeComponent(name, fp); } PBWebAppReader.prototype.getUnresolvedComponents = function () { var ret = []; for (var i in this.components) { if (this.components[i] === null) ret.push(i); } return ret; }; PBWebAppReader.prototype.error = function (str){ throw new Error(str); }; PBWebAppReader.prototype.resolveAssets = function () { var l = this.getUnresolvedComponents().length; if (l) return this.error('Unable to resolve assets untill all components are ready ...'); Lib.traverse(this.pages, this._resolvePage.bind(this)); this.partials.forEach(this._resolveAsset.bind(this)); }; PBWebAppReader.prototype._resolvePage = function (page_data, name){ page_data.js.forEach(this._resolveAsset.bind(this)); page_data.css.forEach(this._resolveAsset.bind(this)); }; PBWebAppReader.prototype._resolveAsset = function (rec) { //console.log(JSON.stringify(rec)); if (rec.resolved || !rec.component) { return; ///nothing to be done: either resolved eithec component igonrant ... } var component = this.components[rec.component]; if (!component) return; /// component not resolved yet ... rec.resolved = true; rec.src_path = Path.resolve(this.cwd, component.path, rec.src_path); }; PBWebAppReader.prototype.finalize = function () { if (!this.isReady()) throw new Error('Unable to finalize since I am not as ready as you might think...'); this._preparelocalPartials(); Lib.traverse(this.pb_data.partials, this._prepare_partials.bind(this)); this.resolveAssets(); }; PBWebAppReader.prototype.isReady = function () { var len = this.getUnresolvedComponents().length; var ret = (this.requires_connection ? len === 0 && this.isConnectionDataSet() : len === 0); if (ret) return !!ret; Node.error('Unresolved components '+len+' ('+this.getUnresolvedComponents().join(',')+')',', connection set: ',!!this.isConnectionDataSet()); }; PBWebAppReader.prototype.getProtoboards = function () { var ret = []; Lib.traverse(this.components, isProtoboard.bind(null, ret)); return ret; }; PBWebAppReader.prototype.doneWithModules = function () { var len = this.getUnresolvedComponents().length; if (!len) { //nothing to be done _fireIfReady will do needed ... return; } }; function isProtoboard (ret, rec) { if (rec.protoboard) ret.push (rec); }; return PBWebAppReader; } module.exports = createPBWebAppReader;