Skip to content

Commit

Permalink
[TIMOB-9028] Fixed bugs discovered while developing the Android build…
Browse files Browse the repository at this point in the history
…. Fixed validation of options with an array of values. Fixed typo in ignoreFiles. Removed try/catch from validate() call to make development easier and it should never throw an error anyways.
  • Loading branch information
cb1kenobi committed Sep 26, 2013
1 parent 72f7426 commit 38569a5
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 95 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ open-source and easy to use. [We've](https://github.com/appcelerator)
designed Titanium to be suitable for command line beginners, but still be
powerful and extensible enough for production usage.

[![NPM](https://nodei.co/npm/titanium.png?downloads=true&stars=true)](https://nodei.co/npm/titanium/)

## Prerequisites

The Titanium CLI requires [Node.js 0.8.x](http://nodejs.org/dist/). You can
Expand Down
228 changes: 143 additions & 85 deletions lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,7 @@ CLI.prototype.validate = function validate(next) {
conf = this.command.conf || {},
options = {},
missingOptions = {},
invalidOptions = {},
globalOptions = Object.keys(this.globalContext.options).concat(['username', 'password']),
args = conf.args,
// missing args is only used when prompting is disabled
Expand All @@ -486,18 +487,27 @@ CLI.prototype.validate = function validate(next) {

series(this, [
function (next) {
// mix the command and platform specific options together
[this.command, this.command.platform].forEach(function (ctx) {
ctx && ctx.conf && ctx.conf.options && mix(options, ctx.conf.options);
});

// check missing required options if prompting is disabled
// check missing required options and invalid options
// the missingOptions is used to determine if there is at least one missing
// required option and if we're doing a build using Titanium SDK < 3.2, we
// will use the missingOptions to prompt the old way. The new way recomputes
// missing options after each missing option is prompted for.
options && Object.keys(options).forEach(function (name) {
var opt = options[name],
obj = mix({ name: name }, opt),
p = globalOptions.indexOf(name);

if ((opt.required || (opt.conf && opt.conf.required)) && argv[name] === void 0) {
missingOptions[name] = obj;
if (argv[name] == undefined) {
if (opt.required || (opt.conf && opt.conf.required)) {
missingOptions[name] = obj;
}
} else if (Array.isArray(opt.values) && !opt.skipValueCheck && opt.values.indexOf(argv[name]) == -1) {
invalidOptions[name] = obj;
}

// if this command or platform option is the same name as a global option,
Expand All @@ -524,71 +534,122 @@ CLI.prototype.validate = function validate(next) {
}
});

// if prompting, prompt for options
if (this.config.cli.prompt && Object.keys(missingOptions).length) {
if (appc.version.gte(this.sdk.manifest.version, '3.2.0')) {
// if there aren't any options, continue
if (!options || !Object.keys(options).length) return next();

var done = false;
async.whilst(
function () { return !done; },
function (callback) {
// find the missing options
var missing = {};
Object.keys(options).forEach(function (name) {
if (globalOptions.indexOf(name) == -1) {
var opt = options[name],
obj = mix({ name: name }, opt);

if ((opt.required || (opt.conf && opt.conf.required)) && argv[name] === void 0) {
missing[name] = obj;
}
}
});

// sort and get the first missing option
var names = Object.keys(missing).sort(function (a, b) {
if (options[a].order && options[b].order) {
return options[a].order < options[b].order ? -1 : options[a].order > options[b].order ? 1 : 0;
} else if (options[a].order) {
return -1;
} else if (options[b].order) {
return 1;
}
return 0;
});
next();
},

console.log(names);
function (next) {
// if prompting, prompt for invalid options
if (!this.config.cli.prompt || !Object.keys(invalidOptions).length) {
return next();
}

name = names.shift();
// invalid options are always based on a list of valid values, so
// we can just use a select prompt to ask for valid values

var opts = {},
_t = this;

Object.keys(invalidOptions).forEach(function (name) {
var opt = invalidOptions[name],
field = opts[name] = fields.select({
title: __('Please select a valid %s value', name.cyan),
promptLabel: __('Select a value by number or name'),
margin: '',
numbered: true,
relistOnError: true,
complete: true,
suggest: true,
options: opt.values
});

if (name) return this.prompt(missing[name], callback);
field.on('pre-prompt', function () {
_t.logger.error(__('Invalid %s value "%s"', name, argv[name]) + '\n');
});
});

// didn't find any more missing options
done = true;
callback();
}.bind(this),
function () {
next();
}
);
} else {
// Titanium SDK < 3.2 handles options and their defaults the old way, so
// we have to treat the prompting the old way
this.prompt(Object.keys(missingOptions).map(function (name) {
var opt = missingOptions[name],
obj = mix({ name: name }, opt);
if (Array.isArray(opt.values) && !opt.skipValueCheck && opt.values.indexOf(argv[name]) == -1) {
obj.invalid = true;
}
return obj;
}), next);
fields.set(opts).prompt(function (err, data) {
if (err) {
if (err.message != 'cancelled') {
_t.logger.error(err);
}
_t.logger.log();
process.exit(1);
}
} else {
mix(argv, data);
next();
});
},

function (next) {
// if prompting, prompt for missing options
if (!this.config.cli.prompt || !Object.keys(missingOptions).length) {
return next();
}

if (appc.version.gte(this.sdk.manifest.version, '3.2.0')) {
// Titanium SDK >= 3.2 prompts using the "new" prompt library and
// we use a while loop to process each missing option. As soon as
// a missing option has been prompted for, we recompute the missing
// options just in case a missing option (i.e. --target) adds new
// dependencies.

// if there aren't any options, continue
if (!options || !Object.keys(options).length) return next();

var done = false;
async.whilst(
function () { return !done; },
function (callback) {
// find the missing options
var missing = {};
Object.keys(options).forEach(function (name) {
if (globalOptions.indexOf(name) == -1) {
var opt = options[name],
obj = mix({ name: name }, opt);

if ((opt.required || (opt.conf && opt.conf.required)) && argv[name] === void 0) {
missing[name] = obj;
}
}
});

// sort and get the first missing option
var names = Object.keys(missing).sort(function (a, b) {
if (options[a].order && options[b].order) {
return options[a].order < options[b].order ? -1 : options[a].order > options[b].order ? 1 : 0;
} else if (options[a].order) {
return -1;
} else if (options[b].order) {
return 1;
}
return 0;
});

name = names.shift();
if (name) return this.prompt(missing[name], callback);

// didn't find any more missing options
done = true;
callback();
}.bind(this),
function () {
next();
}
);
} else {
// Titanium SDK < 3.2 handles options and their defaults the old way, so
// we have to treat the prompting the old way
this.prompt(Object.keys(missingOptions).map(function (name) {
var opt = missingOptions[name],
obj = mix({ name: name }, opt);
if (Array.isArray(opt.values) && !opt.skipValueCheck && opt.values.indexOf(argv[name]) == -1) {
obj.invalid = true;
}
return obj;
}), next);
}
},

function (next) {
// if prompting, prompt for missing arguments
if (this.config.cli.prompt && missingArgs.length) {
Expand All @@ -597,36 +658,32 @@ CLI.prototype.validate = function validate(next) {
next();
}
},

function (next) {
// run the command's validation
try {
var validate = this.command.module.validate;
if (validate && typeof validate == 'function') {
// call validate()
var result = validate(this.logger, this.config, this),
done = 0;
if (result && typeof result == 'function') {
result(function (r) {
if (done++) return; // if callback is fired more than once, just ignore
if (r === false) {
// squelch the run() function
this.command.run = function () {};
}
this.emit('cli:post-validate', { cli: this, command: this.command }, next);
}.bind(this));
return;
} else if (result === false) {
// squelch the run() function
this.command.module.run = function () {};
}
var validate = this.command.module.validate;
if (validate && typeof validate == 'function') {
// call validate()
var result = validate(this.logger, this.config, this),
done = 0;
if (result && typeof result == 'function') {
result(function (r) {
if (done++) return; // if callback is fired more than once, just ignore
if (r === false) {
// squelch the run() function
this.command.module.run = function () {};
}
this.emit('cli:post-validate', { cli: this, command: this.command }, next);
}.bind(this));
return;
} else if (result === false) {
// squelch the run() function
this.command.module.run = function () {};
}
this.emit('cli:post-validate', { cli: this, command: this.command }, next);
} catch (ex) {
argv.exception = ex;
argv._.$command = 'help';
this.command = this.globalContext.commands.help.load(this.logger, this.config, this, next);
}
this.emit('cli:post-validate', { cli: this, command: this.command }, next);
},

function (next) {
// call option callbacks
[this.command, this.subcommand].forEach(function (ctx) {
Expand All @@ -652,6 +709,7 @@ CLI.prototype.validate = function validate(next) {
}, this);
next();
},

function (next) {
// if NOT prompting, display all missing options, invalid options, and missing arguments
if (!this.config.cli.prompt) {
Expand Down
2 changes: 1 addition & 1 deletion lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ var fs = require('fs'),
httpProxyServer: '',
width: 100,
ignoreDirs: '^(\\.svn|\\.git|\\.hg|\\.?[Cc][Vv][Ss]|\\.bzr|\\$RECYCLE\\.BIN)$',
ignoreFiles: '^(\\.gitignore|\\.npmignore|\\.cvsignore|\\.DS_store|\\._\\*|[Tt]humbs.db|\\.vspscc|\\.vssscc|\\.sublime-project|\\.sublime-workspace|\\.project|\\.tmproj)$'
ignoreFiles: '^(\\.gitignore|\\.npmignore|\\.cvsignore|\\.DS_Store|\\._\\*|[Tt]humbs.db|\\.vspscc|\\.vssscc|\\.sublime-project|\\.sublime-workspace|\\.project|\\.tmproj)$'
},

// additional search paths for commands and hooks
Expand Down
18 changes: 9 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,18 @@
"preferGlobal": true,
"dependencies": {
"async": "~0.2.9",
"colors": "~0.6.0",
"fields": "~0.1.7",
"colors": "~0.6.2",
"fields": "~0.1.8",
"humanize": "~0.0.7",
"jade": "~0.31.2",
"longjohn": "~0.2.0",
"moment": "~2.0.0",
"jade": "~0.35.0",
"longjohn": "~0.2.1",
"moment": "~2.2.1",
"node-appc": "git://github.com/appcelerator/node-appc.git",
"optimist": "~0.6.0",
"request": "~2.25.0",
"semver": "~2.0.8",
"sprintf": "~0.1.1",
"temp": "~0.5.0",
"request": "~2.27.0",
"semver": "~2.1.0",
"sprintf": "~0.1.2",
"temp": "~0.6.0",
"winston": "0.6.x",
"wrench": "~1.5.1"
},
Expand Down

0 comments on commit 38569a5

Please sign in to comment.