Merge pull request #1609 from xtuple/4_5_x
[xtuple] / scripts / lib / build_all.js
1 /*jshint node:true, indent:2, curly:false, eqeqeq:true, immed:true, latedef:true, newcap:true, noarg:true,
2 regexp:true, undef:true, strict:true, trailing:true, white:true */
3 /*global Backbone:true, _:true, XM:true, XT:true*/
4
5 var _ = require('underscore'),
6   async = require('async'),
7   buildDatabase = require("./build_database"),
8   buildDatabaseUtil = require("./build_database_util"),
9   buildClient = require("./build_client").buildClient,
10   dataSource = require('../../node-datasource/lib/ext/datasource').dataSource,
11   exec = require('child_process').exec,
12   fs = require('fs'),
13   path = require('path'),
14   unregister = buildDatabaseUtil.unregister,
15   winston = require('winston');
16
17 /*
18   This is the point of entry for both the lightweight CLI entry-point and
19   programmatic calls to build, such as from mocha. Most of the work in this
20   file is in determining what the defaults mean. For example, if the
21   user does not specify an extension, we install the core and all registered
22   extensions, which requires a call to xt.ext.
23
24   We delegate the work of actually building the database and building the
25   client to build_database.js and build_client.js.
26 */
27
28 (function () {
29   "use strict";
30
31   var creds;
32
33
34   exports.build = function (options, callback) {
35     var buildSpecs = {},
36       databases = [],
37       extension,
38       //
39       // Looks in a database to see which extensions are registered, and
40       // tacks onto that list the core directories.
41       //
42       getRegisteredExtensions = function (database, callback) {
43         var result,
44           credsClone = JSON.parse(JSON.stringify(creds)),
45           existsSql = "select relname from pg_class where relname = 'ext'",
46           preInstallSql = "select xt.js_init();update xt.ext set ext_location = '/core-extensions' " +
47             "where ext_name = 'oauth2' and ext_location = '/xtuple-extensions';",
48           extSql = preInstallSql + "SELECT * FROM xt.ext ORDER BY ext_load_order",
49           defaultExtensions = [
50             { ext_location: '/core-extensions', ext_name: 'crm' },
51             { ext_location: '/core-extensions', ext_name: 'project' },
52             { ext_location: '/core-extensions', ext_name: 'sales' },
53             { ext_location: '/core-extensions', ext_name: 'billing' },
54             { ext_location: '/core-extensions', ext_name: 'purchasing' },
55             { ext_location: '/core-extensions', ext_name: 'oauth2' }
56           ],
57           adaptExtensions = function (err, res) {
58             if (err) {
59               callback(err);
60               return;
61             }
62
63             var paths = _.map(_.compact(res.rows), function (row) {
64               var location = row.ext_location,
65                 name = row.ext_name,
66                 extPath;
67
68               if (location === '/core-extensions') {
69                 extPath = path.join(__dirname, "/../../enyo-client/extensions/source/", name);
70               } else if (location === '/xtuple-extensions') {
71                 extPath = path.join(__dirname, "../../../xtuple-extensions/source", name);
72               } else if (location === '/private-extensions') {
73                 extPath = path.join(__dirname, "../../../private-extensions/source", name);
74               }
75               return extPath;
76             });
77
78             paths.unshift(path.join(__dirname, "../../enyo-client")); // core path
79             paths.unshift(path.join(__dirname, "../../lib/orm")); // lib path
80             paths.unshift(path.join(__dirname, "../../foundation-database")); // foundation path
81             callback(null, {
82               extensions: _.compact(paths),
83               database: database,
84               keepSql: options.keepSql,
85               wipeViews: options.wipeViews,
86               clientOnly: options.clientOnly,
87               databaseOnly: options.databaseOnly
88             });
89           };
90
91         credsClone.database = database;
92         dataSource.query(existsSql, credsClone, function (err, res) {
93           if (err) {
94             callback(err);
95             return;
96           }
97           if (res.rowCount === 0) {
98             // xt.ext doesn't exist, because this is probably a brand-new DB.
99             // No problem! Give them the core extensions.
100             adaptExtensions(null, { rows: defaultExtensions });
101           } else {
102             dataSource.query(extSql, credsClone, adaptExtensions);
103           }
104         });
105       },
106       buildAll = function (specs, creds, buildAllCallback) {
107         buildClient(specs, function (err, res) {
108           if (err) {
109             buildAllCallback(err);
110             return;
111           }
112           buildDatabase.buildDatabase(specs, creds, function (databaseErr, databaseRes) {
113             var returnMessage;
114             if (databaseErr && (specs[0].wipeViews || specs[0].initialize)) {
115               buildAllCallback(databaseErr);
116               return;
117
118             } else if (databaseErr) {
119               buildAllCallback("Build failed. Try wiping the views next time by running me without the -q flag.");
120               return;
121             }
122             returnMessage = "\n";
123             _.each(specs, function (spec) {
124               returnMessage += "Database: " + spec.database + '\nDirectories:\n';
125               _.each(spec.extensions, function (ext) {
126                 returnMessage += '  ' + ext + '\n';
127               });
128             });
129             buildAllCallback(null, "Build succeeded." + returnMessage);
130           });
131         });
132       },
133       config;
134
135     // the config path is not relative if it starts with a slash
136     if (options.config && options.config.substring(0, 1) === '/') {
137       config = require(options.config);
138     } else if (options.config) {
139       config = require(path.join(process.cwd(), options.config));
140     } else {
141       config = require(path.join(__dirname, "../../node-datasource/config.js"));
142     }
143     creds = config.databaseServer;
144     creds.host = creds.hostname; // adapt our lingo to node-postgres lingo
145     creds.username = creds.user; // adapt our lingo to orm installer lingo
146
147     if (options.database) {
148       // the user has specified a particular database
149       databases.push(options.database);
150     } else {
151       // build all the databases in node-datasource/config.js
152       databases = config.datasource.databases;
153     }
154
155     if (options.clientOnly && options.databaseOnly) {
156       // This request doesn't make any sense.
157       callback("Make up your mind.");
158
159     } else if (options.backup && options.source) {
160       callback("You can build from backup or from source but not both.");
161
162     } else if (options.initialize &&
163         (options.backup || options.source) &&
164         options.database &&
165         (!options.extension || options.extension === 'foundation-database')) {
166       // Initialize the database. This is serious business, and we only do it if
167       // the user does all the arguments correctly. It must be on one database only,
168       // with no extensions, with the initialize flag, and with a backup file.
169
170       buildSpecs.database = options.database;
171       if (options.backup) {
172         // the backup path is not relative if it starts with a slash
173         buildSpecs.backup = options.backup.substring(0, 1) === '/' ?
174           options.backup :
175           path.join(process.cwd(), options.backup);
176       }
177       if (options.source) {
178         // the source path is not relative if it starts with a slash
179         buildSpecs.source = options.source.substring(0, 1) === '/' ?
180           options.source :
181           path.join(process.cwd(), options.source);
182       }
183       buildSpecs.initialize = true;
184       buildSpecs.keepSql = options.keepSql;
185       buildSpecs.wipeViews = options.wipeViews;
186       buildSpecs.clientOnly = options.clientOnly;
187       buildSpecs.databaseOnly = options.databaseOnly;
188       // if we initialize with the foundation, that means we want
189       // an unmobilized build
190       buildSpecs.extensions = options.extension ? [options.extension] : [
191         path.join(__dirname, '../../foundation-database'),
192         path.join(__dirname, '../../lib/orm'),
193         path.join(__dirname, '../../enyo-client'),
194         path.join(__dirname, '../../enyo-client/extensions/source/crm'),
195         path.join(__dirname, '../../enyo-client/extensions/source/project'),
196         path.join(__dirname, '../../enyo-client/extensions/source/sales'),
197         path.join(__dirname, '../../enyo-client/extensions/source/billing'),
198         path.join(__dirname, '../../enyo-client/extensions/source/purchasing'),
199         path.join(__dirname, '../../enyo-client/extensions/source/oauth2')
200       ];
201       buildAll([buildSpecs], creds, callback);
202
203     } else if (options.initialize || options.backup || options.source) {
204       // The user has not been sufficiently serious.
205       callback("If you want to initialize the database, you must specifify " +
206         " a database, and use no extensions, and use both the init and either the backup or source flags");
207
208     } else if (options.extension) {
209       // the user has specified an extension to build or unregister
210       // extensions are assumed to be specified relative to the cwd
211       buildSpecs = _.map(databases, function (database) {
212         // the extension is not relative if it starts with a slash
213         var extension = options.extension.substring(0, 1) === '/' ?
214           options.extension :
215           path.join(process.cwd(), options.extension);
216         return {
217           database: database,
218           frozen: options.frozen,
219           keepSql: options.keepSql,
220           wipeViews: options.wipeViews,
221           clientOnly: options.clientOnly,
222           databaseOnly: options.databaseOnly,
223           extensions: [extension]
224         };
225       });
226
227       if (options.unregister) {
228         unregister(buildSpecs, creds, callback);
229       } else {
230         // synchronous build
231         buildAll(buildSpecs, creds, callback);
232       }
233     } else {
234       // build all registered extensions for the database
235       async.map(databases, getRegisteredExtensions, function (err, results) {
236         // asynchronous...
237         buildAll(results, creds, callback);
238       });
239     }
240   };
241 }());
242