From 9c99638c7fedde9255990a32a623de42671d19f7 Mon Sep 17 00:00:00 2001 From: ekatek Date: Thu, 18 Sep 2014 23:48:14 -0700 Subject: [PATCH 01/38] be more consistent about using utils.parseConstraint in the tool; test for either-version --- .../constraint-solver/constraint-solver.js | 43 ++++++--- tools/catalog.js | 37 +++----- tools/commands-packages.js | 30 +++--- tools/compiler.js | 8 +- tools/package-source.js | 14 ++- tools/project.js | 13 ++- .../package-of-two-versions/package3.js | 4 + tools/tests/publish.js | 91 ++++++++++++++++++- 8 files changed, 172 insertions(+), 68 deletions(-) create mode 100644 tools/tests/packages/package-of-two-versions/package3.js diff --git a/packages/constraint-solver/constraint-solver.js b/packages/constraint-solver/constraint-solver.js index a3ae9603bb..35f3a71b2c 100644 --- a/packages/constraint-solver/constraint-solver.js +++ b/packages/constraint-solver/constraint-solver.js @@ -160,8 +160,8 @@ ConstraintSolver.PackagesResolver.prototype.resolve = function ( check(dependencies, [String]); check(constraints, [{ - packageName: String, - name: Match.Optional(String), + name: String, + constraintString: Match.Optional(Match.OneOf(String, undefined)), constraints: [{ version: Match.OneOf(String, null), type: String }] @@ -187,7 +187,9 @@ ConstraintSolver.PackagesResolver.prototype.resolve = function ( // were causing crashes, but maybe the real answer is that there shouldn't // have been undefineds? if (options.previousSolution) { - options.previousSolution = _.filter(_.flatten(_.map(options.previousSolution, function (version, packageName) { + options.previousSolution = + _.filter(_.flatten( + _.map(options.previousSolution, function (version, packageName) { return _.map(self._unibuildsForPackage(packageName), function (unitName) { return self.resolver._unitsVersionsMap[unitName + "@" + version]; }); @@ -303,19 +305,34 @@ ConstraintSolver.PackagesResolver.prototype._splitDepsToConstraints = }); _.each(inputConstraints, function (constraint) { - var constraintStr = constraint.constraintString; - if (!constraintStr) { - constraintStr = constraint.version ? - constraint.version : ""; - if (constraint.type === "exactly") - constraintStr = "=" + constraintStr; - } - _.each(self._unibuildsForPackage(constraint.packageName), function (unibuildName) { - constraints.push(self.resolver.getConstraint(unibuildName, constraintStr)); + _.each(self._unibuildsForPackage(constraint.name), function (unibuildName) { + //XXX: This is kind of dumb -- we make this up, so we can reparse it + //later. Todo: clean this up a bit. + if (!constraint.constraintString) { + var constraintArray = []; + _.each(constraint.constraints, function (c) { + if (c.type == "exact") { + constraintArray.push("+" + c.version); + } else if (c.version) { + constraintArray.push(c.version) + } + }); + if (!_.isEmpty(constraintArray)) { + constraint.constraintString = + _.reduce(constraintArray, + function(x, y) { + return x + " || " + y; + }); + } else { + constraint.constraintString = ""; + } + } + constraints.push( + self.resolver.getConstraint(unibuildName, constraint.constraintString)); }); }); - return { dependencies: dependencies, constraints: constraints }; + return { dependencies: dependencies, constraints: constraints }; }; ConstraintSolver.PackagesResolver.prototype._unibuildsForPackage = diff --git a/tools/catalog.js b/tools/catalog.js index 11f3e95c62..df27c4ef0c 100644 --- a/tools/catalog.js +++ b/tools/catalog.js @@ -464,25 +464,25 @@ _.extend(CompleteCatalog.prototype, { // Constraints for uniload should just be packages with no version // constraint and one local version (since they should all be in core). - if (!_.has(constraint, 'packageName') || + if (!_.has(constraint, 'name') || constraint.constraints.length > 1 || constraint.constraints[0].type !== 'any-reasonable') { throw Error("Surprising constraint: " + JSON.stringify(constraint)); } - if (!_.has(self.versions, constraint.packageName)) { + if (!_.has(self.versions, constraint.name)) { throw Error("Trying to resolve unknown package: " + - constraint.packageName); + constraint.name); } - if (_.isEmpty(self.versions[constraint.packageName])) { + if (_.isEmpty(self.versions[constraint.name])) { throw Error("Trying to resolve versionless package: " + - constraint.packageName); + constraint.name); } - if (_.size(self.versions[constraint.packageName]) > 1) { + if (_.size(self.versions[constraint.name]) > 1) { throw Error("Too many versions for package: " + - constraint.packageName); + constraint.name); } - ret[constraint.packageName] = - _.keys(self.versions[constraint.packageName])[0]; + ret[constraint.name] = + _.keys(self.versions[constraint.name])[0]; }); return ret; } @@ -499,8 +499,7 @@ _.extend(CompleteCatalog.prototype, { // arguments to the constraint solver. // // -deps: list of package names that we depend on - // -constr: constraints of form {packageName: String, version: String} with - // {type: exact} for exact constraints. + // -constr: constraints of the proper form from parseConstraint in utils.js // // Weak dependencies are constraints (they constrain the result), but not // dependencies. @@ -509,13 +508,9 @@ _.extend(CompleteCatalog.prototype, { _.each(constraints, function (constraint) { constraint = _.clone(constraint); if (!constraint.weak) { - deps.push(constraint.packageName); + deps.push(constraint.name); } delete constraint.weak; - delete constraint.constraintString; -if (_.has(constraint, "version")) { - console.trace(constraint); -} constr.push(constraint); }); @@ -530,18 +525,14 @@ if (_.has(constraint, "version")) { // for now: we can't use any packages that are of different versions from // what we've already decided from the project! _.each(project.project.getVersions(), function (version, name) { - constr.push({packageName: name, constraints: [ - { version: version, type: 'exactly'}] }); - }); + constr.push(utils.parseConstraint(name + "@=" + version)); + }); } // Local packages can only be loaded from the version we have the source // for: that's a weak exact constraint. _.each(self.packageSources, function (packageSource, name) { - constr.push({packageName: name, - constraints: [ - { version: packageSource.version, - type: 'exactly' }] }); + constr.push(utils.parseConstraint(name + "@=" + packageSource.version)); }); var patience = new utils.Patience({ diff --git a/tools/commands-packages.js b/tools/commands-packages.js index 58be62a44f..1ef62ea9d8 100644 --- a/tools/commands-packages.js +++ b/tools/commands-packages.js @@ -1946,18 +1946,15 @@ main.registerCommand({ process.stdout.write("The version constraint will be removed.\n"); } // Now remove the old constraint from what we're going to calculate - // with. - // This matches code in calculateCombinedConstraints. - // XXX: This is the weirdest hack, all around. - var oldConstraint = ""; - if (constraint.constraintString) { - oldConstraint = "@" + packages[constraint.name]; - } - oldConstraint = - _.extend({packageName: constraint.name}, - utils.parseConstraint(constraint.name + oldConstraint)); - + // with. (XXX: This is hacky.) var removed = false; + var oldC = ""; + if (packages[constraint.name]) { + oldC = "@" + packages[constraint.name]; + } + var oldConstraint = utils.parseConstraint( + constraint.name + oldC); + for (var i = 0; i < allPackages.length; ++i) { if (_.isEqual(oldConstraint, allPackages[i])) { removed = true; @@ -1967,7 +1964,7 @@ main.registerCommand({ } if (!removed) { throw Error("Couldn't find constraint to remove: " + - JSON.stringify(oldConstraint)); + JSON.stringify(constraint.constraintString)); } } } @@ -1977,13 +1974,7 @@ main.registerCommand({ packages[constraint.name] = constraint.constraintString; // Also, add it to all of our combined dependencies. - // This matches code in project.calculateCombinedConstraints. - if (constraint.constraintString) { - oldConstraint = "@" + packages[constraint.name]; - } - allPackages.push( - _.extend({packageName: constraint.name}, - utils.parseConstraint(constraint.name + oldConstraint))); + allPackages.push(constraint); }); @@ -2008,6 +1999,7 @@ main.registerCommand({ allPackages, { previousSolution: versions }, { ignoreProjectDeps: true }); + if ( ! newVersions) { // XXX: Better error handling. process.stderr.write("Cannot resolve package dependencies.\n"); diff --git a/tools/compiler.js b/tools/compiler.js index f2f03e3f20..b412c92383 100644 --- a/tools/compiler.js +++ b/tools/compiler.js @@ -184,7 +184,8 @@ var determineBuildTimeDependencies = function (packageSource, constraintString = "@" + info.constraint; } var version = utils.parseConstraint(packageName + constraintString); - constraints_array.push(_.extend({ packageName: packageName }, version)); + constraints_array.push( + utils.parseConstraint(packageName + constraintString)); }); var versions = packageSource.dependencyVersions.dependencies || {}; @@ -236,9 +237,8 @@ var determineBuildTimeDependencies = function (packageSource, if (info.constraint) { constraintString = "@" + info.constraint; } - var version = utils.parseConstraint(parsedSpec.package + constraintString); - constraints_array.push(_.extend({packageName: parsedSpec.package}, - version)); + constraints_array.push( + utils.parseConstraint(parsedSpec.package + constraintString)); }); var pluginVersion = pluginVersions[info.name] || {}; diff --git a/tools/package-source.js b/tools/package-source.js index a0f2eefc55..fdc0210528 100644 --- a/tools/package-source.js +++ b/tools/package-source.js @@ -1264,11 +1264,23 @@ _.extend(PackageSource.prototype, { } } + // By the way, you can't depend on yourself. + var doNotDepOnSelf = function (dep) { + if (dep.package === self.name) { + buildmessage.error("Circular dependency found: " + + self.name + + " depends on itself.\n"); + } + }; + _.each(self.allArchs, function (label) { + _.each(uses[label], doNotDepOnSelf); + _.each(implies[label], doNotDepOnSelf); + }); + // If we have specified some release, then we should go through the // dependencies and fill in the unspecified constraints with the versions in // the releases (if possible). if (!_.isEmpty(releaseRecords)) { -console.log(releaseRecords); // Given a dependency object with keys package (the name of the package) // and constraint (the version constraint), if the constraint is null, diff --git a/tools/project.js b/tools/project.js index e30d745586..c10e438af0 100644 --- a/tools/project.js +++ b/tools/project.js @@ -275,7 +275,7 @@ _.extend(Project.prototype, { oldConstraint = "@" + constraint; } allDeps.push( - _.extend({packageName: packageName}, + _.extend({name: packageName}, utils.parseConstraint(packageName + oldConstraint))); }); @@ -302,9 +302,7 @@ _.extend(Project.prototype, { if (use.constraint) { oldConstraint = "@" + use.constraint; } - allDeps.push( - _.extend({packageName: use.package}, - utils.parseConstraint(use.packageName + oldConstraint))); + allDeps.push(utils.parseConstraint(use.package + oldConstraint)); }); }); @@ -313,7 +311,9 @@ _.extend(Project.prototype, { // Finally, each release package is a weak exact constraint. So, let's add // those. _.each(releasePackages, function(version, name) { - allDeps.push({packageName: name, weak: true, constraints: [ + allDeps.push({name: name, weak: true, + constraintStr: "=" + name, + constraints: [ { version: version, type: 'exactly' } ]}); }); @@ -323,8 +323,7 @@ _.extend(Project.prototype, { // someday, this will make sense. (The conditional here allows us to work // in tests with releases that have no packages.) if (catalog.complete.getPackage("ctl")) { - allDeps.push({packageName: "ctl", constraints: [ - { version: null, type: 'any-reasonable' } ]}); + allDeps.push(utils.parseConstraint("ctl")); } return allDeps; diff --git a/tools/tests/packages/package-of-two-versions/package3.js b/tools/tests/packages/package-of-two-versions/package3.js new file mode 100644 index 0000000000..036f60743a --- /dev/null +++ b/tools/tests/packages/package-of-two-versions/package3.js @@ -0,0 +1,4 @@ +Package.describe({ + summary: "Test package.", + version: "2.0.0" +}); diff --git a/tools/tests/publish.js b/tools/tests/publish.js index 18104a6f07..4d462b9c84 100644 --- a/tools/tests/publish.js +++ b/tools/tests/publish.js @@ -5,6 +5,7 @@ var testUtils = require('../test-utils.js'); var selftest = require('../selftest.js'); var stats = require('../stats.js'); var Sandbox = selftest.Sandbox; +var files = require('../files.js'); selftest.define("publish-and-search", ["slow", "net", "test-package-server"], function () { var s = new Sandbox; @@ -240,7 +241,7 @@ selftest.define("list-with-a-new-version", }); s.cd('mapp', function () { - // // + // // // run = s.run("search", "asdf"); // run.waitSecs(100); // run.expectExit(0); @@ -265,3 +266,91 @@ selftest.define("list-with-a-new-version", run.expectExit(0); }); }); + +selftest.define("package-depends-on-either-version", + ["slow", "net", "test-package-server"], function () { + var s = new Sandbox; + + var username = "test"; + var password = "testtest"; + testUtils.login(s, username, password); + var packageNameDependent = utils.randomToken(); + + // First, we publish fullPackageNameDep at 1.0 and publish it.. + var fullPackageNameDep = username + ":" + packageNameDependent; + s.createPackage(fullPackageNameDep, "package-of-two-versions"); + s.cd(fullPackageNameDep, function() { + run = s.run("publish", "--create"); + run.waitSecs(20); + run.match("Done"); + }); + + // Then, we publish fullPackageNameDep at 2.0. + s.cd(fullPackageNameDep, function() { + s.cp("package3.js", "package.js"); + run = s.run("publish"); + run.waitSecs(20); + run.match("Done"); + }); + + // Then, we make another one that depends on either version and publish. + var another = utils.randomToken(); + var fullPackageAnother = username + ":" + another; + s.createPackage(fullPackageAnother, "package-of-two-versions"); + s.cd(fullPackageAnother, function() { + var packOpen = s.read("package.js"); + packOpen = packOpen + "\nPackage.onUse(function(api) { \n" + + "api.use(\"" + fullPackageNameDep + + "@1.0.0 || 2.0.0\");\n" + + " });"; + s.write("package.js", packOpen); + run = s.run("publish", "--create"); + run.waitSecs(20); + run.match("Done"); + }); + + // Now we add them to an app. + // Starting a run + s.createApp("myapp", "package-tests"); + s.cd("myapp"); + s.set("METEOR_TEST_TMP", files.mkdtemp()); + s.set("METEOR_OFFLINE_CATALOG", "t"); + + run = s.run("add", fullPackageNameDep + "@=1.0.0"); + run.match(fullPackageNameDep); + run.expectExit(0); + + var readVersions = function () { + var lines = s.read(".meteor/versions").split("\n"); + var depend = {}; + _.each(lines, function(line) { + if (!line) return; + // Packages are stored of the form foo@1.0.0, so this should give us an + // array [foo, 1.0.0]. + var split = line.split('@'); + var pack = split[0]; + depend[pack] = split[1]; + }); + return depend; + }; + + var depend = readVersions(); + selftest.expectEqual(depend[fullPackageNameDep], "1.0.0"); + + run = s.run("add", fullPackageAnother + "@=1.0.0"); + run.match(fullPackageAnother); + run.expectExit(0); + + var depend = readVersions(); + selftest.expectEqual(depend[fullPackageNameDep], "1.0.0"); + selftest.expectEqual(depend[fullPackageAnother], "1.0.0"); + + run = s.run("add", fullPackageNameDep + "@=2.0.0"); + run.match(fullPackageNameDep); + run.expectExit(0); + + depend = readVersions(); + selftest.expectEqual(depend[fullPackageNameDep], "2.0.0"); + selftest.expectEqual(depend[fullPackageAnother], "1.0.0"); + +}); From e1311c407c10d658f61169bb4fb17d5ee3c160e5 Mon Sep 17 00:00:00 2001 From: ekatek Date: Fri, 19 Sep 2014 13:41:01 -0700 Subject: [PATCH 02/38] function to check if a version constraint is compatible with pre0.9.3 versions, default token to send to syncNewData includes format specifications --- .../package-version-parser-tests.js | 20 +++++++++++++++++++ tools/config.js | 4 ++-- tools/package-client.js | 3 ++- tools/package-version-parser.js | 11 ++++++++++ 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/packages/package-version-parser/package-version-parser-tests.js b/packages/package-version-parser/package-version-parser-tests.js index dcdaef6f4a..454c417181 100644 --- a/packages/package-version-parser/package-version-parser-tests.js +++ b/packages/package-version-parser/package-version-parser-tests.js @@ -156,3 +156,23 @@ Tinytest.add("Meteor Version string parsing - compare", function (test) { test.isTrue(PackageVersion.compare("1.2.0", "1.0.0") > 0); test.isTrue(PackageVersion.compare("1.0.1", "1.0.0~5") > 0); }); + +Tinytest.add("Invalid in 0.9.2", function (test) { + // Note that invalidFirstFormatConstraint assumes that the initial version + // passed in has been previously checked to be valid in 0.9.3. + + // These are invalid in 0.9.2, but valid in 0.9.3 and above. + var invalidVersions = + ["1.0.0~1", "1.0.0 || 2.0.0", "1.0.0-rc1~1", + "3.4.0-rc1 || =1.0.0"]; + _.each(invalidVersions, function (v) { + test.isTrue(PackageVersion.invalidFirstFormatConstraint(v)); + }); + + // These are all valid in 0.9.2. + var validVersions = + ["1.0.0", "2.0.0-rc1", "=2.5.0"]; + _.each(validVersions, function (v) { + test.isFalse(PackageVersion.invalidFirstFormatConstraint(v)); + }); +}); diff --git a/tools/config.js b/tools/config.js index b551505054..69dcbb9a09 100644 --- a/tools/config.js +++ b/tools/config.js @@ -213,7 +213,7 @@ _.extend(exports, { getPackageStorage: function (tropo) { var self = this; tropo = tropo || tropohouse.default; - return path.join(tropo.root, "package-metadata", "v1", + return path.join(tropo.root, "package-metadata", "v2", self.getLocalPackageCacheFilename()); }, @@ -223,7 +223,7 @@ _.extend(exports, { getBannersShownFilename: function() { return path.join(tropohouse.default.root, - "package-metadata", "v1", "banners-shown.json"); + "package-metadata", "v2", "banners-shown.json"); }, // Return the domain name of the current Meteor Accounts server in diff --git a/tools/package-client.js b/tools/package-client.js index 4f20a6b7e1..6fd556a1d2 100644 --- a/tools/package-client.js +++ b/tools/package-client.js @@ -38,7 +38,7 @@ var openPackageServerConnection = function (packageServerUrl) { var emptyCachedServerDataJson = function () { return { - syncToken: {}, + syncToken: { format: "1.1" }, collections: null }; }; @@ -696,6 +696,7 @@ exports.publishPackage = function (packageSource, compileResult, conn, options) // XXX check that we're actually providing something new? } else { process.stdout.write('Creating package version...\n'); + var uploadRec = { packageName: packageSource.name, version: version, diff --git a/tools/package-version-parser.js b/tools/package-version-parser.js index dc7bcc0091..00ae68451f 100644 --- a/tools/package-version-parser.js +++ b/tools/package-version-parser.js @@ -271,3 +271,14 @@ var throwVersionParserError = function (message) { PV.constraintToFullString = function (parsedConstraint) { return parsedConstraint.name + "@" + parsedConstraint.constraintString; }; + +// Return true if the version was invalid prior to the 0.9.3. +PV.invalidFirstFormatConstraint = function (validConstraint) { + if (!validConstraint) return false; + // We can check this easily right now, because we introduced some new + // characters. Anything with those characters is invalid prior to + // 0.9.3. XXX: If we ever have to go through these, we should write a more + // complicated regex. + return (/.*~.*/.test(validConstraint) || + /.*\|.*/.test(validConstraint)); +} From 717d9a0198820fe9ddd5a5520fefb963146828d1 Mon Sep 17 00:00:00 2001 From: ekatek Date: Fri, 19 Sep 2014 13:47:37 -0700 Subject: [PATCH 03/38] actually use v1.1, and save v2 for sql lite changes in catalog versioning --- tools/config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/config.js b/tools/config.js index 69dcbb9a09..7afb812b79 100644 --- a/tools/config.js +++ b/tools/config.js @@ -213,7 +213,7 @@ _.extend(exports, { getPackageStorage: function (tropo) { var self = this; tropo = tropo || tropohouse.default; - return path.join(tropo.root, "package-metadata", "v2", + return path.join(tropo.root, "package-metadata", "v1.1", self.getLocalPackageCacheFilename()); }, @@ -223,7 +223,7 @@ _.extend(exports, { getBannersShownFilename: function() { return path.join(tropohouse.default.root, - "package-metadata", "v2", "banners-shown.json"); + "package-metadata", "v1.1", "banners-shown.json"); }, // Return the domain name of the current Meteor Accounts server in From 4ab2bd4ee43d5b8ea4f09122a140cf51dd491d06 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Fri, 19 Sep 2014 15:27:57 -0700 Subject: [PATCH 04/38] Noop regex tweak and comments. --- tools/package-version-parser.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tools/package-version-parser.js b/tools/package-version-parser.js index 00ae68451f..06c5f63793 100644 --- a/tools/package-version-parser.js +++ b/tools/package-version-parser.js @@ -272,13 +272,18 @@ PV.constraintToFullString = function (parsedConstraint) { return parsedConstraint.name + "@" + parsedConstraint.constraintString; }; -// Return true if the version was invalid prior to the 0.9.3. + +// Return true if the version constraint was invalid prior to 0.9.3 +// (adding ~ and || support) +// +// NOTE: this is not used on the client yet. This package is used by the +// package server to determine what is valid. PV.invalidFirstFormatConstraint = function (validConstraint) { if (!validConstraint) return false; // We can check this easily right now, because we introduced some new // characters. Anything with those characters is invalid prior to // 0.9.3. XXX: If we ever have to go through these, we should write a more // complicated regex. - return (/.*~.*/.test(validConstraint) || - /.*\|.*/.test(validConstraint)); -} + return (/~/.test(validConstraint) || + /\|/.test(validConstraint)); +}; From 9963a8bcf35d00dd7b400650591210610feca40d Mon Sep 17 00:00:00 2001 From: Justin SB Date: Fri, 19 Sep 2014 15:34:42 -0700 Subject: [PATCH 05/38] Progress bars and output formatting --- scripts/dev-bundle-package.json | 2 + tools/buildmessage.js | 234 +++++++++++++++--- tools/catalog.js | 23 +- tools/commands-cordova.js | 21 +- tools/commands-packages.js | 416 ++++++++++++++++---------------- tools/commands.js | 197 +++++++-------- tools/compiler.js | 2 +- tools/console.js | 301 +++++++++++++++++++++++ tools/deploy.js | 117 ++++----- tools/http-helpers.js | 137 ++++++++++- tools/main.js | 16 +- tools/package-client.js | 29 +++ tools/progress.js | 278 +++++++++++++++++++++ tools/project.js | 23 +- tools/tropohouse.js | 54 ++--- 15 files changed, 1386 insertions(+), 464 deletions(-) create mode 100644 tools/console.js create mode 100644 tools/progress.js diff --git a/scripts/dev-bundle-package.json b/scripts/dev-bundle-package.json index fbacb95b9f..390d0b1961 100644 --- a/scripts/dev-bundle-package.json +++ b/scripts/dev-bundle-package.json @@ -3,6 +3,8 @@ "version": "0.0.0", "dependencies": { "fibers": "1.0.1", + "progress": "1.1.8", + "chalk": "0.5.1", "underscore": "1.5.2", "source-map-support": "0.2.5", "semver": "2.2.1" diff --git a/tools/buildmessage.js b/tools/buildmessage.js index 2c5939dc2d..2795fda314 100644 --- a/tools/buildmessage.js +++ b/tools/buildmessage.js @@ -1,7 +1,10 @@ +var Fiber = require('fibers'); +var Future = require('fibers/future'); var _ = require('underscore'); var files = require('./files.js'); var parseStack = require('./parse-stack.js'); var fiberHelpers = require('./fiber-helpers.js'); +var Progress = require('./progress.js').Progress; var debugBuild = !!process.env.METEOR_DEBUG_BUILD; @@ -154,9 +157,46 @@ _.extend(MessageSet.prototype, { } }); +// XXX: This is now a little bit silly... ideas: +// Can we just have one hierarchical state? +// Can we combined job & messageSet +// Can we infer nesting level? var currentMessageSet = new fiberHelpers.EnvironmentVariable; var currentJob = new fiberHelpers.EnvironmentVariable; var currentNestingLevel = new fiberHelpers.EnvironmentVariable(0); +var currentProgress = new fiberHelpers.EnvironmentVariable; + +var rootProgress = new Progress(); + +var getRootProgress = function () { + return rootProgress; +}; + +var reportProgress = function (state) { + var progress = currentProgress.get(); + if (progress) { + progress.reportProgress(state); + } +}; + +var reportProgressDone = function () { + var progress = currentProgress.get(); + if (progress) { + progress.reportProgressDone(); + } +}; + +var getCurrentProgressTracker = function () { + var progress = currentProgress.get(); + return progress ? progress : rootProgress; +}; + + +var addChildTracker = function (title) { + var options = {}; + options.title = title; + return getCurrentProgressTracker().addChildTask(options); +}; // Create a new MessageSet, run `f` with that as the current // MessageSet for the purpose of accumulating and recovering from @@ -169,24 +209,51 @@ var currentNestingLevel = new fiberHelpers.EnvironmentVariable(0); // `options`. var capture = function (options, f) { var messageSet = new MessageSet; - currentMessageSet.withValue(messageSet, function () { - var job = null; - if (typeof options === "object") { - job = new Job(options); - messageSet.jobs.push(job); - } else { - f = options; // options not actually provided - } + var parentMessageSet = currentMessageSet.get(); - currentJob.withValue(job, function () { - var nestingLevel = currentNestingLevel.get(); - currentNestingLevel.withValue(nestingLevel + 1, function () { - debugBuild && console.log("START CAPTURE", nestingLevel, options.title); - try { - f(); - } finally { - debugBuild && console.log("END CAPTURE", nestingLevel, options.title); - } + { + var parentProgress = currentProgress.get(); + if (!parentProgress) { + parentProgress = rootProgress; + } + var childOptions = {}; + if (typeof options === "object") { + if (options.title) { + childOptions.title = options.title; + } + } + var progress = parentProgress.addChildTask(childOptions); + } + + currentProgress.withValue(progress, function () { + currentMessageSet.withValue(messageSet, function () { + var job = null; + if (typeof options === "object") { + job = new Job(options); + messageSet.jobs.push(job); + } else { + f = options; // options not actually provided + } + + currentJob.withValue(job, function () { + var nestingLevel = currentNestingLevel.get(); + currentNestingLevel.withValue(nestingLevel + 1, function () { + var start; + if (debugBuild) { + start = Date.now(); + console.log("START CAPTURE", nestingLevel, options.title, "took " + (end - start)); + } + try { + f(); + } finally { + progress.reportProgressDone(); + + if (debugBuild) { + var end = Date.now(); + console.log("END CAPTURE", nestingLevel, options.title, "took " + (end - start)); + } + } + }); }); }); }); @@ -211,24 +278,57 @@ var enterJob = function (options, f) { options = {}; } - if (! currentMessageSet.get()) { - return f(); + var progress; + { + var parentProgress = currentProgress.get(); + if (!parentProgress) { + parentProgress = rootProgress; + } + var progressOptions = {}; + // XXX: Just pass all the options? + if (typeof options === "object") { + if (options.title) { + progressOptions.title = options.title; + } + if (options.forkJoin) { + progressOptions.forkJoin = options.forkJoin; + } + } + progress = parentProgress.addChildTask(progressOptions); } - var job = new Job(options); - var originalJob = currentJob.get(); - originalJob && originalJob.children.push(job); - currentMessageSet.get().jobs.push(job); - - return currentJob.withValue(job, function () { - var nestingLevel = currentNestingLevel.get(); - return currentNestingLevel.withValue(nestingLevel + 1, function () { - debugBuild && console.log("START", nestingLevel, options.title); + currentProgress.withValue(progress, function () { + if (!currentMessageSet.get()) { try { return f(); } finally { - debugBuild && console.log("DONE", nestingLevel, options.title); + progress.reportProgressDone(); } + } + + var job = new Job(options); + var originalJob = currentJob.get(); + originalJob && originalJob.children.push(job); + currentMessageSet.get().jobs.push(job); + + return currentJob.withValue(job, function () { + var nestingLevel = currentNestingLevel.get(); + return currentNestingLevel.withValue(nestingLevel + 1, function () { + var start; + if (debugBuild) { + start = Date.now(); + console.log("START", nestingLevel, options.title); + } + try { + return f(); + } finally { + progress.reportProgressDone(); + if (debugBuild) { + var end = Date.now(); + console.log("DONE", nestingLevel, options.title, "took " + (end - start)); + } + } + }); }); }); }; @@ -366,6 +466,74 @@ var assertInCapture = function () { throw new Error("Expected to be in a buildmessage capture"); }; +// Like _.each, but runs each operation in a separate job +var forkJoin = function (options, iterable, fn) { + if (!_.isFunction(fn)) { + fn = iterable; + iterable = options; + options = {}; + } + + var futures = []; + var results = []; + // XXX: We could check whether the sub-jobs set estimates, and if not + // assume they each take the same amount of time and auto-report their completion + var errors = []; + var firstError = null; + + options.forkJoin = true; + + enterJob(options, function () { + var job = currentJob.get(); + var messageSet = currentMessageSet.get(); + var progress = currentProgress.get(); + + _.each(iterable, function (/*arguments*/) { + var fut = new Future(); + var fnArguments = arguments; + Fiber(function () { + currentProgress.withValue(progress, function () { + currentMessageSet.withValue(messageSet, function () { + currentJob.withValue(job, function () { + try { + var result = enterJob({title: (options.title || '') + ' child' }, function () { + return fn.apply(null, fnArguments); + }); + fut['return'](result); + } catch (e) { + fut['throw'](e); + } + }); + }); + }); + }).run(); + futures.push(fut); + }); + + _.each(futures, function (future) { + try { + var result = future.wait(); + results.push(result); + errors.push(null); + } catch (e) { + results.push(null); + errors.push(e); + + if (firstError === null) { + firstError = e; + } + } + }); + }); + + if (firstError) { + throw firstError; + } + + return results; +}; + + var buildmessage = exports; _.extend(exports, { capture: capture, @@ -375,5 +543,11 @@ _.extend(exports, { exception: exception, jobHasMessages: jobHasMessages, assertInJob: assertInJob, - assertInCapture: assertInCapture + assertInCapture: assertInCapture, + forkJoin: forkJoin, + getRootProgress: getRootProgress, + reportProgress: reportProgress, + reportProgressDone: reportProgressDone, + getCurrentProgressTracker: getCurrentProgressTracker, + addChildTracker: addChildTracker }); diff --git a/tools/catalog.js b/tools/catalog.js index df27c4ef0c..24a9540953 100644 --- a/tools/catalog.js +++ b/tools/catalog.js @@ -138,16 +138,11 @@ _.extend(OfficialCatalog.prototype, { self._refreshFiber = Fiber.current; self._currentRefreshIsLoud = !options.silent; - var patience = new utils.Patience({ - messageAfterMs: 2000, - message: function () { - if (self._currentRefreshIsLoud) { - console.log("Refreshing package metadata. This may take a moment."); - } - } - }); - try { - var thrownError = null; + var thrownError = null; + + buildmessage.enterJob({ + title: 'Refreshing package metadata.' + }, function () { try { self._refresh(); // Force the complete catalog (which is layered on top of our data) to @@ -156,9 +151,7 @@ _.extend(OfficialCatalog.prototype, { } catch (e) { thrownError = e; } - } finally { - patience.stop(); - } + }); while (self._refreshFutures.length) { var fut = self._refreshFutures.pop(); @@ -897,9 +890,9 @@ _.extend(CompleteCatalog.prototype, { }; // Load the package sources for packages and their tests into packageSources. - _.each(self.effectiveLocalPackages, function (x) { + buildmessage.forkJoin({ 'title': 'Initializing packages'}, self.effectiveLocalPackages, function (x) { initSourceFromDir(x); - }); + }); // Remove all packages from the catalog that have the same name as // a local package, along with all of their versions and builds. diff --git a/tools/commands-cordova.js b/tools/commands-cordova.js index 099fe0e4cf..de581109a8 100644 --- a/tools/commands-cordova.js +++ b/tools/commands-cordova.js @@ -21,6 +21,7 @@ var compiler = require('./compiler.js'); var unipackage = require('./unipackage.js'); var tropohouse = require('./tropohouse.js'); var httpHelpers = require('./http-helpers.js'); +var Console = require('./console.js').Console; // XXX hard-coded the use of default tropohouse var tropo = tropohouse.default; @@ -45,7 +46,7 @@ var setVerboseness = cordova.setVerboseness = function (v) { }; var verboseLog = cordova.verboseLog = function (/* args */) { if (verboseness) - process.stderr.write('%% ' + util.format.apply(null, arguments) + '\n'); + Console.stderr.write('%% ' + util.format.apply(null, arguments) + '\n'); }; @@ -306,7 +307,7 @@ cordova.ensureCordovaProject = function (localPath, appName) { if (err instanceof main.ExitWithCode) { process.exit(err.code); } - process.stderr.write("Error creating Cordova project: " + + Console.stderr.write("Error creating Cordova project: " + err.message + "\n" + err.stack + "\n"); } } @@ -382,7 +383,7 @@ var installPlugin = function (cordovaPath, name, version, settings) { additionalArgs.push(variable + '=' + JSON.stringify(value)); }); - process.stdout.write(' installing ' + pluginInstallCommand + '\n'); + Console.stdout.write(' installing ' + pluginInstallCommand + '\n'); var execRes = execFileSyncOrThrow(localCordova, ['plugin', 'add', pluginInstallCommand].concat(additionalArgs), { cwd: cordovaPath }); @@ -528,7 +529,7 @@ var ensureCordovaPlugins = function (localPath, options) { files.rm_recursive(path.join(cordovaPath, 'platforms')); cordova.ensureCordovaPlatforms(localPath); }; - process.stdout.write("Initializing Cordova plugins...\n"); + Console.stdout.write("Initializing Cordova plugins...\n"); uninstallAllPlugins(); // Now install all of the plugins. @@ -922,7 +923,7 @@ main.registerCommand({ cordova.checkIsValidPlatform(platform); }); } catch (err) { - process.stderr.write(err.message + "\n"); + Console.stderr.write(err.message + "\n"); return 1; } @@ -950,7 +951,7 @@ main.registerCommand({ } _.each(platforms, function (platform) { - process.stdout.write("added platform " + platform + "\n"); + Console.stdout.write("added platform " + platform + "\n"); }); }); @@ -966,9 +967,9 @@ main.registerCommand({ _.each(platforms, function (platform) { if (_.contains(currentPlatforms, platform)) { - process.stdout.write("removed platform " + platform + "\n"); + Console.stdout.write("removed platform " + platform + "\n"); } else { - process.stdout.write(platform + " is not in this project\n"); + Console.stdout.write(platform + " is not in this project\n"); } }); project.removeCordovaPlatforms(platforms); @@ -989,11 +990,11 @@ main.registerCommand({ requiresApp: true }, function () { var platforms = project.getCordovaPlatforms(); - process.stdout.write(platforms.join("\n")); + Console.stdout.write(platforms.join("\n")); // print nothing at all if no platforms if (platforms.length) { - process.stdout.write("\n"); + Console.stdout.write("\n"); } }); diff --git a/tools/commands-packages.js b/tools/commands-packages.js index 1ef62ea9d8..aa6068a7b1 100644 --- a/tools/commands-packages.js +++ b/tools/commands-packages.js @@ -25,6 +25,7 @@ var stats = require('./stats.js'); var unipackage = require('./unipackage.js'); var cordova = require('./commands-cordova.js'); var packageLoader = require('./package-loader.js'); +var Console = require('./console.js').Console; // Returns an object with keys: // record : (a package or version record) @@ -51,7 +52,7 @@ var doOrDie = exports.doOrDie = function (f) { ret = f(); }); if (messages.hasMessages()) { - process.stderr.write(messages.formatMessages()); + Console.printMessages(messages); throw main.ExitWithCode(1); } return ret; @@ -126,12 +127,11 @@ main.registerCommand({ } }); if (messages.hasMessages()) { - - process.stderr.write("\n" + messages.formatMessages()); + Console.printMessages(messages); return 1; }; - console.log("You are ready!"); + Console.info("You are ready!"); return 0; }); @@ -159,8 +159,8 @@ main.registerCommand({ }, function (options) { if (options.create && options['existing-version']) { // Make up your mind! - process.stderr.write("The --create and --existing-version options cannot " + - "both be specified.\n"); + Console.error("The --create and --existing-version options cannot " + + "both be specified."); return 1; } @@ -176,11 +176,11 @@ main.registerCommand({ return 1; } if (! conn) { - process.stderr.write('No connection: Publish failed.\n'); + Console.error('No connection: Publish failed.'); return 1; } - process.stdout.write('Reading package...\n'); + Console.info('Reading package...'); // XXX Prettify error messages @@ -205,7 +205,7 @@ main.registerCommand({ }); if (messages.hasMessages()) { - process.stderr.write(messages.formatMessages()); + Console.printMessages(messages); return 1; } @@ -218,25 +218,25 @@ main.registerCommand({ return catalog.official.getPackage(packageName); }); if (packageInfo) { - process.stderr.write( + Console.error( "Package already exists. To create a new version of an existing "+ - "package, do not use the --create flag! \n"); + "package, do not use the --create flag!"); return 2; } if (!options['top-level'] && !packageName.match(/:/)) { - process.stderr.write( + 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.)\n"); +"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. if (auth.isLoggedIn()) { var properName = auth.loggedInUsername() + ":" + packageName; - process.stderr.write( - "\nDid you mean to create " + properName + " instead?\n" + Console.error( + "\nDid you mean to create " + properName + " instead?" ); } return 2; @@ -260,7 +260,7 @@ main.registerCommand({ return 1; } if (messages.hasMessages()) { - process.stderr.write(messages.formatMessages()); + Console.printMessages(messages); return ec || 1; } @@ -278,10 +278,10 @@ main.registerCommand({ if (_.any(allArchs, function (arch) { return arch.match(/^os\./); })) { - process.stdout.write( + Console.warning( "\nWARNING: Your package contains binary code and is only compatible with " + archinfo.host() + " architecture.\n" + - "Please use publish-for-arch to publish new builds of the package.\n\n"); + "Please use publish-for-arch to publish new builds of the package.\n"); } // Refresh, so that we actually learn about the thing we just published. @@ -296,12 +296,11 @@ main.registerCommand({ minArgs: 1, maxArgs: 1 }, function (options) { - // argument processing var all = options.args[0].split('@'); if (all.length !== 2) { - process.stderr.write( - 'Incorrect argument. Please use the form of @\n'); + Console.error( + 'Incorrect argument. Please use the form of @'); throw new main.ShowUsage; } var name = all[0]; @@ -314,10 +313,10 @@ main.registerCommand({ return catalog.complete.getPackage(name); }); if (! packageInfo) { - process.stderr.write( + 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\n"); +"To publish the package, run `meteor publish --create` from the package directory.\n"); return 1; } @@ -325,7 +324,7 @@ main.registerCommand({ return catalog.official.getVersion(name, versionString); }); if (! pkgVersion) { - process.stderr.write( + 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"); @@ -334,8 +333,8 @@ main.registerCommand({ } if (! pkgVersion.source || ! pkgVersion.source.url) { - process.stderr.write('There is no source uploaded for ' + - name + '@' + versionString + "\n"); + Console.error('There is no source uploaded for ' + + name + '@' + versionString); return 1; } @@ -353,7 +352,7 @@ main.registerCommand({ var packageDir = path.join(sourcePath, name); if (! fs.existsSync(packageDir)) { - process.stderr.write('Malformed source tarball\n'); + Console.error('Malformed source tarball'); return 1; } @@ -500,7 +499,7 @@ main.registerCommand({ }); if (messages.hasMessages()) { - process.stderr.write("\n" + messages.formatMessages()); + Console.printMessages(messages); return 1; } @@ -524,7 +523,7 @@ main.registerCommand({ } if (messages.hasMessages()) { - process.stderr.write("\n" + messages.formatMessages()); + Console.printMessages(messages); return 1; } @@ -542,7 +541,7 @@ main.registerCommand({ } }, function (options) { // Refresh the catalog, cacheing the remote package data on the server. - process.stdout.write("Resyncing with package server...\n"); + Console.info("Resyncing with package server..."); refreshOfficialCatalogOrDie(); try { @@ -561,13 +560,12 @@ main.registerCommand({ var data = fs.readFileSync(options.args[0], 'utf8'); relConf = JSON.parse(data); } catch (e) { - process.stderr.write("Could not parse release file: "); - process.stderr.write(e.message + "\n"); + Console.error("Could not parse release file: " + e.message); return 1; } // Fill in the order key and any other generated release.json fields. - process.stdout.write("Double-checking release schema ."); + Console.info("Double-checking release schema ."); // Check that the schema is valid -- release.json contains all the required // fields, does not contain contradicting information, etc. Output all @@ -576,8 +574,8 @@ main.registerCommand({ var badSchema = false; var bad = function (message) { if (!badSchema) - process.stderr.write("\n"); - process.stderr.write(message + "\n"); + Console.error(""); + Console.error(message); badSchema = true; }; if (!_.has(relConf, 'track')) { @@ -630,7 +628,7 @@ main.registerCommand({ if (badSchema) { return 1; } - process.stdout.write("."); + Console.info("."); // Let's check if this is a known release track/ a track to which we are // authorized to publish before we do any complicated/long operations, and @@ -641,8 +639,8 @@ main.registerCommand({ trackRecord = catalog.official.getReleaseTrack(relConf.track); }); if (!trackRecord) { - process.stderr.write('\n There is no release track named ' + relConf.track + - '. If you are creating a new track, use the --create-track flag.\n'); + Console.error('\n There is no release track named ' + relConf.track + + '. If you are creating a new track, use the --create-track flag.'); return 1; } @@ -650,13 +648,14 @@ main.registerCommand({ // we implement things like organizations, we are not handicapped by the // user's meteor version. if (!packageClient.amIAuthorized(relConf.track,conn, true)) { - process.stderr.write('\n You are not an authorized maintainer of ' + relConf.track + ".\n"); - process.stderr.write('Only authorized maintainers may publish new versions.\n'); + Console.error('\n You are not an authorized maintainer of ' + relConf.track + "."); + Console.error('Only authorized maintainers may publish new versions.'); return 1; }; } - process.stdout.write(". OK!\n"); + // XXX: Messages that start with . :-( + Console.info(". OK!"); // This is sort of a hidden option to just take your entire meteor checkout // and make a release out of it. That's what we do now (that's what releases @@ -674,7 +673,7 @@ main.registerCommand({ if (options['from-checkout']) { // You must be running from checkout to bundle up your checkout as a release. if (!files.inCheckout()) { - process.stderr.write("Must run from checkout to make release from checkout.\n"); + Console.error("Must run from checkout to make release from checkout."); return 1; }; @@ -683,9 +682,9 @@ main.registerCommand({ // why you would do that, and maybe you are confused about what you are // trying to do. if (options.appDir) { - process.stderr.write("Trying to publish from checkout while in an application " + + Console.error("Trying to publish from checkout while in an application " + "directory is a bad idea." + - " Please try again from somewhere else.\n"); + " Please try again from somewhere else."); return 1; } @@ -694,10 +693,10 @@ main.registerCommand({ // (which ones did you mean to use) and makes it likely that you did one of // these by accident. So, we will disallow it for now. if (relConf.packages || relConf.tool) { - process.stderr.write( + 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.\n"); + "Your release configuration file should not contain that information."); return 1; } @@ -715,7 +714,7 @@ main.registerCommand({ var messages = buildmessage.capture( {title: "rebuilding local packages"}, function () { - process.stdout.write("Rebuilding local packages...\n"); + Console.info("Rebuilding local packages..."); _.each(contents, function (item) { // We expect the meteor/packages directory to only contain a lot of // directories, each of which is a package. This may one day be false, @@ -733,7 +732,7 @@ main.registerCommand({ buildmessage.enterJob( { title: "building package " + item }, function () { - process.stdout.write(" checking consistency of " + item + " "); + Console.info(" checking consistency of " + item + " "); // Initialize the package source. Core packages have the same // name as their corresponding directories, because otherwise we @@ -745,7 +744,7 @@ main.registerCommand({ name: item }); if (buildmessage.jobHasMessages()) { - process.stdout.write("\n ...Error reading package:" + item + "\n"); + Console.warning("\n ...Error reading package:" + item); canBuild = false; return; }; @@ -759,7 +758,7 @@ main.registerCommand({ path.join(packageSource.sourceRoot, '.build.' + item)); } - process.stdout.write("."); + Console.info("."); // Now compile it! Once again, everything should compile, and if // it doesn't we should fail. Hopefully, of course, we have @@ -767,15 +766,15 @@ main.registerCommand({ // server, but we need to be careful. var directDeps = compiler.determineBuildTimeDependencies(packageSource).directDependencies; - tropohouse.default.downloadMissingPackages(directDeps) + tropohouse.default.downloadMissingPackages(directDeps); var compileResult = compiler.compile(packageSource, { officialBuild: true }); if (buildmessage.jobHasMessages()) { - process.stdout.write("\n ... Error compiling unipackage: " + item + "\n"); + Console.warning("\n ... Error compiling unipackage: " + item ); canBuild = false; return; }; - process.stdout.write("."); + Console.info("."); // Let's get the server version that this local package is // overwriting. If such a version exists, we will need to make sure @@ -785,7 +784,7 @@ main.registerCommand({ // Include this package in our release. myPackages[item] = packageSource.version; - process.stdout.write("."); + Console.info("."); // If there is no old version, then we need to publish this package. if (!oldVersion) { @@ -802,12 +801,12 @@ main.registerCommand({ "at the end (ex: 1.0.0-dev). If this is an " + "official release, please set official to true " + "in the release configuration file."); - process.stdout.write("NOT OK unofficial\n"); + Console.warning("NOT OK unofficial"); return; } toPublish[item] = {source: packageSource, compileResult: compileResult}; - process.stdout.write("new package or version\n"); + Console.info("new package or version"); return; } else { // If we can't build some of our packages, then we care about @@ -815,7 +814,7 @@ main.registerCommand({ // the errors will change the hashes as well). Don't even // bother checking until that happens. if (!canBuild) { - process.stdout.write("hash comparison skipped\n"); + Console.info("hash comparison skipped"); return; } @@ -855,9 +854,9 @@ main.registerCommand({ // a more thorough check. buildmessage.error("Something changed in package " + item + ". Please upgrade version number."); - process.stdout.write("NOT OK\n"); + Console.error("NOT OK"); } else { - process.stdout.write("ok\n"); + Console.info("ok"); } } }); @@ -866,7 +865,7 @@ main.registerCommand({ }); if (messages.hasMessages()) { - process.stderr.write("\n" + messages.formatMessages()); + Console.printMessages(messages); return 1; }; @@ -877,7 +876,7 @@ main.registerCommand({ continue; var prebuilt = toPublish[name]; - process.stdout.write("Publishing package: " + name + "\n"); + Console.info("Publishing package: " + name); // XXX merge with messages? having THREE kinds of error handling here is // um something. @@ -905,13 +904,13 @@ main.registerCommand({ return 1; } if (messages.hasMessages()) { - process.stderr.write(messages.formatMessages()); + Console.printMessages(messages); return pubEC || 1; } // If we fail to publish, just exit outright, something has gone wrong. if (pubEC > 0) { - process.stderr.write("Failed to publish: " + name + "\n"); + Console.error("Failed to publish: " + name); return pubEC; } } @@ -926,7 +925,7 @@ main.registerCommand({ // Create the new track, if we have been told to. if (options['create-track']) { - process.stdout.write("Creating a new release track...\n"); + Console.info("Creating a new release track..."); try { var track = conn.call('createReleaseTrack', { name: relConf.track } ); @@ -936,7 +935,7 @@ main.registerCommand({ } } - process.stdout.write("Creating a new release version...\n"); + Console.info("Creating a new release version..."); var record = { track: relConf.track, version: relConf.version, @@ -963,25 +962,25 @@ main.registerCommand({ // Get it back. refreshOfficialCatalogOrDie(); - process.stdout.write("Done creating " + relConf.track + "@" + - relConf.version + "!\n"); + Console.info("Done creating " + relConf.track + "@" + + relConf.version + "!"); if (options['from-checkout']) { // XXX maybe should discourage publishing if git status says we're dirty? var gitTag = "release/" + relConf.track + "@" + relConf.version; if (config.getPackageServerFilePrefix() !== 'packages') { // Only make a git tag if we're on the default branch. - process.stdout.write("Skipping git tag: not using the main package server.\n"); + Console.info("Skipping git tag: not using the main package server."); } else if (gitTag.indexOf(':') !== -1) { // XXX could run `git check-ref-format --allow-onelevel $gitTag` like we // used to, instead of this simple check // XXX could convert : to / ? - process.stdout.write("Skipping git tag: bad format for git.\n"); + Console.info("Skipping git tag: bad format for git."); } else { - process.stdout.write("Creating git tag " + gitTag + "\n"); + Console.info("Creating git tag " + gitTag); files.runGitInCheckout('tag', gitTag); - process.stdout.write( - "Pushing git tag (this should fail if you are not from MDG)\n"); + Console.info( + "Pushing git tag (this should fail if you are not from MDG)"); files.runGitInCheckout('push', 'git@github.com:meteor/meteor.git', 'refs/tags/' + gitTag); } @@ -1022,7 +1021,7 @@ main.registerCommand({ var record = allRecord.record; if (!record) { - process.stderr.write("Unknown package or release: " + name + "\n"); + Console.error("Unknown package or release: " + name); return 1; } @@ -1077,16 +1076,16 @@ main.registerCommand({ } if (_.isEqual(versionRecords, [])) { if (allRecord.release) { - process.stderr.write( - "No recommended versions of release " + name + " exist.\n"); + Console.error( + "No recommended versions of release " + name + " exist."); } else { - process.stderr.write("No versions of package" + name + " exist.\n"); + Console.error("No versions of package" + name + " exist."); } } else { var lastVersion = versionRecords[versionRecords.length - 1]; if (!lastVersion && full.length > 1) { - process.stderr.write( - "Unknown version of" + name + ":" + full[1] + "\n"); + Console.error( + "Unknown version of" + name + ":" + full[1]); return 1;; } var unknown = "< unknown >"; @@ -1099,21 +1098,21 @@ main.registerCommand({ var versionDesc = "Version " + v.version; if (v.description) versionDesc = versionDesc + " : " + v.description; - process.stdout.write(versionDesc + "\n"); + Console.info(versionDesc + ""); if (v.buildArchitectures && full.length > 1) - process.stdout.write(" Architectures: " - + v.buildArchitectures + "\n"); + Console.info(" Architectures: " + + v.buildArchitectures); if (v.packages && full.length > 1) { - process.stdout.write(" tool: " + v.tool + "\n"); - process.stdout.write(" packages:" + "\n"); + Console.info(" tool: " + v.tool); + Console.info(" packages:"); versionDesc = versionDesc + "\n packages:\n"; _.each(v.packages, function(pv, pn) { - process.stdout.write(" " + pn + ":" + pv + "\n"); + Console.info(" " + pn + ":" + pv); }); } }); - process.stdout.write("\n"); + Console.info("\n"); } // Creating the maintainer string. We have anywhere between 1 and lots of @@ -1147,7 +1146,7 @@ main.registerCommand({ + record.homepage; metamessage += "."; } - process.stdout.write(metamessage + "\n"); + Console.info(metamessage); }); main.registerCommand({ @@ -1183,7 +1182,7 @@ main.registerCommand({ try { search = new RegExp(options.args[0]); } catch (err) { - process.stderr.write(err + "\n"); + Console.error(err + ""); return 1; } @@ -1265,23 +1264,23 @@ main.registerCommand({ var output = false; if (!_.isEqual(matchingPackages, [])) { output = true; - process.stdout.write("Found the following packages:" + "\n"); - process.stdout.write(utils.formatList(matchingPackages) + "\n"); + Console.info("Found the following packages:"); + Console.info(utils.formatList(matchingPackages) + ""); } if (!_.isEqual(matchingReleases, [])) { output = true; - process.stdout.write("Found the following releases:" + "\n"); - process.stdout.write(utils.formatList(matchingReleases) + "\n"); + Console.info("Found the following releases:"); + Console.info(utils.formatList(matchingReleases) + ""); } if (!output) { - process.stderr.write( + Console.error( "Neither packages nor releases matching \'" + - search + "\' could be found.\n"); + search + "\' could be found."); } else { - process.stdout.write( - "To get more information on a specific item, use meteor show. \n"); + Console.info( + "To get more information on a specific item, use meteor show."); } }); @@ -1341,7 +1340,7 @@ main.registerCommand({ }); }); if (messages.hasMessages()) { - process.stderr.write("\n" + messages.formatMessages()); + Console.printMessages(messages); return 1; } @@ -1352,13 +1351,13 @@ main.registerCommand({ items.push({ name: 'cordova:' + name, description: version }); }); - process.stdout.write(utils.formatList(items)); + Console.info(utils.formatList(items)); if (newVersionsAvailable) { - process.stdout.write( + Console.info( "\n * New versions of these packages are available! " + "Run 'meteor update' to try to update\n" + - " those packages to their latest versions.\n"); + " those packages to their latest versions."); } return 0; }); @@ -1381,9 +1380,9 @@ var maybeUpdateRelease = function (options) { // We are running from checkout, so we are not updating the release. if (release.current.isCheckout()) { - process.stderr.write( + 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.\n"); +"Checking to see if we can update your packages."); return 0; } @@ -1414,9 +1413,9 @@ var maybeUpdateRelease = function (options) { // and the user ran 'meteor update' without specifying a release? We // really can't do much here. if (!latestRelease) { - process.stderr.write( + Console.error( "There are no recommended releases on release track " + - releaseTrack + ".\n"); + releaseTrack + "."); return 1; } if (! release.current || release.current.name !== latestRelease) { @@ -1453,19 +1452,19 @@ var maybeUpdateRelease = function (options) { // the requested release in the initialization code, before the // command even ran. They could equivalently have run 'meteor // help --release xyz'. - process.stdout.write( + Console.info( "Installed. Run 'meteor update' inside of a particular project\n" + "directory to update that project to Meteor " + - release.current.name + ".\n"); + release.current.name + "."); } else { // We get here if the user ran 'meteor update' and we didn't // find a new version. - process.stdout.write( + Console.info( "The latest version of Meteor, " + release.current.name + ", is already installed on this\n" + "computer. Run 'meteor update' inside of a particular project\n" + "directory to update that project to Meteor " + - release.current.name + "\n"); + release.current.name); } return 0; } @@ -1474,9 +1473,9 @@ var maybeUpdateRelease = function (options) { var appRelease = project.getMeteorReleaseVersion(); if (appRelease !== null && appRelease === release.current.name) { var maybeTheLatestRelease = release.forced ? "" : ", the latest release"; - process.stdout.write( + Console.info( "This project is already at " + - release.current.getDisplayName() + maybeTheLatestRelease + ".\n"); + release.current.getDisplayName() + maybeTheLatestRelease + "."); return 0; } @@ -1493,8 +1492,8 @@ var maybeUpdateRelease = function (options) { // release. In fact, you are doing something wrong, so we should tell you // to stop. if (appRelease == null) { - process.stderr.write( - "Cannot patch update unless a release is set.\n"); + Console.error( + "Cannot patch update unless a release is set."); return 1; } var r = appRelease.split('@'); @@ -1503,8 +1502,8 @@ var maybeUpdateRelease = function (options) { }); var updateTo = record.patchReleaseVersion; if (!updateTo) { - process.stderr.write( - "You are at the latest patch version.\n"); + Console.error( + "You are at the latest patch version."); return 0; } var patchRecord = doOrDie(function () { @@ -1519,8 +1518,8 @@ 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 ) { - process.stderr.write( - "You are at the latest patch version.\n"); + 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 @@ -1548,9 +1547,9 @@ var maybeUpdateRelease = function (options) { if (!releaseVersionsToTry.length) { // We could not find any releases newer than the one that we are on, on // that track, so we are done. - process.stdout.write( + Console.info( "This project is already at Meteor " + appRelease + - ", which is newer than the latest release.\n"); + ", which is newer than the latest release."); return 0; } } @@ -1563,7 +1562,7 @@ var maybeUpdateRelease = function (options) { previousVersions = project.getVersions({dontRunConstraintSolver: true}); }); if (messages.hasMessages()) { - process.stderr.write(messages.formatMessages()); + Console.printMessages(messages); // We couldn't figure out our current versions, so updating is not going to work. return 1; } @@ -1586,7 +1585,7 @@ var maybeUpdateRelease = function (options) { }); if (messages.hasMessages()) { if (process.env.METEOR_UPDATE_DEBUG) { - process.stderr.write( + Console.error( "Update to release " + releaseTrack + "@" + versionToTry + " is impossible:\n" + messages.formatMessages()); } @@ -1594,9 +1593,9 @@ var maybeUpdateRelease = function (options) { } } catch (e) { if (process.env.METEOR_UPDATE_DEBUG) { - process.stderr.write( + Console.error( "Update to release " + releaseTrack + - "@" + versionToTry + " impossible: " + e.message + "\n"); + "@" + versionToTry + " impossible: " + e.message ); } return false; } @@ -1605,14 +1604,14 @@ var maybeUpdateRelease = function (options) { }); if (!solutionReleaseVersion) { - process.stdout.write( + Console.info( "This project is at the latest release which is compatible with your\n" + - "current package constraints.\n"); + "current package constraints."); return 0; } else if (solutionReleaseVersion !== releaseVersionsToTry[0]) { - process.stdout.write( + Console.info( "(Newer releases are available but are not compatible with your\n" + - "current package constraints.)\n"); + "current package constraints.)"); } var solutionReleaseName = releaseTrack + '@' + solutionReleaseVersion; @@ -1633,7 +1632,7 @@ var maybeUpdateRelease = function (options) { { alwaysRecord : true }); }); if (messages.hasMessages()) { - process.stderr.write("Error while setting versions:\n" + + Console.error("Error while setting versions:\n" + messages.formatMessages()); return 1; } @@ -1641,16 +1640,16 @@ var maybeUpdateRelease = function (options) { onDiskPackages: setV.downloaded }); if (!setV.success) { - process.stderr.write("Could not install all the requested packages.\n"); + Console.error("Could not install all the requested packages."); return 1; } // Write the release to .meteor/release. project.writeMeteorReleaseVersion(solutionReleaseName); - process.stdout.write(path.basename(options.appDir) + ": updated to " + + Console.info(path.basename(options.appDir) + ": updated to " + utils.displayRelease(releaseTrack, solutionReleaseVersion) + - ".\n"); + "."); // Now run the upgraders. // XXX should we also run upgraders on other random commands, in case there @@ -1690,12 +1689,12 @@ main.registerCommand({ // Some basic checks to make sure that this command is being used correctly. if (options["packages-only"] && options["patch"]) { - process.stderr.write("There is no such thing as a patch update to packages."); + Console.error("There is no such thing as a patch update to packages."); return 1; } if (release.explicit && options["patch"]) { - process.stderr.write("You cannot patch update to a specific release."); + Console.error("You cannot patch update to a specific release."); return 1; } @@ -1740,7 +1739,7 @@ main.registerCommand({ allPackages = project.calculateCombinedConstraints(releasePackages); }); if (messages.hasMessages()) { - process.stderr.write(messages.formatMessages()); + Console.printMessages(messages); return 1; } @@ -1766,14 +1765,14 @@ main.registerCommand({ }); }); if (messages.hasMessages()) { - process.stderr.write("Error resolving constraints for packages:\n" + Console.error("Error resolving constraints for packages:\n" + messages.formatMessages()); return 1; } // Just for the sake of good messages, check to see if anything changed. if (_.isEqual(newVersions, versions)) { - process.stdout.write("All your package dependencies are already up to date.\n"); + Console.info("All your package dependencies are already up to date."); return 0; } @@ -1784,7 +1783,7 @@ main.registerCommand({ }); // XXX cleanup this madness of error handling if (messages.hasMessages()) { - process.stderr.write("Error while setting package versions:\n" + + Console.error("Error while setting package versions:\n" + messages.formatMessages()); return 1; } @@ -1799,7 +1798,7 @@ main.registerCommand({ alwaysShow: true }); if (!setV.success) { - process.stderr.write("Could not install all the requested packages.\n"); + Console.error("Could not install all the requested packages."); return 1; } return showExitCode; @@ -1815,6 +1814,7 @@ main.registerCommand({ maxArgs: Infinity, requiresApp: true }, function (options) { + // Special case on reserved package namespaces, such as 'cordova' var cordovaPlugins; try { @@ -1825,7 +1825,7 @@ main.registerCommand({ cordova.checkIsValidPlugin(plugin); }); } catch (err) { - process.stderr.write(err.message + '\n'); + Console.error(err.message + ''); return 1; } @@ -1841,7 +1841,7 @@ main.registerCommand({ project.addCordovaPlugins(pluginsDict); _.each(cordovaPlugins, function (plugin) { - process.stdout.write("added cordova plugin " + plugin + "\n"); + Console.info("added cordova plugin " + plugin); }); var args = filteredPackages.rest; @@ -1886,7 +1886,7 @@ main.registerCommand({ allPackages = project.getCurrentCombinedConstraints(); }); if (messages.hasMessages()) { - process.stderr.write(messages.formatMessages()); + Console.printMessages(messages); return 1; } @@ -1894,7 +1894,7 @@ main.registerCommand({ // Check that the package exists. doOrDie(function () { if (! catalog.complete.getPackage(constraint.name)) { - process.stderr.write(constraint.name + ": no such package\n"); + Console.error(constraint.name + ": no such package"); failed = true; return; } @@ -1907,7 +1907,7 @@ main.registerCommand({ return catalog.complete.getVersion(constraint.name, constr.version); }); if (! versionInfo) { - process.stderr.write( + Console.stderr.write( constraint.name + "@" + constr.version + ": no such version\n"); failed = true; return; @@ -1921,29 +1921,29 @@ main.registerCommand({ if (_.has(packages, constraint.name)) { if (packages[constraint.name] === constraint.constraintString) { if (constraint.constraintString) { - process.stderr.write( + Console.info( constraint.name + " with version constraint " + - constraint.constraintString + " has already been added.\n"); + constraint.constraintString + " has already been added."); } else { - process.stderr.write( + Console.info( constraint.name + - " without a version constraint has already been added.\n"); + " without a version constraint has already been added."); } } else { if (packages[constraint.name]) { - process.stdout.write( + Console.info( "Currently using " + constraint.name + " with version constraint " + packages[constraint.name] - + ".\n"); + + "."); } else { - process.stdout.write("Currently using "+ constraint.name + - " without any version constraint.\n"); + Console.info("Currently using "+ constraint.name + + " without any version constraint."); } if (constraint.constraintString) { - process.stdout.write("The version constraint will be changed to " + - constraint.constraintString + ".\n"); + Console.info("The version constraint will be changed to " + + constraint.constraintString + "."); } else { - process.stdout.write("The version constraint will be removed.\n"); + Console.info("The version constraint will be removed."); } // Now remove the old constraint from what we're going to calculate // with. (XXX: This is hacky.) @@ -2002,7 +2002,7 @@ main.registerCommand({ if ( ! newVersions) { // XXX: Better error handling. - process.stderr.write("Cannot resolve package dependencies.\n"); + Console.error("Cannot resolve package dependencies."); return; } @@ -2020,13 +2020,13 @@ main.registerCommand({ if (!e.constraintSolverError) throw e; // XXX this is too many forms of error handling! - process.stderr.write( + Console.error( "Could not satisfy all the specified constraints:\n" - + e + "\n"); + + e + ""); return 1; } if (messages.hasMessages()) { - process.stderr.write(messages.formatMessages()); + Console.printMessages(messages); return 1; } @@ -2036,16 +2036,16 @@ main.registerCommand({ // Show the user the messageLog of the packages that they installed. // (XXX: this will be rewritten pending geoff's feedback on packaging UX) - process.stdout.write("\n"); + Console.stdout.write("\n"); _.each(constraints, function (constraint) { var version = newVersions[constraint.name]; var versionRecord = doOrDie(function () { return catalog.complete.getVersion(constraint.name, version); }); - process.stdout.write(constraint.name + + Console.stdout.write(constraint.name + (versionRecord.description ? (": " + versionRecord.description) : - "") + "\n"); + "")); }); return 0; @@ -2069,7 +2069,7 @@ main.registerCommand({ project.removeCordovaPlugins(cordovaPlugins); _.each(cordovaPlugins, function (plugin) { - process.stdout.write("removed cordova plugin " + plugin + "\n"); + Console.info("removed cordova plugin " + plugin); }); var args = filteredPackages.rest; @@ -2091,11 +2091,11 @@ main.registerCommand({ var packagesToRemove = []; _.each(args, function (packageName) { if (/@/.test(packageName)) { - process.stderr.write(packageName + ": do not specify version constraints.\n"); + Console.error(packageName + ": do not specify version constraints."); } else if (! _.has(packages, packageName)) { // Check that we are using the package. We don't check if the package // exists. You should be able to remove non-existent packages. - process.stderr.write(packageName + " is not in this project.\n"); + Console.error(packageName + " is not in this project."); } else { packagesToRemove.push(packageName); } @@ -2117,7 +2117,7 @@ main.registerCommand({ var newVersions = project.getVersions(); }); if (messages.hasMessages()) { - process.stderr.write(messages.formatMessages()); + Console.printMessages(messages); return 1; } @@ -2125,7 +2125,7 @@ main.registerCommand({ // constraints that we officially removed that the project still 'depends' on, // which is why there are these two tiers of error messages. _.each(packagesToRemove, function (packageName) { - process.stdout.write("Removed top-level dependency on " + packageName + ".\n"); + Console.info("Removed top-level dependency on " + packageName + "."); }); return 0; @@ -2158,13 +2158,13 @@ main.registerCommand({ // Yay, checking that options are correct. if (options.add && options.remove) { - process.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) { - process.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; } @@ -2185,7 +2185,7 @@ main.registerCommand({ try { if (options.add) { - process.stdout.write("Adding a maintainer to " + name + "...\n"); + Console.info("Adding a maintainer to " + name + "..."); if (fullRecord.release) { packageClient.callPackageServer( conn, 'addReleaseMaintainer', name, options.add); @@ -2194,7 +2194,7 @@ main.registerCommand({ conn, 'addMaintainer', name, options.add); } } else if (options.remove) { - process.stdout.write("Removing a maintainer from " + name + "...\n"); + Console.info("Removing a maintainer from " + name + "..."); if (fullRecord.release) { packageClient.callPackageServer( conn, 'removeReleaseMaintainer', name, options.remove); @@ -2202,7 +2202,7 @@ main.registerCommand({ packageClient.callPackageServer( conn, 'removeMaintainer', name, options.remove); } - process.stdout.write(" Done!\n"); + Console.info(" Done!"); } } catch (err) { packageClient.handlePackageServerConnectionError(err); @@ -2219,12 +2219,12 @@ main.registerCommand({ record = fullRecord.record; } - process.stdout.write("\n The maintainers for " + name + " are:\n"); + Console.info("\n The maintainers for " + name + " are:"); _.each(record.maintainers, function (user) { if (! user || !user.username) - process.stdout.write("" + "\n"); + Console.info(""); else - process.stdout.write(user.username + "\n"); + Console.info(user.username + ""); }); return 0; }); @@ -2259,7 +2259,7 @@ main.registerCommand({ }); if (!release) { // XXX this could also mean package unknown. - process.stderr.write('Release unknown: ' + releaseNameAndVersion + '\n'); + Console.error('Release unknown: ' + releaseNameAndVersion + ''); return 1; } @@ -2272,11 +2272,11 @@ main.registerCommand({ }); if (!toolPkgBuilds) { // XXX this could also mean package unknown. - process.stderr.write('Tool version unknown: ' + release.tool + '\n'); + Console.error('Tool version unknown: ' + release.tool + ''); return 1; } if (!toolPkgBuilds.length) { - process.stderr.write('Tool version has no builds: ' + release.tool + '\n'); + Console.error('Tool version has no builds: ' + release.tool + ''); return 1; } @@ -2295,9 +2295,9 @@ main.registerCommand({ return osArches[0]; }); - process.stderr.write( + Console.error( 'Building bootstrap tarballs for architectures ' + - osArches.join(', ') + '\n'); + 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 () { @@ -2316,7 +2316,7 @@ main.registerCommand({ }); if (messages.hasMessages()) { - process.stderr.write("\n" + messages.formatMessages()); + Console.error("\n" + messages.formatMessages()); return 1; }; @@ -2343,7 +2343,7 @@ main.registerCommand({ track: parsed.package, version: parsed.constraint }); if (!releaseInData) { - process.stderr.write("Can't find release in data!\n"); + Console.error("Can't find release in data!"); return 3; } releaseInData.recommended = true; @@ -2381,7 +2381,7 @@ main.registerCommand({ }); }); if (messages.hasMessages()) { - process.stderr.write("\n" + messages.formatMessages()); + Console.error("\n" + messages.formatMessages()); return 1; } @@ -2424,16 +2424,16 @@ main.registerCommand({ var bannersData = fs.readFileSync(bannersFile, 'utf8'); bannersData = JSON.parse(bannersData); } catch (e) { - process.stderr.write("Could not parse banners file: "); - process.stderr.write(e.message + "\n"); + Console.error("Could not parse banners file: "); + Console.error(e.message + ""); return 1; } if (!bannersData.track) { - process.stderr.write("Banners file should have a 'track' key.\n"); + Console.error("Banners file should have a 'track' key."); return 1; } if (!bannersData.banners) { - process.stderr.write("Banners file should have a 'banners' key.\n"); + Console.error("Banners file should have a 'banners' key."); return 1; } @@ -2473,8 +2473,8 @@ main.registerCommand({ var name = release[0]; var version = release[1]; if (!version) { - process.stderr.write('\n Must specify release version (track@version)\n'); - return 1; + Console.error('\n Must specify release version (track@version)'); + return 1; } // Now let's get down to business! Fetching the thing. @@ -2483,8 +2483,8 @@ main.registerCommand({ record = catalog.official.getReleaseTrack(name); }); if (!record) { - process.stderr.write('\n There is no release track named ' + name + '\n'); - return 1; + Console.error('\n There is no release track named ' + name); + return 1; } try { @@ -2496,15 +2496,15 @@ main.registerCommand({ try { if (options.unrecommend) { - process.stdout.write("Unrecommending " + name + "@" + version + "...\n"); + Console.info("Unrecommending " + name + "@" + version + "..."); packageClient.callPackageServer(conn, 'unrecommendVersion', name, version); - process.stdout.write("Done!\n " + name + "@" + version + - " is no longer a recommended release\n"); + Console.info("Done!\n " + name + "@" + version + + " is no longer a recommended release"); } else { - process.stdout.write("Recommending " + options.args[0] + "...\n"); + Console.info("Recommending " + options.args[0] + "..."); packageClient.callPackageServer(conn, 'recommendVersion', name, version); - process.stdout.write("Done!\n " + name + "@" + version + - " is now a recommended release\n"); + Console.info("Done!\n " + name + "@" + version + + " is now a recommended release"); } } catch (err) { packageClient.handlePackageServerConnectionError(err); @@ -2529,8 +2529,8 @@ main.registerCommand({ var name = package[0]; var version = package[1]; if (!version) { - process.stderr.write('\n Must specify release version (track@version)\n'); - return 1; + Console.error('\n Must specify release version (track@version)'); + return 1; } var ecv = options.args[1]; @@ -2539,8 +2539,8 @@ main.registerCommand({ return catalog.official.getPackage(name); }); if (!record) { - process.stderr.write('\n There is no package named ' + name + '\n'); - return 1; + Console.error('\n There is no package named ' + name); + return 1; } try { @@ -2551,14 +2551,14 @@ main.registerCommand({ } try { - process.stdout.write( + Console.info( "Setting earliest compatible version on " - + options.args[0] + " to " + ecv + "...\n"); + + options.args[0] + " to " + ecv + "..."); var versionInfo = { name : name, version : version }; packageClient.callPackageServer(conn, '_setEarliestCompatibleVersion', versionInfo, ecv); - process.stdout.write("Done!\n"); + Console.info("Done!"); } catch (err) { packageClient.handlePackageServerConnectionError(err); return 1; @@ -2585,8 +2585,8 @@ main.registerCommand({ return catalog.official.getPackage(name); }); if (!record) { - process.stderr.write('\n There is no package named ' + name + '\n'); - return 1; + Console.error('\n There is no package named ' + name); + return 1; } try { @@ -2597,12 +2597,12 @@ main.registerCommand({ } try { - process.stdout.write( + Console.info( "Changing homepage on " - + name + " to " + url + "...\n"); + + name + " to " + url + "..."); packageClient.callPackageServer(conn, '_changePackageHomepage', name, url); - process.stdout.write("Done!\n"); + Console.info("Done!"); } catch (err) { packageClient.handlePackageServerConnectionError(err); return 1; diff --git a/tools/commands.js b/tools/commands.js index dc4aed66d5..90eb8670e5 100644 --- a/tools/commands.js +++ b/tools/commands.js @@ -24,6 +24,7 @@ var unipackage = require('./unipackage.js'); var cordova = require('./commands-cordova.js'); var commandsPackages = require('./commands-packages.js'); var execFileSync = require('./utils.js').execFileSync; +var Console = require('./console.js').Console; // The architecture used by Galaxy servers; it's the architecture used // by 'meteor deploy'. @@ -103,7 +104,7 @@ main.registerCommand({ if (release.current === null) { if (! options.appDir) throw new Error("missing release, but not in an app?"); - process.stderr.write( + 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"); @@ -111,7 +112,7 @@ main.registerCommand({ } if (release.current.isCheckout()) { - process.stderr.write("Unreleased (running from a checkout)\n"); + Console.stderr.write("Unreleased (running from a checkout)\n"); return 1; } @@ -124,15 +125,15 @@ main.registerCommand({ requiresRelease: false }, function (options) { if (files.inCheckout()) { - process.stderr.write("checkout\n"); + Console.stderr.write("checkout\n"); return 1; } else if (release.current === null) { // .meteor/release says "none" but not in a checkout. - process.stderr.write("none\n"); + Console.stderr.write("none\n"); return 1; } else { - process.stdout.write(release.current.name + "\n"); - process.stdout.write(files.getToolsVersion() + "\n"); + Console.stdout.write(release.current.name + "\n"); + Console.stdout.write(files.getToolsVersion() + "\n"); return 0; } }); @@ -186,7 +187,7 @@ main.registerCommand({ project.getVersions(); // #StructuredProjectInitialization }); if (messages.hasMessages()) { - process.stderr.write(messages.formatMessages()); + Console.stderr.write(messages.formatMessages()); return 1; } @@ -197,10 +198,10 @@ main.registerCommand({ var parsedHostPort = parseHostPort(options.port); } catch (err) { if (options.verbose) { - process.stderr.write('Error while parsing --port option: ' + Console.stderr.write('Error while parsing --port option: ' + err.stack + '\n'); } else { - process.stderr.write(err.message + '\n'); + Console.stderr.write(err.message + '\n'); } return 1; } @@ -227,7 +228,7 @@ main.registerCommand({ cordova.verboseLog('A run on a device requested'); // ... and if you didn't specify your ip address as host, print a warning if (parsedMobileHostPort.host === DEFAULT_BUILD_HOST) - process.stderr.write( + Console.stderr.write( "WARN: You are testing your app on a remote device but your host option\n" + "WARN: is set to 'localhost'. Perhaps you want to change it to your local\n" + "WARN: network's IP address with the -p or --mobile-port option?\n"); @@ -263,10 +264,10 @@ main.registerCommand({ runners = runners.concat(cordova.buildPlatformRunners(localPath, options.args, options)); } catch (err) { if (options.verbose) { - process.stderr.write('Error while running for mobile platforms ' + + Console.stderr.write('Error while running for mobile platforms ' + err.stack + '\n'); } else { - process.stderr.write(err.message + '\n'); + Console.stderr.write(err.message + '\n'); } return 1; } @@ -276,7 +277,7 @@ main.registerCommand({ if (options['app-port']) { var appPortMatch = options['app-port'].match(/^(?:(.+):)?([0-9]+)?$/); if (!appPortMatch) { - process.stderr.write( + 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"); return 1; @@ -344,12 +345,12 @@ main.registerCommand({ // No package examples exist yet. if (options.list && options.example) { - process.stderr.write("No package examples exist at this time.\n\n"); + Console.stderr.write("No package examples exist at this time.\n\n"); throw new main.ShowUsage; } if (!packageName) { - process.stderr.write("Please specify the name of the package. \n"); + Console.stderr.write("Please specify the name of the package. \n"); throw new main.ShowUsage; } @@ -362,7 +363,7 @@ main.registerCommand({ var inYourApp = options.appDir ? " in your app" : ""; if (fs.existsSync(packageDir)) { - process.stderr.write(packageName + ": Already exists" + inYourApp + "\n"); + Console.stderr.write(packageName + ": Already exists" + inYourApp + "\n"); return 1; } @@ -402,11 +403,11 @@ main.registerCommand({ ignore: [/^local$/] }); } catch (err) { - process.stderr.write("Could not create package: " + err.message + "\n"); + Console.stderr.write("Could not create package: " + err.message + "\n"); return 1; } - process.stdout.write(packageName + ": created" + inYourApp + "\n"); + Console.stdout.write(packageName + ": created" + inYourApp + "\n"); return 0; } @@ -434,11 +435,11 @@ main.registerCommand({ }); if (options.list) { - process.stdout.write("Available examples:\n"); + Console.stdout.write("Available examples:\n"); _.each(examples, function (e) { - process.stdout.write(" " + e + "\n"); + Console.stdout.write(" " + e + "\n"); }); - process.stdout.write("\n" + + Console.stdout.write("\n" + "Create a project from an example with 'meteor create --example '.\n"); return 0; }; @@ -452,12 +453,12 @@ main.registerCommand({ throw new main.ShowUsage; if (fs.existsSync(appPath)) { - process.stderr.write(appPath + ": Already exists\n"); + Console.stderr.write(appPath + ": Already exists\n"); return 1; } if (files.findAppDir(appPath)) { - process.stderr.write( + Console.stderr.write( "You can't create a Meteor project inside another Meteor project.\n"); return 1; } @@ -468,8 +469,8 @@ main.registerCommand({ if (options.example) { if (examples.indexOf(options.example) === -1) { - process.stderr.write(options.example + ": no such example\n\n"); - process.stderr.write("List available applications with 'meteor create --list'.\n"); + Console.stderr.write(options.example + ": no such example\n\n"); + Console.stderr.write("List available applications with 'meteor create --list'.\n"); return 1; } else { files.cp_r(path.join(exampleDir, options.example), appPath, { @@ -509,7 +510,7 @@ main.registerCommand({ project.appendFinishedUpgrader(upgrader); }); - var messages = buildmessage.capture(function () { + var messages = buildmessage.capture({ title: "Updating dependencies" }, function () { // Run the constraint solver. Override the assumption that using '--release' // means we shouldn't update .meteor/versions. project._ensureDepsUpToDate({alwaysRecord: true}); @@ -517,16 +518,19 @@ main.registerCommand({ if (messages.hasMessages()) { - process.stderr.write(messages.formatMessages()); + Console.stderr.write(messages.formatMessages()); return 1; } - process.stdout.write(appPath + ": created"); - if (options.example && options.example !== appPath) - process.stderr.write(" (from '" + options.example + "' template)"); - process.stdout.write(".\n\n"); + { + var message = appPath + ": created"; + if (options.example && options.example !== appPath) + message += (" (from '" + options.example + "' template)"); + message += ".\n"; + Console.info(message); + } - process.stdout.write( + Console.stdout.write( "To run your new app:\n" + " cd " + appPath + "\n" + " meteor\n"); @@ -588,8 +592,8 @@ main.registerCommand(_.extend({ name: 'build' }, buildCommands), ["os.osx.x86_64", "os.linux.x86_64", "os.linux.x86_32"]; if (options.architecture && _.indexOf(VALID_ARCHITECTURES, options.architecture) === -1) { - process.stderr.write("Invalid architecture: " + options.architecture + "\n"); - process.stderr.write( + Console.stderr.write("Invalid architecture: " + options.architecture + "\n"); + Console.stderr.write( "Please use one of the following: " + VALID_ARCHITECTURES + "\n"); return 1; } @@ -603,11 +607,10 @@ main.registerCommand(_.extend({ name: 'build' }, buildCommands), var appName = path.basename(options.appDir); if (! _.isEmpty(mobilePlatforms)) { - try { var parsedHostPort = parseHostPort(options.port); } catch (err) { - process.stderr.write(err.message); + Console.stderr.write(err.message); return 1; } @@ -638,8 +641,8 @@ main.registerCommand(_.extend({ name: 'build' }, buildCommands), loader = project.getPackageLoader(); }); if (messages.hasMessages()) { - process.stderr.write("Errors prevented bundling your app:\n"); - process.stderr.write(messages.formatMessages()); + Console.stderr.write("Errors prevented bundling your app:\n"); + Console.stderr.write(messages.formatMessages()); return 1; } @@ -647,7 +650,7 @@ main.registerCommand(_.extend({ name: 'build' }, buildCommands), stats.recordPackages("sdk.bundle"); }); if (statsMessages.hasMessages()) { - process.stdout.write("Error recording package list:\n" + + Console.stdout.write("Error recording package list:\n" + statsMessages.formatMessages()); // ... but continue; } @@ -667,8 +670,8 @@ main.registerCommand(_.extend({ name: 'build' }, buildCommands), } }); if (bundleResult.errors) { - process.stderr.write("Errors prevented bundling:\n"); - process.stderr.write(bundleResult.errors.formatMessages()); + Console.stderr.write("Errors prevented bundling:\n"); + Console.stderr.write(bundleResult.errors.formatMessages()); return 1; } @@ -679,7 +682,7 @@ main.registerCommand(_.extend({ name: 'build' }, buildCommands), files.createTarball(path.join(buildDir, 'bundle'), outputTar); } catch (err) { console.log(JSON.stringify(err)); - process.stderr.write("Couldn't create tarball\n"); + Console.stderr.write("Couldn't create tarball\n"); } } @@ -699,7 +702,7 @@ main.registerCommand(_.extend({ name: 'build' }, buildCommands), main.registerCommand(_.extend({ name: 'bundle', hidden: true }, buildCommands), function (options) { cordova.setVerboseness(options.verbose); - process.stdout.write("WARNING: 'bundle' has been deprecated. " + + Console.stdout.write("WARNING: 'bundle' has been deprecated. " + "Use 'build' instead.\n"); // XXX if they pass a file that doesn't end in .tar.gz or .tgz, add // the former for them @@ -718,8 +721,8 @@ main.registerCommand(_.extend({ name: 'bundle', hidden: true }, buildCommands), ["os.osx.x86_64", "os.linux.x86_64", "os.linux.x86_32"]; if (options.architecture && _.indexOf(VALID_ARCHITECTURES, options.architecture) === -1) { - process.stderr.write("Invalid architecture: " + options.architecture + "\n"); - process.stderr.write( + Console.stderr.write("Invalid architecture: " + options.architecture + "\n"); + Console.stderr.write( "Please use one of the following: " + VALID_ARCHITECTURES + "\n"); return 1; } @@ -735,8 +738,8 @@ main.registerCommand(_.extend({ name: 'bundle', hidden: true }, buildCommands), loader = project.getPackageLoader(); }); if (messages.hasMessages()) { - process.stderr.write("Errors prevented bundling your app:\n"); - process.stderr.write(messages.formatMessages()); + Console.stderr.write("Errors prevented bundling your app:\n"); + Console.stderr.write(messages.formatMessages()); return 1; } @@ -744,7 +747,7 @@ main.registerCommand(_.extend({ name: 'bundle', hidden: true }, buildCommands), stats.recordPackages("sdk.bundle"); }); if (statsMessages.hasMessages()) { - process.stdout.write("Error recording package list:\n" + + Console.stdout.write("Error recording package list:\n" + statsMessages.formatMessages()); // ... but continue; } @@ -763,8 +766,8 @@ main.registerCommand(_.extend({ name: 'bundle', hidden: true }, buildCommands), } }); if (bundleResult.errors) { - process.stderr.write("Errors prevented bundling:\n"); - process.stderr.write(bundleResult.errors.formatMessages()); + Console.stderr.write("Errors prevented bundling:\n"); + Console.stderr.write(bundleResult.errors.formatMessages()); return 1; } @@ -773,7 +776,7 @@ main.registerCommand(_.extend({ name: 'bundle', hidden: true }, buildCommands), files.createTarball(path.join(buildDir, 'bundle'), outputPath); } catch (err) { console.log(JSON.stringify(err)); - process.stderr.write("Couldn't create tarball\n"); + Console.stderr.write("Couldn't create tarball\n"); } } files.rm_recursive(buildDir); @@ -806,7 +809,7 @@ main.registerCommand({ // specified? if (! mongoPort) { - process.stdout.write( + 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" + @@ -862,7 +865,7 @@ main.registerCommand({ requiresApp: true }, function (options) { if (options.args.length !== 0) { - process.stderr.write( + Console.stderr.write( "meteor reset only affects the locally stored database.\n" + "\n" + "To reset a deployed application use\n" + @@ -879,7 +882,7 @@ main.registerCommand({ require(path.join(__dirname, 'run-mongo.js')).findMongoPort; var isRunning = !! findMongoPort(options.appDir); if (isRunning) { - process.stderr.write( + Console.stderr.write( "reset: Meteor is running.\n" + "\n" + "This command does not work while Meteor is running your application.\n" + @@ -890,7 +893,7 @@ main.registerCommand({ var localDir = path.join(options.appDir, '.meteor', 'local'); files.rm_recursive(localDir); - process.stdout.write("Project reset.\n"); + Console.stdout.write("Project reset.\n"); }); /////////////////////////////////////////////////////////////////////////////// @@ -945,19 +948,19 @@ main.registerCommand({ // issues are concurrency-related, or possibly some weird order-of-execution // of interaction that we are failing to understand. This seems to fix it in a // clear and understandable fashion.) - var messages = buildmessage.capture(function () { + var messages = buildmessage.capture({ title: "Resolving versions" }, function () { project.getVersions(); // #StructuredProjectInitialization }); if (messages.hasMessages()) { - process.stderr.write(messages.formatMessages()); + Console.stderr.write(messages.formatMessages()); return 1; } if (options.password) { if (useGalaxy) { - process.stderr.write("Galaxy does not support --password.\n"); + Console.stderr.write("Galaxy does not support --password.\n"); } else { - process.stderr.write( + 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" + @@ -969,14 +972,14 @@ main.registerCommand({ var starball = options.star; if (starball && ! useGalaxy) { // XXX it would be nice to support this for non-Galaxy deploys too - process.stderr.write( + Console.stderr.write( "--star: only supported when deploying to Galaxy.\n"); return 1; } var loggedIn = auth.isLoggedIn(); if (! loggedIn) { - process.stderr.write( + Console.stderr.write( "To instantly deploy your app on a free testing server, just enter your\n" + "email address!\n" + "\n"); @@ -988,9 +991,9 @@ main.registerCommand({ // Override architecture iff applicable. var buildArch = DEPLOY_ARCH; if (options['override-architecture-with-local']) { - process.stdout.write( + Console.stdout.write( "\n => WARNING: OVERRIDING DEPLOY ARCHITECTURE WITH LOCAL ARCHITECTURE\n"); - process.stdout.write( + Console.stdout.write( " => If your app contains binary code, it may break terribly and you will be sad.\n\n"); buildArch = archinfo.host(); } @@ -1081,13 +1084,13 @@ main.registerCommand({ }, function (options) { if (options.add && options.remove) { - process.stderr.write( + Console.stderr.write( "Sorry, you can only add or remove one user at a time.\n"); return 1; } if ((options.add || options.remove) && options.list) { - process.stderr.write( + Console.stderr.write( "Sorry, you can't change the users at the same time as you're listing them.\n"); return 1; } @@ -1097,7 +1100,7 @@ main.registerCommand({ var site = qualifySitename(options.args[0]); if (hostedWithGalaxy(site)) { - process.stderr.write( + 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"); @@ -1105,7 +1108,7 @@ main.registerCommand({ } if (! auth.isLoggedIn()) { - process.stderr.write( + Console.stderr.write( "You must be logged in for that. Try 'meteor login'.\n"); return 1; } @@ -1132,7 +1135,7 @@ main.registerCommand({ var site = qualifySitename(options.args[0]); if (! auth.isLoggedIn()) { - process.stderr.write( + 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"); @@ -1140,7 +1143,7 @@ main.registerCommand({ } if (hostedWithGalaxy(site)) { - process.stderr.write( + Console.stderr.write( "Sorry, you can't claim sites that are hosted on Galaxy.\n"); return 1; } @@ -1194,7 +1197,7 @@ main.registerCommand({ try { var parsedHostPort = parseHostPort(options.port); } catch (err) { - process.stderr.write(err.message); + Console.stderr.write(err.message); return 1; } @@ -1213,7 +1216,7 @@ main.registerCommand({ localPackages = packages.localPackages; options.localPackageNames = packages.localPackages; } catch (err) { - process.stderr.write('\n' + err.message); + Console.stderr.write('\n' + err.message); return 1; } @@ -1276,7 +1279,7 @@ main.registerCommand({ runners = runners.concat(cordova.buildPlatformRunners( localPath, mobilePlatforms, options)); } catch (err) { - process.stderr.write(err.message + '\n'); + Console.stderr.write(err.message + '\n'); return 1; } } @@ -1378,7 +1381,7 @@ var getPackagesForTest = function (packages) { }); if (messages.hasMessages()) { - process.stderr.write("\n" + messages.formatMessages()); + Console.stderr.write("\n" + messages.formatMessages()); return 1; } } @@ -1416,7 +1419,7 @@ var runTestAppForPackages = function (testPackages, testRunnerAppDir, options) { }); }); if (messages.hasMessages()) { - process.stderr.write(messages.formatMessages()); + Console.stderr.write(messages.formatMessages()); return 1; } project.forceEditPackages(tests, 'add'); @@ -1429,7 +1432,7 @@ var runTestAppForPackages = function (testPackages, testRunnerAppDir, options) { }); if (messages.hasMessages()) { - process.stderr.write(messages.formatMessages()); + Console.stderr.write(messages.formatMessages()); return 1; } @@ -1519,7 +1522,7 @@ main.registerCommand({ if (count) console.log("Built " + count + " packages."); if (messages.hasMessages()) { - process.stderr.write("\n" + messages.formatMessages()); + Console.stderr.write("\n" + messages.formatMessages()); return 1; } }); @@ -1571,18 +1574,18 @@ main.registerCommand({ var loggedInAccountsConnectionOrPrompt = function (action) { var token = auth.getSessionToken(config.getAccountsDomain()); if (! token) { - process.stderr.write("You must be logged in to " + action + ".\n"); + Console.stderr.write("You must be logged in to " + action + ".\n"); auth.doUsernamePasswordLogin({ retry: true }); - process.stdout.write("\n"); + Console.stdout.write("\n"); } token = auth.getSessionToken(config.getAccountsDomain()); var conn = auth.loggedInAccountsConnection(token); if (conn === null) { // Server rejected our token. - process.stderr.write("You must be logged in to " + action + ".\n"); + Console.stderr.write("You must be logged in to " + action + ".\n"); auth.doUsernamePasswordLogin({ retry: true }); - process.stdout.write("\n"); + Console.stdout.write("\n"); token = auth.getSessionToken(config.getAccountsDomain()); conn = auth.loggedInAccountsConnection(token); } @@ -1599,9 +1602,9 @@ main.registerCommand({ var token = auth.getSessionToken(config.getAccountsDomain()); if (! token) { - process.stderr.write("You must be logged in to list your organizations.\n"); + Console.stderr.write("You must be logged in to list your organizations.\n"); auth.doUsernamePasswordLogin({ retry: true }); - process.stdout.write("\n"); + Console.stdout.write("\n"); } var url = config.getAccountsApiUrl() + "/organizations"; @@ -1614,13 +1617,13 @@ main.registerCommand({ }); var body = JSON.parse(result.body); } catch (err) { - process.stderr.write("Error listing organizations.\n"); + Console.stderr.write("Error listing organizations.\n"); return 1; } if (result.response.statusCode === 401 && body && body.error === "invalid_credential") { - process.stderr.write("You must be logged in to list your organizations.\n"); + Console.stderr.write("You must be logged in to list your organizations.\n"); // XXX It would be nice to do a username/password prompt here like // we do for the other orgs commands. return 1; @@ -1628,14 +1631,14 @@ main.registerCommand({ if (result.response.statusCode !== 200 || ! body || ! body.organizations) { - process.stderr.write("Error listing organizations.\n"); + Console.stderr.write("Error listing organizations.\n"); return 1; } if (body.organizations.length === 0) { - process.stdout.write("You are not a member of any organizations.\n"); + Console.stdout.write("You are not a member of any organizations.\n"); } else { - process.stdout.write(_.pluck(body.organizations, "name").join("\n") + "\n"); + Console.stdout.write(_.pluck(body.organizations, "name").join("\n") + "\n"); } return 0; }); @@ -1652,7 +1655,7 @@ main.registerCommand({ }, function (options) { if (options.add && options.remove) { - process.stderr.write( + Console.stderr.write( "Sorry, you can only add or remove one member at a time.\n"); throw new main.ShowUsage; } @@ -1671,13 +1674,13 @@ main.registerCommand({ options.add ? "addOrganizationMember": "removeOrganizationMember", options.args[0], username); } catch (err) { - process.stderr.write("Error " + + Console.stderr.write("Error " + (options.add ? "adding" : "removing") + " member: " + err.reason + "\n"); return 1; } - process.stdout.write(username + " " + + Console.stdout.write(username + " " + (options.add ? "added to" : "removed from") + " organization " + options.args[0] + ".\n"); } else { @@ -1685,14 +1688,14 @@ main.registerCommand({ try { var result = conn.call("showOrganization", options.args[0]); } catch (err) { - process.stderr.write("Error showing organization: " + + Console.stderr.write("Error showing organization: " + err.reason + "\n"); return 1; } var members = _.pluck(result, "username"); - process.stdout.write(members.join("\n") + "\n"); + Console.stdout.write(members.join("\n") + "\n"); } return 0; @@ -1739,7 +1742,7 @@ main.registerCommand({ } catch (e) { if (!(e instanceof SyntaxError)) throw e; - process.stderr.write("Bad regular expression: " + options.args[0] + "\n"); + Console.stderr.write("Bad regular expression: " + options.args[0] + "\n"); return 1; } } @@ -1770,7 +1773,7 @@ main.registerCommand({ }, function (options) { auth.pollForRegistrationCompletion(); if (! auth.isLoggedIn()) { - process.stderr.write( + Console.stderr.write( "You must be logged in for that. Try 'meteor login'.\n"); return 1; } @@ -1803,10 +1806,10 @@ main.registerCommand({ return 'none'; }; - process.stdout.write(p('email') + " " + p('port') + " " + p('changed') + + Console.stdout.write(p('email') + " " + p('port') + " " + p('changed') + " " + p('args') + "\n"); if (options.url) - process.stdout.write('url\n'); + Console.stdout.write('url\n'); if (options['delete']) - process.stdout.write('delete\n'); + Console.stdout.write('delete\n'); }); diff --git a/tools/compiler.js b/tools/compiler.js index b412c92383..91855c478a 100644 --- a/tools/compiler.js +++ b/tools/compiler.js @@ -797,7 +797,7 @@ compiler.compile = function (packageSource, options) { versions: buildTimeDeps.pluginDependencies[info.name], catalog: packageSource.catalog }); - loader.downloadMissingPackages({serverArch: archinfo.host()}); + loader.downloadMissingPackages({serverArch: archinfo.host() }); var buildResult = bundler.buildJsImage({ name: info.name, diff --git a/tools/console.js b/tools/console.js new file mode 100644 index 0000000000..ac3cdc49ed --- /dev/null +++ b/tools/console.js @@ -0,0 +1,301 @@ +/// +/// utility functions for formatting output to the screen +/// + +var _ = require('underscore'); +var Fiber = require('fibers'); +var Future = require('fibers/future'); +var ProgressBar = require('progress'); +var buildmessage = require('./buildmessage.js'); +// XXX: Are we happy with chalk (and its sub-dependencies)? +var chalk = require('chalk'); + +PROGRESS_DEBUG = !!process.env.METEOR_PROGRESS_DEBUG; +USE_PRETTY = (process.env.METEOR_PRETTY_OUTPUT != '0'); + +var Console = function (options) { + var self = this; + + options = options || {}; + + self._progressBar = null; + self._progressBarText = null; + self._watching = null; + + self._lastStatusPoll = 0; + + // 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; +}; + + +PROGRESS_BAR_WIDTH = 20; +PROGRESS_BAR_FORMAT = '[:bar] :percent :etas'; +STATUS_POSITION = PROGRESS_BAR_WIDTH + 15; +STATUS_MAX_LENGTH = 40; + + +// Message to show when we don't know what we're doing +// XXX: ? FALLBACK_STATUS = 'Pondering'; +FALLBACK_STATUS = ''; + +// This function returns a future which resolves after a timeout. This +// demonstrates manually resolving futures. +function sleep(ms) { + var future = new Future; + setTimeout(function() { + future.return(); + }, ms); + return future.wait(); +}; + +LEVEL_CODE_ERROR = 4; +LEVEL_CODE_WARN = 3; +LEVEL_CODE_INFO = 2; +LEVEL_CODE_DEBUG = 1; + +LEVEL_ERROR = { code: LEVEL_CODE_ERROR }; +LEVEL_WARN = { code: LEVEL_CODE_WARN }; +LEVEL_INFO = { code: LEVEL_CODE_INFO }; +LEVEL_DEBUG = { code: LEVEL_CODE_DEBUG }; + +_.extend(Console.prototype, { + hideProgressBar: function () { + var self = this; + + if (!self._progressBar) { + return; + } + self._progressBar.terminate(); + }, + + _renderProgressBar: function () { + var self = this; + if (self._progressBar) { + self._progressBar.render(); + if (self._progressBarText) { + var text = self._progressBarText; + if (text > STATUS_MAX_LENGTH) { + text = text.substring(0, STATUS_MAX_LENGTH - 3) + "..."; + } else { + while (text.length < STATUS_MAX_LENGTH) { + text = text + ' '; + } + } + self._stream.cursorTo(STATUS_POSITION); + self._stream.write(chalk.bold(text)); + } + } + }, + + _statusPoll: function () { + var self = this; + + self._lastStatusPoll = Date.now(); + + var rootProgress = buildmessage.getRootProgress(); + if (PROGRESS_DEBUG) { + rootProgress.dump(process.stdout, {skipDone: true}); + } + var current = (rootProgress ? rootProgress.getCurrentProgress() : null); + if (self._watching === current) { + return; + } + + self._watching = current; + var title = (current != null ? current._title : null) || FALLBACK_STATUS; + if (title != self._progressBarText) { + self._progressBarText = title; + self._renderProgressBar(); + } + + self._watchProgress(); + }, + + statusPollMaybe: function () { + var self = this; + var now = Date.now(); + + if ((now - self._lastStatusPoll) < 50) { + return; + } + self._statusPoll(); + }, + + enableStatusPoll: function () { + var self = this; + Fiber(function () { + while (true) { + sleep(10); + + self._statusPoll(); + } + }).run(); + }, + + info: function(/*arguments*/) { + var self = this; + + var message = self._format(arguments); + self._print(LEVEL_INFO, message); + }, + + warn: function(/*arguments*/) { + var self = this; + + var message = self._format(arguments); + self._print(LEVEL_WARN, message); + }, + + error: function(/*arguments*/) { + var self = this; + + var message = self._format(arguments); + self._print(LEVEL_ERROR, message); + }, + + _legacyWrite: function (level, message) { + var self = this; + if(message.substr(-1) == '\n') { + message = message.substr(0, message.length - 1); + } + self._print(level, message); + }, + + _print: function(level, message) { + var self = this; + + var progressBar = self._progressBar; + if (progressBar) { + //progressBar.terminate(); + self._stream.clearLine(); + self._stream.cursorTo(0); + } + + var dest = process.stdout; + var style = null; + + if (level && USE_PRETTY) { + switch (level.code) { + case LEVEL_CODE_ERROR: + dest = process.stderr; + style = chalk.bold.red; + break; + case LEVEL_CODE_WARN: + dest = process.stderr; + style = chalk.red; + break; + //case LEVEL_CODE_INFO: + // style = chalk.blue; + // break; + } + } + + if (style) { + dest.write(style(message + '\n')); + } else { + dest.write(message + '\n'); + } + + if (progressBar) { + self._renderProgressBar(); + } + }, + + _format: function (logArguments) { + var self = this; + + var message = ''; + var format = logArguments[0]; + message = format; + return message; + }, + + printMessages: function (messages) { + var self = this; + + if (messages.hasMessages()) { + self._print(null, "\n" + messages.formatMessages()); + } + }, + + showProgressBar: function () { + var self = this; + + if (self._progressBar) { + return; + } + + if (!self._stream.isTTY || !USE_PRETTY) return; + + var options = { + complete: '=', + incomplete: ' ', + width: PROGRESS_BAR_WIDTH, + total: 100, + clear: true, + stream: self._stream + }; + + var progressBar = new ProgressBar(PROGRESS_BAR_FORMAT, options); + progressBar.start = new Date; + + self._progressBar = progressBar; + }, + + _watchProgress: function () { + var self = this; + + var progress = self._watching; + if (!progress) return; + + progress.addWatcher(function (state) { + //console.log(state); + if (progress != self._watching) { + // No longer active + //console.log("NOT WATCHING"); + return; + } + + var progressBar = self._progressBar; + if (!progressBar) { + //console.log("NOT PROGRESS BAR"); + // Progress bar disabled + return; + } + + var fraction; + if (state.done) { + fraction = 1.0; + } else { + var current = state.current; + var end = state.end; + if (end === undefined || end == 0 || current == 0) { + // Arbitrary end-point + fraction = progressBar.curr / 100; + } else { + fraction = current / end; + } + } + + if (!isNaN(fraction) && fraction >= 0) { + progressBar.curr = Math.floor(fraction * progressBar.total); + self._renderProgressBar(); + } + }); + } + +}); + +Console.warning = Console.warn; + +exports.Console = new Console; \ No newline at end of file diff --git a/tools/deploy.js b/tools/deploy.js index be71b9801e..97ec87c53e 100644 --- a/tools/deploy.js +++ b/tools/deploy.js @@ -15,6 +15,7 @@ var utils = require('./utils.js'); var _ = require('underscore'); var Future = require('fibers/future'); var stats = require('./stats.js'); +var Console = require('./console.js').Console; // Make a synchronous RPC to the "classic" MDG deploy API. The deploy // API has the following contract: @@ -65,6 +66,7 @@ var deployRpc = function (options) { if (options.headers.cookie) throw new Error("sorry, can't combine cookie headers yet"); + var progress = buildmessage.addChildTracker("Uploading"); try { var result = httpHelpers.request(_.extend(options, { url: config.getDeployUrl() + '/' + options.operation + @@ -72,13 +74,16 @@ var deployRpc = function (options) { method: options.method || 'GET', bodyStream: options.bodyStream, useAuthHeader: true, - encoding: 'utf8' // Hack, but good enough for the deploy server.. + encoding: 'utf8', // Hack, but good enough for the deploy server.. + progress: progress })); } catch (e) { return { statusCode: null, errorMessage: "Connection error (" + e.message + ")" }; + } finally { + progress.reportProgressDone(); } var response = result.response; @@ -254,7 +259,7 @@ var authedRpc = function (options) { // password-protected app, instruct them to claim it with 'meteor // claim'. var printLegacyPasswordMessage = function (site) { - process.stderr.write( + 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" + @@ -267,7 +272,7 @@ var printLegacyPasswordMessage = function (site) { // --add' or switch accounts. var printUnauthorizedMessage = function () { var username = auth.loggedInUsername(); - process.stderr.write( + 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" + @@ -288,7 +293,7 @@ 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) { - process.stdout.write( + 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"); @@ -302,14 +307,14 @@ site + " is too long.\n" + var parsed = require('url').parse(url); if (! parsed.hostname) { - process.stdout.write( + Console.stdout.write( "Please specify a domain to connect to, such as www.example.com or\n" + "http://www.example.com/\n"); return false; } if (parsed.pathname != '/' || parsed.hash || parsed.query) { - process.stdout.write( + 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"); return false; @@ -362,14 +367,14 @@ var bundleAndDeploy = function (options) { }); if (preflight.errorMessage) { - process.stderr.write("\nError deploying application: " + + Console.stderr.write("\nError deploying application: " + preflight.errorMessage + "\n"); return 1; } if (preflight.protection === "password") { printLegacyPasswordMessage(site); - process.stderr.write("If it's not your site, please try a different name!\n"); + Console.stderr.write("If it's not your site, please try a different name!\n"); return 1; } else if (preflight.protection === "account" && @@ -381,7 +386,7 @@ var bundleAndDeploy = function (options) { var buildDir = path.join(options.appDir, '.meteor', 'local', 'build_tar'); var bundlePath = path.join(buildDir, 'bundle'); - process.stdout.write('Deploying to ' + site + '. Bundling...\n'); + Console.stdout.write('Deploying to ' + site + '. Bundling...\n'); var settings = null; var messages = buildmessage.capture({ @@ -396,11 +401,11 @@ var bundleAndDeploy = function (options) { var bundler = require('./bundler.js'); if (options.recordPackageUsage) { - var statsMessages = buildmessage.capture(function () { + var statsMessages = buildmessage.capture({ title: 'Reporting statistics' }, function () { stats.recordPackages("sdk.deploy", site); }); if (statsMessages.hasMessages()) { - process.stdout.write("Error recording package list:\n" + + Console.stdout.write("Error recording package list:\n" + statsMessages.formatMessages()); // ... but continue; } @@ -416,25 +421,29 @@ var bundleAndDeploy = function (options) { } if (messages.hasMessages()) { - process.stdout.write("\nErrors prevented deploying:\n"); - process.stdout.write(messages.formatMessages()); + Console.stdout.write("\nErrors prevented deploying:\n"); + Console.stdout.write(messages.formatMessages()); return 1; } - process.stdout.write('Uploading...\n'); + Console.stdout.write('Uploading...\n'); - var result = authedRpc({ - method: 'POST', - operation: 'deploy', - site: site, - qs: settings !== null ? { settings: settings } : {}, - bodyStream: files.createTarGzStream(path.join(buildDir, 'bundle')), - expectPayload: ['url'], - preflightPassword: preflight.preflightPassword + var result; + buildmessage.enterJob({ title: "Uploading" }, function () { + result = authedRpc({ + method: 'POST', + operation: 'deploy', + site: site, + qs: settings !== null ? {settings: settings} : {}, + bodyStream: files.createTarGzStream(path.join(buildDir, 'bundle')), + expectPayload: ['url'], + preflightPassword: preflight.preflightPassword + }); }); + if (result.errorMessage) { - process.stderr.write("\nError deploying application: " + + Console.stderr.write("\nError deploying application: " + result.errorMessage + "\n"); return 1; } @@ -442,7 +451,7 @@ var bundleAndDeploy = function (options) { var deployedAt = require('url').parse(result.payload.url); var hostname = deployedAt.hostname; - process.stdout.write('Now serving at ' + hostname + '\n'); + Console.stdout.write('Now serving at ' + hostname + '\n'); files.rm_recursive(buildDir); if (! hostname.match(/meteor\.com$/)) { @@ -451,11 +460,11 @@ 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') { - process.stdout.write('-------------\n'); - process.stdout.write("You've deployed to a custom domain.\n"); - process.stdout.write("Please be sure to CNAME your hostname to origin.meteor.com,\n"); - process.stdout.write("or set an A record to 107.22.210.133.\n"); - process.stdout.write('-------------\n'); + 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'); } }); } @@ -478,12 +487,12 @@ var deleteApp = function (site) { }); if (result.errorMessage) { - process.stderr.write("Couldn't delete application: " + + Console.stderr.write("Couldn't delete application: " + result.errorMessage + "\n"); return 1; } - process.stdout.write("Deleted.\n"); + Console.stdout.write("Deleted.\n"); return 0; }; @@ -504,7 +513,7 @@ var checkAuthThenSendRpc = function (site, operation, what) { }); if (preflight.errorMessage) { - process.stderr.write("Couldn't " + what + ": " + + Console.stderr.write("Couldn't " + what + ": " + preflight.errorMessage + "\n"); return null; } @@ -529,7 +538,7 @@ 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. - process.stderr.write( + 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" + @@ -537,7 +546,7 @@ var checkAuthThenSendRpc = function (site, operation, what) { return null; } } else { // User is logged in but not authorized for this app - process.stderr.write("\n"); + Console.stderr.write("\n"); printUnauthorizedMessage(); return null; } @@ -553,7 +562,7 @@ var checkAuthThenSendRpc = function (site, operation, what) { }); if (result.errorMessage) { - process.stderr.write("Couldn't " + what + ": " + + Console.stderr.write("Couldn't " + what + ": " + result.errorMessage + "\n"); return null; } @@ -589,7 +598,7 @@ var logs = function (site) { if (result === null) { return 1; } else { - process.stdout.write(result.message); + Console.stdout.write(result.message); auth.maybePrintRegistrationLink({ leadingNewline: true }); return 0; } @@ -606,33 +615,33 @@ var listAuthorized = function (site) { expectPayload: [] }); if (result.errorMessage) { - process.stderr.write("Couldn't get authorized users list: " + + Console.stderr.write("Couldn't get authorized users list: " + result.errorMessage + "\n"); return 1; } var info = result.payload; if (! _.has(info, 'protection')) { - process.stdout.write("\n"); + Console.stdout.write("\n"); return 0; } if (info.protection === "password") { - process.stdout.write("\n"); + Console.stdout.write("\n"); return 0; } if (info.protection === "account") { if (! _.has(info, 'authorized')) { - process.stderr.write("Couldn't get authorized users list: " + + Console.stderr.write("Couldn't get authorized users list: " + "You are not authorized\n"); return 1; } - process.stdout.write((auth.loggedInUsername() || "") + "\n"); + Console.stdout.write((auth.loggedInUsername() || "") + "\n"); _.each(info.authorized, function (username) { if (username) - process.stdout.write(username + "\n"); + Console.stdout.write(username + "\n"); }); return 0; } @@ -654,12 +663,12 @@ var changeAuthorized = function (site, action, username) { }); if (result.errorMessage) { - process.stderr.write("Couldn't change authorized users: " + + Console.stderr.write("Couldn't change authorized users: " + result.errorMessage + "\n"); return 1; } - process.stdout.write(site + ": " + + Console.stdout.write(site + ": " + (action === "add" ? "added " : "removed ") + username + "\n"); return 0; @@ -680,7 +689,7 @@ var claim = function (site) { }); if (infoResult.statusCode === 404) { - process.stderr.write( + 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"); return 1; @@ -688,15 +697,15 @@ var claim = function (site) { if (infoResult.payload && infoResult.payload.protection === "account") { if (infoResult.payload.authorized) - process.stderr.write("That site already belongs to you.\n"); + Console.stderr.write("That site already belongs to you.\n"); else - process.stderr.write("Sorry, that site belongs to someone else.\n"); + Console.stderr.write("Sorry, that site belongs to someone else.\n"); return 1; } if (infoResult.payload && infoResult.payload.protection === "password") { - process.stdout.write( + Console.stdout.write( "To claim this site and transfer it to your account, enter the\n" + "site password one last time.\n\n"); } @@ -712,18 +721,18 @@ var claim = function (site) { auth.pollForRegistrationCompletion(); if (! auth.loggedInUsername() && auth.registrationUrl()) { - process.stderr.write( + 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"); } else { - process.stderr.write("Couldn't claim site: " + + Console.stderr.write("Couldn't claim site: " + result.errorMessage + "\n"); } return 1; } - process.stdout.write( + Console.stdout.write( site + ": " + "successfully transferred to your account.\n" + "\n" + "Show authorized users with:\n" + @@ -747,7 +756,7 @@ var listSites = function () { }); if (result.errorMessage) { - process.stderr.write("Couldn't list sites: " + + Console.stderr.write("Couldn't list sites: " + result.errorMessage + "\n"); return 1; } @@ -755,11 +764,11 @@ var listSites = function () { if (! result.payload || ! result.payload.sites || ! result.payload.sites.length) { - process.stdout.write("You don't have any sites yet.\n"); + Console.stdout.write("You don't have any sites yet.\n"); } else { result.payload.sites.sort(); _.each(result.payload.sites, function (site) { - process.stdout.write(site + "\n"); + Console.stdout.write(site + "\n"); }); } return 0; diff --git a/tools/http-helpers.js b/tools/http-helpers.js index 7fde67e432..96f03aa338 100644 --- a/tools/http-helpers.js +++ b/tools/http-helpers.js @@ -13,6 +13,52 @@ var auth = require('./auth.js'); var config = require('./config.js'); var release = require('./release.js'); + +// Helper that tracks bytes written to a writable +var WritableWithProgress = function (writable, listener) { + var self = this; + self._inner = writable; + self._listener = listener; +}; + +_.extend(WritableWithProgress.prototype, { + write: function (chunk, encoding, callback) { + var self = this; + self._listener(chunk.length, false); + return self._inner.write(chunk, encoding); + }, + + end: function (chunk, encoding, callback) { + var self = this; + self._listener(chunk ? chunk.length : 0, true); + return self._inner.end(chunk, encoding); + }, + + _progress: function (n, done) { + var self = this; + + var state = self._state; + state.current += n; + if (done) { + state.current.done = true; + } + self.progress.reportProgress(state); + }, + + on: function (name, callback) { + return this._inner.on(name, callback); + }, + + once: function () { + return this._inner.once.apply(this._inner, arguments); + }, + + emit: function () { + return this._inner.emit.apply(this._inner, arguments); + } +}); + + // Compose a User-Agent header. var getUserAgent = function () { var version; @@ -85,6 +131,16 @@ _.extend(exports, { delete options.bodyStream; } + var progress = null; + if (_.has(options, 'progress')) { + progress = options.progress; + delete options.progress; + + if (callback) { + throw new Error("Not safe to use progress with callback"); + } + } + options.headers = _.extend({ 'User-Agent': getUserAgent() }, options.headers || {}); @@ -161,13 +217,84 @@ _.extend(exports, { var request = require('request'); var req = request(options, callback); - if (bodyStream) - bodyStream.pipe(req); + var bodyStreamLength = 0; + if (bodyStream) { + // XXX + bodyStreamLength += 4000000; + } + // XXX + var responseLength = 128 * 1024; - if (fut) - return fut.wait(); - else + var totalProgress = { current: 0, end: bodyStreamLength + responseLength, done: false }; + + if (bodyStream) { + var dest = req; + if (progress) { + dest = new WritableWithProgress(dest, function (n, done) { + if (!totalProgress.done) { + totalProgress.current += n; + progress.reportProgress(totalProgress); + } + }); + } + bodyStream.pipe(dest); + } + + if (progress) { + httpHelpers._addProgressEvents(req); + req.on('progress', function (state) { + if (!totalProgress.done) { + totalProgress.current = bodyStreamLength + state.current; + totalProgress.end = bodyStreamLength + state.end; + totalProgress.done = state.done; + + progress.reportProgress(totalProgress); + } + }); + } + + if (fut) { + try { + return fut.wait(); + } finally { + if (progress) { + progress.reportProgressDone(); + } + } + } else { return req; + } + }, + + // Adds progress callbacks to a request + // Based on request-progress + _addProgressEvents: function (request) { + var state; + + var emitProgress = function () { + request.emit('progress', state); + }; + + request + .on('response', function (response) { + state = {}; + state.end = undefined; + state.done = false; + state.current = 0; + var contentLength = response.headers['content-length']; + if (contentLength) { + state.end = Number(contentLength); + } + emitProgress(); + }) + .on('data', function (data) { + state.current += data.length; + emitProgress(); + }) + .on('end', function (data) { + state.done = true; + emitProgress(); + }); }, // A synchronous wrapper around request(...) that returns the response "body" diff --git a/tools/main.js b/tools/main.js index 6e4c7beba4..d9faea7ca4 100644 --- a/tools/main.js +++ b/tools/main.js @@ -4,6 +4,7 @@ if (showRequireProfile) var _ = require('underscore'); var Fiber = require('fibers'); +var Console = require('./console.js').Console; var files = require('./files.js'); var path = require('path'); var warehouse = require('./warehouse.js'); @@ -646,7 +647,7 @@ Fiber(function () { // One side effect of this: we really really expect them to all build, and // we're fine with dying if they don't (there's no worries about needing to // springboard). - var messages = buildmessage.capture(function () { + var messages = buildmessage.capture({ title: "Initializing local packages" }, function () { catalog.uniload.initialize({ localPackageDirs: [path.join(files.getCurrentToolsDir(), 'packages')] }); @@ -671,7 +672,7 @@ Fiber(function () { // build anything (except maybe, if running from a checkout, packages // that we need to uniload, which really ought to build) so it's OK // to die on errors. - var messages = buildmessage.capture(function () { + var messages = buildmessage.capture({ title: "Initializing server catalog" }, function () { catalog.official.initialize({ offline: !!process.env.METEOR_OFFLINE_CATALOG }); @@ -814,7 +815,7 @@ Fiber(function () { try { var rel; - var messages = buildmessage.capture(function () { + var messages = buildmessage.capture({ title: "Loading release" }, function () { rel = release.load(releaseName); }); if (messages.hasMessages()) { @@ -918,7 +919,7 @@ Fiber(function () { files.getCurrentToolsDir(), 'packages')); } - var messages = buildmessage.capture(function () { + var messages = buildmessage.capture({ title: "Initializing catalog" }, function () { catalog.complete.initialize({ localPackageDirs: localPackageDirs }); @@ -1227,10 +1228,15 @@ commandName + ": You're not in a Meteor project directory.\n" + if (showRequireProfile) require('./profile-require.js').printReport(); + Console.enableStatusPoll(); + Console.showProgressBar(); + // Run the command! try { var ret = command.func(options); } catch (e) { + Console.hideProgressBar(); + if (e === main.ShowUsage || e === main.WaitForExit || e === main.SpringboardToLatestRelease || e === main.SpringboardToSpecificReleaseg || @@ -1278,6 +1284,8 @@ commandName + ": You're not in a Meteor project directory.\n" + } } + Console.hideProgressBar(); + // Exit. (We will not get here if the command threw an exception // such as main.WaitForExit). if (ret === undefined) diff --git a/tools/package-client.js b/tools/package-client.js index 6fd556a1d2..10a5afdcf1 100644 --- a/tools/package-client.js +++ b/tools/package-client.js @@ -195,6 +195,14 @@ var writePackageDataToDisk = function (syncToken, data, options) { // - useShortPages: Boolean. Request short pages of ~3 records from the // server, instead of ~100 that it would send otherwise exports.updateServerPackageData = function (cachedServerData, options) { + var results; + buildmessage.capture({ title: 'Updating package catalog' }, function () { + results = _updateServerPackageData(cachedServerData, options); + }); + return results; +}; + +_updateServerPackageData = function (cachedServerData, options) { var self = this; options = options || {}; cachedServerData = cachedServerData || emptyCachedServerDataJson(); @@ -202,6 +210,10 @@ exports.updateServerPackageData = function (cachedServerData, options) { var done = false; var ret = {resetData: false}; + var start = undefined; + var state = { current: 0, end: 10, done: false}; + buildmessage.reportProgress(state); + try { var conn = openPackageServerConnection(options.packageServerUrl); } catch (err) { @@ -210,8 +222,22 @@ exports.updateServerPackageData = function (cachedServerData, options) { return ret; } + // Provide some progress indication for connection + // XXX though it is just a hack + state.current = 1; + buildmessage.reportProgress(state); + var getSomeData = function () { var syncToken = cachedServerData.syncToken; + + if (!start) { + start = syncToken.packages; + state.end = Date.now() - start; + } + // XXX: Is packages the best progress indicator? + state.current = syncToken.packages - start; + buildmessage.reportProgress(state); + var remoteData; try { remoteData = loadRemotePackageData(conn, syncToken, { @@ -272,6 +298,9 @@ exports.updateServerPackageData = function (cachedServerData, options) { conn.close(); } + state.done = true; + buildmessage.reportProgress(state); + ret.data = cachedServerData; return ret; }; diff --git a/tools/progress.js b/tools/progress.js new file mode 100644 index 0000000000..8123013308 --- /dev/null +++ b/tools/progress.js @@ -0,0 +1,278 @@ +/// +/// utility functions for computing progress of complex tasks +/// +/// State callback here is an object with these keys: +/// done: bool, true if done +/// current: number, the current progress value +/// end: number, optional, the value of current where we expect to be done +/// + +var _ = require('underscore'); +var Future = require('fibers/future'); +var console = require('./console.js'); + +var Progress = function (options) { + var self = this; + + options = options || {}; + + self._lastState = null; + self._parent = options.parent; + self._watchers = options.watchers || []; + + self._title = options.title; + + //if (!self._title && self._parent) { + // throw new Error("No title passed"); + //} + self._forkJoin = options.forkJoin; + + // XXX: Rationalize this; we probably don't need _completedChildren + // XXX: or _activeChildTasks (?) + self._completedChildren = { current: 0, end: 0}; + self._activeChildTasks = []; + self._allTasks = []; + + self._selfState = { current: 0, end: 0, done: false }; + if (options.estimate) { + self._selfState.end = options.estimate; + } + self._state = _.clone(self._selfState); + + self._isDone = false; + + self._selfActive = false; +}; + +_.extend(Progress.prototype, { + toString: function() { + var self = this; + return "Progress [state=" + JSON.stringify(self._state) + "]"; + }, + + reportProgressDone: function () { + var self = this; + + var state = _.clone(self._selfState); + state.done = true; + if (state.current === 0) { + state.current = 1; + } + if (!state.end || state.end < state.current) { + state.end = state.current; + } + self.reportProgress(state); + }, + + // Tries to determine which is the 'current' job in the tree + // This is very heuristical... we use some hints, like: + // don't descend into fork-join jobs; we know these execute concurrently, + // so we assume the top-level task has the title + // i.e. "Downloading packages", not "downloading supercool-1.0" + getCurrentProgress: function () { + var self = this; + + var isRoot = !self._parent; + + if (self._isDone) { + return null; + } + + if (self._selfActive && !isRoot) { + return self; + } + + if (self._forkJoin) { + // Don't descend into fork-join tasks + return self; + } + + if (self._allTasks.length) { + var active = _.map(self._allTasks, function (task) { + return task.getCurrentProgress(); + }); + active = _.filter(active, function (s) { + return !!s; + }); + if (active.length == 1) { + return active[0]; + } + return self; + } + + //if (self._activeChildTasks.length) { + // var titles = _.map(self._activeChildTasks, function (task) { + // return task.getCurrent(); + // }); + // titles = _.filter(titles, function (s) { return !!s; }); + // if (titles.length == 1) { + // return titles[0]; + // } + // //if (titles.length > 1) { + // // console.log("Multiple titles: " + titles); + // //} + // return self._title; + //} + + return null; + }, + + // Creates a subtask that must be completed as part of this (bigger) task + addChildTask: function (options) { + var self = this; + options = options || {}; + var options = _.extend({ parent: self }, options); + var child = new Progress(options); + self._activeChildTasks.push(child); + self._allTasks.push(child); + self._reportChildState(child, child._state); + return child; + }, + + // Dumps the tree, for debug + dump: function (stream, options, prefix) { + var self = this; + + options = options || {}; + if (options.skipDone && self._isDone) { + return; + } + + if (prefix) { + stream.write(prefix); + } + var end = self._state.end; + if (!end) { + end = '?'; + } + stream.write("Task [" + self._title + "] " + self._state.current + "/" + end + + (self._isDone ? " done" : "") + + (self._selfActive ? " active" : "") +"\n"); + if (self._allTasks.length) { + _.each(self._allTasks, function (child) { + child.dump(stream, options, (prefix || '') + ' '); + }); + } + }, + + // Receives a state report indicating progress of self + reportProgress: function (state) { + var self = this; + + self._selfState = state; + self._selfActive = !state.done; + + self._updateTotalState(); + + console.Console.statusPollMaybe(); + + self._notifyState(); + }, + + // Subscribes a watcher to changes + addWatcher: function (watcher) { + var self = this; + + self._watchers.push(watcher); + }, + + // Notifies watchers & parents + _notifyState: function () { + var self = this; + + if (self._parent) { + self._parent._reportChildState(self, self._state); + } + + if (self._watchers.length) { + _.each(self._watchers, function (watcher) { + watcher(self._state); + }); + } + }, + + // Recomputes state, incorporating children's states + _updateTotalState: function () { + var self = this; + + var state = _.clone(self._selfState); + + //state.current += self._completedChildren.current; + //if (state.end !== undefined) { + // state.end += self._completedChildren.end; + //} + + //var allChildrenDone = true; + //_.each(self._activeChildTasks, function (child) { + // var childState = child._state; + // state.current += childState.current; + // if (!state.done) { + // allChildrenDone = false; + // } + // + // if (state.done) { + // if (state.end !== undefined) { + // state.end += childState.current; + // } + // } else if (state.end !== undefined) { + // if (childState.end !== undefined) { + // state.end += childState.end; + // } else { + // state.end = undefined; + // } + // } + //}); + //if (!allChildrenDone) { + // state.done = false; + //} + + var allChildrenDone = true; + var state = _.clone(self._selfState); + _.each(self._allTasks, function (child) { + var childState = child._state; + + if (!child._isDone) { + allChildrenDone = false; + } + + state.current += childState.current; + if (state.end !== undefined) { + if (childState.done) { + state.end += childState.current; + } else if (childState.end !== undefined) { + state.end += childState.end; + } else { + state.end = undefined; + } + } + }); + self._isDone = allChildrenDone && !self._selfActive; + if (!allChildrenDone) { + state.done = false; + } + + if (!state.done && self._state.done) { + // This shouldn't happen + throw new Error("Progress transition from done => !done"); + } + + self._state = state; + }, + + // Called by a child when its state changes + _reportChildState: function (child, state) { + var self = this; + + if (state.done) { + self._activeChildTasks = _.without(self._activeChildTasks, child); + var weight = state.current; + self._completedChildren.current += weight; + self._completedChildren.end += weight; + } + + self._updateTotalState(); + self._notifyState(); + } +}); + +exports.Progress = Progress; \ No newline at end of file diff --git a/tools/project.js b/tools/project.js index c10e438af0..647075968a 100644 --- a/tools/project.js +++ b/tools/project.js @@ -12,6 +12,7 @@ var buildmessage = require('./buildmessage.js'); var packageLoader = require('./package-loader.js'); var PackageSource = require('./package-source.js'); var packageVersionParser = require('./package-version-parser.js'); +var Console = require('./console.js').Console; var project = exports; @@ -220,9 +221,9 @@ _.extend(Project.prototype, { } catch (err) { // XXX This error handling is bogus. Use buildmessage instead, or // something. See also compiler.determineBuildTimeDependencies - process.stdout.write( + Console.warn( "Could not resolve the specified constraints for this project:\n" - + (err.constraintSolverError ? err : err.stack) + "\n"); + + (err.constraintSolverError ? err : err.stack)); process.exit(1); } @@ -236,8 +237,8 @@ _.extend(Project.prototype, { }); if (!setV.success) { - process.stdout.write( - "Could not install all the requested packages.\n"); + Console.warn( + "Could not install all the requested packages."); process.exit(1); } @@ -363,9 +364,9 @@ _.extend(Project.prototype, { options.onDiskPackages[packageName] !== version)) { // XXX maybe we shouldn't be letting the constraint solver choose // things that don't have the right arches? - process.stderr.write("Package " + packageName + + Console.warn("Package " + packageName + " has no compatible build for version " + - version + "\n"); + version); failed = true; return; } @@ -396,7 +397,7 @@ _.extend(Project.prototype, { if ((!self.muted && !_.isEmpty(versions)) || options.alwaysShow) { _.each(messageLog, function (msg) { - process.stdout.write(msg + "\n"); + Console.info(msg); }); // Pay special attention to non-backwards-compatible changes. @@ -448,11 +449,11 @@ _.extend(Project.prototype, { }); if (!_.isEmpty(incompatibleUpdates)) { - process.stderr.write( + Console.warn( "\nThe following packages have been updated to new versions that are not " + - "backwards compatible:\n"); - process.stderr.write(utils.formatList(incompatibleUpdates)); - process.stderr.write("\n"); + "backwards compatible:"); + Console.warn(utils.formatList(incompatibleUpdates)); + Console.warn("\n"); }; } return 0; diff --git a/tools/tropohouse.js b/tools/tropohouse.js index 8e23da32fe..03dc0dde36 100644 --- a/tools/tropohouse.js +++ b/tools/tropohouse.js @@ -152,11 +152,24 @@ _.extend(exports.Tropohouse.prototype, { downloadBuildToTempDir: function (versionInfo, buildRecord) { var self = this; var targetDirectory = files.mkdtemp(); - var packageTarball = httpHelpers.getUrl({ - url: buildRecord.build.url, - encoding: null - }); - files.extractTarGz(packageTarball, targetDirectory); + + var url = buildRecord.build.url; + + var progress = buildmessage.addChildTracker("Download build"); + try { + buildmessage.capture({}, function () { + var packageTarball = httpHelpers.getUrl({ + url: url, + encoding: null, + progress: progress, + wait: false + }); + files.extractTarGz(packageTarball, targetDirectory); + }); + } finally { + progress.reportProgressDone(); + } + return targetDirectory; }, @@ -239,21 +252,9 @@ _.extend(exports.Tropohouse.prototype, { throw e; } - // XXX replace with a real progress bar in downloadMissingPackages - var header = " downloading " + packageName + " at version " + version + " ..."; - if (!options.silent) { - var animationFrame = 0; - var spinner = ['..-', '..\\', '..|', '../']; - - var printUpdate = function () { - process.stderr.write(header + spinner[animationFrame] + "\r"); - animationFrame = (animationFrame + 1) % spinner.length; - }; - printUpdate(); - var dlTimer = setInterval(printUpdate, 200); - } - - try { + buildmessage.enterJob({ + title: " downloading " + packageName + " at version " + version + " ...", + }, function() { var buildTempDirs = []; // If there's already a package in the tropohouse, start with it. if (packageLinkTarget) { @@ -263,8 +264,7 @@ _.extend(exports.Tropohouse.prototype, { // XXX how does concurrency work here? we could just get errors if we try // to rename over the other thing? but that's the same as in warehouse? _.each(buildsToDownload, function (build) { - buildTempDirs.push(self.downloadBuildToTempDir( - {packageName: packageName, version: version}, build)); + buildTempDirs.push(self.downloadBuildToTempDir({packageName: packageName, version: version}, build)); }); // We need to turn our builds into a single unipackage. @@ -290,12 +290,7 @@ _.extend(exports.Tropohouse.prototype, { if (packageLinkTarget) { files.rm_recursive(self.packagePath(packageName, packageLinkTarget)); } - } finally { - if (!options.silent) { - clearInterval(dlTimer); - process.stderr.write(header + " done\n"); - } - } + }); return; }, @@ -316,7 +311,8 @@ _.extend(exports.Tropohouse.prototype, { options = options || {}; var serverArch = options.serverArch || archinfo.host(); var downloadedPackages = {}; - _.each(versionMap, function (version, name) { + buildmessage.forkJoin({ title: 'Downloading packages'}, + versionMap, function (version, name) { try { self.maybeDownloadPackageForArchitectures({ packageName: name, From 5abc41222cdcb97acd4f79828d5fabde3ce29a06 Mon Sep 17 00:00:00 2001 From: Justin SB Date: Fri, 19 Sep 2014 16:04:29 -0700 Subject: [PATCH 06/38] Hide progress bar when we're not watching any tasks --- tools/console.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/console.js b/tools/console.js index ac3cdc49ed..951563c54f 100644 --- a/tools/console.js +++ b/tools/console.js @@ -256,7 +256,10 @@ _.extend(Console.prototype, { var self = this; var progress = self._watching; - if (!progress) return; + if (!progress) { + self.hideProgressBar(); + return; + } progress.addWatcher(function (state) { //console.log(state); From db11a5de0b258a09b942d1e35607e4eeb4975d21 Mon Sep 17 00:00:00 2001 From: Justin SB Date: Fri, 19 Sep 2014 16:16:02 -0700 Subject: [PATCH 07/38] Use pretty on a per-command basis It can be overridden using METEOR_PRETTY_OUTPUT=0 or METEOR_PRETTY_OUTPUT=1 --- tools/commands-packages.js | 3 ++- tools/commands.js | 3 ++- tools/console.js | 20 ++++++++++++++++---- tools/main.js | 5 ++++- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/tools/commands-packages.js b/tools/commands-packages.js index aa6068a7b1..2cf3dbbe17 100644 --- a/tools/commands-packages.js +++ b/tools/commands-packages.js @@ -1812,7 +1812,8 @@ main.registerCommand({ name: 'add', minArgs: 1, maxArgs: Infinity, - requiresApp: true + requiresApp: true, + pretty: true }, function (options) { // Special case on reserved package namespaces, such as 'cordova' diff --git a/tools/commands.js b/tools/commands.js index 90eb8670e5..67ad4c838d 100644 --- a/tools/commands.js +++ b/tools/commands.js @@ -334,7 +334,8 @@ main.registerCommand({ list: { type: Boolean }, example: { type: String }, package: { type: Boolean } - } + }, + pretty: true }, function (options) { // Creating a package is much easier than creating an app, so if that's what diff --git a/tools/console.js b/tools/console.js index 951563c54f..648de8fe86 100644 --- a/tools/console.js +++ b/tools/console.js @@ -11,7 +11,10 @@ var buildmessage = require('./buildmessage.js'); var chalk = require('chalk'); PROGRESS_DEBUG = !!process.env.METEOR_PROGRESS_DEBUG; -USE_PRETTY = (process.env.METEOR_PRETTY_OUTPUT != '0'); +FORCE_PRETTY=undefined; +if (process.env.METEOR_PRETTY_OUTPUT) { + FORCE_PRETTY = process.env.METEOR_PRETTY_OUTPUT != '0' +} var Console = function (options) { var self = this; @@ -34,7 +37,9 @@ var Console = function (options) { self._legacyWrite(LEVEL_WARN, msg); }; - self._stream =process.stdout; + self._stream = process.stdout; + + self._pretty = (FORCE_PRETTY !== undefined ? FORCE_PRETTY : false); }; @@ -78,6 +83,13 @@ _.extend(Console.prototype, { self._progressBar.terminate(); }, + setPretty: function (pretty) { + var self = this; + if (FORCE_PRETTY === undefined) { + self._pretty = pretty; + } + }, + _renderProgressBar: function () { var self = this; if (self._progressBar) { @@ -184,7 +196,7 @@ _.extend(Console.prototype, { var dest = process.stdout; var style = null; - if (level && USE_PRETTY) { + if (level && self._pretty) { switch (level.code) { case LEVEL_CODE_ERROR: dest = process.stderr; @@ -235,7 +247,7 @@ _.extend(Console.prototype, { return; } - if (!self._stream.isTTY || !USE_PRETTY) return; + if (!self._stream.isTTY || !self._pretty) return; var options = { complete: '=', diff --git a/tools/main.js b/tools/main.js index d9faea7ca4..f909866aaa 100644 --- a/tools/main.js +++ b/tools/main.js @@ -35,7 +35,8 @@ var Command = function (options) { options: {}, requiresApp: false, requiresRelease: true, - hidden: false + hidden: false, + pretty: false }, options); if (! _.has(options, 'maxArgs')) @@ -1228,6 +1229,8 @@ commandName + ": You're not in a Meteor project directory.\n" + if (showRequireProfile) require('./profile-require.js').printReport(); + Console.setPretty(command.pretty); + Console.enableStatusPoll(); Console.showProgressBar(); From cdb840a577f999672518f0150587fcbc627bec2d Mon Sep 17 00:00:00 2001 From: Justin SB Date: Fri, 19 Sep 2014 16:20:58 -0700 Subject: [PATCH 08/38] Make deploy pretty also Still pausing until we merge in the minification changes --- tools/commands.js | 1 + tools/http-helpers.js | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/commands.js b/tools/commands.js index 67ad4c838d..6e855014fa 100644 --- a/tools/commands.js +++ b/tools/commands.js @@ -903,6 +903,7 @@ main.registerCommand({ main.registerCommand({ name: 'deploy', + pretty: true, minArgs: 1, maxArgs: 1, options: { diff --git a/tools/http-helpers.js b/tools/http-helpers.js index 96f03aa338..15e32354bb 100644 --- a/tools/http-helpers.js +++ b/tools/http-helpers.js @@ -219,10 +219,10 @@ _.extend(exports, { var bodyStreamLength = 0; if (bodyStream) { - // XXX - bodyStreamLength += 4000000; + // XXX Horrible hack... we need the correct length estimate + bodyStreamLength += 8000000; } - // XXX + // XXX A default non-zero resposne size; if much bigger we should provide an estimate var responseLength = 128 * 1024; var totalProgress = { current: 0, end: bodyStreamLength + responseLength, done: false }; From ebabe84d13f62a4aaba7383826df76ee2f8f1f16 Mon Sep 17 00:00:00 2001 From: Justin SB Date: Fri, 19 Sep 2014 16:51:09 -0700 Subject: [PATCH 09/38] Turn on pretty for search command --- tools/commands-packages.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/commands-packages.js b/tools/commands-packages.js index 2cf3dbbe17..46f649cef5 100644 --- a/tools/commands-packages.js +++ b/tools/commands-packages.js @@ -1151,6 +1151,7 @@ main.registerCommand({ main.registerCommand({ name: 'search', + pretty: true, minArgs: 1, maxArgs: 1, options: { From 7d171be03bc94c2c7a675c2b16f42c98149c5dac Mon Sep 17 00:00:00 2001 From: Justin SB Date: Fri, 19 Sep 2014 16:52:46 -0700 Subject: [PATCH 10/38] Turn on pretty for show --- tools/commands-packages.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/commands-packages.js b/tools/commands-packages.js index 46f649cef5..3f8317f398 100644 --- a/tools/commands-packages.js +++ b/tools/commands-packages.js @@ -997,6 +997,7 @@ main.registerCommand({ main.registerCommand({ name: 'show', + pretty: true, minArgs: 1, maxArgs: 1, options: { From 98ffa7d6d35e5741fdb8b4eda0b4cbeab4390e33 Mon Sep 17 00:00:00 2001 From: Justin SB Date: Fri, 19 Sep 2014 17:22:30 -0700 Subject: [PATCH 11/38] Add missing return to enterJob I removed it accidentally in 9963a8bc --- tools/buildmessage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/buildmessage.js b/tools/buildmessage.js index 2795fda314..a3312126eb 100644 --- a/tools/buildmessage.js +++ b/tools/buildmessage.js @@ -297,7 +297,7 @@ var enterJob = function (options, f) { progress = parentProgress.addChildTask(progressOptions); } - currentProgress.withValue(progress, function () { + return currentProgress.withValue(progress, function () { if (!currentMessageSet.get()) { try { return f(); From 6b3a1c217b6397803da8c84e73d3693a7048320f Mon Sep 17 00:00:00 2001 From: ekatek Date: Fri, 19 Sep 2014 17:16:02 -0700 Subject: [PATCH 12/38] weird trick that makes test-packages run again --- tools/commands.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tools/commands.js b/tools/commands.js index 6e855014fa..69942fdc96 100644 --- a/tools/commands.js +++ b/tools/commands.js @@ -1323,7 +1323,8 @@ var getPackagesForTest = function (packages) { testPackages = catalog.complete.getLocalPackageNames(); } else { var messages = buildmessage.capture(function () { - testPackages = _.map(packages, function (p) { + testPackages = []; + _.each(packages, function (p) { return buildmessage.enterJob({ title: "trying to test package `" + p + "`" }, function () { @@ -1333,14 +1334,15 @@ var getPackagesForTest = function (packages) { if (p.indexOf('@') !== -1) { buildmessage.error( "You may not specify versions for local packages: " + p ); - // Recover by returning p anyway. + // Recover by returning anyway. + return; } // Check to see if this is a real package, and if it is a real // package, if it has tests. if (!catalog.complete.isLocalPackage(p)) { buildmessage.error( "Not a known local package, cannot test: " + p ); - return p; + return; } var versionNames = catalog.complete.getSortedVersions(p); if (versionNames.length !== 1) @@ -1349,8 +1351,10 @@ var getPackagesForTest = function (packages) { if (versionRec && !versionRec.testName) { buildmessage.error( "There are no tests for package: " + p ); + return; } - return p; + testPackages.push(p); + return; } // Otherwise it's a directory; load it into a Package now. Use // path.resolve to strip trailing slashes, so that packageName doesn't @@ -1375,8 +1379,8 @@ var getPackagesForTest = function (packages) { var PackageSource = require('./package-source.js'); var packageSource = new PackageSource(catalog.complete); packageSource.initFromPackageDir(packageDir); - - return packageSource.name; + testPackages.push(packageSource.name); + return; }); }); From e1d6167be18415ee9c45dc7eebe0cbc4cae5b4f1 Mon Sep 17 00:00:00 2001 From: ekatek Date: Fri, 19 Sep 2014 17:24:36 -0700 Subject: [PATCH 13/38] fix constraint solver test failure by fixing the test to use the new api: --- packages/constraint-solver/constraint-solver-tests.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/constraint-solver/constraint-solver-tests.js b/packages/constraint-solver/constraint-solver-tests.js index 9b796b4773..7b1f667bb9 100644 --- a/packages/constraint-solver/constraint-solver-tests.js +++ b/packages/constraint-solver/constraint-solver-tests.js @@ -86,8 +86,9 @@ var splitArgs = function (deps) { } else { dependencies.push(dep); } - if (constr) - constraints.push({ packageName: dep, type: (constr.indexOf("=") !== -1 ? "exactly" : "compatible-with"), version: constr.replace("=", "")}); + if (constr) { + constraints.push(PackageVersion.parseConstraint(dep + "@" + constr)); + } }); return {dependencies: dependencies, constraints: constraints}; }; @@ -314,7 +315,7 @@ Tinytest.add("constraint solver - no constraint dependency - anything", function Tinytest.add("constraint solver - no constraint dependency - transitive dep still picked right", function (test) { var versions = defaultResolver.resolve( ["sparkle", "sparky-forms"], - [{ packageName: "sparky-forms", version: "1.1.2", type: "compatible-with" }], + [PackageVersion.parseConstraint("sparky-forms@1.1.2")], { _testing: true }).answer; test.equal(versions.sparkle, "2.1.1"); }); From 25961fef6343437be62f00148db692066065d25d Mon Sep 17 00:00:00 2001 From: ekatek Date: Fri, 19 Sep 2014 17:26:02 -0700 Subject: [PATCH 14/38] Revert "weird trick that makes test-packages run again" This reverts commit 6b3a1c217b6397803da8c84e73d3693a7048320f. --- tools/commands.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/tools/commands.js b/tools/commands.js index 69942fdc96..6e855014fa 100644 --- a/tools/commands.js +++ b/tools/commands.js @@ -1323,8 +1323,7 @@ var getPackagesForTest = function (packages) { testPackages = catalog.complete.getLocalPackageNames(); } else { var messages = buildmessage.capture(function () { - testPackages = []; - _.each(packages, function (p) { + testPackages = _.map(packages, function (p) { return buildmessage.enterJob({ title: "trying to test package `" + p + "`" }, function () { @@ -1334,15 +1333,14 @@ var getPackagesForTest = function (packages) { if (p.indexOf('@') !== -1) { buildmessage.error( "You may not specify versions for local packages: " + p ); - // Recover by returning anyway. - return; + // Recover by returning p anyway. } // Check to see if this is a real package, and if it is a real // package, if it has tests. if (!catalog.complete.isLocalPackage(p)) { buildmessage.error( "Not a known local package, cannot test: " + p ); - return; + return p; } var versionNames = catalog.complete.getSortedVersions(p); if (versionNames.length !== 1) @@ -1351,10 +1349,8 @@ var getPackagesForTest = function (packages) { if (versionRec && !versionRec.testName) { buildmessage.error( "There are no tests for package: " + p ); - return; } - testPackages.push(p); - return; + return p; } // Otherwise it's a directory; load it into a Package now. Use // path.resolve to strip trailing slashes, so that packageName doesn't @@ -1379,8 +1375,8 @@ var getPackagesForTest = function (packages) { var PackageSource = require('./package-source.js'); var packageSource = new PackageSource(catalog.complete); packageSource.initFromPackageDir(packageDir); - testPackages.push(packageSource.name); - return; + + return packageSource.name; }); }); From e5dcef91278a5582e4888625ba1b4487f3c3c3c6 Mon Sep 17 00:00:00 2001 From: Justin SB Date: Fri, 19 Sep 2014 17:26:35 -0700 Subject: [PATCH 15/38] Prettify publish --- tools/commands-packages.js | 1 + tools/package-client.js | 79 +++++++++++++++++++------------------- 2 files changed, 41 insertions(+), 39 deletions(-) diff --git a/tools/commands-packages.js b/tools/commands-packages.js index 3f8317f398..e88fdf7fc6 100644 --- a/tools/commands-packages.js +++ b/tools/commands-packages.js @@ -142,6 +142,7 @@ main.registerCommand({ main.registerCommand({ name: 'publish', + pretty: true, minArgs: 0, maxArgs: 0, options: { diff --git a/tools/package-client.js b/tools/package-client.js index 10a5afdcf1..4284ea4215 100644 --- a/tools/package-client.js +++ b/tools/package-client.js @@ -12,6 +12,7 @@ var utils = require('./utils.js'); var buildmessage = require('./buildmessage.js'); var compiler = require('./compiler.js'); var uniload = require('./uniload.js'); +var Console = require('./console.js').Console; // Use uniload to load the packages that we need to open a connection to the // current package server and use minimongo in memory. We need the following @@ -82,7 +83,7 @@ exports.loadCachedServerData = function (packageStorageFile) { } // XXX we should probably return an error to the caller here to // figure out how to handle it - process.stderr.write("ERROR " + e.message + "\n"); + Console.stderr.write("ERROR " + e.message + "\n"); process.exit(1); } var ret = noDataToken; @@ -90,7 +91,7 @@ exports.loadCachedServerData = function (packageStorageFile) { ret = JSON.parse(data); } catch (err) { // XXX error handling - process.stderr.write( + Console.stderr.write( "ERROR: Could not parse JSON for local package-metadata cache. \n"); // This should only happen if you decided to manually edit this or // whatever. Regardless, go on and treat this as an empty file. @@ -323,7 +324,7 @@ exports.loggedInPackagesConnection = function () { if (! auth.isLoggedIn()) { // XXX we should have a better account signup page. - process.stderr.write( + 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"); auth.doUsernamePasswordLogin({ retry: true }); @@ -345,7 +346,7 @@ exports.loggedInPackagesConnection = function () { if (err.message === "access-denied") { // Maybe we thought we were logged in, but our token had been // revoked. - process.stderr.write( + 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"); @@ -385,7 +386,7 @@ var bundleSource = function (unipackage, includeSources, packageDir) { name ); if (! files.mkdir_p(sourcePackageDir)) { - process.stderr.write('Failed to create temporary source directory: ' + + Console.stderr.write('Failed to create temporary source directory: ' + sourcePackageDir); return null; } @@ -496,12 +497,12 @@ var createAndPublishBuiltPackage = function (conn, unipackage) { // Note: we really want to do this before createPackageBuild, because the URL // we get from createPackageBuild will expire! - process.stdout.write('Bundling build...\n'); + Console.stdout.write('Bundling build...\n'); var bundleResult = bundleBuild(unipackage); if (buildmessage.jobHasMessages()) return; - process.stdout.write('Creating package build...\n'); + Console.stdout.write('Creating package build...\n'); var uploadInfo = exports.callPackageServer(conn, 'createPackageBuild', { packageName: unipackage.name, @@ -509,21 +510,21 @@ var createAndPublishBuiltPackage = function (conn, unipackage) { buildArchitectures: unipackage.buildArchitectures() }); - process.stdout.write('Uploading build...\n'); + Console.stdout.write('Uploading build...\n'); uploadTarball(uploadInfo.uploadUrl, bundleResult.buildTarball); - process.stdout.write('Publishing package build...\n'); + Console.stdout.write('Publishing package build...\n'); exports.callPackageServer(conn, 'publishPackageBuild', uploadInfo.uploadToken, bundleResult.tarballHash, bundleResult.treeHash); - process.stdout.write('Published ' + unipackage.name + + Console.stdout.write('Published ' + unipackage.name + ', version ' + unipackage.version); - process.stdout.write('\nDone!\n'); + Console.stdout.write('\nDone!\n'); }; exports.createAndPublishBuiltPackage = createAndPublishBuiltPackage; @@ -532,13 +533,13 @@ exports.handlePackageServerConnectionError = function (error) { if (error instanceof AlreadyPrintedMessageError) { // do nothing } else if (error.errorType === 'Meteor.Error') { - process.stderr.write("Error from package server"); + Console.stderr.write("Error from package server"); if (error.message) { - process.stderr.write(": " + error.message); + Console.stderr.write(": " + error.message); } - process.stderr.write("\n"); + Console.stderr.write("\n"); } else if (error.errorType === "DDP.ConnectionError") { - process.stderr.write("Error connecting to package server: " + Console.stderr.write("Error connecting to package server: " + error.message + "\n"); } else { throw error; @@ -577,13 +578,13 @@ exports.publishPackage = function (packageSource, compileResult, conn, options) } catch (e) { if (!e.versionParserError) throw e; - process.stderr.write(e.error + "\n"); + Console.stderr.write(e.error + "\n"); return 1; } // Check that we have a version. if (! version) { - process.stderr.write( + Console.stderr.write( "That package cannot be published because it doesn't have a version.\n"); return 1; } @@ -592,15 +593,15 @@ exports.publishPackage = function (packageSource, compileResult, conn, options) // all string limits on the server, but this is the one that is mostly likely // to be wrong) if (!packageSource.metadata.summary) { - process.stderr.write("Please describe what your package does. \n"); - process.stderr.write("Set a summary in Package.describe in package.js. \n"); + Console.stderr.write("Please describe what your package does. \n"); + Console.stderr.write("Set a summary in Package.describe in package.js. \n"); return 1; } if (packageSource.metadata.summary && packageSource.metadata.summary.length > 100) { - process.stderr.write("Description must be under 100 chars. \n"); - process.stderr.write("Publish failed. \n"); + Console.stderr.write("Description must be under 100 chars. \n"); + Console.stderr.write("Publish failed. \n"); return 1; } @@ -610,15 +611,15 @@ exports.publishPackage = function (packageSource, compileResult, conn, options) if (!options['new']) { var packRecord = catalog.official.getPackage(name); if (!packRecord) { - process.stderr.write('There is no package named ' + name + + Console.stderr.write('There is no package named ' + name + '. If you are creating a new package, use the --create flag. \n'); - process.stderr.write("Publish failed. \n"); + Console.stderr.write("Publish failed. \n"); return 1; } if (!exports.amIAuthorized(name, conn, false)) { - process.stderr.write('You are not an authorized maintainer of ' + name + ".\n"); - process.stderr.write('Only authorized maintainers may publish new versions. \n'); + Console.stderr.write('You are not an authorized maintainer of ' + name + ".\n"); + Console.stderr.write('Only authorized maintainers may publish new versions. \n'); return 1; } } @@ -641,12 +642,12 @@ exports.publishPackage = function (packageSource, compileResult, conn, options) // then we should force the user to specify them. This is because we are not // sure about pre-0.90 package versions yet. if (!packageSource.isCore && !_.isEqual(badConstraints, [])) { - process.stderr.write( + Console.stderr.write( "You must specify a version constraint for the following packages:"); _.each(badConstraints, function(bad) { - process.stderr.write(" " + bad); + Console.stderr.write(" " + bad); }); - process.stderr.write(". \n" ); + Console.stderr.write(". \n" ); process.exit(1); } @@ -676,11 +677,11 @@ exports.publishPackage = function (packageSource, compileResult, conn, options) }); if (messages.hasMessages()) { - process.stderr.write(messages.formatMessages()); + Console.stderr.write(messages.formatMessages()); return 1; } - process.stdout.write('Bundling source...\n'); + Console.stdout.write('Bundling source...\n'); var sources = _.union(compileResult.sources, testFiles); @@ -697,14 +698,14 @@ exports.publishPackage = function (packageSource, compileResult, conn, options) // Create the package. Check that the metadata exists. if (options.new) { - process.stdout.write('Creating package...\n'); + Console.stdout.write('Creating package...\n'); try { var packageId = exports.callPackageServer(conn, 'createPackage', { name: packageSource.name }); } catch (err) { - process.stderr.write(err.message + "\n"); + Console.stderr.write(err.message + "\n"); return 3; } @@ -713,18 +714,18 @@ exports.publishPackage = function (packageSource, compileResult, conn, options) if (options.existingVersion) { var existingRecord = catalog.official.getVersion(name, version); if (!existingRecord) { - process.stderr.write("Version does not exist.\n"); + Console.stderr.write("Version does not exist.\n"); return 1; } if (existingRecord.source.treeHash !== sourceBundleResult.treeHash) { - process.stderr.write( + Console.stderr.write( "Package source differs from the existing version.\n"); return 1; } // XXX check that we're actually providing something new? } else { - process.stdout.write('Creating package version...\n'); + Console.stdout.write('Creating package version...\n'); var uploadRec = { packageName: packageSource.name, @@ -740,7 +741,7 @@ exports.publishPackage = function (packageSource, compileResult, conn, options) var uploadInfo = exports.callPackageServer(conn, 'createPackageVersion', uploadRec); } catch (err) { - process.stderr.write("ERROR " + err.message + "\n"); + Console.stderr.write("ERROR " + err.message + "\n"); return 3; } @@ -748,10 +749,10 @@ exports.publishPackage = function (packageSource, compileResult, conn, options) // telling them to try 'meteor publish-for-arch' if they want to // publish a new build. - process.stdout.write('Uploading source...\n'); + Console.stdout.write('Uploading source...\n'); uploadTarball(uploadInfo.uploadUrl, sourceBundleResult.sourceTarball); - process.stdout.write('Publishing package version...\n'); + Console.stdout.write('Publishing package version...\n'); try { exports.callPackageServer(conn, 'publishPackageVersion', @@ -759,7 +760,7 @@ exports.publishPackage = function (packageSource, compileResult, conn, options) { tarballHash: sourceBundleResult.tarballHash, treeHash: sourceBundleResult.treeHash }); } catch (err) { - process.stderr.write("ERROR " + err.message + "\n"); + Console.stderr.write("ERROR " + err.message + "\n"); return 3; } From 3b84a63fc2e6af04bebebc93e7a7592be09be16e Mon Sep 17 00:00:00 2001 From: Justin SB Date: Fri, 19 Sep 2014 18:04:06 -0700 Subject: [PATCH 16/38] Choose stdout/stderr, even if we're not in pretty mode --- tools/console.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tools/console.js b/tools/console.js index 648de8fe86..21dab21e17 100644 --- a/tools/console.js +++ b/tools/console.js @@ -194,16 +194,24 @@ _.extend(Console.prototype, { } var dest = process.stdout; - var style = null; - - if (level && self._pretty) { + if (level) { switch (level.code) { case LEVEL_CODE_ERROR: dest = process.stderr; - style = chalk.bold.red; break; case LEVEL_CODE_WARN: dest = process.stderr; + break; + } + } + + var style = null; + if (level && self._pretty) { + switch (level.code) { + case LEVEL_CODE_ERROR: + style = chalk.bold.red; + break; + case LEVEL_CODE_WARN: style = chalk.red; break; //case LEVEL_CODE_INFO: From 6fec720a2a6b01f25166520705ec48bc46d97e96 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Fri, 19 Sep 2014 18:15:18 -0700 Subject: [PATCH 17/38] Use banners from v1 --- tools/config.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/config.js b/tools/config.js index 7afb812b79..b4d86fffe7 100644 --- a/tools/config.js +++ b/tools/config.js @@ -217,13 +217,18 @@ _.extend(exports, { self.getLocalPackageCacheFilename()); }, + // XXX this does not appear to be called. getPackageStorageVersion: function() { return "1.0"; }, + // NOTE: Uses the v1 directory, not v1.1 like package-metadata. This + // is so that we don't double-print banners and the like. Someday we + // should unifiy these so that the tool doesn't have to write to + // multiple different versions of these directories. getBannersShownFilename: function() { return path.join(tropohouse.default.root, - "package-metadata", "v1.1", "banners-shown.json"); + "package-metadata", "v1", "banners-shown.json"); }, // Return the domain name of the current Meteor Accounts server in From d64f157f865201e5543214fff9da850e629c4c9d Mon Sep 17 00:00:00 2001 From: Justin SB Date: Fri, 19 Sep 2014 16:27:48 -0700 Subject: [PATCH 18/38] Add progress & chalk to dev bundle; bump to version 0.3.54 --- meteor | 2 +- scripts/dev-bundle-package.json | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/meteor b/meteor index ce4f0a969a..2cf0fec9a7 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/bin/bash -BUNDLE_VERSION=0.3.53 +BUNDLE_VERSION=0.3.54 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. diff --git a/scripts/dev-bundle-package.json b/scripts/dev-bundle-package.json index 390d0b1961..a8f824ed8e 100644 --- a/scripts/dev-bundle-package.json +++ b/scripts/dev-bundle-package.json @@ -3,10 +3,10 @@ "version": "0.0.0", "dependencies": { "fibers": "1.0.1", - "progress": "1.1.8", - "chalk": "0.5.1", "underscore": "1.5.2", "source-map-support": "0.2.5", - "semver": "2.2.1" + "semver": "2.2.1", + "progress": "1.1.8", + "chalk": "0.5.1" } } From 298f68458063ed912e7f44028f55061d1edf0211 Mon Sep 17 00:00:00 2001 From: ekatek Date: Fri, 19 Sep 2014 18:23:54 -0700 Subject: [PATCH 19/38] we also need to update the warehouse code to use the right version --- tools/selftest.js | 3 ++- tools/tests/autoupdate.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/selftest.js b/tools/selftest.js index fe8652dc61..2e2276d12f 100644 --- a/tools/selftest.js +++ b/tools/selftest.js @@ -637,6 +637,7 @@ _.extend(Sandbox.prototype, { var packagesDirectoryName = config.getPackagesDirectoryName(serverUrl); files.mkdir_p(path.join(self.warehouse, packagesDirectoryName), 0755); files.mkdir_p(path.join(self.warehouse, 'package-metadata', 'v1'), 0755); + files.mkdir_p(path.join(self.warehouse, 'package-metadata', 'v1.1'), 0755); var stubCatalog = { syncToken: {}, @@ -782,7 +783,7 @@ _.extend(Sandbox.prototype, { var dataFile = config.getLocalPackageCacheFilename(serverUrl); fs.writeFileSync( - path.join(self.warehouse, 'package-metadata', 'v1', dataFile), + path.join(self.warehouse, 'package-metadata', 'v1.1', dataFile), JSON.stringify(stubCatalog, null, 2)); // And a cherry on top diff --git a/tools/tests/autoupdate.js b/tools/tests/autoupdate.js index 130fed5b9b..1454c8fd23 100644 --- a/tools/tests/autoupdate.js +++ b/tools/tests/autoupdate.js @@ -7,7 +7,7 @@ var Sandbox = selftest.Sandbox; var editPackageMetadata = function (sandbox, f) { var dataFile = path.join(sandbox.warehouse, - 'package-metadata', 'v1', + 'package-metadata', 'v1.1', config.getLocalPackageCacheFilename()); var data = JSON.parse(fs.readFileSync(dataFile, 'utf8')); f(data); From 099cb726ee9430de89b93cd4b51bbd51061f1f08 Mon Sep 17 00:00:00 2001 From: ekatek Date: Fri, 19 Sep 2014 18:41:19 -0700 Subject: [PATCH 20/38] use right index, use Console.warn --- tools/commands-packages.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/commands-packages.js b/tools/commands-packages.js index e88fdf7fc6..ad22b5ac39 100644 --- a/tools/commands-packages.js +++ b/tools/commands-packages.js @@ -279,7 +279,7 @@ main.registerCommand({ if (_.any(allArchs, function (arch) { return arch.match(/^os\./); })) { - Console.warning( + Console.warn( "\nWARNING: Your package contains binary code and is only compatible with " + archinfo.host() + " architecture.\n" + "Please use publish-for-arch to publish new builds of the package.\n"); @@ -1751,7 +1751,7 @@ main.registerCommand({ // specified, then only upgrade those. var upgradePackages; if (options.args.length === 0) { - upgradePackages = _.pluck(allPackages, 'packageName'); + upgradePackages = _.pluck(allPackages, 'name'); } else { upgradePackages = options.args; } From b97032b85e32e3f0cd21f1b85f4bea66cc0964e0 Mon Sep 17 00:00:00 2001 From: ekatek Date: Fri, 19 Sep 2014 18:41:52 -0700 Subject: [PATCH 21/38] get rid of other Console.warning too --- tools/commands-packages.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/commands-packages.js b/tools/commands-packages.js index ad22b5ac39..616975749e 100644 --- a/tools/commands-packages.js +++ b/tools/commands-packages.js @@ -745,7 +745,7 @@ main.registerCommand({ name: item }); if (buildmessage.jobHasMessages()) { - Console.warning("\n ...Error reading package:" + item); + Console.warn("\n ...Error reading package:" + item); canBuild = false; return; }; @@ -771,7 +771,7 @@ main.registerCommand({ var compileResult = compiler.compile(packageSource, { officialBuild: true }); if (buildmessage.jobHasMessages()) { - Console.warning("\n ... Error compiling unipackage: " + item ); + Console.warn("\n ... Error compiling unipackage: " + item ); canBuild = false; return; }; @@ -802,7 +802,7 @@ main.registerCommand({ "at the end (ex: 1.0.0-dev). If this is an " + "official release, please set official to true " + "in the release configuration file."); - Console.warning("NOT OK unofficial"); + Console.warn("NOT OK unofficial"); return; } toPublish[item] = {source: packageSource, From 30442946cf43ad22cfac499f04e154be5889ef2d Mon Sep 17 00:00:00 2001 From: Justin SB Date: Fri, 19 Sep 2014 18:42:06 -0700 Subject: [PATCH 22/38] Set warning as an alias for warn on Console.prototype, not on the singleton --- tools/console.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/console.js b/tools/console.js index 21dab21e17..be003f5621 100644 --- a/tools/console.js +++ b/tools/console.js @@ -319,6 +319,6 @@ _.extend(Console.prototype, { }); -Console.warning = Console.warn; +Console.prototype.warning = Console.prototype.warn; exports.Console = new Console; \ No newline at end of file From 2c6e615d1728e8fb3796664775e91f5081862232 Mon Sep 17 00:00:00 2001 From: Justin SB Date: Fri, 19 Sep 2014 18:42:25 -0700 Subject: [PATCH 23/38] Auto-agree to the license in self-test; wait for the huge download --- tools/tests/cordova-platforms.js | 3 +++ tools/tests/cordova-plugins.js | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/tests/cordova-platforms.js b/tools/tests/cordova-platforms.js index 6073f350ae..f45f1608b4 100644 --- a/tools/tests/cordova-platforms.js +++ b/tools/tests/cordova-platforms.js @@ -58,6 +58,9 @@ selftest.define("add cordova platforms", function () { run.expectExit(1); run = s.run("add-platform", "android"); + run.match("Do you agree"); + run.write("Y\n"); + run.extraTime = 90; // Huge download run.match("added"); run = s.run("remove-platform", "foo"); diff --git a/tools/tests/cordova-plugins.js b/tools/tests/cordova-plugins.js index 9317219fb8..f0a1654102 100644 --- a/tools/tests/cordova-plugins.js +++ b/tools/tests/cordova-plugins.js @@ -150,7 +150,9 @@ selftest.define("add cordova plugins", ["slow"], function () { run.matchErr("meteor add-platform "); run = s.run("add-platform", "android"); - run.waitSecs(5); + run.match("Do you agree"); + run.write("Y\n"); + run.extraTime = 90; // Huge download run.match("added platform"); run = s.run("add", "cordova:org.apache.cordova.camera@0.3.0"); From c5f87bc3e0087d6e28e44fbdcbd33b429435c965 Mon Sep 17 00:00:00 2001 From: ekatek Date: Fri, 19 Sep 2014 18:57:13 -0700 Subject: [PATCH 24/38] fixed the reduce of empty array bug --- tools/package-source.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/package-source.js b/tools/package-source.js index fdc0210528..52e9cdab2d 100644 --- a/tools/package-source.js +++ b/tools/package-source.js @@ -1297,6 +1297,7 @@ _.extend(PackageSource.prototype, { newConstraint.push(dep.package); } }); + if (_.isEmpty(newConstraint)) return dep; dep.constraint = _.reduce(newConstraint, function(x, y) { return x + " || " + y; From cfefbcf28f364377b74344e2b35a042c30f77b24 Mon Sep 17 00:00:00 2001 From: Justin SB Date: Fri, 19 Sep 2014 19:09:46 -0700 Subject: [PATCH 25/38] Revert 7282c0ee5b, now that 0ac18551663876 has reverted bdcf0d188 --- tools/tests/run.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/tests/run.js b/tools/tests/run.js index c64d99940e..adf08a3561 100644 --- a/tools/tests/run.js +++ b/tools/tests/run.js @@ -217,7 +217,7 @@ selftest.define("run errors", function () { // This time, prevent the proxy from starting. (This time, leaving out the // interface name matches.) f = new Future; - server = net.createServer().listen(proxyPort, 'localhost', f.resolver()); + server = net.createServer().listen(proxyPort, f.resolver()); f.wait(); run = s.run("-p", proxyPort); From 8e9dbe6513bc950d2bb48511fc7106260f197182 Mon Sep 17 00:00:00 2001 From: ekatek Date: Fri, 19 Sep 2014 19:34:04 -0700 Subject: [PATCH 26/38] a less obnoxious banner that only comes up once --- tools/catalog.js | 37 +++++++++++++++++++++---------------- tools/package-client.js | 1 - 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/tools/catalog.js b/tools/catalog.js index 24a9540953..2a19177f75 100644 --- a/tools/catalog.js +++ b/tools/catalog.js @@ -18,7 +18,7 @@ var fiberHelpers = require('./fiber-helpers.js'); var project = require('./project.js'); var Future = require('fibers/future'); var Fiber = require('fibers'); - +var Console = require('./console.js').Console; var catalog = exports; catalog.DEFAULT_TRACK = 'METEOR'; @@ -558,27 +558,32 @@ _.extend(CompleteCatalog.prototype, { if (ret["usedRCs"]) { var expPackages = []; _.each(ret.answer, function(version, package) { - if (version.split('-').length > 1 && - !_.findWhere(constr, - { packageName: package, version: version })) { - expPackages.push({ + if (version.split('-').length > 1) { + if (!(resolverOpts.previousSolution && + resolverOpts.previousSolution[package] === version)) { + var oldConstraints = _.where(constr, { name: package } ); + var printMe = true; + _.each(oldConstraints, function (oC) { + _.each(oC.constraints, function (specOC) { + if (specOC.version === version) { + printMe = false; + } + }); + }); + if (printMe) { + expPackages.push({ name: " " + package + "@" + version, description: self.getVersion(package, version).description }); - } + }; + }} }); if (!_.isEmpty(expPackages)) { // XXX: Couldn't figure out how to word this better for better tenses. - process.stderr.write( - "------------------------------------------------------------ \n"); - process.stderr.write( - "In order to resolve constraints, we had to use the following\n"+ - "experimental package versions:\n"); - process.stderr.write(utils.formatList(expPackages)); - process.stderr.write( - "------------------------------------------------------------ \n"); - - process.stderr.write("\n"); + Console.info( + "\nIn order to resolve constraints, we had to use the following\n"+ + "experimental package versions:"); + Console.info(utils.formatList(expPackages)); } } return ret.answer; diff --git a/tools/package-client.js b/tools/package-client.js index 4284ea4215..59cf1911cf 100644 --- a/tools/package-client.js +++ b/tools/package-client.js @@ -647,7 +647,6 @@ exports.publishPackage = function (packageSource, compileResult, conn, options) _.each(badConstraints, function(bad) { Console.stderr.write(" " + bad); }); - Console.stderr.write(". \n" ); process.exit(1); } From 06434cfefb308f8f49deaf06735f72b40da09f2e Mon Sep 17 00:00:00 2001 From: ekatek Date: Fri, 19 Sep 2014 19:39:30 -0700 Subject: [PATCH 27/38] wrap this output to fewer columns --- tools/commands-packages.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tools/commands-packages.js b/tools/commands-packages.js index 616975749e..01c53eb7b3 100644 --- a/tools/commands-packages.js +++ b/tools/commands-packages.js @@ -1358,9 +1358,8 @@ main.registerCommand({ if (newVersionsAvailable) { Console.info( - "\n * New versions of these packages are available! " + - "Run 'meteor update' to try to update\n" + - " those packages to their latest versions."); +"\n * New versions of these packages are available! Run 'meteor update' to try\n" + +" to update those packages to their latest versions."); } return 0; }); From 18ffeafe20d52c943f0237cb4141d94cad58c6d2 Mon Sep 17 00:00:00 2001 From: ekatek Date: Fri, 19 Sep 2014 19:42:01 -0700 Subject: [PATCH 28/38] start patience messages on a new line --- tools/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/utils.js b/tools/utils.js index 6de1de2ca1..851f28da12 100644 --- a/tools/utils.js +++ b/tools/utils.js @@ -519,7 +519,7 @@ exports.Patience = function (options) { typeof(options.message) !== 'function') { throw Error("message must be string or function"); } - self._message = options.message; + self._message = "\n" + options.message; self._whenMessage = now + options.messageAfterMs; self._messageTimeout = setTimeout(function () { self._messageTimeout = null; From 27a1a66039938c0eb74ffdfad0576f58a621aac0 Mon Sep 17 00:00:00 2001 From: Justin SB Date: Fri, 19 Sep 2014 19:48:12 -0700 Subject: [PATCH 29/38] Make sure the progress bar is hidden when we exit the process Otherwise it leaves artifacts when we call process.exit --- tools/console.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/console.js b/tools/console.js index be003f5621..3c1e07c38b 100644 --- a/tools/console.js +++ b/tools/console.js @@ -40,6 +40,10 @@ var Console = function (options) { self._stream = process.stdout; self._pretty = (FORCE_PRETTY !== undefined ? FORCE_PRETTY : false); + + process.on('exit', function(code) { + self.hideProgressBar(); + }); }; From 9c5abcc8fad6762a6e1661847ec11497717e5f00 Mon Sep 17 00:00:00 2001 From: Justin SB Date: Fri, 19 Sep 2014 19:53:16 -0700 Subject: [PATCH 30/38] Use cleanup onExit instead of process.on('exit', ) --- tools/console.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/console.js b/tools/console.js index 3c1e07c38b..85eb497571 100644 --- a/tools/console.js +++ b/tools/console.js @@ -9,6 +9,7 @@ var ProgressBar = require('progress'); var buildmessage = require('./buildmessage.js'); // XXX: Are we happy with chalk (and its sub-dependencies)? var chalk = require('chalk'); +var cleanup = require('./cleanup.js'); PROGRESS_DEBUG = !!process.env.METEOR_PROGRESS_DEBUG; FORCE_PRETTY=undefined; @@ -41,7 +42,7 @@ var Console = function (options) { self._pretty = (FORCE_PRETTY !== undefined ? FORCE_PRETTY : false); - process.on('exit', function(code) { + cleanup.onExit(function (sig) { self.hideProgressBar(); }); }; From e76d6dc7872caa6caef0b5d41a74d73203d4518c Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Fri, 19 Sep 2014 20:00:19 -0700 Subject: [PATCH 31/38] comment on style. --- tools/catalog.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/catalog.js b/tools/catalog.js index 2a19177f75..eb558a11cd 100644 --- a/tools/catalog.js +++ b/tools/catalog.js @@ -580,6 +580,10 @@ _.extend(CompleteCatalog.prototype, { }); if (!_.isEmpty(expPackages)) { // XXX: Couldn't figure out how to word this better for better tenses. + // + // XXX: this shouldn't be here. This is library code... it + // shouldn't be printing. + // https://github.com/meteor/meteor/wiki/Meteor-Style-Guide#only-user-interface-code-should-engage-with-the-user Console.info( "\nIn order to resolve constraints, we had to use the following\n"+ "experimental package versions:"); From 0403d545c7169ef8746b17706a1228f2de2e9e82 Mon Sep 17 00:00:00 2001 From: ekatek Date: Fri, 19 Sep 2014 20:02:17 -0700 Subject: [PATCH 32/38] slightly better wording on some commands --- tools/commands-packages.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/commands-packages.js b/tools/commands-packages.js index 01c53eb7b3..4e7d8bd32f 100644 --- a/tools/commands-packages.js +++ b/tools/commands-packages.js @@ -1359,7 +1359,9 @@ main.registerCommand({ 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."); +" 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."); } return 0; }); @@ -1774,7 +1776,7 @@ main.registerCommand({ // Just for the sake of good messages, check to see if anything changed. if (_.isEqual(newVersions, versions)) { - Console.info("All your package dependencies are already up to date."); + Console.info("Your packages are at their latest compatible versions."); return 0; } From 292b1077598a67a6bb4901059ddbff11f110da51 Mon Sep 17 00:00:00 2001 From: Justin SB Date: Fri, 19 Sep 2014 18:51:07 -0700 Subject: [PATCH 33/38] Put new dependencies into generate-dev-bundle.sh and LICENSE.txt I had put them into dev-bundle-package.json (incorrectly) --- LICENSE.txt | 10 +++++++++- meteor | 2 +- scripts/dev-bundle-package.json | 4 +--- scripts/generate-dev-bundle.sh | 2 ++ 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index b271ca44a4..4c09e63af0 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -795,7 +795,15 @@ Copyright 2012 Irakli Gozalishvili. All rights reserved. ---------- -strip-json-comments: https://github.com/sindresorhus/strip-json-comments +ansi-regex: https://github.com/sindresorhus/ansi-regex +ansi-styles: https://github.com/sindresorhus/ansi-styles +escape-string-regexp: https://github.com/sindresorhus/escape-string-regexp +chalk: https://github.com/sindresorhus/chalk +get-stdin: https://github.com/sindresorhus/get-stdin +has-ansi: https://github.com/sindresorhus/has-ansi +strip-ansi: https://github.com/sindresorhus/strip-ansi +strip-json-comments: https://github.com/sindresorhus/strip-json-comments +supports-color: https://github.com/sindresorhus/supports-color ---------- Copyright (c) Sindre Sorhus (sindresorhus.com) diff --git a/meteor b/meteor index 2cf0fec9a7..c4edf115f4 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/bin/bash -BUNDLE_VERSION=0.3.54 +BUNDLE_VERSION=0.3.55 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. diff --git a/scripts/dev-bundle-package.json b/scripts/dev-bundle-package.json index a8f824ed8e..fbacb95b9f 100644 --- a/scripts/dev-bundle-package.json +++ b/scripts/dev-bundle-package.json @@ -5,8 +5,6 @@ "fibers": "1.0.1", "underscore": "1.5.2", "source-map-support": "0.2.5", - "semver": "2.2.1", - "progress": "1.1.8", - "chalk": "0.5.1" + "semver": "2.2.1" } } diff --git a/scripts/generate-dev-bundle.sh b/scripts/generate-dev-bundle.sh index 334cfb8d62..e4f96af739 100755 --- a/scripts/generate-dev-bundle.sh +++ b/scripts/generate-dev-bundle.sh @@ -161,6 +161,8 @@ npm install kexec@0.2.0 npm install source-map@0.1.32 npm install browserstack-webdriver@2.41.1 npm install node-inspector@0.7.4 +npm install progress@1.1.8 +npm install chalk@0.5.1 # Clean up a big zip file it leaves behind. npm install phantomjs@1.8.1-1 From 4d572fcc1d013286744cb624efa7744e194f977f Mon Sep 17 00:00:00 2001 From: Justin SB Date: Fri, 19 Sep 2014 20:17:58 -0700 Subject: [PATCH 34/38] Insert some pauses to see if this does indeed fix the self-test This would be a hack, but it does seem to work --- tools/tests/hot-code-push.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/tests/hot-code-push.js b/tools/tests/hot-code-push.js index 09f65dd46a..6775c7aa28 100644 --- a/tools/tests/hot-code-push.js +++ b/tools/tests/hot-code-push.js @@ -6,6 +6,11 @@ var net = require('net'); var _ = require('underscore'); var files = require('../files.js'); +// XXX: Remove me. This shouldn't be neeeded, but sometimes +// if we run too quickly on fast (or Linux?) machines, we get +// 'stuck' +HACK_DELAY = 3; + selftest.define("css hot code push", function (options) { var s = new Sandbox({ clients: options.clients @@ -74,6 +79,7 @@ selftest.define("css hot code push", function (options) { run.match("numCssChanges: 1"); run.match(/background-color: (red|rgb\(255, 0, 0\))/); + run.waitSecs(HACK_DELAY); s.write(".meteor/packages", "standard-app-packages"); run.match("removed my-package"); run.match("numCssChanges: 0"); @@ -203,6 +209,7 @@ selftest.define("javascript hot code push", function (options) { run.match("packageVar: bar"); // Add appcache and ensure that the browser still reloads. + run.waitSecs(HACK_DELAY); s.write(".meteor/packages", "standard-app-packages \n appcache"); run.match("added appcache"); run.match("server restarted"); @@ -214,6 +221,7 @@ selftest.define("javascript hot code push", function (options) { run.match("jsVar: bar"); // Remove appcache and ensure that the browser still reloads. + run.waitSecs(HACK_DELAY); s.write(".meteor/packages", "standard-app-packages"); run.match("removed appcache"); run.match("server restarted"); From 715c13ff7c4c3282c8adc5941894a33a48014c4f Mon Sep 17 00:00:00 2001 From: Justin SB Date: Fri, 19 Sep 2014 20:21:31 -0700 Subject: [PATCH 35/38] Update self-test with new 'up to date' language --- tools/tests/autoupdate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/tests/autoupdate.js b/tools/tests/autoupdate.js index 1454c8fd23..29cae921f2 100644 --- a/tools/tests/autoupdate.js +++ b/tools/tests/autoupdate.js @@ -143,7 +143,7 @@ selftest.define("autoupdate", ['checkout'], function () { run = s.run("update"); run.read("myapp: updated to Meteor v3."); - run.match("All your package dependencies are already up to date.\n"); + run.match("Your packages are at their latest compatible versions.\n"); run.expectEnd(); run.expectExit(0); From d26387fdd7b645b14488f09d0ea78bb8f7200d2c Mon Sep 17 00:00:00 2001 From: Justin SB Date: Fri, 19 Sep 2014 20:45:07 -0700 Subject: [PATCH 36/38] More delays in JS HCP self-test --- tools/tests/hot-code-push.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tools/tests/hot-code-push.js b/tools/tests/hot-code-push.js index 6775c7aa28..57c595c1fb 100644 --- a/tools/tests/hot-code-push.js +++ b/tools/tests/hot-code-push.js @@ -138,25 +138,30 @@ selftest.define("javascript hot code push", function (options) { // The server and client both restart if a shared js file is added // or removed. + run.waitSecs(HACK_DELAY); s.write("test.js", "jsVar = 'foo'"); run.match("server restarted"); run.match("client connected: 0"); run.match("jsVar: foo"); run.match("sessionVar: true"); + run.waitSecs(HACK_DELAY); s.unlink("test.js"); run.match("server restarted"); run.match("client connected: 0"); run.match("jsVar: undefined"); + // Only the client should refresh if a client js file is added. Thus, // "client connected" variable will be incremented. + run.waitSecs(HACK_DELAY); s.mkdir("client"); s.write("client/test.js", "jsVar = 'bar'"); run.match("client connected: 1"); run.match("jsVar: bar"); + run.waitSecs(HACK_DELAY); s.unlink("client/test.js"); run.match("client connected: 2"); run.match("jsVar: undefined"); @@ -164,15 +169,18 @@ selftest.define("javascript hot code push", function (options) { // When we change a server file the client should not refresh. We observe // this by changing a server file and then a client file and verifying // that the client has only connected once. + run.waitSecs(HACK_DELAY); s.mkdir("server"); s.write("server/test.js", "jsVar = 'bar'"); run.match("server restarted"); + run.waitSecs(HACK_DELAY); s.write("client/empty.js", ""); run.match("client connected: 0"); // We should not be able to access a server variable from the client. run.match("jsVar: undefined"); + run.waitSecs(HACK_DELAY); s.unlink("client/empty.js"); run.waitSecs(5); run.match("client connected: 1"); @@ -183,19 +191,23 @@ selftest.define("javascript hot code push", function (options) { // it only affects the client. But this is a regression test for a bug where // fixing the HTML file wouldn't actually restart the server; that's the // important part of this test.) + run.waitSecs(HACK_DELAY); s.write("hot-code-push-test.html", ">"); run.match("Errors prevented startup"); run.match("bad formatting in HTML template"); // Fix it. It should notice, and restart. The client will restart too. + run.waitSecs(HACK_DELAY); s.write("hot-code-push-test.html", ""); run.match("server restarted"); run.match("client connected: 0"); // Write something else to it. The client should restart. + run.waitSecs(HACK_DELAY); s.write("hot-code-push-test.html", "foo"); run.match("Client modified -- refreshing"); run.match("client connected: 1"); run.match("jsVar: undefined"); + run.waitSecs(HACK_DELAY); s.write(".meteor/packages", "standard-app-packages \n my-package"); run.match("added my-package"); run.match("server restarted"); @@ -203,6 +215,7 @@ selftest.define("javascript hot code push", function (options) { run.match("jsVar: undefined"); run.match("packageVar: foo"); + run.waitSecs(HACK_DELAY); s.write("packages/my-package/foo.js", "packageVar = 'bar'"); run.match("client connected: 1"); run.match("jsVar: undefined"); @@ -216,6 +229,7 @@ selftest.define("javascript hot code push", function (options) { run.match("client connected: 0"); run.match("jsVar: undefined"); + run.waitSecs(HACK_DELAY); s.write("client/test.js", "jsVar = 'bar'"); run.match("client connected: 1"); run.match("jsVar: bar"); @@ -227,6 +241,7 @@ selftest.define("javascript hot code push", function (options) { run.match("server restarted"); run.match("client connected: 0"); + run.waitSecs(HACK_DELAY); s.write("client/test.js", "jsVar = 'baz'"); run.match("client connected: 1"); run.match("jsVar: baz"); @@ -235,12 +250,14 @@ selftest.define("javascript hot code push", function (options) { // Setting the autoupdateVersion to a different string should also // force the client to restart. + run.waitSecs(HACK_DELAY); s.write("server/test.js", "Package.autoupdate.Autoupdate.autoupdateVersion = 'random'"); run.match("server restarted"); run.match("client connected: 0"); run.match("jsVar: undefined"); + run.waitSecs(HACK_DELAY); s.unlink("server/test.js"); run.match("server restarted"); From 7e50a51d2b42dfb47402b38cf7d55e00f6ac0c3d Mon Sep 17 00:00:00 2001 From: ekatek Date: Fri, 19 Sep 2014 20:46:16 -0700 Subject: [PATCH 37/38] do not crash on maintainers of unknown package --- tools/commands-packages.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/commands-packages.js b/tools/commands-packages.js index 4e7d8bd32f..b43b6a87d8 100644 --- a/tools/commands-packages.js +++ b/tools/commands-packages.js @@ -2224,6 +2224,12 @@ main.registerCommand({ record = fullRecord.record; } + if (!record) { + Console.info( +"Could not get list of maintainers: package " + name + " does not exist."); + return 1; + } + Console.info("\n The maintainers for " + name + " are:"); _.each(record.maintainers, function (user) { if (! user || !user.username) From 78f36994e332fe1eb80eb70eaa01fdca87155b82 Mon Sep 17 00:00:00 2001 From: ekatek Date: Fri, 19 Sep 2014 20:47:14 -0700 Subject: [PATCH 38/38] do not start line with space --- tools/commands-packages.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/commands-packages.js b/tools/commands-packages.js index b43b6a87d8..bbd1b1593b 100644 --- a/tools/commands-packages.js +++ b/tools/commands-packages.js @@ -2230,7 +2230,7 @@ main.registerCommand({ return 1; } - Console.info("\n The maintainers for " + name + " are:"); + Console.info("\nThe maintainers for " + name + " are:"); _.each(record.maintainers, function (user) { if (! user || !user.username) Console.info("");