├── docs └── images │ ├── screen-diff.png │ ├── flowchart-main.png │ ├── screen-upload.png │ ├── screen-watching.png │ └── screenshot-youtube-devcon-2014.png ├── lib ├── RouterArgs.js ├── Config.js ├── errorException.js ├── uploadAllFiles.js ├── Sites.js ├── help.js ├── Templates.js ├── routerSaveArgs.js ├── router.js ├── saveEverything.js ├── Structures.js ├── cache.js ├── Constants.js ├── uploadStructureUpdate.js ├── getData.js ├── uploadTemplateUpdate.js ├── debug.js ├── mainMenu.js ├── watch.js ├── projectSelect.js ├── PortletKeys.js ├── projectLoad.js ├── uploadStructureNew.js ├── saveStructures.js ├── saveTemplates.js ├── uploadStructure.js ├── uploadTemplate.js ├── ClassNameConfig.js ├── utilities.js ├── download.js ├── uploadTemplateNew.js ├── uploadFiles.js ├── projectCreate.js └── findDiffs.js ├── Gruntfile.js ├── LICENSE ├── index.js ├── package.json └── README.md /docs/images/screen-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emiloberg/liferay-ddmtool/HEAD/docs/images/screen-diff.png -------------------------------------------------------------------------------- /docs/images/flowchart-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emiloberg/liferay-ddmtool/HEAD/docs/images/flowchart-main.png -------------------------------------------------------------------------------- /docs/images/screen-upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emiloberg/liferay-ddmtool/HEAD/docs/images/screen-upload.png -------------------------------------------------------------------------------- /docs/images/screen-watching.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emiloberg/liferay-ddmtool/HEAD/docs/images/screen-watching.png -------------------------------------------------------------------------------- /docs/images/screenshot-youtube-devcon-2014.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emiloberg/liferay-ddmtool/HEAD/docs/images/screenshot-youtube-devcon-2014.png -------------------------------------------------------------------------------- /lib/RouterArgs.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var RouterArgs = { 4 | doSilently: false, 5 | hasProject: false, 6 | hasServer: false, 7 | loadFromCache: false, 8 | doSaveAllFilesToDisk: false, 9 | doShowHelp: false, 10 | temp: false 11 | }; 12 | 13 | function set(prop, val) { 14 | RouterArgs[prop] = val; 15 | } 16 | 17 | function fetch(prop) { 18 | if (typeof prop === 'undefined') { 19 | return RouterArgs; 20 | } else { 21 | return RouterArgs[prop]; 22 | } 23 | } 24 | 25 | 26 | module.exports.set = set; 27 | module.exports.fetch = fetch; 28 | -------------------------------------------------------------------------------- /lib/Config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * 5 | * @type {{logMode: string, logTimestamp: boolean}} 6 | * 7 | * logMode may be set to: 8 | * 'normal' - prints everything to screen 9 | * 'sparse' - only print content with a severity of SEVERITY_IMPORTANT and above. 10 | * 11 | * logTimestamp - whether or not log should be prepended with a timestamp 12 | */ 13 | var Config = { 14 | logMode: 'normal', 15 | logTimestamp: false 16 | }; 17 | 18 | function set(prop, val) { 19 | Config[prop] = val; 20 | } 21 | 22 | function fetch(prop) { 23 | if (typeof prop === 'undefined') { 24 | return Config; 25 | } else { 26 | return Config[prop]; 27 | } 28 | } 29 | 30 | module.exports.set = set; 31 | module.exports.fetch = fetch; 32 | -------------------------------------------------------------------------------- /lib/errorException.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var clc = require('cli-color'); 4 | 5 | var constants = require('./Constants.js'); 6 | 7 | var lrException = function (e) { 8 | 9 | var errStr = ''; 10 | var errCode = ""; 11 | if(typeof e === 'object') { 12 | try { 13 | errCode = e.code; 14 | } catch(thisErr) { 15 | errStr = JSON.stringify(e); 16 | } 17 | } else { 18 | errStr = e; 19 | } 20 | 21 | if (errCode === 'ENOENT') { 22 | errStr = 'No such file or folder'; 23 | } else { 24 | errStr = e; 25 | } 26 | 27 | 28 | 29 | console.log(clc.red('Error')); 30 | console.log(clc.red(errStr)); 31 | process.exit(); 32 | }; 33 | 34 | module.exports = lrException; -------------------------------------------------------------------------------- /lib/uploadAllFiles.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | 4 | function uploadAllFiles (options) { 5 | 6 | var glob = require("glob"); 7 | 8 | var lrException = require('./errorException.js'); 9 | var uploadFiles = require('./uploadFiles.js'); 10 | var Config = require('./Config.js'); 11 | 12 | 13 | var globOptions = { 14 | cwd: Config.fetch('filesFolder') 15 | }; 16 | 17 | glob('**/*.+(ftl|xml|vm)', globOptions, function (err, files) { 18 | if (err) { 19 | lrException(err); 20 | } 21 | 22 | // Prepend with DDM files root path 23 | for (var i = 0; i < files.length; i++) { 24 | files[i] = Config.fetch('filesFolder') + '/' + files[i]; 25 | } 26 | 27 | uploadFiles(files, options); 28 | 29 | }); 30 | 31 | } 32 | 33 | module.exports = uploadAllFiles; -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | jshint: { 4 | all: ['lib/*.js', 'index.js'], 5 | options: { 6 | reporter: require('jshint-table-reporter'), 7 | node: true 8 | } 9 | }, 10 | bump: { 11 | options: { 12 | files: ['package.json'], 13 | updateConfigs: [], 14 | commit: true, 15 | commitMessage: 'Release v%VERSION%', 16 | commitFiles: ['package.json'], 17 | createTag: true, 18 | tagName: 'v%VERSION%', 19 | tagMessage: 'Version %VERSION%', 20 | push: true, 21 | pushTo: 'github', 22 | gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d' 23 | } 24 | }, 25 | }); 26 | 27 | grunt.loadNpmTasks('grunt-bump'); 28 | grunt.loadNpmTasks('grunt-contrib-jshint'); 29 | grunt.registerTask('default', ['jshint']); 30 | grunt.registerTask('release', ['bump']); 31 | }; 32 | -------------------------------------------------------------------------------- /lib/Sites.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Sites = []; 4 | 5 | function fetch(entry, prop) { 6 | if (typeof entry === 'undefined') { 7 | return Sites; 8 | } else { 9 | if (typeof prop === 'undefined') { 10 | return Sites[entry]; 11 | } else { 12 | return Sites[entry][prop]; 13 | } 14 | } 15 | } 16 | 17 | function setAll(entries) { 18 | Sites = entries; 19 | } 20 | 21 | function add(entry) { 22 | Sites.push(entry); 23 | } 24 | 25 | function addToEntry(entry, prop, val) { 26 | Sites[entry][prop] = val; 27 | } 28 | 29 | function getSingleValue(lookForProperty, lookForValue, returnProperty) { 30 | var ret = Sites.filter(function(entry) { 31 | return entry[lookForProperty] == lookForValue; 32 | }); 33 | 34 | if (ret.length === 1) { 35 | return ret[0][returnProperty]; 36 | } else { 37 | return undefined; 38 | } 39 | } 40 | 41 | 42 | 43 | module.exports.fetch = fetch; 44 | module.exports.add = add; 45 | module.exports.setAll = setAll; 46 | module.exports.addToEntry = addToEntry; 47 | module.exports.getSingleValue = getSingleValue; 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 emiloberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /lib/help.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var nprint = require('node-print'); 4 | 5 | var showHelp = function () { 6 | 7 | var helpArgs = [ 8 | { 9 | arg: '--project [PROJECT-NAME]', 10 | help: 'Load a project' 11 | }, 12 | { 13 | arg: '--server [SERVER-NAME]', 14 | help: 'Load a server in project' 15 | }, 16 | { 17 | arg: '--cache, -c', 18 | help: 'Use data from cache (and don\'t download it from server)' 19 | }, 20 | { arg: '', help: '' }, 21 | { 22 | arg: '--download, -d', 23 | help: 'Save all files to disk' 24 | }, 25 | { 26 | arg: '--upload, -u', 27 | help: 'Upload all files' 28 | }, 29 | { 30 | arg: '--watch, -w', 31 | help: 'Go into watch mode' 32 | }, 33 | { 34 | arg: '--diffs, -i', 35 | help: 'Go into diffs mode' 36 | }, 37 | { arg: '', help: '' }, 38 | { 39 | arg: '--help, -h', 40 | help: 'Show this help' 41 | } 42 | ]; 43 | 44 | console.log(); 45 | console.log('This app may be runned with the following arguments:'); 46 | console.log(); 47 | 48 | for (var x = 0; x < helpArgs.length; ++x) { 49 | nprint.pf('%30s %10s', helpArgs[x].arg, helpArgs[x].help); 50 | } 51 | }; 52 | 53 | module.exports = showHelp; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var argv = require('minimist')(process.argv.slice(2)); 4 | 5 | var Constants = require('./lib/Constants.js'); 6 | var saveArgs = require('./lib/routerSaveArgs.js'); 7 | var router = require('./lib/router.js'); 8 | var LrClassNameConfig = require('./lib/ClassNameConfig.js'); 9 | 10 | var utilities = require('./lib/utilities.js'); 11 | var Config = require('./lib/Config.js'); 12 | 13 | // Set paths 14 | var userHome = utilities.getUserHome(); 15 | var ddmToolFolder = Constants.fetch('ddmToolFolder'); 16 | var settingsFolder = Constants.fetch('settingsFolder'); 17 | var projectsFolder = Constants.fetch('projectsFolder'); 18 | var cacheFolder = Constants.fetch('cacheFolder'); 19 | Config.set('userHome', userHome); 20 | Config.set('settingsFolder', userHome + '/' + ddmToolFolder + '/' + settingsFolder); 21 | Config.set('projectsFolder', userHome + '/' + ddmToolFolder + '/' + settingsFolder + '/' + projectsFolder); 22 | Config.set('cacheFolder', userHome + '/' + ddmToolFolder + '/' + cacheFolder); 23 | 24 | saveArgs(argv); 25 | LrClassNameConfig.loadCustomClassNames(); 26 | router(Constants.fetch('STEP_START')); 27 | -------------------------------------------------------------------------------- /lib/Templates.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Templates = []; 4 | 5 | function fetch(entry, prop) { 6 | if (typeof entry === 'undefined') { 7 | return Templates; 8 | } else { 9 | if (typeof prop === 'undefined') { 10 | return Templates[entry]; 11 | } else { 12 | return Templates[entry][prop]; 13 | } 14 | } 15 | } 16 | 17 | function getAllFilter(lookForProperty, lookForValue) { 18 | return Templates.filter(function(entry) { 19 | return entry[lookForProperty] == lookForValue; 20 | }); 21 | } 22 | 23 | function setAll(entries) { 24 | Templates = entries; 25 | } 26 | 27 | function add(entry) { 28 | Templates.push(entry); 29 | } 30 | 31 | function addToEntry(entry, prop, val) { 32 | Templates[entry][prop] = val; 33 | } 34 | 35 | function updateAll(entries) { 36 | var tempObj = []; 37 | for (var i = 0; i < entries.length; i++) { 38 | if (entries[i].hasOwnProperty('templateKey')) { 39 | /*jshint -W083 */ 40 | tempObj = Templates.filter(function(entry) { 41 | return entry.templateKey != entries[i].templateKey; 42 | }); 43 | /*jshint +W083 */ 44 | tempObj.push(entries[i]); 45 | Templates = tempObj; 46 | } 47 | } 48 | } 49 | 50 | module.exports.fetch = fetch; 51 | module.exports.getAllFilter = getAllFilter; 52 | module.exports.add = add; 53 | module.exports.setAll = setAll; 54 | module.exports.addToEntry = addToEntry; 55 | module.exports.updateAll = updateAll; 56 | 57 | -------------------------------------------------------------------------------- /lib/routerSaveArgs.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var RouterArgs = require('./RouterArgs.js'); 4 | 5 | var saveArgs = function saveArgs(argv) { 6 | 7 | RouterArgs.set('projectName', argv.project); 8 | RouterArgs.set('server', argv.server); 9 | 10 | if (argv.hasOwnProperty('project')) { 11 | if (RouterArgs.fetch('projectName').length > 0) { 12 | RouterArgs.set('hasProject', true); 13 | } 14 | } 15 | 16 | if (argv.hasOwnProperty('server')) { 17 | if (RouterArgs.fetch('server').length > 0) { 18 | RouterArgs.set('hasServer', true); 19 | } 20 | } 21 | 22 | if (argv.hasOwnProperty('c') || argv.hasOwnProperty('cache')) { 23 | RouterArgs.set('loadFromCache', true); 24 | } 25 | 26 | if (argv.hasOwnProperty('d') || argv.hasOwnProperty('download')) { 27 | RouterArgs.set('doSaveAllFilesToDisk', true); 28 | RouterArgs.set('doSilently', true); 29 | } 30 | 31 | if (argv.hasOwnProperty('h') || argv.hasOwnProperty('help')) { 32 | RouterArgs.set('doShowHelp', true); 33 | } 34 | 35 | if (argv.hasOwnProperty('debug')) { 36 | RouterArgs.set('debug', true); 37 | } 38 | 39 | if (argv.hasOwnProperty('w') || argv.hasOwnProperty('watch')) { 40 | RouterArgs.set('goIntoWatch', true); 41 | } 42 | 43 | if (argv.hasOwnProperty('i') || argv.hasOwnProperty('diffs')) { 44 | RouterArgs.set('goIntoDiffs', true); 45 | } 46 | 47 | if (argv.hasOwnProperty('u') || argv.hasOwnProperty('upload')) { 48 | RouterArgs.set('doUploadAll', true); 49 | } 50 | 51 | }; 52 | 53 | module.exports = saveArgs; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "liferay-ddmtool", 3 | "version": "0.8.11", 4 | "description": "Tool for authoring, uploading, downloading and synchronizing Liferay DDM related stuff (Structures and Templates) across environments", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Emil Oberg ", 10 | "license": "MIT", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/emiloberg/liferay-ddmtool.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/emiloberg/liferay-ddmtool/issues" 17 | }, 18 | "homepage": "https://github.com/emiloberg/liferay-ddmtool", 19 | "keywords": [ 20 | "liferay", 21 | "tool", 22 | "ddm", 23 | "dynamic data mapping", 24 | "dynamic data list" 25 | ], 26 | "dependencies": { 27 | "q": "^1.0.1", 28 | "superagent": "^0.18.2", 29 | "cli-color": "^0.3.2", 30 | "fs-extra": "^0.11.0", 31 | "inquirer": "^0.6.0", 32 | "minimist": "^1.1.0", 33 | "underscore": "^1.6.0", 34 | "cli": "^0.6.4", 35 | "glob": "^4.0.5", 36 | "xml2js": "^0.4.4", 37 | "cli-table": "^0.3.0", 38 | "watch": "^0.11.0", 39 | "diff": "^1.0.8", 40 | "chalk": "^0.5.1", 41 | "node-print": "0.0.4", 42 | "keypress": "^0.2.1" 43 | }, 44 | "devDependencies": { 45 | "grunt": "^0.4.5", 46 | "grunt-contrib-jshint": "^0.10.0", 47 | "jshint-table-reporter": "^0.1.0", 48 | "grunt-bump": "0.0.15" 49 | }, 50 | "bin": { 51 | "ddm": "index.js" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/router.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Q = require('q'); 4 | 5 | var Constants = require('./Constants.js'); 6 | var RouterArgs = require('./RouterArgs.js'); 7 | var lrException = require('./errorException.js'); 8 | var showHelp = require('./help.js'); 9 | var selectProject = require('./projectSelect.js'); 10 | var download = require('./download.js'); 11 | var showMainMenu = require('./mainMenu.js'); 12 | var cache = require('./cache.js'); 13 | 14 | var router = function (step) { 15 | if (RouterArgs.fetch('doShowHelp')) { 16 | showHelp(); 17 | } else { 18 | if (step === Constants.fetch('STEP_START')) { 19 | selectProject(); 20 | } else if (step === Constants.fetch('STEP_JUST_LOADED_CONFIG')) { 21 | if (RouterArgs.fetch('loadFromCache')) { 22 | cache.readFromCache(); 23 | } else { 24 | download.downloadAllFromServer(); 25 | } 26 | } else if (step === Constants.fetch('STEP_JUST_READ_ALL_FROM_SERVER')) { 27 | showMainMenu(); 28 | } else if (step === Constants.fetch('STEP_JUST_SAVED_ALL_FILES_TO_DISK')) { 29 | showMainMenu(); 30 | } else if (step === Constants.fetch('STEP_JUST_UPLOADED_DDMS')) { 31 | showMainMenu(); 32 | } else if (step === Constants.fetch('STEP_JUST_WATCHED_FOLDER')) { 33 | showMainMenu(); 34 | } else if (step === Constants.fetch('STEP_JUST_CREATED_PROJECT')) { 35 | selectProject(); 36 | } else { 37 | lrException('Unknown next step!'); 38 | } 39 | } 40 | }; 41 | 42 | module.exports = router; -------------------------------------------------------------------------------- /lib/saveEverything.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var utilities = require('./utilities.js'); 4 | var Constants = require('./Constants.js'); 5 | var RouterArgs = require('./RouterArgs.js'); 6 | var saveTemplates = require('./saveTemplates.js'); 7 | var saveStructures = require('./saveStructures.js'); 8 | var Structures = require('./Structures.js'); 9 | var Templates = require('./Templates.js'); 10 | var Config = require('./Config.js'); 11 | 12 | 13 | 14 | var saveEverythingToFile = function(filesRootPath, options) { 15 | 16 | // Set default options and save to uploadOptions object. 17 | options = typeof options !== 'undefined' ? options : {}; 18 | options.silent = typeof options.silent !== 'undefined' ? options.silent : false; 19 | options.returnToMainMenu = typeof options.returnToMainMenu !== 'undefined' ? options.returnToMainMenu : true; 20 | 21 | if (!options.silent) { 22 | utilities.writeToScreen('Saving everything to disk', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_INFO')); 23 | utilities.writeToScreen('', Constants.fetch('SCREEN_PRINT_HEADING')); 24 | } 25 | 26 | saveStructures(Structures.fetch(), filesRootPath, options); 27 | saveTemplates(Templates.fetch(), filesRootPath, options); 28 | 29 | if (!options.silent) { 30 | utilities.writeToScreen('', Constants.fetch('SCREEN_PRINT_HEADING')); 31 | } 32 | 33 | RouterArgs.set('doSaveAllFilesToDisk', false); 34 | 35 | if (options.returnToMainMenu) { 36 | var router = require('./router.js'); 37 | router(Constants.fetch('STEP_JUST_SAVED_ALL_FILES_TO_DISK')); 38 | } 39 | 40 | }; 41 | 42 | module.exports = saveEverythingToFile; -------------------------------------------------------------------------------- /lib/Structures.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Structures = []; 4 | 5 | function fetch(entry, prop) { 6 | if (typeof entry === 'undefined') { 7 | return Structures; 8 | } else { 9 | if (typeof prop === 'undefined') { 10 | return Structures[entry]; 11 | } else { 12 | return Structures[entry][prop]; 13 | } 14 | } 15 | } 16 | 17 | function getAllFilter(lookForProperty, lookForValue) { 18 | return Structures.filter(function(entry) { 19 | return entry[lookForProperty] == lookForValue; 20 | }); 21 | } 22 | 23 | 24 | function setAll(entries) { 25 | Structures = entries; 26 | } 27 | 28 | function add(entry) { 29 | Structures.push(entry); 30 | } 31 | 32 | function addToEntry(entry, prop, val) { 33 | Structures[entry][prop] = val; 34 | } 35 | 36 | function getSingleValue(lookForProperty, lookForValue, returnProperty) { 37 | var ret = Structures.filter(function(entry) { 38 | return entry[lookForProperty] == lookForValue; 39 | }); 40 | 41 | if (ret.length === 1) { 42 | return ret[0][returnProperty]; 43 | } else { 44 | return undefined; 45 | } 46 | } 47 | 48 | function updateAll(entries) { 49 | var tempObj = []; 50 | for (var i = 0; i < entries.length; i++) { 51 | if (entries[i].hasOwnProperty('structureKey')) { 52 | /*jshint -W083 */ 53 | tempObj = Structures.filter(function(entry) { 54 | return entry.structureKey != entries[i].structureKey; 55 | }); 56 | /*jshint +W083 */ 57 | tempObj.push(entries[i]); 58 | Structures = tempObj; 59 | } 60 | } 61 | } 62 | 63 | module.exports.fetch = fetch; 64 | module.exports.getAllFilter = getAllFilter; 65 | module.exports.add = add; 66 | module.exports.setAll = setAll; 67 | module.exports.addToEntry = addToEntry; 68 | module.exports.getSingleValue = getSingleValue; 69 | module.exports.updateAll = updateAll; 70 | 71 | 72 | -------------------------------------------------------------------------------- /lib/cache.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var fs = require('fs-extra'); 4 | var Q = require('q'); 5 | 6 | var Structures = require('./Structures.js'); 7 | var Templates = require('./Templates.js'); 8 | var Sites = require('./Sites.js'); 9 | var LrClassNameConfig = require('./ClassNameConfig.js'); 10 | var Constants = require('./Constants.js'); 11 | var lrException = require('./errorException.js'); 12 | var Config = require('./Config.js'); 13 | var utilities = require('./utilities.js'); 14 | 15 | var cache = { 16 | getAllCache: function () { 17 | // Todo - Add error handling for missing files 18 | Sites.setAll(cache.readFileFromCache(Constants.fetch('cacheSitesFilename'))); 19 | Structures.setAll(cache.readFileFromCache(Constants.fetch('cacheStructuresFilename'))); 20 | Templates.setAll(cache.readFileFromCache(Constants.fetch('cacheTemplatesFilename'))); 21 | LrClassNameConfig.setAll(cache.readFileFromCache(Constants.fetch('cacheClassNameConfig'))); 22 | }, 23 | readFileFromCache: function (filename) { 24 | // Todo - Add error handling for non json files 25 | return fs.readJsonSync(Config.fetch('cacheFolder') + '/' + Config.fetch('projectName') + '/' + filename); 26 | }, 27 | saveToCache: function (e, filename) { 28 | fs.outputFileSync(Config.fetch('cacheFolder') + '/' + Config.fetch('projectName') + '/' + filename, JSON.stringify(e)); 29 | }, 30 | readFromCache: function () { 31 | utilities.writeToScreen('Reading data from Cache', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_INFO')); 32 | Q.resolve() 33 | .then(cache.getAllCache) 34 | .done(function () { 35 | var router = require('./router.js'); 36 | router(Constants.fetch('STEP_JUST_READ_ALL_FROM_SERVER')); 37 | }, function (e) { 38 | lrException(e); 39 | }); 40 | }, 41 | clearCache: function () { 42 | fs.removeSync(Config.fetch('cacheFolder') + '/' + Config.fetch('projectName')); 43 | } 44 | 45 | }; 46 | 47 | module.exports = cache; -------------------------------------------------------------------------------- /lib/Constants.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var constants = { 4 | ddmToolFolder: '.ddmtool', 5 | settingsFolder: 'config', 6 | projectsFolder: 'projects', 7 | ddmCacheFolder: 'ddm', 8 | 9 | customClassNameConfig: 'customClassNameConfig.json', 10 | 11 | cacheFolder: 'cache', 12 | cacheSitesFilename: 'Sites.json', 13 | cacheStructuresFilename: 'Structures.json', 14 | cacheTemplatesFilename: 'Templates.json', 15 | cacheClassNameConfig: 'ClassNameConfig.json', 16 | 17 | pathSlugDocumentTypes: 'document_types', 18 | pathSlugMetadataSets: 'metadata_sets', 19 | 20 | apiPath: '/api/jsonws/invoke', 21 | 22 | filesEncoding: 'utf8', 23 | 24 | ignoreDDMs: [ 25 | 'WIKI-SOCIAL-FTL', 26 | 'ASSET-TAGS-NAVIGATION-COLOR-BY-POPULARITY-FTL', 27 | 'SITE-MAP-MULTI-COLUMN-LAYOUT-FTL', 28 | 'DOCUMENTLIBRARY-CAROUSEL-FTL', 29 | 'ASSET-CATEGORIES-NAVIGATION-MULTI-COLUMN-LAYOUT-FTL', 30 | 'BLOGS-BASIC-FTL', 31 | 'ASSET-PUBLISHER-RICH-SUMMARY-FTL', 32 | 'LEARNING MODULE METADATA', 33 | 'MARKETING CAMPAIGN THEME METADATA', 34 | 'MEETING METADATA', 35 | 'AUTO_F89C99EC-74A2-4C54-AEB4-472015095F42', 36 | 'AUTO_9AFF1B46-5A06-49B0-A98B-668BCD5DEBCF', 37 | 'AUTO_FDFC496B-939A-42BA-8327-63B392D9CB06', 38 | 'AUTO_B58424F3-CABF-469C-AF9C-6169ABFC39DF', 39 | 'CONTACTS', 40 | 'EVENTS', 41 | 'INVENTORY', 42 | 'ISSUES TRACKING', 43 | 'MEETING MINUTES', 44 | 'TO DO' 45 | ], 46 | 47 | watchIgnorePattern: '^(\\#.*|.*\\~)$', 48 | 49 | externalDiff: '/usr/bin/opendiff %1 %2', 50 | 51 | allowSelfSignedCerts: false, 52 | 53 | SEVERITY_DEBUG: -1, 54 | SEVERITY_NORMAL: 0, 55 | SEVERITY_IMPORTANT: 1, 56 | SCREEN_PRINT_INFO: 0, 57 | SCREEN_PRINT_SAVE: 1, 58 | SCREEN_PRINT_ERROR: 2, 59 | SCREEN_PRINT_HEADING: 3, 60 | SCREEN_PRINT_FILE: 4, 61 | 62 | STEP_START: 1, 63 | STEP_JUST_LOADED_CONFIG: 2, 64 | STEP_JUST_READ_ALL_FROM_SERVER: 3, 65 | STEP_JUST_SAVED_ALL_FILES_TO_DISK: 4, 66 | STEP_JUST_UPLOADED_DDMS: 5, 67 | STEP_JUST_CREATED_PROJECT: 6, 68 | STEP_JUST_WATCHED_FOLDER: 7 69 | }; 70 | 71 | function fetch(prop) { 72 | if (typeof prop === 'undefined') { 73 | return constants; 74 | } else { 75 | return constants[prop]; 76 | } 77 | } 78 | 79 | module.exports.fetch = fetch; 80 | -------------------------------------------------------------------------------- /lib/uploadStructureUpdate.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var createUploadObject = function (newObj) { 4 | 5 | var Q = require('q'); 6 | 7 | var LrClassNameConfig = require('./ClassNameConfig.js'); 8 | var Sites = require('./Sites.js'); 9 | var utilities = require('./utilities.js'); 10 | 11 | var deferred = Q.defer(); 12 | var payload = {}; 13 | 14 | var returnObj = { 15 | exceptionFile: newObj.file, 16 | group: {} 17 | }; 18 | 19 | // Check if the file already is up to date 20 | if(newObj.oldObj.xsd === newObj.newScript) { 21 | returnObj.status = 'uptodate'; 22 | } else { 23 | returnObj.status = 'update'; 24 | } 25 | 26 | // Set some values in our return object to be able to do a nice print to the user. 27 | returnObj.className = LrClassNameConfig.getSingleValue('id', newObj.classNameId, 'friendlyName'); 28 | returnObj.fileFriendlyName = newObj.fileFriendlyName; 29 | returnObj.isStructure = true; 30 | 31 | // Set some values in our return object to be able to do a nice print to the user. 32 | returnObj.group.description = Sites.getSingleValue('groupId', newObj.oldObj.groupId, 'description'); 33 | returnObj.group.name = Sites.getSingleValue('groupId', newObj.oldObj.groupId, 'name'); 34 | returnObj.group.type = LrClassNameConfig.getSingleValue('id', Sites.getSingleValue('groupId', newObj.oldObj.groupId, 'classNameId'), 'friendlyName'); 35 | returnObj.group.friendlyURL = Sites.getSingleValue('groupId', newObj.oldObj.groupId, 'friendlyURL'); 36 | returnObj.group.groupId = newObj.oldObj.groupId; 37 | 38 | // Populate payload with data from old structure (things we aren't updating) 39 | payload = { 40 | structureId: newObj.oldObj.structureId, 41 | parentStructureId: newObj.oldObj.parentStructureId, 42 | xsd: newObj.newScript 43 | }; 44 | 45 | // Populate payload with data from old template (things we aren't updating) 46 | // but we need to make it into a Map which Liferay wants. 47 | utilities.xmlMapToObj(newObj.oldObj.name, 'Name') 48 | .then(function (resName) { 49 | payload.nameMap = resName; 50 | }) 51 | .then(utilities.xmlMapToObj(newObj.oldObj.description, 'Description') 52 | .then(function (resDesc) { 53 | payload.descriptionMap = resDesc; 54 | })) 55 | .then( 56 | function () { 57 | returnObj.payload = '{"/ddmstructure/update-structure": ' + JSON.stringify(payload) + '}'; 58 | deferred.resolve(returnObj); 59 | } 60 | ); 61 | 62 | return deferred.promise; 63 | 64 | }; 65 | 66 | module.exports = createUploadObject; -------------------------------------------------------------------------------- /lib/getData.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var request = require('superagent'); 4 | var Q = require('q'); 5 | var clc = require('cli-color'); 6 | var _ = require('underscore'); 7 | var cli = require('cli'); 8 | 9 | var Constants = require('./Constants.js'); 10 | var Config = require('./Config.js'); 11 | var utilities = require('./utilities.js'); 12 | 13 | 14 | var getData = function (api, showSpinner, lrHost, lrUser, lrPass){ 15 | 16 | var deferred = Q.defer(); 17 | var errStr; 18 | 19 | showSpinner = typeof showSpinner !== 'undefined' ? showSpinner : true; 20 | lrHost = typeof lrHost !== 'undefined' ? lrHost : Config.fetch('host'); 21 | lrUser = typeof lrUser !== 'undefined' ? lrUser : Config.fetch('username'); 22 | lrPass = typeof lrPass !== 'undefined' ? lrPass : Config.fetch('password'); 23 | 24 | lrHost = lrHost + Constants.fetch('apiPath'); 25 | 26 | if (showSpinner) { 27 | cli.spinner(clc.blue('Working...')); 28 | } 29 | 30 | utilities.writeToScreen('Requesting data (from server ' + lrHost + '):\n' + api , Constants.fetch('SEVERITY_DEBUG'), Constants.fetch('SCREEN_PRINT_INFO')); 31 | 32 | request 33 | .post(lrHost) 34 | .set('Content-Type', 'application/json') 35 | .auth(lrUser, lrPass) 36 | .send(api) 37 | .end(function(err, res){ 38 | 39 | if (showSpinner) { 40 | cli.spinner('', true); 41 | } 42 | 43 | if (err) { 44 | if (err.code === 'ENOTFOUND') { errStr = 'Host not found'; } 45 | else if (err.code === 'ECONNREFUSED') { errStr = 'Connection refused'; } 46 | else if (err.code === 'EHOSTUNREACH') { errStr = 'No route to host'; } 47 | else { errStr = 'Unknown error: ' + JSON.stringify(err); } 48 | return deferred.reject(errStr); 49 | } 50 | 51 | if (res.ok) { 52 | if(utilities.isJson(res.text)) { 53 | if(res.body.hasOwnProperty('exception')){ 54 | if(res.body.exception === 'Authenticated access required') { 55 | deferred.reject('Could not authenticate (check username/password)'); 56 | } else { 57 | deferred.reject(res.body.exception); 58 | } 59 | 60 | } else { 61 | deferred.resolve(res.body); 62 | } 63 | } else { 64 | deferred.reject('Connected to server but response is not JSON'); 65 | } 66 | } else { 67 | 68 | if(res.statusCode == '404') { errStr = '(Not Found)'; } 69 | else { errStr =''; } 70 | 71 | errStr = 'Error ' + res.statusCode + ' ' + errStr; 72 | 73 | deferred.reject(errStr); 74 | } 75 | }); 76 | return deferred.promise; 77 | 78 | }; 79 | 80 | module.exports = getData; -------------------------------------------------------------------------------- /lib/uploadTemplateUpdate.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var createUploadObject = function (newObj) { 4 | 5 | var Q = require('q'); 6 | 7 | var LrClassNameConfig = require('./ClassNameConfig.js'); 8 | var Sites = require('./Sites.js'); 9 | var utilities = require('./utilities.js'); 10 | 11 | var deferred = Q.defer(); 12 | var payload = {}; 13 | 14 | var returnObj = { 15 | group: {} 16 | }; 17 | 18 | // Check if the file already is up to date 19 | if(newObj.oldObj.script === newObj.newScript) { 20 | returnObj.status = 'uptodate'; 21 | } else { 22 | returnObj.status = 'update'; 23 | } 24 | 25 | // Set some values in our return object to be able to do a nice print to the user. 26 | returnObj.className = newObj.className; 27 | returnObj.fileFriendlyName = newObj.fileFriendlyName; 28 | returnObj.isTemplate = true; 29 | 30 | returnObj.group.description = Sites.getSingleValue('groupId', newObj.oldObj.groupId, 'description'); 31 | returnObj.group.name = Sites.getSingleValue('groupId', newObj.oldObj.groupId, 'name'); 32 | returnObj.group.type = LrClassNameConfig.getSingleValue('id', Sites.getSingleValue('groupId', newObj.oldObj.groupId, 'classNameId'), 'friendlyName'); 33 | returnObj.group.friendlyURL = Sites.getSingleValue('groupId', newObj.oldObj.groupId, 'friendlyURL'); 34 | returnObj.group.groupId = newObj.oldObj.groupId; 35 | 36 | // Populate payload with data from old template (things we aren't updating) 37 | payload = { 38 | templateId: newObj.oldObj.templateId, 39 | classPK: newObj.oldObj.classPK, 40 | type: newObj.oldObj.type, 41 | mode: newObj.oldObj.mode, 42 | language: newObj.oldObj.language, 43 | cacheable: newObj.oldObj.cacheable, 44 | smallImage: newObj.oldObj.smallImage, 45 | smallImageURL: newObj.oldObj.smallImageURL, 46 | smallImageFile: null, // We don't support small images right now. 47 | script: newObj.newScript 48 | }; 49 | 50 | // Populate payload with data from old template (things we aren't updating) 51 | // but we need to make it into a Map which Liferay wants. 52 | utilities.xmlMapToObj(newObj.oldObj.name, 'Name') 53 | .then(function (resName) { 54 | payload.nameMap = resName; 55 | }) 56 | .then(utilities.xmlMapToObj(newObj.oldObj.description, 'Description') 57 | .then(function (resDesc) { 58 | payload.descriptionMap = resDesc; 59 | })) 60 | .then( 61 | function () { 62 | returnObj.payload = '{"/ddmtemplate/update-template": ' + JSON.stringify(payload) + '}'; 63 | deferred.resolve(returnObj); 64 | } 65 | ); 66 | 67 | return deferred.promise; 68 | 69 | }; 70 | 71 | module.exports = createUploadObject; -------------------------------------------------------------------------------- /lib/debug.js: -------------------------------------------------------------------------------- 1 | var debug = function() { 2 | var uploadFiles = require('./uploadFiles.js'); 3 | var utilities = require('./utilities.js'); 4 | 5 | var files = [ 6 | // UPDATE TEMPLATES 7 | '/Users/emiloberg/code/test-wcm/journal/templates/WCM template1.ftl' 8 | // '/Users/emiloberg/code/test-wcm/application_display_template/asset_publisher/templates/My Asset Publisher.ftl', 9 | // '/Users/emiloberg/code/test-wcm/generic_record_set/templates/Generic DDM Display Template.ftl', 10 | 11 | // '/Users/emiloberg/code/test-wcm/application_display_template/documents_and_media/templates/Carousel.ftl', 12 | // '/Users/emiloberg/code/test-wcm/application_display_template/documents_and_media/templates/NyCarousel.ftl' 13 | 14 | // CREATE TEMPLATES 15 | // '/Users/emiloberg/code/test-wcm/journal/templates/NEW WCM Template.ftl', 16 | // '/Users/emiloberg/code/test-wcm/application_display_template/asset_publisher/templates/NEW Asset Publisher.ftl', 17 | // '/Users/emiloberg/code/test-wcm/generic_record_set/templates/NEW Generic DDM Display Template.ftl' 18 | 19 | 20 | 21 | // CREATE STRUCTURES 22 | // '/Users/emiloberg/code/test-wcm/journal/structures/New Structure.xml', 23 | // '/Users/emiloberg/code/test-wcm/generic_record_set/structures/Ny Generic DDM Structure.xml', 24 | // '/Users/emiloberg/code/test-wcm/dynamic_data_lists/structures/NEW DDL Structure.xml' 25 | // '/Users/emiloberg/code/test-wcm/document_and_media/document_types/structures/NEW2 DnM DocType Structure.xml', 26 | 27 | // UPDATE STRUCTURES 28 | // '/Users/emiloberg/code/test-wcm/journal/structures/Second Structure.xml', 29 | // '/Users/emiloberg/code/test-wcm/generic_record_set/structures/Generic DDM Data Definition.xml', 30 | // '/Users/emiloberg/code/test-wcm/dynamic_data_lists/structures/My Dynamic Data Definition.xml', 31 | 32 | // 33 | // '/Users/emiloberg/code/test-wcm/document_and_media/document_types/structures/Contract.xml', 34 | // '/Users/emiloberg/code/test-wcm/document_and_media/metadata_sets/structures/Meeting Metadata.xml' 35 | 36 | // '/Users/emiloberg/code/test-wcm/application_display_template/asset_publisher/Global AP.ftl' 37 | ]; 38 | 39 | 40 | // Update all files with timestamp 41 | var fs = require('fs-extra'); 42 | var Constants = require('./Constants.js'); 43 | for (var i = 0; i < files.length; i++) { 44 | if(utilities.filenameToLanguageType(files[i]) === 'ftl') { 45 | if (fs.existsSync(files[i])) { 46 | newScript = fs.readFileSync(files[i], {encoding: Constants.fetch('filesEncoding')}); 47 | newScript = '<#-- Updated ' + new Date() + ' ' + Date.now() + ' -->\n' + newScript; 48 | fs.writeFileSync(files[i], newScript, {encoding: Constants.fetch('filesEncoding')}); 49 | } 50 | } 51 | } 52 | 53 | uploadFiles(files); 54 | 55 | 56 | }; 57 | 58 | module.exports = debug; -------------------------------------------------------------------------------- /lib/mainMenu.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var showMainMenu = function () { 4 | 5 | var inquirer = require("inquirer"); 6 | 7 | var RouterArgs = require('./RouterArgs.js'); 8 | var saveEverythingToFile = require('./saveEverything.js'); 9 | var uploadAllFiles = require('./uploadAllFiles.js'); 10 | var findDiffs = require('./findDiffs.js'); 11 | var watchFiles = require('./watch.js'); 12 | var Config = require('./Config.js'); 13 | var Constants = require('./Constants.js'); 14 | var utilities = require('./utilities.js'); 15 | 16 | var debug = require('./debug.js'); 17 | 18 | var filesRootPath = Config.fetch('filesFolder'); 19 | 20 | if (RouterArgs.fetch('debug')) { 21 | RouterArgs.set('debug', false); 22 | debug(); 23 | } else if (RouterArgs.fetch('doSaveAllFilesToDisk')) { 24 | RouterArgs.set('doSaveAllFilesToDisk', false); 25 | saveEverythingToFile(filesRootPath); 26 | 27 | } else if (RouterArgs.fetch('doUploadAll')) { 28 | RouterArgs.set('doUploadAll', false); 29 | uploadAllFiles({autoMode: true}); 30 | } else if (RouterArgs.fetch('goIntoWatch')) { 31 | RouterArgs.set('goIntoWatch', false); 32 | watchFiles(Config.fetch('filesFolder')); 33 | } else if (RouterArgs.fetch('goIntoDiffs')) { 34 | RouterArgs.set('goIntoDiffs', false); 35 | findDiffs(); 36 | } else { 37 | if (!RouterArgs.fetch('doSilently')) { 38 | 39 | utilities.writeToScreen('-', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_NORMAL')); 40 | 41 | inquirer.prompt([ 42 | { 43 | type: "list", 44 | name: "mainMenu", 45 | message: "MAIN MENU", 46 | choices: [ 47 | { 48 | name: 'Watch', 49 | value: 'watch' 50 | }, 51 | { 52 | name: 'Download All [Server > Disk]', 53 | value: 'saveAllFilesToDisk' 54 | }, 55 | { 56 | name: 'Upload All [Disk > Server]', 57 | value: 'uploadAllFilesToServer' 58 | }, 59 | { 60 | name: 'Find Diffs', 61 | value: 'findDiffs' 62 | }, 63 | new inquirer.Separator(), 64 | { 65 | name: 'Quit', 66 | value: 'quit' 67 | } 68 | ] 69 | } 70 | ], function( answers ) { 71 | if (answers.mainMenu === 'saveAllFilesToDisk') { 72 | saveEverythingToFile(filesRootPath); 73 | } else if (answers.mainMenu === 'watch') { 74 | watchFiles(Config.fetch('filesFolder')); 75 | } else if (answers.mainMenu === 'uploadAllFilesToServer') { 76 | uploadAllFiles(); 77 | } else if (answers.mainMenu === 'findDiffs') { 78 | findDiffs(); 79 | } else { 80 | console.log('Bye bye!'); 81 | } 82 | } 83 | ); 84 | } 85 | } 86 | }; 87 | 88 | module.exports = showMainMenu; -------------------------------------------------------------------------------- /lib/watch.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | 4 | // FIGURE OUT WHICH SITE WE'RE GOING TO CREATE THE STRUCTURE 5 | // IF THERE'S MORE THAN ONE, WE ASK THE USER WHICH SITE S/HE WANT TO 6 | // UPLOAD THE TEMPLATE TO. 7 | function watchFiles (watchFolder) { 8 | var watch = require('watch'); 9 | 10 | var router = require('./router.js'); 11 | var Constants = require('./Constants.js'); 12 | var utilities = require('./utilities.js'); 13 | var uploadFiles = require('./uploadFiles.js'); 14 | 15 | var Config = require('./Config.js'); 16 | 17 | 18 | watch.createMonitor(watchFolder, {ignoreDotFiles: true}, function (monitor) { 19 | 20 | utilities.writeToScreen('-', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_NORMAL')); 21 | utilities.writeToScreen('Now watching!', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_HEADING')); 22 | utilities.writeToScreen(' Folder: ' + watchFolder, Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_NORMAL')); 23 | utilities.writeToScreen(' Will upload changes to server: ' + Config.fetch('hostFriendlyName'), Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_NORMAL')); 24 | utilities.writeToScreen('\nPress Ctrl+C to stop watching\n', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_NORMAL')); 25 | 26 | Config.set('logMode', 'sparse'); 27 | Config.set('logTimestamp', true); 28 | 29 | monitor.on("created", function (f) { 30 | if (utilities.filenameIsNotBlacklisted(f)) { 31 | utilities.writeToScreen('Created: ' + f, Constants.fetch('SEVERITY_IMPORTANT'), Constants.fetch('SCREEN_PRINT_NORMAL')); 32 | uploadFiles(f, {autoMode: true}); 33 | } 34 | }); 35 | 36 | monitor.on("changed", function (f) { 37 | if (utilities.filenameIsNotBlacklisted(f)) { 38 | utilities.writeToScreen('Changed: ' + f, Constants.fetch('SEVERITY_IMPORTANT'), Constants.fetch('SCREEN_PRINT_NORMAL')); 39 | uploadFiles(f, {autoMode: true}); 40 | } 41 | }); 42 | 43 | monitor.on("removed", function (f) { 44 | if (utilities.filenameIsNotBlacklisted(f)) { 45 | utilities.writeToScreen('Removed: ' + f, Constants.fetch('SEVERITY_IMPORTANT'), Constants.fetch('SCREEN_PRINT_NORMAL')); 46 | utilities.writeToScreen(' Removal of files is not yet supported', Constants.fetch('SEVERITY_IMPORTANT'), Constants.fetch('SCREEN_PRINT_NORMAL')); 47 | } 48 | }); 49 | 50 | 51 | // Wait and listen for ctrl+C 52 | var keypress = require('keypress'); 53 | keypress(process.stdin); 54 | process.stdin.on('keypress', function (ch, key) { 55 | if (key && key.ctrl && key.name == 'c') { 56 | Config.set('logMode', 'normal'); 57 | Config.set('logTimestamp', false); 58 | monitor.stop(); 59 | router(Constants.fetch('STEP_JUST_WATCHED_FOLDER')); 60 | 61 | } 62 | }); 63 | process.stdin.setRawMode(true); 64 | process.stdin.resume(); 65 | 66 | }); 67 | 68 | } 69 | 70 | module.exports = watchFiles; -------------------------------------------------------------------------------- /lib/projectSelect.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var fs = require('fs-extra'); 4 | var glob = require("glob"); 5 | var inquirer = require("inquirer"); 6 | 7 | var Config = require('./Config.js'); 8 | var Constants = require('./Constants.js'); 9 | var lrException = require('./errorException.js'); 10 | var RouterArgs = require('./RouterArgs.js'); 11 | var createProject = require('./projectCreate.js'); 12 | var utilities = require('./utilities.js'); 13 | 14 | var selectProject = function () { 15 | 16 | if (RouterArgs.fetch('hasProject')) { 17 | // If project is supplied in arguments 18 | var projectSettingsPath = Config.fetch('projectsFolder') + '/' + RouterArgs.fetch('projectName').toLowerCase() + '.json'; 19 | if (fs.existsSync(projectSettingsPath)) { 20 | var loadProject = require('./projectLoad.js'); 21 | loadProject(RouterArgs.fetch('projectName').toLowerCase() + '.json'); 22 | } else { 23 | lrException('Project \'' + RouterArgs.fetch('projectName') + '\' does not exist.'); 24 | } 25 | 26 | } else { 27 | var globOptions = { 28 | cwd: Config.fetch('projectsFolder') 29 | }; 30 | 31 | glob('*.json', globOptions, function (err, files) { 32 | if(err) { 33 | lrException(err); 34 | } 35 | 36 | if (files.length === 0) { 37 | console.log(); 38 | console.log('Looks like it\'s the first time you run this App'); 39 | console.log(); 40 | 41 | inquirer.prompt([{ 42 | type: "list", 43 | name: "proceed", 44 | message: "What do you want to do?", 45 | choices: [ 46 | { 47 | name: 'Create a new project', 48 | value: 'new' 49 | }, 50 | { 51 | name: 'Quit', 52 | value: 'quit' 53 | } 54 | ] 55 | } 56 | ], function( answers ) { 57 | if (answers.proceed === 'new') { 58 | createProject(); 59 | } 60 | }); 61 | 62 | 63 | } else { 64 | var projectName; 65 | var projects = []; 66 | for (var i = 0; i < files.length; ++i) { 67 | projectName = utilities.filenameAndPathToFilename(files[i]); 68 | projects.push({ 69 | name: projectName, 70 | value: files[i] 71 | }); 72 | } 73 | 74 | projects.push(new inquirer.Separator()); 75 | projects.push({ 76 | name: "Create new project", 77 | value: "new" 78 | }); 79 | 80 | inquirer.prompt([{ 81 | type: "list", 82 | name: "selectProject", 83 | message: "Which project do you want to work with?", 84 | choices: projects 85 | } 86 | ], function(answers) { 87 | if (answers.selectProject === 'new') { 88 | createProject(); 89 | } else { 90 | var loadProject = require('./projectLoad.js'); 91 | loadProject(answers.selectProject); 92 | } 93 | }); 94 | 95 | } 96 | 97 | }); 98 | 99 | } 100 | 101 | }; 102 | 103 | module.exports = selectProject; -------------------------------------------------------------------------------- /lib/PortletKeys.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var portletKeys = { 4 | ADMIN: "9", 5 | ADMIN_INSTANCE: "135", 6 | ADMIN_PLUGINS: "136", 7 | ADMIN_SERVER: "137", 8 | ALERTS: "83", 9 | ANNOUNCEMENTS: "84", 10 | ASSET_BROWSER: "172", 11 | ASSET_CATEGORIES_ADMIN: "147", 12 | ASSET_CATEGORIES_NAVIGATION: "122", 13 | ASSET_PUBLISHER: "101", 14 | ASSET_TAGS_NAVIGATION: "141", 15 | BACKGROUND_TASK: "189", 16 | BLOGS: "33", 17 | BLOGS_ADMIN: "161", 18 | BLOGS_AGGREGATOR: "115", 19 | BOOKMARKS: "28", 20 | BOOKMARKS_ADMIN: "198", 21 | BREADCRUMB: "73", 22 | CALENDAR: "8", 23 | CHAT: "1_WAR_chatportlet", 24 | COMMENTS: "196", 25 | CONTROL_PANEL_HOME: "190", 26 | CONTROL_PANEL_MENU: "160", 27 | DIRECTORY: "11", 28 | DOCKBAR: "145", 29 | DOCUMENT_LIBRARY: "20", 30 | DOCUMENT_LIBRARY_ADMIN: "199", 31 | DOCUMENT_LIBRARY_DISPLAY: "110", 32 | DOCUMENT_SELECTOR: "200", 33 | DYNAMIC_DATA_LIST_DISPLAY: "169", 34 | DYNAMIC_DATA_LISTS: "167", 35 | DYNAMIC_DATA_MAPPING: "166", 36 | EXPANDO: "139", 37 | FAST_LOGIN: "164", 38 | FLAGS: "142", 39 | FRIENDS_DIRECTORY: "186", 40 | GROUP_PAGES: "156", 41 | GROUP_STATISTICS: "181", 42 | HIGHEST_RATED_ASSETS: "194", 43 | IMAGE_UPLOADER: "195", 44 | INVITATION: "100", 45 | JOURNAL: "15", 46 | JOURNAL_CONTENT: "56", 47 | JOURNAL_CONTENT_LIST: "62", 48 | JOURNAL_CONTENT_SEARCH: "77", 49 | LANGUAGE: "82", 50 | LAYOUT_PROTOTYPE: "146", 51 | LAYOUT_SET_PROTOTYPE: "149", 52 | LAYOUTS_ADMIN: "88", 53 | LIFERAY_PORTAL: "LIFERAY_PORTAL", 54 | LOGIN: "58", 55 | MAIL: "1_WAR_mailportlet", 56 | MARKETPLACE_APP_MANAGER: "3_WAR_marketplaceportlet", 57 | MARKETPLACE_STORE: "1_WAR_marketplaceportlet", 58 | MEDIA_GALLERY_DISPLAY: "31", 59 | MESSAGE_BOARDS: "19", 60 | MESSAGE_BOARDS_ADMIN: "162", 61 | MOBILE_DEVICE_SITE_ADMIN: "178", 62 | MONITORING: "131", 63 | MOST_VIEWED_ASSETS: "193", 64 | MY_ACCOUNT: "2", 65 | MY_PAGES: "140", 66 | MY_SITES: "29", 67 | MY_SITES_DIRECTORY: "188", 68 | MY_WORKFLOW_INSTANCES: "158", 69 | MY_WORKFLOW_TASKS: "153", 70 | NAVIGATION: "71", 71 | NESTED_PORTLETS: "118", 72 | PAGE_COMMENTS: "107", 73 | PAGE_RATINGS: "108", 74 | PASSWORD_POLICIES_ADMIN: "129", 75 | PLUGINS_ADMIN: "132", 76 | PORTAL: "90", 77 | PORTAL_SETTINGS: "130", 78 | PORTLET_CONFIGURATION: "86", 79 | PORTLET_CSS: "113", 80 | PORTLET_DISPLAY_TEMPLATES: "183", 81 | PORTLET_SHARING: "133", 82 | PREFS_OWNER_ID_DEFAULT: 0, 83 | PREFS_OWNER_TYPE_ARCHIVED: 5, 84 | PREFS_OWNER_TYPE_COMPANY: 1, 85 | PREFS_OWNER_TYPE_GROUP: 2, 86 | PREFS_OWNER_TYPE_LAYOUT: 3, 87 | PREFS_OWNER_TYPE_ORGANIZATION: 6, 88 | PREFS_OWNER_TYPE_USER: 4, 89 | PREFS_PLID_SHARED: 0, 90 | RECENT_BLOGGERS: "114", 91 | RECENT_CONTENT: "173", 92 | RECENT_DOCUMENTS: "64", 93 | RELATED_ASSETS: "175", 94 | REQUESTS: "121", 95 | ROLES_ADMIN: "128", 96 | RSS: "39", 97 | SEARCH: "3", 98 | SHOPPING: "34", 99 | SITE_BROWSER: "185", 100 | SITE_MAP: "85", 101 | SITE_MEMBERS_DIRECTORY: "187", 102 | SITE_MEMBERSHIPS_ADMIN: "174", 103 | SITE_REDIRECTOR: "49", 104 | SITE_SETTINGS: "165", 105 | SITE_TEAMS_ADMIN: "191", 106 | SITE_TEMPLATE_SETTINGS: "192", 107 | SITES_ADMIN: "134", 108 | SITES_DIRECTORY: "184", 109 | SOCIAL_ACTIVITY: "179", 110 | SOFTWARE_CATALOG: "98", 111 | STAGING_BAR: "170", 112 | STOCKS: "12", 113 | TAGS_ADMIN: "99" 114 | }; 115 | 116 | function fetch(prop) { 117 | if (typeof prop === 'undefined') { 118 | return portletKeys; 119 | } else { 120 | return portletKeys[prop]; 121 | } 122 | } 123 | 124 | module.exports.fetch = fetch; -------------------------------------------------------------------------------- /lib/projectLoad.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var fs = require('fs-extra'); 4 | var inquirer = require("inquirer"); 5 | 6 | var Constants = require('./Constants.js'); 7 | var Config = require('./Config.js'); 8 | var RouterArgs = require('./RouterArgs.js'); 9 | var lrException = require('./errorException.js'); 10 | var router = require('./router.js'); 11 | 12 | 13 | var loadProject = function (projectJson) { 14 | 15 | fs.readJson(Config.fetch('projectsFolder') + '/' + projectJson, function(er, project) { 16 | if(er) { 17 | lrException(er); 18 | } 19 | 20 | var hosts = []; 21 | 22 | Config.set('defaultLocale', project.defaultLocale); 23 | Config.set('filesFolder', project.filesPath); 24 | Config.set('ignoreDDMs', project.ignoreDDMs); 25 | Config.set('projectName', project.projectName); 26 | Config.set('externalDiff', project.externalDiff); 27 | Config.set('watchIgnorePattern', project.watchIgnorePattern); 28 | Config.set('ddmCacheFolder', Config.fetch('cacheFolder') + '/' + project.projectName + '/' + Constants.fetch('ddmCacheFolder')); 29 | 30 | // Allow self signed certificates if user explicitly wants to 31 | if (project.allowSelfSignedCerts === true) { 32 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0; 33 | } 34 | 35 | // Check if user supplied a project as an argument or if we should present a gui. 36 | if (RouterArgs.fetch('hasProject')) { 37 | if (project.hosts.length === 1) { 38 | // If user supplied a project and there's only one server in the config 39 | // load that config 40 | 41 | Config.set('hostFriendlyName', project.hosts[0].name); 42 | Config.set('host', project.hosts[0].host); 43 | Config.set('username', project.hosts[0].username); 44 | Config.set('password', project.hosts[0].password); 45 | Config.set('email', project.hosts[0].email); 46 | 47 | router(Constants.fetch('STEP_JUST_LOADED_CONFIG')); 48 | } else { 49 | // If the user supplied a project but there's more than one, 50 | // server in the config file, check if the user also supplied an 51 | // argument for which server to use. 52 | if (RouterArgs.fetch('hasServer') === true) { 53 | var currentServer = project.hosts.filter(function(server) { 54 | return server.name === RouterArgs.fetch('server'); 55 | }); 56 | 57 | if (currentServer.length > 0) { 58 | Config.set('hostFriendlyName', currentServer[0].name); 59 | Config.set('host', currentServer[0].host); 60 | Config.set('username', currentServer[0].username); 61 | Config.set('password', currentServer[0].password); 62 | Config.set('email', currentServer[0].email); 63 | 64 | router(Constants.fetch('STEP_JUST_LOADED_CONFIG')); 65 | } else { 66 | lrException('Server \'' + RouterArgs.fetch('server') + '\' does not exist'); 67 | } 68 | } else { 69 | lrException('If a project (--project) with more than one server is supplied\nyou need to supply a server (--server) as well'); 70 | } 71 | 72 | } 73 | } else { 74 | if (project.hosts.length === 1) { 75 | Config.set('hostFriendlyName', project.hosts[0].name); 76 | Config.set('host', project.hosts[0].host); 77 | Config.set('username', project.hosts[0].username); 78 | Config.set('password', project.hosts[0].password); 79 | Config.set('email', project.hosts[0].email); 80 | 81 | router(Constants.fetch('STEP_JUST_LOADED_CONFIG')); 82 | } else { 83 | for (var i = 0; i < project.hosts.length; ++i) { 84 | hosts.push({ 85 | name: project.hosts[i].name, 86 | value: { 87 | name: project.hosts[i].name, 88 | host: project.hosts[i].host, 89 | username: project.hosts[i].username, 90 | password: project.hosts[i].password, 91 | email: project.hosts[i].email 92 | } 93 | }); 94 | } 95 | 96 | inquirer.prompt([{ 97 | type: "list", 98 | name: "selectHosts", 99 | message: "Which host do you want to work with?", 100 | choices: hosts 101 | } 102 | ], function(answers) { 103 | Config.set('hostFriendlyName', answers.selectHosts.name); 104 | Config.set('host', answers.selectHosts.host); 105 | Config.set('username', answers.selectHosts.username); 106 | Config.set('password', answers.selectHosts.password); 107 | Config.set('email', answers.selectHosts.email); 108 | 109 | router(Constants.fetch('STEP_JUST_LOADED_CONFIG')); 110 | }); 111 | } 112 | } 113 | 114 | 115 | 116 | }); 117 | 118 | }; 119 | 120 | module.exports = loadProject; -------------------------------------------------------------------------------- /lib/uploadStructureNew.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Q = require('q'); 4 | var inquirer = require("inquirer"); 5 | var _ = require('underscore'); 6 | 7 | var Constants = require('./Constants.js'); 8 | var utilities = require('./utilities.js'); 9 | var LrClassNameConfig = require('./ClassNameConfig.js'); 10 | var Sites = require('./Sites.js'); 11 | 12 | 13 | // FIGURE OUT WHICH SITE WE'RE GOING TO CREATE THE STRUCTURE 14 | // IF THERE'S MORE THAN ONE, WE ASK THE USER WHICH SITE S/HE WANT TO 15 | // UPLOAD THE TEMPLATE TO. 16 | function getSite (newObj, returnObj) { 17 | 18 | var deferred = Q.defer(); 19 | 20 | var possibleSites = []; 21 | var questionsSites = []; 22 | 23 | Sites.fetch().forEach(function(entry) { 24 | if(LrClassNameConfig.getSingleValue('id', entry.classNameId, 'containsDDMs')) { 25 | possibleSites.push(entry.groupId); 26 | } 27 | }); 28 | 29 | if (possibleSites.length === 0) { 30 | returnObj.exception = 'Could not find any sites to upload the file to'; 31 | deferred.reject(returnObj); 32 | } else if (possibleSites.length === 1) { 33 | deferred.resolve({site: possibleSites[0], autofound: true}); 34 | } else { 35 | // MORE THAN ONE POSSIBLE SITE 36 | // ASK THE USER WHICH SITE S/HE WANT TO UPLOAD THE STRUCTURE TO 37 | // Create Question Array: 38 | possibleSites.forEach(function(entry) { 39 | questionsSites.push({ 40 | name: Sites.getSingleValue('groupId', entry, 'name') + ' [' + LrClassNameConfig.getSingleValue('id', Sites.getSingleValue('groupId', entry, 'classNameId'), 'friendlyName')+ ']', 41 | value: entry 42 | }); 43 | }); 44 | 45 | utilities.writeToScreen('-', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_NORMAL')); 46 | utilities.writeToScreen('Need some input on file: ', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_HEADING')); 47 | utilities.writeToScreen('\'' + newObj.fileFriendlyName + '\' (Structure of Type: \'' + LrClassNameConfig.getSingleValue('id', newObj.classNameId, 'friendlyName') + '\')', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_FILE')); 48 | utilities.writeToScreen('', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_HEADING')); 49 | 50 | inquirer.prompt([ 51 | { 52 | type: "list", 53 | name: "siteSelection", 54 | message: "Which site do you want to add the structure to", 55 | choices: questionsSites 56 | } 57 | ], function(answersSite) { 58 | deferred.resolve({site: answersSite.siteSelection, autofound: false}); 59 | }); 60 | } 61 | return deferred.promise; 62 | } 63 | 64 | 65 | 66 | var createUploadObject = function (newObj) { 67 | 68 | var PortletKeys = require("./PortletKeys.js"); 69 | 70 | var deferred = Q.defer(); 71 | var payload = {}; 72 | 73 | var returnObj = { 74 | exceptionFile: newObj.file, 75 | group: {} 76 | }; 77 | 78 | getSite(newObj, returnObj) 79 | .then(function (currentSite) { 80 | 81 | 82 | returnObj.group.description = Sites.getSingleValue('groupId', currentSite.site, 'description'); 83 | returnObj.group.name = Sites.getSingleValue('groupId', currentSite.site, 'name'); 84 | returnObj.group.type = LrClassNameConfig.getSingleValue('id', Sites.getSingleValue('groupId', currentSite.site, 'classNameId'), 'friendlyName'); 85 | returnObj.group.friendlyURL = Sites.getSingleValue('groupId', currentSite.site, 'friendlyURL'); 86 | returnObj.group.groupId = currentSite.site; 87 | 88 | returnObj.status = 'create'; 89 | returnObj.isStructure = true; 90 | returnObj.fileFriendlyName = newObj.fileFriendlyName; 91 | returnObj.className = LrClassNameConfig.getSingleValue('id', newObj.classNameId, 'friendlyName'); 92 | 93 | payload = { 94 | groupId: currentSite.site, //long 95 | parentStructureId: 0, //long 96 | classNameId: newObj.classNameId , //long 97 | structureKey: '', //java.lang.String 98 | nameMap: utilities.strToJsonMap(newObj.fileFriendlyName), //java.util.Map 99 | descriptionMap: {}, //java.util.Map 100 | xsd: newObj.newScript, //java.lang.String 101 | storageType: newObj.fileLanguageType, //java.lang.String 102 | type: newObj.toPayload.type, //int 103 | '+serviceContext': 'com.liferay.portal.service.ServiceContext', 104 | 'serviceContext.addGroupPermissions': true, 105 | 'serviceContext.addGuestPermissions': true, 106 | 'serviceContext.attributes': { refererPortletName: PortletKeys.fetch('JOURNAL') } 107 | }; 108 | 109 | returnObj.payload = '{"/ddmstructure/add-structure": ' + JSON.stringify(payload) + '}'; 110 | 111 | deferred.resolve(returnObj); 112 | 113 | }, function (er) { 114 | deferred.reject(er); 115 | }); 116 | 117 | return deferred.promise; 118 | 119 | }; 120 | 121 | module.exports = createUploadObject; 122 | -------------------------------------------------------------------------------- /lib/saveStructures.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var fs = require('fs-extra'); 4 | var _ = require('underscore'); 5 | 6 | var utilities = require('./utilities.js'); 7 | var Constants = require('./Constants.js'); 8 | var LrClassNameConfig = require('./ClassNameConfig.js'); 9 | var Table = require('cli-table'); 10 | 11 | var saveStructures = function(e, filesRootPath, options) { 12 | var filePath; 13 | var fileContent; 14 | 15 | var friendlyName = ''; 16 | 17 | var oldFile = ''; 18 | var downloadStatuses = []; 19 | var outTable; 20 | var states = [ 21 | { 22 | status: 'uptodate', 23 | heading: 'Already up to date' 24 | }, 25 | { 26 | status: 'update', 27 | heading: 'Updated' 28 | }, 29 | { 30 | status: 'create', 31 | heading: 'Created new' 32 | } 33 | ]; 34 | 35 | 36 | 37 | for (var i = 0; i < e.length; ++i) { 38 | 39 | filePath = filesRootPath + '/' + LrClassNameConfig.getSingleValue('id', e[i].classNameId, 'filesPath'); 40 | 41 | fileContent = e[i].xsd; 42 | 43 | // If the class is DLFileEntryMetadata, then check 'type'. 44 | // Depending on type, set different save paths for 'Document Type' and 'Metadata Set' 45 | if (e[i].classNameId === LrClassNameConfig.getSingleValue('clazz', 'com.liferay.portlet.documentlibrary.model.DLFileEntryMetadata', 'id')) { 46 | if (e[i].type === 1) { 47 | filePath = filePath + '/' + Constants.fetch('pathSlugDocumentTypes') + '/structures/' + e[i].nameCurrentValue + '.' + e[i].storageType; 48 | } else { 49 | filePath = filePath + '/' + Constants.fetch('pathSlugMetadataSets') + '/structures/' + e[i].nameCurrentValue + '.' + e[i].storageType; 50 | } 51 | } else { 52 | filePath = filePath + '/structures/' + e[i].nameCurrentValue + '.' + e[i].storageType; 53 | } 54 | 55 | 56 | // Check status (if file needs to be updated, if it doesn't or if it's new.) 57 | if (fs.existsSync(filePath)) { 58 | try { 59 | oldFile = fs.readFileSync(filePath, {encoding: Constants.fetch('filesEncoding')}); 60 | 61 | if (oldFile === fileContent) { 62 | downloadStatuses.push({ 63 | status: 'uptodate', 64 | name: e[i].nameCurrentValue, 65 | type: LrClassNameConfig.getSingleValue('id', e[i].classNameId, 'friendlyName') 66 | }); 67 | } else { 68 | downloadStatuses.push({ 69 | status: 'update', 70 | name: e[i].nameCurrentValue, 71 | type: LrClassNameConfig.getSingleValue('id', e[i].classNameId, 'friendlyName') 72 | }); 73 | fs.outputFileSync(filePath, fileContent); 74 | } 75 | } catch(catchErr) {} 76 | } else { 77 | downloadStatuses.push({ 78 | status: 'create', 79 | name: e[i].nameCurrentValue, 80 | type: LrClassNameConfig.getSingleValue('id', e[i].classNameId, 'friendlyName') 81 | }); 82 | fs.outputFileSync(filePath, fileContent); 83 | } 84 | 85 | 86 | } 87 | 88 | if (!options.silent) { 89 | // Echo what has been saved 90 | utilities.writeToScreen('', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_HEADING')); 91 | // Print already up to date 92 | var countAlreadyUpToDate = downloadStatuses.filter(function (entry) { 93 | return entry.status == states[0].status; 94 | }); 95 | if (countAlreadyUpToDate.length > 0) { 96 | utilities.writeToScreen(utilities.pad(countAlreadyUpToDate.length, 3, 'left') + ' structures - ' + states[0].heading, Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_SAVE')); 97 | } 98 | 99 | // Print update and create new 100 | for (var z = 1; z < states.length; z++) { 101 | 102 | /*jshint -W083 */ 103 | var outArr = downloadStatuses.filter(function (entry) { 104 | return entry.status == states[z].status; 105 | }); 106 | /*jshint +W083 */ 107 | 108 | if (outArr.length > 0) { 109 | utilities.writeToScreen(utilities.pad(outArr.length, 3, 'left') + ' structures - ' + states[z].heading, Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_SAVE')); 110 | 111 | outTable = new Table({ 112 | head: ['Name', 'Type'], 113 | chars: { 114 | 'top': '', 'top-mid': '', 'top-left': '', 'top-right': '', 115 | 'bottom': '', 'bottom-mid': '', 'bottom-left': '', 'bottom-right': '', 116 | 'left': '', 'left-mid': '', 'mid': '', 'mid-mid': '', 117 | 'right': '', 'right-mid': '', 'middle': ' ' 118 | }, 119 | style: { 120 | 'padding-left': 2, 121 | 'padding-right': 0, 122 | 'head': ['magenta'] 123 | }, 124 | colWidths: [40] 125 | }); 126 | 127 | for (var x = 0; x < outArr.length; x++) { 128 | outTable.push([ 129 | outArr[x].name, 130 | outArr[x].type 131 | ]); 132 | } 133 | utilities.writeToScreen(outTable.toString(), Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_INFO')); 134 | } 135 | } 136 | } 137 | 138 | }; 139 | 140 | module.exports = saveStructures; -------------------------------------------------------------------------------- /lib/saveTemplates.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var fs = require('fs-extra'); 4 | var _ = require('underscore'); 5 | 6 | var utilities = require('./utilities.js'); 7 | var Constants = require('./Constants.js'); 8 | var LrClassNameConfig = require('./ClassNameConfig.js'); 9 | var Table = require('cli-table'); 10 | 11 | var saveTemplates = function(e, filesRootPath, options) { 12 | 13 | var Structures = require('./Structures.js'); 14 | 15 | var filePath; 16 | var fileContent; 17 | 18 | var curStructureClassNameId = 0; 19 | var curStructureFilesPath; 20 | 21 | var friendlyName = ''; 22 | 23 | var oldFile = ''; 24 | var downloadStatuses = []; 25 | var outTable; 26 | var states = [ 27 | { 28 | status: 'uptodate', 29 | heading: 'Already up to date' 30 | }, 31 | { 32 | status: 'update', 33 | heading: 'Updated' 34 | }, 35 | { 36 | status: 'create', 37 | heading: 'Created new' 38 | } 39 | ]; 40 | 41 | 42 | 43 | for (var i = 0; i < e.length; ++i) { 44 | 45 | // Check if template has a structure connected to it. If so, then we should grab the filesPath from the 46 | // structure. 47 | if (e[i].classPK > 0) { 48 | curStructureClassNameId = Structures.getSingleValue('structureId', e[i].classPK, 'classNameId'); 49 | curStructureFilesPath = LrClassNameConfig.getSingleValue('id', curStructureClassNameId, 'filesPath'); 50 | filePath = filesRootPath + '/' + curStructureFilesPath; 51 | friendlyName = LrClassNameConfig.getSingleValue('id', curStructureClassNameId, 'friendlyName'); 52 | } else { 53 | // If template does NOT have a structure connected to it (such as ADT:s or journalTemplates without a structure 54 | // connected to it). 55 | filePath = filesRootPath + '/' + LrClassNameConfig.getSingleValue('id', e[i].classNameId, 'filesPath'); 56 | friendlyName = LrClassNameConfig.getSingleValue('id', e[i].classNameId, 'friendlyName'); 57 | } 58 | 59 | filePath = filePath + '/templates/' + e[i].nameCurrentValue + '.' + e[i].language; 60 | fileContent = e[i].script; 61 | 62 | 63 | 64 | // Check status (if file needs to be updated, if it doesn't or if it's new.) 65 | if (fs.existsSync(filePath)) { 66 | try { 67 | oldFile = fs.readFileSync(filePath, {encoding: Constants.fetch('filesEncoding')}); 68 | 69 | if (oldFile === fileContent) { 70 | downloadStatuses.push({ 71 | status: 'uptodate', 72 | name: e[i].nameCurrentValue, 73 | type: friendlyName 74 | }); 75 | } else { 76 | downloadStatuses.push({ 77 | status: 'update', 78 | name: e[i].nameCurrentValue, 79 | type: friendlyName 80 | }); 81 | fs.outputFileSync(filePath, fileContent); 82 | } 83 | } catch(catchErr) {} 84 | } else { 85 | downloadStatuses.push({ 86 | status: 'create', 87 | name: e[i].nameCurrentValue, 88 | type: friendlyName 89 | }); 90 | fs.outputFileSync(filePath, fileContent); 91 | } 92 | } 93 | 94 | 95 | 96 | 97 | // Echo what has been saved 98 | if (!options.silent) { 99 | // Print already up to date 100 | var countAlreadyUpToDate = downloadStatuses.filter(function (entry) { 101 | return entry.status == states[0].status; 102 | }); 103 | if (countAlreadyUpToDate.length > 0) { 104 | utilities.writeToScreen(utilities.pad(countAlreadyUpToDate.length, 3, 'left') + ' templates - ' + states[0].heading, Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_SAVE')); 105 | } 106 | 107 | // Print update and create new 108 | for (var z = 1; z < states.length; z++) { 109 | 110 | /*jshint -W083 */ 111 | var outArr = downloadStatuses.filter(function (entry) { 112 | return entry.status == states[z].status; 113 | }); 114 | /*jshint +W083 */ 115 | 116 | if (outArr.length > 0) { 117 | utilities.writeToScreen(utilities.pad(outArr.length, 3, 'left') + ' templates - ' + states[z].heading, Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_SAVE')); 118 | outTable = new Table({ 119 | head: ['Name', 'Type'], 120 | chars: { 121 | 'top': '', 'top-mid': '', 'top-left': '', 'top-right': '', 122 | 'bottom': '', 'bottom-mid': '', 'bottom-left': '', 'bottom-right': '', 123 | 'left': '', 'left-mid': '', 'mid': '', 'mid-mid': '', 124 | 'right': '', 'right-mid': '', 'middle': ' ' 125 | }, 126 | style: { 127 | 'padding-left': 2, 128 | 'padding-right': 0, 129 | 'head': ['magenta'] 130 | }, 131 | colWidths: [40] 132 | }); 133 | 134 | for (var x = 0; x < outArr.length; x++) { 135 | outTable.push([ 136 | outArr[x].name, 137 | outArr[x].type 138 | ]); 139 | } 140 | utilities.writeToScreen(outTable.toString(), Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_INFO')); 141 | } 142 | } 143 | } 144 | 145 | }; 146 | 147 | module.exports = saveTemplates; -------------------------------------------------------------------------------- /lib/uploadStructure.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var LrClassNameConfig = require('./ClassNameConfig.js'); 4 | 5 | function findClassName(filePathSlug) { 6 | var possibleClassNames = LrClassNameConfig.getAllFilter('mayHaveStructures', true).filter(function (entry) { 7 | return entry.filesPath === filePathSlug; 8 | }); 9 | 10 | if (possibleClassNames.length === 0) { 11 | return false; 12 | } else if (possibleClassNames.length === 1) { 13 | return possibleClassNames[0].id; 14 | } else { 15 | return false; 16 | } 17 | } 18 | 19 | var coStructure = function (file) { 20 | 21 | var Q = require('q'); 22 | var fs = require('fs-extra'); 23 | 24 | var Constants = require('./Constants.js'); 25 | var Structures = require('./Structures.js'); 26 | var Templates = require('./Templates.js'); 27 | 28 | var utilities = require('./utilities.js'); 29 | 30 | var deferred = Q.defer(); 31 | 32 | var newObj = { 33 | fileLanguageType: utilities.filenameToLanguageType(file), 34 | fileFriendlyName: utilities.filenameAndPathToFilename(file), 35 | file: file, 36 | isStructure: true, 37 | isNewDDM: false, 38 | isUpdatingDDM: false, 39 | toPayload: { 40 | type: 0 41 | } 42 | }; 43 | 44 | var returnObj = { 45 | exceptionFile: file, 46 | group: {} 47 | }; 48 | 49 | try { 50 | newObj.newScript = fs.readFileSync(file, {encoding: Constants.fetch('filesEncoding')}); 51 | } catch(catchErr) { 52 | returnObj.exception = 'Could not read file '; 53 | deferred.reject(returnObj); 54 | return deferred.promise; 55 | } 56 | 57 | 58 | // FIGURE OUT WHAT KIND OF STRUCTURE IT IS 59 | var filePathSplit = file.split('/'); 60 | newObj.classNameId = findClassName(filePathSplit[filePathSplit.length - 4] + '/' + filePathSplit[filePathSplit.length - 3]); 61 | if (!newObj.classNameId) { 62 | newObj.classNameId = findClassName(filePathSplit[filePathSplit.length - 3]); 63 | } 64 | if (!newObj.classNameId) { 65 | newObj.classNameId = findClassName(filePathSplit[filePathSplit.length - 4]); 66 | if(newObj.classNameId) { 67 | // If the structure is a document and media structure we need to 68 | // find out what type ('document type' or 'metadata set') it is. 69 | if (filePathSplit[filePathSplit.length - 3] === Constants.fetch('pathSlugDocumentTypes')) { 70 | newObj.toPayload.type = 1; 71 | newObj.isDLFileEntryType = true; 72 | } else if (filePathSplit[filePathSplit.length - 3] === Constants.fetch('pathSlugMetadataSets')) { 73 | newObj.toPayload.type = 0; 74 | } else { 75 | returnObj.exception = 'Could not figure out what type of Document and Media Structure it is. (Error 6002)'; 76 | deferred.reject(returnObj); 77 | return deferred.promise; 78 | } 79 | } 80 | } 81 | if (!newObj.classNameId) { 82 | returnObj.exception = 'Could not find matching classNameId. (Error 6001)'; 83 | deferred.reject(returnObj); 84 | return deferred.promise; 85 | } 86 | 87 | // FIGURE OUT IF IT'S A NEW STRUCTURE OR AN ALREADY EXISTING ONE WE NEED TO UPDATE. 88 | 89 | // Get all structures or the same class 90 | var possibleStructures = Structures.fetch().filter(function (entry) { 91 | return entry.classNameId === newObj.classNameId; 92 | }); 93 | 94 | // Compare names 95 | possibleStructures = possibleStructures.filter(function (entry) { 96 | return entry.nameCurrentValue === newObj.fileFriendlyName; 97 | }); 98 | if (possibleStructures.length === 1) { 99 | newObj.isUpdatingDDM = true; 100 | newObj.oldObj = possibleStructures[0]; 101 | } else if (possibleStructures.length === 0) { 102 | newObj.isNewDDM = true; 103 | } else { 104 | returnObj.exception = 'More than one structure named the same thing. (Error 6003)'; 105 | deferred.reject(returnObj); 106 | return deferred.promise; 107 | } 108 | 109 | 110 | if (newObj.isUpdatingDDM) { 111 | var createUploadObjectUpdateStructure = require('./uploadStructureUpdate.js'); 112 | createUploadObjectUpdateStructure(newObj).then( 113 | function (resp) { 114 | deferred.resolve(resp); 115 | return deferred.promise; 116 | }, 117 | function (resp) { 118 | deferred.reject(resp); 119 | return deferred.promise; 120 | }); 121 | } else if (newObj.isNewDDM) { 122 | 123 | if(newObj.isDLFileEntryType) { 124 | // TODO. Creating new DL Document Entry Type is not supported as of now (as they can not be uploaded as 125 | // all the other Structures but rather need a special call to '/dlfileentrytype/add-file-entry-type'). 126 | // therefor, right now we're just returning an ignore flag. 127 | returnObj.ignore = true; 128 | returnObj.ignoreMsg = 'Creating new \'Document and Media - Document Types\' is not supported right now. You may however download them from server and update existing ones.'; 129 | deferred.resolve(returnObj); 130 | } else { 131 | var createUploadObjectNewStructure = require('./uploadStructureNew.js'); 132 | createUploadObjectNewStructure(newObj).then( 133 | function (resp) { 134 | deferred.resolve(resp); 135 | return deferred.promise; 136 | }, 137 | function (resp) { 138 | deferred.reject(resp); 139 | return deferred.promise; 140 | }); 141 | } 142 | } else { 143 | returnObj.exception = 'Could not figure out if I should update or create new Structure (Error 6004)'; 144 | deferred.reject(returnObj); 145 | } 146 | 147 | 148 | return deferred.promise; 149 | }; 150 | 151 | module.exports = coStructure; 152 | 153 | 154 | -------------------------------------------------------------------------------- /lib/uploadTemplate.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var coTemplate = function (file) { 4 | 5 | var Q = require('q'); 6 | var fs = require('fs-extra'); 7 | var _ = require('underscore'); 8 | 9 | var Constants = require('./Constants.js'); 10 | var LrClassNameConfig = require('./ClassNameConfig.js'); 11 | var Structures = require('./Structures.js'); 12 | var Templates = require('./Templates.js'); 13 | 14 | var utilities = require('./utilities.js'); 15 | 16 | var deferred = Q.defer(); 17 | 18 | var newObj = { 19 | fileLanguageType: utilities.filenameToLanguageType(file), 20 | file: file, 21 | validStructures: [], 22 | validTemplates: [], 23 | isTemplate: true, 24 | isStructure: false, 25 | isNewDDM: false, 26 | isUpdatingDDM: false, 27 | isADT: false 28 | }; 29 | 30 | var returnObj = { 31 | exceptionFile: file, 32 | group: {} 33 | }; 34 | 35 | try { 36 | newObj.newScript = fs.readFileSync(file, {encoding: Constants.fetch('filesEncoding')}); 37 | } catch(catchErr) { 38 | returnObj.exception = 'Could not read file '; 39 | deferred.reject(returnObj); 40 | return deferred.promise; 41 | } 42 | 43 | 44 | var filePathSplit = file.split('/'); 45 | var identifyingSingleFilePath = filePathSplit[filePathSplit.length - 3]; 46 | var identifyingDoubleFilePath = filePathSplit[filePathSplit.length - 4] + '/' + filePathSplit[filePathSplit.length - 3]; 47 | 48 | // FIGURE OUT WHAT KIND OF TEMPLATE IT IS 49 | 50 | // Check if the file path with two levels 51 | // (like 'application_display_template/asset_publisher') match a template 52 | var possibleClassNames = LrClassNameConfig.getAllFilter('mayHaveTemplates', true).filter(function (entry) { 53 | return entry.filesPath === identifyingDoubleFilePath; 54 | }); 55 | 56 | if (possibleClassNames.length === 0) { 57 | // We didn't get a match when we tried to compare the two level file path, 58 | // now try with a single level file path instead. 59 | possibleClassNames = LrClassNameConfig.getAllFilter('mayHaveTemplates', true).filter(function (entry) { 60 | return entry.filesPath === identifyingSingleFilePath; 61 | }); 62 | 63 | 64 | if (possibleClassNames.length === 1) { 65 | newObj.classNameId = possibleClassNames[0].id; 66 | } else if (possibleClassNames.length === 0) { 67 | returnObj.exception = 'Template didn\'t match structure. (Error 5001)'; 68 | deferred.reject(returnObj); 69 | return deferred.promise; 70 | } else { 71 | returnObj.exception = 'Template matched more than one structure. (Error 5003)'; 72 | deferred.reject(returnObj); 73 | return deferred.promise; 74 | } 75 | 76 | } else if (possibleClassNames.length === 1) { 77 | newObj.classNameId = possibleClassNames[0].id; 78 | } else { 79 | returnObj.exception = 'Template matched more than one structure. (Error 5002)'; 80 | deferred.reject(returnObj); 81 | return deferred.promise; 82 | } 83 | 84 | // This is the classNameId of the structures we are allowed to bind the template to 85 | newObj.validStructuresClassNameId = possibleClassNames[0].id; 86 | 87 | // Get linked structure if needed 88 | if (possibleClassNames[0].isNativeDDM) { 89 | // newObj.classNameId = LrClassNameConfig.getSingleValue('clazz', possibleClassNames[0].structure, 'id'); 90 | newObj.classNameId = LrClassNameConfig.getSingleValue('thisIsTheNativeDDM', true, 'id'); 91 | } 92 | 93 | // ADTs don't have a classPK, so we might just as well set it right here. 94 | if (LrClassNameConfig.getSingleValue('id', newObj.classNameId, 'isADT')) { 95 | newObj.classPK = 0; 96 | newObj.isADT = true; 97 | } else { 98 | newObj.isADT = false; 99 | } 100 | 101 | if (!newObj.isADT) { // If "normal" template, such as journal or ddm 102 | // These are all structures we are allowed to bind the template to 103 | newObj.validStructures = Structures.getAllFilter('classNameId', newObj.validStructuresClassNameId); 104 | 105 | // These are all templates of the same type as the one we're trying to upload, 106 | // this is the array of templates we'll be looking in to see if it's a new template or we need to update 107 | // an old template. 108 | for (var z = 0; z < newObj.validStructures.length; z++) { 109 | newObj.validStructures[z].validTemplates = Templates.getAllFilter('classPK', newObj.validStructures[z].structureId); 110 | 111 | // This is a bit dirty but I'm saving the templates again, this time as a flat 112 | // array, to make it a bit easier to see if this is a new template or an old 113 | // one which needs to be updated. 114 | /*jshint -W083 */ 115 | newObj.validStructures[z].validTemplates.forEach(function (entry) { 116 | newObj.validTemplates.push(entry); 117 | }); 118 | /*jshint +W083 */ 119 | 120 | } 121 | 122 | } else { // if ADT template. 123 | newObj.validTemplates = Templates.getAllFilter('classNameId', newObj.classNameId); 124 | } 125 | 126 | // Figure out if it's a new template or an already existing one we need to update. 127 | newObj.fileFriendlyName = utilities.filenameAndPathToFilename(file); 128 | var matchingTemplate = newObj.validTemplates.filter(function (entry) { 129 | return entry.nameCurrentValue === newObj.fileFriendlyName; 130 | }); 131 | if (matchingTemplate.length === 1) { 132 | newObj.isUpdatingDDM = true; 133 | newObj.oldObj = matchingTemplate[0]; 134 | } else if (matchingTemplate.length === 0) { 135 | newObj.isNewDDM = true; 136 | } else { 137 | returnObj.exception = 'More than one template named the same thing: \''+ newObj.fileFriendlyName + '\' (Error 5004)'; 138 | deferred.reject(returnObj); 139 | return deferred.promise; 140 | } 141 | 142 | // Set some friendly Output things we need to make a nice output to the user 143 | newObj.className = LrClassNameConfig.getSingleValue('id', newObj.validStructuresClassNameId, 'friendlyName'); 144 | 145 | if (newObj.isUpdatingDDM) { 146 | var createUploadObjectUpdateTemplate = require('./uploadTemplateUpdate.js'); 147 | createUploadObjectUpdateTemplate(newObj).then( 148 | function (resp) { 149 | deferred.resolve(resp); 150 | return deferred.promise; 151 | }, 152 | function (resp) { 153 | deferred.reject(resp); 154 | return deferred.promise; 155 | }); 156 | } else if (newObj.isNewDDM) { 157 | var createUploadObjectCreateTemplate = require('./uploadTemplateNew.js'); 158 | createUploadObjectCreateTemplate(newObj).then( 159 | function (resp) { 160 | deferred.resolve(resp); 161 | return deferred.promise; 162 | }, 163 | function (resp) { 164 | deferred.reject(resp); 165 | return deferred.promise; 166 | }); 167 | } 168 | 169 | return deferred.promise; 170 | }; 171 | 172 | module.exports = coTemplate; 173 | -------------------------------------------------------------------------------- /lib/ClassNameConfig.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var LrClassNameConfig = [ 4 | { 5 | filesPath: 'application_display_template/asset_publisher', 6 | friendlyName: 'ADT - Asset Publisher', 7 | clazz: 'com.liferay.portlet.asset.model.AssetEntry', 8 | // type: 'template', 9 | mayHaveTemplates: true, 10 | getTemplate: true, 11 | isADT: true 12 | }, 13 | { 14 | filesPath: 'application_display_template/blogs', 15 | friendlyName: 'ADT - Blogs', 16 | clazz: 'com.liferay.portlet.blogs.model.BlogsEntry', 17 | // type: 'template', 18 | mayHaveTemplates: true, 19 | getTemplate: true, 20 | isADT: true 21 | }, 22 | { 23 | filesPath: 'application_display_template/categories_navigation', 24 | friendlyName: 'ADT - Categories Navigation', 25 | clazz: 'com.liferay.portlet.asset.model.AssetCategory', 26 | // type: 'template', 27 | mayHaveTemplates: true, 28 | getTemplate: true, 29 | isADT: true 30 | }, 31 | { 32 | filesPath: 'application_display_template/documents_and_media', 33 | friendlyName: 'ADT - Documents and Media', 34 | clazz: 'com.liferay.portal.kernel.repository.model.FileEntry', 35 | // type: 'template', 36 | mayHaveTemplates: true, 37 | getTemplate: true, 38 | isADT: true 39 | }, 40 | { 41 | filesPath: 'application_display_template/site_map', 42 | friendlyName: 'ADT - Site Map', 43 | clazz: 'com.liferay.portal.model.LayoutSet', 44 | // type: 'template', 45 | mayHaveTemplates: true, 46 | getTemplate: true, 47 | isADT: true 48 | }, 49 | { 50 | filesPath: 'application_display_template/tags_navigation', 51 | friendlyName: 'ADT - Tags Navigation', 52 | clazz: 'com.liferay.portlet.asset.model.AssetTag', 53 | // type: 'template', 54 | mayHaveTemplates: true, 55 | getTemplate: true, 56 | isADT: true 57 | }, 58 | { 59 | filesPath: 'application_display_template/wiki', 60 | friendlyName: 'ADT - Wiki', 61 | clazz: 'com.liferay.portlet.wiki.model.WikiPage', 62 | // type: 'template', 63 | mayHaveTemplates: true, 64 | getTemplate: true, 65 | isADT: true 66 | }, 67 | { 68 | filesPath: 'journal', // This is only used if the journalTemplate does not have a structure connected to it. 69 | friendlyName: 'Journal Article Template', 70 | clazz: 'com.liferay.portlet.dynamicdatamapping.model.DDMStructure', 71 | // type: 'template', 72 | getTemplate: true, 73 | thisIsTheNativeDDM: true 74 | }, 75 | { 76 | filesPath: 'journal', 77 | friendlyName: 'Journal Article Structure', 78 | clazz: 'com.liferay.portlet.journal.model.JournalArticle', 79 | mayHaveTemplates: true, 80 | mayHaveStructures: true, 81 | // structure: 'com.liferay.portlet.dynamicdatamapping.model.DDMStructure', 82 | isNativeDDM: true 83 | // type: 'journalStructure' 84 | }, 85 | { 86 | filesPath: 'document_and_media', 87 | friendlyName: 'Document Types', 88 | clazz: 'com.liferay.portlet.documentlibrary.model.DLFileEntryMetadata', 89 | mayHaveStructures: true 90 | // type: 'documentAndMedia' 91 | }, 92 | { 93 | filesPath: 'internal', 94 | friendlyName: 'Liferay Internal - RAW Metadata Processor', 95 | clazz: 'com.liferay.portlet.documentlibrary.util.RawMetadataProcessor' 96 | }, 97 | { 98 | filesPath: 'dynamic_data_lists', 99 | friendlyName: 'Dynamic Data List (DDL) Definition', 100 | clazz: 'com.liferay.portlet.dynamicdatalists.model.DDLRecordSet', 101 | mayHaveStructures: true 102 | }, 103 | { 104 | friendlyName: 'User site', 105 | clazz: 'com.liferay.portal.model.User', 106 | // type: 'group', 107 | containsDDMs: false 108 | }, 109 | { 110 | friendlyName: 'Group', 111 | clazz: 'com.liferay.portal.model.Group', 112 | // type: 'group', 113 | containsDDMs: true 114 | }, 115 | { 116 | friendlyName: 'Organization', 117 | clazz: 'com.liferay.portal.model.Organization', 118 | // type: 'group', 119 | containsDDMs: true 120 | }, 121 | { 122 | friendlyName: 'Company/Global', 123 | clazz: 'com.liferay.portal.model.Company', 124 | // type: 'group', 125 | containsDDMs: true 126 | } 127 | ]; 128 | 129 | 130 | function fetch(entry, prop) { 131 | if (typeof prop === 'undefined') { 132 | return LrClassNameConfig; 133 | } else { 134 | return LrClassNameConfig[entry][prop]; 135 | } 136 | } 137 | 138 | 139 | function setAll(arr) { 140 | LrClassNameConfig = arr; 141 | } 142 | 143 | function addToEntry(entry, prop, val) { 144 | LrClassNameConfig[entry][prop] = val; 145 | } 146 | 147 | function add(entry) { 148 | LrClassNameConfig.push(entry); 149 | } 150 | 151 | function checkExist(lookForProperty, lookForValue) { 152 | var ret = LrClassNameConfig.filter(function(entry) { 153 | return entry[lookForProperty] == lookForValue; 154 | }); 155 | 156 | return ret.length > 0; 157 | } 158 | 159 | function getAllFilter(lookForProperty, lookForValue) { 160 | return LrClassNameConfig.filter(function(entry) { 161 | return entry[lookForProperty] == lookForValue; 162 | }); 163 | } 164 | 165 | 166 | 167 | function getSingleValue(lookForProperty, lookForValue, returnProperty) { 168 | var ret = LrClassNameConfig.filter(function(entry) { 169 | return entry[lookForProperty] == lookForValue; 170 | }); 171 | 172 | if (ret.length === 1) { 173 | return ret[0][returnProperty]; 174 | } else { 175 | return undefined; 176 | } 177 | } 178 | 179 | function loadCustomClassNames() { 180 | var fs = require('fs-extra'); 181 | var Constants = require('./Constants.js'); 182 | var Config = require('./Config.js'); 183 | var lrException = require('./errorException.js'); 184 | var customClassNameConfig; 185 | var customClassNameConfigFile = Config.fetch('settingsFolder') + '/' + Constants.fetch('customClassNameConfig'); 186 | if (fs.existsSync(customClassNameConfigFile)) { 187 | try { 188 | customClassNameConfig = fs.readJsonSync(customClassNameConfigFile, {encoding: Constants.fetch('filesEncoding')}); 189 | for (var i = 0; i < customClassNameConfig.length; i++) { 190 | customClassNameConfig[i].custom = true; 191 | add(customClassNameConfig[i]); 192 | } 193 | } catch(er) { 194 | lrException('Found ' + customClassNameConfigFile + ' but could not understand it!\n' + er ); 195 | } 196 | } 197 | } 198 | 199 | module.exports.fetch = fetch; 200 | module.exports.setAll = setAll; 201 | module.exports.addToEntry = addToEntry; 202 | module.exports.add = add; 203 | module.exports.checkExist = checkExist; 204 | module.exports.getAllFilter = getAllFilter; 205 | module.exports.getSingleValue = getSingleValue; 206 | module.exports.loadCustomClassNames = loadCustomClassNames; -------------------------------------------------------------------------------- /lib/utilities.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var parseString = require('xml2js').parseString; 4 | var clc = require('cli-color'); 5 | var Q = require('q'); 6 | 7 | var Constants = require('./Constants.js'); 8 | var Config = require('./Config.js'); 9 | var lrException = require('./errorException.js'); 10 | 11 | 12 | var utilities = { 13 | filenameAndPathToPath: function (path) { 14 | path = path.substr(0, path.lastIndexOf('/')); 15 | return path; 16 | }, 17 | filenameAndPathToFilename: function (path, showExtension) { 18 | showExtension = typeof showExtension !== 'undefined' ? showExtension : false; 19 | path = path.substr(path.lastIndexOf('/')+1); 20 | if (!showExtension && path.indexOf('.') > 0) { 21 | path = path.substr(0, path.lastIndexOf('.')); 22 | } 23 | return path; 24 | }, 25 | filenameToLanguageType: function (filename) { 26 | if (filename.indexOf('.') > 0) { 27 | var languageType = filename.substr(filename.lastIndexOf('.')+1).toLowerCase(); 28 | if (languageType === 'ftl') { 29 | return 'ftl'; 30 | } else if (languageType === 'vm') { 31 | return 'vm'; 32 | } else if (languageType === 'xml') { 33 | return 'xml'; 34 | } else { 35 | lrException('Could not extract Language Type from filename \'' + filename + '\''); 36 | } 37 | } else { 38 | lrException('Could not extract Language Type from filename \'' + filename + '\''); 39 | } 40 | }, 41 | writeToScreen: function (str, severity, type) { 42 | 43 | var outStr = ''; 44 | var printSeverity = 0; 45 | 46 | if (str === '-') { 47 | str = '\n--------------------------------------------------------------------------------\n'; 48 | } 49 | 50 | if (Config.fetch('logMode') === 'sparse') { 51 | printSeverity = 1; 52 | } 53 | 54 | 55 | if (severity >= printSeverity) { 56 | if (type == Constants.fetch('SCREEN_PRINT_INFO')) { 57 | outStr = str; 58 | } else if (type == Constants.fetch('SCREEN_PRINT_SAVE')) { 59 | outStr = clc.green(str); 60 | } else if (type == Constants.fetch('SCREEN_PRINT_HEADING')) { 61 | outStr = clc.blue(str); 62 | } else if (type == Constants.fetch('SCREEN_PRINT_FILE')) { 63 | outStr = clc.yellow(str); 64 | } else if (type == Constants.fetch('SCREEN_PRINT_ERROR')) { 65 | outStr = clc.red(str); 66 | } else { 67 | outStr = str; 68 | } 69 | 70 | if (Config.fetch('logTimestamp')) { 71 | var currentdate = new Date(); 72 | var timestamp = "[" + 73 | currentdate.getFullYear() + '-' + 74 | utilities.padLeft((currentdate.getMonth()+1), 2) + '-' + 75 | utilities.padLeft(currentdate.getDate(), 2) + " " + 76 | utilities.padLeft(currentdate.getHours(), 2) + ":" + 77 | utilities.padLeft(currentdate.getMinutes(), 2) + ":" + 78 | utilities.padLeft(currentdate.getSeconds(), 2) + 79 | "] "; 80 | 81 | outStr = timestamp + outStr; 82 | } 83 | 84 | console.log(outStr); 85 | } 86 | }, 87 | padLeft: function (str, width, padCharacter) { 88 | padCharacter = padCharacter || '0'; 89 | str = str + ''; 90 | return str.length >= width ? str : new Array(width - str.length + 1).join(padCharacter) + str; 91 | }, 92 | pad: function (str, len, dir, pad) { 93 | if (typeof(len) == "undefined") { len = 0; } 94 | if (typeof(pad) == "undefined") { pad = ' '; } 95 | if (typeof(dir) == "undefined") { dir = 'right'; } 96 | str = '' + str; 97 | 98 | if (len + 1 >= str.length) { 99 | 100 | switch (dir){ 101 | 102 | case 'left': 103 | str = new Array(len + 1 - str.length).join(pad) + str; 104 | break; 105 | default: 106 | str = str + new Array(len + 1 - str.length).join(pad); 107 | break; 108 | } 109 | } 110 | 111 | return str; 112 | 113 | }, 114 | strToJsonMap: function (str) { 115 | var ret = {}; 116 | str = str.trim(); 117 | if (str.length > 0) { 118 | ret[Config.fetch('defaultLocale')] = str; 119 | } else { 120 | ret = {}; 121 | } 122 | return ret; 123 | }, 124 | xmlMapToObj: function (xml, type){ 125 | var deferred = Q.defer(); 126 | if (xml.length > 0) { 127 | parseString(xml, function (err, result) { 128 | if (err) { 129 | lrException('Could not parse XML for ' + type); 130 | } 131 | 132 | var out = {}; 133 | var prop = ''; 134 | var val = ''; 135 | 136 | if(result.root[type] === undefined) { 137 | deferred.resolve(null); 138 | } else { 139 | for (var i = 0; i < result.root[type].length; i++) { 140 | val = result.root[type][i]._; 141 | val = val.replace(/[^a-zA-Z0-9 \.-]/g, ""); 142 | prop = result.root[type][i].$['language-id']; 143 | out[prop] = val; 144 | } 145 | deferred.resolve(out); 146 | } 147 | }); 148 | } else { 149 | deferred.resolve(null); 150 | } 151 | return deferred.promise; 152 | }, 153 | removeTrailingSlash: function (str) { 154 | if (str.charAt(str.length - 1) == "/") str = str.substr(0, str.length - 1); 155 | return str; 156 | }, 157 | isJson: function (text) { 158 | if (/^[\],:{}\s]*$/.test(text.replace(/\\["\\\/bfnrtu]/g, '@'). 159 | replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). 160 | replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 161 | return true; 162 | }else{ 163 | return false; 164 | } 165 | }, 166 | stringifyNumber: function (n) { 167 | var special = ['zeroth','first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eighth', 'ninth', 'tenth', 'eleventh', 'twelvth', 'thirteenth', 'fourteenth', 'fifteenth', 'sixteenth', 'seventeenth', 'eighteenth', 'nineteenth']; 168 | var deca = ['twent', 'thirt', 'fourt', 'fift', 'sixt', 'sevent', 'eight', 'ninet']; 169 | if (n < 20) return special[n]; 170 | if (n%10 === 0) return deca[Math.floor(n/10)-2] + 'ieth'; 171 | return deca[Math.floor(n/10)-2] + 'y-' + special[n%10]; 172 | }, 173 | getUserHome: function() { 174 | return process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE; 175 | }, 176 | filenameIsNotBlacklisted: function (file) { 177 | var blacklistPattern = Config.fetch('watchIgnorePattern'); 178 | if (blacklistPattern !== undefined) { 179 | var filename = utilities.filenameAndPathToFilename(file, true); 180 | var re = new RegExp(blacklistPattern); 181 | var shouldBeIgnored = re.test(filename); 182 | 183 | return !shouldBeIgnored; 184 | } else { 185 | return true; 186 | } 187 | } 188 | 189 | }; 190 | 191 | module.exports = utilities; -------------------------------------------------------------------------------- /lib/download.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Q = require('q'); 4 | var _ = require('underscore'); 5 | 6 | var utilities = require('./utilities.js'); 7 | var Constants = require('./Constants.js'); 8 | var lrException = require('./errorException.js'); 9 | var cache = require('./cache.js'); 10 | var getData = require('./getData.js'); 11 | var LrClassNameConfig = require('./ClassNameConfig.js'); 12 | var Sites = require('./Sites.js'); 13 | var Structures = require('./Structures.js'); 14 | var Templates = require('./Templates.js'); 15 | var Config = require('./Config.js'); 16 | 17 | var CompanyId = 0; 18 | 19 | var download = { 20 | downloadAllFromServer: function () { 21 | 22 | utilities.writeToScreen('Getting data from server', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_INFO')); 23 | 24 | Q.resolve() 25 | .then(cache.clearCache) 26 | .then(download.getClassNameIds) 27 | .then(download.getUserSites) 28 | .then(download.getCompanyGroup) 29 | .then(download.getUserId) 30 | .then(download.getStructures) 31 | .then(download.getTemplates) 32 | .then(function () { 33 | 34 | }) 35 | .done(function () { 36 | var router = require('./router.js'); 37 | router(Constants.fetch('STEP_JUST_READ_ALL_FROM_SERVER')); 38 | }, function (e) { 39 | lrException(e); 40 | }); 41 | }, 42 | 43 | 44 | 45 | getUserId: function() { 46 | utilities.writeToScreen('Downloading user info', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_INFO')); 47 | return getData('{"/user/get-user-id-by-email-address": {"companyId": ' + CompanyId + ', "emailAddress": "' + Config.fetch('email') + '"}}').then( 48 | function (e) { 49 | if(e.length === 0) { 50 | throw Error('Could not find UserID'); 51 | } else { 52 | Config.set('email', e); 53 | } 54 | }); 55 | }, 56 | 57 | 58 | getClassNameIds: function () { 59 | utilities.writeToScreen('Downloading id\'s', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_INFO')); 60 | 61 | var payload = []; 62 | for (var i = 0; i < LrClassNameConfig.fetch().length; i++) { 63 | payload.push('{"/classname/fetch-class-name-id": {"value": "' + LrClassNameConfig.fetch(i, 'clazz') + '"}}'); 64 | } 65 | 66 | return getData('[' + payload.join() + ']').then( 67 | function (e) { 68 | for (var i = 0; i < LrClassNameConfig.fetch().length; i++) { 69 | LrClassNameConfig.addToEntry(i, 'id', e[i]); 70 | } 71 | cache.saveToCache(LrClassNameConfig.fetch(), Constants.fetch('cacheClassNameConfig')); 72 | }); 73 | }, 74 | 75 | 76 | getUserSites: function() { 77 | utilities.writeToScreen('Downloading list of sites', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_INFO')); 78 | return getData('{"/group/get-user-sites": {}}').then( 79 | function (e) { 80 | if(e.length === 0) { 81 | throw Error('Could not find any sites'); 82 | } else { 83 | e.forEach(function(entry) { 84 | Sites.add(entry); 85 | }); 86 | CompanyId = e[0].companyId; 87 | } 88 | }); 89 | }, 90 | 91 | 92 | getCompanyGroup: function () { 93 | utilities.writeToScreen('Downloading company site', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_INFO')); 94 | return getData('{"/group/get-company-group": {"companyId": ' + CompanyId + '}}').then( 95 | function (e) { 96 | // Dirty way of adding the global site to the list of sites. 97 | Sites.setAll(JSON.parse('[' + JSON.stringify(Sites.fetch()).substr(1).slice(0, -1) + ',' + JSON.stringify(e) + ']')); 98 | cache.saveToCache(Sites.fetch(), Constants.fetch('cacheSitesFilename')); 99 | }); 100 | }, 101 | 102 | 103 | getStructures: function () { 104 | utilities.writeToScreen('Downloading structures', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_INFO')); 105 | 106 | var sitesList = []; 107 | var i; 108 | for (i = 0; i < Sites.fetch().length; ++i) { 109 | sitesList.push(Sites.fetch(i, 'groupId')); 110 | } 111 | 112 | return getData('{"/ddmstructure/get-structures": {"groupIds": [' + sitesList.join() + ']}}').then( 113 | function (e) { 114 | 115 | 116 | // Make sure we have all the structures in our LrClassNameConfig 117 | // If not, warn the user about it! 118 | for (var i = 0; i < e.length; ++i) { 119 | if (!LrClassNameConfig.checkExist('id', e[i].classNameId)) { 120 | var newLrClassNameObj = { 121 | filesPath: 'unknown/' + e[i].classNameId, 122 | friendlyName: 'Unknown with classNameId: ' + e[i].classNameId, 123 | clazz: 'Unknown with classNameId: ' + e[i].classNameId, 124 | type: 'structure', 125 | containsDDMs: true, 126 | id: e[i].classNameId, 127 | isUnknown: true, 128 | getTemplate: false 129 | }; 130 | utilities.writeToScreen( 131 | '\nFound a (custom?) structure I don\'t know about\n' + 132 | 'It\'ll be saved in \'' + newLrClassNameObj.filesPath + '\' but I won\'t be able to manage it.\n\n' + 133 | 'To be able to handle unknown structures:\n' + 134 | '1) Search you database: \'select value from classname_ where classNameId = ' + e[i].classNameId + '\' to get className\n' + 135 | '2) Create an entry in \'' + Config.fetch('settingsFolder') + '/' + Constants.fetch('customClassNameConfig') + '\' (please read README)\n', 136 | Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_ERROR')); 137 | LrClassNameConfig.add(newLrClassNameObj); 138 | } 139 | } 140 | 141 | 142 | // Remove every entry (therer is only 1) with className 'PortletDocumentlibraryUtilRawMetadataProcessor'. 143 | // This is a Liferay internal structure which is used to parse 144 | // document metadata and display it in the Document and Media Portlet. 145 | var idRawMetaDataProcessor = LrClassNameConfig.getSingleValue('clazz', 'com.liferay.portlet.documentlibrary.util.RawMetadataProcessor', 'id'); 146 | e = e.filter(function(entry) { 147 | return entry.classNameId != idRawMetaDataProcessor; 148 | }); 149 | 150 | // Check if there's a DDM we should ignore 151 | e = e.filter(function(entry) { 152 | if(_.contains(Config.fetch('ignoreDDMs'), entry.structureKey)) { 153 | return false; 154 | } else { 155 | return true; 156 | } 157 | }); 158 | 159 | e.forEach(function(entry) { 160 | Structures.add(entry); 161 | }); 162 | 163 | cache.saveToCache(Structures.fetch(), Constants.fetch('cacheStructuresFilename')); 164 | 165 | }); 166 | }, 167 | 168 | 169 | getTemplates: function () { 170 | 171 | utilities.writeToScreen('Downloading templates', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_INFO')); 172 | var payload = []; 173 | 174 | for (var i = 0; i < Sites.fetch().length; ++i) { 175 | for (var ii = 0; ii < LrClassNameConfig.fetch().length; ii++) { 176 | if (LrClassNameConfig.fetch(ii, 'getTemplate')) { 177 | payload.push('{"/ddmtemplate/get-templates": {"groupId": ' + Sites.fetch(i, 'groupId') + ', "classNameId": ' + LrClassNameConfig.fetch(ii, 'id') + '}}'); 178 | } 179 | } 180 | } 181 | 182 | return getData('[' + payload.join() + ']').then( 183 | function (e) { 184 | for (var y = 0; y < e.length; ++y) { 185 | for (i = 0; i < e[y].length; ++i) { 186 | // Check if there's a DDM we should ignore 187 | if(!_.contains(Config.fetch('ignoreDDMs'), e[y][i].templateKey)) { 188 | // Templates.push(e[y][i]); 189 | Templates.add(e[y][i]); 190 | } 191 | } 192 | } 193 | cache.saveToCache(Templates.fetch(), Constants.fetch('cacheTemplatesFilename')); 194 | 195 | }); 196 | } 197 | 198 | 199 | 200 | }; 201 | 202 | module.exports = download; -------------------------------------------------------------------------------- /lib/uploadTemplateNew.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Q = require('q'); 4 | var inquirer = require("inquirer"); 5 | var _ = require('underscore'); 6 | 7 | var Constants = require('./Constants.js'); 8 | var utilities = require('./utilities.js'); 9 | var LrClassNameConfig = require('./ClassNameConfig.js'); 10 | var Sites = require('./Sites.js'); 11 | 12 | 13 | // FIGURE OUT WHICH SITE WE'RE GOING TO CREATE THE TEMPLATE AT. 14 | // TO DO SO, WE CHECK WHICH SITES HAVE STRUCTURES OF THE RIGHT TYPE, 15 | // IF THERE'S MORE THAN ONE, WE ASK THE USER WHICH SITE S/HE WANT TO 16 | // UPLOAD THE TEMPLATE TO. 17 | function getSite (validStructures, fileFriendlyName, className, isADT, returnObj) { 18 | var deferred = Q.defer(); 19 | 20 | var possibleSites = []; 21 | var questionsSites = []; 22 | 23 | 24 | if (isADT) { // if template is an ADT, the use may choose to upload to _any_ group. 25 | Sites.fetch().forEach(function(entry) { 26 | if(LrClassNameConfig.getSingleValue('id', entry.classNameId, 'containsDDMs')) { 27 | possibleSites.push(entry.groupId); 28 | } 29 | }); 30 | } else { // If something else than an ADT the use may only choose upload to a group with a corresponding structure. 31 | validStructures.forEach(function (entry) { 32 | possibleSites.push(entry.groupId); 33 | }); 34 | possibleSites = _.unique(possibleSites); 35 | } 36 | 37 | if (possibleSites.length === 0) { 38 | returnObj.exception = 'Could not find any sites to upload the file to'; 39 | deferred.reject(returnObj); 40 | } else if (possibleSites.length === 1) { 41 | deferred.resolve({site: possibleSites[0], autofound: true}); 42 | } else { 43 | // MORE THAN ONE POSSIBLE SITE 44 | // ASK THE USER WHICH SITE S/HE WANT TO UPLOAD THE TEMPLATE TO 45 | // Create Question Array: 46 | possibleSites.forEach(function(entry) { 47 | questionsSites.push({ 48 | name: Sites.getSingleValue('groupId', entry, 'name') + ' [' + LrClassNameConfig.getSingleValue('id', Sites.getSingleValue('groupId', entry, 'classNameId'), 'friendlyName')+ ']', 49 | value: entry 50 | }); 51 | }); 52 | 53 | utilities.writeToScreen('-', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_NORMAL')); 54 | utilities.writeToScreen('Need some input on file: ', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_HEADING')); 55 | utilities.writeToScreen('\'' + fileFriendlyName + '\' (Template to Structure \'' + className + '\')', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_FILE')); 56 | utilities.writeToScreen('', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_HEADING')); 57 | 58 | inquirer.prompt([ 59 | { 60 | type: "list", 61 | name: "siteSelection", 62 | message: "Which site do you want to add the template to", 63 | choices: questionsSites 64 | } 65 | ], function(answersSite) { 66 | deferred.resolve({site: answersSite.siteSelection, autofound: false}); 67 | }); 68 | } 69 | return deferred.promise; 70 | } 71 | 72 | 73 | 74 | // FIGURE OUT WHICH STRUCTURE WE'RE GOING TO BIND THE TEMPLATE TO. 75 | function getStructure (validStructures, fileFriendlyName, className, currentSite, autofoundSite, isADT, returnObj) { 76 | var deferred = Q.defer(); 77 | 78 | if (isADT) { // Don't try to find a structure if it's an ADT. 79 | deferred.resolve({ 80 | structure: {}, 81 | site: currentSite, 82 | autofoundSite: autofoundSite, 83 | autofoundStructure: false 84 | }); 85 | return deferred.promise; 86 | } 87 | 88 | var questionsStructure = []; 89 | 90 | var possibleStructures = validStructures.filter(function (entry) { 91 | return entry.groupId === currentSite; 92 | }); 93 | 94 | if (possibleStructures.length === 0) { 95 | returnObj.exception = 'No structures available to bind the template to'; 96 | deferred.reject(returnObj); 97 | // TODO: There's a bug here, the template is getting associated with the wrong structure. Temporarily disable. 98 | // } else if (possibleStructures.length === 1) { 99 | // utilities.writeToScreen('-', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_NORMAL')); 100 | // utilities.writeToScreen('Automagically linking new template to structure:\n\'' + fileFriendlyName + '\' ==> \'' + possibleStructures[0].nameCurrentValue + '\'', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_FILE')); 101 | // utilities.writeToScreen('', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_HEADING')); 102 | // deferred.resolve({ 103 | // structure: possibleStructures[0], 104 | // site: currentSite, 105 | // autofoundSite: autofoundSite, 106 | // autofoundStructure: true 107 | // }); 108 | } else { 109 | // MORE THAN ONE POSSIBLE STRUCTURE 110 | // ASK THE USER WHICH SITE S/HE WANT TO UPLOAD THE TEMPLATE TO 111 | // Create Question Array: 112 | possibleStructures.forEach(function(entry) { 113 | questionsStructure.push({ 114 | name: entry.nameCurrentValue, 115 | value: entry 116 | }); 117 | }); 118 | 119 | 120 | if (autofoundSite) { 121 | utilities.writeToScreen('-', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_NORMAL')); 122 | utilities.writeToScreen('Need some input on file: ', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_HEADING')); 123 | utilities.writeToScreen('\'' + fileFriendlyName + '\' (Template to Structure \'' + className + '\')', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_FILE')); 124 | utilities.writeToScreen('', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_HEADING')); 125 | } 126 | 127 | inquirer.prompt([ 128 | { 129 | type: "list", 130 | name: "structureSelection", 131 | message: "Which structure do you want to connect the template to", 132 | choices: questionsStructure 133 | } 134 | ], function(answerStructure) { 135 | deferred.resolve({ 136 | structure: answerStructure.structureSelection, 137 | site: currentSite, 138 | autofoundSite: autofoundSite, 139 | autofoundStructure: false 140 | }); 141 | }); 142 | } 143 | 144 | 145 | 146 | return deferred.promise; 147 | } 148 | 149 | 150 | 151 | var createUploadObject = function (newObj) { 152 | 153 | var PortletKeys = require("./PortletKeys.js"); 154 | 155 | var deferred = Q.defer(); 156 | var payload = {}; 157 | 158 | var returnObj = { 159 | exceptionFile: newObj.file, 160 | group: {} 161 | }; 162 | 163 | 164 | getSite(newObj.validStructures, newObj.fileFriendlyName, newObj.className, newObj.isADT, returnObj) 165 | .then(function (currentSite) { 166 | 167 | getStructure(newObj.validStructures, newObj.fileFriendlyName, newObj.className, currentSite.site, currentSite.autofound, newObj.isADT, returnObj) 168 | .then(function (currentSiteAndStructure) { 169 | 170 | // Set some values in our return object to be able to do a nice print to the user. 171 | returnObj.group.description = Sites.getSingleValue('groupId', currentSiteAndStructure.site, 'description'); 172 | returnObj.group.name = Sites.getSingleValue('groupId', currentSiteAndStructure.site, 'name'); 173 | returnObj.group.type = LrClassNameConfig.getSingleValue('id', Sites.getSingleValue('groupId', currentSiteAndStructure.site, 'classNameId'), 'friendlyName'); 174 | returnObj.group.friendlyURL = Sites.getSingleValue('groupId', currentSiteAndStructure.site, 'friendlyURL'); 175 | returnObj.group.groupId = currentSiteAndStructure.site; 176 | 177 | returnObj.status = 'create'; 178 | returnObj.isTemplate = true; 179 | returnObj.fileFriendlyName = newObj.fileFriendlyName; 180 | 181 | // Set some things differenly on ADTs 182 | var classPK = currentSiteAndStructure.structure.structureId; 183 | var classNameId = newObj.classNameId; 184 | var refererPortletName = ''; 185 | if (newObj.isADT) { 186 | classPK = 0; 187 | classNameId = newObj.validStructuresClassNameId; 188 | returnObj.className = LrClassNameConfig.getSingleValue('id', classNameId, 'friendlyName'); 189 | refererPortletName = PortletKeys.fetch('PORTLET_DISPLAY_TEMPLATES'); 190 | returnObj.isADT = true; 191 | } else { 192 | returnObj.className = currentSiteAndStructure.structure.nameCurrentValue; 193 | refererPortletName = PortletKeys.fetch('JOURNAL'); 194 | } 195 | 196 | // Create Payload 197 | payload = { 198 | groupId: currentSiteAndStructure.site, 199 | classNameId: classNameId, 200 | classPK: classPK, 201 | nameMap: utilities.strToJsonMap(newObj.fileFriendlyName), 202 | descriptionMap: {}, 203 | type: 'display', 204 | mode: '', 205 | language: newObj.fileLanguageType, 206 | script: newObj.newScript, 207 | '+serviceContext': 'com.liferay.portal.service.ServiceContext', 208 | 'serviceContext.addGroupPermissions': true, 209 | 'serviceContext.addGuestPermissions': true, 210 | 'serviceContext.attributes': { refererPortletName: refererPortletName } 211 | }; 212 | 213 | returnObj.payload = '{"/ddmtemplate/add-template": ' + JSON.stringify(payload) + '}'; 214 | 215 | deferred.resolve(returnObj); 216 | 217 | }, function (er) { 218 | deferred.reject(er); 219 | }); 220 | 221 | }, function (er) { 222 | deferred.reject(er); 223 | }); 224 | 225 | return deferred.promise; 226 | 227 | }; 228 | 229 | module.exports = createUploadObject; -------------------------------------------------------------------------------- /lib/uploadFiles.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // TODO, make sure new Structures are uploaded first and that the list of structures is populated 4 | // with the new structures (if there are any), so that we may bind new templates to those structures. 5 | 6 | var Q = require('q'); 7 | 8 | var Constants = require('./Constants.js'); 9 | var utilities = require('./utilities.js'); 10 | 11 | var uploadObjects = []; 12 | var uploadOptions = {}; 13 | 14 | function createUploadObject(file) { 15 | 16 | var deferred = Q.defer(); 17 | 18 | var returnObj = { 19 | fileLanguageType: utilities.filenameToLanguageType(file), 20 | exceptionFile: file, 21 | group: {} 22 | }; 23 | 24 | // Figure out what kind of file it is based on it's path. 25 | var filePathSplit = file.split('/'); 26 | if(filePathSplit[filePathSplit.length - 2] === 'templates') { // Is template 27 | var coTemplate = require('./uploadTemplate.js'); 28 | coTemplate(file).then(function (resp) { 29 | deferred.resolve(resp); 30 | },function (resp) { 31 | deferred.reject(resp); 32 | }); 33 | } else if(filePathSplit[filePathSplit.length - 2] === 'structures') { 34 | var coStructure = require('./uploadStructure.js'); 35 | coStructure(file).then(function (resp) { 36 | deferred.resolve(resp); 37 | },function (resp) { 38 | deferred.reject(resp); 39 | }); 40 | } else { 41 | returnObj.exception = 'File is not a structure nor a template'; 42 | deferred.reject(returnObj); 43 | return deferred.promise; 44 | } 45 | 46 | return deferred.promise; 47 | } 48 | 49 | 50 | var preparePayloads = function (i, files, whenFinishedUploadingCallback) { 51 | createUploadObject(files[i]).then(function (e){ 52 | uploadObjects.push(e); 53 | if(i < files.length-1) { 54 | preparePayloads(++i, files); 55 | } else { 56 | doUploads(uploadObjects, whenFinishedUploadingCallback); 57 | } 58 | }, function(er) { 59 | utilities.writeToScreen('\nCould not upload file!\n', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_ERROR')); 60 | utilities.writeToScreen('Name: ' + er.fileName, Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_ERROR')); 61 | utilities.writeToScreen('Error: ' + er.exception, Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_ERROR')); 62 | utilities.writeToScreen('File Path: ' + er.exceptionFile, Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_ERROR')); 63 | }); 64 | }; 65 | 66 | var uploadFiles = function (files, options, whenFinishedUploadingCallback) { 67 | 68 | // Set default options and save to uploadOptions object. 69 | options = typeof options !== 'undefined' ? options : {}; 70 | options.autoMode = typeof options.autoMode !== 'undefined' ? options.autoMode : false; 71 | uploadOptions = options; 72 | 73 | // Make single file into array 74 | if(typeof files === 'string') { 75 | files = [files]; 76 | } 77 | 78 | // Clear uploadObjects 79 | uploadObjects = []; 80 | 81 | // Fire 82 | preparePayloads(0, files, whenFinishedUploadingCallback); 83 | }; 84 | 85 | var doUploads = function (uploadObjects, whenFinishedUploadingCallback) { 86 | 87 | var Table = require('cli-table'); 88 | var inquirer = require("inquirer"); 89 | 90 | var Structures = require('./Structures.js'); 91 | var Templates = require('./Templates.js'); 92 | var Config = require('./Config.js'); 93 | 94 | var cache = require('./cache.js'); 95 | var lrException = require('./errorException.js'); 96 | var getData = require('./getData.js'); 97 | var router = require('./router.js'); 98 | 99 | var fullPayload = []; 100 | var filteredUploadObjects = []; 101 | 102 | var friendlyTypeOutput = ''; 103 | 104 | var states = [ 105 | { 106 | status: 'uptodate', 107 | heading: 'Already up to date, will not update' 108 | }, 109 | { 110 | status: 'update', 111 | heading: 'Update' 112 | }, 113 | { 114 | status: 'create', 115 | heading: 'Create new' 116 | } 117 | ]; 118 | 119 | 120 | uploadObjects = uploadObjects.filter(function (entry) { 121 | if (entry.ignore === true) { 122 | utilities.writeToScreen('Ignoring file: ' + entry.exceptionFile, Constants.fetch('SEVERITY_IMPORTANT'), Constants.fetch('SCREEN_PRINT_ERROR')); 123 | utilities.writeToScreen(entry.ignoreMsg + '\n', Constants.fetch('SEVERITY_IMPORTANT'), Constants.fetch('SCREEN_PRINT_NORMAL')); 124 | return false; 125 | } else { 126 | return true; 127 | } 128 | }); 129 | 130 | // Split the uploadObjects into 3, one with files that are already up to date, 131 | // one with files that needs updating and one with files that needs to be created, 132 | // to be able to present it to the user in a nice way (and avoid) updating things, 133 | // which does not need to be updated. 134 | utilities.writeToScreen('-', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_NORMAL')); 135 | for (var x = 0; x < states.length ; x++) { 136 | 137 | /*jshint -W083 */ 138 | filteredUploadObjects = uploadObjects.filter(function(entry) { 139 | return entry.status == states[x].status; 140 | }); 141 | /*jshint +W083 */ 142 | 143 | states[x].table = new Table({ 144 | head: ['Name', 'Type', 'GrpId', 'Group (Group Type)'], 145 | chars: { 146 | 'top': '' , 'top-mid': '' , 'top-left': '' , 'top-right': '', 147 | 'bottom': '' , 'bottom-mid': '' , 'bottom-left': '' , 'bottom-right': '', 148 | 'left': '' , 'left-mid': '' , 'mid': '' , 'mid-mid': '', 149 | 'right': '' , 'right-mid': '' , 'middle': ' ' 150 | }, 151 | style: { 152 | 'padding-left': 2, 153 | 'padding-right': 0, 154 | 'head': ['magenta'] 155 | }, 156 | colWidths: [35, 55, 7] 157 | }); 158 | 159 | for (var i = 0; i < filteredUploadObjects.length; i++) { 160 | 161 | if (filteredUploadObjects[i].isADT) { 162 | friendlyTypeOutput = filteredUploadObjects[i].className; 163 | } else if (filteredUploadObjects[i].isTemplate) { 164 | friendlyTypeOutput = 'Template (Structure: ' + filteredUploadObjects[i].className + ')'; 165 | } else if (filteredUploadObjects[i].isStructure) { 166 | friendlyTypeOutput = 'Structure (' + filteredUploadObjects[i].className + ')'; 167 | } 168 | 169 | states[x].table.push([ 170 | filteredUploadObjects[i].fileFriendlyName, 171 | friendlyTypeOutput, 172 | filteredUploadObjects[i].group.groupId, 173 | filteredUploadObjects[i].group.name + ' (' + filteredUploadObjects[i].group.type + ')' 174 | ]); 175 | } 176 | 177 | if (states[x].table.length > 0) { 178 | utilities.writeToScreen(states[x].heading + ' (' + states[x].table.length + ')', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_HEADING')); 179 | utilities.writeToScreen('', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_HEADING')); 180 | utilities.writeToScreen(states[x].table.toString(), Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_INFO')); 181 | utilities.writeToScreen('', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_HEADING')); 182 | } 183 | 184 | 185 | } 186 | 187 | // Check to see that we actually have things which needs to be updated/created 188 | if (states[1].table.length > 0 || states[2].table.length > 0 ) { 189 | 190 | // Remove every file which is already to date. 191 | uploadObjects = uploadObjects.filter(function (entry) { 192 | return entry.status != 'uptodate'; 193 | }); 194 | 195 | // Create a batch of all payloads. 196 | uploadObjects.forEach(function(entry) { 197 | fullPayload.push(entry.payload); 198 | }); 199 | 200 | 201 | if (uploadOptions.autoMode === true) { 202 | getData('[' + fullPayload.join() + ']').then(function (resp) { 203 | 204 | Templates.updateAll(resp); 205 | Structures.updateAll(resp); 206 | cache.saveToCache(Templates.fetch(), Constants.fetch('cacheTemplatesFilename')); 207 | cache.saveToCache(Structures.fetch(), Constants.fetch('cacheStructuresFilename')); 208 | 209 | resp.forEach(function(entry) { 210 | utilities.writeToScreen('Upload successful! (' + entry.nameCurrentValue + ')', Constants.fetch('SEVERITY_IMPORTANT'), Constants.fetch('SCREEN_PRINT_SAVE')); 211 | }); 212 | 213 | }, function (e) { 214 | console.dir(e); 215 | lrException('Could not upload DDMs to server!\n'); 216 | }); 217 | } else { 218 | 219 | inquirer.prompt([ 220 | { 221 | type: "list", 222 | name: "confirm", 223 | message: "Do you want to send this to the server?", 224 | choices: [ 225 | { 226 | name: 'Send to server \'' + Config.fetch('hostFriendlyName').toUpperCase() + '\' (of project \'' + Config.fetch('projectName') + '\')', 227 | value: true 228 | }, 229 | { 230 | name: 'Abort', 231 | value: false 232 | } 233 | ] 234 | } 235 | ], function (answers) { 236 | if (answers.confirm === true) { 237 | 238 | getData('[' + fullPayload.join() + ']').then(function (resp) { 239 | Templates.updateAll(resp); 240 | Structures.updateAll(resp); 241 | cache.saveToCache(Templates.fetch(), Constants.fetch('cacheTemplatesFilename')); 242 | cache.saveToCache(Structures.fetch(), Constants.fetch('cacheStructuresFilename')); 243 | utilities.writeToScreen('', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_SAVE')); 244 | utilities.writeToScreen('Files updated/created!', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_SAVE')); 245 | router(Constants.fetch('STEP_JUST_UPLOADED_DDMS')); 246 | }, function (e) { 247 | console.dir(e); 248 | lrException('Could not upload DDMs to server!\n'); 249 | }); 250 | 251 | } else { 252 | router(Constants.fetch('STEP_JUST_UPLOADED_DDMS')); 253 | } 254 | } 255 | ); 256 | } 257 | 258 | 259 | 260 | 261 | } else { 262 | if (uploadOptions.autoMode !== true) { 263 | utilities.writeToScreen('Every file is already up to date\n', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_SAVE')); 264 | router(Constants.fetch('STEP_JUST_UPLOADED_DDMS')); 265 | } 266 | } 267 | 268 | if (typeof whenFinishedUploadingCallback === "function") { 269 | whenFinishedUploadingCallback.call(); 270 | } 271 | }; 272 | 273 | module.exports = uploadFiles; 274 | -------------------------------------------------------------------------------- /lib/projectCreate.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var clc = require('cli-color'); 4 | var fs = require('fs-extra'); 5 | var inquirer = require("inquirer"); 6 | var Q = require('q'); 7 | 8 | var Constants = require('./Constants.js'); 9 | var utilities = require('./utilities.js'); 10 | var getData = require('./getData.js'); 11 | var Config = require('./Config.js'); 12 | 13 | 14 | 15 | var createProject = function () { 16 | 17 | 18 | var hostsOut = []; 19 | var retValue = true; 20 | 21 | console.log(); 22 | console.log('Initializing a New Project'); 23 | console.log(' Need some data to set up the project:'); 24 | console.log(' - Project Name. You\'ll use this every time you run the script. Pick something short.'); 25 | console.log(' - The path to the DDM files (structures, templates, etc) on your local machine. '); 26 | console.log(' Folder will be created if path does not exist. '); 27 | console.log(' This is the folder you want to check-in to your version control system.'); 28 | console.log(' - Whether we should upload/download Liferay default DDMs or not'); 29 | console.log(); 30 | console.log(' Settings will be saved to ' + clc.yellow(Config.fetch('projectsFolder') + '/projectname.json')); 31 | console.log(' and may be edited at any time'); 32 | console.log(); 33 | 34 | // Define Project Questions 35 | var questionsProject = [ 36 | { 37 | type: "input", 38 | name: "projectName", 39 | message: "Project Short Name", 40 | validate: function( value ) { 41 | var pass = value.match(/^[a-z0-9\-]{1,15}$/i); 42 | if (pass) { 43 | if (fs.existsSync(Config.fetch('projectsFolder') + '/' + value.toLowerCase() + '.json')) { 44 | return "Project named '" + value + "' already exists"; 45 | } else { 46 | return true; 47 | } 48 | } else { 49 | return "Project name must be maximum 15 characters and only contain alfanumeric characters and underscore"; 50 | } 51 | }, 52 | filter: function(value) { 53 | return value.trim(); 54 | } 55 | }, 56 | { 57 | type: "input", 58 | name: "filesPath", 59 | message: "Path to files on this machine", 60 | filter: function(value) { 61 | return utilities.removeTrailingSlash(value); 62 | } 63 | }, 64 | { 65 | type: "input", 66 | name: "defaultLocale", 67 | message: "Default locale. Just press enter for", 68 | default: "en_US", 69 | validate: function( value ) { 70 | var pass = value.match(/^[a-z\_]{2}\_[A-Z]{2}$/); 71 | if (pass) { 72 | return true; 73 | } else { 74 | return "Locale must be by the standard \"en_US\""; 75 | } 76 | }, 77 | filter: function(value) { 78 | return value.trim(); 79 | } 80 | }, 81 | { 82 | type: "list", 83 | name: "ignoreLRDefault", 84 | message: "Should I handle or ignore Liferay default templates/structures?", 85 | choices: [ 86 | { 87 | name: 'Include Liferay Defaults', 88 | value: false 89 | }, 90 | { 91 | name: 'Ignore Liferay Defaults', 92 | value: true 93 | } 94 | ] 95 | } 96 | ]; 97 | 98 | // Ask Project Questions 99 | inquirer.prompt( questionsProject, function(answersProject) { 100 | 101 | console.log(); 102 | console.log('Add your first server'); 103 | console.log(' The URL, Username and Password to a Liferay Server (URL may be http://localhost)'); 104 | console.log(' You may add more servers after this.'); 105 | console.log(); 106 | 107 | // Define Hosts Questions 108 | var questionsHosts = [ 109 | { 110 | type: "input", 111 | name: "name", 112 | message: "Host Name (e.g 'prod1' or 'local-dev'):", 113 | validate: function( value ) { 114 | var pass = value.match(/^[a-z0-9\-]{1,15}$/i); 115 | if (pass) { 116 | retValue = true; 117 | for (var i = 0; i < hostsOut.length; i++) { 118 | if (hostsOut[i].name.toLowerCase() === value.toLowerCase()) { 119 | retValue = 'Host name already exists, choose another one'; 120 | } 121 | } 122 | return retValue; 123 | } else { 124 | return "Host name must be maximum 15 characters and only contain alfanumeric characters and underscore"; 125 | } 126 | }, 127 | filter: function(value) { 128 | return value.trim(); 129 | } 130 | }, 131 | { 132 | type: "input", 133 | name: "host", 134 | message: "Liferay Host (url:port):", 135 | filter: function(value) { 136 | return utilities.removeTrailingSlash(value.trim()); 137 | } 138 | }, 139 | { 140 | type: "input", 141 | name: "username", 142 | message: "Liferay Username", 143 | filter: function(value) { 144 | return value.trim(); 145 | } 146 | }, 147 | { 148 | type: "password", 149 | name: "password", 150 | message: "Liferay Password", 151 | filter: function(value) { 152 | return value.trim(); 153 | } 154 | }, 155 | { 156 | type: "input", 157 | name: "email", 158 | message: "Liferay User Email (If same as username - just press enter)", 159 | filter: function(value) { 160 | return value.trim(); 161 | } 162 | } 163 | ]; 164 | 165 | // Ask Hosts Questions 166 | function askForHosts() { 167 | inquirer.prompt(questionsHosts, function(answersHosts) { 168 | 169 | // Check if connection works 170 | var deferred = Q.defer(); 171 | utilities.writeToScreen('Testing connection', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_INFO')); 172 | getData('{"/portal/get-build-number": {}}', true, answersHosts.host, answersHosts.username, answersHosts.password) 173 | .then(function () { 174 | utilities.writeToScreen('Connection okay!', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_INFO')); 175 | // Resolve Promise if connection Works 176 | deferred.resolve(); 177 | }, function (e) { 178 | // If connection didn't work, ask if user want's to save it anyway. 179 | utilities.writeToScreen('Could not establish connection (' + e + ')', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_ERROR')); 180 | 181 | inquirer.prompt([{ 182 | type: "list", 183 | name: "proceed", 184 | message: "What do you want to do?", 185 | choices: [ 186 | { 187 | name: 'Re-enter the server information', 188 | value: 'reenter' 189 | }, 190 | { 191 | name: 'Save configuration, even though connection failed', 192 | value: 'save' 193 | } 194 | ] 195 | } 196 | ], function( answers ) { 197 | // Check if user wanted to re-enter the information of save it anyways 198 | if (answers.proceed === 'reenter') { 199 | // If the user wants to re-enter the information, 200 | // set the current answers as default answers for the new questions, 201 | // and then ask the new question. 202 | utilities.writeToScreen('', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_INFO')); 203 | utilities.writeToScreen('Previously entered values within parentheses\nJust press if you want to leave the field unchanged', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_INFO')); 204 | utilities.writeToScreen('', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_INFO')); 205 | 206 | questionsHosts[0].default = answersHosts.name; 207 | questionsHosts[1].default = answersHosts.host; 208 | questionsHosts[2].default = answersHosts.username; 209 | questionsHosts[3].default = answersHosts.password; 210 | questionsHosts[4].default = answersHosts.email; 211 | 212 | askForHosts(); 213 | } else { 214 | // If the user wants to save the information, even though a connection 215 | // couldn't be made, just resolve this and go to next step. 216 | deferred.resolve(); 217 | } 218 | }); 219 | 220 | return deferred.promise; 221 | 222 | 223 | }) 224 | .done(function () { 225 | // Ask if the user wants to add another server. 226 | inquirer.prompt([{ 227 | type: "list", 228 | name: "askAgain", 229 | message: "Do you want to enter another server", 230 | choices: [ 231 | { 232 | name: 'Yes', 233 | value: true 234 | }, 235 | { 236 | name: 'No', 237 | value: false 238 | } 239 | ] 240 | } 241 | ], function(answersAskAgain) { 242 | 243 | // Set email to the same as username if not supplied. 244 | if(answersHosts.email.length < 1) { 245 | answersHosts.email = answersHosts.username; 246 | } 247 | 248 | // Save the just added server to array 249 | hostsOut.push(answersHosts); 250 | 251 | 252 | if (answersAskAgain.askAgain) { 253 | // If the user wants to add another server 254 | questionsHosts.forEach(function(obj){ delete obj.default; }); 255 | console.log(); 256 | console.log('Add your ' + clc.yellow(utilities.stringifyNumber(hostsOut.length + 1)) + ' server'); 257 | console.log(); 258 | askForHosts(); 259 | } else { 260 | // If the user don't want to add another server, save the configuration to file 261 | // and send the user to the 'Select Projects' screen. 262 | 263 | // Add Placeholder for external Diff tool 264 | answersProject.SAMPLEexternalDiff = Constants.fetch('externalDiff'); 265 | 266 | // Add default ignore pattern. These files will be ignored by watch function. 267 | answersProject.watchIgnorePattern = Constants.fetch('watchIgnorePattern'); 268 | 269 | // Add default allow self signed certs. 270 | answersProject.allowSelfSignedCerts = Constants.fetch('allowSelfSignedCerts'); 271 | 272 | // Add templates to ignore. 273 | if (answersProject.ignoreLRDefault) { 274 | answersProject.ignoreDDMs = Constants.fetch('ignoreDDMs'); 275 | } else { 276 | answersProject.DONTignoreDDMs = Constants.fetch('ignoreDDMs'); 277 | } 278 | delete answersProject.ignoreLRDefault; 279 | 280 | // Append hosts 281 | answersProject.hosts = hostsOut; 282 | 283 | fs.outputFileSync(Config.fetch('projectsFolder') + '/' + answersProject.projectName.toLowerCase() + '.json', JSON.stringify(answersProject, null, " ")); 284 | utilities.writeToScreen('', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_SAVE')); 285 | utilities.writeToScreen('Project created!', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_SAVE')); 286 | utilities.writeToScreen('Settings saved to ' + Config.fetch('projectsFolder') + '/' + answersProject.projectName.toLowerCase() + '.json', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_SAVE')); 287 | utilities.writeToScreen('', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_SAVE')); 288 | 289 | var router = require('./router.js'); 290 | router(Constants.fetch('STEP_JUST_CREATED_PROJECT')); 291 | 292 | } 293 | }); 294 | }); 295 | }); 296 | } 297 | // ask for Hosts the first time. 298 | askForHosts(); 299 | }); 300 | 301 | }; 302 | 303 | module.exports = createProject; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Liferay DDM Tool 2 | Command Line Tool for authoring, uploading, downloading and synchronizing Liferay DDM related stuff (Structures and Templates) across environments. All transactions are done over JSON Web Services. 3 | 4 | ### Demonstration recorded at Liferay DevCon 2014 5 | 6 | [![Screenshot of YouTube video](https://raw.githubusercontent.com/emiloberg/liferay-ddmtool/master/docs/images/screenshot-youtube-devcon-2014.png)](https://www.youtube.com/watch?v=f9x7wL16KIk#t=796) 7 | 8 | ### Flowchart 9 | 10 | ![Flowchart of DDM Tool](https://raw.githubusercontent.com/emiloberg/liferay-ddmtool/master/docs/images/flowchart-main.png) 11 | 12 | ### Abilities 13 | 14 | * **Upload** ddm stuff - structures and templates - from local (version controlled!) repositories to server (localhost, live, dev and whatnot). 15 | * **Download** ddm stuff from server to local repositories. 16 | * **Watch local folder for changes** and when changed immediately upload the file to server. 17 | * **Show diffs** between server and local repositories (files only available locally/on server) 18 | 19 | ### What is "DDM related stuff"? 20 | 21 | * Journal Article Structures and Templates 22 | * Application Display Templates (ADTs), including but not limited to all built-in portlets supporting ADTs, such as Asset Publisher, Blogs, Categories Navigation, Document and Media, Sitemap, Tags Navigation and Wiki. 23 | * Dynamic Data List Definitions 24 | * Document & Media; Metadata Set Definitions and Document Type Definitions 25 | * All structures and templates for portlets you build yourself. 26 | 27 | ## Installation 28 | 29 | Requires [Node.js](http://nodejs.org/) (if unsure if you have node install, run `node -v` in a console.) 30 | 31 | Install with npm: 32 | 33 | ``` 34 | sudo npm install -g liferay-ddmtool 35 | ``` 36 | 37 | and then run: 38 | 39 | ``` 40 | ddm 41 | ``` 42 | 43 | If this is the first time you use this App. You most probably want to download all structures and templates to a local folder. 44 | 45 | 1. Create a new project by following the wizard in the App. 46 | 2. Select the just created project and choose "_Download All_" 47 | 3. A folder containing all structures and templates has now been created in the path you selected when you created the project. Maybe you want to make this into a git repository? 48 | 49 | ## Common use cases 50 | 51 | ### Version contol 52 | The DDM tool will upload (and download) files from a folder on your local machine to a Liferay server of your choice. If you put that local folder under version control you may check in and out your structures and templates just as any other source code. 53 | 54 | ### Live template development in the editor/IDE of your choice 55 | ![Screenshot of Watch mode](https://raw.githubusercontent.com/emiloberg/liferay-ddmtool/master/docs/images/screen-watching.png) 56 | When developing templates, set the Liferay DDM Tool in "watch mode" and as soon as you save a template in your favorite editor, it'll upload to the Liferay server and is used immediately. 57 | 58 | Usually you want to upload files to your _localhost_ development environment. 59 | 60 | **Pro tip:** 61 | Run DDM Tool with the `-w`/`--watch` flag to go straight into watch mode: 62 | 63 | ``` 64 | ddm -w --project --server 65 | ``` 66 | 67 | ### Setting up a new environment, such as a new live/dev server 68 | ![Screenshot of Upload](https://raw.githubusercontent.com/emiloberg/liferay-ddmtool/master/docs/images/screen-upload.png) 69 | 70 | Checkout all structures and templates from your resposity, run the DDM Tool and select _"Upload all"_ 71 | 72 | **Pro tip:** 73 | Run DDM Tool with the `-u`/`--upload` flag to upload all files without any menu selections. 74 | 75 | ``` 76 | ddm -u --project --server 77 | ``` 78 | 79 | 80 | ### Deploying all to the live server as a part of the release process 81 | Just as if you were setting up a new environment, just checkout all ddm stuff and run _"Upload all"_ to deploy all structures and templates. 82 | 83 | If you just want to deploy _some_ of the files, go into _"Find Diffs"_ > _"Select and upload files which differs"_, and pick the files you want to deploy. 84 | 85 | 86 | ### Making sure that each environment have the same structures and templates 87 | ![Screenshot of Diff](https://raw.githubusercontent.com/emiloberg/liferay-ddmtool/master/docs/images/screen-diff.png) 88 | 89 | Start up the DDM Tool and go into _"Find Diffs"_. From there you may show all diffs straight from the command line, open an external diff tool (see _Project settings_ below) or upload/download diffing files. 90 | 91 | **Pro tip:** 92 | Run DDM Tool with the `-i`/`--diffs` flag to go straight into diffs mode: 93 | 94 | ``` 95 | ddm -i --project --server 96 | ``` 97 | 98 | 99 | ### Download all DDM Stuff 100 | Want to save all structures and templates from a server to your local disk? Just run the app and select _"Download All"_ 101 | 102 | **Pro tip:** 103 | Run DDM Tool with the `-d`/`--download` flag to go straight into download mode: 104 | 105 | ``` 106 | ddm -d --project --server 107 | ``` 108 | 109 | ## Command line arguments 110 | You may also start the App with some command line arguments. One common way to start the app is `ddm --project --server ` to skip the project and server selection menues. Run with `--help` to get all available arguments. 111 | 112 | ## Limitations 113 | * Currently there's no way of syncing *removal* of files. If you remove a file on server, you must remove it locally and vice versa. 114 | * Every Template _must_ be assigned to a Structure. Don't create any templates on the Liferay server which does not have a structure connected to it. 115 | * Much of the magic comes form matching names. If there's a journal template on the server named 'My Template' the app will try to match it to the file project/journal/templates/My Template.ftl (or .vm). Therefor, if you rename a structure or template, it'll be seen as a new file. 116 | * There may be no exotic characters in the structure/template names (nothing which is not valid filename on an old Windows machine, so no slash, no comma, etc). 117 | * As we at [Monator](http://www.monator.com) are all running Macs, the DDM Tool hasn't been tested on Windows. 118 | 119 | ## Settings 120 | All config files are saved as JSON in `$USERHOME/.ddmtool`. 121 | 122 | ### Project Configuration Files 123 | For each project, there's a project configuration in `$USERHOME/.ddmtool/config/projects/project-name.json`. 124 | 125 | #### Sample Project Setting File 126 | 127 | ``` 128 | { 129 | "projectName": "myproject", 130 | "filesPath": "/code/ddm-repo", 131 | "defaultLocale": "en_US", 132 | "externalDiff": "/usr/bin/opendiff %1 %2", 133 | "allowSelfSignedCerts": true, 134 | "watchIgnorePattern": "^(\\#.*|.*\\~)$", 135 | "hosts": [ 136 | { 137 | "name": "local", 138 | "host": "http://localhost:8080", 139 | "username": "test@liferay.com", 140 | "password": "test", 141 | "email": "test@liferay.com" 142 | }, 143 | { 144 | "name": "prod1", 145 | "host": "http://123.123.123.123", 146 | "username": "admin", 147 | "password": "superstrongpassword", 148 | "email": "admin@company.com" 149 | } 150 | ], 151 | "ignoreDDMs": [ 152 | "EVENTS", 153 | "INVENTORY", 154 | "ISSUES TRACKING" 155 | ] 156 | } 157 | ``` 158 | 159 | #### Project Settings 160 | * `projectName`. Your project name. App may be called with command line argument `--project myproject` to skip the project selection menu. 161 | * `filesPath`. Full path to where your DDMs are. This is typically the folder you want to have under version control. 162 | * `defaultLocale`. New DDMs will be uploaded with the name/description in this locale. 163 | * `allowSelfSignedCerts`. By default, Node.js (and this tool) does not allow connections to hosts with self signed certificates. Set this to `true` to bypass this check and allow the connection. Added in version 0.8.6. 164 | * `watchIgnorePattern`. Some editors create temporary files. To make sure that the watch function doesn't try to upload those to the server you may specify a regex of files to ignore. Default settings ignore files starting with `#` and/or ending with `~`. Added in version 0.8.5. You need to add the `watchIgnorePattern` line to any previous projects you have, if you want to add the functionality. 165 | 166 | ##### Host(s) 167 | * Array of Liferay servers, e.g your local development server, your test server, your production server etc. 168 | * `name`. Your name of the server. App may be called with command line argument `--server local` to skip the server selection menu. If `--server` is supplied, so must `--project`. 169 | * `host`. Host and port to the Liferay server. 170 | * `username` & `password`. The username and password with which you will log in to Liferay. 171 | * `email`. Often email is the same as username. However, if you're not using email to log in, change this to the email adress connected to your Liferay account. Email is only used to lookup your user id (For some reason, we can't ask Liferay for the user id for the current logged in user). 172 | 173 | 174 | ##### Optional 175 | 176 | * `externalDiff`. Path to external diff tool. If externalDiff is supplied, you'll get a new choice to "open external diff tool" in the diff menu. Must have variables `%1` and `%2`. Like `/usr/bin/opendiff %1 %2`. `%1` will be replaced with "left" folder (local DDM repository) and `%2` will be replaced with "right" folder (server DDMs). 177 | * `ignoreDDMs`. Array of structure/template ID:s to be ignored by the App. Typically Liferay default structures/templates (if you don't remove them from server). 178 | 179 | 180 | ### Custom Class Names Configuraiton 181 | By default, the App will look for Liferay standard DDM entities (such as structures and templates for journal articles, ADTs, dynamic data lists, etc). 182 | 183 | If you want the App to be able to handle custom structures and templates for custom DDM entities you may create a `customClassNameConfig.json` file in `$USERHOME/.ddmtool/config/`. 184 | 185 | 1. Figure out the className of the new structure/template by querying the database for `select value from classname_ where classNameId = 12345`. If the App finds a structure/template it does not recognize, it will tell you. It'll also tell you the server classNameId of that structure/template which you'll use in the query. 186 | 2. Create/update the `customClassNameConfig.json` file, like below: 187 | 188 | #### Sample Custom Class Name Configuration 189 | ``` 190 | [ 191 | { 192 | "filesPath": "generic_record_set", 193 | "friendlyName": "Generic Record Set", 194 | "clazz": "com.monator.moh.genericddm.model.GenericRecordSet", 195 | "getTemplate": false, 196 | "mayHaveTemplates": true, 197 | "mayHaveStructures": true, 198 | "isNativeDDM": true 199 | } 200 | ] 201 | ``` 202 | 203 | * `filesPath` [string]. Subfolder (inside the folder you defined as filesPath for the project) the files will be saved in. No slashes anywhere please; no leading, no trailing and none inside. 204 | * `friendlyName` [string]. Name as it will be displayed in the user interface 205 | * `clazz` [string]. Class Name you got from your database query. 206 | * `getTemplate` [boolean]. Whether or not the `clazz` should be used when asking Liferay for all Templates. If unsure, set this to `false`, run the App and download the structures/templates and check if you get the templates you want. 207 | * `mayHaveTemplates` [boolean]. Within the folder defined in `filesPath`, may there be Templates? Typically this is true. 208 | * `mayHaveStructures` [boolean]. Within the folder defined in `filesPath`, may there be Structures? Typically this is true 209 | * `isNativeDDM` [optional boolean]. True if template's class name should be DDMStructure instead of the entity given by clazz. Typically this is false. -------------------------------------------------------------------------------- /lib/findDiffs.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var fs = require('fs-extra'); 4 | var Q = require('q'); 5 | var inquirer = require("inquirer"); 6 | 7 | var Config = require('./Config.js'); 8 | var Constants = require('./Constants.js'); 9 | var utilities = require('./utilities.js'); 10 | var lrException = require('./errorException.js'); 11 | 12 | var findDiffs = function() { 13 | 14 | var glob = require('glob'); 15 | var Table = require('cli-table'); 16 | 17 | var saveEverythingToFile = require('./saveEverything.js'); 18 | 19 | var allDDMs = []; 20 | var onlyLocalDDMs = []; 21 | var onlyServerDDMs = []; 22 | var bothSidesDDMs = []; 23 | var bothSidesEqual = []; 24 | var bothSidesDifferent = []; 25 | 26 | var ddmLocalPath = Config.fetch('filesFolder'); 27 | var ddmCachePath = Config.fetch('ddmCacheFolder'); 28 | 29 | // Download all DDMs to a temporary folder 30 | fs.removeSync(ddmCachePath); 31 | saveEverythingToFile(ddmCachePath, {silent: true, returnToMainMenu: false}); 32 | 33 | 34 | Q.resolve() 35 | // Find all DDMs available on server 36 | .then(function () { 37 | var deferred = Q.defer(); 38 | glob('**/*.+(ftl|xml|vm)', { cwd: ddmCachePath }, function (err, files) { 39 | if (err) { 40 | lrException(err); 41 | } 42 | 43 | files.forEach(function(entry) { 44 | allDDMs.push({file: entry, onServer: true}); 45 | }); 46 | 47 | deferred.resolve(); 48 | 49 | }); 50 | return deferred.promise; 51 | }) 52 | 53 | // Find all DDMs available locally and check if they're also available on server. 54 | .then(function () { 55 | var deferred = Q.defer(); 56 | glob('**/*.+(ftl|xml|vm)', { cwd: ddmLocalPath }, function (err, files) { 57 | if (err) { 58 | lrException(err); 59 | } 60 | 61 | // Check if file exist on Server or not. 62 | var found = false; 63 | files.forEach(function(entry) { 64 | found = false; 65 | for (var i = 0; i < allDDMs.length; i++) { 66 | if(allDDMs[i].file === entry) { 67 | allDDMs[i].onLocal = true; 68 | found = true; 69 | break; 70 | } 71 | } 72 | if (found === false) { 73 | allDDMs.push({file: entry, onLocal: true}); 74 | } 75 | }); 76 | 77 | deferred.resolve(); 78 | }); 79 | return deferred.promise; 80 | }) 81 | .then(function () { 82 | // Split into 3 different arrays depending on where files exist. 83 | allDDMs.forEach(function(entry) { 84 | if(entry.onServer) { 85 | if (entry.onLocal) { 86 | bothSidesDDMs.push(entry); 87 | } else { 88 | onlyServerDDMs.push(entry); 89 | } 90 | } else if(entry.onLocal) { 91 | onlyLocalDDMs.push(entry); 92 | } 93 | }); 94 | 95 | 96 | // Read all files which exist on both sides and check if equal or not 97 | var preparePromises = bothSidesDDMs.map(function(entry) { 98 | return readDDMs(entry.file, ddmCachePath, ddmLocalPath); 99 | }); 100 | Q.all(preparePromises).then(function (withScriptDDMs) { 101 | 102 | // Split into 2 different arrays, one for equal, one for different 103 | bothSidesEqual = withScriptDDMs.filter(function (entry) { 104 | return entry.isEqual; 105 | }); 106 | 107 | 108 | bothSidesDifferent = withScriptDDMs.filter(function (entry) { 109 | return entry.isEqual ^= true; 110 | }); 111 | 112 | var outTable; 113 | var statusCategories = [ 114 | { 115 | heading: 'Only available locally', 116 | data: onlyLocalDDMs 117 | }, 118 | { 119 | heading: 'Only available on server', 120 | data: onlyServerDDMs 121 | }, 122 | { 123 | heading: 'Differs between local and server', 124 | data: bothSidesDifferent 125 | } 126 | ] ; 127 | 128 | utilities.writeToScreen('-', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_INFO')); 129 | 130 | for (var i = 0; i < 3; i++) { 131 | if (statusCategories[i].data.length > 0) { 132 | outTable = new Table({ 133 | head: ['Path', 'Filename'], 134 | chars: { 135 | 'top': '', 'top-mid': '', 'top-left': '', 'top-right': '', 136 | 'bottom': '', 'bottom-mid': '', 'bottom-left': '', 'bottom-right': '', 137 | 'left': '', 'left-mid': '', 'mid': '', 'mid-mid': '', 138 | 'right': '', 'right-mid': '', 'middle': ' ' 139 | }, 140 | style: { 141 | 'padding-left': 2, 142 | 'padding-right': 0, 143 | 'head': ['magenta'] 144 | }, 145 | colWidths: [50] 146 | }); 147 | 148 | for (var x = 0; x < statusCategories[i].data.length; x++) { 149 | outTable.push([ 150 | utilities.filenameAndPathToPath(statusCategories[i].data[x].file), 151 | utilities.filenameAndPathToFilename(statusCategories[i].data[x].file, true) 152 | ]); 153 | } 154 | 155 | utilities.writeToScreen(statusCategories[i].heading, Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_HEADING')); 156 | utilities.writeToScreen(outTable.toString() + '\n', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_INFO')); 157 | } 158 | 159 | } 160 | 161 | // TODO: If *everything* is equal - print that (in like green). 162 | 163 | utilities.writeToScreen('Summary', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_HEADING')); 164 | utilities.writeToScreen(' ' + onlyLocalDDMs.length + ' files only on local', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_INFO')); 165 | utilities.writeToScreen(' ' + onlyServerDDMs.length + ' files only on server', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_INFO')); 166 | utilities.writeToScreen(' ' + bothSidesDifferent.length + ' files differs', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_INFO')); 167 | utilities.writeToScreen(' ' + bothSidesEqual.length + ' files equal', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_INFO')); 168 | 169 | diffMainMenu(bothSidesDifferent, ddmCachePath, ddmLocalPath, onlyLocalDDMs); 170 | 171 | }); 172 | 173 | }); 174 | 175 | }; 176 | 177 | 178 | 179 | function diffMainMenu(differsDDMs, ddmCachePath, ddmLocalPath, onlyLocalDDMs) { 180 | 181 | var showMainMenu = require('./mainMenu.js'); 182 | 183 | utilities.writeToScreen('-', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_INFO')); 184 | 185 | // TODO, ONLY SHOW CHOICES WHICH ARE AVAILABLE! 186 | 187 | var choices = [ 188 | { 189 | name: 'Show diffs', 190 | value: 'showdiffs' 191 | } 192 | ]; 193 | 194 | // Check if an external diff tool is configured. 195 | if (Config.fetch().hasOwnProperty('externalDiff')) { 196 | var externalDiffTool = Config.fetch('externalDiff'); 197 | 198 | if (externalDiffTool !== undefined) { 199 | choices.push( 200 | { 201 | name: 'Open external diff tool', 202 | value: 'externaldiff' 203 | }); 204 | } 205 | } 206 | 207 | choices.push( 208 | { 209 | name: 'Select and upload files which differs', 210 | value: 'uploaddiffs' 211 | }, { 212 | name: 'Download files which differs (TODO)', 213 | value: 'uploadSingleFileToServer' 214 | }, 215 | new inquirer.Separator(), 216 | { 217 | name: 'Return to Main Menu', 218 | value: 'mainmenu' 219 | } 220 | ); 221 | 222 | 223 | 224 | inquirer.prompt([ 225 | { 226 | type: "list", 227 | name: "diffmenu", 228 | message: "DIFFS MENU", 229 | choices: choices 230 | } 231 | ], function( answers ) { 232 | if (answers.diffmenu === 'mainmenu') { 233 | showMainMenu(); 234 | } else if (answers.diffmenu === 'showdiffs') { 235 | diffMenu(differsDDMs, ddmCachePath, ddmLocalPath, onlyLocalDDMs); 236 | } else if (answers.diffmenu === 'uploaddiffs') { 237 | uploadDiffsMenu(differsDDMs, ddmCachePath, ddmLocalPath, onlyLocalDDMs); 238 | } else if (answers.diffmenu === 'externaldiff') { 239 | externalDiff(externalDiffTool, ddmLocalPath, ddmCachePath, function() { 240 | showMainMenu(); 241 | }); 242 | } 243 | } 244 | ); 245 | } 246 | 247 | function externalDiff(toolPath, leftPath, rightPath, cb) { 248 | toolPath = toolPath.replace('%1', '"' + leftPath + '"'); 249 | toolPath = toolPath.replace('%2', '"' + rightPath + '"'); 250 | var exec = require('child_process').exec; 251 | var child = exec(toolPath, function (error) { 252 | if (error !== null) { 253 | lrException(error); 254 | } 255 | }); 256 | 257 | if (typeof cb === "function") { 258 | cb.call(); 259 | } 260 | 261 | } 262 | 263 | function uploadDiffsMenu(differsDDMs, ddmCachePath, ddmLocalPath, onlyLocalDDMs) { 264 | 265 | 266 | inquirer.prompt([ 267 | { 268 | type: "list", 269 | name: "whichfiles", 270 | message: "WHICH FILES DO YOU WANT TO UPLOAD?", 271 | choices: [ 272 | { 273 | name: 'All files which differs or are only available locally', 274 | value: 'all' 275 | }, 276 | { 277 | name: 'Select files', 278 | value: 'select' 279 | }, 280 | new inquirer.Separator(), 281 | { 282 | name: 'Return to Diff Menu', 283 | value: 'back' 284 | } 285 | ] 286 | } 287 | ], function( answersWhichFiles ) { 288 | if (answersWhichFiles.whichfiles === 'back') { 289 | diffMainMenu(differsDDMs, ddmCachePath, ddmLocalPath, onlyLocalDDMs); 290 | } else if (answersWhichFiles.whichfiles === 'all') { 291 | uploadDiffsAll(differsDDMs, ddmLocalPath, onlyLocalDDMs); 292 | } else if (answersWhichFiles.whichfiles === 'select') { 293 | uploadDiffsSelectMenu(differsDDMs, ddmCachePath, ddmLocalPath, onlyLocalDDMs); 294 | } 295 | } 296 | ); 297 | 298 | 299 | } 300 | 301 | function uploadDiffsAll(differsDDMs, ddmLocalPath, onlyLocalDDMs) { 302 | var uploadFiles = require('./uploadFiles.js'); 303 | 304 | var files = []; 305 | differsDDMs.forEach(function(entry) { 306 | files.push(ddmLocalPath + '/' + entry.file); 307 | }); 308 | onlyLocalDDMs.forEach(function(entry) { 309 | files.push(ddmLocalPath + '/' + entry.file); 310 | }); 311 | 312 | var cb = function() { 313 | var router = require('./router.js'); 314 | router(Constants.fetch('STEP_JUST_UPLOADED_DDMS')); 315 | }; 316 | 317 | uploadFiles(files, {autoMode: true}, cb); 318 | 319 | } 320 | 321 | function uploadDiffsSelectMenu(differsDDMs, ddmCachePath, ddmLocalPath, onlyLocalDDMs) { 322 | 323 | var uploadFiles = require('./uploadFiles.js'); 324 | 325 | var choices = []; 326 | differsDDMs.forEach(function(entry) { 327 | choices.push({name: '(differs): ' + entry.file, value: ddmLocalPath + '/' + entry.file}); 328 | }); 329 | onlyLocalDDMs.forEach(function(entry) { 330 | choices.push({name: '(only on local): ' + entry.file, value: ddmLocalPath + '/' + entry.file}); 331 | }); 332 | 333 | 334 | inquirer.prompt([ 335 | { 336 | type: "checkbox", 337 | name: "files", 338 | message: "SELECT FILES TO UPLOAD", 339 | choices: choices 340 | } 341 | ], function( answers ) { 342 | 343 | var cb = function() { 344 | var router = require('./router.js'); 345 | router(Constants.fetch('STEP_JUST_UPLOADED_DDMS')); 346 | }; 347 | 348 | if (answers.files.length > 0) { 349 | uploadFiles(answers.files, {autoMode: true}, cb); 350 | } else { 351 | uploadDiffsMenu(differsDDMs, ddmCachePath, ddmLocalPath, onlyLocalDDMs); 352 | } 353 | } 354 | ); 355 | 356 | } 357 | 358 | 359 | 360 | function diffMenu(differsDDMs, ddmCachePath, ddmLocalPath, onlyLocalDDMs) { 361 | // TODO: Add possibility to see content of files which are only available on server/locally 362 | var fileChoices = [ 363 | {name: 'Return to Diff Menu ', value: 'back'}, 364 | new inquirer.Separator() 365 | ]; 366 | differsDDMs.forEach(function(entry, index) { 367 | fileChoices.push({name: 'File ' + utilities.pad((index + 1) + ':', 3, 'right') + ' ' + entry.file, value: entry.file}); 368 | }); 369 | 370 | utilities.writeToScreen('-', Constants.fetch('SEVERITY_NORMAL'), Constants.fetch('SCREEN_PRINT_INFO')); 371 | 372 | inquirer.prompt([ 373 | { 374 | type: "list", 375 | name: "difffile", 376 | message: "SELECT FILES TO SHOW DIFF", 377 | choices: fileChoices 378 | } 379 | ], function( answers ) { 380 | if (answers.difffile === 'back') { 381 | diffMainMenu(differsDDMs, ddmCachePath, ddmLocalPath, onlyLocalDDMs); 382 | } else { 383 | showDiff(differsDDMs, ddmCachePath, ddmLocalPath, onlyLocalDDMs, answers.difffile); 384 | } 385 | } 386 | ); 387 | } 388 | 389 | 390 | 391 | function showDiff(differsDDMs, ddmCachePath, ddmLocalPath, onlyLocalDDMs, file) { 392 | var chalk = require('chalk'); 393 | var jsdiff = require('diff'); 394 | 395 | var localFile = fs.readFileSync(ddmCachePath + '/' + file, {encoding: Constants.fetch('filesEncoding')}); 396 | var serverFile = fs.readFileSync(ddmLocalPath + '/' + file, {encoding: Constants.fetch('filesEncoding')}); 397 | var diff = jsdiff.diffLines(localFile, serverFile); 398 | var lines = []; 399 | 400 | // Loop through every huck 401 | // A hunk is a block of text which is either added, removed or unchanged. 402 | diff.forEach(function(part){ 403 | // Split each hunk into lines to be able to add line numbers. 404 | var textBlock = trimTrailingNewLine(part.value).match(/^.*([\n\r]|$)/gm); 405 | textBlock.forEach(function(entry) { 406 | lines.push({line: trimTrailingNewLine(entry), added: part.added, removed: part.removed}); 407 | }); 408 | }); 409 | 410 | // Print every line, with the correct line number, color and +/- marking. 411 | var removedLines = 0; 412 | lines.forEach(function(entry, index) { 413 | if (entry.added) { 414 | console.log(chalk.green(utilities.pad(index + 1 - removedLines, 4, 'left') + ' + | ' + entry.line)); 415 | } else if (entry.removed) { 416 | console.log(chalk.red(' ' + ' - | ' + entry.line)); 417 | removedLines += 1; 418 | } else { 419 | console.log(chalk.reset(utilities.pad(index + 1 - removedLines, 4, 'left') + ' | ' + entry.line)); 420 | } 421 | }); 422 | 423 | // Return to diff menu. 424 | diffMenu(differsDDMs, ddmCachePath, ddmLocalPath, onlyLocalDDMs); 425 | } 426 | 427 | 428 | 429 | function trimTrailingNewLine(str) { 430 | str = str.replace(/(\r\n|\n|\r)$/g,''); 431 | return str; 432 | } 433 | 434 | 435 | 436 | function readDDMs(file, localPath, serverPath) { 437 | var isEqual = false; 438 | var deferred = Q.defer(); 439 | fs.readFile(localPath + '/' + file, {encoding: Constants.fetch('filesEncoding')}, function (er, localData) { 440 | if (er) throw er; 441 | fs.readFile(serverPath + '/' + file, {encoding: Constants.fetch('filesEncoding')}, function (er, serverData) { 442 | if (er) throw er; 443 | isEqual = localData === serverData; 444 | deferred.resolve({file: file, isEqual: isEqual, localData: localData, serverData: serverData}); 445 | }); 446 | }); 447 | return deferred.promise; 448 | } 449 | 450 | module.exports = findDiffs; --------------------------------------------------------------------------------