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*/
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,
13 path = require('path'),
14 unregister = buildDatabaseUtil.unregister,
15 winston = require('winston');
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.
24 We delegate the work of actually building the database and building the
25 client to build_database.js and build_client.js.
34 exports.build = function (options, callback) {
39 // Looks in a database to see which extensions are registered, and
40 // tacks onto that list the core directories.
42 getRegisteredExtensions = function (database, callback) {
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",
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' }
57 adaptExtensions = function (err, res) {
63 var paths = _.map(_.compact(res.rows), function (row) {
64 var location = row.ext_location,
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);
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
82 extensions: _.compact(paths),
84 keepSql: options.keepSql,
85 wipeViews: options.wipeViews,
86 clientOnly: options.clientOnly,
87 databaseOnly: options.databaseOnly
91 credsClone.database = database;
92 dataSource.query(existsSql, credsClone, function (err, res) {
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 });
102 dataSource.query(extSql, credsClone, adaptExtensions);
106 buildAll = function (specs, creds, buildAllCallback) {
107 buildClient(specs, function (err, res) {
109 buildAllCallback(err);
112 buildDatabase.buildDatabase(specs, creds, function (databaseErr, databaseRes) {
114 if (databaseErr && (specs[0].wipeViews || specs[0].initialize)) {
115 buildAllCallback(databaseErr);
118 } else if (databaseErr) {
119 buildAllCallback("Build failed. Try wiping the views next time by running me without the -q flag.");
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';
129 buildAllCallback(null, "Build succeeded." + returnMessage);
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));
141 config = require(path.join(__dirname, "../../node-datasource/config.js"));
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
147 if (options.database) {
148 // the user has specified a particular database
149 databases.push(options.database);
151 // build all the databases in node-datasource/config.js
152 databases = config.datasource.databases;
155 if (options.clientOnly && options.databaseOnly) {
156 // This request doesn't make any sense.
157 callback("Make up your mind.");
159 } else if (options.backup && options.source) {
160 callback("You can build from backup or from source but not both.");
162 } else if (options.initialize &&
163 (options.backup || options.source) &&
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.
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) === '/' ?
175 path.join(process.cwd(), options.backup);
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) === '/' ?
181 path.join(process.cwd(), options.source);
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')
201 buildAll([buildSpecs], creds, callback);
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");
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) === '/' ?
215 path.join(process.cwd(), options.extension);
218 frozen: options.frozen,
219 keepSql: options.keepSql,
220 wipeViews: options.wipeViews,
221 clientOnly: options.clientOnly,
222 databaseOnly: options.databaseOnly,
223 extensions: [extension]
227 if (options.unregister) {
228 unregister(buildSpecs, creds, callback);
231 buildAll(buildSpecs, creds, callback);
234 // build all registered extensions for the database
235 async.map(databases, getRegisteredExtensions, function (err, results) {
237 buildAll(results, creds, callback);