diff --git a/.gitignore b/.gitignore index 83d811f..28e01b2 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ build/Release # Dependency directory # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git +# & Others node_modules .idea -test +snippets diff --git a/LICENSE b/LICENSE index 801d2fe..6a8b56d 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ Apache License same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2015 S-Core Co, Ltd. + Copyright 2016 S-Core Co, Ltd. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 045c38c..85ee1e1 100644 --- a/README.md +++ b/README.md @@ -14,18 +14,18 @@ $ npm -g install ./webida-server-tools *webida-server-tools is not a public node package yet. Do not search this package in your npm repository.* + # webida-package-manager -WPM(Webida Package Manager) is a simple tool to install/remove webida packages. Run webida-package-manager -with -h option to see help, usages. +(under construction) -To install some pacakge from a url https://github.com/webida/webida-core-package -``` -$ export WEBIDA_CATALOG_DIR=(where.is.your.plugin-settings.json) -$ export WEBIDA_PACKAGE_DIR=(where.is.your.plugins) -$ webida-package-manager install https://github.com/webida/webida-core-package -``` +## common options + +most of tools supports following common options -Use options -c and -i not to set environment variables. +- -h (--help) : shows help message. +- -V (--version) : shows program version +- -g (--debug) : run in debug mode +- -D (--dry-run) : shows what will happens with given command diff --git a/lib/common/AbstractProgram.js b/lib/common/AbstractProgram.js new file mode 100644 index 0000000..0174ea9 --- /dev/null +++ b/lib/common/AbstractProgram.js @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2016 S-Core Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +var logger = require('./logger'); + +class AbstractProgram { + + constructor(commanderOptions) { + this._options = commanderOptions; + let globalOptions = commanderOptions.parent || commanderOptions; + this._options.configPath = globalOptions.configPath || process.env.WPM_CONFIG_PATH; + this._options.debug = globalOptions.debug || 'false'; + this._options.dryRun = globalOptions.dryRun || 'false'; + if (this._options.debug) { + logger.level = 'debug'; + Promise.config({ + warnings:true, + longStackTraces:true + }); + } + } + + onHelp() { + // do nothing by default. should be overrided if program wants to add help message + } + + _handleOptions() { + // do nothing by default + } + + // abstract. should implement by each cli programs action + main() { + let args = arguments; + this._handleOptions() + .then( () => this._main.apply(this, args) ) + .then( (exitCode) => process.exit(exitCode) ) + .catch(err => { + logger.error(this.constructor.name, err); + process.exit(-1); + }); + } + + + // return promise should resolve an exit code + // should handle all error. if something is thrown, then the program has some bugs. + _main() { + logger.error(this.constructor.name + "#_main() is not implemented"); + Promise.resolve(-1); + } +} + +module.exports = AbstractProgram; diff --git a/lib/common/GlobbingLoader.js b/lib/common/GlobbingLoader.js new file mode 100644 index 0000000..7e6cf97 --- /dev/null +++ b/lib/common/GlobbingLoader.js @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2016 S-Core Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +var fs = Promise.promisifyAll(require('fs-extra')); +var globAsync = Promise.promisify(require('glob')); +var _ = require('lodash'); + +var logger = require('../common/logger.js'); + +class GlobbingLoader { + + constructor(globPattern, globOptions, filter) { + this.globPattern = globPattern; + this.globOptions = globOptions; + } + + loadAll() { + return new Promise((resolve, reject) => { + let allPaths; + globAsync(this.globPattern, this.globOptions) + .then(paths => { + logger.debug('loader found files ', paths); + allPaths = paths; + return Promise.map(paths, this.loadData_.bind(this)); + }) + .then((objects) => { + // now we have to build a map from paths to objects + let result = this._buildResultMap(allPaths, objects); + _.forOwn(result, (data, path) => { + if(!this.filterResult_(path, data)) { + delete result[path]; + logger.info('loader filtered out %s', path, null); + } else { + logger.debug('loader added path %s ', path, null); + } + }); + logger.debug('loaded files = %j', Object.keys(result), null); + resolve(result); + }) + .catch(err => { + logger.error('loader met error ', err); + reject(err); + }) + }); + } + + _buildResultMap(paths, objects) { + let ret = {}; + for (let i=0; i < paths.length; i++) { + const path = paths[i]; + const data = objects[i]; + ret[path] = data; + } + return ret; + } + + // should return boolean + filterResult_(relativePath, data) { + return true; + } + + // should return a promise or data + loadData_(relativePath) { + return fs.readJsonAsync(relativePath); + } +} + +module.exports = GlobbingLoader; + diff --git a/lib/common/ShellCommandRunner.js b/lib/common/ShellCommandRunner.js new file mode 100644 index 0000000..0293d6c --- /dev/null +++ b/lib/common/ShellCommandRunner.js @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2016 S-Core Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; +var child_process = require('child_process'); +var util = require('util'); +var logger = require('./logger'); + +class ShellCommandResult { + constructor() { + this.code = 0; + this.signal = 0; + this.stdout = 'flowed'; + this.stderr = 'flowed'; + this.error = undefined; + } +} + +class ShellCommandRunner { + + // options are same to child_process.exec arguments + // see https://nodejs.org/api/child_process.html + // additional options are + constructor(command, args, options) { + this.command = command || null; + this.args = args || []; + this.options = options || {}; + this.shellCommand = this._makeShellCommand(); + this.pid = 0; + } + + spawn(resolveAlways) { + let result = new ShellCommandResult(); + return new Promise( (resolve, reject) => { + this.options.stdio = this.options.stdio || 'inherit'; + let spawned = child_process.spawn(this.command, this.args, this.options); + this.pid = spawned.pid; + logger.debug("spawnned shell command %s (%d) with args %j, options %j", + this.command, this.pid, this.args, this.options, {}); + spawned.on('error', (err) => { + logger.error('spawned child %d got error', spawned.pid, err ); + result.error = err; + }); + spawned.on('close', (code, signal) => { + logger.debug('spawned child %d completed with exit code %d, signal %d', spawned.pid, code, signal,{} ); + this._logFinish(code); + result.code = code; + result.signal = signal; + if(!resolveAlways && (code || result.error)) { + reject(result); + } else { + resolve(result); + } + }); + this._logStart(); + }); + } + + exec(resolveAlways) { + let result = new ShellCommandResult(); + return new Promise( (resolve, reject) => { + let executed = child_process.exec(this.shellCommand, this.options, (err, stdout, stderr) => { + this._logFinish(err? err.code : 0); + result.stdout = stdout; + result.stderr = stderr; + if (err) { + result.error = err; + result.code = err.code; + result.signal = err.signal; + if (resolveAlways) { + logger.debug("ignoring error %j", err); + resolve(err); + } else { + reject(result); + } + } else { + resolve (result); + } + }); + this.pid = executed.pid; + logger.debug('executed shell command [%s] with options %j', this.shellCommand, this.options); + this._logStart(); + + }); + } + + _logStart() { + logger.info("start running command %s (pid %d)", this.shellCommand, this.pid, null); + }; + + _logFinish(exitCode) { + logger.info("finish running command %s (pid %d, exit code %d)", this.command, this.pid, exitCode, null); + } + + _makeShellCommand() { + let argv = [this.command]; + for(let arg of this.args) { + if (arg.indexOf(' ') >=0) { + + arg = '"' + arg + '"'; + } + argv.push(arg); + } + return argv.join(' '); + } +} + +module.exports = ShellCommandRunner; \ No newline at end of file diff --git a/lib/common/logger.js b/lib/common/logger.js new file mode 100644 index 0000000..103979c --- /dev/null +++ b/lib/common/logger.js @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2016 S-Core Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +var winston = require('winston'); + +let logger = new winston.Logger({ + transports : [ + new winston.transports.Console() + ] +}); + +logger.cli(); +logger.level = 'info'; + +module.exports = logger; diff --git a/lib/wpm/ManifestLoader.js b/lib/wpm/ManifestLoader.js new file mode 100644 index 0000000..0dcd4a1 --- /dev/null +++ b/lib/wpm/ManifestLoader.js @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2016 S-Core Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +var path = require('path'); + +var fs = Promise.promisifyAll(require('fs-extra')); +var _ = require('lodash'); + +var logger = require('../common/logger'); +var GlobbingLoader = require('../common/GlobbingLoader'); + +class ManifestLoader extends GlobbingLoader { + + constructor(basePath) { + let globPattern = ManifestLoader.MANIFEST_FILE_NAME; + let globOptions = { + matchBase:true, + cwd: basePath, + root: basePath, + ignore : ['**/node_modules/**', '**/bower_components/**'], + nodir : true, + nosort : true, + nonull : false + }; + super(globPattern, globOptions); + this.basePath = basePath; + } + + loadData_(relativePath) { + let filePath = path.resolve(this.basePath, relativePath); + logger.debug('loading ' + filePath); + return fs.readJsonAsync(filePath); + } + + static get MANIFEST_FILE_NAME() { + return 'package.json'; + } +} + +module.exports = ManifestLoader; \ No newline at end of file diff --git a/lib/PluginCatalog.js b/lib/wpm/PluginCatalog.js similarity index 98% rename from lib/PluginCatalog.js rename to lib/wpm/PluginCatalog.js index 2fc4676..f05512b 100644 --- a/lib/PluginCatalog.js +++ b/lib/wpm/PluginCatalog.js @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 S-Core Co., Ltd. + * Copyright (c) 2016 S-Core Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/wpm/TestProgram.js b/lib/wpm/TestProgram.js new file mode 100644 index 0000000..5ff7eb0 --- /dev/null +++ b/lib/wpm/TestProgram.js @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2016 S-Core Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +var logger = require('../common/logger'); +var AbstractProgram = require('../common/AbstractProgram.js'); + +class TestProgram extends AbstractProgram { + + constructor(commanderOptions) { + super(commanderOptions); + } + + onHelp() { + console.log(' test program aux help - need some helps?' ); + } + + _handleOptions() { + return new Promise( (resolve, reject) => { + if (!this._options.testOption2) { + reject(new Error('missing mandatory option --test-option2')); + } else { + resolve(); + } + }); + } + // return promise should resolve an exit code + // should handle all error. if something is thrown, then the program has some bugs. + _main(m1, m2, others) { + return new Promise((resolve, reject) => { + console.log('test : m1 == ', m1); + console.log('test : m2 == ', m2); + console.log('test : others == ', others); + console.log('test : option test-option1 ==', this._options.testOption1); + console.log('test : option test-option2 ==', this._options.testOption2); + //console.log('test options ==', this._options); + throw new Error('intended error thrown'); + }); + } +} + +module.exports = TestProgram; diff --git a/package.json b/package.json index 946f135..b68b7d0 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,22 @@ { "private": true, "name": "webida-server-tools", - "version": "0.3.0", + "version": "0.5.0", "bin": { - "webida-package-manager": "./webida-package-manager.js" + "wpm": "./webida-package-manager.js" }, + "preferGlobal": true, "description": "Various CLI tools for Webida server", "license": "Apache-2.0", + "engines": { + "node": ">=4.2.0" + }, "dependencies": { - "cli": "^0.11.0", - "fs-extra": "^0.24.0", - "promise": "^7.0.4" + "bluebird": "^3.1.1", + "commander": "^2.9.0", + "fs-extra": "^0.26.4", + "glob": "^6.0.4", + "jsonpath": "^0.2.2", + "winston": "^2.1.1" } } diff --git a/webida-package-manager.js b/webida-package-manager.js index 69fccbd..f33cff9 100644 --- a/webida-package-manager.js +++ b/webida-package-manager.js @@ -1,7 +1,7 @@ -#!/usr/bin/env node +#!/bin/env node /* - * Copyright (c) 2015 S-Core Co., Ltd. + * Copyright (c) 2016 S-Core Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,221 +16,92 @@ * limitations under the License. */ -var child_process = require('child_process'), - fs = require('fs'), - path = require('path'), - util = require('util'); +'use strict'; -var cli = require('cli'), - fsExtra = require('fs-extra'), - Promise = require('promise'); +var util = require('util'); +global.Promise = require('bluebird'); -var PluginCatalog = require('./lib/PluginCatalog.js'); +var commander = require('commander'); +var logger = require('./lib/common/logger'); -var PROGRAM_NAME = 'webida-packabge-manager'; -var PROGRAM_VERSION = '0.3.0'; -var CATALOG_FILE = 'plugin-settings.json'; -var PACKAGE_DESCRIPTOR_FILE = "webida-package.json"; -var WEBIDA_CLIENT_CONF_DIR = "WEBIDA_CLIENT_CONF_DIR"; -var WEBIDA_CLIENT_PKGS_DIR = "WEBIDA_CLIENT_PKGS_DIR"; +commander + .version('0.5.0') + .option('-g. --debug', 'enable debug mode') + .option('-D, --dry-run', 'without affecting to current installation, see what happens') + .option('-c, --config-path ', + 'configuration file path that contains install path & other options (defaults to $WPM_CONFIG_PATH'); -cli.setApp(PROGRAM_NAME, PROGRAM_VERSION); -cli.enable('help', 'version', 'status'); -cli.option_width = 26; -cli.width = 100; - -cli.parse( { - 'catalog-dir' : ['c', 'configuration directory path where catalog file exists ($WEBIDA_CLIENT_CONF_DIR)'], - 'install-dir' : ['i', 'package install directory ($WEBIDA_CLIENT_PKGS_DIR)'], - 'tmp-dir' : ['t', 'temporary directory to save git repository before install', 'string', '/tmp'], - 'branch' : ['b', 'use specific branch of source repository to install', 'string', 'master'] -}, ['install', 'update', 'remove']); - -cli.main(function(args, options) { - this.debug("cli starting options \n" + util.inspect(options)); - this.debug("cli command " + cli.command); - this.debug("cli starting args " + util.inspect(args)); - - options.catalogDir = options['catalog-dir'] || process.env[WEBIDA_CLIENT_CONF_DIR]; - options.installDir = options['install-dir'] || process.env[WEBIDA_CLIENT_PKGS_DIR]; - options.tmpDir = options['tmp-dir']; - - if (!options.catalogDir) { - this.fatal("need a catalog dir option or WEBIDA_CLIENT_CONF_DIR in env variable"); - } - if (!options.installDir) { - this.fatal("need a install dir option or WEBIDA_CLIENT_PKGS_DIR in env variable"); - } - - options.catalogPath = options.catalogDir + path.sep + CATALOG_FILE; - - switch(cli.command) { - case 'install': - doInstall(cli, options, args); - break; - case 'remove': - doRemove(cli, options, args); - break; - default: - cli.error("Sorry, not implemented yet."); - break; - } -}); - -function handleInstallError(err, tmpDir) { - if (tmpDir) { - fsExtra.removeSync(tmpDir, function(rmErr) { - if (rmErr) { - cli.error('Garbage collection failed. Remove manually ' + tmpDir); - } else { - cli.info("removed dir " + tmpDir); - } - cli.fatal(err.stack ? err.stack : err); - }); - } else { - cli.fatal(err.stack ? err.stack : err); - } -} - -function runCommand(cli, command, params, ignoreFail) { - cli.info(util.format("running command %s %s", command, util.inspect(params))); - var proc = child_process.spawn(command, params, {stdio: 'inherit'}); - return new Promise(function (resolve, reject) { - proc.on('exit', function (code, signal) { - cli.debug(command + ' process exit code ' + code + " signal " + signal); - if (ignoreFail && code != 0) { - var msg = util.format("%s failed, exit code %s ", command, signal); - if (signal) - msg += " signal " + signal; - return reject( new Error(msg) ); - } else { - return resolve(); - } - }); +commander.command('test [optionalArgs...]') + .option('-1, --test-option1', 'test option 1') + .option('-2, --test-option2 ', 'test option 2 with value') + .action( (mandatoryArg1, mandatoryArg2, optionalArgs, options) => { + let TestProgram = require('./lib/wpm/TestProgram'); + let program = new TestProgram(options); + program.main(mandatoryArg1, mandatoryArg2, optionalArgs ); }); -} - -function installPackage(descriptor, options, packageDir, fromLocal) { - cli.debug("got descriptor" + util.inspect(descriptor)); - var installTo = options.installDir + path.sep + descriptor.id; - if (fs.existsSync(installTo)) { - throw new Error("already have existing package at " + installTo); - } - // TODO : add schema check of webida-package.json - var manifest = descriptor.manifest; - var command = util.format("%s %s %s", - (fromLocal ? "cp -Rf" : "mv"), - packageDir, installTo); - return new Promise(function (resolve, reject) { - cli.debug("installation command " + command); - cli.exec(command, function () { - return resolve(manifest); - }, function (err) { - return reject(err); +//commander +// .command('install [configurations...]') +// .on('--help', () => { +// global.program.onHelp(); +// console.log(' Arguments: '); +// console.log(''); +// console.log(' source : '); +// console.log(' Value of source should be a valid directory path or a property of $.sources in config. ' ); +// console.log(' See sample/wpm-configuration.js to see how to set source id that maps to path or some url'); +// console.log(''); +// console.log(' configurations : = = ...'); +// console.log(' Each key is json path of configuration object property'); +// console.log(' Each value will be coerced to boolean from "true" or "false" only'); +// console.log(' See samples/package-setup.json for available keys'); +// }) +// .action( function (source, configurations, options) { +// console.log("source %s", source); +// console.log("configurations", configurations); +// console.log("program opt parent g", options.parent.debug); +// console.log("program opt parent D", options.parent.dryRun); +// console.log("program opt parent c ", options.parent.configPath); +// process.exit(0); +// }); + + +//commander.command('test') +// .action( (options) => { +// registerOptions(options); +// let runner = new CommandRunner('git', ["branch", "-avv"]); +// runner.spawn().then( (result) => { +// logger.debug("git execution result [%j]", result, {}); // need 'empty meta data' to show result +// process.exit(0); +// }).catch( (err) => { +// logger.error("git command result error %j", err, {}); +// process.exit(-2); +// }); +// }); + +//commander.command('rescan') +// .action( (options) => { +// let Rescanner = require('./lib/wpm/RescannerProgram'); +// global.program = new Rescanner(options); +// global.program.main(); +// }); + + /* + var ManifestLoader = require('./lib/wpm/ManifestLoader'); + registerOptions(options); + let loader = new ManifestLoader('../webida-server-ng-prototype'); + + loader.loadAll().then( (result) => { + logger.debug("glob result [%j]", result, {}); // need 'empty meta data' to show result + process.exit(0); + }).catch( (err) => { + logger.error("glob error %j", err, {}); + process.exit(-2); }); - }) -} + */ -function cleanUpOldTmp(cli, tmpDir) { - return new Promise(function (resolve, reject) { - if (fs.existsSync(tmpDir) ) { - cli.exec("rm -fr " + tmpDir, function () { - cli.info("removed un-clenaed tmp dir " + tmpDir ); - resolve(); - }, function (err) { - cli.fatal('could not remove old tmp dir ' + tmpDir); - }); - } else { - cli.info("no old tmp " + tmpDir); - return resolve(); - } - }); -} - -function doInstall(cli, options, args) { - if (args.length < 1) { - cli.fatal("need url argument. run with -h to see usage "); - } - - var catalog = new PluginCatalog(); - var tmpGitRepoDir = path.resolve(options.tmpDir + '/cloning'); - var descriptor; - var cwd = process.cwd(); - var fromLocal = false; - - cleanUpOldTmp(cli, tmpGitRepoDir).then(function () { - return catalog.load(cli, options.catalogPath); - }).then(function () { - if (fs.existsSync(args[0]) ) { - fromLocal = true; - tmpGitRepoDir = args[0]; - } else { - return runCommand(cli, 'git', ['clone','--verbose', args[0], tmpGitRepoDir ] ); - } - }).then(function () { - process.chdir(tmpGitRepoDir); - if (!fromLocal) { - var params = ['checkout', options.branch]; - return runCommand(cli, 'git', params); - } else { - return Promise.resolve(); - } - }).then(function () { - cli.debug("current working directory is " + process.cwd()); - if (!fs.existsSync(PACKAGE_DESCRIPTOR_FILE)) { - throw new Error("invalid package repository, no webida-packge.json found"); - } - // read descriptor from cloned repository - return fsExtra.readJsonSync(PACKAGE_DESCRIPTOR_FILE); - }).then(function (descriptorObj) { - descriptor = descriptorObj; - process.chdir(cwd); - return installPackage(descriptor, options, tmpGitRepoDir, fromLocal); - }).then(function (manifest) { - catalog.addFromManifest('plugins', manifest.plugins, descriptor.id); - catalog.addFromManifest('starters', manifest.starters, descriptor.id); - catalog.addFromManifest('disabled', manifest.disabled, descriptor.id); - catalog.save(cli, options.catalogPath); - }).then(function () { - cli.info("installed new package " + descriptor.id); - }).catch(function (err) { - var gc = fromLocal ? undefined : tmpGitRepoDir; - handleInstallError(err, gc); - }); -} +commander.parse(process.argv); -function doRemove(cli, options, args) { - - if (args.length < 1) { - cli.fatal("need package-name argument. run with -h to see usage "); - } - - var catalog = new PluginCatalog(); - var packageDir = path.resolve(options.installDir + path.sep + args[0]); - - catalog.load(cli, options.catalogPath).then(function () { - cli.debug("catalog = " + util.inspect(catalog)); - var descriptorPath = packageDir + path.sep + PACKAGE_DESCRIPTOR_FILE; - if (!fs.existsSync(descriptorPath)) { - throw new Error("cannot find package for " + descriptorPath); - } - return fsExtra.readJsonSync(descriptorPath); - }).then(function (descriptor) { - var manifest = descriptor.manifest; - for (var pidx in manifest.plugins) { - var plugin = manifest.plugins[pidx]; - catalog.remove(plugin); - cli.debug(plugin + " is removed from catalog "); - } - cli.info("update catalog file to " + options.catalogPath); - return catalog.save(cli, options.catalogPath); - }).then(function () { - cli.info("removing package directory " + packageDir); - cli.exec("rm -fr " + packageDir, function() { - cli.info("uninstall complete"); - }); - }).catch(function (err) { - cli.fatal(err.stack ? err.stack : err); - }); -} +if (commander.args.length < 1) { + commander.outputHelp(); + process.exit(-1); +} \ No newline at end of file