- "cd .."
script:
- - "npm run-script test"
- "npm run-script test-datasource"
+ - "npm run-script test"
- "npm run-script jshint"
"_closeDate": "Close Date",
"_code": "Code",
"_configure": "Configure",
+ "_commandCenter": "Command Center",
"_commentType": "Comment Type",
"_commentsEditable": "Comments Editable",
"_company": "Company",
"_extendedPrice": "Extended Price",
"_extendedDescription": "Extended Description",
"_extendedPriceScale": "Extended Price Scale",
+ "_extensionName": "Extension Name",
"_externalReference": "External Reference",
"_delivery": "Delivery",
"_department": "Department",
"_invoiceNumber": "Invoice #",
"_invoices": "Invoices",
"_initials": "Initials",
+ "_installExtension": "Install Extension",
"_inventoryUnit": "Inventory Unit",
"_isActive": "Active",
"_isAddresses": "Addresses",
"_customerOrProspect": "Would you like to create a new Customer or a new Prospect?",
"_deleteLine?": "Are you sure you want to delete this line?",
"_exitPageWarning": "You are about to leave the xTuple application.",
+ "_installExtensionWarning": "Extensions are very powerful and potentially have full access to your " +
+ "data. You should only install an extension from a source you trust.",
"_insufficientPrivileges": "You have insufficient privileges to perform this action.",
"_manualFreight": "Manually editing the freight will disable automatic freight recalculations.",
"_mustSave": "You must save your changes before proceeding.",
success: _.bind(this.didComplete, this)
};
var relevantPrivileges = [
+ "InstallExtension",
"MaintainUsers",
"MaintainPreferencesSelf",
"MaintainWorkflowsSelf",
{kind: "onyx.GroupboxHeader", content: "_notes".loc()},
{kind: "XV.TextArea", attr: "DatabaseComments"}
]}
+ ]},
+ {kind: "XV.Groupbox",
+ title: "_commandCenter".loc(), name: "commandPanel", components: [
+ {kind: "XV.ScrollableGroupbox",
+ classes: "in-panel", components: [
+ {kind: "onyx.GroupboxHeader", content: "_installExtension".loc()},
+ {kind: "XV.InputWidget", name: "extensionName", label: "_extensionName".loc()},
+ {kind: "FittableColumns", classes: "xv-buttons center", components: [
+ {kind: "onyx.Button", name: "extensionButton", classes: "icon-ok", ontap: "installExtension"},
+ ]},
+ ]}
]}
]}
- ]
+ ],
+ create: function () {
+ this.inherited(arguments);
+ var hasPriv = XT.session.privileges.get("InstallExtension");
+ this.$.extensionName.setDisabled(!hasPriv);
+ this.$.extensionButton.setDisabled(!hasPriv);
+ },
+ installExtension: function () {
+ var that = this,
+ callback = function (response) {
+ if (!response.answer) {
+ return;
+ }
+
+ XT.dataSource.callRoute("install-extension",
+ {
+ extensionName: that.$.extensionName.getValue()
+ },
+ {
+ success: function (message) {
+ that.doNotify({message: message});
+ },
+ error: function (error) {
+ that.doNotify({message: error.message ? error.message() : error});
+ }
+ }
+ );
+ };
+
+ if (!this.$.extensionName.getValue()) {
+ this.doNotify({
+ type: XM.Model.WARNING,
+ message: "_attributeIsRequired".loc().replace("{attr}", "_extensionName".loc())
+ });
+ return;
+ }
+
+ this.doNotify({
+ type: XM.Model.QUESTION,
+ message: "_installExtensionWarning".loc() + "_confirmAction".loc(),
+ callback: callback
+ });
+ }
});
enyo.kind({
"public/tables/vendaddrinfo.sql",
"public/tables/wo.sql",
"public/tables/womatl.sql",
+ "xt/functions/grant_role_priv.sql",
"xt/functions/add_priv.sql",
+ "public/tables/priv.sql",
"xt/functions/add_role.sql",
"xt/functions/add_report_definition.sql",
"xt/functions/average_cost.sql",
"xt/functions/cntctrestore.sql",
"xt/functions/createuser.sql",
"xt/functions/cust_outstanding_credit.sql",
- "xt/functions/grant_role_priv.sql",
"xt/functions/grant_role_ext.sql",
"xt/functions/grant_user_role.sql",
"xt/functions/install_guiscript.sql",
"public/tables/comment_trigger.sql",
"public/tables/pkghead.sql",
"public/tables/schemaord.sql",
- "priv.sql",
+ "grant_roles.sql",
"update_version.sql"
]
}
--- /dev/null
+select xt.add_priv('InstallExtension', 'Can Install Extensions', 'command_center', 'CommandCenter');
var that = this;
app.use(express.favicon(__dirname + '/views/login/assets/favicon.ico'));
-_.each(X.options.datasource.databases, function (orgValue, orgKey, orgList) {
- "use strict";
- app.use("/" + orgValue + '/client', express.static('../enyo-client/application', { maxAge: 86400000 }));
- app.use("/" + orgValue + '/core-extensions', express.static('../enyo-client/extensions', { maxAge: 86400000 }));
- app.use("/" + orgValue + '/private-extensions', express.static('../../private-extensions', { maxAge: 86400000 }));
- app.use("/" + orgValue + '/xtuple-extensions', express.static('../../xtuple-extensions', { maxAge: 86400000 }));
-});
+if (X.options.datasource.debugging) {
+ _.each(X.options.datasource.databases, function (orgValue, orgKey, orgList) {
+ "use strict";
+ app.use("/" + orgValue + '/client', express.static('../enyo-client/application', { maxAge: 86400000 }));
+ app.use("/" + orgValue + '/core-extensions', express.static('../enyo-client/extensions', { maxAge: 86400000 }));
+ app.use("/" + orgValue + '/private-extensions', express.static('../../private-extensions', { maxAge: 86400000 }));
+ app.use("/" + orgValue + '/xtuple-extensions', express.static('../../xtuple-extensions', { maxAge: 86400000 }));
+ app.use("/" + orgValue + '/npm', express.static('../node_modules', { maxAge: 86400000 }));
+ });
+}
app.use('/assets', express.static('views/login/assets', { maxAge: 86400000 }));
app.get('/:org/dialog/authorize', oauth2.authorization);
app.all('/:org/email', routes.email);
app.all('/:org/export', routes.exxport);
app.get('/:org/file', routes.file);
-app.all('/:org/oauth/generate-key', routes.generateOauthKey);
app.get('/:org/generate-report', routes.generateReport);
+app.all('/:org/install-extension', routes.installExtension);
app.get('/:org/locale', routes.locale);
+app.all('/:org/oauth/generate-key', routes.generateOauthKey);
app.get('/:org/reset-password', routes.resetPassword);
app.post('/:org/oauth/revoke-token', routes.revokeOauthToken);
app.all('/:org/vcfExport', routes.vcfExport);
});
uuids = _.compact(uuids); // eliminate any null values
var extensionPaths = _.compact(_.map(extensions, function (ext) {
- return path.join(ext.location, "source", ext.name);
+ var locationName = ext.location.indexOf("/") === 0 ?
+ path.join(ext.location, "source") :
+ "/" + ext.location;
+ return path.join(locationName, ext.name);
}));
getCoreUuid('js', req.session.passport.user.organization, function (err, jsUuid) {
if (err) {
--- /dev/null
+/*jshint node:true, indent:2, curly:false, eqeqeq:true, immed:true, latedef:true, newcap:true, noarg:true,
+regexp:true, undef:true, strict:true, trailing:true, white:true */
+/*global X:true, SYS:true, _:true */
+
+(function () {
+ "use strict";
+
+ var async = require("async"),
+ npm = require("npm"),
+ path = require("path"),
+ buildAll = require("../../scripts/lib/build_all");
+
+ exports.installExtension = function (req, res) {
+ var database = req.session.passport.user.organization,
+ extensionName = req.query.extensionName,
+ username = req.session.passport.user.id,
+ user = new SYS.User(),
+ validateInput = function (callback) {
+ if (!extensionName) {
+ callback("Error: empty extension name");
+ return;
+ }
+ callback();
+ },
+ validateUser = function (callback) {
+ user.fetch({
+ id: username,
+ username: X.options.databaseServer.user,
+ database: database,
+ success: function (model, results) {
+ var privCheck = _.find(model.get("grantedPrivileges"), function (model) {
+ return model.privilege === "InstallExtension";
+ });
+ if (!privCheck) {
+ callback({message: "_insufficientPrivileges"});
+ return;
+ }
+ callback(); // success!
+ },
+ error: function () {
+ callback({message: "_restoreError"});
+ }
+ });
+ },
+ npmLoad = function (callback) {
+ npm.load(callback);
+ },
+ npmInstall = function (callback) {
+ npm.commands.install([extensionName], callback);
+ npm.on("log", function (message) {
+ // log the progress of the installation
+ console.log(message);
+ });
+ },
+ buildExtension = function (callback) {
+ console.log("extension is", path.join(__dirname, "../../node_modules", extensionName));
+ buildAll.build({
+ database: database,
+ extension: path.join(__dirname, "../../node_modules", extensionName)
+ }, callback);
+ };
+
+ async.series([
+ validateInput,
+ validateUser,
+ npmLoad,
+ npmInstall,
+ buildExtension
+ ], function (err, results) {
+ if (err) {
+ console.log(err);
+ err.isError = true;
+ res.send(err);
+ return;
+ }
+ console.log("all done");
+ res.send({data: "_success"});
+ });
+ };
+}());
+
+
file = require('./file'),
generateReport = require('./generate_report'),
generateOauthKey = require('./generate_oauth_key'),
+ installExtension = require('./install_extension'),
locale = require('./locale'),
passport = require('passport'),
redirector = require('./redirector'),
exports.file = [ensureLogin, file.file];
exports.generateOauthKey = [ensureLogin, generateOauthKey.generateKey];
exports.generateReport = [ensureLogin, generateReport.generateReport];
+ exports.installExtension = [ensureLogin, installExtension.installExtension];
exports.locale = [ensureLogin, locale.locale];
exports.redirect = redirector.redirect;
exports.analysis = [ensureLogin, analysis.analysis];
"less": "1.5.0",
"moment": "2.4.x",
"nodemailer": "0.3.x",
+ "npm":"1.4.x",
"node-forge": "0.6.x",
"oauth2orize": "0.1.x",
"oauth2orize-jwt-bearer": "0.1.x",
dataSource = require('../../node-datasource/lib/ext/datasource').dataSource,
exec = require('child_process').exec,
fs = require('fs'),
+ npm = require('npm'),
path = require('path'),
unregister = buildDatabaseUtil.unregister,
winston = require('winston');
extPath = path.join(__dirname, "../../../xtuple-extensions/source", name);
} else if (location === '/private-extensions') {
extPath = path.join(__dirname, "../../../private-extensions/source", name);
+ } else if (location === 'npm') {
+ extPath = path.join(__dirname, "../../node_modules", name);
}
return extPath;
});
});
},
buildAll = function (specs, creds, buildAllCallback) {
- buildClient(specs, function (err, res) {
- if (err) {
- buildAllCallback(err);
- return;
- }
- buildDatabase.buildDatabase(specs, creds, function (databaseErr, databaseRes) {
- var returnMessage;
- if (databaseErr && (specs[0].wipeViews || specs[0].initialize)) {
- buildAllCallback(databaseErr);
- return;
-
- } else if (databaseErr) {
- buildAllCallback("Build failed. Try wiping the views next time by running me without the -q flag.");
+ async.series([
+ function (done) {
+ // step 1: npm install extension if necessary
+ // an alternate approach would be only npm install these
+ // extensions on an npm install.
+ var allExtensions = _.reduce(specs, function (memo, spec) {
+ memo.push(spec.extensions);
+ return _.flatten(memo);
+ }, []);
+ var npmExtensions = _.filter(allExtensions, function (extName) {
+ return extName.indexOf("node_modules") >= 0;
+ });
+ if (npmExtensions.length === 0) {
+ done();
return;
}
- returnMessage = "\n";
- _.each(specs, function (spec) {
- returnMessage += "Database: " + spec.database + '\nDirectories:\n';
- _.each(spec.extensions, function (ext) {
- returnMessage += ' ' + ext + '\n';
+ npm.load(function (err, res) {
+ if (err) {
+ done(err);
+ return;
+ }
+ npm.on("log", function (message) {
+ // log the progress of the installation
+ console.log(message);
});
+ async.map(npmExtensions, function (extName, next) {
+ npm.commands.install([path.basename(extName)], next);
+ }, done);
});
- buildAllCallback(null, "Build succeeded." + returnMessage);
- });
+ },
+ function (done) {
+ // step 2: build the client
+ buildClient(specs, done);
+ },
+ function (done) {
+ // step 3: build the database
+ buildDatabase.buildDatabase(specs, creds, function (databaseErr, databaseRes) {
+ if (databaseErr) {
+ buildAllCallback(databaseErr);
+ return;
+ }
+ var returnMessage = "\n";
+ _.each(specs, function (spec) {
+ returnMessage += "Database: " + spec.database + '\nDirectories:\n';
+ _.each(spec.extensions, function (ext) {
+ returnMessage += ' ' + ext + '\n';
+ });
+ });
+ done(null, "Build succeeded." + returnMessage);
+ });
+ }
+ ], function (err, results) {
+ buildAllCallback(err, results && results[results.length - 1]);
});
},
config;
callback(null, "");
return;
- } else if (extPath.indexOf("extensions") < 0) {
+ } else if (extPath.indexOf("extensions") < 0 && extPath.indexOf("node_modules") < 0) {
// this is the core app, which has a slightly different process.
fs.readFile(path.join(__dirname, "build/core.js"), "utf8", function (err, jsCode) {
if (err) {
return;
}
// run the enyo deployment method asyncronously
- var rootDir = path.join(extPath, "../..");
+ var rootDir = path.join(extPath, extPath.indexOf("node_modules") >= 0 ? "../../enyo-client/extensions/" : "../..");
// we run the command from /scripts/lib, so that is where build directories and other
// temp files are going to go.
console.log("building " + extName);
};
var build = function (extPath, callback) {
+ var isNodeModule = extPath.indexOf("node_modules") >= 0;
+
if (extPath.indexOf("/lib/orm") >= 0 || extPath.indexOf("foundation-database") >= 0) {
// There is nothing here to install on the client.
callback();
return;
}
- if (extPath.indexOf("extensions") < 0) {
+ if (extPath.indexOf("extensions") < 0 && !isNodeModule) {
// this is the core app, which has a different deploy process.
buildCore(callback);
return;
}
- var enyoDir = path.join(extPath, "../../enyo");
+ var enyoDir = path.join(extPath, isNodeModule ? "../../enyo-client/extensions/enyo" : "../../enyo");
fs.exists(path.join(extPath, "client"), function (exists) {
if (!exists) {
console.log(extPath + " has no client code. Not trying to build it.");
extensionCallback(null, "");
return;
}
- //winston.info("Installing extension", databaseName, extension);
+ //console.log("Installing extension", databaseName, extension);
// deal with directory structure quirks
var baseName = path.basename(extension),
isFoundation = extension.indexOf("foundation-database") >= 0,
extension.indexOf("extension") >= 0,
isPublicExtension = extension.indexOf("xtuple-extensions") >= 0,
isPrivateExtension = extension.indexOf("private-extensions") >= 0,
+ isNpmExtension = baseName.indexOf("xtuple-") >= 0,
dbSourceRoot = (isFoundation || isFoundationExtension) ? extension :
isLibOrm ? path.join(extension, "source") :
path.join(extension, "database/source"),
wipeViews: isApplicationCore && spec.wipeViews,
extensionLocation: isCoreExtension ? "/core-extensions" :
isPublicExtension ? "/xtuple-extensions" :
- isPrivateExtension ? "/private-extensions" : "not-applicable"
+ isPrivateExtension ? "/private-extensions" :
+ isNpmExtension ? "npm" : "not-applicable"
};
buildDatabaseUtil.explodeManifest(path.join(dbSourceRoot, "manifest.js"),