Merge branch 'release-0.6.5' into devel

Conflicts:
	packages/less/plugin/compile-less.js
This commit is contained in:
David Glasser
2013-08-02 17:11:41 -07:00
17 changed files with 163 additions and 67 deletions

View File

@@ -1,6 +1,8 @@
## vNEXT
## v0.6.5
* linker! namespacing, exports, unipackages, weak and unordered dependencies,
etc. sourcemaps (including for coffee). standard-app-packages. don't
implicitly use all app packages. lots of stuff moved from server.js (now

View File

@@ -0,0 +1,3 @@
// Do we already have a global JSON object? Export it as our JSON object.
if (window.JSON)
JSON = window.JSON;

View File

@@ -3,10 +3,12 @@ Package.describe({
internal: true
});
// We need to figure out how to serve this file only to browsers that
// don't have JSON.stringify (eg, IE7 and earlier -- or is that IE8?)
// We need to figure out how to serve this file only to browsers that don't have
// JSON.stringify (eg, IE7 and earlier, and IE8 outside of "standards mode")
Package.on_use(function (api) {
// Node always has JSON; we only need this in some browsers.
api.export('JSON', 'client');
api.add_files('json_native.js', 'client');
api.add_files('json2.js', 'client');
});

View File

@@ -1,8 +1,18 @@
var fs = Npm.require('fs');
var path = Npm.require('path');
var less = Npm.require('less');
var Future = Npm.require('fibers/future');
Plugin.registerSourceHandler("less", function (compileStep) {
// XXX annoying that this is replicated in .css, .less, and .styl
if (! compileStep.archMatches('browser')) {
// XXX in the future, might be better to emit some kind of a
// warning if a stylesheet is included on the server, rather than
// silently ignoring it. but that would mean you can't stick .css
// at the top level of your app, which is kind of silly.
return;
}
var source = compileStep.read().toString('utf8');
var options = {
// Use fs.readFileSync to process @imports. This is the bundler, so
@@ -13,24 +23,28 @@ Plugin.registerSourceHandler("less", function (compileStep) {
paths: [path.dirname(compileStep._fullInputPath)] // for @import
};
var f = new Future;
var css;
try {
less.render(source, options, function (err, css) {
if (err) {
// XXX better error handling, once the Plugin interface support it
throw new Error(err.message);
}
compileStep.addStylesheet({
path: compileStep.inputPath + ".css",
data: css
});
});
less.render(source, options, f.resolver());
css = f.wait();
} catch (e) {
// less.render() is supposed to report any errors via its
// callback. But sometimes, it throws them instead. This is
// probably a bug in less. Be prepared for either behavior.
throw new Error(compileStep.inputPath + ": Less compiler error: " + e.message);
compileStep.error({
message: "Less compiler error: " + e.message,
sourcePath: e.filename || compileStep.inputPath,
line: e.line - 1, // dunno why, but it matches
column: e.column + 1
});
return;
}
compileStep.addStylesheet({
path: compileStep.inputPath + ".css",
data: css
});
});;
// Register lessimport files with the dependency watcher, without actually

View File

@@ -1287,18 +1287,17 @@ Tinytest.add("livedata connection - onReconnect prepends messages correctly with
var getSelfConnectionUrl = function () {
if (Meteor.isClient) {
return "/";
return Meteor._relativeToSiteRootUrl("/");
} else {
return Meteor.absoluteUrl();
}
};
if (Meteor.isServer) {
var reversed = {};
Meteor.methods({
reverse: function (arg) {
reversed[arg] = true;
return arg.split("").reverse().join("");
// Return something notably different from reverse.meteor.com.
return arg.split("").reverse().join("") + " LOCAL";
}
});
}
@@ -1325,22 +1324,10 @@ testAsyncMulti("livedata connection - reconnect to a different server", [
if (self.doTest) {
self.conn.reconnect({url: getSelfConnectionUrl()});
self.conn.call("reverse", "bar", expect(function (err, res) {
test.equal(res, "rab");
}));
}
},
function (test, expect) {
var self = this;
var id = Random.id();
if (self.doTest) {
self.conn.call("reverse", id, expect(function (err, res) {
if (Meteor.isServer) {
test.isTrue(reversed[id]);
}
test.equal(res, "rab LOCAL");
}));
}
}
]);
Tinytest.addAsync("livedata connection - version negotiation requires renegotiating",

View File

@@ -600,6 +600,8 @@ if (Meteor.isClient) {
]);
}
var selfUrl = Meteor.isServer
? Meteor.absoluteUrl() : Meteor._relativeToSiteRootUrl('/');
if (Meteor.isServer) {
Meteor.methods({
@@ -613,7 +615,7 @@ if (Meteor.isServer) {
testAsyncMulti("livedata - connect works from both client and server", [
function (test, expect) {
var self = this;
self.conn = DDP.connect(Meteor.absoluteUrl());
self.conn = DDP.connect(selfUrl);
pollUntil(expect, function () {
return self.conn.status().connected;
}, 10000);
@@ -637,7 +639,7 @@ if (Meteor.isServer) {
testAsyncMulti("livedata - method call on server blocks in a fiber way", [
function (test, expect) {
var self = this;
self.conn = DDP.connect(Meteor.absoluteUrl());
self.conn = DDP.connect(selfUrl);
pollUntil(expect, function () {
return self.conn.status().connected;
}, 10000);

View File

@@ -23,9 +23,10 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// <METEOR> Commented out JSO implementation (use json package instead).
// JSON2 by Douglas Crockford (minified).
var JSON;JSON||(JSON={}),function(){function str(a,b){var c,d,e,f,g=gap,h,i=b[a];i&&typeof i=="object"&&typeof i.toJSON=="function"&&(i=i.toJSON(a)),typeof rep=="function"&&(i=rep.call(b,a,i));switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";gap+=indent,h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c<f;c+=1)h[c]=str(c,i)||"null";e=h.length===0?"[]":gap?"[\n"+gap+h.join(",\n"+gap)+"\n"+g+"]":"["+h.join(",")+"]",gap=g;return e}if(rep&&typeof rep=="object"){f=rep.length;for(c=0;c<f;c+=1)typeof rep[c]=="string"&&(d=rep[c],e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e))}else for(d in i)Object.prototype.hasOwnProperty.call(i,d)&&(e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e));e=h.length===0?"{}":gap?"{\n"+gap+h.join(",\n"+gap)+"\n"+g+"}":"{"+h.join(",")+"}",gap=g;return e}}function quote(a){escapable.lastIndex=0;return escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return typeof b=="string"?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function f(a){return a<10?"0"+a:a}"use strict",typeof Date.prototype.toJSON!="function"&&(Date.prototype.toJSON=function(a){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null},String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(a){return this.valueOf()});var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;typeof JSON.stringify!="function"&&(JSON.stringify=function(a,b,c){var d;gap="",indent="";if(typeof c=="number")for(d=0;d<c;d+=1)indent+=" ";else typeof c=="string"&&(indent=c);rep=b;if(!b||typeof b=="function"||typeof b=="object"&&typeof b.length=="number")return str("",{"":a});throw new Error("JSON.stringify")}),typeof JSON.parse!="function"&&(JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&typeof e=="object")for(c in e)Object.prototype.hasOwnProperty.call(e,c)&&(d=walk(e,c),d!==undefined?e[c]=d:delete e[c]);return reviver.call(a,b,e)}var j;text=String(text),cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)}));if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))){j=eval("("+text+")");return typeof reviver=="function"?walk({"":j},""):j}throw new SyntaxError("JSON.parse")})}()
// var JSON;JSON||(JSON={}),function(){function str(a,b){var c,d,e,f,g=gap,h,i=b[a];i&&typeof i=="object"&&typeof i.toJSON=="function"&&(i=i.toJSON(a)),typeof rep=="function"&&(i=rep.call(b,a,i));switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";gap+=indent,h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c<f;c+=1)h[c]=str(c,i)||"null";e=h.length===0?"[]":gap?"[\n"+gap+h.join(",\n"+gap)+"\n"+g+"]":"["+h.join(",")+"]",gap=g;return e}if(rep&&typeof rep=="object"){f=rep.length;for(c=0;c<f;c+=1)typeof rep[c]=="string"&&(d=rep[c],e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e))}else for(d in i)Object.prototype.hasOwnProperty.call(i,d)&&(e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e));e=h.length===0?"{}":gap?"{\n"+gap+h.join(",\n"+gap)+"\n"+g+"}":"{"+h.join(",")+"}",gap=g;return e}}function quote(a){escapable.lastIndex=0;return escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return typeof b=="string"?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function f(a){return a<10?"0"+a:a}"use strict",typeof Date.prototype.toJSON!="function"&&(Date.prototype.toJSON=function(a){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null},String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(a){return this.valueOf()});var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;typeof JSON.stringify!="function"&&(JSON.stringify=function(a,b,c){var d;gap="",indent="";if(typeof c=="number")for(d=0;d<c;d+=1)indent+=" ";else typeof c=="string"&&(indent=c);rep=b;if(!b||typeof b=="function"||typeof b=="object"&&typeof b.length=="number")return str("",{"":a});throw new Error("JSON.stringify")}),typeof JSON.parse!="function"&&(JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&typeof e=="object")for(c in e)Object.prototype.hasOwnProperty.call(e,c)&&(d=walk(e,c),d!==undefined?e[c]=d:delete e[c]);return reviver.call(a,b,e)}var j;text=String(text),cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)}));if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))){j=eval("("+text+")");return typeof reviver=="function"?walk({"":j},""):j}throw new SyntaxError("JSON.parse")})}()
// </METEOR>
// [*] Including lib/index.js
// Public object

View File

@@ -101,7 +101,7 @@ _.extend(LivedataTest.ClientStream.prototype, {
self._clearConnectionAndHeartbeatTimers();
if (self.socket) {
self.socket.onmessage = self.socket.onclose
= self.socket.onerror = function () {};
= self.socket.onerror = self.socket.onheartbeat = function () {};
self.socket.close();
self.socket = null;
}

View File

@@ -247,7 +247,7 @@ Log.format = function (obj, options) {
'-',
timeStamp,
utcOffsetStr,
timeInexact ? '?' : ' ',
timeInexact ? '? ' : ' ',
appInfo,
sourceInfo,
stderrIndicator].join('');

View File

@@ -51,7 +51,7 @@ Tinytest.add("logging - log", function (test) {
[0, "0", "falsy - 0"],
[null, "null", "falsy - null"],
[undefined, "undefined", "falsy - undefined"],
["2013-06-13T01:15:16.000Z", new Date("2013-06-13T01:15:16.000Z"), "date"],
[new Date("2013-06-13T01:15:16.000Z"), new Date("2013-06-13T01:15:16.000Z"), "date"],
[/[^regexp]{0,1}/g, "/[^regexp]{0,1}/g", "regexp"],
[true, "true", "boolean - true"],
[false, "false", "boolean - false"],
@@ -72,7 +72,11 @@ Tinytest.add("logging - log", function (test) {
var recieved = intercepted[index];
var obj = EJSON.parse(recieved);
if (_.isDate(expected))
// IE8 doesn't support this date format. Skip it.
if (expected && expected.toString && expected.toString() === "NaN")
return;
if (_.isDate(testcase[0]))
obj.message = new Date(obj.message);
test.equal(obj.message, expected, 'Logging ' + testName);
});
@@ -137,7 +141,7 @@ Tinytest.add("logging - format", function (test) {
test.equal(
Log.format({message: "message", time: time, timeInexact: true, level: level}),
level.charAt(0).toUpperCase() + "20120908-07:06:05.004" + utcOffsetStr + "?message");
level.charAt(0).toUpperCase() + "20120908-07:06:05.004" + utcOffsetStr + "? message");
test.equal(
Log.format({foo1: "bar1", foo2: "bar2", time: time, level: level}),

View File

@@ -3,11 +3,12 @@
source file. */
Plugin.registerSourceHandler("css", function (compileStep) {
// XXX use archinfo rather than rolling our own
if (! compileStep.arch.match(/^browser(\.|$)/)) {
// XXX annoying that this is replicated in .css, .less, and .styl
if (! compileStep.archMatches('browser')) {
// XXX in the future, might be better to emit some kind of a
// warning if a stylesheet is included on the server, rather than
// silently ignoring it
// silently ignoring it. but that would mean you can't stick .css
// at the top level of your app, which is kind of silly.
return;
}

View File

@@ -12,7 +12,7 @@ Package._transitional_registerBuildPlugin({
});
Package.on_test(function (api) {
api.use(['tinytest', 'stylus', 'test-helpers'])
api.use(['tinytest', 'stylus', 'test-helpers']);
api.use('spark');
api.add_files(['stylus_tests.styl', 'stylus_tests.js'], 'client');
});

View File

@@ -1,21 +1,34 @@
var fs = Npm.require('fs');
var stylus = Npm.require('stylus');
var nib = Npm.require('nib');
var Future = Npm.require('fibers/future');
Plugin.registerSourceHandler("styl", function (compileStep) {
// XXX annoying that this is replicated in .css, .less, and .styl
if (! compileStep.archMatches('browser')) {
// XXX in the future, might be better to emit some kind of a
// warning if a stylesheet is included on the server, rather than
// silently ignoring it. but that would mean you can't stick .css
// at the top level of your app, which is kind of silly.
return;
}
var f = new Future;
stylus(compileStep.read().toString('utf8'))
.use(nib())
.set('filename', compileStep.inputPath)
.render(function(err, output) {
if (err) {
// XXX better error handling, once the Plugin interface support it
throw new Error('Stylus compiler error: ' + err.message);
}
.render(f.resolver());
compileStep.addStylesheet({
path: compileStep.inputPath + ".css",
data: output
});
try {
var css = f.wait();
} catch (e) {
compileStep.error({
message: "Stylus compiler error: " + e.message
});
return;
}
);
compileStep.addStylesheet({
path: compileStep.inputPath + ".css",
data: css
});
});

View File

@@ -110,9 +110,25 @@ _.extend(Library.prototype, {
for (var i = 0; i < self.localPackageDirs.length; ++i) {
var packageDir = path.join(self.localPackageDirs[i], name);
// XXX or unipackage.json? see also watchLocalPackageDirs
if (fs.existsSync(path.join(packageDir, 'package.js')))
// A directory is a package if it either contains 'package.js' (a package
// source tree) or 'unipackage.json' (a compiled unipackage). (Actually,
// for now, unipackages contain a dummy package.js too.)
//
// XXX support for putting unipackages in a local package dir is
// incomplete! They will be properly loaded, but other packages that
// depend on them have no way of knowing when they change! unipackages
// that are the .build of a source tree work fine (they have a
// buildinfo.json and can be rebuilt), and warehouse unipackages work fine
// too (users are not supposed to edit them (they are read-only on disk),
// and their pathname specifies a version). But if you, eg, have a
// unipackage of coffeescript in a local package directory, build another
// package dependending on it, and substitute another version of the
// unipackage in the same location, nothing will ever rebuild your
// package!
if (fs.existsSync(path.join(packageDir, 'package.js')) ||
fs.existsSync(path.join(packageDir, 'unipackage.json'))) {
return packageDir;
}
}
// Try the Meteor distribution, if we have one.
@@ -120,6 +136,8 @@ _.extend(Library.prototype, {
if (version) {
packageDir = path.join(warehouse.getWarehouseDir(),
'packages', name, version);
// The warehouse is theoretically constructed carefully enough that the
// directory really should not exist unless it is complete.
if (! fs.existsSync(packageDir))
throw new Error("Package missing from warehouse: " + name +
" version " + version);
@@ -224,8 +242,15 @@ _.extend(Library.prototype, {
if (! buildmessage.jobHasMessages() && // ensure no errors!
pkg.canBeSavedAsUnipackage()) {
// Save it, for a fast load next time
files.add_to_gitignore(packageDir, '.build*');
pkg.saveAsUnipackage(buildDir, { buildOfPath: packageDir });
try {
files.add_to_gitignore(packageDir, '.build*');
pkg.saveAsUnipackage(buildDir, { buildOfPath: packageDir });
} catch (e) {
// If we can't write to this directory, we don't get to cache our
// output, but otherwise life is good.
if (!(e && (e.code === 'EACCES' || e.code === 'EPERM')))
throw e;
}
}
});
}
@@ -272,7 +297,7 @@ _.extend(Library.prototype, {
// Register local package directories with a watchSet. We want to know if a
// package is created or deleted, which includes both its top-level source
// directory or its package.js file.
// directory and its main package metadata file.
watchLocalPackageDirs: function (watchSet) {
var self = this;
_.each(self.localPackageDirs, function (packageDir) {
@@ -283,7 +308,8 @@ _.extend(Library.prototype, {
_.each(packages, function (p) {
watch.readAndWatchFile(watchSet,
path.join(packageDir, p, 'package.js'));
// XXX unipackage.json too?
watch.readAndWatchFile(watchSet,
path.join(packageDir, p, 'unipackage.json'));
});
});
},

View File

@@ -231,6 +231,12 @@ _.extend(Slice.prototype, {
var handler = !fileOptions.isAsset && self._getSourceHandler(ext);
var contents = watch.readAndWatchFile(self.watchSet, absPath);
if (contents === null) {
buildmessage.error("File not found: " + source.relPath);
// recover by ignoring
return;
}
if (! handler) {
// If we don't have an extension handler, serve this file as a
// static resource on the client, or ignore it on the server.
@@ -359,6 +365,9 @@ _.extend(Slice.prototype, {
packageName: self.pkg.name,
rootOutputPath: self.pkg.serveRoot,
arch: self.arch,
archMatches: function (pattern) {
return archinfo.matches(self.arch, pattern);
},
fileOptions: fileOptions,
declaredExports: _.pluck(self.declaredExports, 'name'),
read: function (n) {
@@ -1792,6 +1801,28 @@ _.extend(Package.prototype, {
var otherSliceRegExp =
(sliceName === "server" ? /^client\/$/ : /^server\/$/);
// The paths that we've called checkForInfiniteRecursion on.
var seenPaths = {};
// Used internally by fs.realpathSync as an optimization.
var realpathCache = {};
var checkForInfiniteRecursion = function (relDir) {
var absPath = path.join(self.sourceRoot, relDir);
try {
var realpath = fs.realpathSync(absPath, realpathCache);
} catch (e) {
if (!e || e.code !== 'ELOOP')
throw e;
// else leave realpath undefined
}
if (realpath === undefined || _.has(seenPaths, realpath)) {
buildmessage.error("Symlink cycle detected at " + relDir);
// recover by returning no files
return true;
}
seenPaths[realpath] = true;
return false;
};
// Read top-level subdirectories. Ignore subdirectories that have
// special handling.
var sourceDirectories = readAndWatchDirectory('', {
@@ -1800,13 +1831,17 @@ _.extend(Package.prototype, {
/^public\/$/, /^private\/$/,
otherSliceRegExp].concat(sourceExclude)
});
checkForInfiniteRecursion('');
// XXX avoid infinite recursion with bad symlinks
while (!_.isEmpty(sourceDirectories)) {
var dir = sourceDirectories.shift();
// remove trailing slash
dir = dir.substr(0, dir.length - 1);
if (checkForInfiniteRecursion(dir))
return []; // pretend we found no files
// Find source files in this directory.
Array.prototype.push.apply(sources, readAndWatchDirectory(dir, {
include: sourceInclude,
@@ -1845,7 +1880,6 @@ _.extend(Package.prototype, {
include: [new RegExp('^' + assetDir + '/$')]
});
// XXX avoid infinite recursion with bad symlinks
if (!_.isEmpty(assetDirs)) {
if (!_.isEqual(assetDirs, [assetDir + '/']))
throw new Error("Surprising assetDirs: " + JSON.stringify(assetDirs));
@@ -1855,6 +1889,9 @@ _.extend(Package.prototype, {
// remove trailing slash
dir = dir.substr(0, dir.length - 1);
if (checkForInfiniteRecursion(dir))
return []; // pretend we found no files
// Find asset files in this directory.
var assetsAndSubdirs = readAndWatchDirectory(dir, {
include: [/.?/],

View File

@@ -561,9 +561,10 @@ exports.run = function (context, options) {
Status.running = true;
var rootUrl = process.env.ROOT_URL || ('http://localhost:' + outerPort);
var rootUrl = process.env.ROOT_URL ||
('http://localhost:' + outerPort + '/');
if (firstRun) {
process.stdout.write("=> Meteor server running on: " + rootUrl + "/\n");
process.stdout.write("=> Meteor server running on: " + rootUrl + "\n");
firstRun = false;
lastThingThatPrintedWasRestartMessage = false;
} else {

View File

@@ -228,9 +228,12 @@ var readDirectory = function (options) {
// XXX Does the treatment of symlinks make sense?
var stats = fs.statSync(path.join(options.absPath, entry));
} catch (e) {
// Disappeared after the readdirSync (or a dangling symlink)? Eh, pretend
// it was never there in the first place.
return;
if (e && (e.code === 'ENOENT')) {
// Disappeared after the readdirSync (or a dangling symlink)? Eh,
// pretend it was never there in the first place.
return;
}
throw e;
}
// XXX if we're on windows, I guess it's possible for files to end with '/'.
if (stats.isDirectory())