From 9741de3a1872684ee082657b69518b92eacce103 Mon Sep 17 00:00:00 2001 From: Isao Yagi Date: Tue, 9 Oct 2012 14:12:43 -0700 Subject: [PATCH 01/24] new: hybrid archetype (squashed) --- .../archetypes/app/hybrid/application.json.hb | 39 ++ .../archetypes/app/hybrid/assets/favicon.ico | Bin 0 -> 1406 bytes lib/app/archetypes/app/hybrid/index.js | 23 + .../hybrid/mojits/top_frame/assets/index.css | 2 + .../mojits/top_frame/binders/index.js.hb | 57 ++ .../mojits/top_frame/controller.server.js.hb | 46 ++ .../hybrid/mojits/top_frame/definition.json | 5 + .../mojits/top_frame/models/foo.server.js.hb | 37 ++ .../hybrid/mojits/top_frame/package.json.hb | 23 + .../mojits/top_frame/specs/default.json.hb | 22 + .../tests/controller.server-tests.js | 62 ++ .../tests/models/model.server-tests.js | 39 ++ .../mojits/top_frame/views/index.hb.html | 3 + lib/app/archetypes/app/hybrid/package.json.hb | 16 + lib/app/archetypes/app/hybrid/routes.json.hb | 23 + lib/app/archetypes/app/hybrid/server.js | 17 + lib/app/commands/build.js | 529 ------------------ lib/app/commands/build/index.js | 141 +++++ lib/app/commands/build/lib/html5app.js | 200 +++++++ lib/app/commands/build/lib/scraper.js | 54 ++ lib/app/commands/build/lib/writer.js | 24 + lib/app/commands/build/lib/yuiuse.js | 44 ++ package.json | 2 + 23 files changed, 879 insertions(+), 529 deletions(-) create mode 100644 lib/app/archetypes/app/hybrid/application.json.hb create mode 100644 lib/app/archetypes/app/hybrid/assets/favicon.ico create mode 100644 lib/app/archetypes/app/hybrid/index.js create mode 100644 lib/app/archetypes/app/hybrid/mojits/top_frame/assets/index.css create mode 100644 lib/app/archetypes/app/hybrid/mojits/top_frame/binders/index.js.hb create mode 100644 lib/app/archetypes/app/hybrid/mojits/top_frame/controller.server.js.hb create mode 100644 lib/app/archetypes/app/hybrid/mojits/top_frame/definition.json create mode 100644 lib/app/archetypes/app/hybrid/mojits/top_frame/models/foo.server.js.hb create mode 100644 lib/app/archetypes/app/hybrid/mojits/top_frame/package.json.hb create mode 100644 lib/app/archetypes/app/hybrid/mojits/top_frame/specs/default.json.hb create mode 100644 lib/app/archetypes/app/hybrid/mojits/top_frame/tests/controller.server-tests.js create mode 100644 lib/app/archetypes/app/hybrid/mojits/top_frame/tests/models/model.server-tests.js create mode 100644 lib/app/archetypes/app/hybrid/mojits/top_frame/views/index.hb.html create mode 100644 lib/app/archetypes/app/hybrid/package.json.hb create mode 100644 lib/app/archetypes/app/hybrid/routes.json.hb create mode 100644 lib/app/archetypes/app/hybrid/server.js delete mode 100644 lib/app/commands/build.js create mode 100644 lib/app/commands/build/index.js create mode 100644 lib/app/commands/build/lib/html5app.js create mode 100644 lib/app/commands/build/lib/scraper.js create mode 100644 lib/app/commands/build/lib/writer.js create mode 100644 lib/app/commands/build/lib/yuiuse.js diff --git a/lib/app/archetypes/app/hybrid/application.json.hb b/lib/app/archetypes/app/hybrid/application.json.hb new file mode 100644 index 000000000..0bd0c9159 --- /dev/null +++ b/lib/app/archetypes/app/hybrid/application.json.hb @@ -0,0 +1,39 @@ +[ + { + "settings": [ "master" ], + + "appPort": {{port}}, + + "specs": { + "frame": { + "base": "top_frame" + } + }, + + "staticHandling": { + "appName": "yahoo.application.{{name}}", + "prefix": "yahoo.application.{{name}}" + }, + + "builds": { + "html5app": { + "attachManifest": true, + "forceRelativePaths": true, + "urls": ["/yahoo.application.{{name}}/index.html"] + } + }, + + "yui": { + "dependencyCalculations": "precomputed" + } + }, + + { + "settings": [ "environment:dev" ], + "yui": { + "base": "/yahoo.libs.yui/", + "url": "$$yui.base$$yui/yui-min.js", + "loader": "loader/loader-min.js" + } + } +] diff --git a/lib/app/archetypes/app/hybrid/assets/favicon.ico b/lib/app/archetypes/app/hybrid/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c778f52d71655ec08dd493b4be5e61e4998c8580 GIT binary patch literal 1406 zcmeH{Yb@7M7{@;a_fR0@!df}4mSV>BwvdJwa zq^6PnMEH*plDk$ZjW*Gb{?w|My>!mL=lOn~v*)~cp6!5)uoV^QTlTV`A~(;KJzpF;oWZDpsJxB`(QszgZ&}G1j_=)F6gM?< z>PZQ2YN`mU>%il2CN8;^tah>{x3-p`oP1JVOYlp*Pia>>?y-0A5oe)nWsYie6b5Pe zG`w%bHS!jt!aWE`P2f&h4bKY2*xZi8Eb$p4NgE9yPx+WHeRdo0b9Lj`VbO;GQW))6*00pwl=qFDH(uVvVfiX4 ztF6_xSu$n&f^AaPS3Njk=yctcE2KQhbpK9G9V5e|eJranckd$M)3cJ^f@tD^{mb<9 YK&OIKazgw~p9KFeJ&lAfLS1j|5Bi(<)c^nh literal 0 HcmV?d00001 diff --git a/lib/app/archetypes/app/hybrid/index.js b/lib/app/archetypes/app/hybrid/index.js new file mode 100644 index 000000000..52a44ce10 --- /dev/null +++ b/lib/app/archetypes/app/hybrid/index.js @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2011-2012, Yahoo! Inc. All rights reserved. + * Copyrights licensed under the New BSD License. + * See the accompanying LICENSE file for terms. + */ + + +/*jslint anon:true, sloppy:true, nomen:true, node:true*/ + + +process.chdir(__dirname); + + +/** + * @param {object} config The configuration object containing processing params. + * @param {object} token Token used to identify the application. + */ +module.exports = function(config, token) { + var app = require('./server.js'); + + // Signal the application is ready, providing the token and app references. + process.emit('application-ready', token, app); +}; diff --git a/lib/app/archetypes/app/hybrid/mojits/top_frame/assets/index.css b/lib/app/archetypes/app/hybrid/mojits/top_frame/assets/index.css new file mode 100644 index 000000000..1cd511473 --- /dev/null +++ b/lib/app/archetypes/app/hybrid/mojits/top_frame/assets/index.css @@ -0,0 +1,2 @@ +dt { font-weight: bold; } +.sel { background-color: #FF4; } diff --git a/lib/app/archetypes/app/hybrid/mojits/top_frame/binders/index.js.hb b/lib/app/archetypes/app/hybrid/mojits/top_frame/binders/index.js.hb new file mode 100644 index 000000000..d7797f39d --- /dev/null +++ b/lib/app/archetypes/app/hybrid/mojits/top_frame/binders/index.js.hb @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2012 Yahoo! Inc. All rights reserved. + */ +/*jslint anon:true, sloppy:true, nomen:true*/ +YUI.add('top_frameBinderIndex', function(Y, NAME) { + +/** + * The top_frameBinderIndex module. + * + * @module top_frameBinderIndex + */ + + /** + * Constructor for the top_frameBinderIndex class. + * + * @class top_frameBinderIndex + * @constructor + */ + Y.namespace('mojito.binders')[NAME] = { + + /** + * Binder initialization method, invoked after all binders on the page + * have been constructed. + */ + init: function(mojitProxy) { + this.mojitProxy = mojitProxy; + }, + + /** + * The binder method, invoked to allow the mojit to attach DOM event + * handlers. + * + * @param node {Node} The DOM node to which this mojit is attached. + */ + bind: function(node) { + var me = this; + this.node = node; + /** + * Example code for the bind method: + * + * node.all('dt').on('mouseenter', function(evt) { + * var dd = '#dd_' + evt.target.get('text'); + * me.node.one(dd).addClass('sel'); + * + * }); + * node.all('dt').on('mouseleave', function(evt) { + * + * var dd = '#dd_' + evt.target.get('text'); + * me.node.one(dd).removeClass('sel'); + * + * }); + */ + } + + }; + +}, '0.0.1', {requires: ['event-mouseenter', 'mojito-client']}); diff --git a/lib/app/archetypes/app/hybrid/mojits/top_frame/controller.server.js.hb b/lib/app/archetypes/app/hybrid/mojits/top_frame/controller.server.js.hb new file mode 100644 index 000000000..2f3330e50 --- /dev/null +++ b/lib/app/archetypes/app/hybrid/mojits/top_frame/controller.server.js.hb @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2012 Yahoo! Inc. All rights reserved. + */ +/*jslint anon:true, sloppy:true, nomen:true*/ +YUI.add('top_frame', function(Y, NAME) { + +/** + * The top_frame module. + * + * @module top_frame + */ + + /** + * Constructor for the Controller class. + * + * @class Controller + * @constructor + */ + Y.namespace('mojito.controllers')[NAME] = { + + init: function(config) { + this.config = config; + }, + + /** + * Method corresponding to the 'index' action. + * + * @param ac {Object} The ActionContext that provides access + * to the Mojito API. + */ + index: function(ac) { + ac.models.top_frameModelFoo.getData(function(err, data) { + if (err) { + return ac.error(err); + } + + ac.done({ + status: 'Mojito is working.', + data: data + }); + }); + } + + }; + +}, '0.0.1', {requires: ['mojito', 'top_frameModelFoo']}); diff --git a/lib/app/archetypes/app/hybrid/mojits/top_frame/definition.json b/lib/app/archetypes/app/hybrid/mojits/top_frame/definition.json new file mode 100644 index 000000000..470db8060 --- /dev/null +++ b/lib/app/archetypes/app/hybrid/mojits/top_frame/definition.json @@ -0,0 +1,5 @@ +[ + { + "settings": [ "master" ] + } +] diff --git a/lib/app/archetypes/app/hybrid/mojits/top_frame/models/foo.server.js.hb b/lib/app/archetypes/app/hybrid/mojits/top_frame/models/foo.server.js.hb new file mode 100644 index 000000000..96a4b9f81 --- /dev/null +++ b/lib/app/archetypes/app/hybrid/mojits/top_frame/models/foo.server.js.hb @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2012 Yahoo! Inc. All rights reserved. + */ +/*jslint anon:true, sloppy:true, nomen:true*/ +YUI.add('top_frameModelFoo', function(Y, NAME) { + +/** + * The top_frameModelFoo module. + * + * @module top_frame + */ + + /** + * Constructor for the top_frameModelFoo class. + * + * @class top_frameModelFoo + * @constructor + */ + Y.namespace('mojito.models')[NAME] = { + + init: function(config) { + this.config = config; + }, + + /** + * Method that will be invoked by the mojit controller to obtain data. + * + * @param callback {function(err,data)} The callback function to call when the + * data has been retrieved. + */ + getData: function(callback) { + callback(null, { some: 'data' }); + } + + }; + +}, '0.0.1', {requires: []}); diff --git a/lib/app/archetypes/app/hybrid/mojits/top_frame/package.json.hb b/lib/app/archetypes/app/hybrid/mojits/top_frame/package.json.hb new file mode 100644 index 000000000..bd3378b0d --- /dev/null +++ b/lib/app/archetypes/app/hybrid/mojits/top_frame/package.json.hb @@ -0,0 +1,23 @@ +{ + "name": "top_frame", + "version": "0.0.1", + "pkg_type": "frame", + "description": "The {{name}} application's top frame (HTMLFrameMojit)", + "engines": { + "node": ">0.6", + "npm": ">1.0", + "mojito": ">0.4" + }, + "maintainers": [ + { + "email": "yourname@yahoo-inc.com", + "name": "Your Name" + } + ], + "yahoo": { + "mojito": { + "package": "public", + "version": ">0.4" + } + } +} diff --git a/lib/app/archetypes/app/hybrid/mojits/top_frame/specs/default.json.hb b/lib/app/archetypes/app/hybrid/mojits/top_frame/specs/default.json.hb new file mode 100644 index 000000000..0488c5073 --- /dev/null +++ b/lib/app/archetypes/app/hybrid/mojits/top_frame/specs/default.json.hb @@ -0,0 +1,22 @@ +[ + { + "settings": [ "master" ], + + "type": "HTMLFrameMojit", + "config": { + "deploy": true, + "title": "{{name}} App", + "child": { + "type": "top_frame" + }, + "assets": { + "top": { + "css": ["./index.css"] + }, + "bottom": { + "js": ["/yahoo.ychrome.lib/yui-cfg.js"] + } + } + } + } +] diff --git a/lib/app/archetypes/app/hybrid/mojits/top_frame/tests/controller.server-tests.js b/lib/app/archetypes/app/hybrid/mojits/top_frame/tests/controller.server-tests.js new file mode 100644 index 000000000..d630b3fbb --- /dev/null +++ b/lib/app/archetypes/app/hybrid/mojits/top_frame/tests/controller.server-tests.js @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2012 Yahoo! Inc. All rights reserved. + */ + +YUI.add('top_frame-tests', function(Y) { + + var suite = new YUITest.TestSuite('top_frame-tests'), + controller = null, + A = YUITest.Assert; + + suite.add(new YUITest.TestCase({ + + name: 'top_frame user tests', + + setUp: function() { + controller = Y.mojito.controllers.top_frame; + }, + tearDown: function() { + controller = null; + }, + + 'test mojit': function() { + var ac, + modelData, + assetsResults, + doneResults; + modelData = { x:'y' }; + ac = { + assets: { + addCss: function(css) { + assetsResults = css; + } + }, + models: { + top_frameModelFoo: { + getData: function(cb) { + cb(null, modelData); + } + } + }, + done: function(data) { + doneResults = data; + } + }; + + A.isNotNull(controller); + A.isFunction(controller.index); + controller.index(ac); + A.areSame('./index.css', assetsResults); + A.isObject(doneResults); + A.areSame('Mojito is working.', doneResults.status); + A.isObject(doneResults.data); + A.isTrue(doneResults.data.hasOwnProperty('x')); + A.areEqual('y', doneResults.data['x']); + + } + + })); + + YUITest.TestRunner.add(suite); + +}, '0.0.1', {requires: ['mojito-test', 'top_frame']}); diff --git a/lib/app/archetypes/app/hybrid/mojits/top_frame/tests/models/model.server-tests.js b/lib/app/archetypes/app/hybrid/mojits/top_frame/tests/models/model.server-tests.js new file mode 100644 index 000000000..deb54e00e --- /dev/null +++ b/lib/app/archetypes/app/hybrid/mojits/top_frame/tests/models/model.server-tests.js @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2012 Yahoo! Inc. All rights reserved. + */ + +YUI.add('top_frameModelFoo-tests', function(Y, NAME) { + + var suite = new YUITest.TestSuite(NAME), + model = null, + A = YUITest.Assert; + + suite.add(new YUITest.TestCase({ + + name: 'top_frameModelFoo user tests', + + setUp: function() { + model = Y.mojito.models.top_frameModelFoo; + }, + tearDown: function() { + model = null; + }, + + 'test mojit model': function() { + var called = false; + A.isNotNull(model); + A.isFunction(model.getData); + model.getData(function(err, data) { + called = true; + A.isTrue(!err); + A.isObject(data); + A.areSame('data', data.some); + }); + A.isTrue(called); + } + + })); + + YUITest.TestRunner.add(suite); + +}, '0.0.1', {requires: ['mojito-test', 'top_frameModelFoo']}); diff --git a/lib/app/archetypes/app/hybrid/mojits/top_frame/views/index.hb.html b/lib/app/archetypes/app/hybrid/mojits/top_frame/views/index.hb.html new file mode 100644 index 000000000..66aae63a2 --- /dev/null +++ b/lib/app/archetypes/app/hybrid/mojits/top_frame/views/index.hb.html @@ -0,0 +1,3 @@ +
+

Hello Template

+
\ No newline at end of file diff --git a/lib/app/archetypes/app/hybrid/package.json.hb b/lib/app/archetypes/app/hybrid/package.json.hb new file mode 100644 index 000000000..e9000c52e --- /dev/null +++ b/lib/app/archetypes/app/hybrid/package.json.hb @@ -0,0 +1,16 @@ +{ + "name": "yahoo.application.{{name}}", + "author": "Your Name ", + "description": "This application's main package: yahoo.application.{{name}}", + "version": "0.0.1", + "type": "application", + "pkg_type": "application", + "dependencies": { + "mojito": ">0.4" + }, + "engines": { + "node": ">0.6", + "npm": ">1.0", + "mojito": ">0.4" + } +} diff --git a/lib/app/archetypes/app/hybrid/routes.json.hb b/lib/app/archetypes/app/hybrid/routes.json.hb new file mode 100644 index 000000000..e7987beb4 --- /dev/null +++ b/lib/app/archetypes/app/hybrid/routes.json.hb @@ -0,0 +1,23 @@ +[ + { + "settings": [ "master" ], + + "root": { + "verbs": ["get"], + "path": "/", + "call": "frame.index" + }, + + "yahoo.application.{{name}}": { + "verbs": ["get"], + "path": "/yahoo.application.{{name}}/index.html", + "call": "frame.index" + }, + + "any mojit/action": { + "verbs": [ "GET" ], + "path": "/:module/:action", + "call": "{module}.{action}" + } + } +] diff --git a/lib/app/archetypes/app/hybrid/server.js b/lib/app/archetypes/app/hybrid/server.js new file mode 100644 index 000000000..28c2cbcfd --- /dev/null +++ b/lib/app/archetypes/app/hybrid/server.js @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2011-2012, Yahoo! Inc. All rights reserved. + * Copyrights licensed under the New BSD License. + * See the accompanying LICENSE file for terms. + */ + + +/*jslint anon:true, sloppy:true*/ + + +/** + * Create and start a new Mojito server/application. + */ +var Mojito = require('mojito'); +var app = Mojito.createServer(); + +module.exports = app.start(); diff --git a/lib/app/commands/build.js b/lib/app/commands/build.js deleted file mode 100644 index df707e806..000000000 --- a/lib/app/commands/build.js +++ /dev/null @@ -1,529 +0,0 @@ -/* - * Copyright (c) 2011-2012, Yahoo! Inc. All rights reserved. - * Copyrights licensed under the New BSD License. - * See the accompanying LICENSE file for terms. - */ - - -/*jslint anon:true, sloppy:true, nomen:true, regexp:true, stupid:true, - continue: true*/ - - -var libpath = require('path'), - utils = require(libpath.join(__dirname, '../../management/utils')), - Mojito = require(libpath.join(__dirname, '../../mojito')), - fs = require('fs'), - libqs = require('querystring'), - MODE_755 = parseInt('755', 8), - getSpecURL, - mkdirP, - rmdirR, - writeWebPagesToFiles, - YUI = require('yui').YUI, - Y = YUI({useSync: true}).use('json-parse', 'json-stringify', 'escape'); - -Y.applyConfig({useSync: false}); - -/** - * The usage string for this command. - */ -exports.usage = 'mojito build {type} [destination]\n' + - "\t- type: 'html5app'\n" + - '\t- destination: (optional) the directory where the build output goes.\n' + - "\t By default this is the type i.e. './artifacts/builds/'\n" + - '\nOPTIONS: \n' + - '\t --replace: Tells the build system to delete the destination' + - ' directory and replace it.\n' + - '\t -r: Short for --replace\n' + - '\t --context: Tells the build system what context to build with' + - ' i.e. device=iphone&lang=en-GB.\n' + - '\t -c: Short for --context\n'; - -/** - * The options list for this command. - */ -exports.options = [ - { - shortName: 'r', - longName: 'replace', - hasValue: false - }, - { - shortName: 'm', - longName: 'mojit', - hasValue: true - }, - { - shortName: 'c', - longName: 'context', - hasValue: true - } -]; - - -/** - * The run() method which handles processing for this command. - * @param {Array} params Optional parameters to the command. - * @param {Object} options Optional options for the command. - * @param {Function} callback Function to invoke on commmand completion. - */ -exports.run = function(params, options, callback) { - var store, - type = 'html5app', - cwd = process.cwd(), - destination, - appConfig, - config = {}, - context = {}; - - Y.applyConfig({ - useSync: true, - modules: { - 'mojito-util': { - fullpath: libpath.join(__dirname, '../../app/autoload/util.common.js') - }, - 'mojito-resource-store': { - fullpath: libpath.join(__dirname, '../../store.server.js') - } - } - }); - - if (typeof options.context === 'string') { - // Parse the context into an object - context = utils.contextCsvToObject(options.context); - } - - Y.use('mojito-util', 'mojito-resource-store'); - Y.applyConfig({useSync: false}); - store = new Y.mojito.ResourceStore({ - root: cwd, - context: context - }); - - if (!params[0]) { - params[0] = ''; - } - - switch (params[0].toUpperCase()) { - case 'HTML5APP': - type = 'html5app'; - break; - default: - utils.error("Can only build type '" + type + "'", exports.usage); - return; - } - - // Set the destination for the generated files - if (params[1] && params[1][0] === '/') { - destination = libpath.join(params[1]); - } else if (params[1]) { - destination = libpath.join(cwd, params[1]); - } else { - destination = libpath.join(cwd, 'artifacts/builds', type); - } - - // Are we in a Mojito App? - utils.isMojitoApp(cwd, exports.usage, true); - - appConfig = store.getStaticAppConfig(); - // Is there a "builds" section for this "type" in the appConfig - if (appConfig.builds && appConfig.builds[type]) { - config = appConfig.builds[type]; - } - - if (options.replace) { - try { - rmdirR(destination); - console.log('Removing all files in folder "' + destination + '"'); - } catch (err2) { // err2 to get past JSLint - // Dirty way to check dir - } - } - - exports['build' + type](options, store, config, destination, callback); -}; - - -/** - * The build command itself. - * @param {Object} cmdOptions Options for the command. - * @param {Object} store The relevant resource store reference. - * @param {Object} config Configuration data for the context. - * @param {string} destination A path to target with build output. - * @param {Function} callback A function to invoke on completion. - */ -exports.buildhtml5app = function(cmdOptions, store, config, destination, - callback) { - - var type = 'HTML5 Application', - i, - from, - to, - // files to fetch through the server - serverFiles = { css: true, js: true, json: true }, - extension, - manifest = 'CACHE MANIFEST\n', - indexJs = "module.exports = require('express')." + - "createServer(require('express')['static'](__dirname));", - url, - storeURLs, - urls = {}, // from: to - app, - context = '', - contextObj = libqs.parse(cmdOptions.context), - appConfig, - tunnelPrefix, - dynamicURLs = {}, - mr, - mojitRes, - mojitRess, - sr, - specRes, - specRess, - id; - - if (typeof cmdOptions.context === 'string') { - // Parse the context into an object - config.context = utils.contextCsvToObject(cmdOptions.context); - // Stringify the context object into query string - context = '?' + libqs.stringify(config.context); - } - - urls['/' + context] = '/index.html'; - - if (config.urls && config.urls.length) { - for (i = 0; i < config.urls.length; i += 1) { - urls[config.urls[i] + context] = config.urls[i]; - } - } - - store.preload(); - - appConfig = store.getAppConfig(contextObj); - tunnelPrefix = appConfig.tunnelPrefix || '/tunnel'; - - console.log('Building a "' + type + '" of the Mojito application at "' + - store._config.root + '"'); - - console.log('...'); - - // Set the cachable files in the manifest - manifest += 'CACHE:\n'; - - // Copy all the files into the destination directory - storeURLs = store.getAllURLs(); - for (url in storeURLs) { - if (storeURLs.hasOwnProperty(url)) { - from = storeURLs[url]; // filesystem path - to = libpath.join(destination, url); - extension = from.split('.').pop(); - - mkdirP(libpath.dirname(to), MODE_755); - - manifest += url + '\n'; - - if (serverFiles[extension]) { - urls[url + context] = url; - mkdirP(libpath.dirname(libpath.join(destination, url)), - MODE_755); - } else { - fs.writeFileSync(to, fs.readFileSync(from), 'utf8'); - } - } - } - - mojitRess = store.getResources('client', contextObj, {type: 'mojit'}); - for (mr = 0; mr < mojitRess.length; mr += 1) { - mojitRes = mojitRess[mr]; - if (!mojitRes.url) { - continue; - } - url = mojitRes.url + '/definition.json'; - dynamicURLs[url] = true; - - specRess = store.getResources('client', contextObj, {type: 'spec', mojit: mojitRes.name}); - for (sr = 0; sr < specRess.length; sr += 1) { - specRes = specRess[sr]; - dynamicURLs[specRes.url] = true; - } - } - - for (id in appConfig.specs) { - if (appConfig.specs.hasOwnProperty(id)) { - url = getSpecURL(appConfig, id); - dynamicURLs[url] = true; - } - } - - // Get all the dynamic URLs we have to call via the "tunnel" - for (url in dynamicURLs) { - if (dynamicURLs.hasOwnProperty(url)) { - urls[tunnelPrefix + url + context] = url; - mkdirP(libpath.dirname(libpath.join(destination, url)), MODE_755); - } - } - - for (url in urls) { - if (urls.hasOwnProperty(url)) { - manifest += (urls[url] === '/' ? '/index.html' : urls[url]) + '\n'; - } - } - - // Write the "cache.manifest" file - fs.writeFileSync(libpath.join(destination, 'cache.manifest'), manifest, - 'utf8'); - - // Write a "quick test" index.js file - fs.writeFileSync(libpath.join(destination, 'index.js'), indexJs, 'utf8'); - - // Now use the server to generate some of the files - writeWebPagesToFiles(destination, urls, config, callback); -}; - - -getSpecURL = function(appConfig, id) { - var prefix = '/static', - parts = id.split(':'), - typeName = parts[0], - specName = parts[1] || 'default', - ns = typeName.replace(/\./g, '_'), - url; - - if (appConfig && appConfig.staticHandling && - appConfig.staticHandling.hasOwnProperty('prefix')) { - prefix = (appConfig.staticHandling.prefix ? '/' + - appConfig.staticHandling.prefix : ''); - } - url = prefix + '/' + typeName + '/specs/' + specName + '.json'; - return url; -}; - - -mkdirP = function(p, mode) { - var ps = libpath.normalize(p).split('/'), - i; - - for (i = 0; i <= ps.length; i += 1) { - try { - fs.mkdirSync(ps.slice(0, i).join('/'), mode); - } catch (err) { - // Dirty way to check dir - } - } -}; - - -rmdirR = function(path) { - var files = fs.readdirSync(path), - currDir = path, - i, - currFile; - - /* Loop through and delete everything in the sub-tree after checking it */ - for (i = 0; i < files.length; i += 1) { - currFile = fs.statSync(currDir + '/' + files[i]); - - if (currFile.isDirectory()) { - // Recursive function back to the beginning - rmdirR(currDir + '/' + files[i]); - } else if (currFile.isSymbolicLink()) { - // Unlink symlinks - fs.unlinkSync(currDir + '/' + files[i]); - } else { - // Assume it's a file - perhaps a try/catch belongs here? - fs.unlinkSync(currDir + '/' + files[i]); - } - } - - /* - * Now that we know everything in the sub-tree has been deleted, - * we can delete the main directory. - */ - return fs.rmdirSync(path); -}; - - -/** - * Calculates the filesystem path to get from source to destination. - * - * @private - * @param {string} dst The destination directory. - * @param {string} src The source directory. - * @return {string} The path from source to destination. - */ -function pathTo(dst, src) { - var dstParts, - srcParts, - i, - j, - path = []; - - // trimming leading and trailing slashes removes nasty corner cases from the - // algorithm - if ('/' === dst.charAt(0)) { - dst = dst.substring(1); - } - if ('/' === dst.charAt(dst.length - 1)) { - dst = dst.substring(0, dst.length - 1); - } - if ('/' === src.charAt(0)) { - src = src.substring(1); - } - if ('/' === src.charAt(src.length - 1)) { - src = src.substring(0, src.length - 1); - } - - dstParts = dst ? dst.split('/') : []; - srcParts = src ? src.split('/') : []; - path = []; - for (i = 0; i < dstParts.length && i < srcParts.length; i += 1) { - if (dstParts[i] !== srcParts[i]) { - break; - } - } - for (j = i; j < srcParts.length; j += 1) { - path.push('..'); - } - for (j = i; j < dstParts.length; j += 1) { - path.push(dstParts[j]); - } - return libpath.join.apply(libpath, path) || '.'; -} - - -/** - * Adds the HTML5 cache manifest to the - * - * @private - * @param {string} root The directory where html5app is being built into. - * @param {string} relativePath The file path, relative to root. - * @param {string} content The body of the file. - * @param {Boolean} force Whether to fixup the content, even though the path - * might not end in .html. - * @return {string} The fixed up content. - */ -function attachManifest(root, relativePath, content, force) { - var extname = libpath.extname(relativePath), - dirname, - pathToRoot; - - if (force || '.html' === extname) { - dirname = libpath.dirname(relativePath); - pathToRoot = pathTo('/', dirname); - - content = content.replace(//, ''); - } - - return content; -} - - -/** - * Changes server-relative paths to file-relative paths. - * - * @private - * @param {string} root The directory where html5app is being built into. - * @param {string} relativePath The file path, relative to root. - * @param {string} content The body of the file. - * @param {Boolean} force Whether to fixup the content, even though the path - * might not end in .html. - * @return {string} The fixed up content. - */ -function forceRelativePaths(root, relativePath, content, force) { - var extname = libpath.extname(relativePath), - dirname; - - if (force || '.html' === extname) { - dirname = libpath.dirname(relativePath); - - content = content.replace(/(src|href)="([^"]+)"/g, - function(all, name, val) { - // FUTURE: once the "/" aren't escaped, we can do this easier - var fixed = utils.decodeHTMLEntities(val); - if ('/' === fixed.charAt(0)) { - fixed = libpath.join(pathTo(libpath.dirname(fixed), dirname), - libpath.basename(fixed)); - } - fixed = Y.Escape.html(fixed); - return name + '="' + fixed + '"'; - }); - } - - return content; -} - - -writeWebPagesToFiles = function(destination, urls, config, callback) { - var options, - userPages = {}, - i, - app; - - if (config.urls) { - // If the user is using builds.html5app.attachManifest or - // .forceRelativePaths, we explicitly want to fix those URLs which are - // pages (the ones they've specified). - for (i = 0; i < config.urls.length; i += 1) { - userPages[config.urls[i]] = true; - } - } - - options = { - port: 11111, - context: config.context - }; - - app = Mojito.createServer(options); - - app.listen(null, null, function(err) { - var got = 0, - need; - - if (err) { - utils.error(err); - return; - } - need = Object.keys(urls).length; - - Object.keys(urls).forEach(function(u) { - var opts = { - headers: { - 'x-mojito-build': 'html5app', - 'x-mojito-build-path-to-root': pathTo('/', - libpath.dirname(urls[u])) - } - }; - app.getWebPage(u, opts, function(err, url, content) { - var dest; - - got += 1; - - if (err) { - utils.error('FAILED to get ' + url + ' with error:\n' + - err, null, true); - } else { - if (config.attachManifest) { - content = attachManifest(destination, urls[url], - content, - userPages[url]); - } - if (config.forceRelativePaths) { - content = forceRelativePaths(destination, urls[url], - content, - userPages[url] - ); - } - dest = libpath.join(destination, urls[url]); - mkdirP(libpath.dirname(dest), MODE_755); - fs.writeFileSync(dest, content, 'utf8'); - } - - if (got === need) { - app.close(); - callback(); - } - }); - }); - }); -}; diff --git a/lib/app/commands/build/index.js b/lib/app/commands/build/index.js new file mode 100644 index 000000000..a856e4ff8 --- /dev/null +++ b/lib/app/commands/build/index.js @@ -0,0 +1,141 @@ +/*jslint sloppy:true, stupid:true, node:true */ +// see lib/management/cli.js and lib/app/commands/build.js +// this module normalizes input/options and invokes buildhtml5app + +var path = require('path'), + //get path/to/mojito this way in case this is symlinked/`npm link`ed + BASE = module.parent.id.replace('lib/management/cli.js', '') || '', + CWD = process.cwd(), + writer = require('./lib/writer'), + util = require(BASE + 'lib/management/utils'), + M = require(BASE + 'lib/mojito'), + Y = require('./lib/yuiuse')({ + 'escape': null, + 'json-parse': null, + 'json-stringify': null, + 'mojito-util': BASE + 'lib/app/autoload/util.common.js', + 'mojito-resource-store': BASE + 'lib/store.server.js' + }, BASE + 'node_modules/yui');// omit 2nd param if yuiuse moves in core + + +/** + * normalize mojito application configuration object properties + * @param {Object} + * @param {String} type Build type, i.e. 'html5app' + * @param {String} dir Path to create and copy build + * @return {Object} normalized mojito application configuration object + */ +function config(conf, type, dir) { + var bc; // shortcut to conf.builds[type] + + conf = conf || {}; + conf.specs = conf.specs || {}; + conf.tunnelPrefix = conf.tunnelPrefix || '/tunnel'; + + // staticHandling + conf.staticHandling = conf.staticHandling || {}; + conf.staticHandling.prefix = conf.staticHandling.prefix || '/static'; + + // builds + conf.builds = conf.builds || {}; + conf.builds[type] = bc = conf.builds[type] || {}; + bc.urls = bc.urls || []; + + if (dir) { + // build dir from cli arg overrides app.json:build.buildDir + bc.buildDir = path.resolve(dir); + } + + if (!bc.hasOwnProperty('buildDir')) { + // default build dir ./artifacts/builds/html5app + bc.buildDir = path.resolve(CWD, 'artifacts/builds', type); + } + + return conf; +} + +/** + * @param {Array} args Trailing cli arguments passed to cli.js + * @param {Object} opts Parsed cli options like -c (see exports.options) + * @param {Function} cb callback to cli.js, takes string parameter for errors + */ +function run(args, opts, cb) { + var store, + csvctx = util.contextCsvToObject, // shortcut + context, + appconf, + builddir, + buildtype = args[0] || 'html5app'; + + if (!util.isMojitoApp(CWD)) { + return cb('Error: This is not a Mojito directory'); + } + + context = typeof opts.context === 'string' ? csvctx(opts.context) : {}; + store = new Y.mojito.ResourceStore({root: CWD, context: context}); + appconf = config(store.getAppConfig(context), buildtype, args[1]); + builddir = appconf.builds[buildtype].buildDir; + + function die(err) { + cb(err, null, true); + } + + function beforeWrite(file, str) { + writer.write(file, str, false); + writer.write(path.join(builddir, 'filelist.txt'), file + '\n', true); + } + + function makeSnapshot(packages) { + var data = { + "name": opts.snapshotName || 'missing -n ', + "tag": opts.snapshotTag || 'missing -t ', + "packages": packages + }, + str = JSON.stringify(data, null, 4); + beforeWrite(builddir + '/snapshot.json', str); + } + + function next(err) { + if (err) { + return die('Error removing ' + builddir + "\n" + err); + } + + var Build = require('./lib/' + buildtype), + build = new Build(appconf, buildtype, context, store); + + build.on('snapshot', makeSnapshot) + .on('write', beforeWrite) + .on('copy', writer.copy) + .on('error', die) + .on('warn', console.log) + .on('info', console.log) + .on('done', cb) + .exec(M); + } + + return opts.replace ? writer.rmrf(builddir, next) : next(); +} + +module.exports = { + run: run, + options: [ + {shortName: 'n', longName: 'snapshotName', hasValue: true}, + {shortName: 't', longName: 'snapshotTag', hasValue: true}, + {shortName: 'r', longName: 'replace', hasValue: false}, + {shortName: 'c', longName: 'context', hasValue: true}, + {shortName: 'm', longName: 'mojit', hasValue: true} + ], + usage: [ + 'mojito build {type} [destination]', + '', + 'type: "html5app" is the only current type', + 'destination: (optional) the directory where the build output goes.', + ' By default this is the type i.e. "./artifacts/builds/"', + '', + 'OPTIONS: ', + ' --replace: Tells the build system to delete the destination directory and replace it.', + ' -r: Short for --replace', + ' --context: Tells the build system what context to build with i.e. device=iphone&lang=en-GB.', + ' -c: Short for --context\n' + ].join("\n ") +}; diff --git a/lib/app/commands/build/lib/html5app.js b/lib/app/commands/build/lib/html5app.js new file mode 100644 index 000000000..63c19d460 --- /dev/null +++ b/lib/app/commands/build/lib/html5app.js @@ -0,0 +1,200 @@ +/*jslint sloppy:true, stupid:true, node:true */ + +var qs = require('querystring'), + path = require('path'), + Stream = require('stream'), + Scraper = require('./scraper'), + + EXTNS = /\.(css|js|json)$/i, + PORT = 1111; + + +function Html5app(appconf, buildtype, context, store) { + + this.appconf = appconf; + + this.buildtype = buildtype; + this.builddir = appconf.builds[buildtype].buildDir; // shortcut + + this.context = context; + this.contextqs = qs.stringify(this.context); + if (this.contextqs.length) { + this.contextqs = '?' + this.contextqs; + } + + this.store = store; +} + +function exec(mojito) { + var me = this, + buildmap = {}, + scraper = new Scraper(mojito); + + // init + this.store.preload(); + + // init the map of mojito-http to filesystem uris + // this is rm'd for crt, but used for docs + buildmap['/' + this.contextqs] = '/index.html'; + + // add uris enumerated in application.json + this.appconf.builds[this.buildtype].urls.forEach(function (uri) { + buildmap[uri + me.contextqs] = uri; + }); + + // add uris for mojito and app internals to buildmap + this.mapStoreUris(buildmap); + + // add tunnel uris to buildmap + this.mapDefxUris(buildmap); + + // add uris to buildmap from legacy mojit ids that have spec file parts + this.mapFunkySpecUris(buildmap); + + // create a couple files + this.makeManifest(buildmap, this.builddir); + this.makeIndexJs(path.join(this.builddir, 'index.js')); + + // use buildmap to scrape mojito server and save the rendered pages + scraper + .on('scraped-one', this.mungePage.bind(this)) + .on('scraping-done', this.emit.bind(this, 'done')) + .on('error', this.emit.bind(this, 'error')) + .on('warn', this.emit.bind(this, 'warn')) + .on('info', this.emit.bind(this, 'info')) + .start({port: PORT, context: this.context}) + .fetch(buildmap); +} + +// add store uris +function mapStoreUris(buildmap) { + var from, to, uri, storemap = this.store.getAllURLs(); + + for (uri in storemap) { + if (storemap.hasOwnProperty(uri)) { + from = storemap[uri]; // filesystem path + to = path.join(this.builddir, uri); + + if (uri.match(EXTNS)) { + buildmap[uri + this.contextqs] = uri; + } else { + this.emit('copy', from, to); + } + } + } +} + +// add /definition.json tunnel uris to buildmap +function mapDefxUris(buildmap) { + var opts = {type: 'mojit'}, + mojits = this.store.getResources('client', this.context, opts), + mojit, + i, + uri, + snapshot = {}; + + for (i in mojits) { + if (mojits.hasOwnProperty(i)) { + mojit = mojits[i]; + if (mojit.source.pkg.depth < 999) { + snapshot[mojit.name] = mojit.source.pkg.version; + } + uri = mojit.url + '/definition.json'; + buildmap[this.appconf.tunnelPrefix + uri + this.contextqs] = uri; + this.mapSpecUris(buildmap, mojit.name); + } + } + + this.emit('snapshot', snapshot); +} + +// 1. add /specs/default(?).json tunnel uris to buildmap +// 2. create mojit package info aka "snapshot" +function mapSpecUris(buildmap, name) { + var opts = {type: 'spec', mojit: name}, + specs = this.store.getResources('client', this.context, opts), + tunnelpf = this.appconf.tunnelPrefix, + qs = this.contextqs; + + Object.keys(specs).forEach(function (spec) { + if (spec.url) { + buildmap[tunnelpf + spec.url + qs] = spec.url; + } + }); +} + +// add funky mojit spec uris to buildmap +// i.e. "foo:bar" -> /specs/bar.json +function mapFunkySpecUris(buildmap) { + var staticpf = this.appconf.staticHandling.prefix, + tunnelpf = this.appconf.tunnelPrefix, + qs = this.contextqs; + + Object.keys(this.appconf.specs).forEach(function (id) { + var parts = id.split(':'), + type = parts[0], + name = parts[1] || 'default', + uri = [null, staticpf, type, 'specs', name + '.json'].join('/'); + + buildmap[tunnelpf + uri + qs] = uri; + }); +} + +function makeManifest(uris, dir, stamp) { + var lines = ['CACHE MANIFEST', '# ' + (stamp || new Date()), 'CACHE:']; + Object.keys(uris).forEach(function (i) { + lines.push(uris[i]); + }); + this.emit('write', path.join(dir, 'cache.manifest'), lines.join("\n")); +} + +function makeIndexJs(file) { + this.emit('write', file, 'module.exports = require("express")' + + '.createServer(require("express")["static"](__dirname));'); +} + +function injectManifestAttr(uri, str) { + var rel = path.relative(path.dirname(uri), '/cache.manifest'), + rgx = /(/i; + return str.replace(rgx, '$1 manifest="' + rel + '">'); +} + +function forceRelativePaths(uri, str) { + var rgx = /(src|href)="(\/\S+?)"/g, + dir = path.dirname(uri); + /*jslint unparam: true */ + return str.replace(rgx, function (matched, tag, val) { + return tag + '="' + path.relative(dir, val) + '"'; + }); +} + +// post-process .html file contents +function mungePage(mapuri, contents) { + var uri = mapuri === '/' ? '/index.html' : mapuri, + conf = this.appconf.builds[this.buildtype], + file = path.join(this.builddir, uri); + + if (/\.html$/i.test(uri)) { + if (conf.attachManifest) { + contents = injectManifestAttr(uri, contents); + } + if (conf.forceRelativePaths) { + contents = forceRelativePaths(uri, contents); + } + } + + this.emit('write', file, contents); +} + +Html5app.prototype = Object.create(Stream.prototype, { + exec: {value: exec}, + mapStoreUris: {value: mapStoreUris}, + mapDefxUris: {value: mapDefxUris}, + mapSpecUris: {value: mapSpecUris}, + mapFunkySpecUris: {value: mapFunkySpecUris}, + makeManifest: {value: makeManifest}, + makeIndexJs: {value: makeIndexJs}, + mungePage: {value: mungePage} +}); + +module.exports = Html5app; diff --git a/lib/app/commands/build/lib/scraper.js b/lib/app/commands/build/lib/scraper.js new file mode 100644 index 000000000..3b0b8e0ee --- /dev/null +++ b/lib/app/commands/build/lib/scraper.js @@ -0,0 +1,54 @@ +/*jslint sloppy:true, stupid:true, node:true */ + +var Stream = require('stream'); + +function Scaper(mojito) { + this.mojito = mojito; + this.server = null; +} + +function start(opts) { + this.server = this.mojito.createServer(opts); + return this; +} + +function fetch(buildmap, cb) { + var me = this; + + this.server.listen(null, null, function (err) { + var keys = Object.keys(buildmap), + have = 0, + failed = 0, + need = keys.length, + opts = {headers: {'x-mojito-build': 'html5app'}}; + + if (err) { + me.emit('error', err); + return cb('Failed to start server.'); + } + + keys.forEach(function (key) { + me.server.getWebPage(key, opts, function (err, uri, content) { + if (err) { + failed += 1; + me.emit('warn', 'FAILED to get ' + uri); + } else { + me.emit('scraped-one', buildmap[uri], content); + } + + have += 1; + if (have === need) { + me.server.close(); + me.emit('scraping-done'); + } + }); + }); + }); +} + +Scaper.prototype = Object.create(Stream.prototype, { + start: {value: start}, + fetch: {value: fetch} +}); + +module.exports = Scaper; diff --git a/lib/app/commands/build/lib/writer.js b/lib/app/commands/build/lib/writer.js new file mode 100644 index 000000000..c15f22d30 --- /dev/null +++ b/lib/app/commands/build/lib/writer.js @@ -0,0 +1,24 @@ +/*jslint sloppy:true, stupid:true, node:true */ +var fs = require('fs'), + path = require('path'), + rimraf = require('rimraf'), + mkdirp = require('mkdirp'); + +function write(filepath, str, append) { + mkdirp.sync(path.dirname(filepath)); + fs[append ? 'appendFileSync' : 'writeFileSync'](filepath, str, 'utf-8'); +} + +function copy(from, to) { + write(to, fs.readFileSync(from), false); +} + +function rmrf(dir, cb) { + rimraf(dir, cb); +} + +module.exports = { + write: write, + copy: copy, + rmrf: rmrf +}; diff --git a/lib/app/commands/build/lib/yuiuse.js b/lib/app/commands/build/lib/yuiuse.js new file mode 100644 index 000000000..f6806546e --- /dev/null +++ b/lib/app/commands/build/lib/yuiuse.js @@ -0,0 +1,44 @@ +/*jslint sloppy:true, stupid:true, node:true */ +/** + * some sugar for instantiating a Y object and attaching any YUI or Mojito (or + * any other local) modules to it, with useSync == true. So instead of: + * + * var Y = YUI({useSync: true}).use('oop', ...); + * Y.applyConfig({useSync: true, modules:{'mymodule1: ...}}); + * Y.use('mymodule1', ...); + * + * ...specify all modules and optional module configs in one go + * + * var Y = yuiuse({oop: null, mymodule1: 'path/to/it', foo: {base:...}, ...} + * + * @method yuiuse + * @param {Object} modules A hash of module names. If value is falsey, YUI + * will load it by name. If it's a string, it's the path of the module file to + * load, otherwise assume it's a modules config + * @param {String} some_particular_yui optional require() param to load YUI lib + * from somewhere besides default modules.path + * @param {Function} callback Optional for YUI().use(.. callback); Loading is + * synchronous if omitted + * @return {Object} yui instance + */ +function yuiuse(modules, my_yui, callback) { + var yui = require(my_yui || 'yui').YUI, + names = Object.keys(modules || {}), + local = {}, + y = yui({useSync: !callback}); + + names.forEach(function (name) { + var val = modules[name]; + if (val) { + local[name] = typeof val === 'string' ? {fullpath: val} : val; + } + }); + + y.applyConfig({modules: local}); + y.use(names); + y.applyConfig({useSync: false}); + + return callback ? callback(y) : y; +} + +module.exports = yuiuse; diff --git a/package.json b/package.json index 56ec52cfc..3db038196 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,10 @@ "express": "2.5.10", "glob": "~3.1.11", "jslint": "~0.1.9", + "mkdirp": ">0.3", "mime": "1.2.4", "node-static": "*", + "rimraf": ">2", "semver": "1.0.14", "wrench": "~1.3.9", "ycb": "~1.0.0", From f2dcaa1548bb65fdb85ee45f326aa01d5bb29525 Mon Sep 17 00:00:00 2001 From: Isao Yagi Date: Tue, 9 Oct 2012 15:49:13 -0700 Subject: [PATCH 02/24] build/create changes --- lib/app/commands/build/{lib => }/html5app.js | 0 lib/app/commands/build/index.js | 33 ++- lib/app/commands/build/{lib => }/scraper.js | 0 lib/app/commands/build/{lib => }/writer.js | 0 lib/app/commands/build/{lib => }/yuiuse.js | 0 lib/app/commands/create.js | 258 +++++++++---------- 6 files changed, 138 insertions(+), 153 deletions(-) rename lib/app/commands/build/{lib => }/html5app.js (100%) rename lib/app/commands/build/{lib => }/scraper.js (100%) rename lib/app/commands/build/{lib => }/writer.js (100%) rename lib/app/commands/build/{lib => }/yuiuse.js (100%) diff --git a/lib/app/commands/build/lib/html5app.js b/lib/app/commands/build/html5app.js similarity index 100% rename from lib/app/commands/build/lib/html5app.js rename to lib/app/commands/build/html5app.js diff --git a/lib/app/commands/build/index.js b/lib/app/commands/build/index.js index a856e4ff8..075138685 100644 --- a/lib/app/commands/build/index.js +++ b/lib/app/commands/build/index.js @@ -1,22 +1,18 @@ /*jslint sloppy:true, stupid:true, node:true */ -// see lib/management/cli.js and lib/app/commands/build.js -// this module normalizes input/options and invokes buildhtml5app var path = require('path'), - //get path/to/mojito this way in case this is symlinked/`npm link`ed - BASE = module.parent.id.replace('lib/management/cli.js', '') || '', + BASE = path.resolve(__dirname, '../../../../') + '/', CWD = process.cwd(), - writer = require('./lib/writer'), + writer = require('./writer'), util = require(BASE + 'lib/management/utils'), M = require(BASE + 'lib/mojito'), - Y = require('./lib/yuiuse')({ + Y = require('./yuiuse')({ 'escape': null, 'json-parse': null, 'json-stringify': null, 'mojito-util': BASE + 'lib/app/autoload/util.common.js', 'mojito-resource-store': BASE + 'lib/store.server.js' - }, BASE + 'node_modules/yui');// omit 2nd param if yuiuse moves in core - + }); /** * normalize mojito application configuration object properties @@ -65,10 +61,20 @@ function run(args, opts, cb) { context, appconf, builddir, - buildtype = args[0] || 'html5app'; + buildtype = (args[0] + '').toLowerCase(); + + switch (buildtype) { + case 'html5app': + case 'hybridapp': + break; + case 'undefined': + die('Missing type'); + default: + die('Invalid type'); + } if (!util.isMojitoApp(CWD)) { - return cb('Error: This is not a Mojito directory'); + die('Not a Mojito directory'); } context = typeof opts.context === 'string' ? csvctx(opts.context) : {}; @@ -92,6 +98,7 @@ function run(args, opts, cb) { "packages": packages }, str = JSON.stringify(data, null, 4); + beforeWrite(builddir + '/snapshot.json', str); } @@ -100,7 +107,7 @@ function run(args, opts, cb) { return die('Error removing ' + builddir + "\n" + err); } - var Build = require('./lib/' + buildtype), + var Build = require('./' + buildtype), build = new Build(appconf, buildtype, context, store); build.on('snapshot', makeSnapshot) @@ -128,11 +135,11 @@ module.exports = { usage: [ 'mojito build {type} [destination]', '', - 'type: "html5app" is the only current type', + 'type: "html5app" or "hybridapp"', 'destination: (optional) the directory where the build output goes.', ' By default this is the type i.e. "./artifacts/builds/"', '', - 'OPTIONS: ', + 'OPTIONS:', ' --replace: Tells the build system to delete the destination directory and replace it.', ' -r: Short for --replace', ' --context: Tells the build system what context to build with i.e. device=iphone&lang=en-GB.', diff --git a/lib/app/commands/build/lib/scraper.js b/lib/app/commands/build/scraper.js similarity index 100% rename from lib/app/commands/build/lib/scraper.js rename to lib/app/commands/build/scraper.js diff --git a/lib/app/commands/build/lib/writer.js b/lib/app/commands/build/writer.js similarity index 100% rename from lib/app/commands/build/lib/writer.js rename to lib/app/commands/build/writer.js diff --git a/lib/app/commands/build/lib/yuiuse.js b/lib/app/commands/build/yuiuse.js similarity index 100% rename from lib/app/commands/build/lib/yuiuse.js rename to lib/app/commands/build/yuiuse.js diff --git a/lib/app/commands/create.js b/lib/app/commands/create.js index ffd1c4795..0370eff76 100644 --- a/lib/app/commands/create.js +++ b/lib/app/commands/create.js @@ -10,8 +10,8 @@ var path = require('path'), fs = require('fs'), - utils = require(path.join(__dirname, '../../management/utils')), - archetypePath = path.join(__dirname, '../archetypes'), + utils = require(path.resolve(__dirname, '../../management/utils')), + archetypePath = path.resolve(__dirname, '../archetypes'), // Found at http://www.crockford.com/javascript/survey.html reservedWords = [ 'abstract', @@ -31,193 +31,171 @@ var path = require('path'), 'var', 'volatile', 'void', 'while', 'with' ], - usage, Y = require('yui').YUI({useSync: true}).use('json-parse', 'json-stringify'); Y.applyConfig({useSync: false}); -usage = 'mojito create {type} [archetype] {name} [options]\n' + - "\t- type: 'app', 'mojit', or 'project'\n" + - '\t- archetype: (optional) template to use for creation\n' + - '\t- name: given name for creation\n\n' + - 'Example Usage: mojito create app foo\n' + - "\t(creates directory 'foo' containing new Mojito application)\n" + - 'Example Usage: mojito create mojit Bar\n' + - "\t(creates directory 'Bar' containing new Mojit)\n\n" + - 'OPTIONS: \n' + - '\t --port [number]: ' + - 'Provides a default port for your Mojito app to run within.\n' + - '\t -p [number]: Short for --port\n' + - '\t--force: Forced mojit creation even outside a Mojito app.\n' + - '\t -f: Short for --force\n'; - - -var createApp = function(archetype, destdir, inputs, force, callback) { - - var type = 'app', - name = inputs.name, - data = { - name: name, - port: inputs.port - }; - - utils.process_directory(path.join(archetypePath, type, archetype), '/', - destdir, data, force); - utils.success(type + ': ' + name + ' created!'); - -}; - - -var createMojit = function(archetype, destdir, inputs, force, callback) { - - var type = 'mojit', - name = inputs.name, - data = { - name: name - }; +/** + * Determine the path to some archtype and ensure that it exists (or die) + * @param {String} type Is one of: app, mojit, custom + * @param {String} subtype If type is 'custom', this is the path to the source + * directory of the archtype files to use. Otherwise, it's a subdirectory of + * the mojito framework archetype path (archetypePath). + * @return {String} absolute filesystem path + */ +function getArchetypeSrcDir(type, subtype) { + var stat, srcdir, errmsg; - if (force) { - destdir = name; + if (type === 'custom') { + srcdir = path.resolve(subtype); + errmsg = 'Custom archetype path does not exist: ' + srcdir; } else { - destdir = path.join(process.cwd(), 'mojits', name); + srcdir = path.resolve(archetypePath, type, subtype); + errmsg = 'Invalid archetype "' + type + '"'; } - utils.process_directory(path.join(archetypePath, type, archetype), '/', - destdir, data, force); - - utils.success(type + ': ' + name + ' created!'); - - callback(); -}; - - -var createProject = function(archetype, destdir, inputs, force, callback) { - - var type = 'project', - name = inputs.name, - data = { - name: name - }, - config; - - // Are we in a Mojito App? (We need the application.json file to make this - // work) try { - config = Y.JSON.parse(String(fs.readFileSync(path.join(process.cwd(), - 'application.json')))); - config = config[0]; + stat = fs.statSync(srcdir); + if (!stat.isDirectory()) { + utils.error('Archetype path is not a directory', exports.usage, true); + } } catch (err) { - // there is no application.json - return utils.error('You need to be in a Mojito application directory' + - ' that has an \'application.json\' file.'); + utils.error(errmsg, exports.usage, true); } - utils.makeDir(path.join(process.cwd(), 'artifacts/projects/', archetype)); - destdir = path.join('artifacts/projects/', archetype, destdir); + return srcdir; +} - utils.process_directory(path.join(archetypePath, type, archetype), '/', - destdir, data, force); +/** + * @param {String} name + * @return {String} name The cleaned up name of the app, mojit, etc. to create. + */ +function cleanName(name) { + var newname = name && name.replace(/[^a-z_0-9]+/ig, '_'), + msg; - utils.success(type + ': ' + name + ' created!'); + if (!name) { + utils.error('Missing a target name to create', exports.usage, true); + } - callback(); -}; + if (-1 !== reservedWords.indexOf(newname)) { + msg = 'Name cannot be one of: ' + reservedWords.join(', ') + '\n'; + utils.error(msg, exports.usage, true); + } + if (name !== newname) { + msg = ['changing name ', name, ' to ', newname, + ' so it is usable as a javascript identifier'].join('"'); + utils.log(msg); + } -function run(params, options, callback) { + return newname; +} - var type = params[0], - archetype = params[1], - name = params[2], - port = options.port || 8666, +function run(params, options, callback) { + var port = options.port || 8666, force = options.force || false, - inputs = {}, - destdir = name, - create, - key; - - switch (type.toUpperCase()) { - case 'APP': - type = 'app'; - create = createApp; - break; - case 'MOJIT': - type = 'mojit'; - create = createMojit; - break; - case 'PROJECT': - type = 'project'; - create = createProject; - break; - default: - type = null; - } + inputs = utils.contextCsvToObject(options.keyval || ''), + srcdir, + destdir, - // If we have no "type" then fail - if (!type) { - return utils.error('Incorrect type provided', usage); - } + // params[0] must be "app", "mojit", "custom" + type = params[0] ? params[0].toLowerCase() : '', - // If we have no name then the "archetype" may be it - if (!name) { - name = archetype; - destdir = name; - archetype = 'default'; - } + // params[1] may be "default", "simple", "full", "hybrid", path, name + subtype = params[1], - // If we have a name, check its a good one! - if (name) { - name = destdir = - name.replace(/[^a-z0-9]/ig, '_').replace(/(_)\1+/g, '_'); + // params[2] may be name + name = params[2]; - for (key in reservedWords) { - if (reservedWords[key] === name) { - utils.error('Name cannot be one of:\n\n' + - reservedWords.join(', ') + '\n', usage); - } - } + // If we have no name then the "subtype" might be it + if (!name && 'hybrid' !== subtype) { + name = subtype; + subtype = 'default'; } - // If we have no "name" then fail - if (!name) { - return utils.error('Cannot create ' + type + ' without a name!', usage); + // check name, convert to js compat name, or die + name = cleanName(name); + + // Validate type, which dictates destination path/name + switch (type) { + case 'app': + case 'custom': + destdir = path.resolve(process.cwd(), name); + break; + case 'mojit': + destdir = path.resolve(process.cwd(), name, 'mojits'); + break; + default: + return utils.error('Incorrect type "' + type + + '", must be either "app", "mojit", or "custom".', exports.usage); } - utils.log('creating ' + type + " called '" + name + "'"); - utils.log('(using \"' + archetype + '\" archetype)'); + // get path to mojito's archetypes dir, or a custom one, or die + srcdir = getArchetypeSrcDir(type, subtype); - if (!utils.validate_archetype(type, archetype)) { - return utils.error('Invalid archetype: \"' + type + '/' + archetype + - '\"', usage); - } + utils.log('creating ' + type + " type named '" + name + "'"); + utils.log('(using "' + subtype + '" archetype)'); // Define the inputs inputs.name = name; inputs.port = port; - create(archetype, destdir, inputs, force, callback); - + utils.process_directory(srcdir, '/', destdir, inputs, force); + utils.success(type + ': ' + name + ' created!'); + callback(); } /** * Standard usage string export. */ -exports.usage = usage; +exports.usage = [ + 'mojito create {type} [subtype or source directory] {name} [options]', + ' - type: "app", "mojit", or "custom".', + ' - archetype: optional template. Possible values are default, full, simple', + ' "app" types also have a "hybrid" archetype which creates an app and a', + ' mojit with common configurations for use with hybrid app.', + ' If the type is "custom" then this is the path to your own archetype', + ' directory.', + ' - name: name of the app or mojit to create', + '', + 'Example: mojito create app Foo', + " (creates directory 'Foo' containing new Mojito application)", + '', + 'Example: mojito create mojit Bar', + ' (creates directory "Bar" containing new Mojit)', + '', + 'Example: mojito create app hybrid Baz', + ' (creates directory "Baz" containing new hybrid app and mojit)', + '', + 'OPTIONS: ', + ' --port [number] Specifies default port for your Mojito app to run on.', + ' -p [number] Short for --port', + ' --force Forced mojit creation even outside a Mojito app.', + ' -f Short for --force', + ' -keyval [string] key value pairs to pass to a custom archetype template', + ' a key/value is separated by colons, key/value pairs by', + ' commas: "key1:val1,key2:val2', + ' -k [string] Short for --keyval'].join("\n"); /** * Standard options list export. */ exports.options = [{ - shortName: 'p', - longName: 'port', - hasValue: true -}, { shortName: 'f', longName: 'force', hasValue: false +}, { + shortName: 'k', + longName: 'keyval', + hasValue: true +}, { + shortName: 'p', + longName: 'port', + hasValue: true }]; From 3647cb2ae9cfba78f6e9543853521a25097e952d Mon Sep 17 00:00:00 2001 From: Isao Yagi Date: Wed, 17 Oct 2012 11:52:45 -0700 Subject: [PATCH 03/24] hybrid archetype: rm index.js --- lib/app/archetypes/app/hybrid/index.js | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 lib/app/archetypes/app/hybrid/index.js diff --git a/lib/app/archetypes/app/hybrid/index.js b/lib/app/archetypes/app/hybrid/index.js deleted file mode 100644 index 52a44ce10..000000000 --- a/lib/app/archetypes/app/hybrid/index.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2011-2012, Yahoo! Inc. All rights reserved. - * Copyrights licensed under the New BSD License. - * See the accompanying LICENSE file for terms. - */ - - -/*jslint anon:true, sloppy:true, nomen:true, node:true*/ - - -process.chdir(__dirname); - - -/** - * @param {object} config The configuration object containing processing params. - * @param {object} token Token used to identify the application. - */ -module.exports = function(config, token) { - var app = require('./server.js'); - - // Signal the application is ready, providing the token and app references. - process.emit('application-ready', token, app); -}; From 5140c2894f832dd4bccfad644edf1e70dcad8417 Mon Sep 17 00:00:00 2001 From: Isao Yagi Date: Wed, 17 Oct 2012 12:00:21 -0700 Subject: [PATCH 04/24] build.js: rm index.js --- lib/app/commands/build/html5app.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/app/commands/build/html5app.js b/lib/app/commands/build/html5app.js index 63c19d460..88521a168 100644 --- a/lib/app/commands/build/html5app.js +++ b/lib/app/commands/build/html5app.js @@ -53,7 +53,6 @@ function exec(mojito) { // create a couple files this.makeManifest(buildmap, this.builddir); - this.makeIndexJs(path.join(this.builddir, 'index.js')); // use buildmap to scrape mojito server and save the rendered pages scraper @@ -148,11 +147,6 @@ function makeManifest(uris, dir, stamp) { this.emit('write', path.join(dir, 'cache.manifest'), lines.join("\n")); } -function makeIndexJs(file) { - this.emit('write', file, 'module.exports = require("express")' + - '.createServer(require("express")["static"](__dirname));'); -} - function injectManifestAttr(uri, str) { var rel = path.relative(path.dirname(uri), '/cache.manifest'), rgx = /(/i; @@ -193,7 +187,6 @@ Html5app.prototype = Object.create(Stream.prototype, { mapSpecUris: {value: mapSpecUris}, mapFunkySpecUris: {value: mapFunkySpecUris}, makeManifest: {value: makeManifest}, - makeIndexJs: {value: makeIndexJs}, mungePage: {value: mungePage} }); From db9f0ee7e928deeea30da482e8dc7f5ceb4945b4 Mon Sep 17 00:00:00 2001 From: Isao Yagi Date: Wed, 17 Oct 2012 15:43:33 -0700 Subject: [PATCH 05/24] refactor for getConfigs() --- lib/app/commands/build/html5app.js | 179 ++++++++++++----------------- lib/app/commands/build/index.js | 127 ++++++++++---------- 2 files changed, 139 insertions(+), 167 deletions(-) diff --git a/lib/app/commands/build/html5app.js b/lib/app/commands/build/html5app.js index 88521a168..0b593fa86 100644 --- a/lib/app/commands/build/html5app.js +++ b/lib/app/commands/build/html5app.js @@ -3,117 +3,43 @@ var qs = require('querystring'), path = require('path'), Stream = require('stream'), - Scraper = require('./scraper'), - EXTNS = /\.(css|js|json)$/i, PORT = 1111; -function Html5app(appconf, buildtype, context, store) { - - this.appconf = appconf; - - this.buildtype = buildtype; - this.builddir = appconf.builds[buildtype].buildDir; // shortcut - - this.context = context; - this.contextqs = qs.stringify(this.context); - if (this.contextqs.length) { - this.contextqs = '?' + this.contextqs; - } - - this.store = store; -} - -function exec(mojito) { - var me = this, - buildmap = {}, - scraper = new Scraper(mojito); - - // init - this.store.preload(); - - // init the map of mojito-http to filesystem uris - // this is rm'd for crt, but used for docs - buildmap['/' + this.contextqs] = '/index.html'; - - // add uris enumerated in application.json - this.appconf.builds[this.buildtype].urls.forEach(function (uri) { - buildmap[uri + me.contextqs] = uri; - }); - - // add uris for mojito and app internals to buildmap - this.mapStoreUris(buildmap); - - // add tunnel uris to buildmap - this.mapDefxUris(buildmap); - - // add uris to buildmap from legacy mojit ids that have spec file parts - this.mapFunkySpecUris(buildmap); - - // create a couple files - this.makeManifest(buildmap, this.builddir); - - // use buildmap to scrape mojito server and save the rendered pages - scraper - .on('scraped-one', this.mungePage.bind(this)) - .on('scraping-done', this.emit.bind(this, 'done')) - .on('error', this.emit.bind(this, 'error')) - .on('warn', this.emit.bind(this, 'warn')) - .on('info', this.emit.bind(this, 'info')) - .start({port: PORT, context: this.context}) - .fetch(buildmap); -} - -// add store uris -function mapStoreUris(buildmap) { - var from, to, uri, storemap = this.store.getAllURLs(); - - for (uri in storemap) { - if (storemap.hasOwnProperty(uri)) { - from = storemap[uri]; // filesystem path - to = path.join(this.builddir, uri); - - if (uri.match(EXTNS)) { - buildmap[uri + this.contextqs] = uri; - } else { - this.emit('copy', from, to); - } +// add uris from resource store to buildmap, copy some to build dir +function mapStoreUris(buildmap, conf, storemap) { + Object.keys(storemap).forEach(function (uri) { + var from, to; + if (uri.match(EXTNS)) { + buildmap[uri + conf.contextqs] = uri; + } else { + from = storemap[uri]; // source mojito app filesystem path + to = path.join(conf.build.dir, uri); + this.emit('copy', from, to); } - } + }.bind(this)); } // add /definition.json tunnel uris to buildmap -function mapDefxUris(buildmap) { +function mapDefxUris(buildmap, conf, store) { var opts = {type: 'mojit'}, - mojits = this.store.getResources('client', this.context, opts), - mojit, - i, - uri, - snapshot = {}; - - for (i in mojits) { - if (mojits.hasOwnProperty(i)) { - mojit = mojits[i]; - if (mojit.source.pkg.depth < 999) { - snapshot[mojit.name] = mojit.source.pkg.version; - } - uri = mojit.url + '/definition.json'; - buildmap[this.appconf.tunnelPrefix + uri + this.contextqs] = uri; - this.mapSpecUris(buildmap, mojit.name); - } - } + mojits = store.getResources('client', conf.context, opts); + + mojits.forEach(function (mojit) { + var uri = mojit.url + '/definition.json'; - this.emit('snapshot', snapshot); + buildmap[conf.tunnelpf + uri + conf.contextqs] = uri; + this.mapSpecUris(buildmap, conf, mojit.name, store); + }.bind(this)); } -// 1. add /specs/default(?).json tunnel uris to buildmap -// 2. create mojit package info aka "snapshot" -function mapSpecUris(buildmap, name) { +// add /specs/default(?).json tunnel uris to buildmap +function mapSpecUris(buildmap, conf, name, store) { var opts = {type: 'spec', mojit: name}, - specs = this.store.getResources('client', this.context, opts), - tunnelpf = this.appconf.tunnelPrefix, - qs = this.contextqs; + specs = store.getResources('client', conf.context, opts), + tunnelpf = conf.tunnelpf, + qs = conf.contextqs; Object.keys(specs).forEach(function (spec) { if (spec.url) { @@ -124,12 +50,12 @@ function mapSpecUris(buildmap, name) { // add funky mojit spec uris to buildmap // i.e. "foo:bar" -> /specs/bar.json -function mapFunkySpecUris(buildmap) { - var staticpf = this.appconf.staticHandling.prefix, - tunnelpf = this.appconf.tunnelPrefix, - qs = this.contextqs; +function mapFunkySpecUris(buildmap, conf) { + var staticpf = conf.staticpf, + tunnelpf = conf.tunnelpf, + qs = conf.contextqs; - Object.keys(this.appconf.specs).forEach(function (id) { + Object.keys(conf.app.specs).forEach(function (id) { var parts = id.split(':'), type = parts[0], name = parts[1] || 'default', @@ -163,16 +89,15 @@ function forceRelativePaths(uri, str) { } // post-process .html file contents -function mungePage(mapuri, contents) { +function mungePage(conf, mapuri, contents) { var uri = mapuri === '/' ? '/index.html' : mapuri, - conf = this.appconf.builds[this.buildtype], - file = path.join(this.builddir, uri); + file = path.join(conf.build.dir, uri); if (/\.html$/i.test(uri)) { - if (conf.attachManifest) { + if (conf.build.attachManifest) { contents = injectManifestAttr(uri, contents); } - if (conf.forceRelativePaths) { + if (conf.build.forceRelativePaths) { contents = forceRelativePaths(uri, contents); } } @@ -180,6 +105,44 @@ function mungePage(mapuri, contents) { this.emit('write', file, contents); } +function exec(conf, store, scraper) { + var buildmap = {}; + + // init the map of mojito-http to filesystem uris + // this is rm'd for crt, but used for docs + buildmap['/' + conf.contextqs] = '/index.html'; + + // add uris enumerated in application.json + conf.build.uris.forEach(function (uri) { + buildmap[uri + conf.contextqs] = uri; + }); + + // add uris for mojito and app internals to buildmap + this.mapStoreUris(buildmap, conf, store.getAllURLs() || {}); + + // add tunnel uris to buildmap + this.mapDefxUris(buildmap, conf, store); + + // add uris to buildmap from legacy mojit ids that have spec file parts + this.mapFunkySpecUris(buildmap, conf); + + // create a couple files + this.makeManifest(buildmap, conf.build.dir, null); + + // use buildmap to scrape mojito server and save the rendered pages + scraper + .on('scraped-one', this.mungePage.bind(this, conf)) + .on('scraping-done', this.emit.bind(this, 'done')) + .on('error', this.emit.bind(this, 'error')) + .on('warn', this.emit.bind(this, 'warn')) + .on('info', this.emit.bind(this, 'info')) + .start({port: PORT, context: conf.context}) + .fetch(buildmap); +} + + +function Html5app() {} + Html5app.prototype = Object.create(Stream.prototype, { exec: {value: exec}, mapStoreUris: {value: mapStoreUris}, diff --git a/lib/app/commands/build/index.js b/lib/app/commands/build/index.js index 075138685..a4182b98e 100644 --- a/lib/app/commands/build/index.js +++ b/lib/app/commands/build/index.js @@ -1,11 +1,11 @@ /*jslint sloppy:true, stupid:true, node:true */ - +/*jslint nomen:true */ // node:true doesn't seem to register __dirname var path = require('path'), + qs = require('querystring'), BASE = path.resolve(__dirname, '../../../../') + '/', CWD = process.cwd(), writer = require('./writer'), util = require(BASE + 'lib/management/utils'), - M = require(BASE + 'lib/mojito'), Y = require('./yuiuse')({ 'escape': null, 'json-parse': null, @@ -14,40 +14,54 @@ var path = require('path'), 'mojito-resource-store': BASE + 'lib/store.server.js' }); -/** - * normalize mojito application configuration object properties - * @param {Object} - * @param {String} type Build type, i.e. 'html5app' - * @param {String} dir Path to create and copy build - * @return {Object} normalized mojito application configuration object - */ -function config(conf, type, dir) { - var bc; // shortcut to conf.builds[type] - conf = conf || {}; - conf.specs = conf.specs || {}; - conf.tunnelPrefix = conf.tunnelPrefix || '/tunnel'; +function getConfigs(opts, buildtype, builddir, store) { + var appconf = store.getAppConfig(opts.context) || {/* no app.json? */}, + pkgmeta = store.getResourceVersions({name: 'application'})[0].source.pkg, + contextqs = qs.stringify(opts.context), // context as uri query string + dotbuild; // shortcut to appconf.builds[buildtype] - // staticHandling - conf.staticHandling = conf.staticHandling || {}; - conf.staticHandling.prefix = conf.staticHandling.prefix || '/static'; + // define missing build obj properties + dotbuild = (appconf.builds && appconf.builds[buildtype]) || {}; + dotbuild.urls = dotbuild.urls || []; - // builds - conf.builds = conf.builds || {}; - conf.builds[type] = bc = conf.builds[type] || {}; - bc.urls = bc.urls || []; + // define missing staticHandling.prefix + appconf.staticHandling = appconf.staticHandling || {}; + appconf.staticHandling.prefix = appconf.staticHandling.prefix || '/static'; - if (dir) { - // build dir from cli arg overrides app.json:build.buildDir - bc.buildDir = path.resolve(dir); + if (builddir) { + // override application.json:builds[buildtype].buildDir with cli arg + dotbuild.buildDir = path.resolve(builddir); } - if (!bc.hasOwnProperty('buildDir')) { - // default build dir ./artifacts/builds/html5app - bc.buildDir = path.resolve(CWD, 'artifacts/builds', type); + if (!dotbuild.hasOwnProperty('buildDir')) { + // use default build dir ./artifacts/builds/html5app + dotbuild.buildDir = path.resolve(CWD, 'artifacts/builds', buildtype); } - return conf; + // ok, we should have all the inputs we need to proceed with any build + return { + app: { + name: pkgmeta.name, + version: pkgmeta.version, + specs: appconf.specs || {} + }, + snapshot: { + name: opts.snapshotName || 'missing --snapshotName', + tag: opts.snapshotTag || 'missing --snapshotTag' + }, + build: { + attachManifest: dotbuild.attachManifest, + forceRelativePaths: dotbuild.forceRelativePaths, + dir: dotbuild.buildDir, + type: buildtype, + uris: dotbuild.urls || [] + }, + context: opts.context, + contextqs: contextqs.length ? '?' + contextqs : '', + tunnelpf: appconf.tunnelPrefix || '/tunnel', + staticpf: appconf.staticHandling.prefix + }; } /** @@ -56,12 +70,14 @@ function config(conf, type, dir) { * @param {Function} cb callback to cli.js, takes string parameter for errors */ function run(args, opts, cb) { - var store, - csvctx = util.contextCsvToObject, // shortcut - context, - appconf, - builddir, - buildtype = (args[0] + '').toLowerCase(); + var csvctx = util.contextCsvToObject, // shortcut + buildtype = String(args[0]).toLowerCase(), + store, + conf; + + function die(err) { + cb(err, null, true); + } switch (buildtype) { case 'html5app': @@ -69,6 +85,7 @@ function run(args, opts, cb) { break; case 'undefined': die('Missing type'); + break; default: die('Invalid type'); } @@ -77,50 +94,42 @@ function run(args, opts, cb) { die('Not a Mojito directory'); } - context = typeof opts.context === 'string' ? csvctx(opts.context) : {}; - store = new Y.mojito.ResourceStore({root: CWD, context: context}); - appconf = config(store.getAppConfig(context), buildtype, args[1]); - builddir = appconf.builds[buildtype].buildDir; + // hash a cli context string like 'device:iphone,environment:test' + opts.context = typeof opts.context === 'string' ? csvctx(opts.context) : {}; - function die(err) { - cb(err, null, true); - } + // init resource store + store = new Y.mojito.ResourceStore({root: CWD, context: opts.context}); + store.preload(); + + // normalize inputs + conf = getConfigs(opts, buildtype, args[1], store); function beforeWrite(file, str) { + var filelist = path.join(conf.build.dir, 'filelist.txt'); writer.write(file, str, false); - writer.write(path.join(builddir, 'filelist.txt'), file + '\n', true); - } - - function makeSnapshot(packages) { - var data = { - "name": opts.snapshotName || 'missing -n ', - "tag": opts.snapshotTag || 'missing -t ', - "packages": packages - }, - str = JSON.stringify(data, null, 4); - - beforeWrite(builddir + '/snapshot.json', str); + writer.write(filelist, file + '\n', true); } function next(err) { if (err) { - return die('Error removing ' + builddir + "\n" + err); + return die('Error removing ' + conf.build.dir + "\n" + err); } - var Build = require('./' + buildtype), - build = new Build(appconf, buildtype, context, store); + var Mojito = require(BASE + 'lib/mojito'), + Scraper = require('./scraper'), + Builder = require('./' + buildtype); - build.on('snapshot', makeSnapshot) + new Builder() .on('write', beforeWrite) .on('copy', writer.copy) .on('error', die) .on('warn', console.log) .on('info', console.log) .on('done', cb) - .exec(M); + .exec(conf, store, new Scraper(Mojito)); } - return opts.replace ? writer.rmrf(builddir, next) : next(); + return opts.replace ? writer.rmrf(conf.build.dir, next) : next(); } module.exports = { From fb984869b5e778176d0267ed6a0d2ce7c6b72213 Mon Sep 17 00:00:00 2001 From: Isao Yagi Date: Wed, 17 Oct 2012 19:17:22 -0700 Subject: [PATCH 06/24] mk html5app simple js, not a Stream/EventEmmitter --- lib/app/commands/build/html5app.js | 95 ++++++++++++------------------ lib/app/commands/build/index.js | 22 ++----- 2 files changed, 42 insertions(+), 75 deletions(-) diff --git a/lib/app/commands/build/html5app.js b/lib/app/commands/build/html5app.js index 0b593fa86..121b2cbea 100644 --- a/lib/app/commands/build/html5app.js +++ b/lib/app/commands/build/html5app.js @@ -1,8 +1,7 @@ /*jslint sloppy:true, stupid:true, node:true */ -var qs = require('querystring'), - path = require('path'), - Stream = require('stream'), +var path = require('path'), + fw, // ./writer.js module containing static filesystem functions EXTNS = /\.(css|js|json)$/i, PORT = 1111; @@ -16,61 +15,51 @@ function mapStoreUris(buildmap, conf, storemap) { } else { from = storemap[uri]; // source mojito app filesystem path to = path.join(conf.build.dir, uri); - this.emit('copy', from, to); + fw.copy(from, to); } - }.bind(this)); + }); +} + +// add /specs/default(?).json tunnel uris to buildmap +function mapSpecUris(buildmap, conf, name, store) { + var opts = {type: 'spec', mojit: name}, + specs = store.getResources('client', conf.context, opts); + + Object.keys(specs).forEach(function (spec) { + buildmap[conf.tunnelpf + spec.url + conf.contextqs] = spec.url; + }); } // add /definition.json tunnel uris to buildmap function mapDefxUris(buildmap, conf, store) { - var opts = {type: 'mojit'}, - mojits = store.getResources('client', conf.context, opts); + var mojits = store.getResources('client', conf.context, {type: 'mojit'}); mojits.forEach(function (mojit) { var uri = mojit.url + '/definition.json'; - buildmap[conf.tunnelpf + uri + conf.contextqs] = uri; - this.mapSpecUris(buildmap, conf, mojit.name, store); - }.bind(this)); -} - -// add /specs/default(?).json tunnel uris to buildmap -function mapSpecUris(buildmap, conf, name, store) { - var opts = {type: 'spec', mojit: name}, - specs = store.getResources('client', conf.context, opts), - tunnelpf = conf.tunnelpf, - qs = conf.contextqs; - - Object.keys(specs).forEach(function (spec) { - if (spec.url) { - buildmap[tunnelpf + spec.url + qs] = spec.url; - } + mapSpecUris(buildmap, conf, mojit.name, store); }); } // add funky mojit spec uris to buildmap // i.e. "foo:bar" -> /specs/bar.json function mapFunkySpecUris(buildmap, conf) { - var staticpf = conf.staticpf, - tunnelpf = conf.tunnelpf, - qs = conf.contextqs; - Object.keys(conf.app.specs).forEach(function (id) { var parts = id.split(':'), type = parts[0], - name = parts[1] || 'default', - uri = [null, staticpf, type, 'specs', name + '.json'].join('/'); + name = (parts[1] || 'default') + '.json', + uri = [null, conf.staticpf, type, 'specs', name].join('/'); - buildmap[tunnelpf + uri + qs] = uri; + buildmap[conf.tunnelpf + uri + conf.contextqs] = uri; }); } function makeManifest(uris, dir, stamp) { - var lines = ['CACHE MANIFEST', '# ' + (stamp || new Date()), 'CACHE:']; + var lines = ['CACHE MANIFEST', '# ' + stamp, 'CACHE:']; Object.keys(uris).forEach(function (i) { lines.push(uris[i]); }); - this.emit('write', path.join(dir, 'cache.manifest'), lines.join("\n")); + fw.write(path.join(dir, 'cache.manifest'), lines.join("\n")); } function injectManifestAttr(uri, str) { @@ -102,13 +91,12 @@ function mungePage(conf, mapuri, contents) { } } - this.emit('write', file, contents); + fw.write(file, contents); } -function exec(conf, store, scraper) { +function exec(conf, store, cb) { var buildmap = {}; - // init the map of mojito-http to filesystem uris // this is rm'd for crt, but used for docs buildmap['/' + conf.contextqs] = '/index.html'; @@ -118,39 +106,30 @@ function exec(conf, store, scraper) { }); // add uris for mojito and app internals to buildmap - this.mapStoreUris(buildmap, conf, store.getAllURLs() || {}); + mapStoreUris(buildmap, conf, store.getAllURLs() || {}); // add tunnel uris to buildmap - this.mapDefxUris(buildmap, conf, store); + mapDefxUris(buildmap, conf, store); - // add uris to buildmap from legacy mojit ids that have spec file parts - this.mapFunkySpecUris(buildmap, conf); + // add uris from legacy mojit ids that have spec file parts to buildmap + mapFunkySpecUris(buildmap, conf); // create a couple files - this.makeManifest(buildmap, conf.build.dir, null); + makeManifest(buildmap, conf.build.dir, new Date()); // use buildmap to scrape mojito server and save the rendered pages - scraper - .on('scraped-one', this.mungePage.bind(this, conf)) - .on('scraping-done', this.emit.bind(this, 'done')) - .on('error', this.emit.bind(this, 'error')) - .on('warn', this.emit.bind(this, 'warn')) - .on('info', this.emit.bind(this, 'info')) + this.scraper + .on('scraped-one', mungePage.bind(this, conf)) + .on('scraping-done', cb) .start({port: PORT, context: conf.context}) - .fetch(buildmap); + .fetch(buildmap, cb); } +function Html5app(writer, scraper) { + fw = writer; + this.scraper = scraper; +} -function Html5app() {} - -Html5app.prototype = Object.create(Stream.prototype, { - exec: {value: exec}, - mapStoreUris: {value: mapStoreUris}, - mapDefxUris: {value: mapDefxUris}, - mapSpecUris: {value: mapSpecUris}, - mapFunkySpecUris: {value: mapFunkySpecUris}, - makeManifest: {value: makeManifest}, - mungePage: {value: mungePage} -}); +Html5app.prototype.exec = exec; module.exports = Html5app; diff --git a/lib/app/commands/build/index.js b/lib/app/commands/build/index.js index a4182b98e..a52129c5b 100644 --- a/lib/app/commands/build/index.js +++ b/lib/app/commands/build/index.js @@ -1,5 +1,5 @@ /*jslint sloppy:true, stupid:true, node:true */ -/*jslint nomen:true */ // node:true doesn't seem to register __dirname +/*jslint nomen:true */ // node:true doesn't seem to register __dirname :( var path = require('path'), qs = require('querystring'), BASE = path.resolve(__dirname, '../../../../') + '/', @@ -104,12 +104,6 @@ function run(args, opts, cb) { // normalize inputs conf = getConfigs(opts, buildtype, args[1], store); - function beforeWrite(file, str) { - var filelist = path.join(conf.build.dir, 'filelist.txt'); - writer.write(file, str, false); - writer.write(filelist, file + '\n', true); - } - function next(err) { if (err) { return die('Error removing ' + conf.build.dir + "\n" + err); @@ -117,16 +111,10 @@ function run(args, opts, cb) { var Mojito = require(BASE + 'lib/mojito'), Scraper = require('./scraper'), - Builder = require('./' + buildtype); - - new Builder() - .on('write', beforeWrite) - .on('copy', writer.copy) - .on('error', die) - .on('warn', console.log) - .on('info', console.log) - .on('done', cb) - .exec(conf, store, new Scraper(Mojito)); + Builder = require('./' + buildtype), + builder = new Builder(writer, new Scraper(Mojito)); + + builder.exec(conf, store, cb); } return opts.replace ? writer.rmrf(conf.build.dir, next) : next(); From 0b46c706a1d685ea6980afd1439bbd776d2bd8d8 Mon Sep 17 00:00:00 2001 From: Isao Yagi Date: Wed, 17 Oct 2012 19:17:28 -0700 Subject: [PATCH 07/24] minor --- lib/app/commands/build/scraper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/app/commands/build/scraper.js b/lib/app/commands/build/scraper.js index 3b0b8e0ee..9de325e3f 100644 --- a/lib/app/commands/build/scraper.js +++ b/lib/app/commands/build/scraper.js @@ -39,7 +39,7 @@ function fetch(buildmap, cb) { have += 1; if (have === need) { me.server.close(); - me.emit('scraping-done'); + me.emit('scraping-done', null, have, failed); } }); }); From 11dfd07b129de497a89f0011d206f8247b73fc41 Mon Sep 17 00:00:00 2001 From: Isao Yagi Date: Wed, 17 Oct 2012 19:30:06 -0700 Subject: [PATCH 08/24] move functions to shared.js module --- lib/app/commands/build/html5app.js | 102 ++------------------------- lib/app/commands/build/shared.js | 107 +++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 95 deletions(-) create mode 100644 lib/app/commands/build/shared.js diff --git a/lib/app/commands/build/html5app.js b/lib/app/commands/build/html5app.js index 121b2cbea..da419ac57 100644 --- a/lib/app/commands/build/html5app.js +++ b/lib/app/commands/build/html5app.js @@ -1,99 +1,11 @@ /*jslint sloppy:true, stupid:true, node:true */ var path = require('path'), - fw, // ./writer.js module containing static filesystem functions + func = require('./shared'), EXTNS = /\.(css|js|json)$/i, PORT = 1111; -// add uris from resource store to buildmap, copy some to build dir -function mapStoreUris(buildmap, conf, storemap) { - Object.keys(storemap).forEach(function (uri) { - var from, to; - if (uri.match(EXTNS)) { - buildmap[uri + conf.contextqs] = uri; - } else { - from = storemap[uri]; // source mojito app filesystem path - to = path.join(conf.build.dir, uri); - fw.copy(from, to); - } - }); -} - -// add /specs/default(?).json tunnel uris to buildmap -function mapSpecUris(buildmap, conf, name, store) { - var opts = {type: 'spec', mojit: name}, - specs = store.getResources('client', conf.context, opts); - - Object.keys(specs).forEach(function (spec) { - buildmap[conf.tunnelpf + spec.url + conf.contextqs] = spec.url; - }); -} - -// add /definition.json tunnel uris to buildmap -function mapDefxUris(buildmap, conf, store) { - var mojits = store.getResources('client', conf.context, {type: 'mojit'}); - - mojits.forEach(function (mojit) { - var uri = mojit.url + '/definition.json'; - buildmap[conf.tunnelpf + uri + conf.contextqs] = uri; - mapSpecUris(buildmap, conf, mojit.name, store); - }); -} - -// add funky mojit spec uris to buildmap -// i.e. "foo:bar" -> /specs/bar.json -function mapFunkySpecUris(buildmap, conf) { - Object.keys(conf.app.specs).forEach(function (id) { - var parts = id.split(':'), - type = parts[0], - name = (parts[1] || 'default') + '.json', - uri = [null, conf.staticpf, type, 'specs', name].join('/'); - - buildmap[conf.tunnelpf + uri + conf.contextqs] = uri; - }); -} - -function makeManifest(uris, dir, stamp) { - var lines = ['CACHE MANIFEST', '# ' + stamp, 'CACHE:']; - Object.keys(uris).forEach(function (i) { - lines.push(uris[i]); - }); - fw.write(path.join(dir, 'cache.manifest'), lines.join("\n")); -} - -function injectManifestAttr(uri, str) { - var rel = path.relative(path.dirname(uri), '/cache.manifest'), - rgx = /(/i; - return str.replace(rgx, '$1 manifest="' + rel + '">'); -} - -function forceRelativePaths(uri, str) { - var rgx = /(src|href)="(\/\S+?)"/g, - dir = path.dirname(uri); - /*jslint unparam: true */ - return str.replace(rgx, function (matched, tag, val) { - return tag + '="' + path.relative(dir, val) + '"'; - }); -} - -// post-process .html file contents -function mungePage(conf, mapuri, contents) { - var uri = mapuri === '/' ? '/index.html' : mapuri, - file = path.join(conf.build.dir, uri); - - if (/\.html$/i.test(uri)) { - if (conf.build.attachManifest) { - contents = injectManifestAttr(uri, contents); - } - if (conf.build.forceRelativePaths) { - contents = forceRelativePaths(uri, contents); - } - } - - fw.write(file, contents); -} - function exec(conf, store, cb) { var buildmap = {}; @@ -106,27 +18,27 @@ function exec(conf, store, cb) { }); // add uris for mojito and app internals to buildmap - mapStoreUris(buildmap, conf, store.getAllURLs() || {}); + func.mapStoreUris(buildmap, conf, store.getAllURLs() || {}); // add tunnel uris to buildmap - mapDefxUris(buildmap, conf, store); + func.mapDefxUris(buildmap, conf, store); // add uris from legacy mojit ids that have spec file parts to buildmap - mapFunkySpecUris(buildmap, conf); + func.mapFunkySpecUris(buildmap, conf); // create a couple files - makeManifest(buildmap, conf.build.dir, new Date()); + func.makeManifest(buildmap, conf.build.dir, new Date()); // use buildmap to scrape mojito server and save the rendered pages this.scraper - .on('scraped-one', mungePage.bind(this, conf)) + .on('scraped-one', func.mungePage.bind(this, conf)) .on('scraping-done', cb) .start({port: PORT, context: conf.context}) .fetch(buildmap, cb); } function Html5app(writer, scraper) { - fw = writer; + func.init(writer); this.scraper = scraper; } diff --git a/lib/app/commands/build/shared.js b/lib/app/commands/build/shared.js new file mode 100644 index 000000000..8541d855d --- /dev/null +++ b/lib/app/commands/build/shared.js @@ -0,0 +1,107 @@ +/*jslint sloppy:true, stupid:true, node:true */ + +var path = require('path'), + fw, // ./writer.js module containing static filesystem functions + EXTNS = /\.(css|js|json)$/i, + PORT = 1111; + +function init(writer) { + fw = writer; +} + +// add uris from resource store to buildmap, copy some to build dir +function mapStoreUris(buildmap, conf, storemap) { + Object.keys(storemap).forEach(function (uri) { + var from, to; + if (uri.match(EXTNS)) { + buildmap[uri + conf.contextqs] = uri; + } else { + from = storemap[uri]; // source mojito app filesystem path + to = path.join(conf.build.dir, uri); + fw.copy(from, to); + } + }); +} + +// add /specs/default(?).json tunnel uris to buildmap +function mapSpecUris(buildmap, conf, name, store) { + var opts = {type: 'spec', mojit: name}, + specs = store.getResources('client', conf.context, opts); + + Object.keys(specs).forEach(function (spec) { + buildmap[conf.tunnelpf + spec.url + conf.contextqs] = spec.url; + }); +} + +// add /definition.json tunnel uris to buildmap +function mapDefxUris(buildmap, conf, store) { + var mojits = store.getResources('client', conf.context, {type: 'mojit'}); + + mojits.forEach(function (mojit) { + var uri = mojit.url + '/definition.json'; + buildmap[conf.tunnelpf + uri + conf.contextqs] = uri; + mapSpecUris(buildmap, conf, mojit.name, store); + }); +} + +// add funky mojit spec uris to buildmap +// i.e. "foo:bar" -> /specs/bar.json +function mapFunkySpecUris(buildmap, conf) { + Object.keys(conf.app.specs).forEach(function (id) { + var parts = id.split(':'), + type = parts[0], + name = (parts[1] || 'default') + '.json', + uri = [null, conf.staticpf, type, 'specs', name].join('/'); + + buildmap[conf.tunnelpf + uri + conf.contextqs] = uri; + }); +} + +function makeManifest(uris, dir, stamp) { + var lines = ['CACHE MANIFEST', '# ' + stamp, 'CACHE:']; + Object.keys(uris).forEach(function (i) { + lines.push(uris[i]); + }); + fw.write(path.join(dir, 'cache.manifest'), lines.join("\n")); +} + +function injectManifestAttr(uri, str) { + var rel = path.relative(path.dirname(uri), '/cache.manifest'), + rgx = /(/i; + return str.replace(rgx, '$1 manifest="' + rel + '">'); +} + +function forceRelativePaths(uri, str) { + var rgx = /(src|href)="(\/\S+?)"/g, + dir = path.dirname(uri); + /*jslint unparam: true */ + return str.replace(rgx, function (matched, tag, val) { + return tag + '="' + path.relative(dir, val) + '"'; + }); +} + +// post-process .html file contents +function mungePage(conf, mapuri, contents) { + var uri = mapuri === '/' ? '/index.html' : mapuri, + file = path.join(conf.build.dir, uri); + + if (/\.html$/i.test(uri)) { + if (conf.build.attachManifest) { + contents = injectManifestAttr(uri, contents); + } + if (conf.build.forceRelativePaths) { + contents = forceRelativePaths(uri, contents); + } + } + + fw.write(file, contents); +} + +module.exports = { + init: init, + mapStoreUris: mapStoreUris, + mapDefxUris: mapDefxUris, + mapFunkySpecUris: mapFunkySpecUris, + makeManifest: makeManifest, + mungePage: mungePage +}; From 8b2dab015f5cdfd40333a1e71c3760fd02eb6383 Mon Sep 17 00:00:00 2001 From: Isao Yagi Date: Wed, 17 Oct 2012 19:39:57 -0700 Subject: [PATCH 09/24] parameterize port --- lib/app/commands/build/html5app.js | 6 ++---- lib/app/commands/build/index.js | 6 ++++++ lib/app/commands/build/shared.js | 10 +++++----- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/app/commands/build/html5app.js b/lib/app/commands/build/html5app.js index da419ac57..cd44c0455 100644 --- a/lib/app/commands/build/html5app.js +++ b/lib/app/commands/build/html5app.js @@ -1,9 +1,7 @@ /*jslint sloppy:true, stupid:true, node:true */ var path = require('path'), - func = require('./shared'), - EXTNS = /\.(css|js|json)$/i, - PORT = 1111; + func = require('./shared'); function exec(conf, store, cb) { @@ -33,7 +31,7 @@ function exec(conf, store, cb) { this.scraper .on('scraped-one', func.mungePage.bind(this, conf)) .on('scraping-done', cb) - .start({port: PORT, context: conf.context}) + .start({port: conf.build.port, context: conf.context}) .fetch(buildmap, cb); } diff --git a/lib/app/commands/build/index.js b/lib/app/commands/build/index.js index a52129c5b..51db7a15a 100644 --- a/lib/app/commands/build/index.js +++ b/lib/app/commands/build/index.js @@ -1,11 +1,15 @@ /*jslint sloppy:true, stupid:true, node:true */ /*jslint nomen:true */ // node:true doesn't seem to register __dirname :( + var path = require('path'), qs = require('querystring'), + BASE = path.resolve(__dirname, '../../../../') + '/', CWD = process.cwd(), + writer = require('./writer'), util = require(BASE + 'lib/management/utils'), + Y = require('./yuiuse')({ 'escape': null, 'json-parse': null, @@ -53,6 +57,7 @@ function getConfigs(opts, buildtype, builddir, store) { build: { attachManifest: dotbuild.attachManifest, forceRelativePaths: dotbuild.forceRelativePaths, + port: opts.port || 1111, dir: dotbuild.buildDir, type: buildtype, uris: dotbuild.urls || [] @@ -125,6 +130,7 @@ module.exports = { options: [ {shortName: 'n', longName: 'snapshotName', hasValue: true}, {shortName: 't', longName: 'snapshotTag', hasValue: true}, + {shortName: 'p', longName: 'port', hasValue: true}, {shortName: 'r', longName: 'replace', hasValue: false}, {shortName: 'c', longName: 'context', hasValue: true}, {shortName: 'm', longName: 'mojit', hasValue: true} diff --git a/lib/app/commands/build/shared.js b/lib/app/commands/build/shared.js index 8541d855d..d978cc9b8 100644 --- a/lib/app/commands/build/shared.js +++ b/lib/app/commands/build/shared.js @@ -1,12 +1,12 @@ /*jslint sloppy:true, stupid:true, node:true */ -var path = require('path'), - fw, // ./writer.js module containing static filesystem functions - EXTNS = /\.(css|js|json)$/i, - PORT = 1111; +var fw, + path = require('path'), + EXTNS = /\.(css|js|json)$/i; + function init(writer) { - fw = writer; + fw = writer; // module ./writer.js containing static filesystem functions } // add uris from resource store to buildmap, copy some to build dir From 2c81b99d2e57848f766248ffc0f00e8dca3cf1a7 Mon Sep 17 00:00:00 2001 From: Isao Yagi Date: Wed, 17 Oct 2012 20:28:11 -0700 Subject: [PATCH 10/24] minor --- lib/app/commands/build/html5app.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/app/commands/build/html5app.js b/lib/app/commands/build/html5app.js index cd44c0455..3efe14471 100644 --- a/lib/app/commands/build/html5app.js +++ b/lib/app/commands/build/html5app.js @@ -1,7 +1,7 @@ /*jslint sloppy:true, stupid:true, node:true */ var path = require('path'), - func = require('./shared'); + fn = require('./shared'); function exec(conf, store, cb) { @@ -16,27 +16,27 @@ function exec(conf, store, cb) { }); // add uris for mojito and app internals to buildmap - func.mapStoreUris(buildmap, conf, store.getAllURLs() || {}); + fn.mapStoreUris(buildmap, conf, store.getAllURLs() || {}); // add tunnel uris to buildmap - func.mapDefxUris(buildmap, conf, store); + fn.mapDefxUris(buildmap, conf, store); // add uris from legacy mojit ids that have spec file parts to buildmap - func.mapFunkySpecUris(buildmap, conf); + fn.mapFunkySpecUris(buildmap, conf); // create a couple files - func.makeManifest(buildmap, conf.build.dir, new Date()); + fn.makeManifest(buildmap, conf.build.dir, new Date()); // use buildmap to scrape mojito server and save the rendered pages this.scraper - .on('scraped-one', func.mungePage.bind(this, conf)) + .on('scraped-one', fn.mungePage.bind(this, conf)) .on('scraping-done', cb) .start({port: conf.build.port, context: conf.context}) .fetch(buildmap, cb); } function Html5app(writer, scraper) { - func.init(writer); + fn.init(writer); this.scraper = scraper; } From da8bcc4d0c33613a725815eb57f06f5f7ea223c4 Mon Sep 17 00:00:00 2001 From: Isao Yagi Date: Wed, 17 Oct 2012 20:28:56 -0700 Subject: [PATCH 11/24] use rs package info instead of app info --- lib/app/commands/build/index.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/app/commands/build/index.js b/lib/app/commands/build/index.js index 51db7a15a..b101b8b4c 100644 --- a/lib/app/commands/build/index.js +++ b/lib/app/commands/build/index.js @@ -21,7 +21,7 @@ var path = require('path'), function getConfigs(opts, buildtype, builddir, store) { var appconf = store.getAppConfig(opts.context) || {/* no app.json? */}, - pkgmeta = store.getResourceVersions({name: 'application'})[0].source.pkg, + pkgmeta = store.getResourceVersions({name: 'package'})[0].source, contextqs = qs.stringify(opts.context), // context as uri query string dotbuild; // shortcut to appconf.builds[buildtype] @@ -46,9 +46,10 @@ function getConfigs(opts, buildtype, builddir, store) { // ok, we should have all the inputs we need to proceed with any build return { app: { - name: pkgmeta.name, - version: pkgmeta.version, - specs: appconf.specs || {} + name: pkgmeta.pkg.name, + version: pkgmeta.pkg.version, + specs: appconf.specs || {}, + dir: pkgmeta.fs.rootDir // should be same as CWD }, snapshot: { name: opts.snapshotName || 'missing --snapshotName', @@ -128,12 +129,12 @@ function run(args, opts, cb) { module.exports = { run: run, options: [ + {shortName: 'c', longName: 'context', hasValue: true}, + {shortName: 'm', longName: 'mojit', hasValue: true}, + {shortName: 'r', longName: 'replace', hasValue: false}, {shortName: 'n', longName: 'snapshotName', hasValue: true}, {shortName: 't', longName: 'snapshotTag', hasValue: true}, - {shortName: 'p', longName: 'port', hasValue: true}, - {shortName: 'r', longName: 'replace', hasValue: false}, - {shortName: 'c', longName: 'context', hasValue: true}, - {shortName: 'm', longName: 'mojit', hasValue: true} + {shortName: 'p', longName: 'port', hasValue: true} ], usage: [ 'mojito build {type} [destination]', From f2a80b6404e1b7be4d126f4e816bd53d1f7a97e9 Mon Sep 17 00:00:00 2001 From: Isao Yagi Date: Wed, 17 Oct 2012 21:04:55 -0700 Subject: [PATCH 12/24] rename --- lib/app/commands/build/shared.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/app/commands/build/shared.js b/lib/app/commands/build/shared.js index d978cc9b8..66be9a3fa 100644 --- a/lib/app/commands/build/shared.js +++ b/lib/app/commands/build/shared.js @@ -1,12 +1,12 @@ /*jslint sloppy:true, stupid:true, node:true */ -var fw, +var fl, path = require('path'), EXTNS = /\.(css|js|json)$/i; function init(writer) { - fw = writer; // module ./writer.js containing static filesystem functions + fl = writer; // module ./writer.js containing static filesystem functions } // add uris from resource store to buildmap, copy some to build dir @@ -18,7 +18,7 @@ function mapStoreUris(buildmap, conf, storemap) { } else { from = storemap[uri]; // source mojito app filesystem path to = path.join(conf.build.dir, uri); - fw.copy(from, to); + fl.copy(from, to); } }); } @@ -33,12 +33,13 @@ function mapSpecUris(buildmap, conf, name, store) { }); } -// add /definition.json tunnel uris to buildmap +// add /tunnel/*/definition.json uris to buildmap function mapDefxUris(buildmap, conf, store) { var mojits = store.getResources('client', conf.context, {type: 'mojit'}); mojits.forEach(function (mojit) { var uri = mojit.url + '/definition.json'; + buildmap[conf.tunnelpf + uri + conf.contextqs] = uri; mapSpecUris(buildmap, conf, mojit.name, store); }); @@ -62,7 +63,7 @@ function makeManifest(uris, dir, stamp) { Object.keys(uris).forEach(function (i) { lines.push(uris[i]); }); - fw.write(path.join(dir, 'cache.manifest'), lines.join("\n")); + fl.write(path.join(dir, 'cache.manifest'), lines.join("\n")); } function injectManifestAttr(uri, str) { @@ -94,7 +95,7 @@ function mungePage(conf, mapuri, contents) { } } - fw.write(file, contents); + fl.write(file, contents); } module.exports = { From d9796f873c60ed682a099fd29e124328c7667e8b Mon Sep 17 00:00:00 2001 From: Isao Yagi Date: Wed, 17 Oct 2012 21:27:52 -0700 Subject: [PATCH 13/24] hybridapp.js, same as html5app.js except: - make snapshot.json - copy package/json to /static/package.json - no /index.html, /cache.manifest --- lib/app/commands/build/hybridapp.js | 68 +++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 lib/app/commands/build/hybridapp.js diff --git a/lib/app/commands/build/hybridapp.js b/lib/app/commands/build/hybridapp.js new file mode 100644 index 000000000..29387be06 --- /dev/null +++ b/lib/app/commands/build/hybridapp.js @@ -0,0 +1,68 @@ +/*jslint sloppy:true, stupid:true, node:true */ + +var path = require('path'), + fn = require('./shared'), + fl, + SNAP_FILENAME = 'snapshot.json'; + + +function copyPackageJson(from, to) { + var name = 'package.json'; + fl.copy(path.join(from, name), path.join(to, name)); +} + +function makeSnapshot(conf) { + var file = path.join(conf.build.dir, SNAP_FILENAME), + snap = { + 'name': conf.snapshot.name, + 'tag': conf.snapshot.tag, + 'packages': {'yahoo.libs.yui': '*' /* hardcoded for now */} + }; + + snap.packages[conf.app.name] = conf.app.version; + fl.write(file, JSON.stringify(snap, null, 4)); +} + +function exec(conf, store, cb) { + var buildmap = {}; + + // this is rm'd for crt, but used for docs + buildmap['/' + conf.contextqs] = '/index.html'; + + // add uris enumerated in application.json + conf.build.uris.forEach(function (uri) { + buildmap[uri + conf.contextqs] = uri; + }); + + // add uris for mojito and app internals to buildmap + fn.mapStoreUris(buildmap, conf, store.getAllURLs() || {}); + + // add tunnel uris to buildmap + fn.mapDefxUris(buildmap, conf, store); + + // add uris from legacy mojit ids that have spec file parts to buildmap + fn.mapFunkySpecUris(buildmap, conf); + + // copy mojito app's package.json to path/to/builddir// + copyPackageJson(conf.app.dir, path.join(conf.build.dir, conf.staticpf)); + + // make a snapshot file for the CRT updater + makeSnapshot(conf); + + // use buildmap to scrape mojito server and save the rendered pages + this.scraper + .on('scraped-one', fn.mungePage.bind(this, conf)) + .on('scraping-done', cb) + .start({port: conf.build.port, context: conf.context}) + .fetch(buildmap, cb); +} + +function Hybridapp(writer, scraper) { + fl = writer; + fn.init(writer); + this.scraper = scraper; +} + +Hybridapp.prototype.exec = exec; + +module.exports = Hybridapp; From ee650c325fea7b2dd684a137c19db5f5440f732b Mon Sep 17 00:00:00 2001 From: Isao Yagi Date: Wed, 17 Oct 2012 21:28:34 -0700 Subject: [PATCH 14/24] hybrid archetype: build.hybridapp (not html5aqpp) --- lib/app/archetypes/app/hybrid/application.json.hb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/app/archetypes/app/hybrid/application.json.hb b/lib/app/archetypes/app/hybrid/application.json.hb index 0bd0c9159..ead8c60b0 100644 --- a/lib/app/archetypes/app/hybrid/application.json.hb +++ b/lib/app/archetypes/app/hybrid/application.json.hb @@ -16,7 +16,7 @@ }, "builds": { - "html5app": { + "hybridapp": { "attachManifest": true, "forceRelativePaths": true, "urls": ["/yahoo.application.{{name}}/index.html"] From 563719da81aa30318567f33b68ab10fad9d5ab91 Mon Sep 17 00:00:00 2001 From: Isao Yagi Date: Wed, 17 Oct 2012 21:28:53 -0700 Subject: [PATCH 15/24] minor --- lib/app/commands/build/hybridapp.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/app/commands/build/hybridapp.js b/lib/app/commands/build/hybridapp.js index 29387be06..f61c77be2 100644 --- a/lib/app/commands/build/hybridapp.js +++ b/lib/app/commands/build/hybridapp.js @@ -2,9 +2,7 @@ var path = require('path'), fn = require('./shared'), - fl, - SNAP_FILENAME = 'snapshot.json'; - + fl; function copyPackageJson(from, to) { var name = 'package.json'; @@ -12,7 +10,7 @@ function copyPackageJson(from, to) { } function makeSnapshot(conf) { - var file = path.join(conf.build.dir, SNAP_FILENAME), + var file = path.join(conf.build.dir, 'snapshot.json'), snap = { 'name': conf.snapshot.name, 'tag': conf.snapshot.tag, @@ -26,9 +24,6 @@ function makeSnapshot(conf) { function exec(conf, store, cb) { var buildmap = {}; - // this is rm'd for crt, but used for docs - buildmap['/' + conf.contextqs] = '/index.html'; - // add uris enumerated in application.json conf.build.uris.forEach(function (uri) { buildmap[uri + conf.contextqs] = uri; From 30da8d61242a450db6d887030bb32de76952ff20 Mon Sep 17 00:00:00 2001 From: Isao Yagi Date: Wed, 17 Oct 2012 21:51:13 -0700 Subject: [PATCH 16/24] hybrid archetype rm css --- .../archetypes/app/hybrid/mojits/top_frame/assets/index.css | 2 -- .../app/hybrid/mojits/top_frame/specs/default.json.hb | 3 --- 2 files changed, 5 deletions(-) delete mode 100644 lib/app/archetypes/app/hybrid/mojits/top_frame/assets/index.css diff --git a/lib/app/archetypes/app/hybrid/mojits/top_frame/assets/index.css b/lib/app/archetypes/app/hybrid/mojits/top_frame/assets/index.css deleted file mode 100644 index 1cd511473..000000000 --- a/lib/app/archetypes/app/hybrid/mojits/top_frame/assets/index.css +++ /dev/null @@ -1,2 +0,0 @@ -dt { font-weight: bold; } -.sel { background-color: #FF4; } diff --git a/lib/app/archetypes/app/hybrid/mojits/top_frame/specs/default.json.hb b/lib/app/archetypes/app/hybrid/mojits/top_frame/specs/default.json.hb index 0488c5073..591b55f19 100644 --- a/lib/app/archetypes/app/hybrid/mojits/top_frame/specs/default.json.hb +++ b/lib/app/archetypes/app/hybrid/mojits/top_frame/specs/default.json.hb @@ -10,9 +10,6 @@ "type": "top_frame" }, "assets": { - "top": { - "css": ["./index.css"] - }, "bottom": { "js": ["/yahoo.ychrome.lib/yui-cfg.js"] } From b75e912a0d5aa7094b05e2a229df213d927142df Mon Sep 17 00:00:00 2001 From: Isao Yagi Date: Wed, 17 Oct 2012 22:15:07 -0700 Subject: [PATCH 17/24] yui.dependencyCalculation: precomputed+ondemand --- lib/app/archetypes/app/hybrid/application.json.hb | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/lib/app/archetypes/app/hybrid/application.json.hb b/lib/app/archetypes/app/hybrid/application.json.hb index ead8c60b0..172d20021 100644 --- a/lib/app/archetypes/app/hybrid/application.json.hb +++ b/lib/app/archetypes/app/hybrid/application.json.hb @@ -17,20 +17,12 @@ "builds": { "hybridapp": { - "attachManifest": true, - "forceRelativePaths": true, "urls": ["/yahoo.application.{{name}}/index.html"] } }, "yui": { - "dependencyCalculations": "precomputed" - } - }, - - { - "settings": [ "environment:dev" ], - "yui": { + "dependencyCalculations": "precomputed+ondemand", "base": "/yahoo.libs.yui/", "url": "$$yui.base$$yui/yui-min.js", "loader": "loader/loader-min.js" From 8514abf4f713d82fdd598f94519c91efbff9bc30 Mon Sep 17 00:00:00 2001 From: Isao Yagi Date: Thu, 18 Oct 2012 09:29:16 -0700 Subject: [PATCH 18/24] add "yahoo.libs.yui": "*" --- lib/app/archetypes/app/hybrid/package.json.hb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/app/archetypes/app/hybrid/package.json.hb b/lib/app/archetypes/app/hybrid/package.json.hb index e9000c52e..3be069124 100644 --- a/lib/app/archetypes/app/hybrid/package.json.hb +++ b/lib/app/archetypes/app/hybrid/package.json.hb @@ -6,7 +6,8 @@ "type": "application", "pkg_type": "application", "dependencies": { - "mojito": ">0.4" + "mojito": ">0.4", + "yahoo.libs.yui": "*" }, "engines": { "node": ">0.6", From ce61a685fc19928dbfbf515400de0f85103de9ce Mon Sep 17 00:00:00 2001 From: Isao Yagi Date: Thu, 18 Oct 2012 12:58:28 -0700 Subject: [PATCH 19/24] rm trailing whitespace --- lib/app/commands/build/shared.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/app/commands/build/shared.js b/lib/app/commands/build/shared.js index 66be9a3fa..fd5feb511 100644 --- a/lib/app/commands/build/shared.js +++ b/lib/app/commands/build/shared.js @@ -6,7 +6,7 @@ var fl, function init(writer) { - fl = writer; // module ./writer.js containing static filesystem functions + fl = writer; // module ./writer.js containing static filesystem functions } // add uris from resource store to buildmap, copy some to build dir @@ -104,5 +104,5 @@ module.exports = { mapDefxUris: mapDefxUris, mapFunkySpecUris: mapFunkySpecUris, makeManifest: makeManifest, - mungePage: mungePage + mungePage: mungePage }; From 98e1c49291a3911990d4ba845a5e2c6f7a89a6b1 Mon Sep 17 00:00:00 2001 From: Isao Yagi Date: Thu, 18 Oct 2012 19:06:59 -0700 Subject: [PATCH 20/24] fix path for create mojit.. --- lib/app/commands/create.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/app/commands/create.js b/lib/app/commands/create.js index 0370eff76..376ca02db 100644 --- a/lib/app/commands/create.js +++ b/lib/app/commands/create.js @@ -125,7 +125,7 @@ function run(params, options, callback) { destdir = path.resolve(process.cwd(), name); break; case 'mojit': - destdir = path.resolve(process.cwd(), name, 'mojits'); + destdir = path.resolve(process.cwd(), 'mojits', name); break; default: return utils.error('Incorrect type "' + type + From 209b6ef957747a7cb300810ac839e0bfe09eaef1 Mon Sep 17 00:00:00 2001 From: Isao Yagi Date: Fri, 19 Oct 2012 10:52:52 -0700 Subject: [PATCH 21/24] add read(), writeJson() remove append flag from write --- lib/app/commands/build/writer.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/app/commands/build/writer.js b/lib/app/commands/build/writer.js index c15f22d30..9ec43d46b 100644 --- a/lib/app/commands/build/writer.js +++ b/lib/app/commands/build/writer.js @@ -4,9 +4,18 @@ var fs = require('fs'), rimraf = require('rimraf'), mkdirp = require('mkdirp'); -function write(filepath, str, append) { + +function read(from) { + return fs.readFileSync(from, 'utf-8'); +} + +function write(filepath, str) { mkdirp.sync(path.dirname(filepath)); - fs[append ? 'appendFileSync' : 'writeFileSync'](filepath, str, 'utf-8'); + fs.writeFileSync(filepath, str, 'utf-8'); +} + +function writeJson(filepath, str) { + write(filepath, JSON.stringify(str, null, 4)); } function copy(from, to) { @@ -18,7 +27,9 @@ function rmrf(dir, cb) { } module.exports = { + read: read, write: write, + writeJson: writeJson, copy: copy, rmrf: rmrf }; From 90fb0eb8b2acf31b3bf2ae50044f1db13e49e3d6 Mon Sep 17 00:00:00 2001 From: Isao Yagi Date: Fri, 19 Oct 2012 10:53:36 -0700 Subject: [PATCH 22/24] rm dependencies.mojito from build's package.json --- lib/app/commands/build/hybridapp.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/app/commands/build/hybridapp.js b/lib/app/commands/build/hybridapp.js index f61c77be2..49afe22a9 100644 --- a/lib/app/commands/build/hybridapp.js +++ b/lib/app/commands/build/hybridapp.js @@ -4,9 +4,16 @@ var path = require('path'), fn = require('./shared'), fl; -function copyPackageJson(from, to) { - var name = 'package.json'; - fl.copy(path.join(from, name), path.join(to, name)); +function mungePackageJson(source_json, to) { + var name = 'package.json', + file = path.join(source_json, name), + data = JSON.parse(fl.read(file)); + + if (data.dependencies && data.dependencies.hasOwnProperty('mojito')) { + delete data.dependencies.mojito; + } + + fl.writeJson(path.join(to, name), data); } function makeSnapshot(conf) { @@ -18,7 +25,7 @@ function makeSnapshot(conf) { }; snap.packages[conf.app.name] = conf.app.version; - fl.write(file, JSON.stringify(snap, null, 4)); + fl.writeJson(file, snap); } function exec(conf, store, cb) { @@ -39,7 +46,7 @@ function exec(conf, store, cb) { fn.mapFunkySpecUris(buildmap, conf); // copy mojito app's package.json to path/to/builddir// - copyPackageJson(conf.app.dir, path.join(conf.build.dir, conf.staticpf)); + mungePackageJson(conf.app.dir, path.join(conf.build.dir, conf.staticpf)); // make a snapshot file for the CRT updater makeSnapshot(conf); From 1a5196c265ecaca7a071c7a4796c85788625cf2d Mon Sep 17 00:00:00 2001 From: Isao Yagi Date: Mon, 22 Oct 2012 09:05:48 -0700 Subject: [PATCH 23/24] s/ychrome/crt/ Thanks Haibiao Chen for the fix --- .../app/hybrid/mojits/top_frame/specs/default.json.hb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/app/archetypes/app/hybrid/mojits/top_frame/specs/default.json.hb b/lib/app/archetypes/app/hybrid/mojits/top_frame/specs/default.json.hb index 591b55f19..c0701228d 100644 --- a/lib/app/archetypes/app/hybrid/mojits/top_frame/specs/default.json.hb +++ b/lib/app/archetypes/app/hybrid/mojits/top_frame/specs/default.json.hb @@ -11,7 +11,7 @@ }, "assets": { "bottom": { - "js": ["/yahoo.ychrome.lib/yui-cfg.js"] + "js": ["/yahoo.crt.lib/yui-cfg.js"] } } } From 08d9a71d5fa8affbff957d8d892e7a4595ee3512 Mon Sep 17 00:00:00 2001 From: Isao Yagi Date: Mon, 22 Oct 2012 15:15:50 -0700 Subject: [PATCH 24/24] make hybrid archetype view template look like the create-mojit-default one per orig docs --- .../app/hybrid/mojits/top_frame/views/index.hb.html | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/app/archetypes/app/hybrid/mojits/top_frame/views/index.hb.html b/lib/app/archetypes/app/hybrid/mojits/top_frame/views/index.hb.html index 66aae63a2..947e77261 100644 --- a/lib/app/archetypes/app/hybrid/mojits/top_frame/views/index.hb.html +++ b/lib/app/archetypes/app/hybrid/mojits/top_frame/views/index.hb.html @@ -1,3 +1,10 @@
-

Hello Template

-
\ No newline at end of file +
+
status
+
{{status}}
+
data
+
+ some: {{#data}}{{some}}{{/data}} +
+
+