diff --git a/meteor b/meteor index fb7b13b350..6e5eb77379 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/bin/bash -BUNDLE_VERSION=0.3.72 # 0.3.63 on the Windows branch +BUNDLE_VERSION=0.3.74 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. diff --git a/scripts/dev-bundle-tool-package.js b/scripts/dev-bundle-tool-package.js index c3bcdb53fb..109d0a1c76 100644 --- a/scripts/dev-bundle-tool-package.js +++ b/scripts/dev-bundle-tool-package.js @@ -33,6 +33,7 @@ var packageJson = { netroute: "0.2.5", phantomjs: "1.9.12", "http-proxy": "1.6.0", + "wordwrap": "0.0.2", // XXX We ought to be able to get this from the copy in js-analyze rather // than in the dev bundle.) esprima: "1.2.2", diff --git a/scripts/generate-dev-bundle.sh b/scripts/generate-dev-bundle.sh index 0ddadb27a0..f67f143106 100755 --- a/scripts/generate-dev-bundle.sh +++ b/scripts/generate-dev-bundle.sh @@ -117,7 +117,6 @@ npm install npm dedupe cp -R node_modules/* "${DIR}/lib/node_modules/" - cd "${DIR}/lib" # TODO Move this into dev-bundle-tool-package.js when it can be safely @@ -142,6 +141,7 @@ delete browserstack-webdriver/docs delete browserstack-webdriver/lib/test delete sqlite3/deps +delete wordwrap/test # dedupe isn't good enough to eliminate 3 copies of esprima, sigh. find . -path '*/esprima/test' | xargs rm -rf diff --git a/tools/auth-client.js b/tools/auth-client.js index bc9e9ac664..60d775a164 100644 --- a/tools/auth-client.js +++ b/tools/auth-client.js @@ -58,9 +58,10 @@ exports.loggedInConnection = function (url, domain, sessionType) { if (! auth.isLoggedIn()) { // XXX we should have a better account signup page. - Console.stderr.write( -"Please log in with your Meteor developer account. If you don't have one,\n" + -"you can quickly create one at www.meteor.com.\n"); + Console.error( + "Please log in with your Meteor developer account.", + "If you don't have one,", + "you can quickly create one at www.meteor.com."); auth.doUsernamePasswordLogin({ retry: true }); } @@ -78,10 +79,10 @@ exports.loggedInConnection = function (url, domain, sessionType) { if (err.message === "access-denied") { // Maybe we thought we were logged in, but our token had been // revoked. - Console.stderr.write( -"It looks like you have been logged out! Please log in with your Meteor\n" + -"developer account. If you don't have one, you can quickly create one\n" + -"at www.meteor.com.\n"); + Console.error( + "It looks like you have been logged out!", + "Please log in with your Meteor developer account. If you don't have", + "one, you can quickly create one at www.meteor.com."); auth.doUsernamePasswordLogin({ retry: true }); auth.loginWithTokenOrOAuth( conn, diff --git a/tools/auth.js b/tools/auth.js index 43b1b7a456..62c8061817 100644 --- a/tools/auth.js +++ b/tools/auth.js @@ -336,11 +336,10 @@ var tryRevokeOldTokens = function (options) { var logoutFailWarning = function (domain) { if (! warned) { // This isn't ideal but is probably better that saying nothing at all - process.stderr.write("warning: " + - (options.firstTry ? - "couldn't" : "still trying to") + - " confirm logout with " + domain + - "\n"); + Console.error("warning: " + + (options.firstTry ? + "couldn't" : "still trying to") + + " confirm logout with " + domain); warned = true; } }; @@ -603,7 +602,7 @@ var doInteractivePasswordLogin = function (options) { var loginFailed = function () { if (! options.suppressErrorMessage) { - process.stderr.write("Login failed.\n"); + Console.error("Login failed."); } }; @@ -634,7 +633,7 @@ var doInteractivePasswordLogin = function (options) { } else { loginFailed(); if (options.retry) { - process.stderr.write("\n"); + Console.error(); continue; } else { maybeCloseConnection(); @@ -715,17 +714,18 @@ exports.loginCommand = withAccountsConnection(function (options, var galaxyLoginResult = logInToGalaxy(galaxy); if (galaxyLoginResult.error) { // XXX add human readable error messages - process.stderr.write('\nLogin to ' + galaxy + ' failed. '); - + var failedLoginMsg = "\nLogin to ' + galaxy + ' failed. "; if (galaxyLoginResult.error === 'unauthorized') { - process.stderr.write('You are not authorized for this galaxy.\n'); + Console.error( + failedLoginMsg + 'You are not authorized for this galaxy.'); } else if (galaxyLoginResult.error === 'no_oauth_server') { - process.stderr.write('The galaxy could not ' + - 'contact Meteor Accounts.\n'); + Console.error( + failedLoginMsg + 'The galaxy could not contact Meteor Accounts.'); } else if (galaxyLoginResult.error === 'no_identity') { - process.stderr.write('Your login information could not be found.\n'); + Console.error( + failedLoginMsg + 'Your login information could not be found.'); } else { - process.stderr.write('Error: ' + galaxyLoginResult.error + '\n'); + Console.error(failedLoginMsg + 'Error: ' + galaxyLoginResult.error ); } return 1; @@ -741,10 +741,11 @@ exports.loginCommand = withAccountsConnection(function (options, tryRevokeOldTokens({ firstTry: true, connection: connection }); data = readSessionData(); - process.stderr.write("\nLogged in" + (galaxy ? " to " + galaxy : "") + - (currentUsername(data) ? - " as " + currentUsername(data) : "") + ".\n" + - "Thanks for being a Meteor developer!\n"); + Console.error(); + Console.error("Logged in" + (galaxy ? " to " + galaxy : "") + + (currentUsername(data) ? + " as " + currentUsername(data) : "") + "." + + "Thanks for being a Meteor developer!"); return 0; }); @@ -759,11 +760,11 @@ exports.logoutCommand = function (options) { tryRevokeOldTokens({ firstTry: true }); if (wasLoggedIn) - process.stderr.write("Logged out.\n"); + Console.error("Logged out."); else // We called logOutAllSessions/writeSessionData anyway, out of an // abundance of caution. - process.stderr.write("Not logged in.\n"); + Console.error("Not logged in."); }; // If this is fully set up account (with a username and password), or @@ -845,25 +846,25 @@ exports.whoAmICommand = function (options) { var data = readSessionData(); if (! loggedIn(data)) { - process.stderr.write("Not logged in. 'meteor login' to log in.\n"); + Console.error( + "Not logged in. " + Console.command("'meteor login'") + " to log in."); return 1; } var username = currentUsername(data); if (username) { - process.stdout.write(username + "\n"); + Console.rawInfo(username + "\n"); return 0; } var url = getSession(data, config.getAccountsDomain()).registrationUrl; if (url) { - process.stderr.write( -"You haven't chosen your username yet. To pick it, go here:\n" + -"\n" + -url + "\n"); + Console.error("You haven't chosen your username yet. To pick it, go here:"); + Console.error(); + Console.error(Console.url(url)); } else { // Won't happen in normal operation - process.stderr.write("You haven't chosen your username yet.\n"); + Console.error("You haven't chosen your username yet."); } return 1; @@ -893,11 +894,13 @@ exports.registerOrLogIn = withAccountsConnection(function (connection) { break; } catch (err) { if (err.error === 400 && ! utils.validEmail(email)) { - if (email.trim().length) - process.stderr.write("Please double-check that address.\n\n"); + if (email.trim().length) { + Console.error("Please double-check that address."); + Console.error(); + } } else { - process.stderr.write("\nCouldn't connect to server. " + - "Check your internet connection.\n"); + Console.error("\nCouldn't connect to server. " + + "Check your internet connection."); return false; } } @@ -917,10 +920,11 @@ exports.registerOrLogIn = withAccountsConnection(function (connection) { writeSessionData(data); return true; } else if (result.alreadyExisted && result.sentRegistrationEmail) { - process.stderr.write( -"\n" + -"You need to pick a password for your account so that you can log in.\n" + -"An email has been sent to you with the link.\n\n"); + Console.error(); + Console.error( + "You need to pick a password for your account so that you can log in.", + "An email has been sent to you with the link."); + Console.error(); var animationFrame = 0; var lastLinePrinted = ""; @@ -928,12 +932,12 @@ exports.registerOrLogIn = withAccountsConnection(function (connection) { var spinner = ['-', '\\', '|', '/']; lastLinePrinted = "Waiting for you to register on the web... " + spinner[animationFrame]; - process.stderr.write(lastLinePrinted + "\r"); + Console.rawError(lastLinePrinted + "\r"); animationFrame = (animationFrame + 1) % spinner.length; }, 200); var stopSpinner = function () { - process.stderr.write(new Array(lastLinePrinted.length + 1).join(' ') + - "\r"); + Console.rawError(new Array(lastLinePrinted.length + 1).join(' ') + + "\r"); clearInterval(timer); }; @@ -946,14 +950,14 @@ exports.registerOrLogIn = withAccountsConnection(function (connection) { stopSpinner(); if (e.errorType !== "Meteor.Error") throw e; - process.stderr.write( - "When you've picked your password, run 'meteor login' to log in.\n") + Console.error( + "When you've picked your password, run " + + Console.command("'meteor login'") + " to log in."); return false; } stopSpinner(); - process.stderr.write("Username: " + - waitForRegistrationResult.username + "\n"); + Console.error("Username: " + waitForRegistrationResult.username); loginResult = doInteractivePasswordLogin({ username: waitForRegistrationResult.username, retry: true, @@ -961,7 +965,7 @@ exports.registerOrLogIn = withAccountsConnection(function (connection) { }); return loginResult; } else if (result.alreadyExisted && result.username) { - process.stderr.write("\nLogging in as " + result.username + ".\n"); + Console.error("\nLogging in as " + Console.command(result.username) + "."); loginResult = doInteractivePasswordLogin({ username: result.username, @@ -971,8 +975,9 @@ exports.registerOrLogIn = withAccountsConnection(function (connection) { return loginResult; } else { // Hmm, got an email we don't understand. - process.stderr.write( - "\nThere was a problem. Please log in with 'meteor login'.\n"); + Console.error( + "\nThere was a problem. Please log in with " + + Console.command("'meteor login'") + "."); return false; } }); @@ -989,26 +994,29 @@ exports.maybePrintRegistrationLink = function (options) { if (session.userId && ! session.username && session.registrationUrl) { if (options.leadingNewline) - process.stderr.write("\n"); + Console.error(); if (options.onlyAllowIfRegistered) { // A stronger message: we're going to not allow whatever they were trying // to do! - process.stderr.write( -"You need to claim a username and set a password on your Meteor developer\n" + -"account to run this command. It takes about a minute at:\n" + -" " + session.registrationUrl + "\n"); + Console.error( + "You need to claim a username and set a password on your Meteor", + "developer account to run this command. It takes about a minute at:", + session.registrationUrl); + Console.error(); } else if (! options.firstTime) { // If they've already been prompted to set a password then this // is more of a friendly reminder, so we word it slightly // differently than the first time they're being shown a // registration url. - process.stderr.write( -"You should set a password on your Meteor developer account. It takes\n" + -"about a minute at: " + session.registrationUrl + "\n\n"); + Console.error( + "You should set a password on your Meteor developer account.", + "It takes about a minute at:", session.registrationUrl); + Console.error(); } else { - process.stderr.write( -"You can set a password on your account or change your email address at:\n" + -session.registrationUrl + "\n\n"); + Console.error( + "You can set a password on your account or change your email", + "address at:" + session.registrationUrl); + Console.error(); } return true; } diff --git a/tools/commands-cordova.js b/tools/commands-cordova.js index f2f16fe7c1..f14653a9fc 100644 --- a/tools/commands-cordova.js +++ b/tools/commands-cordova.js @@ -90,17 +90,17 @@ cordova.buildTargets = function (projectContext, targets, options) { if (! inProject) { if (! supported) { - Console.warn(Console.fail(MESSAGE_IOS_ONLY_ON_MAC)); + Console.failWarn(MESSAGE_IOS_ONLY_ON_MAC); } else { Console.warn("Please add the " + displayPlatform + " platform to your project first."); if (! hasSdk) { Console.info("First install the SDK by running: " + - Console.bold("meteor install-sdk " + platform)); + Console.command("meteor install-sdk " + platform)); Console.info("Then run: " + - Console.bold("meteor add-platform " + platform)); + Console.command("meteor add-platform " + platform)); } else { - Console.info("Run: " + Console.bold("meteor add-platform " + platform)); + Console.info("Run: " + Console.command("meteor add-platform " + platform)); } } throw new main.ExitWithCode(2); @@ -115,9 +115,9 @@ cordova.buildTargets = function (projectContext, targets, options) { if (supported) Console.warn("The " + displayPlatform + " platform is not installed;" + " please run: " + - Console.bold("meteor install-sdk " + platform)); + Console.command("meteor install-sdk " + platform)); else - Console.warn(Console.fail(MESSAGE_IOS_ONLY_ON_MAC)); + Console.failWarn(MESSAGE_IOS_ONLY_ON_MAC); throw new main.ExitWithCode(2); } @@ -184,7 +184,7 @@ var setVerboseness = cordova.setVerboseness = function (v) { }; var verboseLog = cordova.verboseLog = function (/* args */) { if (verboseness) - Console.stderr.write('%% ' + util.format.apply(null, arguments) + '\n'); + Console.rawError('%% ' + util.format.apply(null, arguments) + "\n"); }; @@ -374,8 +374,8 @@ var ensureCordovaProject = function (projectContext, appName) { if (err instanceof main.ExitWithCode) { process.exit(err.code); } - Console.stderr.write("Error creating Cordova project: " + - err.message + "\n" + err.stack + "\n"); + Console.error("Error creating Cordova prject: " + err.message); + Console.rawError(err.stack + "\n"); } } }; @@ -904,14 +904,16 @@ var CordovaRunner = function (projectContext, platformName, options) { // projectContext being asynchronously reset.) if (self.platformName !== "ios" && self.projectContext.packageMap.getInfo('oauth2')) { - Console.warn( -"\n" + -"WARNING: It looks like you are using OAuth2 login in your app.\n" + -" Meteor's OAuth2 implementation does not currently work with\n" + -" mobile apps in local development mode, except in the iOS\n" + -" simulator. You can run the iOS simulator with 'meteor run ios'.\n" + -" For additional workarounds, see\n" + -" https://github.com/meteor/meteor/wiki/OAuth-for-mobile-Meteor-clients.\n"); + Console.warn(); + Console.labelWarn( + "It looks like you are using OAuth2 login in your app. " + + "Meteor's OAuth2 implementation does not currently work with " + + "mobile apps in local development mode, except in the iOS " + + "simulator. You can run the iOS simulator with 'meteor run ios'. " + + "For additional workarounds, see " + + Console.url( + "https://github.com/meteor/meteor/wiki/" + + "OAuth-for-mobile-Meteor-clients.")); } }; @@ -1030,30 +1032,29 @@ var execCordovaOnPlatform = function (projectContext, platformName, options) { try { execFileSyncOrThrow('sh', args); } catch (err) { - Console.stderr.write([ - "", - chalk.green("Could not open your project in Xcode."), - chalk.green("Try running again with the --verbose option."), - chalk.green("Instructions for running your app on an iOS device:"), - chalk.cyan("https://github.com/meteor/meteor/wiki/How-to-run-your-app-on-an-iOS-device"), - "" - ].join("\n")); - + Console.error(); + Console.error(chalk.green("Could not open your project in Xcode.")); + Console.error(chalk.green("Try running again with the --verbose option.")); + Console.error( + chalk.green("Instructions for running your app on an iOS device: ") + + Console.url( + "https://github.com/meteor/meteor/wiki/" + + "How-to-run-your-app-on-an-iOS-device") + ); + Console.error(); process.exit(2); } - - Console.stdout.write([ - "", - chalk.green([ - "Your project has been opened in Xcode so that you can run your app on ", - "an iOS device. For further instructions, visit this wiki page:", - ].join("\n")), - chalk.cyan( - "https://github.com/meteor/meteor/wiki/How-to-run-your-app-on-an-iOS-device" - ), - "" - ].join("\n")); - + Console.info(); + Console.info( + chalk.green( + "Your project has been opened in Xcode so that you can run your " + + "app on an iOS device. For further instructions, visit this " + + "wiki page: ") + + Console.url( + "https://github.com/meteor/meteor/wiki/" + + "How-to-run-your-app-on-an-iOS-device" + )); + Console.info(); } else { verboseLog('Running emulator:', localCordova, args); var emulatorOptions = { verbose: options.verbose, cwd: cordovaPath }; @@ -1068,35 +1069,31 @@ var execCordovaOnPlatform = function (projectContext, platformName, options) { localCordova, args, emulatorOptions, function(err, code) { if (err && platform === "android" && isDevice) { - Console.stderr.write([ - "", - chalk.green("Could not start the app on your device. Is it plugged in?"), - chalk.green("Try running again with the --verbose option."), - chalk.green("Instructions for running your app on an Android device:"), - chalk.cyan("https://github.com/meteor/meteor/wiki/How-to-run-your-app-on-an-Android-device"), - "" - ].join("\n")); + Console.error(); + Console.error( + chalk.green("Could not start the app on your device. Is it plugged in?")); + Console.error("Try running again with the --verbose option."); + Console.error( + chalk.green("Instructions for running your app on an Android device: ") + + Console.url( + "https://github.com/meteor/meteor/wiki/" + + "How-to-run-your-app-on-an-Android-device")); + Console.error(); } else if (err && platform === "android") { - Console.stderr.write([ - "", - chalk.green("Could not start the app in the Android emulator."), - chalk.green("Try running again with the --verbose option."), - "" - ].join("\n")); + Console.error(); + Console.error(chalk.green("Could not start the app in the Android emulator.")); + Console.error(chalk.green("Try running again with the --verbose option.")); + Console.error(); } else if (err && platform === "ios") { - Console.stderr.write([ - "", - chalk.green("Could not start the app in the iOS simulator."), - chalk.green("Try running again with the --verbose option."), - "" - ].join("\n")); + Console.error(); + Console.error(chalk.green("Could not start the app in the iOS simulator.")); + Console.error(chalk.green("Try running again with the --verbose option.")); + Console.error(); } else if (err) { - Console.stderr.write([ - "", - chalk.green("Could not start your app."), - chalk.green("Try running again with the --verbose option."), - "" - ].join("\n")); + Console.error(); + Console.error(chalk.green("Could not start your app.")); + Console.error(chalk.green("Try running again with the --verbose option.")); + Console.error(); } // Don't throw an error or print the stack trace, but still exit the @@ -1292,9 +1289,12 @@ var checkAgreePlatformTerms = function (platform, name) { return true; } - Console.stdout.write("The following terms apply to " + name + ":\n\n"); - Console.stdout.write(terms + "\n\n"); - Console.stdout.write("You must agree to the terms to proceed.\n"); + Console.info("The following terms apply to " + name + ":"); + Console.info(); + Console.info(terms); + Console.info(); + Console.info("You must agree to the terms to proceed."); + Console.info(); var agreed = false; @@ -1312,7 +1312,8 @@ var checkAgreePlatformTerms = function (platform, name) { }; var checkPlatformRequirements = function (platform, options) { - options = _.extend({log: false, fix: false, fixConsole: false, fixSilent: false}, options); + options = _.extend( + { log: false, fix: false, fixConsole: false, fixSilent: false }, options); if (platform == 'android') { return Android.checkRequirements(options); } else if (platform == 'ios') { @@ -1327,7 +1328,9 @@ var requirePlatformReady = function (platform) { try { var installed = checkPlatformRequirements(platform); if (!installed.acceptable) { - Console.warn("The " + platformToHuman(platform) + " platform is not installed; please run: " + Console.bold("meteor install-sdk " + platform)); + Console.warn( + "The " + platformToHuman(platform) + " platform is not installed;", + "please run: " + Console.command("meteor install-sdk " + platform)); throw new main.ExitWithCode(2); } } catch (err) { @@ -1336,7 +1339,8 @@ var requirePlatformReady = function (platform) { } else if (err instanceof main.ExitWithCode) { throw err; } else { - Console.warn("Unexpected error while checking platform requirements: ", err); + Console.warn( + "Unexpected error while checking platform requirements: ", err); } throw new main.ExitWithCode(2); } @@ -1787,7 +1791,9 @@ _.extend(IOS.prototype, { } buildmessage.enterJob({title: 'Installing Xcode'}, function () { - //Console.info("Launching Xcode installer; please choose 'Get Xcode' to install Xcode"); + //Console.info( + // "Launching Xcode installer;", + // "please choose 'Get Xcode' to install Xcode"); //files.run('/usr/bin/xcodebuild', '--install'); // XXX: Any way to open direct in AppStore (rather than in browser)? @@ -1832,7 +1838,9 @@ _.extend(IOS.prototype, { var fix = !!options.fix; if (!Host.isMac()) { - log && Console.info("You are not running on OSX; we won't be able to install Xcode for local iOS development"); + log && Console.info( + "You are not running on OSX;", + "we won't be able to install Xcode for local iOS development"); return { acceptable: false, missing: [ "ios" ] }; } @@ -1840,14 +1848,14 @@ _.extend(IOS.prototype, { var okay = true; if (self.hasXcode()) { - log && Console.info(Console.success("Xcode is installed")); + log && Console.success("Xcode is installed"); } else { if (fix) { log && Console.info("Installing Xcode"); self.installXcode(); } else { - log && Console.info(Console.fail("Xcode is not installed")); + log && Console.failInfo("Xcode is not installed"); result.missing.push("xcode"); result.acceptable = false; @@ -1864,7 +1872,7 @@ _.extend(IOS.prototype, { if (self.hasXcode()) { if (self.hasAgreedXcodeLicense()) { - log && Console.info(Console.success("Xcode license agreed")); + log && Console.success("Xcode license agreed"); } else { if (fix) { log && Console.info("Please accept the Xcode license"); @@ -1873,7 +1881,7 @@ _.extend(IOS.prototype, { // XXX: Wait? } else { - log && Console.info(Console.fail("You must accept the Xcode license")); + log && Console.failInfo("You must accept the Xcode license"); result.missing.push("xcode-license"); result.acceptable = false; @@ -1882,14 +1890,17 @@ _.extend(IOS.prototype, { } _.each(['5.0', '5.0.1', '5.1', '6.0', '6.1'], function (version) { - if (self.isSdkInstalled(version)) { - log && Console.warn("An old version of the iPhone SDK is installed (" + version + "); you should"); - log && Console.warn("probably delete it. With SDK versions prior to 7.0 installed, your apps"); - log && Console.warn("can't be published to the App Store. Moreover, some Cordova plugins are"); - log && Console.warn("incompatible with this SDK."); - log && Console.info("You can remove it by deleting this directory: "); - log && Console.info(" " + self.getDirectoryForSdk(version)); - + if (self.isSdkInstalled(version) && log) { + Console.warn( + "An old version of the iPhone SDK is installed", + Console.noWrap("(" + version + ")") + ";", + "you should probably delete it. With SDK versions prior to 7.0", + "installed, your apps can't be published to the App Store.", + "Moreover, some Cordova plugins are incompatible with this SDK.", + "You can remove it by deleting this directory: "); + Console.warn( + Console.path(self.getDirectoryForSdk(version)), + Console.options({ indent: 4 })); // Not really a failure; just warn... } }); @@ -1935,7 +1946,9 @@ _.extend(Android.prototype, { return stat != null; } - Console.info("Can't determine acceleration for unknown host: ", archinfo.host()); + Console.info( + "Can't determine acceleration for unknown host: ", + Console.noWrap(archinfo.host())); return undefined; }, @@ -1956,7 +1969,9 @@ _.extend(Android.prototype, { files.mkdir_p(dir); fs.writeFileSync(filepath, mpkg); - Console.info("Launching HAXM installer; we recommend allocating 1024MB of RAM (or more)"); + Console.info( + "Launching HAXM installer;", + "we recommend allocating 1024MB of RAM (or more)"); files.run('open', filepath); return; @@ -1969,7 +1984,8 @@ _.extend(Android.prototype, { return; } - throw new Error("Can't install acceleration for unknown host: " + archinfo.host()); + throw new Error( + "Can't install acceleration for unknown host: " + archinfo.host()); }, useGlobalAdk: function () { @@ -1998,7 +2014,8 @@ _.extend(Android.prototype, { } if (!optional && !androidSdkPath) { - throw new Error("Cannot find Android SDK; be sure the 'android' tool is on your path"); + throw new Error( + "Cannot find Android SDK; be sure the 'android' tool is on your path"); } Console.debug("Using (global) Android SDK at", androidSdkPath); @@ -2054,9 +2071,10 @@ _.extend(Android.prototype, { } if (execution.exitCode !== 0) { - Console.warn("Unexpected exit code from android process: " + execution.exitCode); - Console.warn("stdout: " + execution.stdout); - Console.warn("stderr: " + execution.stderr); + Console.warn( + "Unexpected exit code from android process: " + execution.exitCode); + Console.rawWarn("stdout: " + execution.stdout + "\n"); + Console.rawWarn("stderr: " + execution.stderr + "\n"); throw new Error("Error running android tool: exit code " + execution.exitCode); } @@ -2175,9 +2193,9 @@ _.extend(Android.prototype, { if (execution.exitCode !== 0) { Console.debug("Unable to run aapt." + " (This is normal if 32 bit libraries are not found)"); - Console.debug(" exit code: " + execution.exitCode); - Console.debug(" stdout: " + execution.stdout); - Console.debug(" stderr: " + execution.stderr); + Console.rawDebug(" exit code: " + execution.exitCode + "\n"); + Console.rawDebug(" stdout: " + execution.stdout + "\n"); + Console.rawDebug(" stderr: " + execution.stderr + "\n"); return false; } @@ -2376,25 +2394,38 @@ _.extend(Android.prototype, { if (Host.hasAptGet()) { Console.info("You can install the JDK using:"); - Console.info(" sudo apt-get install --yes openjdk-7-jdk"); + Console.info( + Console.comand("sudo apt-get install --yes openjdk-7-jdk"), + Console.options({ indent: 2 })); // XXX: Technically, these are for Android, not installing Java if (processor == "x86_64") { Console.info("You will also need some 32-bit libraries:"); - Console.info(" sudo apt-get install --yes lib32z1 lib32stdc++6"); + Console.info( + Console.command("sudo apt-get install --yes lib32z1 lib32stdc++6"), + Console.options({ indent: 2 })); } } else if (Host.hasYum()) { Console.info("You can install the JDK using:"); - Console.info(" sudo yum install -y java-1.7.0-openjdk-devel"); + Console.info( + Console.command("sudo yum install -y java-1.7.0-openjdk-devel"), + Console.options({ indent: 2 })); // XXX: Technically, these are for Android, not installing Java if (processor == "x86_64") { Console.info("You will also need some 32-bit libraries:"); - Console.info(" sudo yum install -y glibc.i686 zlib.i686 libstdc++.i686 ncurses-libs.i686"); + Console.info( + Console.command( + "sudo yum install -y glibc.i686 zlib.i686 " + + "libstdc++.i686 ncurses-libs.i686"), + Console.options({ indent: 2 })); } } else { - Console.warn("You should install the JDK; we don't have instructions for your distribution (sorry!)"); - Console.info("Please do submit the instructions so we can include them.") + Console.warn( + "You should install the JDK; we don't have instructions", + "for your distribution (sorry!)"); + Console.info( + "Please do submit the instructions so we can include them."); } return; @@ -2463,9 +2494,10 @@ _.extend(Android.prototype, { } if (execution.exitCode != 0) { - Console.warn("Unexpected exit code from script: " + execution.exitCode); - Console.warn("stdout: " + execution.stdout); - Console.warn("stderr: " + execution.stderr); + Console.warn( + "Unexpected exit code from script: " + execution.exitCode); + Console.rawWarn("stdout: " + execution.stdout + "\n"); + Console.rawWarn("stderr: " + execution.stderr + "\n"); throw new Error('Could not download Android bundle'); } }); @@ -2484,8 +2516,12 @@ _.extend(Android.prototype, { // Boost timeout if not running HAXM/KVM if (self.hasAcceleration() === false) { - Console.warn("Android emulator acceleration was not installed; the emulator will be very slow."); - Console.info("You can run '" + Console.command("meteor install-sdk android") + "' for help.") + Console.warn( + "Android emulator acceleration was not installed;", + "the emulator will be very slow."); + Console.info( + "You can run '" + + Console.command("meteor install-sdk android") + "' for help."); timeLimit *= 4; } @@ -2503,11 +2539,15 @@ _.extend(Android.prototype, { Console.error("The emulator did not start in the expected time."); if (self.hasAcceleration() === false) { if (Host.isLinux()) { - Console.info("We highly recommend enabling KVM to speed up the emulator."); + Console.info( + "We highly recommend enabling KVM to speed up the emulator."); } else { - Console.info("We highly recommend installing HAXM to speed up the emulator."); + Console.info( + "We highly recommend installing HAXM to speed up the emulator."); } - Console.info("You can run '" + Console.command("meteor install-sdk android") + "' for help.") + Console.info( + "You can run '" + + Console.command("meteor install-sdk android") + "' for help."); } throw new main.ExitWithCode(1); @@ -2552,7 +2592,7 @@ _.extend(Android.prototype, { device[kv[0]] = kv[1]; } devices.push(device); - Console.debug("Found device", JSON.stringify(device)); + Console.rawDebug("Found device", JSON.stringify(device) + "\n"); }); return devices; }, @@ -2581,7 +2621,7 @@ _.extend(Android.prototype, { var hasAndroid = false; if (!self.useGlobalAdk()) { if (self.hasAndroidBundle()) { - log && Console.info(Console.success("Found Android bundle")); + log && Console.success("Found Android bundle"); hasAndroid = true; } else { if (fixConsole) { @@ -2590,7 +2630,7 @@ _.extend(Android.prototype, { self.installAndroidBundle(); hasAndroid = true; } else { - log && Console.info(Console.fail("Android bundle not found")); + log && Console.failInfo("Android bundle not found"); result.missing.push("android-bundle"); result.acceptable = false; @@ -2601,14 +2641,14 @@ _.extend(Android.prototype, { if (self.useGlobalAdk()) { var androidSdk = self.findAndroidSdk(true); if (androidSdk) { - log && Console.info(Console.success("Found Android SDK")); + log && Console.success("Found Android SDK"); // XXX: Verify hasAndroid = true; } else { - log && Console.info(Console.fail("Android SDK not found")); - - log && Console.info("If you set USE_GLOBAL_ADK, the 'android' tool must be on your path"); + log && Console.failInfo("Android SDK not found"); + log && Console.info( + "If you set USE_GLOBAL_ADK, the 'android' tool must be on your path"); result.missing.push("android-global-sdk"); result.acceptable = false; @@ -2616,9 +2656,9 @@ _.extend(Android.prototype, { var hasAnt = !!Host.which("ant"); if (hasAnt) { - log && Console.info(Console.success("Found ant on PATH")); + log && Console.success("Found ant on PATH"); } else { - log && Console.info(Console.fail("Ant not found on PATH")); + log && Console.failInfo("Ant not found on PATH"); result.missing.push("apache-ant"); result.acceptable = false; @@ -2627,7 +2667,7 @@ _.extend(Android.prototype, { var hasJava = false; if (self.hasJdk()) { - log && Console.info(Console.success("A JDK is installed")); + log && Console.success("A JDK is installed"); hasJava = true; } else { if (fix) { @@ -2636,7 +2676,7 @@ _.extend(Android.prototype, { self.installJdk(); hasJava = true; } else { - log && Console.info(Console.fail("A JDK is not installed")); + log && Console.failInfo("A JDK is not installed"); result.missing.push("jdk"); result.acceptable = false; @@ -2645,16 +2685,16 @@ _.extend(Android.prototype, { if (hasAndroid && hasJava) { if (self.isPlatformToolsInstalled()) { - log && Console.info(Console.success("Found Android Platform tools")); + log && Console.success("Found Android Platform tools"); } else { if (fixSilent) { log && Console.info("Installing Android Platform tools"); self.installTarget('platform-tools', function () { return self.isPlatformToolsInstalled(); }); - log && Console.info(Console.success("Installed Android Platform tools")); + log && Console.success("Installed Android Platform tools"); } else { - log && Console.info(Console.fail("Android Platform tools not found")); + log && Console.failInfo("Android Platform tools not found"); result.missing.push("android-platform-tools"); result.acceptable = false; @@ -2663,7 +2703,7 @@ _.extend(Android.prototype, { var hasBuildToolsVersion; if (self.isBuildToolsInstalled('21.0.0')) { - log && Console.info(Console.success("Found Android Build Tools")); + log && Console.success("Found Android Build Tools"); hasBuildToolsVersion = '21.0.0'; } else { if (fixSilent) { @@ -2671,10 +2711,10 @@ _.extend(Android.prototype, { self.installTarget('build-tools-21.0.0', function () { return self.isBuildToolsInstalled('21.0.0'); }); - log && Console.info(Console.success("Installed Android Build Tools")); + log && Console.success("Installed Android Build Tools"); hasBuildToolsVersion = '21.0.0'; } else { - log && Console.info(Console.fail("Android Build Tools not found")); + log && Console.failInfo("Android Build Tools not found"); result.missing.push("android-build-tools"); result.acceptable = false; @@ -2685,7 +2725,7 @@ _.extend(Android.prototype, { // Check that we can actually run aapt - on 64 bit, we need 32 bit libs // We need aapt to be installed to do this! if (!self.canRunAapt(hasBuildToolsVersion)) { - log && Console.info(Console.fail("32-bit libraries not found")); + log && Console.failInfo("32-bit libraries not found"); result.missing.push("libs32"); result.acceptable = false; @@ -2693,31 +2733,33 @@ _.extend(Android.prototype, { } if (self.isPlatformInstalled('android-19')) { - log && Console.info(Console.success("Found Android 19 API")); + log && Console.success("Found Android 19 API"); } else { if (fixSilent) { log && Console.info("Installing Android 19 API"); self.installTarget('android-19', function () { return self.isPlatformInstalled('android-19'); }); - log && Console.info(Console.success("Installed Android 19 API")); + log && Console.success("Installed Android 19 API"); } else { - log && Console.info(Console.fail("Android API 19 not found")); + log && Console.failInfo("Android API 19 not found"); result.missing.push("android-api"); result.acceptable = false; } } - // (We could alternatively check for {SDK}/system-images/android-19/default/x86/build.prop) + // (We could alternatively check for + // {SDK}/system-images/android-19/default/x86/build.prop) if (self.hasTarget('19', 'default/x86')) { - log && Console.info(Console.success("Found suitable Android x86 image")); + log && Console.success("Found suitable Android x86 image"); } else { if (fixSilent) { // The x86 image will fail to install if dependencies aren't there; // we've checked the others by version,but we should double-check // platform-tools as we don't check versions there - log && Console.info("Making sure Android Platform tools are up to date"); + log && Console.info( + "Making sure Android Platform tools are up to date"); self.installTarget('platform-tools', function () { return self.isPlatformToolsInstalled(); }); @@ -2726,9 +2768,9 @@ _.extend(Android.prototype, { self.installTarget('sys-img-x86-android-19', function () { return self.hasTarget('19', 'default/x86'); }); - log && Console.info(Console.success("Installed Android x86 image")); + log && Console.success("Installed Android x86 image"); } else { - log && Console.info(Console.fail("Suitable Android x86 image not found")); + log && Console.failInfo("Suitable Android x86 image not found"); result.missing.push("android-sys-img"); result.acceptable = false; @@ -2737,21 +2779,26 @@ _.extend(Android.prototype, { var avdName = self.getAvdName(); if (self.hasAvd(avdName)) { - log && Console.info(Console.success("'" + avdName + "' android virtual device (AVD) found")); + log && Console.success( + "'" + avdName + "' android virtual device (AVD) found"); } else { var isDefaultAvd = avdName === DEFAULT_AVD_NAME; if (fixSilent && isDefaultAvd) { - log && Console.info("Creating android virtual device (AVD): " + avdName); - + log && Console.info( + "Creating android virtual device (AVD): " + avdName); var avdOptions = {}; self.createAvd(avdName, avdOptions); - log && Console.info(Console.success("'" + avdName + "' android virtual device (AVD) created")); + log && Console.success( + "'" + avdName + "' android virtual device (AVD) created"); } else { - log && Console.info(Console.fail("'" + avdName + "' android virtual device (AVD) not found")); + log && Console.failInfo( + "'" + avdName + "' android virtual device (AVD) not found"); if (!isDefaultAvd) { - log && Console.info("(Because you specified a custom AVD, we don't create it automatically)"); + log && Console.info( + "(Because you specified a custom AVD, we don't create it", + "automatically)"); } result.missing.push("android-avd"); @@ -2766,18 +2813,21 @@ _.extend(Android.prototype, { if (fix) { self.installAcceleration(); } else { - log && Console.info(Console.fail("Android emulator acceleration is not installed")); - log && Console.info(" (The Android emulator will be very slow without acceleration)"); + log && Console.failInfo( + "Android emulator acceleration is not installed"); + log && Console.info( + "(The Android emulator will be very slow without acceleration)", + Console.options({ indent: 2 })); result.missing.push("haxm"); // Not all systems can install the accelerator, so don't block - // XXX: Maybe we should block the emulator (only); it is unusable without it - //result.acceptable = false + // XXX: Maybe we should block the emulator (only); it is unusable + //without it result.acceptable = false } } else if (hasAcceleration === true) { // (can be undefined) - log && Console.info(Console.success("Android emulator acceleration is installed")); + log && Console.success("Android emulator acceleration is installed"); } return result; @@ -2886,19 +2936,19 @@ main.registerCommand({ // explain why we can't remove server or browser platforms if (_.contains(projectContextModule.PlatformList.DEFAULT_PLATFORMS, platform)) { - Console.stdout.write(platform + ": cannot remove platform " + - "in this version of Meteor\n"); + Console.warn( + platform + ": cannot remove platform in this version of Meteor"); return; } if (_.contains(platforms, platform)) { - Console.stdout.write(platform + ": removed platform\n"); + Console.info(platform + ": removed platform"); platforms = _.without(platforms, platform); changed = true; return; } - Console.stdout.write(platform + ": platform is not in this project\n"); + Console.error(platform + ": platform is not in this project"); }); if (! changed) { @@ -2927,7 +2977,7 @@ main.registerCommand({ var platforms = projectContext.platformList.getPlatforms(); - Console.stdout.write(platforms.join("\n")); + Console.rawInfo(platforms.join("\n") + "\n"); }); main.registerCommand({ @@ -2978,7 +3028,9 @@ main.registerCommand({ if (!Android.hasAvd(avd)) { Console.error("'" + avd + "' android virtual device (AVD) does not exist"); - Console.info("The default AVD is called meteor, and will be created automatically for you"); + Console.info( + "The default AVD is called meteor, and will be created", + "automatically for you"); return 1; } @@ -3023,7 +3075,7 @@ main.registerCommand({ var installed = checkPlatformRequirements(platform, { log:true, fix: false, fixConsole: true, fixSilent: true } ); if (!_.isEmpty(installed.missing)) { if (Host.isLinux() && platform === "ios") { - Console.warn(Console.fail(MESSAGE_IOS_ONLY_ON_MAC)); + Console.failWarn(MESSAGE_IOS_ONLY_ON_MAC); return 1; } @@ -3043,7 +3095,8 @@ main.registerCommand({ url += "#" + anchor; } openUrl(url); - Console.info("Please follow the instructions here:\n" + Console.bold(url) + "\n"); + Console.info( + "Please follow the instructions here:\n" + Console.url(url) + "\n"); } else { Console.info("We don't have installation instructions for your platform"); } diff --git a/tools/commands-packages.js b/tools/commands-packages.js index b145120d58..5987f4e9a2 100644 --- a/tools/commands-packages.js +++ b/tools/commands-packages.js @@ -27,7 +27,8 @@ var Console = require('./console.js').Console; var projectContextModule = require('./project-context.js'); var packageVersionParser = require('./package-version-parser.js'); -// On some informational actions, we only refresh the package catalog if it is > 15 minutes old +// On some informational actions, we only refresh the package catalog if it is > +// 15 minutes old var DEFAULT_MAX_AGE_MS = 15 * 60 * 1000; // Returns an object with keys: @@ -153,7 +154,6 @@ main.registerCommand({ // not actually in the app! // XXX Maybe we should do a first pass that only builds packages actually in // the app and does display the PackageMapDelta? - return 0; }); @@ -260,9 +260,9 @@ main.registerCommand({ // weird. Let's not allow this. Console.error( "The package you are in appears to be inside a Meteor app but is not " + - "in its packages directory. You may only publish packages that are " + - "entirely outside of a project or that are loaded by the project " + - "that they are inside."); + "in its packages directory. You may only publish packages that are " + + "entirely outside of a project or that are loaded by the project " + + "that they are inside."); return 1; } var packageName = localVersionRecord.packageName; @@ -290,10 +290,10 @@ main.registerCommand({ if (!options['top-level'] && !packageName.match(/:/)) { Console.error( -"Only administrators can create top-level packages without an account prefix.\n" + -"(To confirm that you wish to create a top-level package with no account\n" + -"prefix, please run this command again with the --top-level option.)"); - + "Only administrators can create top-level packages without an", + "account prefix. (To confirm that you wish to create a top-level", + "package with no account prefix, please run this command again", + "with the --top-level option.)"); // You actually shouldn't be able to get here without being logged in, but // it seems poor form to assume anything like that for the point of a // brief error message. @@ -360,28 +360,41 @@ main.registerCommand({ // This is an undocumented command that you are not supposed to run! We // assume that you know what you are doing, if you ran it, and are OK with // overrwriting normal compatibilities. - Console.warn("\nWARNING: Your package contains binary code."); + Console.warn(); + Console.labelWarn("Your package contains binary code."); } else if (binary) { // Normal publish flow. Tell the user nicely. + Console.warn(); Console.warn( -"\nThis package contains binary code and must be built on multiple architectures.\n"); - + "This package contains binary code and must be built on", + "multiple architectures."); + Console.warn(); Console.info( -"You can access Meteor provided build machines, pre-configured to support\n" + -"older versions of MacOS and Linux, by running:\n"); - - _.each(["os.osx.x86_64", "os.linux.x86_64", "os.linux.x86_32"], function (a) { - Console.info(" meteor admin get-machine", a); + "You can access Meteor provided build machines, pre-configured to", + "support older versions of MacOS and Linux, by running:"); + _.each(["os.osx.x86_64", "os.linux.x86_64", "os.linux.x86_32"], + function (a) { + Console.info( + Console.command("meteor admin get-machine " + a), + Console.options({ indent: 2 })); }); - Console.info("\nOn each machine, run:\n"); - - Console.info(" meteor", - "publish-for-arch", - packageSource.name + "@" + packageSource.version); - - Console.info("\nFor more information on binary ABIs and consistent builds, see:"); - Console.info(" https://github.com/meteor/meteor/wiki/Build-Machines\n"); + Console.info(); + Console.info("On each machine, run:"); + Console.info(); + Console.info( + Console.command( + "meteor publish-for-arch " + + packageSource.name + "@" + packageSource.version), + Console.options({ indent: 2 })); + Console.info(); + Console.info( + "For more information on binary ABIs and consistent builds, see:"); + Console.info( + Console.url("https://github.com/meteor/meteor/wiki/Build-Machines"), + Console.options({ indent: 2 }) + ); + Console.info(); } // Refresh, so that we actually learn about the thing we just published. @@ -411,26 +424,36 @@ main.registerCommand({ var packageInfo = catalog.official.getPackage(name); if (! packageInfo) { Console.error( -"You can't call `meteor publish-for-arch` on package '" + name + "' without\n" + -"publishing it first.\n\n" + -"To publish the package, run `meteor publish --create` from the package directory.\n"); - + "You can't call " + Console.command("`meteor publish-for-arch`") + + "on package '" + name + "' without " +" publishing it first." + ); + Console.error(); + Console.error( + "To publish the package, run " + + Console.command("`meteor publish --create` ") + + "from the package directory."); + Console.error(); return 1; } var pkgVersion = catalog.official.getVersion(name, versionString); if (! pkgVersion) { Console.error( -"You can't call `meteor publish-for-arch` on version " + versionString + " of\n" + -"package '" + name + "' without publishing it first.\n\n" + -"To publish the version, run `meteor publish` from the package directory.\n\n"); - + "You can't call", Console.command("`meteor publish-for-arch`"), + "on version " + versionString + " of " + "package '" + name + + "' without publishing it first."); + Console.error(); + Console.error( + "To publish the package, run " + Console.command("`meteor publish ` ") + + "from the package directory."); + Console.error(); return 1; } if (! pkgVersion.source || ! pkgVersion.source.url) { - Console.error('There is no source uploaded for ' + - name + '@' + versionString); + Console.error( + "There is no source uploaded for", + name + '@' + versionString); return 1; } @@ -439,30 +462,33 @@ main.registerCommand({ // further springboarding based on reading a nested json file. if (! _.has(pkgVersion, 'releaseName')) { if (files.inCheckout()) { - process.stderr.write( - "This package was published from an old version of meteor," + - "but you are running from checkout!\nConsider running " + - "`meteor --release 1.0`, so we can springboard correctly.\n"); - process.stderr.exit(1); + Console.error( + "This package was published from an old version of meteor, " + + "but you are running from checkout! Consider running " + + Console.command("`meteor --release 1.0`"), + "so we can springboard correctly."); + process.exit(1); } throw new main.SpringboardToSpecificRelease("METEOR@1.0"); } if (pkgVersion.releaseName === null) { if (! files.inCheckout()) { - process.stderr.write( - "This package was published from a checkout of meteor! The tool cannot replicate\n" + - "that environment and will not even try. Please check out meteor at the \n" + - "corresponding git commit and try again.\n"); + Console.error( + "This package was published from a checkout of meteor!", + "The tool cannot replicate that environment and will not even try.", + "Please check out meteor at the " + + "corresponding git commit and try again."); process.exit(1); } } else if (files.inCheckout()) { - process.stderr.write( - "This package was published from a built version of meteor," + - "but you are running from checkout!\nConsider running from a " + - "proper Meteor release with `meteor --release " + - pkgVersion.releaseName + "` so we can springboard correctly.\n"); - process.stderr.exit(1); + Console.error( + "This package was published from a built version of meteor, " + + "but you are running from checkout! Consider running from a " + + "proper Meteor release with " + + Console.command("`meteor --release " + pkgVersion.releaseName + "`"), + "so we can springboard correctly."); + process.exit(1); } else if (pkgVersion.releaseName !== release.current.name) { // We are in a built release, and so is the package, but it's a different // one. Springboard! @@ -496,10 +522,11 @@ main.registerCommand({ // Copy over a version lock file from the source tarball. var versionsFile = path.join(packageDir, '.versions'); if (! fs.existsSync(versionsFile)) { - process.stderr.write( - "This package has no valid version lock file: are you trying to use publish-for-arch on\n" + - "a core package? Publish-for-arch cannot guarantee safety. Please use\n" + - "publish --existing-version instead.\n"); + Console.error( + "This package has no valid version lock file: are you trying to use " + + "publish-for-arch on a core package? Publish-for-arch cannot " + + "guarantee safety. Please use", + Console.command("'meteor publish --existing-version'"), "instead."); process.exit(1); } files.copyFile(path.join(packageDir, '.versions'), @@ -640,9 +667,9 @@ main.registerCommand({ if (start === "0.8." || start === "0.7." || start === "0.6." || start === "0.5.") { buildmessage.error( - "It looks like you are trying to publish a pre-package-server meteor release.\n" + - "Doing this through the package server is going to cause a lot of confusion.\n" + - "Please use the old release process."); + "It looks like you are trying to publish a pre-package-server " + + "meteor release. Doing this through the package server is going " + + "to cause a lot of confusion. Please use the old release process."); } } } @@ -656,7 +683,7 @@ main.registerCommand({ if (!trackRecord) { Console.error( 'There is no release track named ' + relConf.track + - '. If you are creating a new track, use the --create-track flag.'); + '. If you are creating a new track, use the --create-track flag.'); return 1; } @@ -696,9 +723,9 @@ main.registerCommand({ // these by accident. So, we will disallow it for now. if (relConf.packages || relConf.tool) { Console.error( - "Setting the --from-checkout option will use the tool and packages in your meteor " + - "checkout.\n" + - "Your release configuration file should not contain that information."); + "Setting the --from-checkout option will use the tool and packages " + + "in your meteor checkout. " + + "Your release configuration file should not contain that information."); return 1; } @@ -924,13 +951,17 @@ main.registerCommand({ Console.error( "Failed to push git tag. Please push git tag manually!"); Console.error( - "If you are publishing a non-prerelease version, then the readme will show up " + - "in atmosphere. To make sure that happens, after pushing the git tag, please " + - "run the following:"); + "If you are publishing a non-prerelease version, then the readme " + + "should show up in Atmosphere. To make sure that happens, after " + + "pushing the git tag, please run the following:"); _.each(toPublish, function (name) { - Console.info("meteor admin set-latest-readme " + name + " --tag " + gitTag); + Console.info( + Console.command( + "meteor admin set-latest-readme " + name + " --tag " + gitTag)); }); - Console.error("If you are publishing an experimental version, don't worry about it."); + Console.error( + "If you are publishing an experimental version, ", + "don't worry about it."); fail = true; } if (! fail) { @@ -938,14 +969,15 @@ main.registerCommand({ var isopk = projectContext.isopackCache.getIsopack(name); if (! isopk) throw Error("no isopack for " + name); - - var url = "https://raw.githubusercontent.com/meteor/meteor/" + gitTag + + var url = + "https://raw.githubusercontent.com/meteor/meteor/" + gitTag + "/packages/" + name + "/README.md"; var version = isopk.version; packageClient.callPackageServer( conn, '_changeReadmeURL', name, version, url); - Console.info("Setting the readme of", name + "@" + version, "to", url); + Console.info( + "Setting the readme of", name + "@" + version, "to", Console.url(url)); }); } } @@ -954,12 +986,13 @@ main.registerCommand({ // packages. Unlike publish, this is advanced functionality, so the user // should be familiar with the concept. if (! _.isEmpty(unfinishedBuilds)) { - Console.warning(); - Console.warning( - "WARNING: Some packages contain binary dependencies."); - Console.warning("Builds have not been published for the following packages:"); + Console.warn(); + Console.labelWarn( + "Some packages contain binary dependencies."); + Console.warn( + "Builds have not been published for the following packages:"); _.each(unfinishedBuilds, function (version, name) { - Console.warning(name + "@" + version); + Console.warn(name + "@" + version); }); // Note: we don't actually enforce the proper build machine thing. You // can't use publish-for-arch for meteor-tool, for example, you need to @@ -969,7 +1002,7 @@ main.registerCommand({ // --existing-version: presumably you don't care about compatibility // etc. If it is an official release, you ought to use a build machine // though. - Console.warning( + Console.warn( "Please publish the builds separately, from a proper build machine."); } } @@ -997,6 +1030,7 @@ main.registerCommand({ var versionVisible = function (record) { return options['show-old'] || !record.unmigrated; }; + var INDENT = 6; var full = options.args[0].split('@'); var name = full[0]; @@ -1097,20 +1131,25 @@ main.registerCommand({ if (v.buildArchitectures) { var buildArchitectures = v.buildArchitectures.split(' '); - Console.info(" Architectures: ", formatAsList(buildArchitectures, { formatter: formatArchitecture })); + Console.info( + "Architectures: ", + formatAsList( + buildArchitectures, { formatter: formatArchitecture }), + Console.options({ indent: INDENT })); } // XXX: else show "no architectures"? if (v.packages) { - Console.info(" tool: " + v.tool); - Console.info(" packages:"); + Console.info("tool: " + v.tool, Console.options({ indent: INDENT })); + Console.info("packages:", Console.options({ indent: INDENT })); _.each(v.packages, function(pv, pn) { - Console.info(" " + pn + "@" + pv); + Console.info(pn + "@" + pv, Console.options({ indent: INDENT })); }); } }); - Console.info("\n"); + Console.info(); + Console.info(); } else { // Non-detailed list of versions @@ -1126,7 +1165,7 @@ main.registerCommand({ rows.push(row); }); - utils.printTwoColumns(rows); + Console.printTwoColumns(rows); } } @@ -1136,7 +1175,8 @@ main.registerCommand({ var myMaintainerString = ""; var myMaintainers = _.pluck(record.maintainers, 'username'); if (myMaintainers.length === 0) { - Console.debug("No maintainer records found: ", JSON.stringify(record)); + Console.rawDebug( + "No maintainer records found: ", JSON.stringify(record), "\n"); } else if (myMaintainers.length === 1) { myMaintainerString = myMaintainers[0]; } else { @@ -1180,7 +1220,8 @@ main.registerCommand({ catalogRefresh: new catalog.Refresh.OnceAtStart({ maxAge: DEFAULT_MAX_AGE_MS, ignoreErrors: true }) }, function (options) { if (options.args.length === 0) { - Console.info("To show all packages, do", Console.command("meteor search .")); + Console.info( + "To show all packages, do", Console.command("meteor search .")); return 1; } @@ -1310,7 +1351,8 @@ main.registerCommand({ explainIfRefreshFailed(); } else { Console.info( - "To get more information on a specific item, use", Console.command("meteor show")); + "To get more information on a specific item, use", + Console.command("meteor show")); } }); @@ -1405,21 +1447,26 @@ main.registerCommand({ utils.printPackageList(items); if (newVersionsAvailable) { - Console.info("\n" + -"* New versions of these packages are available! Run 'meteor update' to try\n" + -" to update those packages to their latest versions. If your packages cannot be\n" + -" updated further, try typing meteor add @ to see more\n" + -" information."); + Console.info(); + Console.info( + "New versions of these packages are available! Run", + Console.command("'meteor update'"), "to try to update those", + "packages to their latest versions. If your packages cannot be", + "updated further, try typing", + Console.command("`meteor add @`"), + "to see more information.", + Console.options({ bulletPoint: "* " })); } if (anyBuiltLocally) { - Console.info("\n" + -"+ These packages are built locally from source."); + Console.info(); + Console.info( + "These packages are built locally from source.", + Console.options({ bulletPoint: "+ " })); } return 0; }); - /////////////////////////////////////////////////////////////////////////////// // update /////////////////////////////////////////////////////////////////////////////// @@ -1437,8 +1484,8 @@ var maybeUpdateRelease = function (options) { // We are running from checkout, so we are not updating the release. if (release.current && release.current.isCheckout()) { Console.error( -"You are running Meteor from a checkout, so we cannot update the Meteor release.\n" + -"Checking to see if we can update your packages."); + "You are running Meteor from a checkout, so we cannot update", + "the Meteor release. Checking to see if we can update your packages."); return 0; } @@ -1508,18 +1555,18 @@ var maybeUpdateRelease = function (options) { // command even ran. They could equivalently have run 'meteor // help --release xyz'. Console.info( - "Installed. Run 'meteor update' inside of a particular project\n" + - "directory to update that project to " + - release.current.getDisplayName() + "."); + "Installed. Run " + Console.command("'meteor update' ") + + "inside of a particular project directory to update that project to " + + release.current.getDisplayName() + "."); } else { // We get here if the user ran 'meteor update' and we didn't // find a new version. Console.info( "The latest version of Meteor, " + release.current.getReleaseVersion() + - ", is already installed on this\n" + - "computer. Run 'meteor update' inside of a particular project\n" + - "directory to update that project to " + - release.current.getDisplayName()); + ", is already installed on this computer. Run " + + Console.command("'meteor update'") + " inside of a particular " + + "project directory to update that project to " + + release.current.getDisplayName()); } return 0; } @@ -1544,7 +1591,7 @@ var maybeUpdateRelease = function (options) { var maybeTheLatestRelease = release.explicit ? "" : ", the latest release"; Console.info( "This project is already at " + - release.current.getDisplayName() + maybeTheLatestRelease + "."); + release.current.getDisplayName() + maybeTheLatestRelease + "."); return 0; } @@ -1590,8 +1637,7 @@ var maybeUpdateRelease = function (options) { // not try to patch you to an unfriendly release. So, either way, as far // as we are concerned you are at the 'latest patch version' if (!patchRecord || !patchRecord.recommended ) { - Console.error( - "You are at the latest patch version."); + Console.error("You are at the latest patch version."); return 0; } // Great, we found a patch version. You can only have one latest patch for @@ -1616,9 +1662,9 @@ var maybeUpdateRelease = function (options) { // We could not find any releases newer than the one that we are on, on // that track, so we are done. Console.info( -"This project is already at " + projectContext.releaseFile.displayReleaseName + -", which is newer\n" + -"than the latest release."); + "This project is already at " + + Console.noWrap(projectContext.releaseFile.displayReleaseName) + + ", which is newer than the latest release."); return 0; } } @@ -1639,7 +1685,8 @@ var maybeUpdateRelease = function (options) { // Nope, this release didn't work. Console.debug( "Update to release", releaseTrack + "@" + versionToTry, - "is impossible:\n" + messages.formatMessages()); + "is impossible:"); + Console.debug(messages.formatMessages()); return false; } @@ -1651,8 +1698,8 @@ var maybeUpdateRelease = function (options) { var newerAvailable = false; if (! solutionReleaseVersion) { Console.info( - "This project is at the latest release which is compatible with your\n" + - "current package constraints."); + "This project is at the latest release which is compatible with your " + + "current package constraints."); return 0; } else if (solutionReleaseVersion !== releaseVersionsToTry[0]) { newerAvailable = true; @@ -1689,8 +1736,8 @@ var maybeUpdateRelease = function (options) { projectContext.releaseFile.displayReleaseName + "."); if (newerAvailable) { Console.info( - "(Newer releases are available but are not compatible with your\n" + - "current package constraints.)"); + "(Newer releases are available but are not compatible with your " + + "current package constraints.)"); } // Now run the upgraders. @@ -2001,7 +2048,7 @@ main.registerCommand({ }); }); if (messages.hasMessages()) { - Console.error("=> Errors while parsing arguments:"); + Console.arrowError("Errors while parsing arguments:", 1); Console.printMessages(messages); explainIfRefreshFailed(); // this is why we're not using captureAndExit return 1; @@ -2014,7 +2061,7 @@ main.registerCommand({ projectContext.prepareProjectForBuild(); }); if (messages.hasMessages()) { - Console.error("=> Errors while adding packages:"); + Console.arrowError("Errors while adding packages:", 1); Console.printMessages(messages); explainIfRefreshFailed(); // this is why we're not using captureAndExit return 1; @@ -2026,12 +2073,12 @@ main.registerCommand({ projectContext.packageMapDelta.displayOnConsole(); // Show descriptions of directly added packages. - Console.stdout.write("\n"); + Console.info(); _.each(constraintsToAdd, function (constraint) { var version = projectContext.packageMap.getInfo(constraint.name).version; var versionRecord = projectContext.projectCatalog.getVersion( constraint.name, version); - Console.stdout.write( + Console.info( constraint.name + (versionRecord.description ? (": " + versionRecord.description) : "")); }); @@ -2178,7 +2225,8 @@ main.registerCommand({ } if ((options.add || options.remove) && options.list) { Console.error( -"Sorry, you can't change the users at the same time as you're listing them."); + "Sorry, you can't change the users at the same time as you're", + "listing them."); return 1; } @@ -2213,7 +2261,7 @@ main.registerCommand({ packageClient.callPackageServer( conn, 'removeMaintainer', name, options.remove); } - Console.info(" Done!"); + Console.info("Success."); } } catch (err) { packageClient.handlePackageServerConnectionError(err); @@ -2230,16 +2278,18 @@ main.registerCommand({ if (!record) { Console.info( -"Could not get list of maintainers: package " + name + " does not exist."); + "Could not get list of maintainers:", + "package " + name + " does not exist."); return 1; } - Console.info("\nThe maintainers for " + name + " are:"); + Console.info(); + Console.info("The maintainers for " + name + " are:"); _.each(record.maintainers, function (user) { if (! user || !user.username) - Console.info(""); + Console.rawInfo("" + "\n"); else - Console.info(user.username + ""); + Console.rawInfo(user.username + "\n"); }); return 0; }); @@ -2284,11 +2334,11 @@ main.registerCommand({ toolPackage, toolVersion); if (!toolPkgBuilds) { // XXX this could also mean package unknown. - Console.error('Tool version unknown: ' + release.tool + ''); + Console.error('Tool version unknown: ' + release.tool); return 1; } if (!toolPkgBuilds.length) { - Console.error('Tool version has no builds: ' + release.tool + ''); + Console.error('Tool version has no builds: ' + release.tool); return 1; } @@ -2308,8 +2358,8 @@ main.registerCommand({ }); Console.error( - 'Building bootstrap tarballs for architectures ' + - osArches.join(', ') + ''); + 'Building bootstrap tarballs for architectures ' + osArches.join(', ')); + // Before downloading anything, check that the catalog contains everything we // need for the OSes that the tool is built for. var messages = buildmessage.capture(function () { @@ -2328,7 +2378,7 @@ main.registerCommand({ }); if (messages.hasMessages()) { - Console.error("\n" + messages.formatMessages()); + Console.printMessages(messages); return 1; }; @@ -2432,8 +2482,7 @@ main.registerCommand({ var bannersData = fs.readFileSync(bannersFile, 'utf8'); bannersData = JSON.parse(bannersData); } catch (e) { - Console.error("Could not parse banners file: "); - Console.error(e.message + ""); + Console.error("Could not parse banners file: " + e.message); return 1; } if (!bannersData.track) { @@ -2482,14 +2531,15 @@ main.registerCommand({ var name = release[0]; var version = release[1]; if (!version) { - Console.error('\n Must specify release version (track@version)'); + Console.error('Must specify release version (track@version)'); return 1; } // Now let's get down to business! Fetching the thing. var record = catalog.official.getReleaseTrack(name); if (!record) { - Console.error('\n There is no release track named ' + name); + Console.error(); + Console.error('There is no release track named ' + name); return 1; } @@ -2503,14 +2553,15 @@ main.registerCommand({ try { if (options.unrecommend) { Console.info("Unrecommending " + name + "@" + version + "..."); - packageClient.callPackageServer(conn, 'unrecommendVersion', name, version); - Console.info("Done!\n " + name + "@" + version + - " is no longer a recommended release"); + packageClient.callPackageServer( + conn, 'unrecommendVersion', name, version); + Console.info("Success."); + Console.info(name + "@" + version, "is no longer a recommended release"); } else { Console.info("Recommending " + options.args[0] + "..."); packageClient.callPackageServer(conn, 'recommendVersion', name, version); - Console.info("Done!\n " + name + "@" + version + - " is now a recommended release"); + Console.info("Success."); + Console.info(name + "@" + version, "is now a recommended release"); } } catch (err) { packageClient.handlePackageServerConnectionError(err); @@ -2538,7 +2589,8 @@ main.registerCommand({ // Now let's get down to business! Fetching the thing. var record = catalog.official.getPackage(name); if (!record) { - Console.error('\n There is no package named ' + name); + Console.error(); + Console.error('There is no package named ' + name); return 1; } @@ -2550,12 +2602,12 @@ main.registerCommand({ } try { - Console.info( - "Changing homepage on " + Console.rawInfo( + "Changing homepage on " + name + " to " + url + "..."); packageClient.callPackageServer(conn, '_changePackageHomepage', name, url); - Console.info("Done!"); + Console.info(" done"); } catch (err) { packageClient.handlePackageServerConnectionError(err); return 1; @@ -2601,16 +2653,16 @@ main.registerCommand({ try { var status = options.success ? "successfully" : "unsuccessfully"; + // XXX: This should probably use progress bars instead. _.each(versions, function (version) { - process.stdout.write( - "Setting " - + name + "@" + version + " as " + - status + " migrated ..."); + Console.rawInfo( + "Setting " + name + "@" + version + " as " + + status + " migrated ... "); packageClient.callPackageServer( conn, '_changeVersionMigrationStatus', name, version, !options.success); - process.stdout.write(" done!\n"); + Console.info("done."); }); } catch (err) { packageClient.handlePackageServerConnectionError(err); @@ -2656,14 +2708,13 @@ main.registerCommand({ } try { - Console.info( - "Setting README of " - + name + "@" + version + " to " + url); - packageClient.callPackageServer( - conn, - '_changeReadmeURL', - name, version, url); - Console.info(" done!\n"); + // XXX: This output should probably use progress bars instead! + Console.rawInfo("Setting README of " + name + "@" + version + " to " + url + " ... "); + packageClient.callPackageServer( + conn, + '_changeReadmeURL', + name, version, url); + Console.info("done."); } catch (err) { packageClient.handlePackageServerConnectionError(err); return 1; diff --git a/tools/commands.js b/tools/commands.js index e8ea5615a5..c0f1efe800 100644 --- a/tools/commands.js +++ b/tools/commands.js @@ -70,7 +70,9 @@ var showInvalidArchMsg = function (arch) { Console.info("Invalid architecture: " + arch); Console.info("The following are valid Meteor architectures:"); _.each(_.keys(VALID_ARCHITECTURES), function (va) { - Console.info(" " + va); + Console.info( + Console.command(va), + Console.options({ indent: 2 })); }); }; @@ -85,7 +87,7 @@ main.registerCommand({ catalogRefresh: new catalog.Refresh.Never() }, function (options) { var archinfo = require('./archinfo.js'); - console.log(archinfo.host()); + Console.rawInfo(archinfo.host() + "\n"); }); // Prints the current release in use. Note that if there is not @@ -101,15 +103,16 @@ main.registerCommand({ if (release.current === null) { if (! options.appDir) throw new Error("missing release, but not in an app?"); - Console.stderr.write( -"This project was created with a checkout of Meteor, rather than an\n" + -"official release, and doesn't have a release number associated with\n" + -"it. You can set its release with 'meteor update'.\n"); + Console.error( + "This project was created with a checkout of Meteor, rather than an " + + "official release, and doesn't have a release number associated with " + + "it. You can set its release with " + + Console.command("'meteor update'") + "."); return 1; } if (release.current.isCheckout()) { - Console.stderr.write("Unreleased (running from a checkout)\n"); + Console.error("Unreleased (running from a checkout)."); return 1; } @@ -123,15 +126,15 @@ main.registerCommand({ catalogRefresh: new catalog.Refresh.Never() }, function (options) { if (files.inCheckout()) { - Console.stderr.write("checkout\n"); + Console.error("checkout"); return 1; } else if (release.current === null) { // .meteor/release says "none" but not in a checkout. - Console.stderr.write("none\n"); + Console.error("none"); return 1; } else { - Console.stdout.write(release.current.name + "\n"); - Console.stdout.write(files.getToolsVersion() + "\n"); + Console.rawInfo(release.current.name + "\n"); + Console.rawInfo(files.getToolsVersion() + "\n"); return 0; } }); @@ -193,16 +196,16 @@ function doRunCommand (options) { var parsedUrl = utils.parseUrl(options.port); } catch (err) { if (options.verbose) { - Console.stderr.write('Error while parsing --port option: ' - + err.stack + '\n'); + Console.rawError( + "Error while parsing --port option: " + err.stack + "\n"); } else { - Console.stderr.write(err.message + '\n'); + Console.error(err.message); } return 1; } if (! parsedUrl.port) { - Console.stderr.write("--port must include a port.\n"); + Console.error("--port must include a port."); return 1; } @@ -210,10 +213,10 @@ function doRunCommand (options) { var parsedMobileServer = utils.mobileServerForRun(options); } catch (err) { if (options.verbose) { - Console.stderr.write('Error while parsing --mobile-server option: ' - + err.stack + '\n'); + Console.rawError( + "Error while parsing --mobile-server option: " + err.stack + "\n"); } else { - Console.stderr.write(err.message + '\n'); + Console.error(err.message); } return 1; } @@ -289,14 +292,13 @@ function doRunCommand (options) { // If we are targeting the remote devices, warn about ports and same network if (utils.runOnDevice(options)) { cordova.verboseLog('A run on a device requested'); - var warning = [ -"WARNING: You are testing your app on a remote device.", -" For the mobile app to be able to connect to the local server, make", -" sure your device is on the same network, and that the network", -" configuration allows clients to talk to each other", -" (no client isolation)."]; - - Console.stderr.write(warning.join("\n")); + var warning = + "You are testing your app on a remote device." + + "For the mobile app to be able to connect to the local server, make " + + "sure your device is on the same network, and that the network " + + "configuration allows clients to talk to each other " + + "(no client isolation)."; + Console.labelWarn(warning); } @@ -304,9 +306,10 @@ function doRunCommand (options) { if (options['app-port']) { var appPortMatch = options['app-port'].match(/^(?:(.+):)?([0-9]+)?$/); if (!appPortMatch) { - Console.stderr.write( -"run: --app-port must be a number or be of the form 'host:port' where\n" + -"port is a number. Try 'meteor help run' for help.\n"); + Console.error( + "run: --app-port must be a number or be of the form 'host:port' ", + "where port is a number. Try", + Console.command("'meteor help run'") + " for help."); return 1; } appHost = appPortMatch[1] || null; @@ -378,8 +381,9 @@ main.registerCommand({ catalogRefresh: new catalog.Refresh.Never() }, function (options) { if (!options.appDir) { - Console.stderr.write( - "The 'meteor shell' command must be run in a Meteor app directory." + Console.error( + "The " + Console.command("'meteor shell'") + " command must be run", + "in a Meteor app directory." ); } else { require('./server/shell.js').connect(options.appDir); @@ -411,12 +415,13 @@ main.registerCommand({ // No package examples exist yet. if (options.list && options.example) { - Console.stderr.write("No package examples exist at this time.\n\n"); + Console.error("No package examples exist at this time."); + Console.error(); throw new main.ShowUsage; } if (!packageName) { - Console.stderr.write("Please specify the name of the package. \n"); + Console.error("Please specify the name of the package."); throw new main.ShowUsage; } @@ -429,7 +434,7 @@ main.registerCommand({ var inYourApp = options.appDir ? " in your app" : ""; if (fs.existsSync(packageDir)) { - Console.stderr.write(packageName + ": Already exists" + inYourApp + "\n"); + Console.error(packageName + ": Already exists" + inYourApp); return 1; } @@ -468,11 +473,11 @@ main.registerCommand({ ignore: [/^local$/] }); } catch (err) { - Console.stderr.write("Could not create package: " + err.message + "\n"); + Console.error("Could not create package: " + err.message); return 1; } - Console.stdout.write(packageName + ": created" + inYourApp + "\n"); + Console.info(packageName + ": created" + inYourApp); return 0; } @@ -498,12 +503,16 @@ main.registerCommand({ }); if (options.list) { - Console.stdout.write("Available examples:\n"); + Console.info("Available examples:"); _.each(examples, function (e) { - Console.stdout.write(" " + e + "\n"); + Console.info( + Console.command(e), + Console.options({ indent: 2 })); }); - Console.stdout.write("\n" + -"Create a project from an example with 'meteor create --example '.\n"); + Console.info(); + Console.info( + "Create a project from an example with " + + Console.command("'meteor create --example '") + "."); return 0; }; @@ -517,13 +526,13 @@ main.registerCommand({ var appPath = path.resolve(appPathAsEntered); if (fs.existsSync(appPath)) { - Console.stderr.write(appPath + ": Already exists\n"); + Console.error(appPath + ": Already exists"); return 1; } if (files.findAppDir(appPath)) { - Console.stderr.write( - "You can't create a Meteor project inside another Meteor project.\n"); + Console.error( + "You can't create a Meteor project inside another Meteor project."); return 1; } @@ -533,8 +542,11 @@ main.registerCommand({ if (options.example) { if (examples.indexOf(options.example) === -1) { - Console.stderr.write(options.example + ": no such example\n\n"); - Console.stderr.write("List available applications with 'meteor create --list'.\n"); + Console.error(options.example + ": no such example."); + Console.error(); + Console.error( + "List available applications with", + Console.command("'meteor create --list'") + "."); return 1; } else { files.cp_r(path.join(exampleDir, options.example), appPath, { @@ -597,10 +609,12 @@ main.registerCommand({ Console.info(message); } - Console.stdout.write( - "To run your new app:\n" + - " cd " + appPathAsEntered + "\n" + - " meteor\n"); + Console.info("To run your new app:"); + Console.info( + Console.command("cd " + appPathAsEntered), Console.options({ indent: 2 })); + Console.info( + Console.command("meteor"), Console.options({ indent: 2 })); + }); /////////////////////////////////////////////////////////////////////////////// @@ -638,11 +652,13 @@ main.registerCommand(_.extend({ name: 'bundle', hidden: true }, buildCommands), function (options) { - Console.stderr.write( -"This command has been deprecated in favor of 'meteor build', which allows you to\n" + -"build for multiple platforms and outputs a directory instead of a single\n" + -"tarball. See 'meteor help build' for more information.\n\n"); - + Console.error( + "This command has been deprecated in favor of " + + Console.command("'meteor build'") + ", which allows you to " + + "build for multiple platforms and outputs a directory instead of " + + "a single tarball. See " + Console.command("'meteor help build'") + + "for more information."); + Console.error(); return buildCommand(_.extend(options, { _serverOnly: true })); }); @@ -697,20 +713,20 @@ var buildCommand = function (options) { var parsedMobileServer = utils.parseUrl( mobileServer, { protocol: "http://" }); } catch (err) { - Console.stderr.write(err.message); + Console.error(err.message); return 1; } if (! parsedMobileServer.host) { - Console.stderr.write("--server must include a hostname.\n"); + Console.error("--server must include a hostname."); return 1; } } else { // For Cordova builds, require '--server'. // XXX better error message? - Console.stderr.write( -"Supply the server hostname and port in the --server option\n" + -"for mobile app builds.\n"); + Console.error( + "Supply the server hostname and port in the --server option " + + "for mobile app builds."); return 1; } var cordovaSettings = {}; @@ -742,11 +758,14 @@ var buildCommand = function (options) { // We would like the output path to be outside the app directory, which // means the first step to getting there is going up a level. if (relative.substr(0, 3) !== ('..' + path.sep)) { - Console.warn(""); - Console.warn("Warning: The output directory is under your source tree."); - Console.warn(" Your generated files may get interpreted as source code!"); - Console.warn(" Consider building into a different directory instead (" + Console.command("meteor build ../output") + ")"); - Console.warn(""); + Console.warn(); + Console.labelWarn( + "The output directory is under your source tree.", + "Your generated files may get interpreted as source code!", + "Consider building into a different directory instead (" + + Console.command("meteor build ../output") + ")", + Console.options({ indent: 2 })); + Console.warn(); } } @@ -775,8 +794,8 @@ var buildCommand = function (options) { } }); if (bundleResult.errors) { - Console.stderr.write("Errors prevented bundling:\n"); - Console.stderr.write(bundleResult.errors.formatMessages()); + Console.error("Errors prevented bundling:"); + Console.error(bundleResult.errors.formatMessages()); return 1; } @@ -790,8 +809,8 @@ var buildCommand = function (options) { files.createTarball(path.join(buildDir, 'bundle'), outputTar); } catch (err) { - Console.stderr.write("Errors during tarball creation:\n"); - Console.stderr.write(err.message); + Console.error("Errors during tarball creation:"); + Console.error(err.message); files.rm_recursive(buildDir); return 1; } @@ -868,17 +887,18 @@ main.registerCommand({ // specified? if (! mongoPort) { - Console.stdout.write( -"mongo: Meteor isn't running a local MongoDB server.\n" + -"\n" + -"This command only works while Meteor is running your application\n" + -"locally. Start your application first. (This error will also occur if\n" + -"you asked Meteor to use a different MongoDB server with $MONGO_URL when\n" + -"you ran your application.)\n" + -"\n" + -"If you're trying to connect to the database of an app you deployed\n" + -"with 'meteor deploy', specify your site's name with this command.\n" -); + Console.info("mongo: Meteor isn't running a local MongoDB server."); + Console.info(); + Console.info( + "This command only works while Meteor is running your application " + + "locally. Start your application first. (This error will also occur " + + "if you asked Meteor to use a different MongoDB server with " + + "$MONGO_URL when you ran your application.)"); + Console.info(); + Console.info( + "If you're trying to connect to the database of an app you deployed " + + "with " + Console.command("'meteor deploy'") + + ", specify your site's name with this command."); return 1; } mongoUrl = "mongodb://127.0.0.1:" + mongoPort + "/meteor"; @@ -925,13 +945,14 @@ main.registerCommand({ catalogRefresh: new catalog.Refresh.Never() }, function (options) { if (options.args.length !== 0) { - Console.stderr.write( -"meteor reset only affects the locally stored database.\n" + -"\n" + -"To reset a deployed application use\n" + -" meteor deploy --delete appname\n" + -"followed by\n" + -" meteor deploy appname\n"); + Console.error("meteor reset only affects the locally stored database."); + Console.error(); + Console.error("To reset a deployed application use"); + Console.error( + Console.command("meteor deploy --delete appname"), Console.options({ indent: 2 })); + Console.error("followed by"); + Console.error( + Console.command("meteor deploy appname"), Console.options({ indent: 2 })); return 1; } @@ -942,18 +963,18 @@ main.registerCommand({ require(path.join(__dirname, 'run-mongo.js')).findMongoPort; var isRunning = !! findMongoPort(options.appDir); if (isRunning) { - Console.stderr.write( -"reset: Meteor is running.\n" + -"\n" + -"This command does not work while Meteor is running your application.\n" + -"Exit the running Meteor development server.\n"); + Console.error("reset: Meteor is running."); + Console.error(); + Console.error( + "This command does not work while Meteor is running your application.", + "Exit the running Meteor development server."); return 1; } var localDir = path.join(options.appDir, '.meteor', 'local'); files.rm_recursive(localDir); - Console.stdout.write("Project reset.\n"); + Console.info("Project reset."); }); /////////////////////////////////////////////////////////////////////////////// @@ -1004,13 +1025,14 @@ main.registerCommand({ if (options.password) { if (useGalaxy) { - Console.stderr.write("Galaxy does not support --password.\n"); + Console.error("Galaxy does not support --password."); } else { - Console.stderr.write( -"Setting passwords on apps is no longer supported. Now there are\n" + -"user accounts and your apps are associated with your account so that\n" + -"only you (and people you designate) can access them. See the\n" + -"'meteor claim' and 'meteor authorized' commands.\n"); + Console.error( + "Setting passwords on apps is no longer supported. Now there are " + + "user accounts and your apps are associated with your account so " + + "that only you (and people you designate) can access them. See the " + + Console.command("'meteor claim'") + " and " + + Console.command("'meteor authorized'") + " commands."); } return 1; } @@ -1018,18 +1040,16 @@ main.registerCommand({ var starball = options.star; if (starball && ! useGalaxy) { // XXX it would be nice to support this for non-Galaxy deploys too - Console.stderr.write( -"--star: only supported when deploying to Galaxy.\n"); + Console.error("--star: only supported when deploying to Galaxy."); return 1; } var loggedIn = auth.isLoggedIn(); if (! loggedIn) { - Console.stderr.write( -"To instantly deploy your app on a free testing server, just enter your\n" + -"email address!\n" + -"\n"); - + Console.error( + "To instantly deploy your app on a free testing server,", + "just enter your email address!"); + Console.error(); if (! auth.registerOrLogIn()) return 1; } @@ -1037,10 +1057,11 @@ main.registerCommand({ // Override architecture iff applicable. var buildArch = DEPLOY_ARCH; if (options['override-architecture-with-local']) { - Console.stdout.write( - "\n => WARNING: OVERRIDING DEPLOY ARCHITECTURE WITH LOCAL ARCHITECTURE\n"); - Console.stdout.write( - " => If your app contains binary code, it may break terribly and you will be sad.\n\n"); + Console.warn(); + Console.labelWarn( + "OVERRIDING DEPLOY ARCHITECTURE WITH LOCAL ARCHITECTURE.", + "If your app contains binary code, it may break in unexpected " + + "and terrible ways."); buildArch = archinfo.host(); } @@ -1142,14 +1163,15 @@ main.registerCommand({ }, function (options) { if (options.add && options.remove) { - Console.stderr.write( - "Sorry, you can only add or remove one user at a time.\n"); + Console.error( + "Sorry, you can only add or remove one user at a time."); return 1; } if ((options.add || options.remove) && options.list) { - Console.stderr.write( -"Sorry, you can't change the users at the same time as you're listing them.\n"); + Console.error( + "Sorry, you can't change the users at the same time as", + "you're listing them."); return 1; } @@ -1158,16 +1180,17 @@ main.registerCommand({ var site = qualifySitename(options.args[0]); if (hostedWithGalaxy(site)) { - Console.stderr.write( -"Sites hosted on Galaxy do not have an authorized user list.\n" + -"Instead, go to your Galaxy dashboard to change the authorized users\n" + -"of your Galaxy.\n"); + Console.error( + "Sites hosted on Galaxy do not have an authorized user list. " + + "Instead, go to your Galaxy dashboard to change the authorized users " + + "of your Galaxy.\n"); return 1; } if (! auth.isLoggedIn()) { - Console.stderr.write( - "You must be logged in for that. Try 'meteor login'.\n"); + Console.error( + "You must be logged in for that. Try " + + Console.command("'meteor login'")); return 1; } @@ -1194,16 +1217,19 @@ main.registerCommand({ var site = qualifySitename(options.args[0]); if (! auth.isLoggedIn()) { - Console.stderr.write( -"You must be logged in to claim sites. Use 'meteor login' to log in.\n" + -"If you don't have a Meteor developer account yet, create one by clicking\n" + -"'Sign in' and then 'Create account' at www.meteor.com.\n\n"); + Console.error( + "You must be logged in to claim sites. Use " + + Console.command("'meteor login'") + " to log in. If you don't have a " + + "Meteor developer account yet, create one by clicking " + + Console.command("'Sign in'") + " and then " + + Console.command("'Create account'") + " at www.meteor.com."); + Console.error(); return 1; } if (hostedWithGalaxy(site)) { - Console.stderr.write( - "Sorry, you can't claim sites that are hosted on Galaxy.\n"); + Console.error( + "Sorry, you can't claim sites that are hosted on Galaxy."); return 1; } @@ -1270,19 +1296,19 @@ main.registerCommand({ try { var parsedUrl = utils.parseUrl(options.port); } catch (err) { - Console.stderr.write(err.message); + Console.error(err.message); return 1; } if (! parsedUrl.port) { - Console.stderr.write("--port must include a port.\n"); + Console.error("--port must include a port."); return 1; } try { var parsedMobileServer = utils.mobileServerForRun(options); } catch (err) { - Console.stderr.write(err.message); + Console.error(err.message); return 1; } @@ -1583,18 +1609,18 @@ main.registerCommand({ var loggedInAccountsConnectionOrPrompt = function (action) { var token = auth.getSessionToken(config.getAccountsDomain()); if (! token) { - Console.stderr.write("You must be logged in to " + action + ".\n"); + Console.error("You must be logged in to " + action + "."); auth.doUsernamePasswordLogin({ retry: true }); - Console.stdout.write("\n"); + Console.info(); } token = auth.getSessionToken(config.getAccountsDomain()); var conn = auth.loggedInAccountsConnection(token); if (conn === null) { // Server rejected our token. - Console.stderr.write("You must be logged in to " + action + ".\n"); + Console.error("You must be logged in to " + action + "."); auth.doUsernamePasswordLogin({ retry: true }); - Console.stdout.write("\n"); + Console.info(); token = auth.getSessionToken(config.getAccountsDomain()); conn = auth.loggedInAccountsConnection(token); } @@ -1612,9 +1638,9 @@ main.registerCommand({ var token = auth.getSessionToken(config.getAccountsDomain()); if (! token) { - Console.stderr.write("You must be logged in to list your organizations.\n"); + Console.error("You must be logged in to list your organizations."); auth.doUsernamePasswordLogin({ retry: true }); - Console.stdout.write("\n"); + Console.info(); } var url = config.getAccountsApiUrl() + "/organizations"; @@ -1627,13 +1653,13 @@ main.registerCommand({ }); var body = JSON.parse(result.body); } catch (err) { - Console.stderr.write("Error listing organizations.\n"); + Console.error("Error listing organizations."); return 1; } if (result.response.statusCode === 401 && body && body.error === "invalid_credential") { - Console.stderr.write("You must be logged in to list your organizations.\n"); + Console.error("You must be logged in to list your organizations."); // XXX It would be nice to do a username/password prompt here like // we do for the other orgs commands. return 1; @@ -1641,14 +1667,14 @@ main.registerCommand({ if (result.response.statusCode !== 200 || ! body || ! body.organizations) { - Console.stderr.write("Error listing organizations.\n"); + Console.error("Error listing organizations."); return 1; } if (body.organizations.length === 0) { - Console.stdout.write("You are not a member of any organizations.\n"); + Console.info("You are not a member of any organizations."); } else { - Console.stdout.write(_.pluck(body.organizations, "name").join("\n") + "\n"); + Console.rawInfo(_.pluck(body.organizations, "name").join("\n") + "\n"); } return 0; }); @@ -1666,8 +1692,8 @@ main.registerCommand({ }, function (options) { if (options.add && options.remove) { - Console.stderr.write( - "Sorry, you can only add or remove one member at a time.\n"); + Console.error( + "Sorry, you can only add or remove one member at a time."); throw new main.ShowUsage; } @@ -1685,28 +1711,26 @@ main.registerCommand({ options.add ? "addOrganizationMember": "removeOrganizationMember", options.args[0], username); } catch (err) { - Console.stderr.write("Error " + - (options.add ? "adding" : "removing") + - " member: " + err.reason + "\n"); + Console.error("Error " + + (options.add ? "adding" : "removing") + + " member: " + err.reason); return 1; } - Console.stdout.write(username + " " + + Console.info(username + " " + (options.add ? "added to" : "removed from") + - " organization " + options.args[0] + ".\n"); + " organization " + options.args[0] + "."); } else { // Showing the members of an org try { var result = conn.call("showOrganization", options.args[0]); } catch (err) { - Console.stderr.write("Error showing organization: " + - err.reason + "\n"); + Console.error("Error showing organization: " + err.reason); return 1; } var members = _.pluck(result, "username"); - - Console.stdout.write(members.join("\n") + "\n"); + Console.rawInfo(members.join("\n") + "\n"); } return 0; @@ -1755,7 +1779,7 @@ main.registerCommand({ } catch (e) { if (!(e instanceof SyntaxError)) throw e; - Console.stderr.write("Bad regular expression: " + str + "\n"); + Console.error("Bad regular expression: " + str); return null; } }; @@ -1818,8 +1842,9 @@ main.registerCommand({ }, function (options) { auth.pollForRegistrationCompletion(); if (! auth.isLoggedIn()) { - Console.stderr.write( - "You must be logged in for that. Try 'meteor login'.\n"); + Console.error( + "You must be logged in for that. Try " + + Console.command("'meteor login'") + "."); return 1; } @@ -1898,7 +1923,7 @@ main.registerCommand({ 'key' : ret.sshKey, 'hostKey' : ret.hostKey }; - Console.info(JSON.stringify(retJson, null, 2)); + Console.rawInfo(JSON.stringify(retJson, null, 2) + "\n"); return 0; } @@ -1926,7 +1951,7 @@ main.registerCommand({ maybeVerbose]; var printOptions = connOptions.join(' '); - maybeLog("Connecting: ssh " + printOptions); + maybeLog("Connecting: " + Console.command("ssh " + printOptions)); var child_process = require('child_process'); var future = new Future; @@ -1978,10 +2003,10 @@ main.registerCommand({ return 'none'; }; - Console.stdout.write(p('email') + " " + p('port') + " " + p('changed') + - " " + p('args') + "\n"); + Console.info(p('email') + " " + p('port') + " " + p('changed') + + " " + p('args')); if (options.url) - Console.stdout.write('url\n'); + Console.info('url'); if (options['delete']) - Console.stdout.write('delete\n'); + Console.info('delete'); }); diff --git a/tools/console.js b/tools/console.js index 3f729e0588..c16e09ee7e 100644 --- a/tools/console.js +++ b/tools/console.js @@ -1,17 +1,17 @@ /// -/// utility functions for formatting output to the screen +/// A set of utility functions for formatting output sent to the screen. /// /// Console offers several pieces of functionality: -/// debug / info / warn messages: -/// Outputs to the screen, optionally with colors (when pretty == true) -/// 'legacy' functions: Console.stdout.write & Console.stderr.write -/// Make porting code a lot easier (just a regex from process -> Console) -/// Progress bar support -/// Displays a progress bar on the screen, but hides it around log messages -/// (The need to hide it is why we have this class) +/// - debug / info / warn messages: Output to the screen, optionally with +/// colors (when pretty == true). Wrap the output to the width of the user's +/// terminal, making sure to not split the same word over multiple +/// lines. (Also provides 'rawInfo', 'rawDebug' (etc) for when you DON'T want +/// to pre-process the output.) +/// - Progress bar support +/// Display a progress bar on the screen, but hide it around log messages. /// -/// In future, we might do things like move all support for verbose mode in here, -/// and also integrate the buildmessage functionality into here +/// In future, we might do things like move all support for verbose mode in +/// here, and also integrate the buildmessage functionality into here /// var _ = require('underscore'); @@ -24,6 +24,7 @@ var buildmessage = require('./buildmessage.js'); var chalk = require('chalk'); var cleanup = require('./cleanup.js'); var utils = require('./utils.js'); +var wordwrap = require('wordwrap'); var PROGRESS_DEBUG = !!process.env.METEOR_PROGRESS_DEBUG; var FORCE_PRETTY=undefined; @@ -31,11 +32,10 @@ if (process.env.METEOR_PRETTY_OUTPUT) { FORCE_PRETTY = process.env.METEOR_PRETTY_OUTPUT != '0'; } -if (!process.env.METEOR_COLOR) { +if (! process.env.METEOR_COLOR) { chalk.enabled = false; } - var STATUSLINE_MAX_LENGTH = 60; // XXX unused? var STATUS_MAX_LENGTH = 40; @@ -49,6 +49,18 @@ var STATUS_INTERVAL_MS = 500; // XXX: ? FALLBACK_STATUS = 'Pondering'; var FALLBACK_STATUS = ''; +// If there is a part of the larger text, and we really want to make sure that +// it doesn't get split up, we will replace the space with a utf character that +// we are not likely to use anywhere else. This one looks like the a BLACK SUN +// WITH RAYS. We intentionally want to NOT use a space-like character: it should +// be obvious that something has gone wrong if this ever gets printed. +var SPACE_REPLACEMENT = '\u2600'; +// In Javascript, replace only replaces the first occurance and this is the +// proposed alternative. +var replaceAll = function (str, search, replace) { + return str.split(search).join(replace); +}; + var spacesArray = new Array(200).join(' '); var spacesString = function (length) { if (length > spacesArray.length) { @@ -56,6 +68,8 @@ var spacesString = function (length) { } return spacesArray.substring(0, length); }; +var ARROW = "=> "; + var toFixedLength = function (text, length) { text = text || ""; @@ -72,7 +86,8 @@ var toFixedLength = function (text, length) { return text; }; -// No-op progress display, that means we don't have to handle the 'no progress display' case +// No-op progress display, that means we don't have to handle the 'no progress +// display' case var ProgressDisplayNone = function () { }; @@ -217,7 +232,7 @@ _.extend(ProgressBarRenderer.prototype, { .replace(':current', self.curr) .replace(':total', self.total) .replace(':elapsed', isNaN(elapsed) ? '0.0' : (elapsed / 1000).toFixed(1)) - .replace(':eta', (isNaN(eta) || !isFinite(eta)) ? '0.0' : (eta / 1000).toFixed(1)) + .replace(':eta', (isNaN(eta) || ! isFinite(eta)) ? '0.0' : (eta / 1000).toFixed(1)) .replace(':percent', percent.toFixed(0) + '%'); /* compute the available space (non-zero) for the bar */ @@ -312,7 +327,7 @@ _.extend(ProgressDisplayFull.prototype, { var streamColumns = this._stream.columns; var statusColumns; var progressColumns; - if (!streamColumns) { + if (! streamColumns) { statusColumns = STATUS_MAX_LENGTH; progressColumns = 0; } else { @@ -377,7 +392,7 @@ _.extend(StatusPoller.prototype, { } self._pollFiber = Fiber(function () { - while (!self._stop) { + while (! self._stop) { utils.sleepMs(100); self.statusPoll(); @@ -414,7 +429,7 @@ _.extend(StatusPoller.prototype, { } else { var fraction = state.done ? 1.0 : (state.current / state.end); - if (!isNaN(fraction) && fraction >= 0) { + if (! isNaN(fraction) && fraction >= 0) { progressDisplay.updateProgress(fraction, startTime); } else { progressDisplay.updateProgress(0, startTime); @@ -471,12 +486,6 @@ var Console = function (options) { // Legacy helpers self.stdout = {}; self.stderr = {}; - self.stdout.write = function (msg) { - self._legacyWrite(LEVEL_INFO, msg); - }; - self.stderr.write = function (msg) { - self._legacyWrite(LEVEL_WARN, msg); - }; self._stream = process.stdout; @@ -497,7 +506,6 @@ var Console = function (options) { }); }; - var LEVEL_CODE_ERROR = 4; var LEVEL_CODE_WARN = 3; var LEVEL_CODE_INFO = 2; @@ -508,6 +516,15 @@ var LEVEL_WARN = { code: LEVEL_CODE_WARN }; var LEVEL_INFO = { code: LEVEL_CODE_INFO }; var LEVEL_DEBUG = { code: LEVEL_CODE_DEBUG }; +// We use a special class to represent the options that we send to the Console +// because it allows us to call 'instance of' on the last argument of variadic +// functions. This allows us to keep the signature of our custom output +// functions (ex: info) roughly the same as the originals. +var ConsoleOptions = function (o) { + var self = this; + self.options = o; +} + _.extend(Console.prototype, { LEVEL_ERROR: LEVEL_ERROR, LEVEL_WARN: LEVEL_WARN, @@ -537,7 +554,7 @@ _.extend(Console.prototype, { self._pretty = self._progressDisplayEnabled = true; // Update the screen if anything changed. - if (!originalPretty || !originalProgressDisplayEnabled) + if (! originalPretty || ! originalProgressDisplayEnabled) self._updateProgressDisplay(); try { @@ -547,7 +564,7 @@ _.extend(Console.prototype, { self._pretty = originalPretty; self._progressDisplayEnabled = originalProgressDisplayEnabled; // Update the screen if anything changed. - if (!originalPretty || !originalProgressDisplayEnabled) + if (! originalPretty || ! originalProgressDisplayEnabled) self._updateProgressDisplay(); } }, @@ -557,6 +574,16 @@ _.extend(Console.prototype, { self.verbose = verbose; }, + // Get the current width of the Console. + width: function () { + var width = 80; + var stream = process.stdout; + if (stream && stream.isTTY && stream.columns) { + width = stream.columns; + } + return width; + }, + // This can be called during long lived operations; it will keep the spinner spinning. // (This code used to be in Patience.nudge) // @@ -583,6 +610,38 @@ _.extend(Console.prototype, { } }, + // Initializes and returns a new ConsoleOptions object. This allows us to call + // 'instance of' on the ConsoleOptions in parseVariadicInput, by ensuring that + // the object created with Console.options is, in fact, a new object. + options: function (o) { + return new ConsoleOptions(o); + }, + + // Deal with the arguments to a variadic print function that also takes an + // optional ConsoleOptions argument at the end. + // + // Returns an object with keys: + // - options: The options that were passed in, or an empty object. + // - message: Arguments to the original function, parsed as a string. + // + _parseVariadicInput: function (args) { + var self = this; + var msgArgs; + var options; + // If the last argument is an instance of ConsoleOptions, then we should + // separate it out, and only send the first N-1 arguments to be parsed as a + // message. + if (_.last(args) instanceof ConsoleOptions) { + msgArgs = _.initial(args); + options = _.last(args).options; + } else { + msgArgs = args; + options = {}; + } + var message = self._format(msgArgs); + return { message: message, options: options }; + }, + isLevelEnabled: function (levelCode) { return (this.verbose || this._logThreshold <= levelCode); }, @@ -591,9 +650,12 @@ _.extend(Console.prototype, { return this.isLevelEnabled(LEVEL_CODE_DEBUG); }, - debug: function(/*arguments*/) { + + // Don't pretty-fy this output by trying to, for example, line-wrap it. Just + // print it to the screen as it is. + rawDebug: function(/*arguments*/) { var self = this; - if (!self.isDebugEnabled()) { + if (! self.isDebugEnabled()) { return; } @@ -601,13 +663,32 @@ _.extend(Console.prototype, { self._print(LEVEL_DEBUG, message); }, + // By default, Console.debug automatically line wraps the output. + // + // Takes in an optional Console.options({}) argument at the end, with the + // following keys: + // - bulletPoint: start the first line with a given string, then offset the + // subsequent lines by the length of that string. See _wrap for more details. + // - indent: offset the entire string by a specific number of + // characters. See _wrap for more details. + // + debug: function(/*arguments*/) { + var self = this; + if (! self.isDebugEnabled()) { return; } + + var message = self._prettifyMessage(arguments); + self._print(LEVEL_DEBUG, message); + }, + isInfoEnabled: function () { return this.isLevelEnabled(LEVEL_CODE_INFO); }, - info: function(/*arguments*/) { + // Don't pretty-fy this output by trying to, for example, line-wrap it. Just + // print it to the screen as it is. + rawInfo: function(/*arguments*/) { var self = this; - if (!self.isInfoEnabled()) { + if (! self.isInfoEnabled()) { return; } @@ -615,13 +696,24 @@ _.extend(Console.prototype, { self._print(LEVEL_INFO, message); }, + // Generally, we want to process the output for legibility, for example, by + // wrapping it. For raw output (ex: stack traces, user logs, etc), use the + // rawInfo function. For more information about options, see: debug. + info: function(/*arguments*/) { + var self = this; + if (! self.isInfoEnabled()) { return; } + + var message = self._prettifyMessage(arguments); + self._print(LEVEL_INFO, message); + }, + isWarnEnabled: function () { return this.isLevelEnabled(LEVEL_CODE_WARN); }, - warn: function(/*arguments*/) { + rawWarn: function(/*arguments*/) { var self = this; - if (!self.isWarnEnabled()) { + if (! self.isWarnEnabled()) { return; } @@ -629,19 +721,45 @@ _.extend(Console.prototype, { self._print(LEVEL_WARN, message); }, - error: function(/*arguments*/) { + // Generally, we want to process the output for legibility, for example, by + // wrapping it. For raw output (ex: stack traces, user logs, etc), use the + // rawWarn function. For more information about options, see: debug. + warn: function(/* arguments */) { + var self = this; + if (! self.isWarnEnabled()) { return; } + + var message = self._prettifyMessage(arguments); + self._print(LEVEL_WARN, message); + }, + + rawError: function(/*arguments*/) { var self = this; var message = self._format(arguments); self._print(LEVEL_ERROR, message); }, - _legacyWrite: function (level, message) { + // Generally, we want to process the output for legibility, for example, by + // wrapping it. For raw output (ex: stack traces, user logs, etc), use the + // rawError function. For more information about options, see: debug. + error: function(/*arguments*/) { var self = this; - if(message.substr && message.substr(-1) == '\n') { - message = message.substr(0, message.length - 1); - } - self._print(level, message); + + var message = self._prettifyMessage(arguments); + self._print(LEVEL_ERROR, message); + }, + + _prettifyMessage: function (msgArguments) { + var self = this; + var parsedArgs = self._parseVariadicInput(msgArguments); + var wrapOpts = { + indent: parsedArgs.options.indent, + bulletPoint: parsedArgs.options.bulletPoint + }; + + var wrappedMessage = self._wrapText(parsedArgs.message, wrapOpts); + wrappedMessage += "\n"; + return wrappedMessage; }, _print: function(level, message) { @@ -679,69 +797,95 @@ _.extend(Console.prototype, { } if (style) { - dest.write(style(message + '\n')); + dest.write(style(message)); } else { - dest.write(message + '\n'); + dest.write(message); } - // XXX: Pause before showing the progress display, to prevent flicker/spewing messages + // XXX: Pause before showing the progress display, to prevent + // flicker/spewing messages // Repaint the progress display progressDisplay.repaint(); }, + // A wrapper around Console.info. Prints the message out in green (if pretty), + // with the CHECKMARK as the bullet point in front of it. success: function (message) { var self = this; - if (!self._pretty) { - return message; + if (! self._pretty) { + return self.info(message); } - return chalk.green('\u2713 ' + message); // CHECK MARK + var checkmark = chalk.green('\u2713'); // CHECKMARK + return self.info( + chalk.green(message), + self.options({ bulletPoint: checkmark + " "})); }, - fail: function (message) { + // Wrapper around Console.info. Prints the message out in red (if pretty) + // with the BALLOT X as the bullet point in front of it. + failInfo: function (message) { + var self = this; + return self._fail(message, "info"); + }, + + // Wrapper around Console.warn. Prints the message out in red (if pretty) + // with the ascii x as the bullet point in front of it. + failWarn: function (message) { + var self = this; + return self._fail(message, "warn"); + }, + + // Print the message in red (if pretty) with an x bullet point in front of it. + _fail: function (message, printFn) { var self = this; - if (!self._pretty) { - return message; + if (! self._pretty) { + return printFn(message); } - return chalk.red('\u2717 ' + message); // BALLOT X + + var xmark = chalk.red('\u2717'); + return self[printFn]( + chalk.red(message), + self.options({ bulletPoint: xmark + " " })); }, - command: function (message) { - return this.bold(message); - }, - - url: function (message) { - return this.underline(message); - }, - - underline: function (message) { + // Wrapper around Console.warn that prints a large "WARNING" label in front. + labelWarn: function (message) { var self = this; - - if (!self._pretty) { - return message; - } - return chalk.underline(message); + return self.warn(message, self.options({ bulletPoint: "WARNING: " })); }, - bold: function (message) { + // Wrappers around Console functions to prints an "=> " in front. Optional + // indent to indent the arrow. + arrowError: function (message, indent) { var self = this; - - if (!self._pretty) { - return message; - } - return chalk.bold(message); - }, - - _format: function (logArguments) { - return util.format.apply(util, logArguments); + return self._arrowPrint("error", message, indent); + }, + arrowWarn: function (message, indent) { + var self = this; + return self._arrowPrint("warn", message, indent); + }, + arrowInfo: function (message, indent) { + var self = this; + return self._arrowPrint("info", message, indent); + }, + _arrowPrint: function(printFn, message, indent) { + var self = this; + indent = indent || 0; + return self[printFn]( + message, + self.options({ bulletPoint: ARROW, indent: indent })); }, + // A wrapper around console.error. Given an error and some background + // information, print out the correct set of messages depending on verbose + // level, etc. printError: function (err, info) { var self = this; var message = err.message; - if (!message) { + if (! message) { message = "Unexpected error"; if (self.verbose) { message += " (" + err.toString() + ")"; @@ -754,18 +898,187 @@ _.extend(Console.prototype, { self.error(message); if (self.verbose && err.stack) { - self.info(err.stack); + self.rawInfo(err.stack + "\n"); } }, + // A wrapper to print out buildmessage errors. printMessages: function (messages) { var self = this; if (messages.hasMessages()) { - self._print(LEVEL_ERROR, "\n" + messages.formatMessages()); + self.error("\n" + messages.formatMessages()); } }, + // Wrap commands in this function -- it ensures that commands don't get line + // wrapped (ie: print 'meteor' at the end of the line, and 'create --example' + // at the beginning of the next one). + // + // To use, wrap commands that you send into print functions with this + // function, like so: Console.info(text + Console.command("meteor create + // --example leaderboard") + moretext). + // + // If pretty print is on, this will also bold the commands. + command: function (message) { + var self = this; + var unwrapped = self.noWrap(message); + return self.bold(unwrapped); + }, + + // Underline the URLs (if pretty print is on). + url: function (message) { + var self = this; + // If we are going to print URLs with spaces, we should turn spaces into + // things browsers understand. + var unspaced = + replaceAll(message, ' ', '%20'); + // There is no need to call noWrap here, since that only handles spaces (and + // we have done that). If it ever handles things other than spaces, we + // should make sure to call it here. + return self.underline(unspaced); + }, + + // Format a filepath to not wrap. This does NOT automatically escape spaces + // (ie: add a slash in front so the user could copy paste the file path into a + // terminal). + path: function (message) { + var self = this; + // Make sure that we don't wrap this. + var unwrapped = self.noWrap(message); + return self.bold(unwrapped); + }, + + // Do not wrap this substring when you send it into a non-raw print function. + // DO NOT print the result of this call with a raw function. + noWrap: function (message) { + var noBlanks = replaceAll(message, ' ', SPACE_REPLACEMENT); + return noBlanks; + }, + + // A wrapper around the underline functionality of chalk. + underline: function (message) { + var self = this; + + if (! self._pretty) { + return message; + } + return chalk.underline(message); + }, + + // A wrapper around the bold functionality of chalk. + bold: function (message) { + var self = this; + + if (! self._pretty) { + return message; + } + return chalk.bold(message); + }, + + // Prints a two column table in a nice format: + // The first column is printed entirely, the second only as space permits + printTwoColumns : function (rows, options) { + var self = this; + options = options || {}; + + var longest = ''; + _.each(rows, function (row) { + var col0 = row[0] || ''; + if (col0.length > longest.length) + longest = col0; + }); + + var pad = longest.replace(/./g, ' '); + var width = self.width(); + + var out = ''; + _.each(rows, function (row) { + var col0 = row[0] || ''; + var col1 = row[1] || ''; + var line = self.bold(col0) + pad.substr(col0.length); + line += " " + col1; + if (line.length > width) { + line = line.substr(0, width - 3) + '...'; + } + out += line + "\n"; + }); + + var level = options.level || self.LEVEL_INFO; + out += "\n"; + self._print(level, out); + + return out; + }, + + // Format logs according to the spec in utils. + _format: function (logArguments) { + return util.format.apply(util, logArguments); + }, + + // Wraps long strings to the length of user's terminal. Inserts linebreaks + // between words when nearing the end of the line. Returns the wrapped string + // and takes the following arguments: + // + // text: the text to wrap + // options: + + // - bulletPoint: start the first line with a given string, then offset the + // subsequent lines by the length of that string. For example, if the + // bulletpoint is " => ", we would get: + // " => some long message starts here + // and then continues here." + // - indent: offset the entire string by a specific number of + // characters. For example: + // " This entire message is indented + // by two characters." + // + // Passing in both options will offset the bulletPoint by the indentation, + // like so: + // " this message is indented by two." + // " => this mesage indented by two and + // and also starts with an arrow." + // + // When printing commands in-line, it is best to wrap commands in with Console.command + // to make sure that they don't get line-wrapped. See Console.command for more details. + _wrapText: function (text, options) { + var self = this; + options = options || {}; + + // Compute the maximum offset on the bulk of the message. + var maxIndent = 0; + if (options.indent && options.indent > 0) { + maxIndent = maxIndent + options.indent; + } + if (options.bulletPoint) { + maxIndent = maxIndent + options.bulletPoint.length; + } + + // Get the maximum width, or if we are not running in a terminal (self-test, + // for example), default to 80 columns. + var max = self.width(); + + // Wrap the text using the npm wordwrap library. + var wrappedText = wordwrap(maxIndent, max)(text); + + // Insert the start string, if applicable. + if (options.bulletPoint) { + // Save the initial indent level. + var initIndent = options.indent ? + wrappedText.substring(0, options.indent) : ""; + // Add together the initial indent (if any), the bullet point and the + // remainder of the message. + wrappedText = initIndent + options.bulletPoint + + wrappedText.substring(maxIndent); + } + + // If we have previously replaces any spaces, now is the time to bring them + // back. + wrappedText = replaceAll(wrappedText, SPACE_REPLACEMENT, ' '); + return wrappedText; + }, + + // Enables the progress bar, or disables it when called with (false) enableProgressDisplay: function (enabled) { var self = this; @@ -789,12 +1102,12 @@ _.extend(Console.prototype, { var newProgressDisplay; - if (!self._progressDisplayEnabled) { + if (! self._progressDisplayEnabled) { newProgressDisplay = new ProgressDisplayNone(); - } else if ((!self._stream.isTTY) || (!self._pretty)) { + } else if ((! self._stream.isTTY) || (! self._pretty)) { // No progress bar if not in pretty / on TTY. newProgressDisplay = new ProgressDisplayNone(self); - } else if (self._stream.isTTY && !self._stream.columns) { + } else if (self._stream.isTTY && ! self._stream.columns) { // We might be in a pseudo-TTY that doesn't support // clearLine() and cursorTo(...). // It's important that we only enter status message mode @@ -809,7 +1122,7 @@ _.extend(Console.prototype, { // Start/stop the status poller, so we never block exit if (self._progressDisplayEnabled) { - if (!self._statusPoller) { + if (! self._statusPoller) { self._statusPoller = new StatusPoller(self); } } else { @@ -834,8 +1147,6 @@ _.extend(Console.prototype, { } }); -Console.prototype.warning = Console.prototype.warn; - // options: // - echo (boolean): defaults to true // - prompt (string) diff --git a/tools/deploy-galaxy.js b/tools/deploy-galaxy.js index 173092d20a..e139a82918 100644 --- a/tools/deploy-galaxy.js +++ b/tools/deploy-galaxy.js @@ -13,6 +13,7 @@ var _ = require('underscore'); var buildmessage = require('./buildmessage.js'); var ServiceConnection = require('./service-connection.js'); var stats = require('./stats.js'); +var Console = require('./console.js').Console; // If 'error' is an exception that we know how to report in a // user-friendly way, print an approprite message to stderr and return @@ -27,9 +28,9 @@ var handleError = function (error, galaxyName, messages) { if (error.errorType === "Meteor.Error") { var msg = messages[error.error]; if (msg) - process.stderr.write(msg + "\n"); + Console.error(msg); else if (error.message) - process.stderr.write("Denied: " + error.message + "\n"); + Console.error("Denied: " + error.message); return 1; } else if (error.errorType === "DDP.ConnectionError") { // If we have an http/https URL for a galaxyName instead of a @@ -39,7 +40,7 @@ var handleError = function (error, galaxyName, messages) { if (m) galaxyName = m[1]; - process.stderr.write(galaxyName + ": connection failed\n"); + Console.error(galaxyName + ": connection failed"); return 1; } else { throw error; @@ -147,7 +148,7 @@ exports.deleteApp = function (app) { try { conn.call("destroyApp", app); - process.stdout.write("Deleted.\n"); + Console.info("Deleted."); } catch (e) { return handleError(e, galaxy); } finally { @@ -193,7 +194,7 @@ exports.deploy = function (options) { // concurrent with bundling. if (! options.starball && ! messages.hasMessages()) { - process.stdout.write('Deploying ' + options.app + '. Bundling...\n'); + Console.info('Deploying ' + options.app + '. Bundling...'); var bundleResult = bundler.bundle({ projectContext: options.projectContext, outputPath: bundlePath, @@ -230,12 +231,12 @@ exports.deploy = function (options) { } if (messages.hasMessages()) { - process.stdout.write("\nErrors prevented deploying:\n"); - process.stdout.write(messages.formatMessages()); + Console.info("\nErrors prevented deploying:"); + Console.info(messages.formatMessages()); return 1; } - process.stdout.write('Uploading...\n'); + Console.info('Uploading...'); var galaxy = exports.discoverGalaxy(options.app); conn = galaxyServiceConnection(galaxy, "ultraworld"); @@ -289,11 +290,11 @@ exports.deploy = function (options) { if (error || ((response.statusCode !== 200) && (response.statusCode !== 201))) { if (error && error.message) - process.stderr.write("Upload failed: " + error.message + "\n"); + Console.error("Upload failed: " + error.message); else - process.stderr.write("Upload failed" + - (response.statusCode ? - " (" + response.statusCode + ")\n" : "\n")); + Console.error("Upload failed" + + (response.statusCode ? + " (" + response.statusCode + ")" : "")); future['return'](false); } else future['return'](true); @@ -313,10 +314,10 @@ exports.deploy = function (options) { } if (created) - process.stderr.write(options.app + ": created app\n"); + Console.error(options.app + ": created app\n"); - process.stderr.write(options.app + ": " + - "pushed revision " + result.serial + "\n"); + Console.error(options.app + ": " + + "pushed revision " + result.serial); return 0; } finally { // Close the connection to Galaxy (otherwise Node will continue running). diff --git a/tools/deploy.js b/tools/deploy.js index 3696cb5bbd..3fc4c8013d 100644 --- a/tools/deploy.js +++ b/tools/deploy.js @@ -256,12 +256,15 @@ var authedRpc = function (options) { // password-protected app, instruct them to claim it with 'meteor // claim'. var printLegacyPasswordMessage = function (site) { - Console.stderr.write( -"\nThis site was deployed with an old version of Meteor that used\n" + -"site passwords instead of user accounts. Now we have a much better\n" + -"system, Meteor developer accounts.\n\n" + -"If this is your site, please claim it into your account with\n" + -" meteor claim " + site + "\n"); + Console.error( + "\nThis site was deployed with an old version of Meteor that used " + + "site passwords instead of user accounts. Now we have a much better " + + "system, Meteor developer accounts."); + Console.error(); + Console.error("If this is your site, please claim it into your account with"); + Console.error( + Console.command("meteor claim " + site), + Console.options({ indent: 2 })); }; // When the user is trying to do something with an app that they are not @@ -269,12 +272,16 @@ var printLegacyPasswordMessage = function (site) { // --add' or switch accounts. var printUnauthorizedMessage = function () { var username = auth.loggedInUsername(); - Console.stderr.write( -"Sorry, that site belongs to a different user.\n" + -(username ? "You are currently logged in as " + username + ".\n" : "") + -"\nEither have the site owner use 'meteor authorized --add' to add you\n" + -"as an authorized developer for the site, or switch to an authorized\n" + -"account with 'meteor login'.\n"); + Console.error("Sorry, that site belongs to a different user."); + if (username) { + Console.error("You are currently logged in as " + username + "."); + } + Console.error(); + Console.error( + "Either have the site owner use " + + Console.command("'meteor authorized --add'") + " to add you as an " + + "authorized developer for the site, or switch to an authorized account " + + "with " + Console.command("'meteor login'") + "."); }; // Take a proposed sitename for deploying to. If it looks @@ -290,10 +297,10 @@ var canonicalizeSite = function (site) { // characters (url.parse will do something very strange if a component is // larger than 63, which is the maximum legal length). if (site.length > 63) { - Console.stdout.write( -"The maximum hostname length currently supported is 63 characters.\n" + -site + " is too long.\n" + -"Please try again with a shorter URL for your site.\n"); + Console.error( + "The maximum hostname length currently supported is 63 characters: " + + site + " is too long. " + + "Please try again with a shorter URL for your site."); return false; } @@ -304,16 +311,16 @@ site + " is too long.\n" + var parsed = require('url').parse(url); if (! parsed.hostname) { - Console.stdout.write( -"Please specify a domain to connect to, such as www.example.com or\n" + -"http://www.example.com/\n"); + Console.info( + "Please specify a domain to connect to, such as www.example.com or " + + "http://www.example.com/"); return false; } if (parsed.pathname != '/' || parsed.hash || parsed.query) { - Console.stdout.write( -"Sorry, Meteor does not yet support specific path URLs, such as\n" + -"http://www.example.com/blog . Please specify the root of a domain.\n"); + Console.info( + "Sorry, Meteor does not yet support specific path URLs, such as " + + Console.url("http://www.example.com/blog") + " . Please specify the root of a domain."); return false; } @@ -364,14 +371,13 @@ var bundleAndDeploy = function (options) { }); if (preflight.errorMessage) { - Console.stderr.write("\nError deploying application: " + - preflight.errorMessage + "\n"); + Console.error("Error deploying application: " + preflight.errorMessage); return 1; } if (preflight.protection === "password") { printLegacyPasswordMessage(site); - Console.stderr.write("If it's not your site, please try a different name!\n"); + Console.error("If it's not your site, please try a different name!"); return 1; } else if (preflight.protection === "account" && @@ -383,7 +389,7 @@ var bundleAndDeploy = function (options) { var buildDir = options.projectContext.getProjectLocalDirectory('build_tar'); var bundlePath = path.join(buildDir, 'bundle'); - Console.stdout.write('Deploying to ' + site + '.\n'); + Console.info('Deploying to ' + site + '.'); var settings = null; var messages = buildmessage.capture({ @@ -408,8 +414,8 @@ var bundleAndDeploy = function (options) { } if (messages.hasMessages()) { - Console.stdout.write("\nErrors prevented deploying:\n"); - Console.stdout.write(messages.formatMessages()); + Console.info("\nErrors prevented deploying:"); + Console.info(messages.formatMessages()); return 1; } @@ -435,15 +441,14 @@ var bundleAndDeploy = function (options) { if (result.errorMessage) { - Console.stderr.write("\nError deploying application: " + - result.errorMessage + "\n"); + Console.error("\nError deploying application: " + result.errorMessage); return 1; } var deployedAt = require('url').parse(result.payload.url); var hostname = deployedAt.hostname; - Console.stdout.write('Now serving at http://' + hostname + '\n'); + Console.info('Now serving at http://' + hostname); files.rm_recursive(buildDir); if (! hostname.match(/meteor\.com$/)) { @@ -452,11 +457,12 @@ var bundleAndDeploy = function (options) { if (err || cnames[0] !== 'origin.meteor.com') { dns.resolve(hostname, 'A', function (err, addresses) { if (err || addresses[0] !== '107.22.210.133') { - Console.stdout.write('-------------\n'); - Console.stdout.write("You've deployed to a custom domain.\n"); - Console.stdout.write("Please be sure to CNAME your hostname to origin.meteor.com,\n"); - Console.stdout.write("or set an A record to 107.22.210.133.\n"); - Console.stdout.write('-------------\n'); + Console.info('-------------'); + Console.info( + "You've deployed to a custom domain.", + "Please be sure to CNAME your hostname", + "to origin.meteor.com, or set an A record to 107.22.210.133."); + Console.info('-------------'); } }); } @@ -479,12 +485,11 @@ var deleteApp = function (site) { }); if (result.errorMessage) { - Console.stderr.write("Couldn't delete application: " + - result.errorMessage + "\n"); + Console.error("Couldn't delete application: " + result.errorMessage); return 1; } - Console.stdout.write("Deleted.\n"); + Console.info("Deleted."); return 0; }; @@ -505,8 +510,7 @@ var checkAuthThenSendRpc = function (site, operation, what) { }); if (preflight.errorMessage) { - Console.stderr.write("Couldn't " + what + ": " + - preflight.errorMessage + "\n"); + Console.error("Couldn't " + what + ": " + preflight.errorMessage); return null; } @@ -530,15 +534,17 @@ var checkAuthThenSendRpc = function (site, operation, what) { } else { // Shouldn't ever get here because we set the retry flag on the // login, but just in case. - Console.stderr.write( -"\nYou must be logged in to " + what + " for this app. Use 'meteor login'\n" + -"to log in.\n\n" + -"If you don't have a Meteor developer account yet, you can quickly\n" + -"create one at www.meteor.com.\n"); + Console.error( + "\nYou must be logged in to " + what + " for this app. Use " + + Console.command("'meteor login'") + "to log in."); + Console.error(); + Console.error( + "If you don't have a Meteor developer account yet, you can quickly " + + "create one at www.meteor.com."); return null; } } else { // User is logged in but not authorized for this app - Console.stderr.write("\n"); + Console.error(); printUnauthorizedMessage(); return null; } @@ -554,8 +560,7 @@ var checkAuthThenSendRpc = function (site, operation, what) { }); if (result.errorMessage) { - Console.stderr.write("Couldn't " + what + ": " + - result.errorMessage + "\n"); + Console.error("Couldn't " + what + ": " + result.errorMessage); return null; } @@ -590,7 +595,7 @@ var logs = function (site) { if (result === null) { return 1; } else { - Console.stdout.write(result.message); + Console.info(result.message); auth.maybePrintRegistrationLink({ leadingNewline: true }); return 0; } @@ -607,33 +612,35 @@ var listAuthorized = function (site) { expectPayload: [] }); if (result.errorMessage) { - Console.stderr.write("Couldn't get authorized users list: " + - result.errorMessage + "\n"); + Console.error("Couldn't get authorized users list: " + result.errorMessage); return 1; } var info = result.payload; if (! _.has(info, 'protection')) { - Console.stdout.write("\n"); + Console.info(""); return 0; } if (info.protection === "password") { - Console.stdout.write("\n"); + Console.info(""); return 0; } if (info.protection === "account") { if (! _.has(info, 'authorized')) { - Console.stderr.write("Couldn't get authorized users list: " + - "You are not authorized\n"); + Console.error("Couldn't get authorized users list: " + + "You are not authorized"); return 1; } - Console.stdout.write((auth.loggedInUsername() || "") + "\n"); + Console.info((auth.loggedInUsername() || "")); _.each(info.authorized, function (username) { if (username) - Console.stdout.write(username + "\n"); + // Current username rules don't let you register anything that we might + // want to split over multiple lines (ex: containing a space), but we + // don't want confusion if we ever change some implementation detail. + Console.rawInfo(username + "\n"); }); return 0; } @@ -655,14 +662,13 @@ var changeAuthorized = function (site, action, username) { }); if (result.errorMessage) { - Console.stderr.write("Couldn't change authorized users: " + - result.errorMessage + "\n"); + Console.error("Couldn't change authorized users: " + result.errorMessage); return 1; } - Console.stdout.write(site + ": " + - (action === "add" ? "added " : "removed ") - + username + "\n"); + Console.info(site + ": " + + (action === "add" ? "added " : "removed ") + + username); return 0; }; @@ -679,27 +685,28 @@ var claim = function (site) { operation: 'info', site: site }); - if (infoResult.statusCode === 404) { - Console.stderr.write( -"There isn't a site deployed at that address. Use 'meteor deploy' if\n" + -"you'd like to deploy your app here.\n"); + Console.error( + "There isn't a site deployed at that address. Use " + + Console.command("'meteor deploy'") + " " + + "if you'd like to deploy your app here."); return 1; } if (infoResult.payload && infoResult.payload.protection === "account") { if (infoResult.payload.authorized) - Console.stderr.write("That site already belongs to you.\n"); + Console.error("That site already belongs to you.\n"); else - Console.stderr.write("Sorry, that site belongs to someone else.\n"); + Console.error("Sorry, that site belongs to someone else.\n"); return 1; } if (infoResult.payload && infoResult.payload.protection === "password") { - Console.stdout.write( -"To claim this site and transfer it to your account, enter the\n" + -"site password one last time.\n\n"); + Console.info( + "To claim this site and transfer it to your account, enter the", + "site password one last time."); + Console.info(); } var result = authedRpc({ @@ -713,29 +720,34 @@ var claim = function (site) { auth.pollForRegistrationCompletion(); if (! auth.loggedInUsername() && auth.registrationUrl()) { - Console.stderr.write( -"You need to set a password on your Meteor developer account before\n" + -"you can claim sites. You can do that here in under a minute:\n\n" + -auth.registrationUrl() + "\n\n"); + Console.error( + "You need to set a password on your Meteor developer account before", + "you can claim sites. You can do that here in under a minute:"); + Console.error(Console.url(auth.registrationUrl())); + Console.error(); } else { - Console.stderr.write("Couldn't claim site: " + - result.errorMessage + "\n"); + Console.error("Couldn't claim site: " + result.errorMessage); } return 1; } - Console.stdout.write( -site + ": " + "successfully transferred to your account.\n" + -"\n" + -"Show authorized users with:\n" + -" meteor authorized " + site + "\n" + -"\n" + -"Add authorized users with:\n" + -" meteor authorized " + site + " --add \n" + -"\n" + -"Remove authorized users with:\n" + -" meteor authorized " + site + " --remove \n" + -"\n"); + Console.info(site + ": " + "successfully transferred to your account."); + Console.info(); + Console.info("Show authorized users with:"); + Console.info( + Console.command("meteor authorized " + site), + Console.options({ indent: 2 })); + Console.info(); + Console.info("Add authorized users with:"); + Console.info( + Console.command("meteor authorized " + site + " --add "), + Console.options({ indent: 2 })); + Console.info(); + Console.info("Remove authorized users with:"); + Console.info( + Console.command("meteor authorized " + site + " --remove "), + Console.options({ indent: 2 })); + Console.info(); return 0; }; @@ -748,19 +760,18 @@ var listSites = function () { }); if (result.errorMessage) { - Console.stderr.write("Couldn't list sites: " + - result.errorMessage + "\n"); + Console.error("Couldn't list sites: " + result.errorMessage); return 1; } if (! result.payload || ! result.payload.sites || ! result.payload.sites.length) { - Console.stdout.write("You don't have any sites yet.\n"); + Console.info("You don't have any sites yet."); } else { result.payload.sites.sort(); _.each(result.payload.sites, function (site) { - Console.stdout.write(site + "\n"); + Console.info(site); }); } return 0; diff --git a/tools/isopack.js b/tools/isopack.js index 01ce87f1a2..0448a840f0 100644 --- a/tools/isopack.js +++ b/tools/isopack.js @@ -14,6 +14,7 @@ var isopackets = require("./isopackets.js"); var isopackCacheModule = require('./isopack-cache.js'); var packageMapModule = require('./package-map.js'); var Future = require('fibers/future'); +var Console = require('./console.js').Console; var rejectBadPath = function (p) { if (p.match(/\.\./)) @@ -1090,8 +1091,8 @@ _.extend(Isopack.prototype, { // and similar to a isopacket load failure, it can just crash the app // instead of being handled nicely. if (messages.hasMessages()) { - process.stderr.write("Errors prevented tool build:\n"); - process.stderr.write(messages.formatMessages()); + Console.error("Errors prevented tool build:"); + Console.error(messages.formatMessages()); throw new Error("tool build failed?"); } diff --git a/tools/isopackets.js b/tools/isopackets.js index 1d24792573..b6160ee37f 100644 --- a/tools/isopackets.js +++ b/tools/isopackets.js @@ -218,7 +218,7 @@ var newIsopacketBuildingCatalog = function () { }); }); if (messages.hasMessages()) { - Console.error("=> Errors while scanning core packages:"); + Console.arrowError("Errors while scanning core packages:"); Console.printMessages(messages); throw new Error("isopacket scan failed?"); } diff --git a/tools/main.js b/tools/main.js index a65f008f23..f2dc91ec84 100644 --- a/tools/main.js +++ b/tools/main.js @@ -401,13 +401,13 @@ var springboard = function (rel, options) { // to! That's bad. Let's exit. if (options.fromApp) { Console.error( -"Sorry, this project uses " + rel.getDisplayName() + ", which is not\n" + -"installed and could not be downloaded. Please check to make sure that you\n" + -"are online."); + "Sorry, this project uses " + rel.getDisplayName() + ", which is not", + "installed and could not be downloaded. Please check to make sure", + "that you are online."); } else { Console.error( -"Sorry, " + rel.getDisplayName() + " is not installed and could not be\n" + -"downloaded. Please check to make sure that you are online."); + "Sorry, " + rel.getDisplayName() + " is not installed and could not", + "be downloaded. Please check to make sure that you are online."); } process.exit(1); } @@ -712,8 +712,8 @@ Fiber(function () { if (_.has(rawOptions, '--release')) { if (rawOptions['--release'].length > 1) { Console.error( -"--release should only be passed once.\n" + -"Try 'meteor help' for help."); + "--release should only be passed once. " + + "Try 'meteor help' for help."); process.exit(1); } releaseOverride = rawOptions['--release'][0]; @@ -721,8 +721,8 @@ Fiber(function () { releaseExplicit = true; if (! releaseOverride) { Console.error( -"The --release option needs a value.\n" + -"Try 'meteor help' for help."); + "The --release option needs a value. " + + "Try 'meteor help' for help."); process.exit(1); } delete rawOptions['--release']; @@ -748,19 +748,21 @@ Fiber(function () { // shouldn't happen unless the user did it manually. if (appReleaseFile.noReleaseSpecified()) { Console.error( -"Problem! This project has a .meteor/release file which is empty.\n" + -"The file should either contain the release of Meteor that you want to use,\n" + -"or the word 'none' if you will only use the project with unreleased\n" + -"checkouts of Meteor. Please edit the .meteor/release file in the project\n" + -"and change it to a valid Meteor release or 'none'."); + "Problem! This project has a .meteor/release file which is empty.", + "The file should either contain the release of Meteor that you want", + "to use, or the word 'none' if you will only use the project with", + "unreleased checkouts of Meteor. Please edit the .meteor/release", + "file in the project and change it to a valid Meteor release or", + "'none'."); process.exit(1); } else if (appReleaseFile.fileMissing()) { Console.error( -"Problem! This project does not have a .meteor/release file.\n" + -"The file should either contain the release of Meteor that you want to use,\n" + -"or the word 'none' if you will only use the project with unreleased\n" + -"checkouts of Meteor. Please edit the .meteor/release file in the project\n" + -"and change it to a valid Meteor release or 'none'."); + "Problem! This project does not have a .meteor/release file.", + "The file should either contain the release of Meteor that you", + "want to use, or the word 'none' if you will only use the project", + "with unreleased checkouts of Meteor. Please edit the", + ".meteor/release file in the project and change it to a valid Meteor", + "release or 'none'."); process.exit(1); } } @@ -805,11 +807,12 @@ Fiber(function () { if (!releaseName) { if (catalog.refreshFailed) { Console.error( -"The package catalog has no information about any Meteor releases, and we\n" + -"had trouble connecting to the package server."); + "The package catalog has no information about any Meteor", + "releases, and we had trouble connecting to the package server."); } else { Console.error( -"The package catalog has no information about any Meteor releases."); + "The package catalog has no information about", + "any Meteor releases."); } process.exit(1); } @@ -883,7 +886,8 @@ Fiber(function () { } else if (e instanceof files.OfflineError) { if (!catalog.refreshFailed) { // Warn if we didn't already warn. - Console.warn("Unable to contact release server (are you offline?)"); + Console.warn( + "Unable to contact release server (are you offline?)"); } // Treat this like a failure to refresh the catalog // (map the old world to the new world) @@ -918,17 +922,18 @@ Fiber(function () { } if (catalog.refreshFailed) { Console.error( -"This project says that it uses " + displayRelease + ", but\n" + -"you don't have that version of Meteor installed, and we were unable to\n" + -"contact Meteor's update servers to find out about it. Please edit the\n" + -".meteor/release file in the project and change it to a valid Meteor\n" + -"release, or go online."); + "This project says that it uses " + displayRelease + ", but", + "you don't have that version of Meteor installed, and we were", + "unable to contact Meteor's update servers to find out about it.", + "Please edit the .meteor/release file in the project and change", + "it to a valid Meteor release, or go online."); } else { Console.error( -"This project says that it uses " + displayRelease + ", but you don't have\n" + -"that version of Meteor installed and the Meteor update servers\n" + -"don't have it either. Please edit the .meteor/release file in\n" + -"the project and change it to a valid Meteor release."); + "This project says that it uses " + displayRelease + ", but you", + "don't have that version of Meteor installed and the Meteor", + "update servers don't have it either. Please edit the", + ".meteor/release file in the project and change it to a valid", + "Meteor release."); } } else { throw new Error("can't load latest release?"); @@ -971,12 +976,12 @@ Fiber(function () { if (rawOptions[fullName]) { if (rawOptions[fullName].length > 1) { Console.error("It doesn't make sense to pass " + - fullName + " more than once."); + fullName + " more than once."); process.exit(1); } if (_.size(rawOptions) > 1 || rawArgs.length !== 0 || command) { Console.error("Can't pass anything else along with " + - value.name + "."); + value.name + "."); process.exit(1); } command = value; @@ -1017,7 +1022,9 @@ Fiber(function () { if (! _.has(walk, word)) { Console.error( -"'" + commandName + "' is not a Meteor command. See 'meteor --help'."); + Console.command("'" + commandName + "'") + + " is not a Meteor command. See " + + Console.command("'meteor --help'")+ "."); process.exit(1); } @@ -1036,7 +1043,8 @@ Fiber(function () { // They typed something like 'meteor admin' (when they were // supposed to type 'meteor admin grant' or something). Console.error( -"Try 'meteor " + commandName + " help' for available commands."); + "Try " + Console.command("'meteor " + commandName + " help'") + " " + + "for available commands."); process.exit(1); } @@ -1048,7 +1056,9 @@ Fiber(function () { // which case showHelp will be true and command will be null if (showHelp) { - Console.stdout.write(longHelp(commandName) + "\n"); + // XXX: Until we rewrite the longHelp function to cope with the new output + // format, let's go with the static, painstakingly-formatted version. + Console.rawInfo(longHelp(commandName) + "\n"); process.exit(0); } @@ -1061,13 +1071,16 @@ Fiber(function () { var presentLong = _.has(rawOptions, "--" + optionName); var presentShort = _.has(optionInfo, 'short') && _.has(rawOptions, "-" + optionInfo.short); + var tryHelpMessage = + "Try " + Console.command("'meteor help " + commandName + "'") + " " + + "for help."; + if (presentShort && presentLong) { // this would get caught below, but give a clearer error message Console.error( -commandName + ": can't pass both -" + optionInfo.short + " and --" + - optionName + ".\n" + -"Try 'meteor help " + commandName + "' for help."); + commandName + ": can't pass both -" + optionInfo.short + " and --" + + optionName + ". " + tryHelpMessage); process.exit(1); } var helpfulOptionName = "--" + optionName + @@ -1086,8 +1099,10 @@ commandName + ": can't pass both -" + optionInfo.short + " and --" + // in the future, we could support multiple values, but we don't // for now since no command needs it Console.error( -commandName + ": can only take one " + helpfulOptionName + " option.\n" + -"Try 'meteor help " + commandName + "' for help."); + Console.command(commandName) + ": can only take one " + + Console.command(helpfulOptionName) + " option."); + Console.error(tryHelpMessage); + process.exit(1); } else if (values.length === 1) { // OK, they provided exactly one value. Check its type and add @@ -1097,22 +1112,27 @@ commandName + ": can only take one " + helpfulOptionName + " option.\n" + // This option requires a value and they didn't give it one // (it was the last word on the command line). Console.error( -commandName + ": the " + helpfulOptionName + " option needs a value.\n" + -"Try 'meteor help " + commandName + "' for help."); + Console.command(commandName) + ": the " + + Console.command(helpfulOptionName) + " option needs a value."); + Console.error(tryHelpMessage); + process.exit(1); } else if (optionInfo.type === Number) { if (! value.match(/^[0-9]+$/)) { Console.error( -commandName + ": " + helpfulOptionName + " must be a number.\n" + -"Try 'meteor help " + commandName + "' for help."); + Console.command(commandName) + ": " + + Console.command(helpfulOptionName) + " must be a number."); + Console.error(tryHelpMessage); process.exit(1); } value = parseInt(value); } else if (optionInfo.type === Boolean) { if (!value) { Console.error( -commandName + ": the " + helpfulOptionName + " option does not need a value.\n" + -"Try 'meteor help " + commandName + "' for help."); + Console.command(commandName) + ": the " + + Console.command(helpfulOptionName) + " " + + "option does not need a value."); + Console.error(tryHelpMessage); process.exit(1); } value = true; @@ -1137,8 +1157,9 @@ commandName + ": the " + helpfulOptionName + " option does not need a value.\n" options[optionName] = optionInfo.default; } else if (optionInfo.required) { Console.error( -commandName + ": the --" + optionName + " option is required.\n" + -longHelp(commandName)); + Console.command(commandName) + ": the --" + + Console.command(optionName) + " option is required."); + Console.rawError(longHelp(commandName)); process.exit(1); } } @@ -1147,23 +1168,26 @@ longHelp(commandName)); // Check for unrecognized options. if (_.keys(rawOptions).length > 0) { Console.error( -_.keys(rawOptions)[0] + ": unknown option.\n" + -longHelp(commandName)); + Console.command(_.keys(rawOptions)[0]) + ": unknown option."); + Console.rawError( + longHelp(commandName)); process.exit(1); } // Check argument count. if (options.args.length < command.minArgs) { Console.error( -commandName + ": not enough arguments.\n" + -longHelp(commandName)); + Console.command(commandName) + ": not enough arguments."); + Console.rawError( + longHelp(commandName)); process.exit(1); } if (options.args.length > command.maxArgs) { Console.error( -commandName + ": too many arguments.\n" + -longHelp(commandName)); + Console.command(commandName) + ": too many arguments."); + Console.rawError( + longHelp(commandName)); process.exit(1); } @@ -1183,14 +1207,20 @@ longHelp(commandName)); // since you'll default to the 'run' command which requires an // app. Be welcoming to our new developers! Console.error( -commandName + ": You're not in a Meteor project directory.\n" + -"\n" + -"To create a new Meteor project:\n" + -" meteor create \n" + -"For example:\n" + -" meteor create myapp\n" + -"\n" + -"For more help, see 'meteor --help'."); + Console.command(commandName) + + ": You're not in a Meteor project directory."); + Console.error(); + Console.error("To create a new Meteor project:"); + Console.error( + Console.command("meteor create "), + Console.options({ indent: 2 })); + Console.error("For example:"); + Console.error( + Console.command("meteor create myapp"), + Console.options({ indent: 2 })); + Console.error(); + Console.error( + "For more help, see " + Console.command("'meteor --help'") + "."); process.exit(1); } @@ -1211,18 +1241,20 @@ commandName + ": You're not in a Meteor project directory.\n" + if (! options.packageDir) { Console.error( - commandName + ": You're not in a Meteor package directory."); + Console.command(commandName) + + ": You're not in a Meteor package directory."); process.exit(1); } } if (command.requiresRelease && ! release.current) { Console.error( -"You must specify a Meteor version with --release when you work with this\n" + -"project. It was created from an unreleased Meteor checkout and doesn't\n" + -"have a version associated with it.\n" + -"\n" + -"You can permanently set a release for this project with 'meteor update'."); + "You must specify a Meteor version with --release when you work with", + "this project. It was created from an unreleased Meteor checkout and", + "doesn't have a version associated with it."); + Console.error( + "You can permanently set a release for this project with " + + Console.command("'meteor update'") + "."); process.exit(1); } @@ -1230,9 +1262,9 @@ commandName + ": You're not in a Meteor project directory.\n" + appReleaseFile && ! appReleaseFile.isCheckout()) { // For commands that work with apps, if we have overridden the // app's usual release by using a checkout, print a reminder banner. - Console.warn( -"=> Running Meteor from a checkout -- overrides project version (" + - appReleaseFile.displayReleaseName + ")"); + Console.arrowWarn( + "Running Meteor from a checkout -- overrides project version " + + Console.noWrap("(" + appReleaseFile.displayReleaseName + ")")); } // Now that we're ready to start executing the command, if we are in @@ -1264,7 +1296,7 @@ commandName + ": You're not in a Meteor project directory.\n" + throw new Error( "you meant 'throw new main.Foo', not 'throw main.Foo'"); } else if (e instanceof main.ShowUsage) { - Console.error(longHelp(commandName)); + Console.rawError(longHelp(commandName) + "\n"); process.exit(1); } else if (e instanceof main.SpringboardToLatestRelease) { // Load the metadata for the latest release (or at least, the latest diff --git a/tools/processes.js b/tools/processes.js index 8df08c5d58..0dfb976a53 100644 --- a/tools/processes.js +++ b/tools/processes.js @@ -69,7 +69,7 @@ _.extend(RunCommand.prototype, { self.process.stdout.on('data', function (data) { self.stdout = self.stdout + data; if (self.options.pipeOutput) { - Console.stdout.write(data); + Console.rawInfo(data); } if (self.options.onStdout) { self.options.onStdout(data); @@ -79,7 +79,7 @@ _.extend(RunCommand.prototype, { self.process.stderr.on('data', function (data) { self.stderr = self.stderr + data; if (self.options.pipeOutput) { - Console.stderr.write(data); + Console.rawError(data); } if (self.options.onStderr) { self.options.onStderr(data); @@ -121,4 +121,3 @@ _.extend(RunCommand.prototype, { }); exports.RunCommand = RunCommand; - diff --git a/tools/run-all.js b/tools/run-all.js index 3070917712..340a49fdc1 100644 --- a/tools/run-all.js +++ b/tools/run-all.js @@ -129,7 +129,7 @@ _.extend(Runner.prototype, { // print the banner only once we've successfully bound the port if (! self.quiet && ! self.stopped) { runLog.log("[[[[[ " + self.banner + " ]]]]]\n"); - runLog.log("=> Started proxy."); + runLog.log("Started proxy.", { arrow: true }); } self._startMongoAsync(); @@ -142,7 +142,7 @@ _.extend(Runner.prototype, { if (! self.stopped && self.httpProxy) { self.httpProxy.start(); if (! self.quiet) { - runLog.log("=> Started http proxy."); + runLog.log("Started http proxy.", { arrow: true }); } } @@ -153,7 +153,7 @@ _.extend(Runner.prototype, { extraRunner.start(); }); if (! self.quiet && ! self.stopped) - runLog.log("=> Started " + title + "."); + runLog.log("Started " + title + ".", { arrow: true }); } }); @@ -162,18 +162,20 @@ _.extend(Runner.prototype, { self.appRunner.start(); }); if (! self.quiet && ! self.stopped) - runLog.log("=> Started your app."); + runLog.log("Started your app.", { arrow: true }); } - if (! self.stopped && ! self.quiet) - runLog.log("\n=> App running at: " + self.rootUrl); + if (! self.stopped && ! self.quiet) { + runLog.log(""); + runLog.log("App running at: " + self.rootUrl, { arrow: true }); + } if (self.selenium && ! self.stopped) { buildmessage.enterJob({ title: "Starting Selenium" }, function () { self.selenium.start(); }); if (! self.quiet && ! self.stopped) - runLog.log("=> Started Selenium."); + runLog.log("Started Selenium.", { arrow: true }); } // XXX It'd be nice to (cosmetically) handle failure better. Right @@ -191,7 +193,7 @@ _.extend(Runner.prototype, { _startMongoFuture: function () { this.mongoRunner.start(); if (! this.stopped && ! this.quiet) { - runLog.log("=> Started MongoDB."); + runLog.log("Started MongoDB.", { arrow: true }); } }.future(), @@ -321,25 +323,24 @@ exports.run = function (options) { runner.stop(); if (result.outcome === "conflicting-versions") { - process.stderr.write( -"The constraint solver could not find a set of package versions to use that would\n" + -"satisfy the constraints of .meteor/versions and .meteor/packages. This could be\n" + -"caused by conflicts in .meteor/versions, conflicts in .meteor/packages, and/or\n" + -"inconsistent changes to the dependencies in local packages."); + Console.error( + "The constraint solver could not find a set of package versions to", + "use that would satisfy the constraints of .meteor/versions and", + ".meteor/packages. This could be caused by conflicts in", + ".meteor/versions, conflicts in .meteor/packages, and/or", + "inconsistent changes to the dependencies in local packages."); return 254; } if (result.outcome === "outdated-cordova-plugins") { - process.stderr.write( -"Your app's Cordova plugins have changed.\n" + -"Restart meteor to use the new set of plugins.\n"); + Console.error("Your app's Cordova plugins have changed."); + Console.error("Restart meteor to use the new set of plugins."); return 254; } if (result.outcome === "outdated-cordova-platforms") { - process.stderr.write( -"Your app's platforms have changed.\n" + -"Restart meteor to use the new set of platforms.\n"); + Console.error("Your app's platforms have changed."); + Console.error("Restart meteor to use the new set of platforms."); return 254; } @@ -357,10 +358,9 @@ exports.run = function (options) { // this (which prevents weird errors) is a start.) var from = release.current.getDisplayName(); var to = result.displayReleaseNeeded; - process.stderr.write( -"Your app has been updated to " + to + " from " + from + -".\n" + -"Restart meteor to use the new release.\n"); + Console.error( + "Your app has been updated to " + to + " from " + from + ".", + "Restart meteor to use the new release."); return 254; } @@ -373,14 +373,14 @@ exports.run = function (options) { } if (once && result.outcome === "bundle-fail") { - process.stderr.write("=> Build failed:\n\n" + - result.errors.formatMessages() + "\n"); + Console.arrowError("Build failed:\n\n" + + result.errors.formatMessages()); return 254; } if (once && result.outcome === "terminated") { if (result.signal) { - process.stderr.write("Killed (" + result.signal + ")\n"); + Console.error("Killed (" + result.signal + ")"); return 255; } else if (typeof result.code === "number") { // We used to print 'Your application is exiting' here, but that diff --git a/tools/run-app.js b/tools/run-app.js index 8fdf52aca3..a9280b78b2 100644 --- a/tools/run-app.js +++ b/tools/run-app.js @@ -112,7 +112,7 @@ _.extend(AppProcess.prototype, { })); self.proc.on('error', fiberHelpers.inBareFiber(function (err) { - runLog.log("=> Couldn't spawn process: " + err.message); + runLog.log("Couldn't spawn process: " + err.message, { arrow: true }); // node docs say that it might make both an 'error' and a // 'close' callback, so we use a guard to make sure we only call @@ -770,11 +770,11 @@ _.extend(AppRunner.prototype, { } else if (runResult.outcome === "bundle-fail") { - runLog.log("=> Errors prevented startup:\n\n" + - runResult.errors.formatMessages()); + runLog.log("Errors prevented startup:\n\n" + + runResult.errors.formatMessages(), { arrow: true }); if (self.watchForChanges) { - runLog.log("=> Your application has errors. " + - "Waiting for file change."); + runLog.log("Your application has errors. " + + "Waiting for file change.", { arrow: true }); Console.enableProgressDisplay(false); } } @@ -784,9 +784,9 @@ _.extend(AppRunner.prototype, { else if (runResult.outcome === "terminated") { if (runResult.signal) { - runLog.log('=> Exited from signal: ' + runResult.signal); + runLog.log('Exited from signal: ' + runResult.signal, { arrow: true }); } else if (runResult.code !== undefined) { - runLog.log('=> Exited with code: ' + runResult.code); + runLog.log('Exited with code: ' + runResult.code, { arrow: true }); } else { // explanation should already have been logged } @@ -796,8 +796,9 @@ _.extend(AppRunner.prototype, { continue; if (self.watchForChanges) { - runLog.log("=> Your application is crashing. " + - "Waiting for file change."); + runLog.log("Your application is crashing. " + + "Waiting for file change.", + { arrow: true }); Console.enableProgressDisplay(false); } } @@ -824,7 +825,7 @@ _.extend(AppRunner.prototype, { // While we were waiting, did somebody stop() us? if (self.exitFuture) break; - runLog.log("=> Modified -- restarting."); + runLog.log("Modified -- restarting.", { arrow: true }); Console.enableProgressDisplay(true); continue; } diff --git a/tools/run-log.js b/tools/run-log.js index edd4ca5d72..8728d20ca2 100644 --- a/tools/run-log.js +++ b/tools/run-log.js @@ -62,12 +62,12 @@ _.extend(RunLog.prototype, { if (self.consecutiveRestartMessages) { self.consecutiveRestartMessages = null; - Console.stdout.write("\n"); + Console.info(); } if (self.consecutiveClientRestartMessages) { self.consecutiveClientRestartMessages = null; - Console.stdout.write("\n"); + Console.info(); } if (self.temporaryMessageLength) { @@ -93,16 +93,20 @@ _.extend(RunLog.prototype, { self._clearSpecial(); if (self.rawLogs) - Console[isStderr ? "stderr" : "stdout"].write(line + "\n"); + Console[isStderr ? "rawError" : "rawInfo"](line + "\n"); else - Console.stdout.write(Log.format(obj, { color: true }) + "\n"); + Console.rawInfo(Log.format(obj, { color: true }) + "\n"); // XXX deal with test server logging differently?! }, - log: function (msg) { + // Log the message. + // msg: message + // options: + // - arrow: if true, preface with => and wrap accordingly. + log: function (msg, options) { var self = this; - + options = options || {}; var obj = { time: new Date, message: msg @@ -113,7 +117,11 @@ _.extend(RunLog.prototype, { self._record(obj); self._clearSpecial(); - Console.stdout.write(msg + "\n"); + + // Process the options. By default, we want to wordwrap the message with + // Console.info. If we ask for raw output, then we don't want to do that. If + // we ask for an arrow, we want to wrap around with => as the bulletPoint. + Console[options.arrow ? 'arrowInfo' : 'info'](msg); }, // Write a message to the terminal that will get overwritten by the diff --git a/tools/run-velocity.js b/tools/run-velocity.js index ee30700ee8..e7a9ba489e 100644 --- a/tools/run-velocity.js +++ b/tools/run-velocity.js @@ -30,8 +30,8 @@ var runVelocity = function (url) { ddpConnection.subscribe("VelocityTestReports", { onError: function () { - Console.stderr.write("failed to subscribe to VelocityTestReports " - + "subscription"); + Console.error("failed to subscribe to VelocityTestReports " + + "subscription"); // XXX tell user to add velocity:core // XXX these also fire if the user turns on autopublish }, onReady: function () { @@ -65,8 +65,8 @@ var runVelocity = function (url) { var isFinished = false; ddpConnection.subscribe("VelocityAggregateReports", { onError: function () { - Console.stderr.write("failed to subscribe to " + - "VelocityAggregateReports subscription"); + Console.error("failed to subscribe to " + + "VelocityAggregateReports subscription"); }, onReady: function () { this.connection.registerStore("velocityAggregateReports", { update: function (msg) { @@ -113,8 +113,8 @@ var runVelocity = function (url) { ddpConnection.subscribe("VelocityMirrors", { onError: function (err) { - Console.stderr.write("failed to subscribe to VelocityMirrors " + - "subscription", err); + Console.error("failed to subscribe to VelocityMirrors " + + "subscription", err); }, onReady: function () { this.connection.registerStore("velocityMirrors", { update: function (msg) { diff --git a/tools/selftest.js b/tools/selftest.js index 33718c4c10..f0969a6ef6 100644 --- a/tools/selftest.js +++ b/tools/selftest.js @@ -171,7 +171,7 @@ var newSelfTestCatalog = function () { }); }); if (messages.hasMessages()) { - Console.error("=> Errors while scanning core packages:"); + Console.arrowError("Errors while scanning core packages:"); Console.printMessages(messages); throw new Error("scan failed?"); } @@ -240,7 +240,7 @@ _.extend(Matcher.prototype, { var self = this; if (self.buf.length > 0) { - console.log("Extra junk is ", self.buf); + Console.info("Extra junk is :", self.buf); throw new TestFailure('junk-at-end', { run: self.run }); } }, @@ -1639,22 +1639,22 @@ var listTests = function (options) { var testList = getFilteredTests(options); if (! testList.allTests.length) { - Console.stderr.write("No tests defined.\n"); + Console.error("No tests defined.\n"); return; } _.each(_.groupBy(testList.filteredTests, 'file'), function (tests, file) { - Console.stdout.write(file + ':\n'); + Console.rawInfo(file + ':\n'); _.each(tests, function (test) { - Console.stdout.write(' - ' + test.name + - (test.tags.length ? ' [' + test.tags.join(' ') + ']' - : '')); + Console.rawInfo(' - ' + test.name + + (test.tags.length ? ' [' + test.tags.join(' ') + ']' + : '')); }); }); - Console.stderr.write('\n'); - Console.stderr.write(testList.filteredTests.length + " tests listed."); - Console.stderr.write(testList.generateSkipReport()); + Console.error(); + Console.error(testList.filteredTests.length + " tests listed."); + Console.error(testList.generateSkipReport()); }; /////////////////////////////////////////////////////////////////////////////// @@ -1669,7 +1669,7 @@ var runTests = function (options) { var testList = getFilteredTests(options); if (! testList.allTests.length) { - Console.stderr.write("No tests defined.\n"); + Console.error("No tests defined."); return 0; } @@ -1689,7 +1689,7 @@ var runTests = function (options) { if (e instanceof TestFailure) { failure = e; } else { - Console.stderr.write("exception\n\n"); + Console.error("exception\n"); throw e; } } finally { @@ -1698,84 +1698,85 @@ var runTests = function (options) { } if (failure) { - Console.stderr.write("fail!\n"); + Console.error("fail!"); failedTests.push(test); testList.notifyFailed(test); var frames = parseStack.parse(failure); var relpath = path.relative(files.getCurrentToolsDir(), frames[0].file); - Console.stderr.write(" => " + failure.reason + " at " + - relpath + ":" + frames[0].line + "\n"); + Console.rawError(" => " + failure.reason + " at " + + relpath + ":" + frames[0].line + "\n"); if (failure.reason === 'no-match') { - Console.stderr.write(" => Pattern: " + failure.details.pattern + "\n"); + Console.arrowError("Pattern: " + failure.details.pattern, 2); } if (failure.reason === "wrong-exit-code") { var s = function (status) { return status.signal || ('' + status.code) || "???"; }; - Console.stderr.write(" => Expected: " + s(failure.details.expected) + - "; actual: " + s(failure.details.actual) + "\n"); + Console.rawError(" => " + "Expected: " + s(failure.details.expected) + + "; actual: " + s(failure.details.actual) + "\n"); } if (failure.reason === 'expected-exception') { } if (failure.reason === 'not-equal') { - Console.stderr.write( - " => Expected: " + JSON.stringify(failure.details.expected) + - "; actual: " + JSON.stringify(failure.details.actual) + "\n"); + Console.rawError( + " => " + "Expected: " + JSON.stringify(failure.details.expected) + + "; actual: " + JSON.stringify(failure.details.actual) + "\n"); } if (failure.details.run) { failure.details.run.outputLog.end(); var lines = failure.details.run.outputLog.get(); if (! lines.length) { - Console.stderr.write(" => No output\n"); + Console.arrowError("No output", 2); } else { var historyLines = options.historyLines || 100; - Console.stderr.write(" => Last " + historyLines + " lines:\n"); + Console.arrowError("Last " + historyLines + " lines:", 2 + ); _.each(lines.slice(-historyLines), function (line) { - Console.stderr.write(" " + - (line.channel === "stderr" ? "2| " : "1| ") + - line.text + - (line.bare ? "%" : "") + "\n"); + Console.rawError(" " + + (line.channel === "stderr" ? "2| " : "1| ") + + line.text + + (line.bare ? "%" : "") + "\n"); }); } } if (failure.details.messages) { - Console.stderr.write(" => Errors while building:\n"); - Console.stderr.write(failure.details.messages.formatMessages()); + Console.arrowError("Errors while building:", 2); + Console.rawError(failure.details.messages.formatMessages() + "\n"); } } else { var durationMs = +(new Date) - startTime; - Console.stderr.write("ok (" + durationMs + " ms)\n"); + Console.error("ok (" + durationMs + " ms)"); } }); testList.saveTestState(); if (totalRun > 0) - Console.stderr.write("\n"); + Console.error(); - Console.stderr.write(testList.generateSkipReport()); + Console.error(testList.generateSkipReport()); if (testList.filteredTests.length === 0) { - Console.stderr.write("No tests run.\n"); + Console.error("No tests run."); return 0; } else if (failedTests.length === 0) { var disclaimers = ''; if (testList.filteredTests.length < testList.allTests.length) disclaimers += " other"; - Console.stderr.write("All" + disclaimers + " tests passed.\n"); + Console.error("All" + disclaimers + " tests passed."); return 0; } else { var failureCount = failedTests.length; - Console.stderr.write(failureCount + " failure" + - (failureCount > 1 ? "s" : "") + ":\n"); + Console.error(failureCount + " failure" + + (failureCount > 1 ? "s" : "") + ":"); _.each(failedTests, function (test) { - Console.stderr.write(" - " + test.file + ": " + test.name); + Console.rawError(" - " + test.file + ": " + test.name + "\n"); }); return 1; } diff --git a/tools/stats.js b/tools/stats.js index 0ece142866..95fc3780a3 100644 --- a/tools/stats.js +++ b/tools/stats.js @@ -118,11 +118,14 @@ var recordPackages = function (options) { var logErrorIfInCheckout = function (err) { if (files.inCheckout() || process.env.METEOR_PACKAGE_STATS_TEST_OUTPUT) { - Console.stderr.write("Failed to record package usage.\n"); - Console.stderr.write( - "(This error is hidden when you are not running Meteor from a checkout.)\n"); - Console.stderr.write(err.stack || err); - Console.stderr.write("\n\n"); + Console.warn("Failed to record package usage."); + Console.warn( + "(This error is hidden when you are not running Meteor from a", + "checkout.)"); + var printErr = err.stack || err; + Console.rawWarn(printErr + "\n"); + Console.warn(); + Console.warn(); } }; diff --git a/tools/tests/cordova-platforms.js b/tools/tests/cordova-platforms.js index b831486014..42c9f7b1e5 100644 --- a/tools/tests/cordova-platforms.js +++ b/tools/tests/cordova-platforms.js @@ -35,7 +35,7 @@ selftest.define("add cordova platforms", function () { run.match("added"); run = s.run("remove-platform", "foo"); - run.match("foo: platform is not"); + run.matchErr("foo: platform is not"); run = s.run("remove-platform", "android"); run.match("removed"); diff --git a/tools/tests/package-tests.js b/tools/tests/package-tests.js index 2ffd21c9f1..ad2aac35f8 100644 --- a/tools/tests/package-tests.js +++ b/tools/tests/package-tests.js @@ -872,7 +872,7 @@ selftest.define("add package with no builds", ["net"], function () { var run = s.run("add", "glasser:binary-package-with-no-builds"); run.waitSecs(10); - run.matchErr("No compatible build found for " + + run.matchErr("No compatible build found for\n" + "glasser:binary-package-with-no-builds@1.0.0"); run.expectExit(1); }); diff --git a/tools/tests/releases.js b/tools/tests/releases.js index 5460578be2..18c9edaedd 100644 --- a/tools/tests/releases.js +++ b/tools/tests/releases.js @@ -79,7 +79,8 @@ selftest.define("springboard", ['checkout', 'net'], function () { run = s.run(); run.matchErr("offline"); run.matchErr("it uses Meteor strange"); - run.matchErr("don't have that version of Meteor installed"); + run.matchErr("don't have that version"); + run.matchErr("of Meteor installed"); run.matchErr("update servers"); run.expectExit(1); @@ -186,7 +187,8 @@ selftest.define("checkout", ['checkout'], function () { s.write(".meteor/release", "something"); run = s.run("list"); run.readErr("=> Running Meteor from a checkout"); - run.matchErr("project version (Meteor something)\n"); + run.matchErr("project version"); + run.matchErr("(Meteor something)\n"); run.expectExit(0); }); }); diff --git a/tools/utils.js b/tools/utils.js index 02d9ebc5b0..b0966966ea 100644 --- a/tools/utils.js +++ b/tools/utils.js @@ -112,49 +112,8 @@ exports.printPackageList = function (items, options) { }; rows = _.sortBy(rows, alphaSort); - return utils.printTwoColumns(rows, options); -}; - -// XXX: Move to e.g. formatters.js? -// Prints a two column table in a nice format: -// The first column is printed entirely, the second only as space permits -exports.printTwoColumns = function (rows, options) { - options = options || {}; - - var longest = ''; - _.each(rows, function (row) { - var col0 = row[0] || ''; - if (col0.length > longest.length) - longest = col0; - }); - - var pad = longest.replace(/./g, ' '); - - var width = 80; - var stream = process.stdout; - if (stream && stream.isTTY && stream.columns) { - width = stream.columns; - } - - var Console = require("./console.js").Console; - - var out = ''; - _.each(rows, function (row) { - var col0 = row[0] || ''; - var col1 = row[1] || ''; - var line = Console.bold(col0) + pad.substr(col0.length); - line += " " + col1; - if (line.length > width) { - line = line.substr(0, width - 3) + '...'; - } - out += line + "\n"; - }); - - // XXX: Naughty call to 'private' function - var level = options.level || Console.LEVEL_INFO; - Console._print(level, out); - - return out; + var Console = require('./console.js').Console; + return Console.printTwoColumns(rows, options); }; // Determine a human-readable hostname for this computer. Prefer names @@ -300,7 +259,8 @@ exports.validatePackageNameOrExit = function (packageName, options) { } catch (e) { if (!e.versionParserError) throw e; - process.stderr.write("Error: " + e.message + "\n"); + var Console = require('./console.js').Console; + Console.error(e.message, Console.options({ bulletPoint: "Error: " })); // lazy-load main: old bundler tests fail if you add a circular require to // this file var main = require('./main.js');