diff --git a/cli/domain/local.js b/cli/domain/local.js index 5643904fb..3f484a7f3 100644 --- a/cli/domain/local.js +++ b/cli/domain/local.js @@ -14,7 +14,7 @@ var path = require('path'); var settings = require('../../resources/settings'); var Targz = require('tar.gz'); var uglifyJs = require('uglify-js'); -var validator = require('../../registry/domain/validator'); +var validator = require('../../registry/domain/validators'); var _ = require('underscore'); module.exports = function(){ @@ -119,11 +119,20 @@ module.exports = function(){ var components = fs.readdirSync(componentsDir).filter(function(file){ var filePath = path.resolve(componentsDir, file), - isDir = fs.lstatSync(filePath).isDirectory(); + isDir = fs.lstatSync(filePath).isDirectory(), + packagePath = path.join(filePath, 'package.json'); - return isDir ? (fs.readdirSync(filePath).filter(function(file){ - return file === 'package.json'; - }).length === 1) : false; + if(!isDir || !fs.existsSync(packagePath)){ + return false; + } + + var content = fs.readJsonSync(packagePath); + + if(!content.oc || !!content.oc.packaged){ + return false; + } + + return true; }); var fullPathComponents = _.map(components, function(component){ @@ -278,6 +287,7 @@ module.exports = function(){ delete component.oc.files.client; component.oc.version = ocInfo.version; + component.oc.packaged = true; if(!!component.oc.files.data){ var dataPath = path.join(componentPath, component.oc.files.data), diff --git a/cli/facade/dev.js b/cli/facade/dev.js index cd439b176..0d1526a1c 100644 --- a/cli/facade/dev.js +++ b/cli/facade/dev.js @@ -73,7 +73,7 @@ module.exports = function(dependencies){ if(!packaging){ packaging = true; - logger.log('Packaging components...'.yellow); + logger.logNoNewLine('Packaging components...'.yellow); async.eachSeries(componentsDirs, function(dir, cb){ local.package(dir, false, function(err){ @@ -85,15 +85,15 @@ module.exports = function(dependencies){ }, function(error){ if(!!error){ var errorDescription = ((error instanceof SyntaxError) || !!error.message) ? error.message : error; - logger.log(format('An error happened while packaging {0}: {1}', componentsDirs[i], errorDescription.red)); - logger.log('retrying in 10 seconds...'.yellow); + logger.log(format('an error happened while packaging {0}: {1}', componentsDirs[i], errorDescription.red)); + logger.log('Retrying in 10 seconds...'.yellow); setTimeout(function(){ packaging = false; packageComponents(componentsDirs); }, 10000); } else { packaging = false; - logger.log('complete'.green); + logger.log('OK'.green); if(_.isFunction(callback)){ callback(); } @@ -103,7 +103,7 @@ module.exports = function(dependencies){ }; var loadDependencies = function(components, cb){ - logger.log('Ensuring dependencies are loaded'.yellow); + logger.logNoNewLine('Ensuring dependencies are loaded...'.yellow); var dependencies = getDepsFromComponents(components), missing = []; @@ -131,12 +131,13 @@ module.exports = function(dependencies){ }); }); } else { + logger.log('OK'.green); cb(dependencies); } }); }; - logger.log('Looking for components...'.yellow); + logger.logNoNewLine('Looking for components...'.yellow); local.getComponentsByDir(componentsDir, function(err, components){ if(err){ @@ -145,9 +146,14 @@ module.exports = function(dependencies){ return logger.log(format(errors.DEV_FAIL, errors.COMPONENTS_NOT_FOUND).red); } + logger.log('OK'.green); + _.forEach(components, function(component){ + logger.log('>> '.green + component); + }); + loadDependencies(components, function(dependencies){ packageComponents(components, function(){ - logger.log('Starting dev registry on localhost:' + port); + logger.logNoNewLine(format('Starting dev registry on localhost:{0}...', port).yellow); var conf = { local: true, diff --git a/cli/index.js b/cli/index.js index fb45f2b03..d44a412a1 100644 --- a/cli/index.js +++ b/cli/index.js @@ -8,7 +8,12 @@ var _ = require('underscore'); var dependencies = { local: new Local(), - logger: console, + logger: { + log: console.log, + logNoNewLine: function(msg){ + return process.stdout.write(msg.toString()); + } + }, registry: new Registry() }; diff --git a/components/oc-client/_package/package.json b/components/oc-client/_package/package.json index 559f95ba5..0902e40d1 100644 --- a/components/oc-client/_package/package.json +++ b/components/oc-client/_package/package.json @@ -21,6 +21,7 @@ "src": "server.js" } }, - "version": "0.15.3" + "version": "0.15.3", + "packaged": true } } diff --git a/components/oc-client/_package/src/head.load.js b/components/oc-client/_package/src/head.load.js index 3f73427e2..b7a31c778 100644 --- a/components/oc-client/_package/src/head.load.js +++ b/components/oc-client/_package/src/head.load.js @@ -1 +1,707 @@ -!function(e,t){"use strict";function n(){}function r(e,t){if(e){"object"==typeof e&&(e=[].slice.call(e));for(var n=0,r=e.length;r>n;n++)t.call(e,e[n],n)}}function a(e,n){var r=Object.prototype.toString.call(n).slice(8,-1);return n!==t&&null!==n&&r===e}function o(e){return a("Function",e)}function u(e){return a("Array",e)}function i(e){var t=e.split("/"),n=t[t.length-1],r=n.indexOf("?");return-1!==r?n.substring(0,r):n}function c(e){e=e||n,e._done||(e(),e._done=1)}function l(e,t,r,a){var o="object"==typeof e?e:{test:e,success:t?u(t)?t:[t]:!1,failure:r?u(r)?r:[r]:!1,callback:a||n},i=!!o.test;return i&&o.success?(o.success.push(o.callback),C.load.apply(null,o.success)):!i&&o.failure?(o.failure.push(o.callback),C.load.apply(null,o.failure)):a(),C}function s(e){var t={};if("object"==typeof e)for(var n in e)e[n]&&(t={name:n,url:e[n]});else t={name:i(e),url:e};var r=O[t.name];return r&&r.url===t.url?r:(O[t.name]=t,t)}function d(e){e=e||O;for(var t in e)if(e.hasOwnProperty(t)&&e[t].state!==R)return!1;return!0}function f(e){e.state=x,r(e.onpreload,function(e){e.call()})}function m(e){e.state===t&&(e.state=_,e.onpreload=[],g({url:e.url,type:"cache"},function(){f(e)}))}function p(){var e=arguments,t=e[e.length-1],n=[].slice.call(e,1),a=n[0];return o(t)||(t=null),u(e[0])?(e[0].push(t),C.load.apply(null,e[0]),C):(a?(r(n,function(e){!o(e)&&e&&m(s(e))}),v(s(e[0]),o(a)?a:function(){C.load.apply(null,n)})):v(s(e[0])),C)}function y(){var e=arguments,t=e[e.length-1],n={};return o(t)||(t=null),u(e[0])?(e[0].push(t),C.load.apply(null,e[0]),C):(r(e,function(e){e!==t&&(e=s(e),n[e.name]=e)}),r(e,function(e){e!==t&&(e=s(e),v(e,function(){d(n)&&c(t)}))}),C)}function v(e,t){return t=t||n,e.state===R?void t():e.state===B?void C.ready(e.name,t):e.state===_?void e.onpreload.push(function(){v(e,t)}):(e.state=B,void g(e,function(){e.state=R,t(),r(M[e.name],function(e){c(e)}),S&&d()&&r(M.ALL,function(e){c(e)})}))}function h(e){e=e||"";var t=e.split("?")[0].split(".");return t[t.length-1].toLowerCase()}function g(t,r){function a(t){t=t||e.event,i.onload=i.onreadystatechange=i.onerror=null,r()}function o(n){n=n||e.event,("load"===n.type||/loaded|complete/.test(i.readyState)&&(!j.documentMode||j.documentMode<9))&&(e.clearTimeout(t.errorTimeout),e.clearTimeout(t.cssTimeout),i.onload=i.onreadystatechange=i.onerror=null,r())}function u(){if(t.state!==R&&t.cssRetries<=20){for(var n=0,r=j.styleSheets.length;r>n;n++)if(j.styleSheets[n].href===i.href)return void o({type:"load"});t.cssRetries++,t.cssTimeout=e.setTimeout(u,250)}}r=r||n;var i,c=h(t.url);"css"===c?(i=j.createElement("link"),i.type="text/"+(t.type||"css"),i.rel="stylesheet",i.href=t.url,t.cssRetries=0,t.cssTimeout=e.setTimeout(u,500)):(i=j.createElement("script"),i.type="text/"+(t.type||"javascript"),i.src=t.url),i.onload=i.onreadystatechange=o,i.onerror=a,i.async=!1,i.defer=!1,t.errorTimeout=e.setTimeout(function(){a({type:"timeout"})},7e3);var l=j.head||j.getElementsByTagName("head")[0];l.insertBefore(i,l.lastChild)}function T(){for(var e=j.getElementsByTagName("script"),t=0,n=e.length;n>t;t++){var r=e[t].getAttribute("data-headjs-load");if(r)return void C.load(r)}}function E(e,t){if(e===j)return S?c(t):A.push(t),C;if(o(e)&&(t=e,e="ALL"),u(e)){var n={};return r(e,function(e){n[e]=O[e],C.ready(e,function(){d(n)&&c(t)})}),C}if("string"!=typeof e||!o(t))return C;var a=O[e];if(a&&a.state===R||"ALL"===e&&d()&&S)return c(t),C;var i=M[e];return i?i.push(t):i=M[e]=[t],C}function L(){return j.body?void(S||(S=!0,T(),r(A,function(e){c(e)}))):(e.clearTimeout(C.readyTimeout),void(C.readyTimeout=e.setTimeout(L,50)))}function b(){j.addEventListener?(j.removeEventListener("DOMContentLoaded",b,!1),L()):"complete"===j.readyState&&(j.detachEvent("onreadystatechange",b),L())}var S,j=e.document,A=[],M={},O={},k="async"in j.createElement("script")||"MozAppearance"in j.documentElement.style||e.opera,w=e.head_conf&&e.head_conf.head||"head",C=e[w]=e[w]||function(){C.ready.apply(null,arguments)},_=1,x=2,B=3,R=4;if("complete"===j.readyState)L();else if(j.addEventListener)j.addEventListener("DOMContentLoaded",b,!1),e.addEventListener("load",L,!1);else{j.attachEvent("onreadystatechange",b),e.attachEvent("onload",L);var D=!1;try{D=!e.frameElement&&j.documentElement}catch(N){}D&&D.doScroll&&!function z(){if(!S){try{D.doScroll("left")}catch(t){return e.clearTimeout(C.readyTimeout),void(C.readyTimeout=e.setTimeout(z,50))}L()}}()}C.load=C.js=k?y:p,C.test=l,C.ready=E,C.ready(j,function(){d()&&r(M.ALL,function(e){c(e)}),C.feature&&C.feature("domloaded",!0)})}(window); \ No newline at end of file +///#source 1 1 /src/1.0.0/load.js +/*! head.load - v1.0.3 */ +/* + * HeadJS The only script in your + * Author Tero Piirainen (tipiirai) + * Maintainer Robert Hoffmann (itechnology) + * License MIT / http://bit.ly/mit-license + * WebSite http://headjs.com + */ +(function (win, undefined) { + "use strict"; + + //#region variables + var doc = win.document, + domWaiters = [], + handlers = {}, // user functions waiting for events + assets = {}, // loadable items in various states + isAsync = "async" in doc.createElement("script") || "MozAppearance" in doc.documentElement.style || win.opera, + isDomReady, + + /*** public API ***/ + headVar = win.head_conf && win.head_conf.head || "head", + api = win[headVar] = (win[headVar] || function () { api.ready.apply(null, arguments); }), + + // states + PRELOADING = 1, + PRELOADED = 2, + LOADING = 3, + LOADED = 4; + //#endregion + + //#region PRIVATE functions + + //#region Helper functions + function noop() { + // does nothing + } + + function each(arr, callback) { + if (!arr) { + return; + } + + // arguments special type + if (typeof arr === "object") { + arr = [].slice.call(arr); + } + + // do the job + for (var i = 0, l = arr.length; i < l; i++) { + callback.call(arr, arr[i], i); + } + } + + /* A must read: http://bonsaiden.github.com/JavaScript-Garden + ************************************************************/ + function is(type, obj) { + var clas = Object.prototype.toString.call(obj).slice(8, -1); + return obj !== undefined && obj !== null && clas === type; + } + + function isFunction(item) { + return is("Function", item); + } + + function isArray(item) { + return is("Array", item); + } + + function toLabel(url) { + ///Converts a url to a file label + var items = url.split("/"), + name = items[items.length - 1], + i = name.indexOf("?"); + + return i !== -1 ? name.substring(0, i) : name; + } + + // INFO: this look like a "im triggering callbacks all over the place, but only wanna run it one time function" ..should try to make everything work without it if possible + // INFO: Even better. Look into promises/defered's like jQuery is doing + function one(callback) { + ///Execute a callback only once + callback = callback || noop; + + if (callback._done) { + return; + } + + callback(); + callback._done = 1; + } + //#endregion + + function conditional(test, success, failure, callback) { + /// + /// INFO: use cases: + /// head.test(condition, null , "file.NOk" , callback); + /// head.test(condition, "fileOk.js", null , callback); + /// head.test(condition, "fileOk.js", "file.NOk" , callback); + /// head.test(condition, "fileOk.js", ["file.NOk", "file.NOk"], callback); + /// head.test({ + /// test : condition, + /// success : [{ label1: "file1Ok.js" }, { label2: "file2Ok.js" }], + /// failure : [{ label1: "file1NOk.js" }, { label2: "file2NOk.js" }], + /// callback: callback + /// ); + /// head.test({ + /// test : condition, + /// success : ["file1Ok.js" , "file2Ok.js"], + /// failure : ["file1NOk.js", "file2NOk.js"], + /// callback: callback + /// ); + /// + var obj = (typeof test === "object") ? test : { + test: test, + success: !!success ? isArray(success) ? success : [success] : false, + failure: !!failure ? isArray(failure) ? failure : [failure] : false, + callback: callback || noop + }; + + // Test Passed ? + var passed = !!obj.test; + + // Do we have a success case + if (passed && !!obj.success) { + obj.success.push(obj.callback); + api.load.apply(null, obj.success); + } + // Do we have a fail case + else if (!passed && !!obj.failure) { + obj.failure.push(obj.callback); + api.load.apply(null, obj.failure); + } + else { + callback(); + } + + return api; + } + + function getAsset(item) { + /// + /// Assets are in the form of + /// { + /// name : label, + /// url : url, + /// state: state + /// } + /// + var asset = {}; + + if (typeof item === "object") { + for (var label in item) { + if (!!item[label]) { + asset = { + name: label, + url : item[label] + }; + } + } + } + else { + asset = { + name: toLabel(item), + url : item + }; + } + + // is the item already existant + var existing = assets[asset.name]; + if (existing && existing.url === asset.url) { + return existing; + } + + assets[asset.name] = asset; + return asset; + } + + function allLoaded(items) { + items = items || assets; + + for (var name in items) { + if (items.hasOwnProperty(name) && items[name].state !== LOADED) { + return false; + } + } + + return true; + } + + function onPreload(asset) { + asset.state = PRELOADED; + + each(asset.onpreload, function (afterPreload) { + afterPreload.call(); + }); + } + + function preLoad(asset, callback) { + if (asset.state === undefined) { + + asset.state = PRELOADING; + asset.onpreload = []; + + loadAsset({ url: asset.url, type: "cache" }, function () { + onPreload(asset); + }); + } + } + + function apiLoadHack() { + /// preload with text/cache hack + /// + /// head.load("http://domain.com/file.js","http://domain.com/file.js", callBack) + /// head.load(["http://domain.com/file.js","http://domain.com/file.js"], callBack) + /// head.load({ label1: "http://domain.com/file.js" }, { label2: "http://domain.com/file.js" }, callBack) + /// head.load([{ label1: "http://domain.com/file.js" }, { label2: "http://domain.com/file.js" }], callBack) + /// + var args = arguments, + callback = args[args.length - 1], + rest = [].slice.call(args, 1), + next = rest[0]; + + if (!isFunction(callback)) { + callback = null; + } + + // if array, repush as args + if (isArray(args[0])) { + args[0].push(callback); + api.load.apply(null, args[0]); + + return api; + } + + // multiple arguments + if (!!next) { + /* Preload with text/cache hack (not good!) + * http://blog.getify.com/on-script-loaders/ + * http://www.nczonline.net/blog/2010/12/21/thoughts-on-script-loaders/ + * If caching is not configured correctly on the server, then items could load twice ! + *************************************************************************************/ + each(rest, function (item) { + // item is not a callback or empty string + if (!isFunction(item) && !!item) { + preLoad(getAsset(item)); + } + }); + + // execute + load(getAsset(args[0]), isFunction(next) ? next : function () { + api.load.apply(null, rest); + }); + } + else { + // single item + load(getAsset(args[0])); + } + + return api; + } + + function apiLoadAsync() { + /// + /// simply load and let browser take care of ordering + /// + /// head.load("http://domain.com/file.js","http://domain.com/file.js", callBack) + /// head.load(["http://domain.com/file.js","http://domain.com/file.js"], callBack) + /// head.load({ label1: "http://domain.com/file.js" }, { label2: "http://domain.com/file.js" }, callBack) + /// head.load([{ label1: "http://domain.com/file.js" }, { label2: "http://domain.com/file.js" }], callBack) + /// + var args = arguments, + callback = args[args.length - 1], + items = {}; + + if (!isFunction(callback)) { + callback = null; + } + + // if array, repush as args + if (isArray(args[0])) { + args[0].push(callback); + api.load.apply(null, args[0]); + + return api; + } + + // JRH 262#issuecomment-26288601 + // First populate the items array. + // When allLoaded is called, all items will be populated. + // Issue when lazy loaded, the callback can execute early. + each(args, function (item, i) { + if (item !== callback) { + item = getAsset(item); + items[item.name] = item; + } + }); + + each(args, function (item, i) { + if (item !== callback) { + item = getAsset(item); + + load(item, function () { + if (allLoaded(items)) { + one(callback); + } + }); + } + }); + + return api; + } + + function load(asset, callback) { + ///Used with normal loading logic + callback = callback || noop; + + if (asset.state === LOADED) { + callback(); + return; + } + + // INFO: why would we trigger a ready event when its not really loaded yet ? + if (asset.state === LOADING) { + api.ready(asset.name, callback); + return; + } + + if (asset.state === PRELOADING) { + asset.onpreload.push(function () { + load(asset, callback); + }); + return; + } + + asset.state = LOADING; + + loadAsset(asset, function () { + asset.state = LOADED; + + callback(); + + // handlers for this asset + each(handlers[asset.name], function (fn) { + one(fn); + }); + + // dom is ready & no assets are queued for loading + // INFO: shouldn't we be doing the same test above ? + if (isDomReady && allLoaded()) { + each(handlers.ALL, function (fn) { + one(fn); + }); + } + }); + } + + function getExtension(url) { + url = url || ""; + + var items = url.split("?")[0].split("."); + return items[items.length-1].toLowerCase(); + } + + /* Parts inspired from: https://github.com/cujojs/curl + ******************************************************/ + function loadAsset(asset, callback) { + callback = callback || noop; + + function error(event) { + event = event || win.event; + + // release event listeners + ele.onload = ele.onreadystatechange = ele.onerror = null; + + // do callback + callback(); + + // need some more detailed error handling here + } + + function process(event) { + event = event || win.event; + + // IE 7/8 (2 events on 1st load) + // 1) event.type = readystatechange, s.readyState = loading + // 2) event.type = readystatechange, s.readyState = loaded + + // IE 7/8 (1 event on reload) + // 1) event.type = readystatechange, s.readyState = complete + + // event.type === 'readystatechange' && /loaded|complete/.test(s.readyState) + + // IE 9 (3 events on 1st load) + // 1) event.type = readystatechange, s.readyState = loading + // 2) event.type = readystatechange, s.readyState = loaded + // 3) event.type = load , s.readyState = loaded + + // IE 9 (2 events on reload) + // 1) event.type = readystatechange, s.readyState = complete + // 2) event.type = load , s.readyState = complete + + // event.type === 'load' && /loaded|complete/.test(s.readyState) + // event.type === 'readystatechange' && /loaded|complete/.test(s.readyState) + + // IE 10 (3 events on 1st load) + // 1) event.type = readystatechange, s.readyState = loading + // 2) event.type = load , s.readyState = complete + // 3) event.type = readystatechange, s.readyState = loaded + + // IE 10 (3 events on reload) + // 1) event.type = readystatechange, s.readyState = loaded + // 2) event.type = load , s.readyState = complete + // 3) event.type = readystatechange, s.readyState = complete + + // event.type === 'load' && /loaded|complete/.test(s.readyState) + // event.type === 'readystatechange' && /complete/.test(s.readyState) + + // Other Browsers (1 event on 1st load) + // 1) event.type = load, s.readyState = undefined + + // Other Browsers (1 event on reload) + // 1) event.type = load, s.readyState = undefined + + // event.type == 'load' && s.readyState = undefined + + // !doc.documentMode is for IE6/7, IE8+ have documentMode + if (event.type === "load" || (/loaded|complete/.test(ele.readyState) && (!doc.documentMode || doc.documentMode < 9))) { + // remove timeouts + win.clearTimeout(asset.errorTimeout); + win.clearTimeout(asset.cssTimeout); + + // release event listeners + ele.onload = ele.onreadystatechange = ele.onerror = null; + + // do callback + callback(); + } + } + + function isCssLoaded() { + // should we test again ? 20 retries = 5secs ..after that, the callback will be triggered by the error handler at 7secs + if (asset.state !== LOADED && asset.cssRetries <= 20) { + + // loop through stylesheets + for (var i = 0, l = doc.styleSheets.length; i < l; i++) { + // do we have a match ? + // we need to tests agains ele.href and not asset.url, because a local file will be assigned the full http path on a link element + if (doc.styleSheets[i].href === ele.href) { + process({ "type": "load" }); + return; + } + } + + // increment & try again + asset.cssRetries++; + asset.cssTimeout = win.setTimeout(isCssLoaded, 250); + } + } + + var ele; + var ext = getExtension(asset.url); + + if (ext === "css") { + ele = doc.createElement("link"); + ele.type = "text/" + (asset.type || "css"); + ele.rel = "stylesheet"; + ele.href = asset.url; + + /* onload supported for CSS on unsupported browsers + * Safari windows 5.1.7, FF < 10 + */ + + // Set counter to zero + asset.cssRetries = 0; + asset.cssTimeout = win.setTimeout(isCssLoaded, 500); + } + else { + ele = doc.createElement("script"); + ele.type = "text/" + (asset.type || "javascript"); + ele.src = asset.url; + } + + ele.onload = ele.onreadystatechange = process; + ele.onerror = error; + + /* Good read, but doesn't give much hope ! + * http://blog.getify.com/on-script-loaders/ + * http://www.nczonline.net/blog/2010/12/21/thoughts-on-script-loaders/ + * https://hacks.mozilla.org/2009/06/defer/ + */ + + // ASYNC: load in parallel and execute as soon as possible + ele.async = false; + // DEFER: load in parallel but maintain execution order + ele.defer = false; + + // timout for asset loading + asset.errorTimeout = win.setTimeout(function () { + error({ type: "timeout" }); + }, 7e3); + + // use insertBefore to keep IE from throwing Operation Aborted (thx Bryan Forbes!) + var head = doc.head || doc.getElementsByTagName("head")[0]; + + // but insert at end of head, because otherwise if it is a stylesheet, it will not override values + head.insertBefore(ele, head.lastChild); + } + + /* Parts inspired from: https://github.com/jrburke/requirejs + ************************************************************/ + function init() { + var items = doc.getElementsByTagName("script"); + + // look for a script with a data-head-init attribute + for (var i = 0, l = items.length; i < l; i++) { + var dataMain = items[i].getAttribute("data-headjs-load"); + if (!!dataMain) { + api.load(dataMain); + return; + } + } + } + + function ready(key, callback) { + /// + /// INFO: use cases: + /// head.ready(callBack); + /// head.ready(document , callBack); + /// head.ready("file.js", callBack); + /// head.ready("label" , callBack); + /// head.ready(["label1", "label2"], callback); + /// + + // DOM ready check: head.ready(document, function() { }); + if (key === doc) { + if (isDomReady) { + one(callback); + } + else { + domWaiters.push(callback); + } + + return api; + } + + // shift arguments + if (isFunction(key)) { + callback = key; + key = "ALL"; // holds all callbacks that where added without labels: ready(callBack) + } + + // queue all items from key and return. The callback will be executed if all items from key are already loaded. + if (isArray(key)) { + var items = {}; + + each(key, function (item) { + items[item] = assets[item]; + + api.ready(item, function() { + if (allLoaded(items)) { + one(callback); + } + }); + }); + + return api; + } + + // make sure arguments are sane + if (typeof key !== "string" || !isFunction(callback)) { + return api; + } + + // this can also be called when we trigger events based on filenames & labels + var asset = assets[key]; + + // item already loaded --> execute and return + if (asset && asset.state === LOADED || key === "ALL" && allLoaded() && isDomReady) { + one(callback); + return api; + } + + var arr = handlers[key]; + if (!arr) { + arr = handlers[key] = [callback]; + } + else { + arr.push(callback); + } + + return api; + } + + /* Mix of stuff from jQuery & IEContentLoaded + * http://dev.w3.org/html5/spec/the-end.html#the-end + ***************************************************/ + function domReady() { + // Make sure body exists, at least, in case IE gets a little overzealous (jQuery ticket #5443). + if (!doc.body) { + // let's not get nasty by setting a timeout too small.. (loop mania guaranteed if assets are queued) + win.clearTimeout(api.readyTimeout); + api.readyTimeout = win.setTimeout(domReady, 50); + return; + } + + if (!isDomReady) { + isDomReady = true; + + init(); + each(domWaiters, function (fn) { + one(fn); + }); + } + } + + function domContentLoaded() { + // W3C + if (doc.addEventListener) { + doc.removeEventListener("DOMContentLoaded", domContentLoaded, false); + domReady(); + } + + // IE + else if (doc.readyState === "complete") { + // we're here because readyState === "complete" in oldIE + // which is good enough for us to call the dom ready! + doc.detachEvent("onreadystatechange", domContentLoaded); + domReady(); + } + } + + // Catch cases where ready() is called after the browser event has already occurred. + // we once tried to use readyState "interactive" here, but it caused issues like the one + // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 + if (doc.readyState === "complete") { + domReady(); + } + + // W3C + else if (doc.addEventListener) { + doc.addEventListener("DOMContentLoaded", domContentLoaded, false); + + // A fallback to window.onload, that will always work + win.addEventListener("load", domReady, false); + } + + // IE + else { + // Ensure firing before onload, maybe late but safe also for iframes + doc.attachEvent("onreadystatechange", domContentLoaded); + + // A fallback to window.onload, that will always work + win.attachEvent("onload", domReady); + + // If IE and not a frame + // continually check to see if the document is ready + var top = false; + + try { + top = !win.frameElement && doc.documentElement; + } catch (e) { } + + if (top && top.doScroll) { + (function doScrollCheck() { + if (!isDomReady) { + try { + // Use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + top.doScroll("left"); + } catch (error) { + // let's not get nasty by setting a timeout too small.. (loop mania guaranteed if assets are queued) + win.clearTimeout(api.readyTimeout); + api.readyTimeout = win.setTimeout(doScrollCheck, 50); + return; + } + + // and execute any waiting functions + domReady(); + } + }()); + } + } + //#endregion + + //#region Public Exports + // INFO: determine which method to use for loading + api.load = api.js = isAsync ? apiLoadAsync : apiLoadHack; + api.test = conditional; + api.ready = ready; + //#endregion + + //#region INIT + // perform this when DOM is ready + api.ready(doc, function () { + if (allLoaded()) { + each(handlers.ALL, function (callback) { + one(callback); + }); + } + + if (api.feature) { + api.feature("domloaded", true); + } + }); + //#endregion +}(window)); \ No newline at end of file diff --git a/components/oc-client/_package/src/oc-client.js b/components/oc-client/_package/src/oc-client.js index 5b6cd6200..e2899aa92 100644 --- a/components/oc-client/_package/src/oc-client.js +++ b/components/oc-client/_package/src/oc-client.js @@ -1 +1,336 @@ -"use strict";var oc=oc||{};!function(e,r,n){oc.conf=oc.conf||{},oc.cmd=oc.cmd||[];var o,t="https://cdnjs.cloudflare.com/ajax/libs/",a=t+"jquery-ajaxtransport-xdomainrequest/1.0.3/jquery.xdomainrequest.min.js",c=t+"handlebars.js/3.0.1/handlebars.runtime.js",i=t+"jade/1.9.2/runtime.min.js",d=t+"jquery/1.11.2/jquery.min.js",u=oc.conf.retryInterval||5e3,f=oc.conf.pollingInterval||500,l=oc.conf.tag||"oc-component",s="Href parameter missing",m="Error getting compiled view: {0}",p="Error rendering component: {0}, error: {1}",v="Failed to retrieve the component. Retrying in {0} seconds...".replace("{0}",u/1e3),h='Error loading component: view engine "{0}" not supported',y="Loading...",g="Component '{0}' correctly rendered",j="Unrendered component found. Trying to retrieve it...",q=oc.conf.debug||!1,b=function(){},x=n.navigator.userAgent,w=!!x.match(/MSIE 8/),C=!!x.match(/MSIE 9/),E=!1,I=!1,U={error:function(e){return console.log(e)},info:function(e){return q?console.log(e):!1}};oc.require=function(r,o,t){"function"==typeof o&&(t=o,o=r,r=void 0),"string"==typeof r&&(r=[r]);var a=function(){var e=n;if("undefined"==typeof r)return!0;for(var o=0;o"},oc.ready=function(e){if(E)return e();if(I)oc.cmd.push(e);else{I=!0;var r=function(e){!w&&!C||o.IE_POLYFILL_LOADED?e():oc.require(a,e)},n=function(){E=!0,I=!1,e();for(var r=0;r'+y+""),oc.renderByHref(e.attr("href"),function(n,o){return!n&&o&&o.html?void M(e,o,r):U.error(n)}))})},oc.renderByHref=function(e,r){oc.ready(function(){return""===e?r(p.replace("{1}",s)):void o.ajax({url:e,headers:{Accept:"application/vnd.oc.prerendered+json"},contentType:"text/plain",crossDomain:!0,async:!0,success:function(e){if("pre-rendered"===e.renderMode)oc.render(e.template,e.data,function(n,o){return n?r(p.replace("{0}",e.href).replace("{1}",n)):(U.info(g.replace("{0}",e.template.src)),void r(null,{html:o,key:e.template.key,version:e.version}))});else if("rendered"===e.renderMode){if(U.info(g.replace("{0}",e.href)),0===e.html.indexOf("<"+l)){var n=e.html.slice(e.html.indexOf(">")+1),o=n.slice(0,n.lastIndexOf("<"));e.html=o}r(null,{html:e.html,version:e.version})}},error:function(){U.error(v),setTimeout(function(){oc.renderByHref(e,r)},u)}})})},oc.renderUnloadedComponents=function(){oc.ready(function(){var e=w?"div[data-oc-component=true]":l,r=o(e+"[data-rendered!=true]"),n=function(e,r){oc.renderNestedComponent(o(e[r]),function(){r++,r0&&n(r,0)})},oc.load=function(e,r,n){oc.ready(function(){if("function"!=typeof n&&(n=b),o(e)){o(e).html("<"+l+' href="'+r+'" />');var t=o(l,e);oc.renderNestedComponent(t,function(){n(t)})}})},oc.ready(oc.renderUnloadedComponents)}(head,document,window); \ No newline at end of file +'use strict'; + +var oc = oc || {}; + +(function(head, $document, $window){ + + oc.conf = oc.conf || {}; + oc.cmd = oc.cmd || []; + + // Constants + var CDNJS_BASEURL = 'https://cdnjs.cloudflare.com/ajax/libs/', + IE89_AJAX_POLYFILL_URL = CDNJS_BASEURL + 'jquery-ajaxtransport-xdomainrequest/1.0.3/jquery.xdomainrequest.min.js', + HANDLEBARS_URL = CDNJS_BASEURL + 'handlebars.js/3.0.1/handlebars.runtime.js', + JADE_URL = CDNJS_BASEURL + 'jade/1.9.2/runtime.min.js', + JQUERY_URL = CDNJS_BASEURL + 'jquery/1.11.2/jquery.min.js', + RETRY_INTERVAL = oc.conf.retryInterval || 5000, + POLLING_INTERVAL = oc.conf.pollingInterval || 500, + OC_TAG = oc.conf.tag || 'oc-component', + MESSAGES_ERRORS_HREF_MISSING = 'Href parameter missing', + MESSAGES_ERRORS_LOADING_COMPILED_VIEW = 'Error getting compiled view: {0}', + MESSAGES_ERRORS_RENDERING = 'Error rendering component: {0}, error: {1}', + MESSAGES_ERRORS_RETRIEVING = 'Failed to retrieve the component. Retrying in {0} seconds...'.replace('{0}', RETRY_INTERVAL/1000), + MESSAGES_ERRORS_VIEW_ENGINE_NOT_SUPPORTED = 'Error loading component: view engine "{0}" not supported', + MESSAGES_LOADING_COMPONENT = 'Loading...', + MESSAGES_RENDERED = 'Component \'{0}\' correctly rendered', + MESSAGES_RETRIEVING = 'Unrendered component found. Trying to retrieve it...'; + + // The code + var debug = oc.conf.debug || false, + headScripts = [], + $, + noop = function(){}, + nav = $window.navigator.userAgent, + is8 = !!(nav.match(/MSIE 8/)), + is9 = !!(nav.match(/MSIE 9/)), + initialised = false, + initialising = false; + + var logger = { + error: function(msg){ + return console.log(msg); + }, + info: function(msg){ + return !!debug ? console.log(msg) : false; + } + }; + + // A minimal require.js-ish that uses head.js + oc.require = function(nameSpace, url, callback){ + if(typeof(url) === 'function'){ + callback = url; + url = nameSpace; + nameSpace = undefined; + } + + if(typeof(nameSpace) === 'string'){ + nameSpace = [nameSpace]; + } + + var needsToBeLoaded = function(){ + var base = $window; + + if(typeof(nameSpace) === 'undefined'){ + return true; + } + + for(var i = 0; i < nameSpace.length; i++){ + base = base[nameSpace[i]]; + if(!base){ + return true; + } + } + + return false; + }; + + var getObj = function(){ + var base = $window; + + if(typeof(nameSpace) === 'undefined'){ + return undefined; + } + + for(var i = 0; i < nameSpace.length; i++){ + base = base[nameSpace[i]]; + if(!base){ + return undefined; + } + } + + return base; + }; + + if(needsToBeLoaded()){ + head.load(url, function(){ + callback(getObj()); + }); + } else { + callback(getObj()); + } + }; + + + var processHtml = function($component, data, callback){ + + var newId = Math.floor(Math.random()*9999999999); + + $component.html(data.html); + $component.attr('id', newId); + $component.attr('data-rendered', true); + $component.attr('data-rendering', false); + $component.attr('data-version', data.version); + + if(!!data.key){ + $component.attr('data-hash', data.key); + } + + callback(); + }; + + oc.build = function(options){ + + if(!options.baseUrl){ + throw 'baseUrl parameter is required'; + } + + if(!options.name){ + throw 'name parameter is required'; + } + + var withFinalSlash = function(s){ + s = s || ''; + + if(s.slice(-1) !== '/'){ + s += '/'; + } + + return s; + }; + + var href = withFinalSlash(options.baseUrl) + withFinalSlash(options.name); + + if(!!options.version){ + href += withFinalSlash(options.version); + } + + if(!!options.parameters){ + href += '?'; + for(var parameter in options.parameters){ + if(options.parameters.hasOwnProperty(parameter)){ + href += parameter + '=' + options.parameters[parameter] + '&'; + } + } + href = href.slice(0, -1); + } + + return '<' + OC_TAG + ' href="' + href + '">'; + + }; + + oc.ready = function(callback){ + + if(initialised){ + return callback(); + } else if(initialising) { + oc.cmd.push(callback); + } else { + + initialising = true; + + var requirePolyfills = function(cb){ + if((is8 || is9) && !$.IE_POLYFILL_LOADED){ + oc.require(IE89_AJAX_POLYFILL_URL, cb); + } else { + cb(); + } + }; + + var done = function(){ + initialised = true; + initialising = false; + callback(); + for(var i = 0; i < oc.cmd.length; i++){ + oc.cmd[i](oc); + } + }; + + oc.require('jQuery', JQUERY_URL, function(jQuery){ + $ = jQuery; + requirePolyfills(done); + }); + } + }; + + oc.render = function(compiledViewInfo, model, callback){ + oc.ready(function(){ + if(!!compiledViewInfo.type.match(/jade|handlebars/g)){ + oc.require(['oc', 'components', compiledViewInfo.key], compiledViewInfo.src, function(compiledView){ + if(!compiledView){ + callback(MESSAGES_ERRORS_LOADING_COMPILED_VIEW.replace('{0}', compiledViewInfo.src)); + } else { + if(compiledViewInfo.type === 'handlebars'){ + oc.require('Handlebars', HANDLEBARS_URL, function(Handlebars){ + var linkedComponent = Handlebars.template(compiledView, []); + callback(null, linkedComponent(model)); + }); + } else if(compiledViewInfo.type === 'jade'){ + oc.require('jade', JADE_URL, function(jade){ + callback(null, compiledView(model)); + }); + } + } + }); + } else { + callback(MESSAGES_ERRORS_VIEW_ENGINE_NOT_SUPPORTED.replace('{0}', compiledViewInfo.type)); + } + }); + }; + + oc.renderNestedComponent = function($component, callback){ + oc.ready(function(){ + var dataRendering = $component.attr('data-rendering'), + dataRendered = $component.attr('data-rendered'), + isRendering = typeof(dataRendering) === 'boolean' ? dataRendering : (dataRendering === 'true'), + isRendered = typeof(dataRendered) === 'boolean' ? dataRendered : (dataRendered === 'true'); + + if(!isRendering && !isRendered){ + logger.info(MESSAGES_RETRIEVING); + $component.attr('data-rendering', true); + $component.html('
' + MESSAGES_LOADING_COMPONENT + '
'); + + oc.renderByHref($component.attr('href'), function(err, data){ + if(err || !data || !data.html){ + return logger.error(err); + } + + processHtml($component, data, callback); + }); + } else { + setTimeout(callback, POLLING_INTERVAL); + } + }); + }; + + oc.renderByHref = function(href, callback){ + oc.ready(function(){ + if(href !== ''){ + $.ajax({ + url: href, + headers: { 'Accept': 'application/vnd.oc.prerendered+json' }, + contentType: 'text/plain', + crossDomain: true, + async: true, + success: function(apiResponse){ + if(apiResponse.renderMode === 'pre-rendered'){ + oc.render(apiResponse.template, apiResponse.data, function(err, html){ + if(err){ + return callback(MESSAGES_ERRORS_RENDERING.replace('{0}', apiResponse.href).replace('{1}', err)); + } + logger.info(MESSAGES_RENDERED.replace('{0}', apiResponse.template.src)); + callback(null, { + html: html, + key: apiResponse.template.key, + version: apiResponse.version + }); + }); + } else if(apiResponse.renderMode === 'rendered'){ + logger.info(MESSAGES_RENDERED.replace('{0}', apiResponse.href)); + + if(apiResponse.html.indexOf('<' + OC_TAG) === 0){ + + var innerHtmlPlusEnding = apiResponse.html.slice(apiResponse.html.indexOf('>') + 1), + innerHtml = innerHtmlPlusEnding.slice(0, innerHtmlPlusEnding.lastIndexOf('<')); + apiResponse.html = innerHtml; + } + + callback(null, { + html: apiResponse.html, + version: apiResponse.version + }); + } + }, + error: function(){ + logger.error(MESSAGES_ERRORS_RETRIEVING); + setTimeout(function() { + oc.renderByHref(href, callback); + }, RETRY_INTERVAL); + } + }); + } else { + return callback(MESSAGES_ERRORS_RENDERING.replace('{1}', MESSAGES_ERRORS_HREF_MISSING)); + } + }); + }; + + oc.renderUnloadedComponents = function(){ + oc.ready(function(){ + var selector = (is8 ? 'div[data-oc-component=true]' : OC_TAG), + $unloadedComponents = $(selector + '[data-rendered!=true]'); + + var renderUnloadedComponent = function($unloadedComponents, i){ + oc.renderNestedComponent($($unloadedComponents[i]), function(){ + i++; + if(i < $unloadedComponents.length){ + renderUnloadedComponent($unloadedComponents, i); + } else { + oc.renderUnloadedComponents(); + } + }); + }; + + if($unloadedComponents.length > 0){ + renderUnloadedComponent($unloadedComponents, 0); + } + }); + }; + + oc.load = function(placeholder, href, callback){ + oc.ready(function(){ + if(typeof(callback) !== 'function'){ + callback = noop; + } + + if($(placeholder)){ + $(placeholder).html('<' + OC_TAG + ' href="' + href + '" />'); + var newComponent = $(OC_TAG, placeholder); + oc.renderNestedComponent(newComponent, function(){ + callback(newComponent); + }); + } + }); + }; + + oc.ready(oc.renderUnloadedComponents); + +})(head, document, window); // jshint ignore:line \ No newline at end of file diff --git a/registry/domain/repository.js b/registry/domain/repository.js index 1cd29d61b..c08a404d3 100644 --- a/registry/domain/repository.js +++ b/registry/domain/repository.js @@ -8,7 +8,7 @@ var path = require('path'); var S3 = require('./s3'); var settings = require('../../resources/settings'); var strings = require('../../resources'); -var validator = require('./validator'); +var validator = require('./validators'); var versionHandler = require('./version-handler'); var _ = require('underscore'); diff --git a/registry/domain/validator.js b/registry/domain/validator.js deleted file mode 100644 index 81bbb997b..000000000 --- a/registry/domain/validator.js +++ /dev/null @@ -1,197 +0,0 @@ -'use strict'; - -var format = require('stringformat'); -var semver = require('semver'); -var strings = require('../../resources'); -var _ = require('underscore'); - -var validate = { - booleanParameter: function(booleanParameter){ - return _.isBoolean(booleanParameter); - }, - - numberParameter: function(numberParameter){ - return !!numberParameter && _.isNumber(numberParameter); - }, - - parameter: function(parameter, expectedType){ - var expected = expectedType.toLowerCase(); - - if(_.contains(['string', 'boolean', 'number'], expected)){ - return validate[expected + 'Parameter'](parameter); - } - - return false; - }, - - stringParameter: function(stringParameter){ - return !!stringParameter && _.isString(stringParameter) && stringParameter !== ''; - } -}; - -module.exports = { - validateComponentName: function(componentName){ - return !/[^a-zA-Z0-9\-\_]/.test(componentName); - }, - validateTemplateType: function(templateType){ - return _.contains(['handlebars', 'jade'], templateType); - }, - validateComponentParameters: function(requestParameters, expectedParameters){ - - var result = { isValid: true, errors: {} }, - mandatoryParameters = []; - - _.forEach(expectedParameters, function(expectedParameter, expectedParameterName){ - if(expectedParameter.mandatory){ - mandatoryParameters.push(expectedParameterName); - } - }, this); - - _.forEach(mandatoryParameters, function(mandatoryParameterName){ - if(!_.has(requestParameters, mandatoryParameterName)){ - if(!result.errors.mandatory){ - result.errors.mandatory = {}; - result.isValid = false; - } - - result.errors.mandatory[mandatoryParameterName] = strings.errors.registry.MANDATORY_PARAMETER_MISSING_CODE; - } - }, this); - - _.forEach(requestParameters, function(requestParameter, requestParameterName){ - if(_.has(expectedParameters, requestParameterName)){ - - var expectedType = expectedParameters[requestParameterName].type; - - if(!validate.parameter(requestParameter, expectedType)){ - if(!result.errors.types){ - result.errors.types = {}; - result.isValid = false; - } - - result.errors.types[requestParameterName] = strings.errors.registry.PARAMETER_WRONG_FORMAT_CODE; - } - } - }, this); - - result.errors.message = (function(){ - var errorString = ''; - - if(_.keys(result.errors.mandatory).length > 0){ - - var missingParams = _.map(result.errors.mandatory, function(mandatoryParameter, mandatoryParameterName){ - return mandatoryParameterName + ', '; - }).join('').slice(0, -2); - - errorString += format(strings.errors.registry.MANDATORY_PARAMETER_MISSING, missingParams); - } - - if(_.keys(result.errors.types).length > 0){ - if(errorString.length > 0){ - errorString += '; '; - } - - var badParams = _.map(result.errors.types, function(parameter, parameterName){ - return parameterName + ', '; - }).join('').slice(0, -2); - - errorString += format(strings.errors.registry.PARAMETER_WRONG_FORMAT, badParams); - } - return errorString; - }()); - - return result; - }, - registryConfiguration: function(conf){ - - var response = { isValid: true }; - - var returnError = function(message){ - response.isValid = false; - response.message = message || 'registry configuration is not valid'; - return response; - }; - - if(!conf || !_.isObject(conf) || _.keys(conf).length === 0){ - return returnError(strings.errors.registry.CONFIGURATION_EMPTY); - } - - var prefix = conf.prefix; - - if(!!prefix){ - if(prefix.substr(0, 1) !== '/'){ - return returnError(strings.errors.registry.CONFIGURATION_PREFIX_DOES_NOT_START_WITH_SLASH); - } - - if(prefix.substr(prefix.length - 1) !== '/'){ - return returnError(strings.errors.registry.CONFIGURATION_PREFIX_DOES_NOT_END_WITH_SLASH); - } - } - - var publishAuth = conf.publishAuth; - - if(!!publishAuth){ - if(publishAuth.type !== 'basic'){ - return returnError(strings.errors.registry.CONFIGURATION_PUBLISH_AUTH_NOT_SUPPORTED); - } else { - if(!publishAuth.username || !publishAuth.password){ - return returnError(strings.errors.registry.CONFIGURATION_PUBLISH_AUTH_CREDENTIALS_MISSING); - } - } - } - - var dependencies = conf.dependencies; - - if(!!dependencies && (!_.isObject(dependencies)) || _.isArray(dependencies)){ - return returnError(strings.errors.registry.CONFIGURATION_DEPENDENCIES_MUST_BE_OBJECT); - } - - var routes = conf.routes; - - if(!!routes && !_.isArray(routes)){ - return returnError(strings.errors.registry.CONFIGURATION_ROUTES_MUST_BE_ARRAY); - } else { - _.forEach(routes, function(route){ - if(!route.route || !route.handler || !route.method){ - return returnError(strings.errors.registry.CONFIGURATION_ROUTES_NOT_VALID); - } - - if(!_.isFunction(route.handler)){ - return returnError(strings.errors.registry.CONFIGURATION_ROUTES_HANDLER_MUST_BE_FUNCTION); - } - }); - } - - return response; - }, - validatePackage: function(input){ - var response = { - isValid: true - }; - - var returnError = function(message){ - response.isValid = false; - response.message = message || 'uploaded package is not valid'; - return response; - }; - - if(!input || !_.isObject(input) || _.keys(input).length === 0){ - return returnError('empty'); - } - - if(_.keys(input).length !== 1){ - return returnError('not_valid'); - } - - var file = input[_.keys(input)[0]]; - - if(file.mimetype !== 'application/octet-stream' || !!file.truncated || file.extension !== 'gz' || file.path.indexOf('.tar.gz') < 0){ - return returnError('not_valid'); - } - - return response; - }, - validateVersion: function(version){ - return !!semver.valid(version); - } -}; diff --git a/registry/domain/validators/component-parameters.js b/registry/domain/validators/component-parameters.js new file mode 100644 index 000000000..fd76f3394 --- /dev/null +++ b/registry/domain/validators/component-parameters.js @@ -0,0 +1,96 @@ +'use strict'; + +var format = require('stringformat'); +var strings = require('../../../resources'); +var _ = require('underscore'); + +var validate = { + booleanParameter: function(booleanParameter){ + return _.isBoolean(booleanParameter); + }, + + numberParameter: function(numberParameter){ + return !!numberParameter && _.isNumber(numberParameter); + }, + + parameter: function(parameter, expectedType){ + var expected = expectedType.toLowerCase(); + + if(_.contains(['string', 'boolean', 'number'], expected)){ + return validate[expected + 'Parameter'](parameter); + } + + return false; + }, + + stringParameter: function(stringParameter){ + return !!stringParameter && _.isString(stringParameter) && stringParameter !== ''; + } +}; + +module.exports = function(requestParameters, expectedParameters){ + + var result = { isValid: true, errors: {} }, + mandatoryParameters = []; + + _.forEach(expectedParameters, function(expectedParameter, expectedParameterName){ + if(expectedParameter.mandatory){ + mandatoryParameters.push(expectedParameterName); + } + }, this); + + _.forEach(mandatoryParameters, function(mandatoryParameterName){ + if(!_.has(requestParameters, mandatoryParameterName)){ + if(!result.errors.mandatory){ + result.errors.mandatory = {}; + result.isValid = false; + } + + result.errors.mandatory[mandatoryParameterName] = strings.errors.registry.MANDATORY_PARAMETER_MISSING_CODE; + } + }, this); + + _.forEach(requestParameters, function(requestParameter, requestParameterName){ + if(_.has(expectedParameters, requestParameterName)){ + + var expectedType = expectedParameters[requestParameterName].type; + + if(!validate.parameter(requestParameter, expectedType)){ + if(!result.errors.types){ + result.errors.types = {}; + result.isValid = false; + } + + result.errors.types[requestParameterName] = strings.errors.registry.PARAMETER_WRONG_FORMAT_CODE; + } + } + }, this); + + result.errors.message = (function(){ + var errorString = ''; + + if(_.keys(result.errors.mandatory).length > 0){ + + var missingParams = _.map(result.errors.mandatory, function(mandatoryParameter, mandatoryParameterName){ + return mandatoryParameterName + ', '; + }).join('').slice(0, -2); + + errorString += format(strings.errors.registry.MANDATORY_PARAMETER_MISSING, missingParams); + } + + if(_.keys(result.errors.types).length > 0){ + if(errorString.length > 0){ + errorString += '; '; + } + + var badParams = _.map(result.errors.types, function(parameter, parameterName){ + return parameterName + ', '; + }).join('').slice(0, -2); + + errorString += format(strings.errors.registry.PARAMETER_WRONG_FORMAT, badParams); + } + return errorString; + }()); + + return result; +}; \ No newline at end of file diff --git a/registry/domain/validators/index.js b/registry/domain/validators/index.js new file mode 100644 index 000000000..2f5eba185 --- /dev/null +++ b/registry/domain/validators/index.js @@ -0,0 +1,23 @@ +'use strict'; + +var componentParametersValidator = require('./component-parameters'); +var registryConfigurationValidator = require('./registry-configuration'); +var uploadedPackageValidator = require('./uploaded-package'); + +var semver = require('semver'); +var _ = require('underscore'); + +module.exports = { + validateComponentName: function(componentName){ + return !/[^a-zA-Z0-9\-\_]/.test(componentName) && componentName !== '_package'; + }, + validateTemplateType: function(templateType){ + return _.contains(['handlebars', 'jade'], templateType); + }, + validateComponentParameters: componentParametersValidator, + registryConfiguration: registryConfigurationValidator, + validatePackage: uploadedPackageValidator, + validateVersion: function(version){ + return !!semver.valid(version); + } +}; diff --git a/registry/domain/validators/registry-configuration.js b/registry/domain/validators/registry-configuration.js new file mode 100644 index 000000000..2ed5cd02c --- /dev/null +++ b/registry/domain/validators/registry-configuration.js @@ -0,0 +1,67 @@ +'use strict'; + +var strings = require('../../../resources'); +var _ = require('underscore'); + +module.exports = function(conf){ + + var response = { isValid: true }; + + var returnError = function(message){ + response.isValid = false; + response.message = message || 'registry configuration is not valid'; + return response; + }; + + if(!conf || !_.isObject(conf) || _.keys(conf).length === 0){ + return returnError(strings.errors.registry.CONFIGURATION_EMPTY); + } + + var prefix = conf.prefix; + + if(!!prefix){ + if(prefix.substr(0, 1) !== '/'){ + return returnError(strings.errors.registry.CONFIGURATION_PREFIX_DOES_NOT_START_WITH_SLASH); + } + + if(prefix.substr(prefix.length - 1) !== '/'){ + return returnError(strings.errors.registry.CONFIGURATION_PREFIX_DOES_NOT_END_WITH_SLASH); + } + } + + var publishAuth = conf.publishAuth; + + if(!!publishAuth){ + if(publishAuth.type !== 'basic'){ + return returnError(strings.errors.registry.CONFIGURATION_PUBLISH_AUTH_NOT_SUPPORTED); + } else { + if(!publishAuth.username || !publishAuth.password){ + return returnError(strings.errors.registry.CONFIGURATION_PUBLISH_AUTH_CREDENTIALS_MISSING); + } + } + } + + var dependencies = conf.dependencies; + + if(!!dependencies && (!_.isObject(dependencies)) || _.isArray(dependencies)){ + return returnError(strings.errors.registry.CONFIGURATION_DEPENDENCIES_MUST_BE_OBJECT); + } + + var routes = conf.routes; + + if(!!routes && !_.isArray(routes)){ + return returnError(strings.errors.registry.CONFIGURATION_ROUTES_MUST_BE_ARRAY); + } else { + _.forEach(routes, function(route){ + if(!route.route || !route.handler || !route.method){ + return returnError(strings.errors.registry.CONFIGURATION_ROUTES_NOT_VALID); + } + + if(!_.isFunction(route.handler)){ + return returnError(strings.errors.registry.CONFIGURATION_ROUTES_HANDLER_MUST_BE_FUNCTION); + } + }); + } + + return response; +}; \ No newline at end of file diff --git a/registry/domain/validators/uploaded-package.js b/registry/domain/validators/uploaded-package.js new file mode 100644 index 000000000..684c56117 --- /dev/null +++ b/registry/domain/validators/uploaded-package.js @@ -0,0 +1,31 @@ +'use strict'; + +var _ = require('underscore'); + +module.exports = function(input){ + var response = { + isValid: true + }; + + var returnError = function(message){ + response.isValid = false; + response.message = message || 'uploaded package is not valid'; + return response; + }; + + if(!input || !_.isObject(input) || _.keys(input).length === 0){ + return returnError('empty'); + } + + if(_.keys(input).length !== 1){ + return returnError('not_valid'); + } + + var file = input[_.keys(input)[0]]; + + if(file.mimetype !== 'application/octet-stream' || !!file.truncated || file.extension !== 'gz' || file.path.indexOf('.tar.gz') < 0){ + return returnError('not_valid'); + } + + return response; +}; \ No newline at end of file diff --git a/registry/index.js b/registry/index.js index 629e1e8d9..85459b28d 100644 --- a/registry/index.js +++ b/registry/index.js @@ -14,7 +14,7 @@ var Repository = require('./domain/repository'); var requestHandler = require('./middleware/request-handler'); var Router = require('./router'); var settings = require('../resources/settings'); -var validator = require('./domain/validator'); +var validator = require('./domain/validators'); var _ = require('underscore'); module.exports = function(options){ diff --git a/registry/routes/component.js b/registry/routes/component.js index 2c434bddb..f65bd127d 100644 --- a/registry/routes/component.js +++ b/registry/routes/component.js @@ -7,7 +7,7 @@ var format = require('stringformat'); var RequireWrapper = require('../domain/require-wrapper'); var sanitiser = require('../domain/sanitiser'); var urlBuilder = require('../domain/url-builder'); -var validator = require('../domain/validator'); +var validator = require('../domain/validators'); var vm = require('vm'); var _ = require('underscore'); diff --git a/registry/routes/publish.js b/registry/routes/publish.js index 6f2809899..d75053e0a 100644 --- a/registry/routes/publish.js +++ b/registry/routes/publish.js @@ -4,7 +4,7 @@ var format = require('stringformat'); var path = require('path'); var RequireWrapper = require('../domain/require-wrapper'); var Targz = require('tar.gz'); -var validator = require('../domain/validator'); +var validator = require('../domain/validators'); var _ = require('underscore'); module.exports = function(repository){ diff --git a/test/mocks/console.js b/test/mocks/console.js index 3ba9837f7..2ce04fc8a 100644 --- a/test/mocks/console.js +++ b/test/mocks/console.js @@ -10,6 +10,10 @@ module.exports = { logs.push(message); return message; }, + logNoNewLine: function(message){ + logs.push(message); + return message; + }, reset: function(){ logs = []; } diff --git a/test/unit/cli-domain-local.js b/test/unit/cli-domain-local.js index a2a98168d..597b86828 100644 --- a/test/unit/cli-domain-local.js +++ b/test/unit/cli-domain-local.js @@ -2,16 +2,19 @@ var expect = require('chai').expect; var injectr = require('injectr'); +var path = require('path'); var sinon = require('sinon'); +var _ = require('underscore'); var initialise = function(){ var fsMock = { - readdirSync: sinon.spy(), + existsSync: sinon.stub(), + lstatSync: sinon.stub(), mkdirSync: sinon.spy(), - readJsonSync: sinon.stub(), + readdirSync: sinon.stub(), readFileSync: sinon.stub(), - existsSync: sinon.stub(), + readJsonSync: sinon.stub(), writeFileSync: sinon.spy(), writeJsonSync: sinon.spy() }; @@ -24,6 +27,13 @@ var initialise = function(){ code: code }; } + }, + path: { + extname: path.extname, + join: path.join, + resolve: function(){ + return _.toArray(arguments).join('/'); + } } }, { __dirname: '' }); @@ -36,11 +46,15 @@ var executePackaging = function(local, callback){ return local.package('.', callback); }; +var executeComponentsListingByDir = function(local, callback){ + return local.getComponentsByDir('.', callback); +}; + describe('cli : domain : local', function(){ describe('when packaging', function(){ - describe('when component is logic-less', function(){ + describe('when component is valid', function(){ var component; beforeEach(function(done){ @@ -71,6 +85,10 @@ describe('cli : domain : local', function(){ it('should add version to package.json file', function(){ expect(component.oc.version).to.eql('1.2.3'); }); + + it('should mark the package.json as a packaged', function(){ + expect(component.oc.packaged).to.eql(true); + }); }); describe('when component has a server.js logic', function(){ @@ -234,4 +252,43 @@ describe('cli : domain : local', function(){ }); }); }); + + describe('when getting components from dir', function(){ + + var error, result; + beforeEach(function(done){ + + var data = initialise(); + + data.fs.readdirSync.onCall(0).returns([ + 'a-component', + 'a-not-component-dir', + 'a-file.json', + '_package' + ]); + + data.fs.lstatSync.onCall(0).returns({ isDirectory: function(){ return true; }}); + data.fs.existsSync.onCall(0).returns(true); + data.fs.readJsonSync.onCall(0).returns({ oc: {}}); + + data.fs.lstatSync.onCall(1).returns({ isDirectory: function(){ return true; }}); + data.fs.existsSync.onCall(1).returns(false); + + data.fs.lstatSync.onCall(2).returns({ isDirectory: function(){ return false; }}); + + data.fs.lstatSync.onCall(3).returns({ isDirectory: function(){ return true; }}); + data.fs.existsSync.onCall(2).returns(true); + data.fs.readJsonSync.onCall(1).returns({ oc: { packaged: true }}); + + executeComponentsListingByDir(data.local, function(err, res){ + error = err; + result = res; + done(); + }); + }); + + it('should add version to package.json file', function(){ + expect(result).to.eql(['./a-component']); + }); + }); }); diff --git a/test/unit/registry-domain-validator.js b/test/unit/registry-domain-validator.js index ffa66bee5..7c0e183e2 100644 --- a/test/unit/registry-domain-validator.js +++ b/test/unit/registry-domain-validator.js @@ -4,7 +4,7 @@ var expect = require('chai').expect; describe('registry : domain : validator', function(){ - var validator = require('../../registry/domain/validator'); + var validator = require('../../registry/domain/validators'); describe('when validating registry configuration', function(){ @@ -486,6 +486,14 @@ describe('registry : domain : validator', function(){ }); }); + describe('when name is reserved', function(){ + + var name = '_package'; + it('should not be valid', function(){ + expect(validate(name)).to.be.false; + }); + }); + }); describe('when validating template type for new candidate', function(){