Merge pull request #1843 from xtuple/4_6_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   buildDictionary = require("./build_dictionary"),
9   buildClient = require("./build_client").buildClient,
10   defaultExtensions = require("./util/default_extensions").extensions,
11   dataSource = require('../../node-datasource/lib/ext/datasource').dataSource,
12   exec = require('child_process').exec,
13   fs = require('fs'),
14   initDatabase = require("./util/init_database").initDatabase,
15   inspectDatabaseExtensions = require("./util/inspect_database").inspectDatabaseExtensions,
16   npm = require('npm'),
17   path = require('path'),
18   unregister = require("./util/unregister").unregister,
19   winston = require('winston');
20
21 /*
22   This is the point of entry for both the lightweight CLI entry-point and
23   programmatic calls to build, such as from mocha. Most of the work in this
24   file is in determining what the defaults mean. For example, if the
25   user does not specify an extension, we install the core and all registered
26   extensions, which requires a call to xt.ext.
27
28   We delegate the work of actually building the database and building the
29   client to build_database.js and build_client.js.
30 */
31
32 (function () {
33   "use strict";
34
35   var creds;
36
37   var buildAll = function (specs, creds, buildAllCallback) {
38     async.series([
39       function (done) {
40         // step 0: init the database, if requested
41
42         if (specs.length === 1 &&
43             specs[0].initialize &&
44             (specs[0].backup || specs[0].source)) {
45
46           // The user wants to initialize the database first (i.e. Step 0)
47           // Do that, then call this function again
48           initDatabase(specs[0], creds, function (err, res) {
49             specs[0].wasInitialized = true;
50             done(err, res);
51           });
52           return;
53         } else {
54           done();
55         }
56
57       },
58       function (done) {
59         // step 1: npm install extension if necessary
60         // an alternate approach would be only npm install these
61         // extensions on an npm install.
62         var allExtensions = _.reduce(specs, function (memo, spec) {
63           memo.push(spec.extensions);
64           return _.flatten(memo);
65         }, []);
66         var npmExtensions = _.filter(allExtensions, function (extName) {
67           return extName && extName.indexOf("node_modules") >= 0;
68         });
69         if (npmExtensions.length === 0) {
70           done();
71           return;
72         }
73         npm.load(function (err, res) {
74           if (err) {
75             done(err);
76             return;
77           }
78           npm.on("log", function (message) {
79             // log the progress of the installation
80             console.log(message);
81           });
82           async.map(npmExtensions, function (extName, next) {
83             npm.commands.install([path.basename(extName)], next);
84           }, done);
85         });
86       },
87       function (done) {
88         // step 2: build the client
89         buildClient(specs, done);
90       },
91       function (done) {
92         // step 3: build the database
93         buildDatabase.buildDatabase(specs, creds, function (databaseErr, databaseRes) {
94           if (databaseErr) {
95             buildAllCallback(databaseErr);
96             return;
97           }
98           var returnMessage = "\n";
99           _.each(specs, function (spec) {
100             returnMessage += "Database: " + spec.database + '\nDirectories:\n';
101             _.each(spec.extensions, function (ext) {
102               returnMessage += '  ' + ext + '\n';
103             });
104           });
105           done(null, "Build succeeded." + returnMessage);
106         });
107       },
108       function (done) {
109         // step 4: import all dictionary files
110         if (specs[0].clientOnly || specs[0].databaseOnly) {
111           // don't build dictionaries if the user doesn't want us to
112           console.log("Not importing the dictionaries");
113           return done();
114         }
115         var databases = _.map(specs, function (spec) {
116           return spec.database;
117         });
118         async.map(databases, buildDictionary.importAllDictionaries, done);
119       }
120     ], function (err, results) {
121       buildAllCallback(err, results && results[results.length - 2]);
122     });
123   };
124
125   exports.build = function (options, callback) {
126     var buildSpecs = {},
127       databases = [],
128       extension,
129       getRegisteredExtensions = function (database, callback) {
130         var credsClone = JSON.parse(JSON.stringify(creds));
131         credsClone.database = database;
132         inspectDatabaseExtensions(credsClone, function (err, paths) {
133           callback(null, {
134             extensions: paths,
135             database: database,
136             keepSql: options.keepSql,
137             populateData: options.populateData,
138             wipeViews: options.wipeViews,
139             clientOnly: options.clientOnly,
140             databaseOnly: options.databaseOnly
141           });
142         });
143       },
144       config;
145
146     if (options.config) {
147       config = require(path.resolve(process.cwd(), options.config));
148     } else {
149       config = require(path.resolve(__dirname, "../../node-datasource/config.js"));
150     }
151     creds = config.databaseServer;
152     creds.encryptionKeyFile = config.datasource.encryptionKeyFile;
153     creds.host = creds.hostname; // adapt our lingo to node-postgres lingo
154     creds.username = creds.user; // adapt our lingo to orm installer lingo
155
156     if (options.database) {
157       // the user has specified a particular database
158       databases.push(options.database);
159     } else {
160       // build all the databases in node-datasource/config.js
161       databases = config.datasource.databases;
162     }
163
164     if (options.clientOnly && options.databaseOnly) {
165       // This request doesn't make any sense.
166       callback("Make up your mind.");
167
168     } else if (options.backup && options.source) {
169       callback("You can build from backup or from source but not both.");
170
171     } else if (options.backup && options.extension) {
172       callback("When you're building from a backup you get whatever extensions the backup merits.");
173
174     } else if (options.initialize &&
175         (options.backup || options.source) &&
176         options.database &&
177         (!options.extension || options.extension === 'foundation-database')) {
178       // Initialize the database. This is serious business, and we only do it if
179       // the user does all the arguments correctly. It must be on one database only,
180       // with no extensions, with the initialize flag, and with a backup file.
181
182       buildSpecs.database = options.database;
183       if (options.backup) {
184         buildSpecs.backup = path.resolve(process.cwd(), options.backup);
185         buildSpecs.extensions = false;
186         // we'll determine the extensions by looking at the db after restore
187       }
188       if (options.source) {
189         buildSpecs.source = path.resolve(process.cwd(), options.source);
190         // if we initialize with the foundation, that means we want
191         // an unmobilized build
192         buildSpecs.extensions = options.extension ?
193           [options.extension] :
194           defaultExtensions;
195       }
196       buildSpecs.initialize = true;
197       buildSpecs.keepSql = options.keepSql;
198       buildSpecs.populateData = options.populateData;
199       buildSpecs.wipeViews = options.wipeViews;
200       buildSpecs.clientOnly = options.clientOnly;
201       buildSpecs.databaseOnly = options.databaseOnly;
202       buildAll([buildSpecs], creds, callback);
203
204     } else if (options.initialize || options.backup || options.source) {
205       // The user has not been sufficiently serious.
206       callback("If you want to initialize the database, you must specifify " +
207         " a database, and use no extensions, and use both the init and either the backup or source flags");
208
209     } else if (options.extension) {
210       // the user has specified an extension to build or unregister
211       // extensions are assumed to be specified relative to the cwd
212       buildSpecs = _.map(databases, function (database) {
213         var extension = path.resolve(process.cwd(), options.extension);
214         return {
215           database: database,
216           frozen: options.frozen,
217           keepSql: options.keepSql,
218           populateData: options.populateData,
219           wipeViews: options.wipeViews,
220           clientOnly: options.clientOnly,
221           databaseOnly: options.databaseOnly,
222           extensions: [extension]
223         };
224       });
225
226       if (options.unregister) {
227         unregister(buildSpecs, creds, callback);
228       } else {
229         // synchronous build
230         buildAll(buildSpecs, creds, callback);
231       }
232     } else {
233       // build all registered extensions for the database
234       async.map(databases, getRegisteredExtensions, function (err, results) {
235         // asynchronous...
236         buildAll(results, creds, callback);
237       });
238     }
239   };
240 }());
241