@@ -350,7 +350,6 @@ Fiber(function () {
350350 var command = null ;
351351 var isRawCommand = false ;
352352 var showHelp = false ;
353- var optimistOptions = { } ;
354353
355354 // Check to see if it is a "raw" command that handles its own
356355 // argument parsing.
@@ -371,21 +370,42 @@ Fiber(function () {
371370 // 'flag', or in the command 'thing' with an option 'flag' that is
372371 // set to 'stuff'? To resolve this we require that 'flag' be
373372 // consistently declared as a boolean (or not a boolean) across all
374- // commands. We could just require the user to put the command
375- // before any flag, but this being Meteor, we go to lengths to be
376- // both correct and accommodating.
373+ // commands.
377374 //
378- // (We used to do this in two passes, where the first pass just
379- // pulled out the command and the release, and the second pass
380- // parsed the arguments with knowledge of the command, but now that
381- // we're determining upfront which options are boolean there's no
382- // real benefit to two passes.)
383- var opt = require ( 'optimist' ) ( process . argv . slice ( 2 ) ) ;
375+ // XXX The problem with the above is that which commands are boolean
376+ // may change across releases, and when we springboard, we actually
377+ // have to parse the options with the *target* version's
378+ // semantics. All in all, I think we might be better served to
379+ // require options to come after the command, other than special
380+ // options (--release, --help, and options that act as
381+ // commands). Then we don't have to require consistency of boolean
382+ // status between commands; we instead have to require consistency
383+ // of boolean status of a particular option, for a command, across
384+ // releases. Since we always start out by running the latest version
385+ // of Meteor, which can have knowledge of all past versions
386+ // (including the boolean status of formerly present but removed
387+ // options, including options to removed commands), this should let
388+ // us be 100% correct. (Of course, we could still do this if we
389+ // required options to be consistent across commands as well, but I
390+ // think this is a better tradeoff.) In this model, we'd do option
391+ // parsing in two passes, where the first pass just pulls out the
392+ // command, and the second parses the arguments with knowledge of
393+ // the command. I would make this change right now but we're on a
394+ // tight timetable for 1.0 and there is no advantage to doing it now
395+ // rather than later. #ImprovingCrossVersionOptionParsing
396+ //
397+ // If --foo is an argument that takes a String, and we've told
398+ // optimist to treat it as a string and not try to parse it as an
399+ // integer (which would irretrievably lose leading zeros), then
400+ // optimist provides no way to distinguish between 'meteor --foo'
401+ // (--foo with no value supplied) and 'meteor'. So, append a random
402+ // sentinel value to the end of the arguments to distinguish the two
403+ // cases.
404+ var sentinel = '$' + Math . random ( ) ; // '$' suppresses number conversion
405+ var opt = require ( 'optimist' ) ( process . argv . slice ( 2 ) . concat ( [ sentinel ] ) ) ;
384406 opt . alias ( "h" , "help" )
385407 . boolean ( "h" )
386408 . boolean ( "help" ) ;
387- optimistOptions [ 'help' ] = true ;
388- optimistOptions [ 'h' ] = true ;
389409
390410 var isBoolean = { help : true , h : true } ;
391411 var walkCommands = function ( node ) {
@@ -412,8 +432,6 @@ Fiber(function () {
412432 // manually parse it into the correct type later
413433 opt . string ( name ) ;
414434
415- optimistOptions [ name ] = true ;
416-
417435 // side note: it's a little unfortunate that optimist
418436 // puts all options in the same namespace and can't
419437 // distinguish between '-a foo' and '--a foo'. one day
@@ -438,7 +456,6 @@ Fiber(function () {
438456 if ( _ . has ( isBoolean , key ) )
439457 throw new Error ( "--" + key + " is both an option and a command?" )
440458 opt . boolean ( key ) ;
441- optimistOptions [ key ] = true ;
442459 } ) ;
443460
444461 // The following line actually parses the arguments (argv is a
@@ -449,18 +466,23 @@ Fiber(function () {
449466 // even if it didn't appear on the command line. Delete the
450467 // options that didn't actually appear, which will have the value
451468 // 'false'.
452- //
453- // Only do this for options that we actually told optimist about,
454- // so that if the user specifies '--unknownoption' at the end of a
455- // command, we'll still print an error.
456- //
457- // XXX this strategy still doesn't help us catch a user "meteor
458- // run --foo" if foo is an option that takes a string. rethink
459- if ( parsed [ key ] === false && _ . has ( optimistOptions , key ) )
469+ if ( parsed [ key ] === false )
460470 delete parsed [ key ] ;
461471 } ) ;
462472 delete parsed [ '$0' ] ;
463473
474+ // Pull off the sentinel and make sure there were no options that
475+ // expected a value but didn't get one.
476+ var missingSentinel = false ;
477+ if ( parsed . _ . length && parsed . _ [ parsed . _ . length - 1 ] === sentinel ) {
478+ // All good
479+ parsed . _ . pop ( ) ;
480+ } else {
481+ // Enable a sanity check further down that makes sure we actually
482+ // found the option that wasn't given a value.
483+ missingSentinel = true ;
484+ }
485+
464486 // Figure out if we're running in a directory that is part of a
465487 // Meteor application. Determine any additional directories to
466488 // search for packages.
@@ -489,7 +511,8 @@ Fiber(function () {
489511 // decide whether to exec the other version of meteor that would
490512 // interpret the flags. That's not ideal, but it should do fine in
491513 // practice, and it's better than assuming that all options are or
492- // aren't boolean when interpreting --release.
514+ // aren't boolean when interpreting --release. See
515+ // #ImprovingCrossVersionOptionParsing.
493516
494517 var releaseOverride = null ;
495518 if ( _ . has ( parsed , 'release' ) ) {
@@ -697,8 +720,8 @@ commandName + ": can't pass both -" + optionInfo.short + " and --" +
697720"Try 'meteor help " + commandName + "' for help.\n" ) ;
698721 process . exit ( 1 ) ;
699722 }
700- var actualOptionName = presentShort ? "- " + optionInfo . short :
701- "-- " + optionName ;
723+ var helpfulOptionName = "-- " + optionName +
724+ ( presentShort ? " (- " + optionInfo . short + ")" : "" ) ;
702725
703726 // If you pass an option twice, optimist gives us an
704727 // array. OK. Concatenate all of the values we've got into one big
@@ -716,19 +739,24 @@ commandName + ": can't pass both -" + optionInfo.short + " and --" +
716739 // in the future, we could support multiple values, but we don't
717740 // for now since no command needs it
718741 process . stderr . write (
719- commandName + ": can only take one " + actualOptionName + " option.\n" +
742+ commandName + ": can only take one " + helpfulOptionName + " option.\n" +
720743"Try 'meteor help " + commandName + "' for help.\n" ) ;
721744 process . exit ( 1 ) ;
722745 } else if ( values . length === 1 ) {
723746 // OK, they provided exactly one value. Check its type and add
724747 // to the output.
725748 var value = values [ 0 ] ;
726- if ( optionInfo . type === Number ) {
749+ if ( value === sentinel ) {
750+ // This option requires a value and they didn't give it one
751+ // (it was the last word on the command line).
752+ process . stderr . write (
753+ commandName + ": the " + helpfulOptionName + " option needs a value.\n" +
754+ "Try 'meteor help " + commandName + "' for help.\n" ) ;
755+ process . exit ( 1 ) ;
756+ } else if ( optionInfo . type === Number ) {
727757 if ( ! value . match ( / ^ [ 1 - 9 ] [ 0 - 9 ] * $ / ) ) {
728758 process . stderr . write (
729- "--" + optionName + " " +
730- ( presentShort ? "(-" + optionInfo . short + ") " : "" ) +
731- "must be a number.\n" +
759+ commandName + ": " + helpfulOptionName + " must be a number.\n" +
732760"Try 'meteor help " + commandName + "' for help.\n" ) ;
733761 process . exit ( 1 ) ;
734762 }
@@ -778,6 +806,10 @@ longHelp(commandName) + "\n");
778806 process . exit ( 1 ) ;
779807 }
780808
809+ if ( missingSentinel )
810+ // sanity check
811+ throw new Error ( "couldn't find the option that didn't have a value?" ) ;
812+
781813 // Check argument count.
782814 if ( options . args . length < command . minArgs ) {
783815 process . stderr . write (
0 commit comments