1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | "use strict";
|
9 |
|
10 | const fs = require("fs");
|
11 | const path = require("path");
|
12 | const assert = require("assert");
|
13 | const ugly = require("uglify-js");
|
14 | const less = require("less");
|
15 | const svgo = require("svgo");
|
16 | const util = require("./util");
|
17 |
|
18 | const getBuildName = function(next) {
|
19 | var basePath = this.urlBase.split(path.sep);
|
20 | if(basePath.length) {
|
21 | this.build = basePath.pop();
|
22 |
|
23 |
|
24 | if(!this.runtime.apps.hasOwnProperty(this.build)) this.runtime.apps[this.build] = {};
|
25 |
|
26 | next();
|
27 | } else {
|
28 | console.error("getBuildName: build name unknown");
|
29 | this.res.status(500).send("Build name unknown");
|
30 | }
|
31 | };
|
32 |
|
33 | const loadApplicationConfiguration = function(next) {
|
34 | this.appRoot = path.join(
|
35 | this.runtime.conf.root,
|
36 | this.runtime.conf.appsRoot,
|
37 | this.build + "/"
|
38 | );
|
39 |
|
40 | var appConfFile = this.appRoot + "app.json";
|
41 |
|
42 |
|
43 | util.modified(appConfFile, this.build).then((modified) => {
|
44 | if(modified) {
|
45 | util.localJson(appConfFile).then((confData) => {
|
46 | this.appConf = confData;
|
47 | this.appConf.modified = modified;
|
48 | this.runtime.apps[this.build].appConf = this.appConf;
|
49 | next();
|
50 | }, () => {
|
51 | console.error("loadApplicationConfiguration", appConfFile);
|
52 | this.res.status(500).send("Error: " + this.build);
|
53 | });
|
54 | }
|
55 |
|
56 | else {
|
57 | this.appConf = this.runtime.apps[this.build].appConf;
|
58 | this.appConf.modified = false;
|
59 | next();
|
60 | }
|
61 | }, (err) => {
|
62 | console.error("loadApplicationConfiguration", err);
|
63 | this.res.status(500).send("Error: " + this.build);
|
64 | });
|
65 | };
|
66 |
|
67 | const setStaticRoot = function(next){
|
68 | if(this.appConf.staticRoot && this.appConf.staticRoot.constructor === String) {
|
69 |
|
70 |
|
71 | this.staticRoot = path.join (
|
72 | this.appRoot,
|
73 | this.appConf.staticRoot
|
74 | );
|
75 |
|
76 | next();
|
77 | } else {
|
78 | console.error("serveStatic, String expected: appConf.staticRoot");
|
79 | this.res.status(500).send("Expected staticRoot");
|
80 | }
|
81 | };
|
82 |
|
83 |
|
84 | const makeStaticRoot = function(next){
|
85 | var exists;
|
86 | new util.que(this)
|
87 |
|
88 | .add((next) => {
|
89 | fs.exists(this.staticRoot, function(existing){
|
90 | exists = existing;
|
91 | next();
|
92 | });
|
93 | })
|
94 |
|
95 | .then(() => {
|
96 | if (exists){
|
97 | next();
|
98 | } else {
|
99 | console.log("'%s', new path '%s'", this.appConf.name, this.staticRoot);
|
100 | fs.mkdir(this.staticRoot, "0775", () => {
|
101 | next();
|
102 | });
|
103 | }
|
104 | });
|
105 | };
|
106 |
|
107 | const serveStaticRoot = function(next) {
|
108 |
|
109 | if(!this.runtime.apps[this.build].hasOwnProperty("serveStatic")) {
|
110 | this.runtime.router.use(this.urlBase, this.runtime.static(this.staticRoot));
|
111 | this.runtime.apps[this.build].serveStatic = true;
|
112 | }
|
113 |
|
114 |
|
115 | if(this.urlPath.length > 1 && this.urlPath != "/index.html") {
|
116 | this.exit();
|
117 | }
|
118 |
|
119 |
|
120 | else {
|
121 | next();
|
122 | }
|
123 | };
|
124 |
|
125 | const controlCache = function(next) {
|
126 | if (this.runtime.lastDebugMode !== this.debug) {
|
127 | util.clearModificationCache();
|
128 | this.runtime.lastDebugMode = this.debug;
|
129 | this.appConf.modified = true;
|
130 | }
|
131 | next();
|
132 | };
|
133 |
|
134 | const confLess = function(next) {
|
135 |
|
136 | this.lessConfig = {
|
137 | paths: (() => {
|
138 | var lessImportPaths = [];
|
139 | this.appConf.stylePaths.forEach((stylePath) => {
|
140 | lessImportPaths.push(path.join(this.appRoot + stylePath));
|
141 | });
|
142 | return lessImportPaths;
|
143 | })(),
|
144 | filename: "Cannot parse less",
|
145 | compress: !this.debug
|
146 | };
|
147 | next();
|
148 | };
|
149 |
|
150 | const addBlob = function(blobName, blobPath, done) {
|
151 | var pathPrefix = path.join(
|
152 | blobPath,
|
153 | blobName,
|
154 | "/",
|
155 | blobName
|
156 | );
|
157 |
|
158 | var blob = {
|
159 | name: blobName,
|
160 | path: blobPath,
|
161 | blobRoot: this.blobRoot,
|
162 | lessConfig: this.lessConfig,
|
163 | debug: this.debug,
|
164 |
|
165 | mvcFile: pathPrefix + ".js",
|
166 | mvcModified: undefined,
|
167 |
|
168 | cssFile: pathPrefix + ".less",
|
169 | cssModified: undefined,
|
170 |
|
171 | svgFile: pathPrefix + ".svg",
|
172 | svgModified: undefined
|
173 | };
|
174 |
|
175 | new util.all()
|
176 |
|
177 |
|
178 | .add((done) => {
|
179 | util.modified(blob.mvcFile, blobName + "Mvc").then((state) => {
|
180 | blob.mvcModified = state;
|
181 | blob.mvc = true;
|
182 | done();
|
183 | }, (err) => {
|
184 | blob.mvcModified = false;
|
185 | done();
|
186 | });
|
187 | })
|
188 |
|
189 |
|
190 | .add((done) => {
|
191 | util.modified(blob.cssFile, blobName + "Css").then((state) => {
|
192 | blob.cssModified = state;
|
193 | blob.css = true;
|
194 | done();
|
195 | }, (err) => {
|
196 | blob.cssModified = false;
|
197 | done();
|
198 | });
|
199 | })
|
200 |
|
201 |
|
202 | .add((done) => {
|
203 | util.modified(blob.svgFile, blobName + "Svg").then((state) => {
|
204 | blob.svgModified = state;
|
205 | blob.svg = true;
|
206 | done();
|
207 | }, (err) => {
|
208 | blob.svgModified = false;
|
209 | done();
|
210 | });
|
211 | })
|
212 |
|
213 |
|
214 | .then(() => {
|
215 | if(blob.mvcModified || blob.cssModified || blob.svgModified) {
|
216 | this.blobs.push(blob);
|
217 | }
|
218 | done();
|
219 | });
|
220 | };
|
221 |
|
222 |
|
223 | const blobsConfigure = function(next) {
|
224 |
|
225 | this.blobRoot = path.join(
|
226 | this.staticRoot,
|
227 | "bin/"
|
228 | );
|
229 |
|
230 |
|
231 | var configureBlobs = new util.all(this);
|
232 |
|
233 | if(util.obj.isArray(this.appConf.blobs)) {
|
234 | this.appConf.blobs.forEach((blobNode) => {
|
235 | var blobList;
|
236 |
|
237 |
|
238 | const blobPath = util.parsePaths([
|
239 | this.appRoot,
|
240 | blobNode.path
|
241 | ]);
|
242 |
|
243 | if(blobNode.select) {
|
244 |
|
245 | blobList = util.obj.arrayify(blobNode.select);
|
246 | } else {
|
247 |
|
248 | blobList = util.dir.getDirectories(blobPath);
|
249 | }
|
250 |
|
251 | blobList.forEach((blobName) => {
|
252 | var myPath = blobPath;
|
253 | configureBlobs.add((done) => {
|
254 | addBlob.call(this, blobName, myPath, done);
|
255 | });
|
256 | });
|
257 | });
|
258 | }
|
259 |
|
260 | configureBlobs.then(next);
|
261 | };
|
262 |
|
263 |
|
264 | const blobsStaticRoot = function(next){
|
265 | var exists;
|
266 |
|
267 | new util.que(this)
|
268 |
|
269 | .add((next) => {
|
270 | fs.exists(this.blobRoot, function(existing){
|
271 | exists = existing;
|
272 | next();
|
273 | });
|
274 | })
|
275 |
|
276 | .then(() => {
|
277 | if (exists){
|
278 | next();
|
279 | } else {
|
280 | console.log("'%s', new path '%s'", this.appConf.name, this.blobRoot);
|
281 | fs.mkdir(this.blobRoot, "0775", () => {
|
282 | next();
|
283 | });
|
284 | }
|
285 | });
|
286 | };
|
287 |
|
288 | const blobCss = function(done) {
|
289 | fs.readFile(this.cssFile, "utf8", (err, data) => {
|
290 | if (err) {
|
291 | console.error("Blob, style sheet not found", this.cssFile);
|
292 | this.css = false;
|
293 | done();
|
294 | } else {
|
295 | less.render(data, this.lessConfig, (lessErr, cssResult) => {
|
296 | if (lessErr) {
|
297 | console.error(lessErr);
|
298 | this.css = false;
|
299 | } else {
|
300 | this.css = cssResult.css;
|
301 | }
|
302 | done();
|
303 | });
|
304 | }
|
305 | });
|
306 | };
|
307 |
|
308 |
|
309 | const blobSvg = function(done) {
|
310 | fs.readFile(this.svgFile, "utf8", (err, data) => {
|
311 | if (err) {
|
312 | console.error("Blob, svg not found", this.svgFile);
|
313 | done();
|
314 | } else {
|
315 | if(this.debug) {
|
316 | this.svg = data;
|
317 | done();
|
318 | } else {
|
319 | new svgo({
|
320 | full: true,
|
321 | plugins: [
|
322 | {
|
323 | removeViewBox: false,
|
324 | cleanupIDs: false,
|
325 | moveElemsAttrsToGroup: false,
|
326 | moveGroupAttrsToElems: false,
|
327 | collapseGroups: false,
|
328 | convertPathData: false,
|
329 | convertTransform: false,
|
330 | mergePaths: false
|
331 | }
|
332 | ]
|
333 | }).optimize(data, (result) => {
|
334 | this.svg = result.data;
|
335 | done();
|
336 | });
|
337 | }
|
338 | }
|
339 | });
|
340 | };
|
341 |
|
342 |
|
343 | const blobMvc = function(done) {
|
344 | var blob = this;
|
345 |
|
346 | var parseBlob = (data) => {
|
347 |
|
348 | const blobData = [
|
349 | this.debug ? "//# sourceURL=" + this.name + "\n" : "",
|
350 | "bubbleSet." + this.name + "(function(){\n",
|
351 | this.css ? "this.css = true;\n" : "",
|
352 | this.svg ? "this.svg = true;\n" : "",
|
353 | data,
|
354 | "});"
|
355 | ];
|
356 |
|
357 | data = blobData.join("");
|
358 |
|
359 | if(!this.debug) {
|
360 | data = ugly.minify(data, {
|
361 | warnings: true,
|
362 | fromString: true,
|
363 | mangle: true
|
364 | }).code;
|
365 | }
|
366 |
|
367 | this.mvc = data;
|
368 | done();
|
369 | };
|
370 |
|
371 | if(blob.mvc) {
|
372 | fs.readFile(this.mvcFile, "utf8", (err, data) => {
|
373 | if(err) {
|
374 | console.error("Blob, mvc not found", this.mvcFile);
|
375 | done();
|
376 | } else {
|
377 | parseBlob(data);
|
378 | }
|
379 | });
|
380 | } else {
|
381 | parseBlob("");
|
382 | }
|
383 | };
|
384 |
|
385 |
|
386 | const blobExport = function(done) {
|
387 | const writeFiles = new util.all(this);
|
388 |
|
389 | if(this.css) {
|
390 | writeFiles.add(function(ready){
|
391 | fs.writeFile(this.blobRoot + this.name + ".css", this.css, "utf8", (err) => {
|
392 | assert.equal(err);
|
393 | ready();
|
394 | });
|
395 | });
|
396 | }
|
397 |
|
398 | if(this.svg) {
|
399 | writeFiles.add(function(ready){
|
400 | fs.writeFile(this.blobRoot + this.name + ".svg", this.svg, "utf8", (err) => {
|
401 | assert.equal(err);
|
402 | ready();
|
403 | });
|
404 | });
|
405 | }
|
406 |
|
407 | writeFiles.add(function(ready){
|
408 | fs.writeFile(this.blobRoot + this.name + ".js", this.mvc, "utf8", (err) => {
|
409 | assert.equal(err);
|
410 | ready();
|
411 | });
|
412 | });
|
413 |
|
414 | writeFiles.then(done);
|
415 | };
|
416 |
|
417 |
|
418 | const blobsCompile = function(next) {
|
419 | var logTime = (t) => { return t ? new Date(t).toString() : "unchanged";};
|
420 | var compiling = new util.all();
|
421 |
|
422 | this.blobs.forEach((blob) => {
|
423 | compiling.add((done) => {
|
424 | var parts = new util.all(blob);
|
425 |
|
426 | console.log("'%s' blob \x1b[36m%s\x1b[0m", this.appConf.name, blob.name);
|
427 |
|
428 | if(blob.css) {
|
429 | console.log("\x1b[34m\tCSS %s\x1b[0m", logTime(blob.cssModified));
|
430 | parts.add(blobCss);
|
431 | }
|
432 |
|
433 | if(blob.svg) {
|
434 | console.log("\x1b[32m\tSVG %s\x1b[0m", logTime(blob.svgModified));
|
435 | parts.add(blobSvg);
|
436 | }
|
437 |
|
438 | console.log("\x1b[33m\tMVC %s\x1b[0m", logTime(blob.mvcModified));
|
439 | parts.add(blobMvc);
|
440 |
|
441 | parts.then(() => {
|
442 | blobExport.call(blob, done);
|
443 | });
|
444 | });
|
445 | });
|
446 | compiling.then(next);
|
447 | };
|
448 |
|
449 |
|
450 | const parseBubbles = function(ready) {
|
451 | if(this.appConf.modified) {
|
452 | var pathBubbles = __dirname + "/bubbles.js";
|
453 | fs.readFile(pathBubbles, "utf8", (err, data) => {
|
454 | if(err) {
|
455 | console.error(`bubbles.js, fs.readFile: ${
|
456 | err
|
457 | }`);
|
458 | ready();
|
459 | } else {
|
460 |
|
461 | if(!this.debug) {
|
462 | var uglyfied = ugly.minify(data, {
|
463 | warnings: true,
|
464 | fromString: true,
|
465 | mangle: true
|
466 | });
|
467 | assert.equal(uglyfied.error);
|
468 | data = uglyfied.code;
|
469 | }
|
470 |
|
471 | var filePath = path.join(
|
472 | this.blobRoot,
|
473 | "bubbles.js"
|
474 | );
|
475 |
|
476 | fs.writeFile(filePath, data, "utf8", (err) => {
|
477 | assert.equal(err);
|
478 | ready();
|
479 | });
|
480 | }
|
481 | });
|
482 | } else {
|
483 | ready();
|
484 | }
|
485 | };
|
486 |
|
487 |
|
488 | const indexWrite = function (next) {
|
489 | if(this.appConf.modified) {
|
490 | var dom = new util.dombo();
|
491 |
|
492 |
|
493 | dom.comment(`${
|
494 | this.appConf.name || "app"
|
495 | } v${
|
496 | this.appConf.version || 0
|
497 | }: ${
|
498 | this.runtime.conf.name || "server"
|
499 | } v${
|
500 | this.runtime.conf.version || 0
|
501 | } ${
|
502 | new Date()
|
503 | }`);
|
504 |
|
505 |
|
506 | if(this.appConf.meta && this.appConf.meta.length) {
|
507 | this.appConf.meta.forEach(function (meta) {
|
508 | dom.head("meta", false, meta);
|
509 | });
|
510 | }
|
511 |
|
512 |
|
513 | if(this.appConf.tag && this.appConf.tag.length) {
|
514 | this.appConf.tag.forEach(function (tag) {
|
515 | dom.head(tag.name, false, tag.attr);
|
516 | });
|
517 | }
|
518 |
|
519 |
|
520 | if(this.debug) {
|
521 | dom.head("meta", false, {
|
522 | "http-equiv": "cache-control",
|
523 | "content": "no-cache"
|
524 | });
|
525 | }
|
526 |
|
527 |
|
528 | dom.head("title", this.appConf.name || "app");
|
529 |
|
530 | var scriptTags = [{
|
531 | src: "bin/bubbles.js",
|
532 | type: "text/javascript",
|
533 | charset: "utf-8",
|
534 | async: undefined
|
535 | }];
|
536 |
|
537 | if(util.obj.isArray(this.appConf.externalScripts)) {
|
538 | scriptTags = scriptTags.concat(this.appConf.externalScripts);
|
539 | }
|
540 |
|
541 |
|
542 | scriptTags.forEach((src) => {
|
543 | dom.head("script", "", src);
|
544 | });
|
545 |
|
546 |
|
547 | if (this.appConf.index) {
|
548 | fs.writeFile(this.staticRoot + this.appConf.index, dom.parse(), "utf8", (err) => {
|
549 | assert.equal(err);
|
550 | next();
|
551 | });
|
552 | }
|
553 | } else {
|
554 | next();
|
555 | }
|
556 | };
|
557 |
|
558 |
|
559 | const confNginx = function(){
|
560 | var nginx = this.appConf.nginx;
|
561 |
|
562 | if(nginx && this.appConf.modified) {
|
563 | var tpl = "";
|
564 |
|
565 | new util.que(this)
|
566 |
|
567 | .add((done) => {
|
568 | fs.readFile(util.parsePaths(nginx.template), "utf8", (err, data) => {
|
569 | if (err) console.error("confNginx", err);
|
570 | else {
|
571 | tpl = data;
|
572 | done();
|
573 | }
|
574 | });
|
575 | })
|
576 |
|
577 | .then(() => {
|
578 | tpl = tpl
|
579 | .replace("<<title>>", this.appConf.name + " " + this.appConf.version)
|
580 | .replace("<<domains>>", nginx.domains)
|
581 | .replace("<<srcerer>>", nginx.srcerer);
|
582 |
|
583 |
|
584 | for(var key in nginx.paths) {
|
585 | tpl = tpl.replace("<<" + key + ">>", util.parsePaths(
|
586 | nginx.paths[key]
|
587 | ));
|
588 | }
|
589 |
|
590 | fs.writeFile(util.parsePaths(nginx.file), tpl, "utf8", (err) => {
|
591 | if(err) assert.equal(err);
|
592 | else console.log("'%s' new\x1b[36m nginx", this.appConf.name);
|
593 | });
|
594 | });
|
595 | }
|
596 | };
|
597 |
|
598 |
|
599 | const exitBuild = function(next){
|
600 | this.exit();
|
601 | next();
|
602 | };
|
603 |
|
604 |
|
605 | module.exports = function () {
|
606 |
|
607 |
|
608 |
|
609 |
|
610 |
|
611 |
|
612 |
|
613 |
|
614 |
|
615 |
|
616 |
|
617 |
|
618 |
|
619 |
|
620 | new util.que({
|
621 | server: this.express.req.server,
|
622 | runtime: this.express.req.runtime,
|
623 | res: this.express.res,
|
624 | urlBase: this.express.req.baseUrl,
|
625 | urlPath: this.express.req.path,
|
626 | build: undefined,
|
627 | appConf: undefined,
|
628 | blobs: [],
|
629 | debug: this.express.req.query.debug === "true",
|
630 | exit: this.express.next
|
631 | })
|
632 |
|
633 | .add(getBuildName)
|
634 | .add(loadApplicationConfiguration)
|
635 | .add(setStaticRoot)
|
636 | .add(makeStaticRoot)
|
637 | .add(serveStaticRoot)
|
638 | .add(controlCache)
|
639 | .add(confLess)
|
640 | .add(blobsConfigure)
|
641 | .add(blobsStaticRoot)
|
642 | .add(blobsCompile)
|
643 | .add(parseBubbles)
|
644 | .add(indexWrite)
|
645 | .add(exitBuild)
|
646 | .then(confNginx);
|
647 | };
|
648 |
|