UNPKG

ljswitchboard-package_loader

Version:

A small node/node-webkit project loader for ljswitchboard that loads files into the global.ljswitchboard namespace

1,392 lines (1,267 loc) 41.8 kB
var EventEmitter = require('events').EventEmitter; var util = require('util'); var path = require('path'); var q = require('q'); // Switch to using a minified local version of semver // var semver = require('semver'); var semver = require('./semver_min'); var fs = require('fs.extra'); var fse = require('fs-extra'); var gns = 'ljswitchboard'; global[gns] = {}; var useYauzl = true; var DEBUG_PACKAGE_EXTRACTION_STEPS = false; function debugSteps () { if(DEBUG_PACKAGE_EXTRACTION_STEPS) { console.log.apply(console, arguments); } } function debugPackageChecking () { if(DEBUG_PACKAGE_EXTRACTION_STEPS) { console.log.apply(console, arguments); } } var EVENTS = { // Events emitted during the loadPackage function, they all return the // name of the package being handled. OPENED_WINDOW: 'opened_window', NOT_STARTING_NW_APP: 'not_starting_nw_app', LOADED_PACKAGE: 'loaded_package', NOT_LOADING_PACKAGE: 'not_loading_package', SET_PACKAGE: 'set_package', OVERWRITING_MANAGED_PACKAGE: 'overwriting_managed_package', // Events emitted during the runPackageManager function, they all return the // 'bundle' object which is all of the data currently extracted/collected // for an individual packaged being managed. Within is a packageInfo object // that is +- what was passed to the package_loader in the loadPackage // function. PACKAGE_MANAGEMENT_STARTED: 'package_management_started', VALID_UPGRADE_DETECTED: 'valid_upgrade_detected', NO_VALID_UPGRADE_DETECTED: 'no_valid_upgrade_detected', DETECTED_UNINITIALIZED_PACKAGE: 'detected_uninitialized_package', RESETTING_PACKAGE: 'resetting_package', FINISHED_RESETTING_PACKAGE: 'finished_resetting_package', FINISHED_RESETTING_PACKAGE_ERROR: 'finished_resetting_package_error', DETECTED_UP_TO_DATE_PACKAGE: 'detected_up_to_date_package', SKIPPING_PACKAGE_RESET: 'skipping_package_reset', SKIPPING_PACKAGE_UPGRADE: 'skipping_package_upgrade', STARTING_EXTRACTION: 'starting_extraction', STARTING_ZIP_FILE_EXTRACTION: 'starting_zip_file_extraction', STARTING_DIRECTORY_EXTRACTION: 'starting_directory_extraction', FINISHED_EXTRACTION: 'finished_extraction', FINISHED_ZIP_FILE_EXTRACTION: 'finished_zip_file_extraction', FINISHED_DIRECTORY_EXTRACTION: 'finished_directory_extraction', FINISHED_EXTRACTION_ERROR: 'finished_extraction_error', FINISHED_ZIP_FILE_EXTRACTION_ERROR: 'finished_zip_file_extraction_error', FINISHED_DIRECTORY_EXTRACTION_ERROR: 'finished_directory_extraction_error', FAILED_TO_LOAD_MANAGED_PACKAGE: 'failed_to_load_managed_package', FAILED_TO_INITIALIZE_PACKAGE_MANAGER: 'failed_to_initialize_package_manager', }; exports.eventList = EVENTS; exports.setNameSpace = function(namespace) { global[namespace] = {}; var curKeys = Object.keys(global[gns]); curKeys.forEach(function(curKey) { global[namespace][curKey] = global[gns][curKey]; global[gns][curKey] = null; delete global[gns][curKey]; }); global[gns] = null; delete global[gns]; gns = namespace; }; exports.getNameSpace = function() { return gns; }; function createPackageLoader() { this.extractionPath = ''; this.managedPackages = {}; this.managedPackagesList = []; this.dependencyData = {}; // Load the package.json of the current module (ljswitchboard-package_loader) // to save its information in the dependencyData object incase other modules // require it as a dependency. var packageLoaderPackageInfo = require('../package.json'); var selfName = packageLoaderPackageInfo.name; var selfVersion = packageLoaderPackageInfo.version; this.dependencyData[selfName] = { 'name': selfName, 'version': selfVersion }; var packageInfoFileName = 'package_loader_data.json'; var startNWApp = function(packageInfo, info) { // console.log('nwApp detected', packageInfo, info); if(global[gns].gui) { // Local reference to nw.gui var gui = global[gns].gui; var curApp = global[gns][packageInfo.name]; // Get the module's data that should be used when opening the new window var newWindowData; if(curApp.data) { if(curApp.data.window) { newWindowData = curApp.data.window; } } // Build the url and moduleData path var windowPath = 'file:///' + path.join(packageInfo.location, info.main); // Open a new window and save its reference curApp.win = gui.Window.open( windowPath, newWindowData ); // If desired, open the window's devTools if(packageInfo.showDevTools) { curApp.win.showDevTools(); } // Emit an event indicating that a new window has been opened. self.emit(EVENTS.OPENED_WINDOW, packageInfo.name); } }; var startPackage = function(packageInfo, name) { try { if(global[gns][name].info) { // Load the moduleData (its package.json file) & save it to info.data; var moduleData = getRequiredPackageData(packageInfo.location); // Add module path to the global.module.paths index var extraPaths = [ 'node_modules', path.join('lib', 'node_modules') ]; if(global[gns][name].info.type) { if(global[gns][name].info.type === 'library') { extraPaths.push('..'); } } extraPaths.forEach(function(extraPath) { var dirToAdd = packageInfo.location; var modulesDirToAdd = path.join(dirToAdd, extraPath); modulesDirToAdd = path.normalize(modulesDirToAdd); if(global.module) { if(global.module.paths) { global.module.paths.splice(2,0,modulesDirToAdd); } } }); global[gns][name].data = moduleData; if(global[gns][name].info.type) { if(global[gns][name].info.type === 'nwApp') { // Determine if the app should be started. var startApp = false; if(typeof(packageInfo.startApp) !== 'undefined') { if(packageInfo.startApp) { startApp = true; } } else { startApp = true; } // If desired, start the application. if(startApp) { startNWApp(packageInfo, global[gns][name].info); } else { // Emit an event indicating that we aren't // starting the detected nwApp self.emit(EVENTS.NOT_STARTING_NW_APP, packageInfo.name); } } } } else { console.warn('Info Object does not exist, not doing anything special', packageInfo, name); } } catch (err) { console.error(' - package_loader: startPackage Error', err, name); } }; var getRequiredPackageData = function(moduleLocation) { var moduleData = {}; try { // Build package.json path var moduleDataPath = path.join(moduleLocation, 'package.json'); // Load the moduleData (its package.json file) & save it to info.data; moduleData = require(moduleDataPath); } catch(err) { console.error(' - Failed to load package data', err, name); } return moduleData; }; var requirePackage = function(packageInfo) { var name = packageInfo.name; var location; var requireStr; var loadedPackages = Object.keys(global[gns]); // Make sure that we aren't over-writing a global variable by requiring // the new lib. if(loadedPackages.indexOf(name) < 0) { if(packageInfo.location) { location = packageInfo.location; if(packageInfo.requireStr) { requireStr = packageInfo.requireStr; } else { requireStr = location; } if(global.require) { global[gns][name] = global.require(requireStr); startPackage(packageInfo, name); } else { global[gns][name] = require(requireStr); startPackage(packageInfo, name); } global[gns][name].packageInfo = packageInfo; self.emit(EVENTS.LOADED_PACKAGE, packageInfo); } } else { self.emit(EVENTS.NOT_LOADING_PACKAGE, packageInfo); } }; var setPackage = function(packageInfo) { var name = packageInfo.name; var location; var requireStr; if(packageInfo.ref) { global[gns][name] = packageInfo.ref; self.emit(EVENTS.SET_PACKAGE, name); self.emit(EVENTS.LOADED_PACKAGE, packageInfo); } }; var managePackage = function(packageInfo) { var name = packageInfo.name; var location; var requireStr; if(self.managedPackages[name]) { self.emit(EVENTS.OVERWRITING_MANAGED_PACKAGE, packageInfo); } else { self.managedPackagesList.push(name); // console.log('Adding a managed package', name); } self.managedPackages[name] = packageInfo; self.managedPackages[name].version = ''; self.managedPackages[name].location = ''; }; /** * Loads packages and saves them to the global scope. It also prepares a * list of managedPackages that have to get started separately due to their * async loading requirements. */ this.loadPackage = function(packageInfo) { var defered = q.defer(); try { var name = packageInfo.name; var location; var requireStr; var method = 'require'; if(packageInfo.loadMethod) { method = packageInfo.loadMethod; } if(method === 'require') { try { requirePackage(packageInfo); } catch (err) { console.error(' - package_loader: Failed to require package', err); } } else if (method === 'set') { setPackage(packageInfo); } else if (method === 'managed') { managePackage(packageInfo); } } catch (err) { console.error(' - package_loader: loadPackage Error', err); } }; /** * Returns a list of the packages managed by the package_loader */ this.getManagedPackages = function() { return self.managedPackagesList; }; this.getDependencyList = function() { return Object.keys(self.dependencyData); }; this.getDependencyData = function() { var retData = {}; try { retData = JSON.parse(JSON.stringify(self.dependencyData)); } catch(err) { retData = {}; } return retData; }; this.deleteManagedPackage = function(name) { var isValid = false; if(self.managedPackages[name]) { // Delete the item from the managedPackagesList var newList = []; self.managedPackagesList.forEach(function(item) { if(item !== name) { newList.push(item); } }); self.managedPackagesList = newList; self.managedPackages[name] = null; self.managedPackages[name] = undefined; delete self.managedPackages[name]; isValid = true; } // Only delete managedObjects from the global scope & dependency obj. if(isValid) { if(self.dependencyData[name]) { self.dependencyData[name] = null; self.dependencyData[name] = undefined; delete self.dependencyData[name]; } if(global[gns][name]) { global[gns][name] = null; global[gns][name] = undefined; delete global[gns][name]; } } }; this.deleteAllManagedPackages = function() { var packagesToDelete = []; self.managedPackagesList.forEach(function(packageToDelete) { packagesToDelete.push(packageToDelete); }); packagesToDelete.forEach(self.deleteManagedPackage); }; var initializeInfoFile = function(bundle) { var defered = q.defer(); var filePath = path.normalize(path.join( self.extractionPath, packageInfoFileName )); fs.writeFile(filePath, JSON.stringify({}), function(err) { if(err) { if(err.code === 'ENOENT') { var errPath = err.path; console.error('Error Initializing package_loader info file b/c of permissions', err); var message = ''; message += 'Write-access required for: ' + path.dirname(errPath); console.error('Message:', message); self.emit(EVENTS.FAILED_TO_INITIALIZE_PACKAGE_MANAGER, message); } else { console.error('Error Initializing package_loader info file', err); } defered.resolve(bundle); } else { defered.resolve(bundle); } }); return defered.promise; }; var readInfoFile = function(defaultData) { var defered = q.defer(); var filePath = path.normalize(path.join( self.extractionPath, packageInfoFileName )); var finishFunc = function(fileData) { var parsedFileData = {}; try { parsedFileData = JSON.parse( fileData.toString('ascii')); // If the file is only "{}" the obj will be 'null'. // force it to be an object. if(parsedFileData === null) { parsedFileData = {}; } defered.resolve(parsedFileData); } catch(jsonParseError) { // Error parsing the JSON file so it should be // re-initialized and assume data is invalid/what was // passed into the function. initializeInfoFile(defaultData) .then(defered.resolve); } }; fs.exists(filePath, function(exists) { if(exists) { fs.readFile(filePath, function(err, fileData) { if(err) { fs.readFile(filePath, function(errB, fileData) { if(errB) { fs.readFile(filePath, function(errB, fileData) { if(errB) { initializeInfoFile(defaultData) .then(defered.resolve); } else { finishFunc(fileData); } }); } else { finishFunc(fileData); } }); } else { finishFunc(fileData); } }); } else { // Error finding the file so try to re-initialize it and assume // data is invalid/what was passed into the function. initializeInfoFile(defaultData) .then(defered.resolve); } }); return defered.promise; }; var writeInfoFile = function(dataToWrite) { var defered = q.defer(); var filePath = path.normalize(path.join( self.extractionPath, packageInfoFileName )); // Create a string that is readable. dataToWrite = JSON.stringify(dataToWrite, null, 2); fs.exists(filePath, function(exists) { if(exists) { fs.writeFile(filePath, dataToWrite, function(err) { if(err) { initializeInfoFile({}) .then(defered.resolve); } else { defered.resolve({}); } }); } else { // Error finding the file so try to re-initialize it and assume // data is invalid/what was passed into the function. initializeInfoFile({}) .then(defered.resolve); } }); return defered.promise; }; var declarePackageInvalid = function(bundle) { var defered = q.defer(); var defaultData = {}; // Store information about packages by their folderName var packageName = bundle.packageInfo.folderName; // Read the existing file contents readInfoFile(defaultData) .then(function(data) { // If the packageInfo.name object dats is already there, declare the // validity attribute to be false. if(data[packageName]) { data[packageName].isValid = false; data[packageName].version = bundle.currentPackage.version; } else { // Otherwise, initialize it to be false. var initData = { 'isValid': false, 'name': packageName, 'location': bundle.currentPackage.location, 'version': bundle.currentPackage.version }; data[packageName] = initData; } // Write the data back to the file. writeInfoFile(data) .then(function(res) { defered.resolve(bundle); }); }); return defered.promise; }; var declarePackageValid = function(bundle) { var defered = q.defer(); var defaultData = {}; // Store information about packages by their folderName var packageName = bundle.packageInfo.folderName; // Read the existing file contents readInfoFile(defaultData) .then(function(data) { // If the packageInfo.name object dats is already there, declare the // validity attribute to be false. if(data[packageName]) { data[packageName].isValid = true; data[packageName].version = bundle.managedPackage.version; } else { // Otherwise, initialize it to be true. var initData = { 'isValid': true, 'name': packageName, 'location': bundle.currentPackage.location, 'version': bundle.managedPackage.version }; data[packageName] = initData; } // Write the data back to the file. writeInfoFile(data) .then(function(res) { defered.resolve(bundle); }); }); return defered.promise; }; var checkForExtractionErrors = function(bundle) { debugSteps('in checkForExtractionErrors'); var defered = q.defer(); var defaultData = {}; // Information about packages is stored by their folderName. var packageName = bundle.packageInfo.folderName; // console.log('currentPackage', bundle.currentPackage) // Read the existing file contents readInfoFile(defaultData) .then(function(data) { // console.log('Read Data', data); // If the packageInfo.name object data is there then read the // isValid attribute, otherwise say that there are extraction errors. if(data) { if(data[packageName]) { if(data[packageName].isValid) { // Current Package is valid (should already be true). } else { // Current package isn't valid. bundle.currentPackage.isValid = false; } } else { // Current package isn't valid. bundle.currentPackage.isValid = false; } } else { bundle.currentPackage.isValid = false; } defered.resolve(bundle); }); return defered.promise; }; var checkForManagedPackageExtractionErrors = function(bundle) { var defered = q.defer(); var defaultData = {}; // Information about packages is stored by their folderName. var packageName = bundle.packageInfo.folderName; // console.log('currentPackage', bundle.currentPackage) // Read the existing file contents readInfoFile(defaultData) .then(function(data) { // console.log('Read Data', data); // If the packageInfo.name object data is there then read the // isValid attribute, otherwise say that there are extraction errors. if(data) { if(data[packageName]) { if(data[packageName].isValid) { // Current Package is valid (should already be true). } else { // Current package isn't valid. bundle.managedPackage.isValid = false; } } else { // Current package isn't valid. bundle.managedPackage.isValid = false; } } else { bundle.managedPackage.isValid = false; } defered.resolve(bundle); }); return defered.promise; }; var checkforExistingDirectory = function(packageInfo) { var defered = q.defer(); fs.exists(packageInfo.location, function(exists) { packageInfo.exists = exists; defered.resolve(packageInfo); }); return defered.promise; }; var checkPackageType = function(packageInfo) { var defered = q.defer(); if(packageInfo.exists) { fs.stat(packageInfo.location, function(err, stats) { if(err) { packageInfo.type = ''; defered.resolve(packageInfo); } else { var isFile = stats.isFile(); var isDirectory = stats.isDirectory(); packageInfo.type = ''; if(isFile) { packageInfo.type = path.extname(packageInfo.location); } if(isDirectory) { packageInfo.type = 'directory'; } defered.resolve(packageInfo); } }); } else { packageInfo.type = ''; defered.resolve(packageInfo); } return defered.promise; }; var getPackageVersionOfDirectory = function(packageInfo) { var defered = q.defer(); var packageDataDir = path.join(packageInfo.location, 'package.json'); var finishFunc = function(data) { try { var packageData = JSON.parse(data); if(packageData.version) { if(semver.valid(packageData.version)) { packageInfo.version = packageData.version; } } if(packageData.ljswitchboardDependencies) { packageInfo.dependencies = packageData.ljswitchboardDependencies; } defered.resolve(packageInfo); } catch(jsonParseError) { defered.resolve(packageInfo); } }; fs.exists(packageDataDir, function(exists) { if(exists) { fs.readFile(packageDataDir, function(err, data) { if(err) { fs.readFile(packageDataDir, function(err, data) { if(err) { fs.readFile(packageDataDir, function(err, data) { if(err) { defered.resolve(packageInfo); } else { finishFunc(data); } }); } else { finishFunc(data); } }); } else { finishFunc(data); } }); } else { defered.resolve(packageInfo); } }); return defered.promise; }; try { // var parse_with_unzip = require('./parsers/parse_with_unzip'); // var parseWithUnzip = parse_with_unzip.parseWithUnzip; var parse_with_yauzl = require('./parsers/parse_with_yauzl'); var parseWithYauzl = parse_with_yauzl.parseWithYauzl; } catch(err) { console.log('ERROR requiring parsers', err); } var getPackageVersionOfZip = function(packageInfo) { // return parseWithUnzip(packageInfo); debugPackageChecking('in getPackageVersionOfZip', packageInfo); return parseWithYauzl(packageInfo); }; var checkPackageVersion = function(packageInfo) { var defered = q.defer(); packageInfo.version = ''; if(packageInfo.exists) { if(packageInfo.type === 'directory') { getPackageVersionOfDirectory(packageInfo) .then(defered.resolve); } else if(packageInfo.type === '.zip') { getPackageVersionOfZip(packageInfo) .then(defered.resolve); } else { defered.resolve(packageInfo); } } else { defered.resolve(packageInfo); } return defered.promise; }; var checkPackageValidity = function(packageInfo) { debugPackageChecking('in checkPackageValidity'); var defered = q.defer(); if(packageInfo.exists) { if(packageInfo.type) { if(packageInfo.version) { packageInfo.isValid = true; } } } defered.resolve(packageInfo); return defered.promise; }; var checkForValidPackage = function(location) { debugPackageChecking('in checkForValidPackage', location); var defered = q.defer(); var packageInfo = { 'location': location, 'exists': null, 'version': null, 'type': null, 'isValid': false, 'dependencies': {} }; checkforExistingDirectory(packageInfo) .then(checkPackageType) .then(checkPackageVersion) .then(checkPackageValidity) .then(function(info) { defered.resolve(info); }); return defered.promise; }; var checkForUpgradeOptions = function(bundle) { debugSteps('in checkForUpgradeOptions'); var defered = q.defer(); var dirsToCheck = bundle.packageInfo.locations; var checkDirOps = []; dirsToCheck.forEach(function(dir) { checkDirOps.push(checkForValidPackage(dir)); }); // Wait for all of the operations to complete q.allSettled(checkDirOps) .then(function(opgradeOptions) { var validUpgrades = []; // Loop through and pick out the valid upgrades opgradeOptions.forEach(function(opgradeOption) { if(opgradeOption.value) { if(opgradeOption.value.isValid) { validUpgrades.push(opgradeOption.value); } } }); // Save the information about the currently available upgrades bundle.availableUpgrades = validUpgrades; defered.resolve(bundle); }, function(err) { console.error(' - Finished Managing err', err, bundle.name); defered.reject(err); }); return defered.promise; }; var checkForExistingPackage = function(bundle) { var defered = q.defer(); var dirToCheck = path.join(self.extractionPath, bundle.packageInfo.folderName); checkForValidPackage(dirToCheck) .then(function(currentPackage) { // Save the information about the currently installed package bundle.currentPackage = currentPackage; // Also save the found version number to the managed packages // object and the dependencyData object. (create it if it doesn't // exist). self.managedPackages[bundle.name].version = currentPackage.version; if(self.dependencyData[bundle.name]) { self.dependencyData[bundle.name].version = currentPackage.version; self.dependencyData[bundle.name].name = bundle.name; } else { self.dependencyData[bundle.name] = {}; self.dependencyData[bundle.name].version = currentPackage.version; self.dependencyData[bundle.name].name = bundle.name; } defered.resolve(bundle); }); return defered.promise; }; var chooseValidUpgrade = function(bundle) { debugSteps('in chooseValidUpgrade'); var defered = q.defer(); var chosenUpgrade; // Pick the first-found package whose dependencies are met var foundValidUpgrade = bundle.availableUpgrades.forEach(function(upgrade) { var isValid = true; // Check to see if its dependencies are met by the objects currently // managed by the package_loader, aka is the data in // the self.dependencyData object and are the versions compatable. var requirementKeys = Object.keys(upgrade.dependencies); requirementKeys.every(function(key) { // Check to see if the dependency is found if(typeof(self.dependencyData[key]) !== 'undefined') { // Make sure that the dependency has a valid version number if(self.dependencyData[key].version) { var satisfies = semver.satisfies( self.dependencyData[key].version, upgrade.dependencies[key] ); if(satisfies) { isValid = true; } else { isValid = false; } } else { isValid = false; } } else { isValid = false; } return isValid; }); // Check to make sure that the available upgrade has a valid type if(isValid) { if(upgrade.type) { if(upgrade.type === 'directory') { isValid = true; } else if (upgrade.type === '.zip') { isValid = true; } else { isValid = false; } } else { isValid = false; } } if(isValid) { if(typeof(chosenUpgrade) === 'undefined') { // Save the selected upgrade option chosenUpgrade = upgrade; } else { // Only replace the chosenUpgrade obj if the newer one has a // newer verion number var isNewer = semver.lt( chosenUpgrade.version, upgrade.version ); // console.log(isNewer, chosenUpgrade.version, upgrade.version, upgrade.type); if(isNewer) { chosenUpgrade = upgrade; } } } }); if(typeof(chosenUpgrade) === 'undefined') { // Emit an event indicating that a valid upgrade has been // detected. self.emit(EVENTS.NO_VALID_UPGRADE_DETECTED, bundle); } else { // Emit an event indicating that a valid upgrade has been // detected. self.emit(EVENTS.VALID_UPGRADE_DETECTED, bundle); } bundle.chosenUpgrade = chosenUpgrade; defered.resolve(bundle); return defered.promise; }; var determineRequiredOperations = function(bundle) { var defered = q.defer(); var isCurrentValid = bundle.currentPackage.isValid; var isUpgradeValid = false; var isUpgradeAvailable = false; if(bundle.chosenUpgrade) { if(bundle.chosenUpgrade.isValid) { isUpgradeAvailable = true; } } // If there is a valid current installation then see if it needs to be // upgraded. if(isCurrentValid) { // Check to see if the upgrade version is newer than what is // installed. if(isUpgradeAvailable) { var performUpgrade = semver.lt( bundle.currentPackage.version, bundle.chosenUpgrade.version ); // If the package indicates that it should be forced to refresh // then handle that flag here. if(bundle.packageInfo.forceRefresh) { performUpgrade = true; } if(performUpgrade) { bundle.resetPackage = true; bundle.performUpgrade = true; } else { self.emit(EVENTS.DETECTED_UP_TO_DATE_PACKAGE, bundle); } } else { self.emit(EVENTS.DETECTED_UP_TO_DATE_PACKAGE, bundle); } } else { // If the current installation isn't valid then force it to be // upgraded. unless there are no valid upgrades. if(isUpgradeAvailable) { bundle.resetPackage = true; bundle.performUpgrade = true; // Emit an event indicating that an unitialized package has been // detected self.emit(EVENTS.DETECTED_UNINITIALIZED_PACKAGE, bundle); } else { console.error(' - Detected uninitialized package w/ no upgrade options', bundle.name); var msg = 'Detected an uninitialized package with no upgrade ' + 'options'; bundle.resultMessages.push({ 'step': 'resetPackageDirectory', 'message': msg, 'isError': true, }); bundle.overallResult = false; bundle.isError = true; } } defered.resolve(bundle); return defered.promise; }; var performPackageReset = function(bundle) { var defered = q.defer(); fs.exists(bundle.currentPackage.location, function(exists) { if(exists) { // Emit an event indicating that the package is being reset. self.emit(EVENTS.RESETTING_PACKAGE, bundle); fs.rmrf(bundle.currentPackage.location, function(err) { if(err) { console.error(' - Error resetPackageDirectory', err, bundle.name); var msg = 'Error resetting the package-cache, try' + 'manually deleting the folder:' + bundle.currentPackage.location; bundle.resultMessages.push({ 'step': 'resetPackageDirectory', 'message': msg, 'isError': true, 'error': JSON.stringify(err) }); bundle.overallResult = false; bundle.isError = true; // Emit an event indicating that the package is // finished being reset with an error. self.emit(EVENTS.FINISHED_RESETTING_PACKAGE_ERROR, bundle); defered.resolve(bundle); } else { // Emit an event indicating that the package is // finished being reset. self.emit(EVENTS.FINISHED_RESETTING_PACKAGE, bundle); defered.resolve(bundle); } }); } else { bundle.resultMessages.push({ 'step': 'resetPackageDirectory', 'message': 'Package directory not deleted b/c it does not exist' }); // The folder doesn't exist so don't remove it defered.resolve(bundle); } }); return defered.promise; }; var resetPackageDirectory = function(bundle) { var defered = q.defer(); if(bundle.resetPackage) { declarePackageInvalid(bundle) .then(performPackageReset) .then(defered.resolve, defered.reject); } else { self.emit(EVENTS.SKIPPING_PACKAGE_RESET, bundle); defered.resolve(bundle); } return defered.promise; }; var performDirectoryUpgrade = function(bundle) { var defered = q.defer(); // Save info to shorter variables var destinationPath = bundle.currentPackage.location; var upgradeFilesPath = bundle.chosenUpgrade.location; // Emit events indicating that a directory extraction has started self.emit(EVENTS.STARTING_EXTRACTION, bundle); self.emit(EVENTS.STARTING_DIRECTORY_EXTRACTION, bundle); // fs.copyRecursive(upgradeFilesPath, destinationPath, function(err) { fse.copy(upgradeFilesPath, destinationPath, function(err) { if(err) { console.error(' - Error performDirectoryUpgrade', err, bundle.name); var msg = 'Error performing a directory upgrade. Verify' + 'the user-permissions for the two directories: ' + upgradeFilesPath + ', and ' + destinationPath; bundle.resultMessages.push({ 'step': 'performDirectoryUpgrade-copyRecursive', 'message': msg, 'isError': true, 'error': JSON.stringify(err) }); bundle.overallResult = false; bundle.isError = true; // Emit events indicating that a zip file extraction has // finished w/ an error self.emit(EVENTS.FINISHED_EXTRACTION_ERROR, bundle); self.emit(EVENTS.FINISHED_DIRECTORY_EXTRACTION_ERROR, bundle); defered.resolve(bundle); } else { // Emit events indicating that a directory extraction has finished self.emit(EVENTS.FINISHED_EXTRACTION, bundle); self.emit(EVENTS.FINISHED_DIRECTORY_EXTRACTION, bundle); defered.resolve(bundle); } }); return defered.promise; }; try { // var extract_with_unzip = require('./extractors/extract_with_unzip'); // var extractWithUnzip = extract_with_unzip.extractWithUnzip; var extract_with_yauzl = require('./extractors/extract_with_yauzl'); var extractWithYauzl = extract_with_yauzl.extractWithYauzl; } catch(err) { console.log('Error including extractors', err); } var performZipFileUpgrade = function(bundle) { // return extractWithUnzip(bundle, self, EVENTS); return extractWithYauzl(bundle, self, EVENTS); }; var performUpgrade = function(bundle) { var defered = q.defer(); if(bundle.performUpgrade) { // Save info to shorter variables var destination = bundle.currentPackage.location; var upgradePath = ''; var upgradeType = ''; if(bundle.chosenUpgrade) { upgradePath = bundle.chosenUpgrade.location; upgradeType = bundle.chosenUpgrade.type; } // Determine what kind of upgrade process we need to perform if(upgradeType === 'directory') { declarePackageInvalid(bundle) .then(performDirectoryUpgrade) .then(defered.resolve, defered.reject); } else if(upgradeType === '.zip') { declarePackageInvalid(bundle) .then(performZipFileUpgrade) .then(defered.resolve, defered.reject); } else { console.error(' - in performUpgrade, invalid upgradeType detected', bundle.name); var msg = 'Failed to perform upgrade, invalid upgradeType ' + 'detected: ' + upgradeType; bundle.resultMessages.push({ 'step': 'performUpgrade', 'message': msg }); // If the current installation isn't valid and we reset the // package directory, we have issues because we can't // load/upgrade/install this library... var isCurrentValid = bundle.currentPackage.isValid; if((!isCurrentValid) && (bundle.resetPackage)) { bundle.overallResult = false; bundle.isError = true; } defered.resolve(bundle); } } else { self.emit(EVENTS.SKIPPING_PACKAGE_UPGRADE, bundle); defered.resolve(bundle); } return defered.promise; }; var verifyPackageUpgrade = function(bundle) { var defered = q.defer(); var dirToCheck = path.join(self.extractionPath, bundle.packageInfo.folderName); checkForValidPackage(dirToCheck) .then(function(managedPackage) { // Save the information about the currently installed package bundle.managedPackage = managedPackage; // Also save the found version number to the managed packages // object and the dependencyData object. (create it if it doesn't // exist). self.managedPackages[bundle.name].version = managedPackage.version; self.managedPackages[bundle.name].location = managedPackage.location; // Also save that information to the local packageInfo object bundle.packageInfo.version = managedPackage.version; bundle.packageInfo.location = managedPackage.location; if(self.dependencyData[bundle.name]) { self.dependencyData[bundle.name].version = managedPackage.version; self.dependencyData[bundle.name].name = bundle.name; } else { self.dependencyData[bundle.name] = {}; self.dependencyData[bundle.name].version = managedPackage.version; self.dependencyData[bundle.name].name = bundle.name; } declarePackageValid(bundle) .then(checkForManagedPackageExtractionErrors) .then(defered.resolve, defered.reject); }); return defered.promise; }; var extendedLoadManagedPackage = function(bundle) { requirePackage(bundle.packageInfo); bundle.packageData = getRequiredPackageData( bundle.packageInfo.location ); bundle.packageLoaded = true; bundle.overallResult = true; return bundle; }; var loadManagedPackage = function(bundle) { var defered = q.defer(); // Verify that the packageManagement process went smoothly if(!bundle.isError) { if(bundle.managedPackage.isValid) { // If process succeeded load the package try { if(bundle.packageInfo.directLoad) { if(bundle.chosenUpgrade) { if(bundle.chosenUpgrade.type) { if(bundle.chosenUpgrade.type === 'directory') { if(bundle.chosenUpgrade.exists && bundle.chosenUpgrade.isValid) { bundle.packageInfo.location = bundle.chosenUpgrade.location; } } } } } bundle = extendedLoadManagedPackage(bundle); } catch(err) { console.error(' - Error in loadManagedPackage', err, bundle.name, err.stack); var errMsg = 'Error requiring managedPackage during the ' + 'loadManagedPackage step.'; bundle.resultMessages.push({ 'step': 'loadManagedPackage', 'message': errMsg, 'isError': true, 'error': JSON.stringify(err) }); bundle.overallResult = false; bundle.isError = true; self.emit(EVENTS.FAILED_TO_LOAD_MANAGED_PACKAGE, bundle); } } else { console.error(' - Error requiring package, detected an error via isValid', bundle.name); var isValidMsg = 'Error requiring managedPackage during the ' + 'loadManagedPackage step. managedPackage is not valid'; bundle.resultMessages.push({ 'step': 'loadManagedPackage', 'message': isValidMsg, 'isError': true, 'error': 'bundle.managedPackage.isValid is not true' }); bundle.overallResult = false; bundle.isError = true; self.emit(EVENTS.FAILED_TO_LOAD_MANAGED_PACKAGE, bundle); } } else { console.error(' - Error requiring package, detected an error via isError', bundle.name); var isErrorMsg = 'Error requiring managedPackage during the ' + 'loadManagedPackage step. Previous error detected'; bundle.resultMessages.push({ 'step': 'loadManagedPackage', 'message': isErrorMsg, 'isError': true, 'error': 'bundle.isError is true, previous error detected' }); bundle.overallResult = false; bundle.isError = true; self.emit(EVENTS.FAILED_TO_LOAD_MANAGED_PACKAGE, bundle); } defered.resolve(bundle); return defered.promise; }; var getManageSinglePackage = function(bundle) { var manageSinglePackage = function(results) { var defered = q.defer(); var getOnErr = function(msg) { return function(err) { var innerDefered = q.defer(); console.error('manageSinglePackage err', msg, err, bundle); innerDefered.reject(err); return innerDefered.promise; }; }; // Event a message indicating that we are starting to manage a // package. self.emit(EVENTS.PACKAGE_MANAGEMENT_STARTED, bundle); checkForExistingPackage(bundle) .then(checkForUpgradeOptions, getOnErr('checkingForExistingPackage')) .then(chooseValidUpgrade, getOnErr('checkForUpgradeOptions')) .then(checkForExtractionErrors, getOnErr('chooseValidUpgrade')) .then(determineRequiredOperations, getOnErr('checkForExtractionErrors')) .then(resetPackageDirectory, getOnErr('determineRequiredOperations')) .then(performUpgrade, getOnErr('resetPackageDirectory')) .then(verifyPackageUpgrade, getOnErr('performUpgrade')) .then(loadManagedPackage, getOnErr('verifyPackageUpgrade')) .then(function(res) { results[res.name] = res; defered.resolve(results); }, defered.reject); return defered.promise; }; return manageSinglePackage; }; /** * runPackageManager instructs the package manager to start its async * loading operations to load packages. * arg: packageNamesToLoad is an optional array of packageNames to load. If * it isn't given the manager performs its operations on all managed * packages. */ this.runPackageManager = function(packageNamesToLoad) { var defered = q.defer(); var packageNames; if(packageNamesToLoad) { packageNames = packageNamesToLoad; } else { packageNames = self.getManagedPackages(); } var manageOps = []; packageNames.forEach(function(packageName) { // Only add packages that aren't in the global name space; var gnsObjs = Object.keys(global[gns]); if(gnsObjs.indexOf(packageName) < 0) { var bundle = { 'name': packageName, 'packageInfo': self.managedPackages[packageName], 'packageData': null, 'currentPackage': null, 'availableUpgrades': null, 'managedPackage': null, 'resetPackage': false, 'performUpgrade': false, 'chosenUpgrade': null, 'overallResult': false, 'resultMessages': [], 'isError': false, 'packageLoaded': false }; // Commented out async operation // manageOps.push(manageSinglePackage(bundle)); manageOps.push(getManageSinglePackage(bundle)); } }); // Wait for all of the operations to complete // q.allSettled(manageOps) // .then(function(res) { // var results = {}; // res.forEach(function(re) { // results[re.value.name] = re.value; // }); // defered.resolve(results); // }, function(err) { // console.log('Finished Managing err', err); // defered.reject(err); // }); // Execute each operation one at a time if(manageOps.length > 0) { var results = {}; manageOps.reduce(function(soFar, f) { return soFar.then(f); }, q(results)) .then(function(res) { // console.log('Finished Managing success!', res); defered.resolve(res); }, function(err) { console.log('Finished managing err', err); defered.reject(err); }); } else { defered.resolve({}); } return defered.promise; }; /** * Configures the extraction path of the package_loader. If this hasn't * been done before running the package manager it will fail as the default * path is invalid. */ this.setExtractionPath = function(path) { self.extractionPath = path; }; this.getExtractionPath = function() { return self.extractionPath; }; var self = this; } util.inherits(createPackageLoader, EventEmitter); var PACKAGE_LOADER = new createPackageLoader(); // Define the functions to be exported exports.on = function(eventName, callback) { PACKAGE_LOADER.on(eventName, callback); }; exports.loadPackage = PACKAGE_LOADER.loadPackage; exports.setExtractionPath = PACKAGE_LOADER.setExtractionPath; exports.getExtractionPath = PACKAGE_LOADER.getExtractionPath; exports.getManagedPackages = PACKAGE_LOADER.getManagedPackages; exports.getDependencyList = PACKAGE_LOADER.getDependencyList; exports.getDependencyData = PACKAGE_LOADER.getDependencyData; exports.deleteManagedPackage = PACKAGE_LOADER.deleteManagedPackage; exports.deleteAllManagedPackages = PACKAGE_LOADER.deleteAllManagedPackages; exports.runPackageManager = PACKAGE_LOADER.runPackageManager;