From f7fb9742b36841d0ec616d219f4e7c59a653f6ba Mon Sep 17 00:00:00 2001 From: Geoff Schmidt Date: Tue, 16 Jul 2013 20:01:28 -0700 Subject: [PATCH 001/175] cache unipackage.load --- tools/unipackage.js | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/tools/unipackage.js b/tools/unipackage.js index 7f45e029fc..aa13d82f71 100644 --- a/tools/unipackage.js +++ b/tools/unipackage.js @@ -7,9 +7,18 @@ var bundler = require('./bundler.js'); // tools (such as 'meteor'.) The requested packages will be loaded // together will all of their dependencies, and each time you call // this function you load another, distinct copy of all of the -// packages. The return value is an object that maps package name to -// package exports (that is, it is the Package object from inside the -// sandbox created for the newly loaded packages.) +// packages (except see note about caching below.) The return value is +// an object that maps package name to package exports (that is, it is +// the Package object from inside the sandbox created for the newly +// loaded packages.) +// +// Caching: There is a simple cache. If you call this function with +// exactly the same library, release, and packages, we will attempt to +// return the memoized return value from the previous load (rather +// than creating a whole new copy of the packages in memory.) The +// caching logic is not particularly sophisticated. For example, +// whenever you call load() with a different library the cache is +// flushed. // // Options: // - library: The Library to use to retrieve packages and their @@ -30,11 +39,27 @@ var bundler = require('./bundler.js'); // var reverse = Meteor.connect('reverse.meteor.com'); // console.log(reverse.call('reverse', 'hello world')); +var cacheLibrary = null; +var cacheRelease = null; +var cache = null; // map from package names (joined with ',') to return value + var load = function (options) { options = options || {}; if (! (options.library instanceof library.Library)) throw new Error("unipackage.load requires a library"); + // Check the cache first + if (cacheLibrary !== options.library || + cacheRelease !== options.release) { + cacheLibrary = options.library; + cacheRelease = options.release; + cache = {}; + } + var cacheKey = (options.packages || []).join(','); + if (_.has(cache, cacheKey)) { + return cache[cacheKey]; + } + // Set up a minimal server-like environment (omitting the parts that // are specific to the HTTP server.) Kind of a hack. I suspect this // will get refactored before too long. Note that @@ -55,6 +80,9 @@ var load = function (options) { // Run any user startup hooks. _.each(env.__meteor_bootstrap__.startup_hooks, function (x) { x(); }); + // Save to cache + cache[cacheKey] = ret; + return ret; }; From 83ab263a88b45fac60ef9c3e064102923896ccda Mon Sep 17 00:00:00 2001 From: Geoff Schmidt Date: Tue, 16 Jul 2013 21:25:56 -0700 Subject: [PATCH 002/175] Adjust source maps more efficiently at link time. (Saves more than a second off of linking a small app!) --- tools/linker.js | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/tools/linker.js b/tools/linker.js index b6eaf7eab7..92a5bad07f 100644 --- a/tools/linker.js +++ b/tools/linker.js @@ -693,20 +693,26 @@ var link = function (options) { var ret = []; _.each(options.prelinkFiles, function (file) { if (file.sourceMap) { - var chunks = [header]; if (options.includeSourceMapInstructions) - chunks.push("\n" + SOURCE_MAP_INSTRUCTIONS_COMMENT + "\n\n"); - chunks.push(sourcemap.SourceNode.fromStringWithSourceMap( - file.source, new sourcemap.SourceMapConsumer(file.sourceMap))); - chunks.push(footer); - var node = new sourcemap.SourceNode(null, null, null, chunks); - var results = node.toStringWithSourceMap({ - file: file.servePath - }); + header += "\n" + SOURCE_MAP_INSTRUCTIONS_COMMENT + "\n\n"; + + // Bias the source map by the length of the header without + // (fully) parsing and re-serializing it. (We used to do this + // with the source-map library, but it was incredibly slow, + // accounting for over half of bundling time.) It would be nice + // if we could use "index maps" for this (the 'sections' key), + // as that would let us avoid even JSON-parsing the source map, + // but that doesn't seem to be supported by Firefox yet. + if (header.charAt(header.length - 1) !== "\n") + header += "\n"; // make sure it's a whole number of lines + var headerLines = header.split('\n').length - 1; + var sourceMapJson = JSON.parse(file.sourceMap); + sourceMapJson.mappings = (new Array(headerLines + 1).join(';')) + + sourceMapJson.mappings; ret.push({ - source: results.code, + source: header + file.source + footer, servePath: file.servePath, - sourceMap: results.map.toString() + sourceMap: JSON.stringify(sourceMapJson) }); } else { ret.push({ From b3130558d2356a17f2327775aaf857d128f111ae Mon Sep 17 00:00:00 2001 From: Geoff Schmidt Date: Tue, 16 Jul 2013 23:26:56 -0700 Subject: [PATCH 003/175] During development, don't reload packages that didn't change. Saves about 150ms off development reload. --- tools/library.js | 43 ++++++++++++++++++++++--- tools/packages.js | 80 ++++++++++++++++++++++++++++++++++++----------- tools/run.js | 2 +- 3 files changed, 102 insertions(+), 23 deletions(-) diff --git a/tools/library.js b/tools/library.js index 040cbf645e..b2f5cc48ca 100644 --- a/tools/library.js +++ b/tools/library.js @@ -34,7 +34,11 @@ var Library = function (options) { return stats.isDirectory(); }); - self.loadedPackages = {}; + // these three are maps from package name to Package + self.loadedPackages = {}; // current cache + self.previouslyLoadedPackages = {}; // need validation after soft reset + self.warehouseCache = {}; // unconditionally survive soft reset + self.overrides = {}; // package name to package directory }; @@ -60,9 +64,29 @@ _.extend(Library.prototype, { delete self.overrides[packageName]; }, - // Force reload of all packages. See description at get(). - refresh: function () { + // Force reload of changed packages. See description at get(). + // + // If soft is false, the default, the cache is totally flushed and + // all packages are reloaded unconditionally. + // + // If soft is true, then packages from the warehouse aren't reloaded + // (they are supposed to be immutable, after all), and if we loaded + // a built package with dependency info, we won't reload it if the + // dependency info says that its source files are still up to + // date. The ideas is that assuming the user is "following the + // rules", this will correctly reload any changed packages while in + // most cases avoiding nearly all reloading. + refresh: function (soft) { var self = this; + soft = soft || false; + + if (soft) { + self.previouslyLoadedPackages = self.loadedPackages; + } else { + self.previouslyLoadedPackages = {}; + self.warehouseCache = {}; + } + self.loadedPackages = {}; }, @@ -91,8 +115,16 @@ _.extend(Library.prototype, { return name; // Packages cached from previous calls - if (name in self.loadedPackages) + if (_.has(self.warehouseCache, name)) + self.loadedPackages[name] = self.warehouseCache[name]; + else if (_.has(self.previouslyLoadedPackages, name) && + self.previouslyLoadedPackages[name].checkUpToDate()) + self.loadedPackages[name] = self.previouslyLoadedPackages[name]; + delete self.previouslyLoadedPackages[name]; + + if (_.has(self.loadedPackages, name)) { return self.loadedPackages[name]; + } // If there's an override for this package, use that without // looking at any other options. @@ -187,6 +219,9 @@ _.extend(Library.prototype, { } } + if (fromWarehouse) + self.warehouseCache[name] = pkg; + return pkg; }, diff --git a/tools/packages.js b/tools/packages.js index 9762b33d93..5b918aa69f 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -828,6 +828,12 @@ var Package = function (library) { // building, into two flags. self.pluginsBuilt = false; self.slicesBuilt = false; + + // True if the dependencyInfo in the slices can be taken as as + // accurate. (False if we loaded the package from disk and it didn't + // come with dependency info, eg, it was a release build built by + // someone else.) + self.haveDependencyInfo = false; }; _.extend(Package.prototype, { @@ -1018,6 +1024,7 @@ _.extend(Package.prototype, { slice.build(); }); self.slicesBuilt = true; + self.haveDependencyInfo = true; }, // Programmatically initialized a package from scratch. For now, @@ -1862,15 +1869,21 @@ _.extend(Package.prototype, { // Read the dependency info (if present), and make the strings // back into regexps - var dependencies = buildInfoJson.dependencies || - { files: {}, directories: {} }; - _.each(dependencies.directories, function (d) { - _.each(["include", "exclude"], function (k) { - d[k] = _.map(d[k], function (s) { - return new RegExp(s); + var haveDependencyInfo, dependencies; + if (buildInfoJson.dependencies) { + haveDependencyInfo = true; + dependencies = buildInfoJson.dependencies; + _.each(dependencies.directories, function (d) { + _.each(["include", "exclude"], function (k) { + d[k] = _.map(d[k], function (s) { + return new RegExp(s); + }); }); }); - }); + } else { + haveDependencyInfo = false; + dependencies = { files: {}, directories: {} }; + } // If we're supposed to check the dependencies, go ahead and do so if (options.onlyIfUpToDate) { @@ -1890,17 +1903,7 @@ _.extend(Package.prototype, { return false; } - var isUpToDate = true; - var watcher = new watch.Watcher({ - files: dependencies.files, - directories: dependencies.directories, - onChange: function () { - isUpToDate = false; - } - }); - watcher.stop(); - - if (! isUpToDate) + if (! self.checkUpToDate(dependencies)) return false; } @@ -1909,6 +1912,7 @@ _.extend(Package.prototype, { summary: mainJson.summary, internal: mainJson.internal }; + self.haveDependencyInfo = haveDependencyInfo; self.defaultSlices = mainJson.defaultSlices; self.testSlices = mainJson.testSlices; @@ -2029,6 +2033,46 @@ _.extend(Package.prototype, { return true; }, + // Try to check if this package is up-to-date (that is, whether its + // source files have been modified.) True if we have dependency info + // and it says that the package is up-to-date. False if a source + // file has changed OR if we loaded the package from disk and it + // didn't have dependency info. + // + // The argument _dependencyInfo is for internal use and should not + // be set by the caller. + checkUpToDate: function (_dependencyInfo) { + var self = this; + + // Compute the dependency info to use + var dependencyInfo = _dependencyInfo; + if (! dependencyInfo && self.haveDependencyInfo) { + dependencyInfo = { files: {}, directories: {} }; + _.each(self.slices, function (slice) { + // XXX is naive merge sufficient here? + _.extend(dependencyInfo.files, + slice.dependencyInfo.files); + _.extend(dependencyInfo.directories, + slice.dependencyInfo.directories); + }); + } + + if (! dependencyInfo) + return false; + + var isUpToDate = true; + var watcher = new watch.Watcher({ + files: dependencyInfo.files, + directories: dependencyInfo.directories, + onChange: function () { + isUpToDate = false; + } + }); + watcher.stop(); + + return isUpToDate; + }, + // True if this package can be saved as a unipackage canBeSavedAsUnipackage: function () { var self = this; diff --git a/tools/run.js b/tools/run.js index 00c4553a9e..12cd5f86e5 100644 --- a/tools/run.js +++ b/tools/run.js @@ -462,7 +462,7 @@ exports.run = function (context, options) { if (Status.crashing) logToClients({'system': "=> Modified -- restarting."}); Status.reset(); - context.library.refresh(); // pick up changes to packages + context.library.refresh(true); // pick up changes to packages restartServer(); } }); From 869bc23c25478b2bb51603292849b11dddd01ae7 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Tue, 16 Jul 2013 17:23:13 -0700 Subject: [PATCH 004/175] Rename "static" directory to "assets". --- tools/bundler.js | 65 ++++++++++++++---------------- tools/packages.js | 36 ++++++++--------- tools/server/boot.js | 4 +- tools/tests/test_bundler_assets.js | 14 +++---- 4 files changed, 58 insertions(+), 61 deletions(-) diff --git a/tools/bundler.js b/tools/bundler.js index 529db4c358..6e39787009 100644 --- a/tools/bundler.js +++ b/tools/bundler.js @@ -64,7 +64,7 @@ // - manifest: array of resources to serve with HTTP, each an object: // - path: path of file relative to program.json // - where: "client" -// - type: "js", "css", or "static" +// - type: "js", "css", or "asset" // - cacheable: is it safe to ask the browser to cache this file (boolean) // - url: relative url to download the resource, includes cache busting // parameter when used @@ -103,7 +103,7 @@ // - node_modules: if Npm.require is called from this file, this is // the path (relative to program.json) of the directory that should // be search for npm modules -// - staticDirectory: directory to search for static assets when +// - assetsDirectory: directory to search for static assets when // Assets.getText and Assets.getBinary are called from this file. // - sourceMap: if present, path of a file that contains a source // map for this file, relative to program.json @@ -218,12 +218,12 @@ var NodeModulesDirectory = function (options) { }; /////////////////////////////////////////////////////////////////////////////// -// StaticDirectory +// AssetsDirectory /////////////////////////////////////////////////////////////////////////////// // Like a NodeModulesDirectory but for static assets that are accessible via the // Assets API. -var StaticDirectory = function (options) { +var AssetsDirectory = function (options) { var self = this; self.sourcePath = options.sourcePath; self.bundlePath = options.bundlePath; @@ -276,7 +276,7 @@ var File = function (options) { // in the "server" architecture. self.nodeModulesDirectory = null; - self.staticDirectory = null; + self.assetsDirectory = null; self._contents = options.data || null; // contents, if known, as a Buffer self._hash = null; // hash, if known, as a hex string @@ -360,30 +360,28 @@ _.extend(File.prototype, { setTargetPathFromRelPath: function (relPath) { var self = this; // XXX hack - if (relPath.match(/^packages\//) || relPath.match(/^static\//)) + if (relPath.match(/^packages\//) || relPath.match(/^assets\//)) self.targetPath = relPath; else self.targetPath = path.join('app', relPath); }, - setStaticDirectory: function (relPath, staticSourceDirectory) { + setAssetsDirectory: function (relPath, assetsSourceDirectory) { var self = this; // For package code, static assets go inside a directory inside - // static/packages specific to this package. Application assets (e.g. those - // inside private/) go in static/app/. + // assets/packages specific to this package. Application assets (e.g. those + // inside private/) go in assets/app/. // XXX same hack as above - // XXX XXX is this all still true? - // XXX rename static -> assets on server var bundlePath; if (relPath.match(/^packages\//)) { var dir = path.dirname(relPath); var base = path.basename(relPath, ".js"); - bundlePath = path.join('static', dir, base); + bundlePath = path.join('assets', dir, base); } else { - bundlePath = path.join('static', 'app'); + bundlePath = path.join('assets', 'app'); } - self.staticDirectory = new StaticDirectory({ - sourcePath: staticSourceDirectory, + self.assetsDirectory = new AssetsDirectory({ + sourcePath: assetsSourceDirectory, bundlePath: bundlePath }); }, @@ -437,7 +435,7 @@ var Target = function (options) { // Static assets to include in the bundle. List of File. // For browser targets, these are served over HTTP. - self.static = []; + self.asset = []; }; _.extend(Target.prototype, { @@ -610,7 +608,7 @@ _.extend(Target.prototype, { // Emit the resources _.each(slice.getResources(self.arch), function (resource) { - if (_.contains(["js", "css", "static"], resource.type)) { + if (_.contains(["js", "css", "asset"], resource.type)) { if (resource.type === "css" && ! isBrowser) // XXX might be nice to throw an error here, but then we'd // have to make it so that packages.js ignores css files @@ -626,8 +624,8 @@ _.extend(Target.prototype, { }); var relPath; - if (resource.type === "static" && isNative) - relPath = path.join("static", resource.servePath); + if (resource.type === "asset" && isNative) + relPath = path.join("assets", resource.servePath); else { relPath = stripLeadingSlash(resource.servePath); } @@ -637,7 +635,7 @@ _.extend(Target.prototype, { f.setUrlFromRelPath(resource.servePath); } else if (isNative) { if (resource.type === "js") - f.setStaticDirectory(relPath, resource.staticDirectory); + f.setAssetsDirectory(relPath, resource.assetsDirectory); } if (isNative && resource.type === "js" && ! isApp && @@ -771,14 +769,13 @@ _.extend(Target.prototype, { if (setUrl) f.setUrlFromRelPath(assetPath); // XXX why is this separate from _emitResources ? - // XXX fix up server static resources var relPath = assetDir.useSubDirectory - ? path.join('static', 'app', assetPath) + ? path.join('assets', 'app', assetPath) : assetPath; if (setTargetPath) f.setTargetPathFromRelPath(relPath); self.dependencyInfo.files[absPath] = f.hash(); - self.static.push(f); + self.asset.push(f); }); }; @@ -851,7 +848,7 @@ _.extend(ClientTarget.prototype, { // Helper to iterate over all resources that we serve over HTTP. var eachResource = function (f) { - _.each(["js", "css", "static"], function (type) { + _.each(["js", "css", "asset"], function (type) { _.each(self[type], function (file) { f(file, type); }); @@ -988,7 +985,7 @@ _.extend(JsImage.prototype, { // XXX This is mostly duplicated from server/boot.js, as is Npm.require // below. Some way to avoid this? - var getAsset = function (staticDirectory, assetPath, encoding, callback) { + var getAsset = function (assetsDirectory, assetPath, encoding, callback) { var fut; if (! callback) { if (! Fiber.current) @@ -1003,7 +1000,7 @@ _.extend(JsImage.prototype, { result = new Uint8Array(result); callback(err, result); }; - var filePath = path.join(staticDirectory, assetPath); + var filePath = path.join(assetsDirectory, assetPath); if (filePath.indexOf("..") !== -1) throw new Error(".. is not allowed in asset paths."); fs.readFile(filePath, encoding, _callback); @@ -1047,11 +1044,11 @@ _.extend(JsImage.prototype, { }, Assets: { getText: function (assetPath, callback) { - return getAsset(item.staticDirectory.sourcePath, + return getAsset(item.assetsDirectory.sourcePath, assetPath, "utf8", callback); }, getBinary: function (assetPath, callback) { - return getAsset(item.staticDirectory.sourcePath, + return getAsset(item.assetsDirectory.sourcePath, assetPath, undefined, callback); } } @@ -1112,8 +1109,8 @@ _.extend(JsImage.prototype, { path: loadPath, node_modules: item.nodeModulesDirectory ? item.nodeModulesDirectory.preferredBundlePath : undefined, - staticDirectory: item.staticDirectory ? - item.staticDirectory.bundlePath : undefined + assetsDirectory: item.assetsDirectory ? + item.assetsDirectory.bundlePath : undefined }; if (item.sourceMap) { @@ -1187,8 +1184,8 @@ JsImage.readFromDisk = function (controlFilePath) { targetPath: item.path, source: fs.readFileSync(path.join(dir, item.path)), nodeModulesDirectory: nmd, - staticDirectory: new StaticDirectory({ - sourcePath: item.staticDirectory + assetsDirectory: new AssetsDirectory({ + sourcePath: item.assetsDirectory }) }; if (item.sourceMap) { @@ -1226,7 +1223,7 @@ _.extend(JsImageTarget.prototype, { targetPath: file.targetPath, source: file.contents().toString('utf8'), nodeModulesDirectory: file.nodeModulesDirectory, - staticDirectory: file.staticDirectory, + assetsDirectory: file.assetsDirectory, sourceMap: file.sourceMap, sourceMapRoot: file.sourceMapRoot }); @@ -1352,7 +1349,7 @@ _.extend(ServerTarget.prototype, { } // Static assets - _.each(self.static, function (file) { + _.each(self.asset, function (file) { writeFile(file, builder); }); diff --git a/tools/packages.js b/tools/packages.js index 5b918aa69f..c00cf56cda 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -20,7 +20,7 @@ var sourcemap = require('source-map'); // unipackage/slice changes, but this version (which is build-tool-specific) can // change when the the contents (not structure) of the built output changes. So // eg, if we improve the linker's static analysis, this should be bumped. -exports.BUILD_VERSION = 'meteor/2'; +exports.BUILT_BY = 'meteor/3'; // Find all files under `rootPath` that have an extension in // `extensions` (an array of extensions without leading dot), and @@ -187,17 +187,17 @@ var Slice = function (pkg, options) { // other than JavaScript that still needs to be fed through the // final link stage. A list of objects with these keys: // - // type: "js", "css", "head", "body", "static" + // type: "js", "css", "head", "body", "asset" // // data: The contents of this resource, as a Buffer. For example, // for "head", the data to insert in ; for "js", the // JavaScript source code (which may be subject to further - // processing such as minification); for "static", the contents of a + // processing such as minification); for "asset", the contents of a // static resource such as an image. // // servePath: The (absolute) path at which the resource would prefer // to be served. Interpretation varies by type. For example, always - // honored for "static", ignored for "head" and "body", sometimes + // honored for "asset", ignored for "head" and "body", sometimes // honored for CSS but ignored if we are concatenating. // // sourceMap: Allowed only for "js". If present, a string. @@ -212,7 +212,7 @@ var Slice = function (pkg, options) { // Absolute path to the location on disk where Assets API calls will search in // this slice. - self.staticDirectory = options.staticDirectory; + self.assetsDirectory = options.assetsDirectory; }; _.extend(Slice.prototype, { @@ -267,7 +267,7 @@ _.extend(Slice.prototype, { // XXX This is pretty confusing, especially if you've // accidentally forgotten a plugin -- revisit? resources.push({ - type: "static", + type: "asset", data: contents, servePath: path.join(self.pkg.serveRoot, relPath) }); @@ -442,7 +442,7 @@ _.extend(Slice.prototype, { if (! (options.data instanceof Buffer)) throw new Error("'data' option to addAsset must be a Buffer"); resources.push({ - type: "static", + type: "asset", data: options.data, servePath: path.join(self.pkg.serveRoot, options.path) }); @@ -578,7 +578,7 @@ _.extend(Slice.prototype, { type: "js", data: new Buffer(file.source, 'utf8'), // XXX encoding servePath: file.servePath, - staticDirectory: self.staticDirectory, + assetsDirectory: self.assetsDirectory, sourceMap: file.sourceMap }; }); @@ -1090,7 +1090,7 @@ _.extend(Package.prototype, { }), getSourcesFunc: function () { return sources; }, nodeModulesPath: nodeModulesPath, - staticDirectory: options.sourceRoot + assetsDirectory: options.sourceRoot }); self.slices.push(slice); @@ -1691,7 +1691,7 @@ _.extend(Package.prototype, { forceExport: forceExport[role][where], dependencyInfo: dependencyInfo, nodeModulesPath: arch === nativeArch && nodeModulesPath || undefined, - staticDirectory: self.sourceRoot, + assetsDirectory: self.sourceRoot, // test slices don't get used by other packages, so they have nothing // to export. (And notably, they should NOT stomp on the Package.foo // object defined by their corresponding use slice.) @@ -1889,7 +1889,7 @@ _.extend(Package.prototype, { if (options.onlyIfUpToDate) { // Do we think we'll generate different contents than the tool that built // this package? - if (buildInfoJson.builtBy !== exports.BUILD_VERSION) + if (buildInfoJson.builtBy !== exports.BUILT_BY) return false; if (options.buildOfPath && @@ -1959,9 +1959,9 @@ _.extend(Package.prototype, { nodeModulesPath = path.join(sliceBasePath, sliceJson.node_modules); } - var staticDirectory = null; - if (sliceJson.staticDirectory) - staticDirectory = path.join(sliceBasePath, sliceJson.staticDirectory); + var assetsDirectory = null; + if (sliceJson.assetsDirectory) + assetsDirectory = path.join(sliceBasePath, sliceJson.assetsDirectory); var slice = new Slice(self, { name: sliceMeta.name, @@ -1980,7 +1980,7 @@ _.extend(Package.prototype, { spec: u['package'] + (u.slice ? "." + u.slice : "") }; }), - staticDirectory: staticDirectory + assetsDirectory: assetsDirectory }); slice.isBuilt = true; @@ -2014,7 +2014,7 @@ _.extend(Package.prototype, { path.join(sliceBasePath, resource.sourceMap), 'utf8'); } slice.prelinkFiles.push(prelinkFile); - } else if (_.contains(["head", "body", "css", "js", "static"], + } else if (_.contains(["head", "body", "css", "js", "asset"], resource.type)) { slice.resources.push({ type: resource.type, @@ -2105,7 +2105,7 @@ _.extend(Package.prototype, { }; var buildInfoJson = { - builtBy: exports.BUILD_VERSION, + builtBy: exports.BUILT_BY, dependencies: { files: {}, directories: {} }, source: options.buildOfPath || undefined }; @@ -2190,7 +2190,7 @@ _.extend(Package.prototype, { })), node_modules: slice.nodeModulesPath ? 'npm/node_modules' : undefined, resources: [], - staticDirectory: path.join(sliceDir, self.serveRoot) + assetsDirectory: path.join(sliceDir, self.serveRoot) }; // Output 'head', 'body' resources nicely diff --git a/tools/server/boot.js b/tools/server/boot.js index 57fa52fd90..3bb9d54dec 100644 --- a/tools/server/boot.js +++ b/tools/server/boot.js @@ -108,7 +108,7 @@ Fiber(function () { } } }; - var staticDirectory = path.resolve(serverDir, fileInfo.staticDirectory); + var assetsDirectory = path.resolve(serverDir, fileInfo.assetsDirectory); var getAsset = function (assetPath, encoding, callback) { var fut; if (! callback) { @@ -123,7 +123,7 @@ Fiber(function () { }, function (e) { Meteor._debug("Exception in callback of getAsset", e.stack); }); - var filePath = path.join(staticDirectory, assetPath); + var filePath = path.join(assetsDirectory, assetPath); if (filePath.indexOf("..") !== -1) throw new Error(".. is not allowed in asset paths."); fs.readFile(filePath, encoding, _callback); diff --git a/tools/tests/test_bundler_assets.js b/tools/tests/test_bundler_assets.js index c99865df4a..4b035f8eae 100644 --- a/tools/tests/test_bundler_assets.js +++ b/tools/tests/test_bundler_assets.js @@ -57,28 +57,28 @@ assert.doesNotThrow(function () { "program.json") ) ); - var staticDir; + var assetsDir; var packageTxtPath; var unregisteredExtensionPath; _.each(serverManifest.load, function (item) { if (item.path === "packages/test-package.js") { packageTxtPath = path.join(tmpOutputDir, "programs", "server", - item.staticDirectory, "test-package.txt"); + item.assetsDirectory, "test-package.txt"); unregisteredExtensionPath = path.join(tmpOutputDir, "programs", "server", - item.staticDirectory, + item.assetsDirectory, "test.notregistered"); } if (item.path === "app/test.js") { - staticDir = path.join(tmpOutputDir, + assetsDir = path.join(tmpOutputDir, "programs", "server", - item.staticDirectory); + item.assetsDirectory); } }); // check that the files are where the manifest says they are - var testTxtPath = path.join(staticDir, "test.txt"); - var nestedTxtPath = path.join(staticDir, "nested", "test.txt"); + var testTxtPath = path.join(assetsDir, "test.txt"); + var nestedTxtPath = path.join(assetsDir, "nested", "test.txt"); assert.strictEqual(result.errors, false, result.errors && result.errors[0]); assert(fs.existsSync(testTxtPath)); assert(fs.existsSync(nestedTxtPath)); From 9538bec6852814c61bee2f8a62f263465c0f558e Mon Sep 17 00:00:00 2001 From: David Glasser Date: Tue, 16 Jul 2013 20:32:22 -0700 Subject: [PATCH 005/175] Replace "asset directory" concept with manifest of assets. We were partway here already: the client served assets from the manifest instead of from a static directory (since 5b8e1c1), and we already generated the list of assets in the slice JSON file. But on the server, we ignored that list and re-walked the asset directory at bundle time. Now, the idea of asset directory is solely a part of initFromAppDir. This also fixes a bug where assets that weren't associated with on-disk files wouldn't work properly if Asset.get* is called in a package loaded with unipackage.load. Not really sure how dev-bundle-fetcher even worked... Also fixes a bug in builder where generated filenames didn't actually avoid duplicate files. This does not bump BUILT_BY because the previous commit did, and this commit will not be pushed without the previous commit. --- tools/builder.js | 4 +- tools/bundler.js | 325 ++++++++++++----------------- tools/packages.js | 92 +++++--- tools/server/boot.js | 12 +- tools/tests/test_bundler_assets.js | 23 +- 5 files changed, 210 insertions(+), 246 deletions(-) diff --git a/tools/builder.js b/tools/builder.js index d8febca434..7a97ce2427 100644 --- a/tools/builder.js +++ b/tools/builder.js @@ -115,8 +115,8 @@ _.extend(Builder.prototype, { while (true) { var candidate = path.join(partsOut.join(path.sep), part + suffix + ext); if (candidate.length && - ! (candidate in self.usedAsFile) || - (shouldBeFile === self.usedAsFile[candidate])) + (! (candidate in self.usedAsFile) || + (!shouldBeFile && !self.usedAsFile[candidate]))) // No conflict -- either not used, or it's two paths that // share a common ancestor directory (as opposed to one path // thinking that a/b should be a file, and another thinking diff --git a/tools/bundler.js b/tools/bundler.js index 6e39787009..d9ecdb0107 100644 --- a/tools/bundler.js +++ b/tools/bundler.js @@ -103,8 +103,9 @@ // - node_modules: if Npm.require is called from this file, this is // the path (relative to program.json) of the directory that should // be search for npm modules -// - assetsDirectory: directory to search for static assets when -// Assets.getText and Assets.getBinary are called from this file. +// - assets: map from path (the argument to Assets.getText and +// Assets.getBinary) to path on disk (relative to program.json) +// of the asset // - sourceMap: if present, path of a file that contains a source // map for this file, relative to program.json // @@ -217,18 +218,6 @@ var NodeModulesDirectory = function (options) { self.preferredBundlePath = options.preferredBundlePath; }; -/////////////////////////////////////////////////////////////////////////////// -// AssetsDirectory -/////////////////////////////////////////////////////////////////////////////// - -// Like a NodeModulesDirectory but for static assets that are accessible via the -// Assets API. -var AssetsDirectory = function (options) { - var self = this; - self.sourcePath = options.sourcePath; - self.bundlePath = options.bundlePath; -}; - /////////////////////////////////////////////////////////////////////////////// // File /////////////////////////////////////////////////////////////////////////////// @@ -276,7 +265,9 @@ var File = function (options) { // in the "server" architecture. self.nodeModulesDirectory = null; - self.assetsDirectory = null; + // For server JS only. Assets associated with this slice; map from the path + // that is the argument to Assets.getBinary, to a Buffer that is its contents. + self.assets = null; self._contents = options.data || null; // contents, if known, as a Buffer self._hash = null; // hash, if known, as a hex string @@ -366,26 +357,6 @@ _.extend(File.prototype, { self.targetPath = path.join('app', relPath); }, - setAssetsDirectory: function (relPath, assetsSourceDirectory) { - var self = this; - // For package code, static assets go inside a directory inside - // assets/packages specific to this package. Application assets (e.g. those - // inside private/) go in assets/app/. - // XXX same hack as above - var bundlePath; - if (relPath.match(/^packages\//)) { - var dir = path.dirname(relPath); - var base = path.basename(relPath, ".js"); - bundlePath = path.join('assets', dir, base); - } else { - bundlePath = path.join('assets', 'app'); - } - self.assetsDirectory = new AssetsDirectory({ - sourcePath: assetsSourceDirectory, - bundlePath: bundlePath - }); - }, - // Set a source map for this File. sourceMap is given as a string. setSourceMap: function (sourceMap, root) { var self = this; @@ -394,6 +365,13 @@ _.extend(File.prototype, { throw new Error("sourceMap must be given as a string"); self.sourceMap = sourceMap; self.sourceMapRoot = root; + }, + + // note: this assets object may be shared among multiple files! + setAssets: function (assets) { + var self = this; + if (!_.isEmpty(assets)) + self.assets = assets; } }); @@ -450,9 +428,6 @@ _.extend(Target.prototype, { // per _determineLoadOrder // - test: packages to test (Package or 'foo'), per _determineLoadOrder // - minify: true to minify - // - assetDirs: array of asset directories to add - // object with keys 'rootDir', 'exclude', 'assetPathPrefix', - // 'setUrl', 'setTargetPath', all per addAssetDir. // - addCacheBusters: if true, make all files cacheable by adding // unique query strings to their URLs. unlikely to be of much use // on server targets. @@ -475,12 +450,6 @@ _.extend(Target.prototype, { self.minifyCss(); } - // Process asset directories (eg, '/public') - // XXX this should probably be part of the appDir reader - _.each(options.assetDirs || [], function (ad) { - self.addAssetDir(ad); - }); - if (options.addCacheBusters) { // Make client-side CSS and JS assets cacheable forever, by // adding a query string with a cache-busting hash. @@ -607,8 +576,37 @@ _.extend(Target.prototype, { var isApp = ! slice.pkg.name; // Emit the resources - _.each(slice.getResources(self.arch), function (resource) { - if (_.contains(["js", "css", "asset"], resource.type)) { + var resources = slice.getResources(self.arch); + + // First, find all the assets, so that we can associate them with each js + // resource (for native slices). + var sliceAssets = {}; + _.each(resources, function (resource) { + if (resource.type !== "asset") + return; + + var f = new File({data: resource.data, cacheable: false}); + + var relPath = isNative + ? path.join("assets", resource.servePath) + : stripLeadingSlash(resource.servePath); + f.setTargetPathFromRelPath(relPath); + + if (isBrowser) + f.setUrlFromRelPath(resource.servePath); + else { + sliceAssets[resource.path] = resource.data; + } + + self.asset.push(f); + }); + + // Now look for the other kinds of resources. + _.each(resources, function (resource) { + if (resource.type === "asset") + return; // already handled + + if (_.contains(["js", "css"], resource.type)) { if (resource.type === "css" && ! isBrowser) // XXX might be nice to throw an error here, but then we'd // have to make it so that packages.js ignores css files @@ -618,41 +616,36 @@ _.extend(Target.prototype, { // meteor.js? return; - var f = new File({ - data: resource.data, - cacheable: false - }); + var f = new File({data: resource.data, cacheable: false}); - var relPath; - if (resource.type === "asset" && isNative) - relPath = path.join("assets", resource.servePath); - else { - relPath = stripLeadingSlash(resource.servePath); - } + var relPath = stripLeadingSlash(resource.servePath); f.setTargetPathFromRelPath(relPath); if (isBrowser) { f.setUrlFromRelPath(resource.servePath); - } else if (isNative) { - if (resource.type === "js") - f.setAssetsDirectory(relPath, resource.assetsDirectory); } - if (isNative && resource.type === "js" && ! isApp && - slice.nodeModulesPath) { - var nmd = self.nodeModulesDirectories[slice.nodeModulesPath]; - if (! nmd) { - nmd = new NodeModulesDirectory({ - sourcePath: slice.nodeModulesPath, - // It's important that this path end with - // node_modules. Otherwise, if two modules in this package - // depend on each other, they won't be able to find each other! - preferredBundlePath: path.join('npm', slice.pkg.name, - slice.sliceName, 'node_modules') - }); - self.nodeModulesDirectories[slice.nodeModulesPath] = nmd; + if (resource.type === "js" && isNative) { + // Hack, but otherwise we'll end up putting app assets on this file. + if (resource.servePath !== "/packages/global-imports.js") + f.setAssets(sliceAssets); + + if (! isApp && slice.nodeModulesPath) { + var nmd = self.nodeModulesDirectories[slice.nodeModulesPath]; + if (! nmd) { + nmd = new NodeModulesDirectory({ + sourcePath: slice.nodeModulesPath, + // It's important that this path end with + // node_modules. Otherwise, if two modules in this package + // depend on each other, they won't be able to find each + // other! + preferredBundlePath: path.join( + 'npm', slice.pkg.name, slice.sliceName, 'node_modules') + }); + self.nodeModulesDirectories[slice.nodeModulesPath] = nmd; + } + f.nodeModulesDirectory = nmd; } - f.nodeModulesDirectory = nmd; } if (resource.type === "js" && resource.sourceMap) { @@ -726,60 +719,6 @@ _.extend(Target.prototype, { mostCompatibleArch: function () { var self = this; return archinfo.leastSpecificDescription(_.pluck(self.slices, 'arch')); - }, - - // assetDir has properties rootDir, exclude, assetPathPrefix, setUrl, - // and setTargetPath. (All but rootDir are optional.) - // Add all of the files in a directory `rootDir` (and its - // subdirectories) as static assets. `rootDir` should be an absolute - // path. If provided, exclude is an - // array of filename regexps to exclude. If provided, assetPathPrefix is a - // prefix to use when computing the path for each file. - addAssetDir: function (assetDir) { - var self = this; - var rootDir = assetDir.rootDir; - var exclude = assetDir.exclude; - var assetPathPrefix = assetDir.assetPathPrefix; - var setUrl = assetDir.setUrl; - var setTargetPath = assetDir.setTargetPath; - exclude = exclude || []; - - self.dependencyInfo.directories[rootDir] = { - include: [/.?/], - exclude: exclude - }; - - var walk = function (dir, assetPathPrefix) { - _.each(fs.readdirSync(dir), function (item) { - // Skip excluded files - var matchesAnExclude = _.any(exclude, function (pattern) { - return item.match(pattern); - }); - if (matchesAnExclude) - return; - - var absPath = path.resolve(dir, item); - var assetPath = path.join(assetPathPrefix, item); - if (fs.statSync(absPath).isDirectory()) { - walk(absPath, assetPath); - return; - } - - var f = new File({ sourcePath: absPath }); - if (setUrl) - f.setUrlFromRelPath(assetPath); - // XXX why is this separate from _emitResources ? - var relPath = assetDir.useSubDirectory - ? path.join('assets', 'app', assetPath) - : assetPath; - if (setTargetPath) - f.setTargetPathFromRelPath(relPath); - self.dependencyInfo.files[absPath] = f.hash(); - self.asset.push(f); - }); - }; - - walk(rootDir, assetPathPrefix || ''); } }); @@ -985,7 +924,7 @@ _.extend(JsImage.prototype, { // XXX This is mostly duplicated from server/boot.js, as is Npm.require // below. Some way to avoid this? - var getAsset = function (assetsDirectory, assetPath, encoding, callback) { + var getAsset = function (assets, assetPath, encoding, callback) { var fut; if (! callback) { if (! Fiber.current) @@ -1000,10 +939,14 @@ _.extend(JsImage.prototype, { result = new Uint8Array(result); callback(err, result); }; - var filePath = path.join(assetsDirectory, assetPath); - if (filePath.indexOf("..") !== -1) - throw new Error(".. is not allowed in asset paths."); - fs.readFile(filePath, encoding, _callback); + + if (!assets || !_.has(assets, assetPath)) { + _.callback(new Error("Unknown asset: " + assetPath)); + } else { + var buffer = assets[assetPath]; + var result = encoding ? buffer.toString(encoding) : buffer; + _callback(null, result); + } if (fut) return fut.wait(); }; @@ -1044,12 +987,10 @@ _.extend(JsImage.prototype, { }, Assets: { getText: function (assetPath, callback) { - return getAsset(item.assetsDirectory.sourcePath, - assetPath, "utf8", callback); + return getAsset(item.assets, assetPath, "utf8", callback); }, getBinary: function (assetPath, callback) { - return getAsset(item.assetsDirectory.sourcePath, - assetPath, undefined, callback); + return getAsset(item.assets, assetPath, undefined, callback); } } }, bindings || {}); @@ -1096,6 +1037,10 @@ _.extend(JsImage.prototype, { })); }); + // If multiple load files share the same asset, only write one copy of + // each. (eg, for app assets.) + var assetFilesBySha = {}; + // JavaScript sources var load = []; _.each(self.jsToLoad, function (item) { @@ -1108,9 +1053,7 @@ _.extend(JsImage.prototype, { var loadItem = { path: loadPath, node_modules: item.nodeModulesDirectory ? - item.nodeModulesDirectory.preferredBundlePath : undefined, - assetsDirectory: item.assetsDirectory ? - item.assetsDirectory.bundlePath : undefined + item.nodeModulesDirectory.preferredBundlePath : undefined }; if (item.sourceMap) { @@ -1123,6 +1066,33 @@ _.extend(JsImage.prototype, { loadItem.sourceMapRoot = item.sourceMapRoot; } + if (!_.isEmpty(item.assets)) { + // For package code, static assets go inside a directory inside + // assets/packages specific to this package. Application assets (e.g. those + // inside private/) go in assets/app/. + // XXX same hack as setTargetPathFromRelPath + var assetBundlePath; + if (item.targetPath.match(/^packages\//)) { + var dir = path.dirname(item.targetPath); + var base = path.basename(item.targetPath, ".js"); + assetBundlePath = path.join('assets', dir, base); + } else { + assetBundlePath = path.join('assets', 'app'); + } + + loadItem.assets = {}; + _.each(item.assets, function (data, relPath) { + var sha = Builder.sha1(data); + if (_.has(assetFilesBySha, sha)) { + loadItem.assets[relPath] = assetFilesBySha[sha]; + } else { + loadItem.assets[relPath] = assetFilesBySha[sha] = + builder.writeToGeneratedFilename( + path.join(assetBundlePath, relPath), { data: data }); + } + }); + } + load.push(loadItem); }); @@ -1183,11 +1153,9 @@ JsImage.readFromDisk = function (controlFilePath) { var loadItem = { targetPath: item.path, source: fs.readFileSync(path.join(dir, item.path)), - nodeModulesDirectory: nmd, - assetsDirectory: new AssetsDirectory({ - sourcePath: item.assetsDirectory - }) + nodeModulesDirectory: nmd }; + if (item.sourceMap) { // XXX this is the same code as initFromUnipackage rejectBadPath(item.sourceMap); @@ -1195,6 +1163,14 @@ JsImage.readFromDisk = function (controlFilePath) { path.join(dir, item.sourceMap), 'utf8'); loadItem.sourceMapRoot = item.sourceMapRoot; } + + if (!_.isEmpty(item.assets)) { + loadItem.assets = {}; + _.each(item.assets, function (filename, relPath) { + loadItem.assets[relPath] = fs.readFileSync(path.join(dir, filename)); + }); + } + ret.jsToLoad.push(loadItem); }); @@ -1223,7 +1199,7 @@ _.extend(JsImageTarget.prototype, { targetPath: file.targetPath, source: file.contents().toString('utf8'), nodeModulesDirectory: file.nodeModulesDirectory, - assetsDirectory: file.assetsDirectory, + assets: file.assets, sourceMap: file.sourceMap, sourceMapRoot: file.sourceMapRoot }); @@ -1297,7 +1273,8 @@ _.extend(ServerTarget.prototype, { if (! options.omitDependencyKit) builder.reserve("node_modules", { directory: true }); - // Linked JavaScript image + // Linked JavaScript image (including static assets, assuming that there are + // any JS files at all) var imageControlFile = self.toJsImage().write(builder); // Server bootstrap @@ -1348,11 +1325,6 @@ _.extend(ServerTarget.prototype, { }); } - // Static assets - _.each(self.asset, function (file) { - writeFile(file, builder); - }); - return scriptName; } }); @@ -1580,49 +1552,23 @@ exports.bundle = function (appDir, outputPath, options) { var targets = {}; var controlProgram = null; - var getValidAssetDirs = function (dirNames, assetDirDefaults) { - var assetDirs = []; - assetDirDefaults = assetDirDefaults || {}; - if (appDir) { - if (files.is_app_dir(appDir)) { /* XXX what is this checking? */ - _.each(dirNames, function (dirName) { - var assetDir = path.join(appDir, dirName); - var assetDirObj = _.extend({ rootDir: assetDir }, assetDirDefaults); - if (fs.existsSync(assetDir)) - assetDirs.push(assetDirObj); - }); - } - } - return assetDirs; - }; - - var makeClientTarget = function (app, appDir, assetDirs) { + var makeClientTarget = function (app) { var client = new ClientTarget({ library: library, arch: "browser" }); - // Scan /public if the client has it - // XXX this should probably be part of the appDir reader - assetDirs = assetDirs || []; - var clientAssetDirs = getValidAssetDirs(assetDirs, { - exclude: ignoreFiles, - setUrl: true, - setTargetPath: true - }); - client.make({ packages: [app], test: options.testPackages || [], minify: options.minify, - assetDirs: clientAssetDirs, addCacheBusters: true }); return client; }; - var makeBlankClientTarget = function (app) { + var makeBlankClientTarget = function () { var client = new ClientTarget({ library: library, arch: "browser" @@ -1636,16 +1582,7 @@ exports.bundle = function (appDir, outputPath, options) { return client; }; - var makeServerTarget = function (app, clientTarget, assetDirs) { - assetDirs = assetDirs || []; - var serverAssetDirs = getValidAssetDirs(assetDirs, { - exclude: ignoreFiles, - // We need to set the target path when the asset dir is added, - // because the target path comes from the asset's path. - setTargetPath: true, - // XXX this is a hack, re-assess how the subdirs are named - useSubDirectory: true - }); + var makeServerTarget = function (app, clientTarget) { var targetOptions = { library: library, arch: archinfo.host(), @@ -1659,8 +1596,7 @@ exports.bundle = function (appDir, outputPath, options) { server.make({ packages: [app], test: options.testPackages || [], - minify: false, - assetDirs: serverAssetDirs + minify: false }); return server; @@ -1675,11 +1611,11 @@ exports.bundle = function (appDir, outputPath, options) { var app = library.getForApp(appDir, ignoreFiles); // Client - var client = makeClientTarget(app, appDir, ['public']); + var client = makeClientTarget(app); targets.client = client; // Server - var server = makeServerTarget(app, client, ['private']); + var server = makeServerTarget(app, client); targets.server = server; } @@ -1792,10 +1728,7 @@ exports.bundle = function (appDir, outputPath, options) { target = makeServerTarget(p.name, clientTarget); break; case "client": - // We pass null for appDir because we are a - // package.js-driven directory and don't want to scan a - // /public directory for assets. - target = makeClientTarget(p.name, null); + target = makeClientTarget(p.name); break; default: buildmessage.error( diff --git a/tools/packages.js b/tools/packages.js index c00cf56cda..23c75b5a6e 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -209,10 +209,6 @@ var Slice = function (pkg, options) { // resolve Npm.require() calls in this slice. null if this slice // does not have a node_modules. self.nodeModulesPath = options.nodeModulesPath; - - // Absolute path to the location on disk where Assets API calls will search in - // this slice. - self.assetsDirectory = options.assetsDirectory; }; _.extend(Slice.prototype, { @@ -251,12 +247,25 @@ _.extend(Slice.prototype, { self[field] = scrubbed; }); + var addAsset = function (contents, relPath) { + // XXX hack + if (!self.pkg.name) + relPath = relPath.replace(/^(private|public)\//, ''); + + resources.push({ + type: "asset", + data: contents, + path: relPath, + servePath: path.join(self.pkg.serveRoot, relPath) + }); + }; + _.each(self.getSourcesFunc(), function (source) { var relPath = source.relPath; var fileOptions = _.clone(source.fileOptions) || {}; var absPath = path.resolve(self.pkg.sourceRoot, relPath); var ext = path.extname(relPath).substr(1); - var handler = self._getSourceHandler(ext); + var handler = !source.isAsset && self._getSourceHandler(ext); var contents = fs.readFileSync(absPath); self.dependencyInfo.files[absPath] = Builder.sha1(contents); @@ -266,11 +275,7 @@ _.extend(Slice.prototype, { // // XXX This is pretty confusing, especially if you've // accidentally forgotten a plugin -- revisit? - resources.push({ - type: "asset", - data: contents, - servePath: path.join(self.pkg.serveRoot, relPath) - }); + addAsset(contents, relPath); return; } @@ -441,11 +446,7 @@ _.extend(Slice.prototype, { addAsset: function (options) { if (! (options.data instanceof Buffer)) throw new Error("'data' option to addAsset must be a Buffer"); - resources.push({ - type: "asset", - data: options.data, - servePath: path.join(self.pkg.serveRoot, options.path) - }); + addAsset(options.data, options.path); }, error: function (options) { buildmessage.error(options.message || ("error building " + relPath), { @@ -578,7 +579,6 @@ _.extend(Slice.prototype, { type: "js", data: new Buffer(file.source, 'utf8'), // XXX encoding servePath: file.servePath, - assetsDirectory: self.assetsDirectory, sourceMap: file.sourceMap }; }); @@ -1089,8 +1089,7 @@ _.extend(Package.prototype, { return { spec: spec }; }), getSourcesFunc: function () { return sources; }, - nodeModulesPath: nodeModulesPath, - assetsDirectory: options.sourceRoot + nodeModulesPath: nodeModulesPath }); self.slices.push(slice); @@ -1691,7 +1690,6 @@ _.extend(Package.prototype, { forceExport: forceExport[role][where], dependencyInfo: dependencyInfo, nodeModulesPath: arch === nativeArch && nodeModulesPath || undefined, - assetsDirectory: self.sourceRoot, // test slices don't get used by other packages, so they have nothing // to export. (And notably, they should NOT stomp on the Package.foo // object defined by their corresponding use slice.) @@ -1814,7 +1812,7 @@ _.extend(Package.prototype, { path.resolve(appDir, '.meteor', 'local')] = { exclude: [/.?/] }; // Convert into relPath/fileOptions objects. - return _.map(withoutOtherPrograms, function (relPath) { + var sources = _.map(withoutOtherPrograms, function (relPath) { var sourceObj = {relPath: relPath}; // Special case: on the client, JavaScript files in a @@ -1827,6 +1825,44 @@ _.extend(Package.prototype, { } return sourceObj; }); + + var assetDir = sliceName === "client" ? "public" : "private"; + var absAssetDir = path.resolve(appDir, assetDir); + slice.dependencyInfo.directories[absAssetDir] + = { include: [/.?/], exclude: ignoreFiles}; + var walkAssetDir = function (subdir) { + var dir = path.join(appDir, subdir); + try { + var items = fs.readdirSync(dir); + } catch (e) { + // OK if the directory (esp the top level asset dir) doesn't exist. + if (e.code === "ENOENT") + return; + throw e; + } + _.each(items, function (item) { + // Skip excluded files + var matchesAnExclude = _.any(ignoreFiles, function (pattern) { + return item.match(pattern); + }); + if (matchesAnExclude) + return; + + var assetAbsPath = path.join(dir, item); + var assetRelPath = path.join(subdir, item); + if (fs.statSync(assetAbsPath).isDirectory()) { + walkAssetDir(assetRelPath); + return; + } + + sources.push({ + relPath: assetRelPath, + isAsset: true + }); + }); + }; + walkAssetDir(assetDir); + return sources; }; }); @@ -1959,10 +1995,6 @@ _.extend(Package.prototype, { nodeModulesPath = path.join(sliceBasePath, sliceJson.node_modules); } - var assetsDirectory = null; - if (sliceJson.assetsDirectory) - assetsDirectory = path.join(sliceBasePath, sliceJson.assetsDirectory); - var slice = new Slice(self, { name: sliceMeta.name, arch: sliceMeta.arch, @@ -1979,8 +2011,7 @@ _.extend(Package.prototype, { return { spec: u['package'] + (u.slice ? "." + u.slice : "") }; - }), - assetsDirectory: assetsDirectory + }) }); slice.isBuilt = true; @@ -2019,7 +2050,8 @@ _.extend(Package.prototype, { slice.resources.push({ type: resource.type, data: data, - servePath: resource.servePath || undefined + servePath: resource.servePath || undefined, + path: resource.path || undefined }); } else throw new Error("bad resource type in unipackage: " + @@ -2189,8 +2221,7 @@ _.extend(Package.prototype, { }; })), node_modules: slice.nodeModulesPath ? 'npm/node_modules' : undefined, - resources: [], - assetsDirectory: path.join(sliceDir, self.serveRoot) + resources: [] }; // Output 'head', 'body' resources nicely @@ -2234,7 +2265,8 @@ _.extend(Package.prototype, { { data: resource.data }), length: resource.data.length, offset: 0, - servePath: resource.servePath || undefined + servePath: resource.servePath || undefined, + path: resource.path || undefined }); }); diff --git a/tools/server/boot.js b/tools/server/boot.js index 3bb9d54dec..3d21f7cb3c 100644 --- a/tools/server/boot.js +++ b/tools/server/boot.js @@ -108,7 +108,6 @@ Fiber(function () { } } }; - var assetsDirectory = path.resolve(serverDir, fileInfo.assetsDirectory); var getAsset = function (assetPath, encoding, callback) { var fut; if (! callback) { @@ -123,10 +122,13 @@ Fiber(function () { }, function (e) { Meteor._debug("Exception in callback of getAsset", e.stack); }); - var filePath = path.join(assetsDirectory, assetPath); - if (filePath.indexOf("..") !== -1) - throw new Error(".. is not allowed in asset paths."); - fs.readFile(filePath, encoding, _callback); + + if (!fileInfo.assets || !_.has(fileInfo.assets, assetPath)) { + _callback(new Error("Unknown asset: " + assetPath)); + } else { + var filePath = path.join(serverDir, fileInfo.assets[assetPath]); + fs.readFile(filePath, encoding, _callback); + } if (fut) return fut.wait(); }; diff --git a/tools/tests/test_bundler_assets.js b/tools/tests/test_bundler_assets.js index 4b035f8eae..e3771bf089 100644 --- a/tools/tests/test_bundler_assets.js +++ b/tools/tests/test_bundler_assets.js @@ -57,28 +57,25 @@ assert.doesNotThrow(function () { "program.json") ) ); - var assetsDir; + var testTxtPath; + var nestedTxtPath; var packageTxtPath; var unregisteredExtensionPath; _.each(serverManifest.load, function (item) { if (item.path === "packages/test-package.js") { - packageTxtPath = path.join(tmpOutputDir, - "programs", "server", - item.assetsDirectory, "test-package.txt"); - unregisteredExtensionPath = path.join(tmpOutputDir, - "programs", "server", - item.assetsDirectory, - "test.notregistered"); + packageTxtPath = path.join( + tmpOutputDir, "programs", "server", item.assets['test-package.txt']); + unregisteredExtensionPath = path.join( + tmpOutputDir, "programs", "server", item.assets["test.notregistered"]); } if (item.path === "app/test.js") { - assetsDir = path.join(tmpOutputDir, - "programs", "server", - item.assetsDirectory); + testTxtPath = path.join( + tmpOutputDir, "programs", "server", item.assets['test.txt']); + nestedTxtPath = path.join( + tmpOutputDir, "programs", "server", item.assets["nested/test.txt"]); } }); // check that the files are where the manifest says they are - var testTxtPath = path.join(assetsDir, "test.txt"); - var nestedTxtPath = path.join(assetsDir, "nested", "test.txt"); assert.strictEqual(result.errors, false, result.errors && result.errors[0]); assert(fs.existsSync(testTxtPath)); assert(fs.existsSync(nestedTxtPath)); From 1e002707a254fea8f3370d676b70a352075d8b9c Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 17 Jul 2013 00:02:58 -0700 Subject: [PATCH 006/175] Rename "build version" to "built by" in other places too. --- scripts/admin/build-package-tarballs.sh | 4 ++-- tools/meteor.js | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/scripts/admin/build-package-tarballs.sh b/scripts/admin/build-package-tarballs.sh index 0a912965f3..9f33a7dafc 100755 --- a/scripts/admin/build-package-tarballs.sh +++ b/scripts/admin/build-package-tarballs.sh @@ -42,7 +42,7 @@ fi # The "build version" of the current tools. A change to this should result in a # change to all package versions; this will only be incremented when it's # important for all packages to be rebuilt, not on every tools release. -BUILD_VERSION=$(./meteor --build-version) +BUILT_BY=$(./meteor --built-by) FIRST_RUN=true # keep track to place commas correctly cd packages @@ -53,7 +53,7 @@ do echo "," >> "$TOPDIR/.package_manifest_chunk" fi - PACKAGE_VERSION=$(cat <(echo "$BUILD_VERSION") <(git ls-tree HEAD $PACKAGE) | shasum | cut -f 1 -d " ") # shasum's output looks like: 'SHA -' + PACKAGE_VERSION=$(cat <(echo "$BUILT_BY") <(git ls-tree HEAD $PACKAGE) | shasum | cut -f 1 -d " ") # shasum's output looks like: 'SHA -' echo "$PACKAGE version $PACKAGE_VERSION" ROOTDIR="$PACKAGE-${PACKAGE_VERSION}-${PLATFORM}" TARBALL="$OUTDIR/$PACKAGE-${PACKAGE_VERSION}-${PLATFORM}.tar.gz" diff --git a/tools/meteor.js b/tools/meteor.js index 4739812230..3e9918d554 100644 --- a/tools/meteor.js +++ b/tools/meteor.js @@ -1383,10 +1383,10 @@ Fiber(function () { process.exit(0); }; - // Implements --build-version. - var printBuildVersion = function () { + // Implements --built-by + var printBuiltBy = function () { var packages = require('./packages.js'); - console.log(packages.BUILD_VERSION); + console.log(packages.BUILT_BY); process.exit(0); }; @@ -1418,7 +1418,7 @@ Fiber(function () { .boolean("h") .boolean("help") .boolean("version") - .boolean("build-version") + .boolean("built-by") .boolean("arch") .boolean("debug") .alias("i", "ssh-identity"); @@ -1445,8 +1445,8 @@ Fiber(function () { delete argv.help; } - if (argv['build-version']) { - printBuildVersion(); + if (argv['built-by']) { + printBuiltBy(); return; } From 058f883a35a2a78bcbfdaec09051a770d53f98fd Mon Sep 17 00:00:00 2001 From: Geoff Schmidt Date: Wed, 17 Jul 2013 00:25:41 -0700 Subject: [PATCH 007/175] Be more conservative about soft library reload. Only reuse a package if we still intend to be loading it from the same packageDir. --- tools/library.js | 54 +++++++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/tools/library.js b/tools/library.js index b2f5cc48ca..54c976ea3a 100644 --- a/tools/library.js +++ b/tools/library.js @@ -34,12 +34,15 @@ var Library = function (options) { return stats.isDirectory(); }); - // these three are maps from package name to Package - self.loadedPackages = {}; // current cache - self.previouslyLoadedPackages = {}; // need validation after soft reset - self.warehouseCache = {}; // unconditionally survive soft reset - + self.loadedPackages = {}; // map from package name to Package self.overrides = {}; // package name to package directory + + // map from package name to: + // - pkg: cached Package object + // - packageDir: directory from which it was loaded + // - revalidate: true if the package needs to have its dependencies + // checked before it can be reused + self.softReloadCache = {}; }; _.extend(Library.prototype, { @@ -62,6 +65,7 @@ _.extend(Library.prototype, { throw new Error("No override present for package '" + packageName + "'"); delete self.loadedPackages[packageName]; delete self.overrides[packageName]; + delete self.softReloadCache[packageName]; }, // Force reload of changed packages. See description at get(). @@ -80,14 +84,9 @@ _.extend(Library.prototype, { var self = this; soft = soft || false; - if (soft) { - self.previouslyLoadedPackages = self.loadedPackages; - } else { - self.previouslyLoadedPackages = {}; - self.warehouseCache = {}; - } - self.loadedPackages = {}; + if (! soft) + self.softReloadCache = {}; }, // Given a package name as a string, retrieve a Package object. If @@ -115,13 +114,6 @@ _.extend(Library.prototype, { return name; // Packages cached from previous calls - if (_.has(self.warehouseCache, name)) - self.loadedPackages[name] = self.warehouseCache[name]; - else if (_.has(self.previouslyLoadedPackages, name) && - self.previouslyLoadedPackages[name].checkUpToDate()) - self.loadedPackages[name] = self.previouslyLoadedPackages[name]; - delete self.previouslyLoadedPackages[name]; - if (_.has(self.loadedPackages, name)) { return self.loadedPackages[name]; } @@ -162,6 +154,23 @@ _.extend(Library.prototype, { return pkg; } + // See if we can reuse a package that we have cached from before + // the last soft refresh. + if (_.has(self.softReloadCache, name)) { + var entry = self.softReloadCache[name]; + + if (entry.packageDir === packageDir && + (! entry.revalidate || entry.pkg.checkUpToDate())) { + // Cache hit + self.loadedPackages[name] = entry.pkg; + return entry.pkg; + } + + // Package has either changed, or it has been shadowed by a + // package in a different location. + delete self.softReloadCache[name]; + } + // Load package from disk var pkg = new packages.Package(self); if (fs.existsSync(path.join(packageDir, 'unipackage.json'))) { @@ -219,8 +228,11 @@ _.extend(Library.prototype, { } } - if (fromWarehouse) - self.warehouseCache[name] = pkg; + self.softReloadCache[name] = { + packageDir: packageDir, + revalidate: ! fromWarehouse, + pkg: pkg + }; return pkg; }, From 4da6391ba5e9b6792c0a7c76d227c6b563889191 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Wed, 17 Jul 2013 11:05:31 -0700 Subject: [PATCH 008/175] Make galaxy configure command discover galaxy when a sitename is passed as an argument. --- tools/meteor.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/meteor.js b/tools/meteor.js index 3e9918d554..d40feabff1 100644 --- a/tools/meteor.js +++ b/tools/meteor.js @@ -395,7 +395,14 @@ Fiber(function () { // We don't use galaxyCommand here because we want the tunnel to stay // open (galaxyCommand closes the tunnel as soon as the command finishes // running). The tunnel will be cleaned up when the process exits. - prepareForGalaxy(null, context, argv["ssh-identity"]); + prepareForGalaxy(argv._[0], context, argv["ssh-identity"]); + if (! context.galaxy) { + process.stdout.write("You must provide a galaxy to configure " + + "(by setting the GALAXY environment variable " + + "or providing a sitename " + + "(meteor galaxy configure )."); + process.exit(1); + } console.log("Visit http://localhost:" + context.galaxy.port + "/panel to configure your galaxy"); break; default: From b7f6a093a49c990c9f1e491f7b30bcce7363cf27 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 17 Jul 2013 11:41:48 -0700 Subject: [PATCH 009/175] dev bundle: use source-map version which took our PR. Point to a slightly refactored version of source-map-support. Have not yet bumped dev bundle version. --- scripts/generate-dev-bundle.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/generate-dev-bundle.sh b/scripts/generate-dev-bundle.sh index c97eb65a37..65fcfc663f 100755 --- a/scripts/generate-dev-bundle.sh +++ b/scripts/generate-dev-bundle.sh @@ -109,6 +109,7 @@ npm install tar@0.1.14 npm install kexec@0.1.1 npm install shell-quote@0.0.1 npm install byline@2.0.3 +npm install source-map@0.1.26 # Using the unreleased 1.1 branch. We can probably switch to a built NPM version # when it gets released. @@ -120,9 +121,10 @@ npm install https://github.com/ariya/esprima/tarball/5044b87f94fb802d9609f1426c8 npm install https://github.com/meteor/source-map/tarball/4a52398901fdb4b55b06ef4dd8b69f8256072b09 # Fork of node-source-map-support which allows us to specify our own -# retrieveSourceMap function, and uses the above version of source-map. -# XXX send a pull request -npm install https://github.com/meteor/node-source-map-support/tarball/d048eaa765bf743ddaad64716647f8760e2b8507 +# retrieveSourceMap function, and uses source-map 0.1.26. +# https://github.com/evanw/node-source-map-support/pull/18 +# https://github.com/evanw/node-source-map-support/pull/17 +npm install https://github.com/meteor/node-source-map-support/tarball/980e444c8346bbe29992fd3086bab0456b8d8667 # uglify-js has a bug which drops 'undefined' in arrays: # https://github.com/mishoo/UglifyJS2/pull/97 From 625aed5ea36e0516a0fb88fc00e3d4dc28a61304 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 17 Jul 2013 11:47:45 -0700 Subject: [PATCH 010/175] Upgrade fibers to 1.0.1. Specify the tested Node version in more places. Dev bundle version not yet bumped. Fixes #1153. --- docs/client/concepts.html | 15 ++++++++------- scripts/generate-dev-bundle.sh | 7 ++++--- tools/bundler.js | 7 ++++--- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/docs/client/concepts.html b/docs/client/concepts.html index 107ff6d416..2735fb5a91 100644 --- a/docs/client/concepts.html +++ b/docs/client/concepts.html @@ -684,12 +684,13 @@ To get started, run $ meteor bundle myapp.tgz -This command will generate a fully-contained Node.js application in -the form of a tarball. To run this application, you need to provide -Node.js 0.8 and a MongoDB server. You can then run the application by -invoking node, specifying the HTTP port for the application to listen -on, and the MongoDB endpoint. If you don't already have a MongoDB -server, we can recommend our friends at [MongoHQ](http://mongohq.com). +This command will generate a fully-contained Node.js application in the form of +a tarball. To run this application, you need to provide Node.js 0.8 and a +MongoDB server. (The current release of Meteor has been tested with Node +0.8.24.) You can then run the application by invoking node, specifying the HTTP +port for the application to listen on, and the MongoDB endpoint. If you don't +already have a MongoDB server, we can recommend our friends at +[MongoHQ](http://mongohq.com). $ PORT=3000 MONGO_URL=mongodb://localhost:27017/myapp node bundle/main.js @@ -704,7 +705,7 @@ have `npm` available, and run the following: $ cd bundle/server/node_modules $ rm -r fibers - $ npm install fibers@1.0.0 + $ npm install fibers@1.0.1 {{/warning}} {{/better_markdown}} diff --git a/scripts/generate-dev-bundle.sh b/scripts/generate-dev-bundle.sh index 65fcfc663f..e10f676c76 100755 --- a/scripts/generate-dev-bundle.sh +++ b/scripts/generate-dev-bundle.sh @@ -74,7 +74,8 @@ cd build git clone git://github.com/joyent/node.git cd node # When upgrading node versions, also update the values of MIN_NODE_VERSION at -# the top of tools/meteor.js and tools/server/server.js. +# the top of tools/meteor.js and tools/server/server.js, and the text in +# docs/client/concepts.html and the README in tools/bundler.js. git checkout v0.8.24 ./configure --prefix="$DIR" @@ -137,8 +138,8 @@ npm install progress@0.0.5 # If you update the version of fibers in the dev bundle, also update the "npm # install" command in docs/client/concepts.html and in the README in -# app/lib/bundler.js. -npm install fibers@1.0.0 +# tools/bundler.js. +npm install fibers@1.0.1 # Fibers ships with compiled versions of its C code for a dozen platforms. This # bloats our dev bundle, and confuses dpkg-buildpackage and rpmbuild into # thinking that the packages need to depend on both 32- and 64-bit versions of diff --git a/tools/bundler.js b/tools/bundler.js index d9ecdb0107..82b0229a43 100644 --- a/tools/bundler.js +++ b/tools/bundler.js @@ -1437,10 +1437,11 @@ var writeSiteArchive = function (targets, outputPath, options) { builder.write('main.js', { data: stub }); builder.write('README', { data: new Buffer( -"This is a Meteor application bundle. It has only one dependency,\n" + -"node.js (with the 'fibers' package). To run the application:\n" + +"This is a Meteor application bundle. It has only one dependency:\n" + +"Node.js 0.8 (with the 'fibers' package). The current release of Meteor\n" + +"has been tested with Node 0.8.24. To run the application:\n" + "\n" + -" $ npm install fibers@1.0.0\n" + +" $ npm install fibers@1.0.1\n" + " $ export MONGO_URL='mongodb://user:password@host:port/databasename'\n" + " $ export ROOT_URL='http://example.com'\n" + " $ export MAIL_URL='smtp://user:password@mailhost:port/'\n" + From 46871d3fa609963232d1e3fc4a3cc310a22f0d68 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 17 Jul 2013 12:10:25 -0700 Subject: [PATCH 011/175] Update more NPM packages. - clean-css: 0.8.3 - 1.0.11 - progress: 0.0.5 - 1.0.0 (includes the bug fix we requested) Don't try to install source-map twice, oops. --- scripts/generate-dev-bundle.sh | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/scripts/generate-dev-bundle.sh b/scripts/generate-dev-bundle.sh index e10f676c76..c2eeb3d9d0 100755 --- a/scripts/generate-dev-bundle.sh +++ b/scripts/generate-dev-bundle.sh @@ -100,7 +100,7 @@ cd "$DIR/lib/node_modules" npm install optimist@0.3.5 npm install semver@1.1.0 npm install handlebars@1.0.7 -npm install clean-css@0.8.3 +npm install clean-css@1.0.11 npm install request@2.12.0 npm install keypress@0.1.0 npm install http-proxy@0.10.1 # not 0.10.2, which contains a sketchy websocket change @@ -108,19 +108,15 @@ npm install underscore@1.4.4 npm install fstream@0.1.21 npm install tar@0.1.14 npm install kexec@0.1.1 -npm install shell-quote@0.0.1 -npm install byline@2.0.3 +npm install shell-quote@0.0.1 # now at 1.3.3, which adds plenty of options to parse but doesn't change quote +npm install byline@2.0.3 # v3 requires node 0.10 npm install source-map@0.1.26 +npm install progress@1.0.0 # Using the unreleased 1.1 branch. We can probably switch to a built NPM version # when it gets released. npm install https://github.com/ariya/esprima/tarball/5044b87f94fb802d9609f1426c838874ec2007b3 -# Fork of source-map which fixes one function with empty maps. -# https://github.com/mozilla/source-map/pull/70 -# See also below, where we get it into source-map-support. -npm install https://github.com/meteor/source-map/tarball/4a52398901fdb4b55b06ef4dd8b69f8256072b09 - # Fork of node-source-map-support which allows us to specify our own # retrieveSourceMap function, and uses source-map 0.1.26. # https://github.com/evanw/node-source-map-support/pull/18 @@ -131,11 +127,6 @@ npm install https://github.com/meteor/node-source-map-support/tarball/980e444c83 # https://github.com/mishoo/UglifyJS2/pull/97 npm install https://github.com/meteor/UglifyJS2/tarball/aa5abd14d3 -# progress 0.1.0 has a regression where it opens stdin and thus does not -# allow the node process to exit cleanly. See -# https://github.com/visionmedia/node-progress/issues/19 -npm install progress@0.0.5 - # If you update the version of fibers in the dev bundle, also update the "npm # install" command in docs/client/concepts.html and in the README in # tools/bundler.js. From 88d12e76e62c728e1b55460a2a642dd09726f167 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 17 Jul 2013 12:21:38 -0700 Subject: [PATCH 012/175] Upgrade Underscore to 1.5.1. A cursory glance at the diff suggests that this will not affect anything we do in Meteor. --- packages/underscore/underscore.js | 112 ++++++++++++++++++------------ scripts/generate-dev-bundle.sh | 2 +- 2 files changed, 67 insertions(+), 47 deletions(-) diff --git a/packages/underscore/underscore.js b/packages/underscore/underscore.js index a12f0d96cf..7d4ee27c7d 100644 --- a/packages/underscore/underscore.js +++ b/packages/underscore/underscore.js @@ -1,6 +1,6 @@ -// Underscore.js 1.4.4 +// Underscore.js 1.5.1 // http://underscorejs.org -// (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc. +// (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors // Underscore may be freely distributed under the MIT license. (function() { @@ -21,11 +21,12 @@ var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; // Create quick reference variables for speed access to core prototypes. - var push = ArrayProto.push, - slice = ArrayProto.slice, - concat = ArrayProto.concat, - toString = ObjProto.toString, - hasOwnProperty = ObjProto.hasOwnProperty; + var + push = ArrayProto.push, + slice = ArrayProto.slice, + concat = ArrayProto.concat, + toString = ObjProto.toString, + hasOwnProperty = ObjProto.hasOwnProperty; // All **ECMAScript 5** native function implementations that we hope to use // are declared here. @@ -64,7 +65,7 @@ } // Current version. - _.VERSION = '1.4.4'; + _.VERSION = '1.5.1'; // Collection Functions // -------------------- @@ -96,7 +97,7 @@ if (obj == null) return results; if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); each(obj, function(value, index, list) { - results[results.length] = iterator.call(context, value, index, list); + results.push(iterator.call(context, value, index, list)); }); return results; }; @@ -171,7 +172,7 @@ if (obj == null) return results; if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); each(obj, function(value, index, list) { - if (iterator.call(context, value, index, list)) results[results.length] = value; + if (iterator.call(context, value, index, list)) results.push(value); }); return results; }; @@ -238,7 +239,7 @@ // Convenience version of a common use case of `filter`: selecting only objects // containing specific `key:value` pairs. _.where = function(obj, attrs, first) { - if (_.isEmpty(attrs)) return first ? null : []; + if (_.isEmpty(attrs)) return first ? void 0 : []; return _[first ? 'find' : 'filter'](obj, function(value) { for (var key in attrs) { if (attrs[key] !== value[key]) return false; @@ -255,7 +256,7 @@ // Return the maximum element or (element-based computation). // Can't optimize arrays of integers longer than 65,535 elements. - // See: https://bugs.webkit.org/show_bug.cgi?id=80797 + // See [WebKit Bug 80797](https://bugs.webkit.org/show_bug.cgi?id=80797) _.max = function(obj, iterator, context) { if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { return Math.max.apply(Math, obj); @@ -264,7 +265,7 @@ var result = {computed : -Infinity, value: -Infinity}; each(obj, function(value, index, list) { var computed = iterator ? iterator.call(context, value, index, list) : value; - computed >= result.computed && (result = {value : value, computed : computed}); + computed > result.computed && (result = {value : value, computed : computed}); }); return result.value; }; @@ -324,7 +325,7 @@ // An internal function used for aggregate "group by" operations. var group = function(obj, value, context, behavior) { var result = {}; - var iterator = lookupIterator(value || _.identity); + var iterator = lookupIterator(value == null ? _.identity : value); each(obj, function(value, index) { var key = iterator.call(context, value, index, obj); behavior(result, key, value); @@ -363,7 +364,7 @@ return low; }; - // Safely convert anything iterable into a real, live array. + // Safely create a real, live array from anything iterable. _.toArray = function(obj) { if (!obj) return []; if (_.isArray(obj)) return slice.call(obj); @@ -422,8 +423,11 @@ // Internal implementation of a recursive `flatten` function. var flatten = function(input, shallow, output) { + if (shallow && _.every(input, _.isArray)) { + return concat.apply(output, input); + } each(input, function(value) { - if (_.isArray(value)) { + if (_.isArray(value) || _.isArguments(value)) { shallow ? push.apply(output, value) : flatten(value, shallow, output); } else { output.push(value); @@ -466,7 +470,7 @@ // Produce an array that contains the union: each distinct element from all of // the passed-in arrays. _.union = function() { - return _.uniq(concat.apply(ArrayProto, arguments)); + return _.uniq(_.flatten(arguments, true)); }; // Produce an array that contains every item shared between all the @@ -490,11 +494,10 @@ // Zip together multiple lists into a single array -- elements that share // an index go together. _.zip = function() { - var args = slice.call(arguments); - var length = _.max(_.pluck(args, 'length')); + var length = _.max(_.pluck(arguments, "length").concat(0)); var results = new Array(length); for (var i = 0; i < length; i++) { - results[i] = _.pluck(args, "" + i); + results[i] = _.pluck(arguments, '' + i); } return results; }; @@ -574,14 +577,25 @@ // Function (ahem) Functions // ------------------ + // Reusable constructor function for prototype setting. + var ctor = function(){}; + // Create a function bound to a given object (assigning `this`, and arguments, // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if // available. _.bind = function(func, context) { - if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); - var args = slice.call(arguments, 2); - return function() { - return func.apply(context, args.concat(slice.call(arguments))); + var args, bound; + if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); + if (!_.isFunction(func)) throw new TypeError; + args = slice.call(arguments, 2); + return bound = function() { + if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); + ctor.prototype = func.prototype; + var self = new ctor; + ctor.prototype = null; + var result = func.apply(self, args.concat(slice.call(arguments))); + if (Object(result) === result) return result; + return self; }; }; @@ -598,7 +612,7 @@ // all callbacks defined on an object belong to it. _.bindAll = function(obj) { var funcs = slice.call(arguments, 1); - if (funcs.length === 0) funcs = _.functions(obj); + if (funcs.length === 0) throw new Error("bindAll must be passed function names"); each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); return obj; }; @@ -627,17 +641,23 @@ }; // Returns a function, that, when invoked, will only be triggered at most once - // during a given window of time. - _.throttle = function(func, wait) { - var context, args, timeout, result; + // during a given window of time. Normally, the throttled function will run + // as much as it can, without ever going more than once per `wait` duration; + // but if you'd like to disable the execution on the leading edge, pass + // `{leading: false}`. To disable execution on the trailing edge, ditto. + _.throttle = function(func, wait, options) { + var context, args, result; + var timeout = null; var previous = 0; + options || (options = {}); var later = function() { - previous = new Date; + previous = options.leading === false ? 0 : new Date; timeout = null; result = func.apply(context, args); }; return function() { var now = new Date; + if (!previous && options.leading === false) previous = now; var remaining = wait - (now - previous); context = this; args = arguments; @@ -646,7 +666,7 @@ timeout = null; previous = now; result = func.apply(context, args); - } else if (!timeout) { + } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } return result; @@ -658,7 +678,8 @@ // N milliseconds. If `immediate` is passed, trigger the function on the // leading edge, instead of the trailing. _.debounce = function(func, wait, immediate) { - var timeout, result; + var result; + var timeout = null; return function() { var context = this, args = arguments; var later = function() { @@ -712,7 +733,6 @@ // Returns a function that will only be executed after being called N times. _.after = function(times, func) { - if (times <= 0) return func(); return function() { if (--times < 1) { return func.apply(this, arguments); @@ -728,7 +748,7 @@ _.keys = nativeKeys || function(obj) { if (obj !== Object(obj)) throw new TypeError('Invalid object'); var keys = []; - for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key; + for (var key in obj) if (_.has(obj, key)) keys.push(key); return keys; }; @@ -800,7 +820,7 @@ each(slice.call(arguments, 1), function(source) { if (source) { for (var prop in source) { - if (obj[prop] == null) obj[prop] = source[prop]; + if (obj[prop] === void 0) obj[prop] = source[prop]; } } }); @@ -824,7 +844,7 @@ // Internal recursive comparison function for `isEqual`. var eq = function(a, b, aStack, bStack) { // Identical objects are equal. `0 === -0`, but they aren't identical. - // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. + // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). if (a === b) return a !== 0 || 1 / a == 1 / b; // A strict comparison is necessary because `null == undefined`. if (a == null || b == null) return a === b; @@ -866,6 +886,13 @@ // unique nested structures. if (aStack[length] == a) return bStack[length] == b; } + // Objects with different constructors are not equivalent, but `Object`s + // from different frames are. + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && + _.isFunction(bCtor) && (bCtor instanceof bCtor))) { + return false; + } // Add the first object to the stack of traversed objects. aStack.push(a); bStack.push(b); @@ -882,13 +909,6 @@ } } } else { - // Objects with different constructors are not equivalent, but `Object`s - // from different frames are. - var aCtor = a.constructor, bCtor = b.constructor; - if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && - _.isFunction(bCtor) && (bCtor instanceof bCtor))) { - return false; - } // Deep compare objects. for (var key in a) { if (_.has(a, key)) { @@ -1012,7 +1032,7 @@ // Run a function **n** times. _.times = function(n, iterator, context) { - var accum = Array(n); + var accum = Array(Math.max(0, n)); for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i); return accum; }; @@ -1055,10 +1075,10 @@ }; }); - // If the value of the named property is a function then invoke it; - // otherwise, return it. + // If the value of the named `property` is a function then invoke it with the + // `object` as context; otherwise, return it. _.result = function(object, property) { - if (object == null) return null; + if (object == null) return void 0; var value = object[property]; return _.isFunction(value) ? value.call(object) : value; }; diff --git a/scripts/generate-dev-bundle.sh b/scripts/generate-dev-bundle.sh index c2eeb3d9d0..8796f68834 100755 --- a/scripts/generate-dev-bundle.sh +++ b/scripts/generate-dev-bundle.sh @@ -104,7 +104,7 @@ npm install clean-css@1.0.11 npm install request@2.12.0 npm install keypress@0.1.0 npm install http-proxy@0.10.1 # not 0.10.2, which contains a sketchy websocket change -npm install underscore@1.4.4 +npm install underscore@1.5.1 npm install fstream@0.1.21 npm install tar@0.1.14 npm install kexec@0.1.1 From d16f90623842530936a89e8e89bea5844106da07 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 17 Jul 2013 12:22:16 -0700 Subject: [PATCH 013/175] Bump dev bundle version. --- meteor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meteor b/meteor index ca0159ce1c..b6d4ef642c 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/bin/bash -BUNDLE_VERSION=0.3.11 +BUNDLE_VERSION=0.3.12 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. From ecf859e2a791eb570e9e786fe22f2776314035ec Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 17 Jul 2013 14:10:26 -0700 Subject: [PATCH 014/175] Don't advertise 'meteor galaxy' command until Galaxy support is official. --- tools/meteor.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/meteor.js b/tools/meteor.js index d40feabff1..5d30b672dc 100644 --- a/tools/meteor.js +++ b/tools/meteor.js @@ -388,6 +388,8 @@ Fiber(function () { Commands.push({ name: "galaxy", help: "Interact with your galaxy server", + // Remove this once Galaxy support is official. + hidden: true, func: function (argv) { var cmd = argv._.splice(0, 1)[0]; switch (cmd) { From 7da056199586485c5ec85da79ae630f9aa28524c Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Wed, 17 Jul 2013 15:23:01 -0700 Subject: [PATCH 015/175] Print error body if we get one from meteor mongo. This got lost when we refactored to have the same interface as deploy-galaxy. --- tools/deploy.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tools/deploy.js b/tools/deploy.js index cb86f9219a..90e00d0811 100644 --- a/tools/deploy.js +++ b/tools/deploy.js @@ -189,7 +189,13 @@ var temporaryMongoUrl = function (url) { if (password) opts.password = password; meteor_rpc('mongo', 'GET', - parsed_url.hostname, opts, urlFut.resolver()); + parsed_url.hostname, opts, function (err, body) { + if (err) { + process.stderr.write(body + "\n"); + process.exit(1); + } + urlFut.return(body); + }); var mongoUrl = urlFut.wait(); return mongoUrl; }; From fc307660acbcaf9f8d61e0796adb1f5a7b861482 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Wed, 17 Jul 2013 19:57:40 -0700 Subject: [PATCH 016/175] Add a note about how isServer doesn't strip code from the client. --- docs/client/api.html | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/client/api.html b/docs/client/api.html index f748f3bf32..d2c54fa776 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -12,6 +12,15 @@ on the client, just on the server, or *Anywhere*. {{> api_box isClient}} {{> api_box isServer}} + +{{#note}} +`Meteor.isServer` can be used to limit where code runs, but it does not +prevent code from being sent to the client. Any sensitive code that you +don't want served to the client, such as code containing passwords or +authentication mechanisms, should be kept in the `server` directory. +{{/note}} + + {{> api_box startup}} On a server, the function will run as soon as the server process is From f29e4a37c919172ccc04ac56426c6f96a99d6701 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 18 Jul 2013 08:06:27 -0700 Subject: [PATCH 017/175] Switch bootstrap tarballs to CNAME for CloudFront. --- scripts/admin/install-engine.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/admin/install-engine.sh b/scripts/admin/install-engine.sh index 4347d997b1..f2a2ccf330 100644 --- a/scripts/admin/install-engine.sh +++ b/scripts/admin/install-engine.sh @@ -59,8 +59,7 @@ trap "echo Installation failed." EXIT # install here: [ -e "$HOME/.meteor" ] && rm -rf "$HOME/.meteor" -# This is the CloudFront CDN serving com.meteor.warehouse. -TARBALL_URL="https://d3fm2vapipm3k9.cloudfront.net/bootstrap/__RELEASE__/meteor-bootstrap-${PLATFORM}.tar.gz" +TARBALL_URL="https://warehouse.meteor.com/bootstrap/__RELEASE__/meteor-bootstrap-${PLATFORM}.tar.gz" INSTALL_TMPDIR="$HOME/.meteor-install-tmp" rm -rf "$INSTALL_TMPDIR" From 59bec95724aeda7577b190da9ed910bde97fd44e Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 18 Jul 2013 10:17:49 -0700 Subject: [PATCH 018/175] Add a comment about new Uglify version. --- scripts/generate-dev-bundle.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/generate-dev-bundle.sh b/scripts/generate-dev-bundle.sh index 8796f68834..56e3eaf3f9 100755 --- a/scripts/generate-dev-bundle.sh +++ b/scripts/generate-dev-bundle.sh @@ -124,7 +124,13 @@ npm install https://github.com/ariya/esprima/tarball/5044b87f94fb802d9609f1426c8 npm install https://github.com/meteor/node-source-map-support/tarball/980e444c8346bbe29992fd3086bab0456b8d8667 # uglify-js has a bug which drops 'undefined' in arrays: -# https://github.com/mishoo/UglifyJS2/pull/97 +# This has finally been merged: +# https://github.com/mishoo/UglifyJS2/commit/b1febde3 +# so we should consider upgrading to a newer version of Uglify soon. +# This version was forked from 3a591c4 which was v2.2.5. A casual glance +# at the commits between there and b1febde3 suggests that we should +# be able to upgrade without any negative consequences (no obvious +# API changes, etc). npm install https://github.com/meteor/UglifyJS2/tarball/aa5abd14d3 # If you update the version of fibers in the dev bundle, also update the "npm From c1667c7d0c24f282b522f9217c115099599785c5 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 18 Jul 2013 10:31:52 -0700 Subject: [PATCH 019/175] Move clean-css and uglify-js from dev bundle to a new minifiers package. This will make it much easier to upgrade them and test new versions. (They are still called from the bundler, so changes to how we access them (eg source map support) will require modifying tools, but just "upgrade to new version" now is much easier.) --- packages/js-analyze/package.js | 3 +- packages/minifiers/.gitignore | 1 + packages/minifiers/.npm/package/.gitignore | 1 + packages/minifiers/.npm/package/README | 7 ++++ .../.npm/package/npm-shrinkwrap.json | 41 +++++++++++++++++++ packages/minifiers/minifiers.js | 5 +++ packages/minifiers/package.js | 14 +++++++ scripts/generate-dev-bundle.sh | 11 ----- tools/bundler.js | 20 +++++---- 9 files changed, 82 insertions(+), 21 deletions(-) create mode 100644 packages/minifiers/.gitignore create mode 100644 packages/minifiers/.npm/package/.gitignore create mode 100644 packages/minifiers/.npm/package/README create mode 100644 packages/minifiers/.npm/package/npm-shrinkwrap.json create mode 100644 packages/minifiers/minifiers.js create mode 100644 packages/minifiers/package.js diff --git a/packages/js-analyze/package.js b/packages/js-analyze/package.js index 58db2fd97d..d417eb58fc 100644 --- a/packages/js-analyze/package.js +++ b/packages/js-analyze/package.js @@ -1,5 +1,6 @@ Package.describe({ - summary: "JavaScript code analysis for Meteor" + summary: "JavaScript code analysis for Meteor", + internal: true }); // Use some packages from the Esprima project. If it turns out we need these on diff --git a/packages/minifiers/.gitignore b/packages/minifiers/.gitignore new file mode 100644 index 0000000000..677a6fc263 --- /dev/null +++ b/packages/minifiers/.gitignore @@ -0,0 +1 @@ +.build* diff --git a/packages/minifiers/.npm/package/.gitignore b/packages/minifiers/.npm/package/.gitignore new file mode 100644 index 0000000000..3c3629e647 --- /dev/null +++ b/packages/minifiers/.npm/package/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/packages/minifiers/.npm/package/README b/packages/minifiers/.npm/package/README new file mode 100644 index 0000000000..3d492553a4 --- /dev/null +++ b/packages/minifiers/.npm/package/README @@ -0,0 +1,7 @@ +This directory and the files immediately inside it are automatically generated +when you change this package's NPM dependencies. Commit the files in this +directory (npm-shrinkwrap.json, .gitignore, and this README) to source control +so that others run the same versions of sub-dependencies. + +You should NOT check in the node_modules directory that Meteor automatically +creates; if you are using git, the .gitignore file tells git to ignore it. diff --git a/packages/minifiers/.npm/package/npm-shrinkwrap.json b/packages/minifiers/.npm/package/npm-shrinkwrap.json new file mode 100644 index 0000000000..b8040d824d --- /dev/null +++ b/packages/minifiers/.npm/package/npm-shrinkwrap.json @@ -0,0 +1,41 @@ +{ + "dependencies": { + "clean-css": { + "version": "1.0.11", + "dependencies": { + "commander": { + "version": "1.2.0", + "dependencies": { + "keypress": { + "version": "0.1.0" + } + } + } + } + }, + "uglify-js": { + "from": "https://github.com/mishoo/UglifyJS2/tarball/b1febde3e9be32b9d88918ed733efc3796e3f143", + "dependencies": { + "async": { + "version": "0.2.9" + }, + "source-map": { + "version": "0.1.26", + "dependencies": { + "amdefine": { + "version": "0.0.5" + } + } + }, + "optimist": { + "version": "0.3.7", + "dependencies": { + "wordwrap": { + "version": "0.0.2" + } + } + } + } + } + } +} diff --git a/packages/minifiers/minifiers.js b/packages/minifiers/minifiers.js new file mode 100644 index 0000000000..6b0eaf4608 --- /dev/null +++ b/packages/minifiers/minifiers.js @@ -0,0 +1,5 @@ +// @export CleanCSSProcess +CleanCSSProcess = Npm.require('clean-css').process; + +// @export UglifyJSMinify +UglifyJSMinify = Npm.require('uglify-js').minify; diff --git a/packages/minifiers/package.js b/packages/minifiers/package.js new file mode 100644 index 0000000000..3d2d73fdc6 --- /dev/null +++ b/packages/minifiers/package.js @@ -0,0 +1,14 @@ +Package.describe({ + summary: "JavaScript and CSS minifiers", + internal: true +}); + +Npm.depends({ + "clean-css": "1.0.11", + // We depend on this commit, which has not been released yet. + "uglify-js": "https://github.com/mishoo/UglifyJS2/tarball/b1febde3e9be32b9d88918ed733efc3796e3f143" +}); + +Package.on_use(function (api, where) { + api.add_files('minifiers.js', 'server'); +}); diff --git a/scripts/generate-dev-bundle.sh b/scripts/generate-dev-bundle.sh index 56e3eaf3f9..8967aec352 100755 --- a/scripts/generate-dev-bundle.sh +++ b/scripts/generate-dev-bundle.sh @@ -100,7 +100,6 @@ cd "$DIR/lib/node_modules" npm install optimist@0.3.5 npm install semver@1.1.0 npm install handlebars@1.0.7 -npm install clean-css@1.0.11 npm install request@2.12.0 npm install keypress@0.1.0 npm install http-proxy@0.10.1 # not 0.10.2, which contains a sketchy websocket change @@ -123,16 +122,6 @@ npm install https://github.com/ariya/esprima/tarball/5044b87f94fb802d9609f1426c8 # https://github.com/evanw/node-source-map-support/pull/17 npm install https://github.com/meteor/node-source-map-support/tarball/980e444c8346bbe29992fd3086bab0456b8d8667 -# uglify-js has a bug which drops 'undefined' in arrays: -# This has finally been merged: -# https://github.com/mishoo/UglifyJS2/commit/b1febde3 -# so we should consider upgrading to a newer version of Uglify soon. -# This version was forked from 3a591c4 which was v2.2.5. A casual glance -# at the commits between there and b1febde3 suggests that we should -# be able to upgrade without any negative consequences (no obvious -# API changes, etc). -npm install https://github.com/meteor/UglifyJS2/tarball/aa5abd14d3 - # If you update the version of fibers in the dev bundle, also update the "npm # install" command in docs/client/concepts.html and in the README in # tools/bundler.js. diff --git a/tools/bundler.js b/tools/bundler.js index 82b0229a43..ca054e0228 100644 --- a/tools/bundler.js +++ b/tools/bundler.js @@ -445,9 +445,13 @@ _.extend(Target.prototype, { // Minify, if requested if (options.minify) { - self.minifyJs(); + var minifiers = unipackage.load({ + library: self.library, + packages: ['minifiers'] + }).minifiers; + self.minifyJs(minifiers); if (self.minifyCss) // XXX a bit of a hack - self.minifyCss(); + self.minifyCss(minifiers); } if (options.addCacheBusters) { @@ -677,15 +681,14 @@ _.extend(Target.prototype, { }, // Minify the JS in this target - minifyJs: function () { + minifyJs: function (minifiers) { var self = this; var allJs = _.map(self.js, function (file) { return file.contents('utf8'); }).join('\n;\n'); - var uglify = require('uglify-js'); - allJs = uglify.minify(allJs, { + allJs = minifiers.UglifyJSMinify(allJs, { fromString: true, compress: {drop_debugger: false} }).code; @@ -744,16 +747,15 @@ var ClientTarget = function (options) { inherits(ClientTarget, Target); _.extend(ClientTarget.prototype, { - // Minify the JS in this target - minifyCss: function () { + // Minify the CSS in this target + minifyCss: function (minifiers) { var self = this; var allCss = _.map(self.css, function (file) { return file.contents('utf8'); }).join('\n'); - var cleanCSS = require('clean-css'); - allCss = cleanCSS.process(allCss); + allCss = minifiers.CleanCSSProcess(allCss); self.css = [new File({ data: new Buffer(allCss, 'utf8') })]; self.css[0].setUrlToHash(".css"); From 99560c7d34ffacfc66781b238b9498a75b15501f Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 18 Jul 2013 10:42:14 -0700 Subject: [PATCH 020/175] Bump bundle version. --- meteor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meteor b/meteor index b6d4ef642c..d41941d01f 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/bin/bash -BUNDLE_VERSION=0.3.12 +BUNDLE_VERSION=0.3.13 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. From 2f6a5b21934efb6e5155a6c7f180719158de6be7 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 18 Jul 2013 11:39:42 -0700 Subject: [PATCH 021/175] "meteor rebuild-all" should not try to rebuild warehouse packages. That's because warehouse packages are now pre-built and have no source tree. (The existing code didn't work anyway. It iterated over self.releaseManifest instead of self.releaseManifest.packages, and the "name" and "version" arguments to the each were reversed. So it was trying to delete directories named ".build" inside "/Users/glasser/.meteor/packages/de0d7206ad/tools" and "/Users/glasser/.meteor/packages/packages". Since those directories never existed, it didn't manage to try to rebuild any packages anyway.) --- tools/library.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tools/library.js b/tools/library.js index 54c976ea3a..5d98eb410d 100644 --- a/tools/library.js +++ b/tools/library.js @@ -346,11 +346,8 @@ _.extend(Library.prototype, { }); }); - _.each(self.releaseManifest || {}, function (name, version) { - var packageDir = path.join(warehouse.getWarehouseDir(), - 'packages', name, version); - all[packageDir] = name; - }); + // We *DON'T* look in the warehouse here, because warehouse packages are + // prebuilt. // Delete any that are source packages with builds. var count = 0; From 74da4b1adb78fc7165caf5e8b5a35df073a23df5 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 18 Jul 2013 12:40:16 -0700 Subject: [PATCH 022/175] Fix Meteor.release. --- tools/bundler.js | 3 +-- tools/server/boot.js | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tools/bundler.js b/tools/bundler.js index ca054e0228..cad3ab3e51 100644 --- a/tools/bundler.js +++ b/tools/bundler.js @@ -115,8 +115,7 @@ // expressed as a path (relative to program.json) to the *client's* // program.json. // -// - config: additional framework-specific configuration. currently: -// - meteorRelease: the value to use for Meteor.release, if any +// - meteorRelease: the value to use for Meteor.release, if any // // // /app/*: source code of the (server part of the) app diff --git a/tools/server/boot.js b/tools/server/boot.js index 3d21f7cb3c..9128280ebd 100644 --- a/tools/server/boot.js +++ b/tools/server/boot.js @@ -25,7 +25,7 @@ __meteor_bootstrap__ = { startup_hooks: [], serverDir: serverDir, configJson: configJson }; -__meteor_runtime_config__ = { meteorRelease: configJson.release }; +__meteor_runtime_config__ = { meteorRelease: configJson.meteorRelease }; // connect (and some other NPM modules) use $NODE_ENV to make some decisions; From 9e6653d3ba48a261be25b934303f226b88dd15c9 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 18 Jul 2013 18:08:12 -0700 Subject: [PATCH 023/175] Update docs to linker-pre5. --- docs/.meteor/release | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/.meteor/release b/docs/.meteor/release index d2b13eb644..d9c783a0c0 100644 --- a/docs/.meteor/release +++ b/docs/.meteor/release @@ -1 +1 @@ -0.6.4 +linker-pre5 From ee60b7bcae26c1e74092e4032b43d381fa6d10a0 Mon Sep 17 00:00:00 2001 From: Geoff Schmidt Date: Thu, 18 Jul 2013 23:46:31 -0700 Subject: [PATCH 024/175] If you @export Foo.Bar.Baz, define Foo.Bar for you (if it wasn't already defined by an import.) --- tools/linker.js | 45 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/tools/linker.js b/tools/linker.js index 92a5bad07f..e4714ff6a6 100644 --- a/tools/linker.js +++ b/tools/linker.js @@ -181,10 +181,17 @@ _.extend(Module.prototype, { // Given 'symbolMap' like {Foo: 's1', 'Bar.Baz': 's2', 'Bar.Quux.A': 's3', 'Bar.Quux.B': 's4'} // return something like // {Foo: 's1', Bar: {Baz: 's2', Quux: {A: 's3', B: 's4'}}} -var buildSymbolTree = function (symbolMap, f) { +// +// If 'inputTree' is given, it is modified (augumented) instead of +// constructing a new tree. +// +// If the value of a symbol in symbolMap is set null, then we just +// ensure that its parents exist. For example, {'A.B.C': null} means +// to make sure that symbol tree contains at least {A: {B: {}}}. +var buildSymbolTree = function (symbolMap, inputTree) { // XXX XXX detect and report conflicts, like one file exporting // Foo and another file exporting Foo.Bar - var ret = {}; + var ret = inputTree || {}; _.each(symbolMap, function (value, symbol) { var parts = symbol.split('.'); @@ -196,7 +203,9 @@ var buildSymbolTree = function (symbolMap, f) { walk[part] = {}; walk = walk[part]; }); - walk[lastPart] = value; + + if (value) + walk[lastPart] = value; }); return ret; @@ -210,6 +219,9 @@ var writeSymbolTree = function (symbolTree, indent) { if (typeof node === "string") { return node; } + if (_.keys(node).length === 0) { + return '{}'; + } var spacing = new Array(indent + 1).join(' '); // XXX prettyprint! return "{\n" + @@ -662,6 +674,8 @@ var SOURCE_MAP_INSTRUCTIONS_COMMENT = banner([ // 'Foo', "Foo.bar", etc) to the module from which it should be // imported (which must load before us at runtime) // +// exports: symbols to export, as an array of symbol names as strings +// // useGlobalNamespace: must be the same value that was passed to link() // // prelinkFiles: the 'files' output from prelink() @@ -674,7 +688,8 @@ var link = function (options) { if (!_.isEmpty(options.imports)) { ret.push({ source: getImportCode(options.imports, - "/* Imports for global scope */\n\n", true), + "/* Imports for global scope */\n\n", true, + options.exports), servePath: options.importStubServePath }); } @@ -683,6 +698,7 @@ var link = function (options) { var header = getHeader({ imports: options.imports, + exports: options.exports, packageScopeVariables: options.packageScopeVariables }); var footer = getFooter({ @@ -728,7 +744,8 @@ var link = function (options) { var getHeader = function (options) { var chunks = []; chunks.push("(function () {\n\n" ); - chunks.push(getImportCode(options.imports, "/* Imports */\n")); + chunks.push(getImportCode(options.imports, "/* Imports */\n", false, + options.exports)); if (options.packageScopeVariables && !_.isEmpty(options.packageScopeVariables)) { chunks.push("/* Package-scope variables */\n"); @@ -737,26 +754,36 @@ var getHeader = function (options) { return chunks.join(''); }; -var getImportCode = function (imports, header, omitvar) { +var getImportCode = function (imports, header, omitvar, exports) { var self = this; + exports = exports || {}; - if (_.isEmpty(imports)) + if (_.isEmpty(imports) && _.isEmpty(exports)) return ""; + // Imports var scratch = {}; _.each(imports, function (name, symbol) { scratch[symbol] = packageDot(name) + "." + symbol; }); var tree = buildSymbolTree(scratch); + // Now, if we export a symbol A.B.C, and A.B.* isn't imported, set + // up A.B = {} + scratch = {}; + _.each(exports, function (symbol) { + scratch[symbol] = null; + }); + buildSymbolTree(scratch, tree); + + // Generate output var buf = header; _.each(tree, function (node, key) { buf += (omitvar ? "" : "var " ) + key + " = " + writeSymbolTree(node) + ";\n"; }); - - // XXX need to remove newlines, whitespace, in line number preserving mode buf += "\n"; + return buf; }; From 15c396b6868da897ceac8ddeba147e85363be119 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 19 Jul 2013 10:08:10 -0700 Subject: [PATCH 025/175] linker: Add a few newlines before the exports section. --- tools/linker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/linker.js b/tools/linker.js index e4714ff6a6..7299c6ba3c 100644 --- a/tools/linker.js +++ b/tools/linker.js @@ -791,7 +791,7 @@ var getFooter = function (options) { var chunks = []; if (options.name && options.exports && !_.isEmpty(options.exports)) { - chunks.push("/* Exports */\n"); + chunks.push("\n\n/* Exports */\n"); chunks.push("if (typeof Package === 'undefined') Package = {};\n"); chunks.push(packageDot(options.name), " = "); From 46180063c71d7025a27e413188df89e4d116a418 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 19 Jul 2013 10:13:05 -0700 Subject: [PATCH 026/175] Update to mongodb 1.3.12. Includes Emily's patch. --- packages/mongo-livedata/package.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mongo-livedata/package.js b/packages/mongo-livedata/package.js index df8acf2517..adf314796c 100644 --- a/packages/mongo-livedata/package.js +++ b/packages/mongo-livedata/package.js @@ -12,7 +12,7 @@ Package.describe({ internal: true }); -Npm.depends({mongodb: "1.3.7"}); +Npm.depends({mongodb: "1.3.12"}); Package.on_use(function (api) { api.use(['random', 'ejson', 'json', 'underscore', 'minimongo', 'logging', From 94810814d63d0ac97d1e3eaa49ce4bc7c072acb5 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 19 Jul 2013 10:42:56 -0700 Subject: [PATCH 027/175] No longer use NPM progress module. --- scripts/generate-dev-bundle.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/generate-dev-bundle.sh b/scripts/generate-dev-bundle.sh index 8967aec352..65268a7e96 100755 --- a/scripts/generate-dev-bundle.sh +++ b/scripts/generate-dev-bundle.sh @@ -110,7 +110,6 @@ npm install kexec@0.1.1 npm install shell-quote@0.0.1 # now at 1.3.3, which adds plenty of options to parse but doesn't change quote npm install byline@2.0.3 # v3 requires node 0.10 npm install source-map@0.1.26 -npm install progress@1.0.0 # Using the unreleased 1.1 branch. We can probably switch to a built NPM version # when it gets released. From 7684c726c84bdf6c83a95c090ea9ce0e51afeb84 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 19 Jul 2013 10:23:15 -0700 Subject: [PATCH 028/175] Use mongodb with extra null checks. --- packages/mongo-livedata/package.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/mongo-livedata/package.js b/packages/mongo-livedata/package.js index 8d05778fb2..8f46ec9602 100644 --- a/packages/mongo-livedata/package.js +++ b/packages/mongo-livedata/package.js @@ -12,7 +12,8 @@ Package.describe({ internal: true }); -Npm.depends({mongodb: "1.3.7"}); +Npm.depends({ + mongodb: "https://github.com/meteor/node-mongodb-native/tarball/8cde6990c24fcafbac892c5ccb1d59e40601c20c"}); Package.on_use(function (api) { api.use(['random', 'ejson', 'json', 'underscore', 'minimongo', 'logging', 'livedata'], From d0ae0a86eaa832cf2b68c0c885485c1bf72965a4 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 19 Jul 2013 10:53:47 -0700 Subject: [PATCH 029/175] Commit mongo npm-shrinkwrap.json change. --- packages/mongo-livedata/.npm/npm-shrinkwrap.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/mongo-livedata/.npm/npm-shrinkwrap.json b/packages/mongo-livedata/.npm/npm-shrinkwrap.json index 03ac6db249..e13cf8b7fd 100644 --- a/packages/mongo-livedata/.npm/npm-shrinkwrap.json +++ b/packages/mongo-livedata/.npm/npm-shrinkwrap.json @@ -1,13 +1,13 @@ { "dependencies": { "mongodb": { - "version": "1.3.7", + "from": "https://github.com/meteor/node-mongodb-native/tarball/8cde6990c24fcafbac892c5ccb1d59e40601c20c", "dependencies": { "bson": { - "version": "0.1.9" + "version": "0.2.1" }, "kerberos": { - "version": "0.0.2" + "version": "0.0.3" } } } From abe145736d0c6362a76b5d985d166fbe41c426b2 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 19 Jul 2013 10:55:24 -0700 Subject: [PATCH 030/175] Update docs and examples to the RC. --- docs/.meteor/release | 2 +- examples/leaderboard/.meteor/release | 2 +- examples/parties/.meteor/release | 2 +- examples/todos/.meteor/release | 2 +- examples/wordplay/.meteor/release | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/.meteor/release b/docs/.meteor/release index d2b13eb644..52a0ab86cb 100644 --- a/docs/.meteor/release +++ b/docs/.meteor/release @@ -1 +1 @@ -0.6.4 +0.6.4.1-rc1 diff --git a/examples/leaderboard/.meteor/release b/examples/leaderboard/.meteor/release index d2b13eb644..52a0ab86cb 100644 --- a/examples/leaderboard/.meteor/release +++ b/examples/leaderboard/.meteor/release @@ -1 +1 @@ -0.6.4 +0.6.4.1-rc1 diff --git a/examples/parties/.meteor/release b/examples/parties/.meteor/release index d2b13eb644..52a0ab86cb 100644 --- a/examples/parties/.meteor/release +++ b/examples/parties/.meteor/release @@ -1 +1 @@ -0.6.4 +0.6.4.1-rc1 diff --git a/examples/todos/.meteor/release b/examples/todos/.meteor/release index d2b13eb644..52a0ab86cb 100644 --- a/examples/todos/.meteor/release +++ b/examples/todos/.meteor/release @@ -1 +1 @@ -0.6.4 +0.6.4.1-rc1 diff --git a/examples/wordplay/.meteor/release b/examples/wordplay/.meteor/release index d2b13eb644..52a0ab86cb 100644 --- a/examples/wordplay/.meteor/release +++ b/examples/wordplay/.meteor/release @@ -1 +1 @@ -0.6.4 +0.6.4.1-rc1 From 18168429d831eb2a799e7d69902eaf03fb3aacbb Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 19 Jul 2013 11:08:09 -0700 Subject: [PATCH 031/175] Add History entry for 0.6.4.1. --- History.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/History.md b/History.md index 466d52faaa..dfa0b250b9 100644 --- a/History.md +++ b/History.md @@ -1,6 +1,10 @@ ## vNEXT +## v0.6.4.1 + +* Update mongodb driver to use version 0.2.1 of the bson module. + ## v0.6.4 * Separate OAuth flow logic from Accounts into separate packages. The From d0b7b41860ed65c8b1b53fb378e0869b1fa68107 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 19 Jul 2013 11:26:22 -0700 Subject: [PATCH 032/175] Update shrinkwrap for mongodb update. --- packages/mongo-livedata/.npm/package/npm-shrinkwrap.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mongo-livedata/.npm/package/npm-shrinkwrap.json b/packages/mongo-livedata/.npm/package/npm-shrinkwrap.json index 414c6ec42f..a8aafc87d9 100644 --- a/packages/mongo-livedata/.npm/package/npm-shrinkwrap.json +++ b/packages/mongo-livedata/.npm/package/npm-shrinkwrap.json @@ -1,10 +1,10 @@ { "dependencies": { "mongodb": { - "version": "1.3.7", + "version": "1.3.12", "dependencies": { "bson": { - "version": "0.1.9" + "version": "0.2.1" }, "kerberos": { "version": "0.0.3" From 237f6da87e0c0a745d35243831971a11b2ceebbd Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 19 Jul 2013 11:27:55 -0700 Subject: [PATCH 033/175] Store runner.js in test-in-console as an asset; extract via env var. Allow packages to specify that files are assets, overriding any handler defined for them. (May be useful for web workers!) Let Assets.getBinary work before global-imports.js is executed. --- packages/test-in-console/package.js | 3 +++ packages/test-in-console/reporter.js | 6 ++++++ tools/packages.js | 6 ++++-- tools/server/boot.js | 8 ++++++-- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/test-in-console/package.js b/packages/test-in-console/package.js index 0480b1e28d..0fca2a1962 100644 --- a/packages/test-in-console/package.js +++ b/packages/test-in-console/package.js @@ -14,4 +14,7 @@ Package.on_use(function (api) { api.add_files([ 'reporter.js' ], "server"); + + // This is to be run by phantomjs, not as part of normal package code. + api.add_files('runner.js', 'server', {isAsset: true}); }); diff --git a/packages/test-in-console/reporter.js b/packages/test-in-console/reporter.js index 668abc4a96..62f56cf3bc 100644 --- a/packages/test-in-console/reporter.js +++ b/packages/test-in-console/reporter.js @@ -1,3 +1,9 @@ +// A hacky way to extract the phantom runner script from the package. +if (process.env.WRITE_RUNNER_JS) { + Npm.require('fs').writeFileSync( + process.env.WRITE_RUNNER_JS, new Buffer(Assets.getBinary('runner.js'))); +} + var url = null; if (Meteor.settings && Meteor.settings.public && diff --git a/tools/packages.js b/tools/packages.js index 23c75b5a6e..d8c6050f0e 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -265,7 +265,7 @@ _.extend(Slice.prototype, { var fileOptions = _.clone(source.fileOptions) || {}; var absPath = path.resolve(self.pkg.sourceRoot, relPath); var ext = path.extname(relPath).substr(1); - var handler = !source.isAsset && self._getSourceHandler(ext); + var handler = !fileOptions.isAsset && self._getSourceHandler(ext); var contents = fs.readFileSync(absPath); self.dependencyInfo.files[absPath] = Builder.sha1(contents); @@ -1857,7 +1857,9 @@ _.extend(Package.prototype, { sources.push({ relPath: assetRelPath, - isAsset: true + fileOptions: { + isAsset: true + } }); }); }; diff --git a/tools/server/boot.js b/tools/server/boot.js index 9128280ebd..f2381f273f 100644 --- a/tools/server/boot.js +++ b/tools/server/boot.js @@ -114,13 +114,17 @@ Fiber(function () { fut = new Future(); callback = fut.resolver(); } - var _callback = Meteor.bindEnvironment(function (err, result) { + // This assumes that we've already loaded the meteor package, so meteor + // itself (and weird special cases like js-analyze) can't call + // Assets.get*. (We could change this function so that it doesn't call + // bindEnvironment if you don't pass a callback if we need to.) + var _callback = Package.meteor.Meteor.bindEnvironment(function (err, result) { if (result && ! encoding) // Sadly, this copies in Node 0.10. result = new Uint8Array(result); callback(err, result); }, function (e) { - Meteor._debug("Exception in callback of getAsset", e.stack); + console.log("Exception in callback of getAsset", e.stack); }); if (!fileInfo.assets || !_.has(fileInfo.assets, assetPath)) { From ab0e8e770ef5ccd25c8fbd0886683adcb6510ca3 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 19 Jul 2013 11:51:58 -0700 Subject: [PATCH 034/175] Use a commit of node-mongo-native with a unique package.json version. Avoids confusing the npm cache. --- packages/mongo-livedata/.npm/npm-shrinkwrap.json | 2 +- packages/mongo-livedata/package.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mongo-livedata/.npm/npm-shrinkwrap.json b/packages/mongo-livedata/.npm/npm-shrinkwrap.json index e13cf8b7fd..a11cf086ae 100644 --- a/packages/mongo-livedata/.npm/npm-shrinkwrap.json +++ b/packages/mongo-livedata/.npm/npm-shrinkwrap.json @@ -1,7 +1,7 @@ { "dependencies": { "mongodb": { - "from": "https://github.com/meteor/node-mongodb-native/tarball/8cde6990c24fcafbac892c5ccb1d59e40601c20c", + "from": "https://github.com/meteor/node-mongodb-native/tarball/20e17040c5eccf3c431788dd281b8099cd2099f8", "dependencies": { "bson": { "version": "0.2.1" diff --git a/packages/mongo-livedata/package.js b/packages/mongo-livedata/package.js index 8f46ec9602..83f3d85c09 100644 --- a/packages/mongo-livedata/package.js +++ b/packages/mongo-livedata/package.js @@ -13,7 +13,7 @@ Package.describe({ }); Npm.depends({ - mongodb: "https://github.com/meteor/node-mongodb-native/tarball/8cde6990c24fcafbac892c5ccb1d59e40601c20c"}); + mongodb: "https://github.com/meteor/node-mongodb-native/tarball/20e17040c5eccf3c431788dd281b8099cd2099f8"}); Package.on_use(function (api) { api.use(['random', 'ejson', 'json', 'underscore', 'minimongo', 'logging', 'livedata'], From e8af79e15196f0be29698038c3e1f393239b5da2 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 19 Jul 2013 12:08:53 -0700 Subject: [PATCH 035/175] Update docs and examples to rc3. --- docs/.meteor/release | 2 +- examples/leaderboard/.meteor/release | 2 +- examples/parties/.meteor/release | 2 +- examples/todos/.meteor/release | 2 +- examples/wordplay/.meteor/release | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/.meteor/release b/docs/.meteor/release index 52a0ab86cb..2f310beb14 100644 --- a/docs/.meteor/release +++ b/docs/.meteor/release @@ -1 +1 @@ -0.6.4.1-rc1 +0.6.4.1-rc3 diff --git a/examples/leaderboard/.meteor/release b/examples/leaderboard/.meteor/release index 52a0ab86cb..2f310beb14 100644 --- a/examples/leaderboard/.meteor/release +++ b/examples/leaderboard/.meteor/release @@ -1 +1 @@ -0.6.4.1-rc1 +0.6.4.1-rc3 diff --git a/examples/parties/.meteor/release b/examples/parties/.meteor/release index 52a0ab86cb..2f310beb14 100644 --- a/examples/parties/.meteor/release +++ b/examples/parties/.meteor/release @@ -1 +1 @@ -0.6.4.1-rc1 +0.6.4.1-rc3 diff --git a/examples/todos/.meteor/release b/examples/todos/.meteor/release index 52a0ab86cb..2f310beb14 100644 --- a/examples/todos/.meteor/release +++ b/examples/todos/.meteor/release @@ -1 +1 @@ -0.6.4.1-rc1 +0.6.4.1-rc3 diff --git a/examples/wordplay/.meteor/release b/examples/wordplay/.meteor/release index 52a0ab86cb..2f310beb14 100644 --- a/examples/wordplay/.meteor/release +++ b/examples/wordplay/.meteor/release @@ -1 +1 @@ -0.6.4.1-rc1 +0.6.4.1-rc3 From 6d6a0977e2c2aea1698c129cb86a3fdb94937fc9 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Fri, 19 Jul 2013 15:32:57 -0700 Subject: [PATCH 036/175] Update banner and notices to 0.6.4.1 --- scripts/admin/banner.txt | 5 ++--- scripts/admin/notices.json | 3 +++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/admin/banner.txt b/scripts/admin/banner.txt index 41f18ade38..1442c35362 100644 --- a/scripts/admin/banner.txt +++ b/scripts/admin/banner.txt @@ -1,5 +1,4 @@ -=> Meteor 0.6.4 released: New OAuth packages and recommended updates. See - https://github.com/meteor/meteor/blob/devel/History.md for details. +=> Meteor 0.6.4.1: security fix for upstream MongoDB BSON library. This is being downloaded in the background. Update your project - to Meteor 0.6.4 by running 'meteor update'. + to Meteor 0.6.4.1 by running 'meteor update'. diff --git a/scripts/admin/notices.json b/scripts/admin/notices.json index 59bc24d1f9..ce6d818d9f 100644 --- a/scripts/admin/notices.json +++ b/scripts/admin/notices.json @@ -40,6 +40,9 @@ { "release": "0.6.4" }, + { + "release": "0.6.4.1" + }, { "release": "NEXT" } From 614ab6a172afd4747a13c812f499a36129e21248 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Fri, 19 Jul 2013 15:54:21 -0700 Subject: [PATCH 037/175] Update docs and examples to 0.6.4.1 --- docs/.meteor/release | 2 +- examples/leaderboard/.meteor/release | 2 +- examples/parties/.meteor/release | 2 +- examples/todos/.meteor/release | 2 +- examples/wordplay/.meteor/release | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/.meteor/release b/docs/.meteor/release index 2f310beb14..f43621550c 100644 --- a/docs/.meteor/release +++ b/docs/.meteor/release @@ -1 +1 @@ -0.6.4.1-rc3 +0.6.4.1 diff --git a/examples/leaderboard/.meteor/release b/examples/leaderboard/.meteor/release index 2f310beb14..f43621550c 100644 --- a/examples/leaderboard/.meteor/release +++ b/examples/leaderboard/.meteor/release @@ -1 +1 @@ -0.6.4.1-rc3 +0.6.4.1 diff --git a/examples/parties/.meteor/release b/examples/parties/.meteor/release index 2f310beb14..f43621550c 100644 --- a/examples/parties/.meteor/release +++ b/examples/parties/.meteor/release @@ -1 +1 @@ -0.6.4.1-rc3 +0.6.4.1 diff --git a/examples/todos/.meteor/release b/examples/todos/.meteor/release index 2f310beb14..f43621550c 100644 --- a/examples/todos/.meteor/release +++ b/examples/todos/.meteor/release @@ -1 +1 @@ -0.6.4.1-rc3 +0.6.4.1 diff --git a/examples/wordplay/.meteor/release b/examples/wordplay/.meteor/release index 2f310beb14..f43621550c 100644 --- a/examples/wordplay/.meteor/release +++ b/examples/wordplay/.meteor/release @@ -1 +1 @@ -0.6.4.1-rc3 +0.6.4.1 From d5a1295977da80368fdf205f299878f29ab5de00 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Fri, 19 Jul 2013 16:49:40 -0700 Subject: [PATCH 038/175] Make galaxy discovery not follow redirects --- tools/deploy-galaxy.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/deploy-galaxy.js b/tools/deploy-galaxy.js index 6ff3b0ccb0..2b4f1358f9 100644 --- a/tools/deploy-galaxy.js +++ b/tools/deploy-galaxy.js @@ -87,7 +87,11 @@ exports.discoverGalaxy = function (app) { // At some point we may want to send a version in the request so that galaxy // can respond differently to different versions of meteor. - request({ url: url, json: true }, function (err, resp, body) { + request({ + url: url, + json: true, + followRedirect: false + }, function (err, resp, body) { if (! err && resp.statusCode === 200 && body && From 96d5ce2fa4424df18524c3d1e799772fc114c63d Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 22 Jul 2013 11:58:33 -0700 Subject: [PATCH 039/175] Followup adding comment to d5a1295 fix. --- tools/deploy-galaxy.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/deploy-galaxy.js b/tools/deploy-galaxy.js index 2b4f1358f9..e393ddc760 100644 --- a/tools/deploy-galaxy.js +++ b/tools/deploy-galaxy.js @@ -90,6 +90,8 @@ exports.discoverGalaxy = function (app) { request({ url: url, json: true, + // We don't want to be confused by, eg, a non-Galaxy-hosted site which + // redirects to a Galaxy-hosted site. followRedirect: false }, function (err, resp, body) { if (! err && From 2e79a3145e1e6f260f21f61fb29e147b40da2f1f Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Mon, 22 Jul 2013 15:08:08 -0700 Subject: [PATCH 040/175] Add .meteor.com to sitenames in meteor tool if no domain. Commands that don't use galaxyCommand (logs and configure, for now) have to call qualifySitename themselves, but I think this is okay for now because we're mainly concerned with preventing deploys to non-fully-qualified domains. --- tools/meteor.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tools/meteor.js b/tools/meteor.js index 5d30b672dc..a9c47a6712 100644 --- a/tools/meteor.js +++ b/tools/meteor.js @@ -162,6 +162,15 @@ Fiber(function () { return tunnel; }; + var qualifySitename = function (site) { + // Append .meteor.com if we don't have a domain name. In the future, we + // probably want this to be configurable via a client-side preference of + // some kind. + if (site.indexOf(".") === -1) + site = site + ".meteor.com"; + return site; + }; + var prepareForGalaxy = function (site, context, sshIdentity) { if (! deployGalaxy) deployGalaxy = require('./deploy-galaxy.js'); @@ -180,6 +189,7 @@ Fiber(function () { var galaxyCommand = function (cmd) { return function (argv) { if (argv._[1]) { + argv._[1] = qualifySitename(argv._[1]); var tunnel = prepareForGalaxy(argv._[1], context, argv["ssh-identity"]); var result; try { @@ -397,6 +407,7 @@ Fiber(function () { // We don't use galaxyCommand here because we want the tunnel to stay // open (galaxyCommand closes the tunnel as soon as the command finishes // running). The tunnel will be cleaned up when the process exits. + argv._[0] = qualifySitename(argv._[0]); prepareForGalaxy(argv._[0], context, argv["ssh-identity"]); if (! context.galaxy) { process.stdout.write("You must provide a galaxy to configure " + @@ -1031,6 +1042,7 @@ Fiber(function () { // We don't use galaxyCommand here because we want the tunnel to stay // open (galaxyCommand closes the tunnel as soon as the command finishes // running). The tunnel will be cleaned up when the process exits. + site = qualifySitename(site); var tunnel = prepareForGalaxy(site, context, argv["ssh-identity"]); var useGalaxy = !!context.galaxy; From a27e6239b965e49fdd26fae787a099e3373d90ef Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Mon, 22 Jul 2013 15:14:11 -0700 Subject: [PATCH 041/175] Validate certs on galaxy discovery requests. --- tools/deploy-galaxy.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/deploy-galaxy.js b/tools/deploy-galaxy.js index e393ddc760..c51269ee42 100644 --- a/tools/deploy-galaxy.js +++ b/tools/deploy-galaxy.js @@ -90,6 +90,7 @@ exports.discoverGalaxy = function (app) { request({ url: url, json: true, + strictSSL: true, // We don't want to be confused by, eg, a non-Galaxy-hosted site which // redirects to a Galaxy-hosted site. followRedirect: false From 732847436b84cd37205404fd090b01dfc6d59264 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Mon, 22 Jul 2013 15:31:49 -0700 Subject: [PATCH 042/175] Time out if we dont connect to galaxy in 10s --- tools/deploy-galaxy.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/deploy-galaxy.js b/tools/deploy-galaxy.js index c51269ee42..05fd2f5f45 100644 --- a/tools/deploy-galaxy.js +++ b/tools/deploy-galaxy.js @@ -35,6 +35,11 @@ var getGalaxy = function (context) { } _galaxy = Meteor.connect(context.galaxy.url); + Meteor.setTimeout(function () { + if (_galaxy.status().status !== "connected") { + exitWithError("Could not connect to galaxy " + context.galaxy + ": " + _galaxy.status().status); + } + }, 10*1000); } return _galaxy; From d5ed4fd87854a70c6738bac5094856657d427902 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 22 Jul 2013 16:58:44 -0700 Subject: [PATCH 043/175] Don't try to read empty files. Fixes #1237. --- tools/packages.js | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/tools/packages.js b/tools/packages.js index d8c6050f0e..4f976cc67c 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -2025,16 +2025,22 @@ _.extend(Package.prototype, { _.each(sliceJson.resources, function (resource) { rejectBadPath(resource.file); - var fd = fs.openSync(path.join(sliceBasePath, resource.file), "r"); - try { - var data = new Buffer(resource.length); - var count = fs.readSync( - fd, data, 0, resource.length, resource.offset); - } finally { - fs.closeSync(fd); + var data = new Buffer(resource.length); + // Read the data from disk, if it is non-empty. Avoid doing IO for empty + // files, because (a) unnecessary and (b) fs.readSync with length 0 + // throws instead of acting like POSIX read: + // https://github.com/joyent/node/issues/5685 + if (resource.length > 0) { + var fd = fs.openSync(path.join(sliceBasePath, resource.file), "r"); + try { + var count = fs.readSync( + fd, data, 0, resource.length, resource.offset); + } finally { + fs.closeSync(fd); + } + if (count !== resource.length) + throw new Error("couldn't read entire resource"); } - if (count !== resource.length) - throw new Error("couldn't read entire resource"); if (resource.type === "prelink") { var prelinkFile = { From 2d8b96c946d77a71d6a48a3fce3f16f099a9d8c7 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Mon, 22 Jul 2013 18:14:31 -0700 Subject: [PATCH 044/175] Fix timeout so that it gets cancelled when the connection is closed --- tools/deploy-galaxy.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tools/deploy-galaxy.js b/tools/deploy-galaxy.js index 05fd2f5f45..3f3a5128d8 100644 --- a/tools/deploy-galaxy.js +++ b/tools/deploy-galaxy.js @@ -35,11 +35,17 @@ var getGalaxy = function (context) { } _galaxy = Meteor.connect(context.galaxy.url); - Meteor.setTimeout(function () { + var timeout = Meteor.setTimeout(function () { if (_galaxy.status().status !== "connected") { + console.log(_galaxy.status()); exitWithError("Could not connect to galaxy " + context.galaxy + ": " + _galaxy.status().status); } }, 10*1000); + var close = _galaxy.close; + _galaxy.close = function (/*arguments*/) { + Meteor.clearTimeout(timeout); + close.apply(_galaxy, arguments); + }; } return _galaxy; From 2d2da146d91762e4f5080fe7dd88b8a796233f20 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Mon, 22 Jul 2013 18:24:31 -0700 Subject: [PATCH 045/175] Fix messages on timeout error --- tools/deploy-galaxy.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/deploy-galaxy.js b/tools/deploy-galaxy.js index 3f3a5128d8..0c116789c9 100644 --- a/tools/deploy-galaxy.js +++ b/tools/deploy-galaxy.js @@ -37,8 +37,8 @@ var getGalaxy = function (context) { _galaxy = Meteor.connect(context.galaxy.url); var timeout = Meteor.setTimeout(function () { if (_galaxy.status().status !== "connected") { - console.log(_galaxy.status()); - exitWithError("Could not connect to galaxy " + context.galaxy + ": " + _galaxy.status().status); + process.stderr.write("Could not connect to galaxy " + context.galaxy.url + ": " + _galaxy.status().status + '\n'); + process.exit(1); } }, 10*1000); var close = _galaxy.close; From 902d3b5f1c37ee8402bea0e37fc860e5699cad81 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Mon, 22 Jul 2013 21:17:34 -0700 Subject: [PATCH 046/175] Don't try to add .meteor.com to an undefined sitename in galaxy configure. --- tools/meteor.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/meteor.js b/tools/meteor.js index a9c47a6712..0d88a79a20 100644 --- a/tools/meteor.js +++ b/tools/meteor.js @@ -407,7 +407,8 @@ Fiber(function () { // We don't use galaxyCommand here because we want the tunnel to stay // open (galaxyCommand closes the tunnel as soon as the command finishes // running). The tunnel will be cleaned up when the process exits. - argv._[0] = qualifySitename(argv._[0]); + if (argv._[0]) + argv._[0] = qualifySitename(argv._[0]); prepareForGalaxy(argv._[0], context, argv["ssh-identity"]); if (! context.galaxy) { process.stdout.write("You must provide a galaxy to configure " + From f46d94cffda2bf2d7a8f4a01c3b3e6beefd6b9b7 Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Tue, 23 Jul 2013 11:43:03 -0700 Subject: [PATCH 047/175] Option to deploy an app as 'admin app'. So app binds to host.local/appName and is available in admin panel --- tools/deploy-galaxy.js | 4 ++++ tools/meteor.js | 14 +++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/tools/deploy-galaxy.js b/tools/deploy-galaxy.js index 0c116789c9..37ebfc6034 100644 --- a/tools/deploy-galaxy.js +++ b/tools/deploy-galaxy.js @@ -175,6 +175,10 @@ exports.deploy = function (options) { var appConfig = { METEOR_SETTINGS: options.settings }; + + if (options.admin) + appConfig.admin = true; + try { galaxy.call('createApp', options.app, appConfig); } catch (e) { diff --git a/tools/meteor.js b/tools/meteor.js index 0d88a79a20..dc7bc7a5ec 100644 --- a/tools/meteor.js +++ b/tools/meteor.js @@ -941,8 +941,12 @@ Fiber(function () { .describe('debug', 'deploy in debug mode (don\'t minify, etc)') .describe('settings', 'set optional data for Meteor.settings') .alias('ssh-identity', 'i') - .describe('ssh-identity', 'Selects a file from which the identity (private key) is read. See ssh(1) for details.') + .describe('ssh-identity', 'Selects a file from which the identity (private key) is read. See ssh(1) for details.') .describe('star', 'a star (tarball) to deploy instead of the current meteor app') + .alias('admin', 'A') + .boolean('admin') + .boolean('A') + .describe('admin', 'Marks the application as an admin app, it will be available in Galaxy admin interface.') .usage( "Usage: meteor deploy [--password] [--settings settings.json] [--debug] [--delete]\n" + "\n" + @@ -967,7 +971,10 @@ Fiber(function () { "\n" + "The --password flag sets an administrative password for the domain. Once\n" + "set, any subsequent 'deploy', 'logs', or 'mongo' command will prompt for\n" + - "the password. You can change the password with a second 'deploy' command." + "the password. You can change the password with a second 'deploy' command.\n" + + "\n" + + "The --admin flag marks application as administrative to Galaxy interface.\n" + + "Application's web-interface will be accessible from admin's panel only.\n" ); var new_argv = opt.argv; @@ -1012,7 +1019,8 @@ Fiber(function () { minify: !argv.debug, releaseStamp: context.releaseVersion, library: context.library - } + }, + admin: argv.admin }); } else { deploy.deployCmd({ From 095e81a0b6202c0a80b9ab90898d3595c0d06e55 Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Tue, 23 Jul 2013 12:35:57 -0700 Subject: [PATCH 048/175] Hide documentation to admin flag. Shouldn't distract people who don't have Galaxy yet. (everyone outside MDG) --- tools/meteor.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tools/meteor.js b/tools/meteor.js index dc7bc7a5ec..0d64473ced 100644 --- a/tools/meteor.js +++ b/tools/meteor.js @@ -946,7 +946,8 @@ Fiber(function () { .alias('admin', 'A') .boolean('admin') .boolean('A') - .describe('admin', 'Marks the application as an admin app, it will be available in Galaxy admin interface.') + // Shouldn't be public until the Galaxy release + //.describe('admin', 'Marks the application as an admin app, it will be available in Galaxy admin interface.') .usage( "Usage: meteor deploy [--password] [--settings settings.json] [--debug] [--delete]\n" + "\n" + @@ -971,10 +972,11 @@ Fiber(function () { "\n" + "The --password flag sets an administrative password for the domain. Once\n" + "set, any subsequent 'deploy', 'logs', or 'mongo' command will prompt for\n" + - "the password. You can change the password with a second 'deploy' command.\n" + - "\n" + - "The --admin flag marks application as administrative to Galaxy interface.\n" + - "Application's web-interface will be accessible from admin's panel only.\n" + "the password. You can change the password with a second 'deploy' command.\n" + // Shouldn't be public until the Galaxy release + //"\n" + + //"The --admin flag marks application as administrative to Galaxy interface.\n" + + //"Application's web-interface will be accessible from admin's panel only.\n" ); var new_argv = opt.argv; From d65cf0307bce767f2d16e5d52611c6b3087be53d Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Tue, 23 Jul 2013 17:58:44 -0700 Subject: [PATCH 049/175] Remove one letter option for --admin. --- tools/meteor.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/meteor.js b/tools/meteor.js index 0d64473ced..ff76d26379 100644 --- a/tools/meteor.js +++ b/tools/meteor.js @@ -943,9 +943,7 @@ Fiber(function () { .alias('ssh-identity', 'i') .describe('ssh-identity', 'Selects a file from which the identity (private key) is read. See ssh(1) for details.') .describe('star', 'a star (tarball) to deploy instead of the current meteor app') - .alias('admin', 'A') .boolean('admin') - .boolean('A') // Shouldn't be public until the Galaxy release //.describe('admin', 'Marks the application as an admin app, it will be available in Galaxy admin interface.') .usage( From 9251942b9a2e05421c8810da8f688502c12ffb6b Mon Sep 17 00:00:00 2001 From: Tim Haines Date: Mon, 24 Jun 2013 23:04:30 -0700 Subject: [PATCH 050/175] OAuth client: Only invoke the callback if it exists --- packages/accounts-oauth/oauth_client.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/accounts-oauth/oauth_client.js b/packages/accounts-oauth/oauth_client.js index 5b189fdc05..f3d161e8c0 100644 --- a/packages/accounts-oauth/oauth_client.js +++ b/packages/accounts-oauth/oauth_client.js @@ -19,9 +19,9 @@ Accounts.oauth.tryLoginAfterPopupClosed = function(credentialToken, callback) { Accounts.oauth.credentialRequestCompleteHandler = function(callback) { return function (credentialTokenOrError) { if(credentialTokenOrError && credentialTokenOrError instanceof Error) { - callback(credentialTokenOrError); + callback && callback(credentialTokenOrError); } else { Accounts.oauth.tryLoginAfterPopupClosed(credentialTokenOrError, callback); } }; -} +}; From 65c288bdb830537d99940e9c941866c6bb7df8a4 Mon Sep 17 00:00:00 2001 From: Tim Haines Date: Mon, 24 Jun 2013 23:05:24 -0700 Subject: [PATCH 051/175] Handle Meteor.loginWithX called with callback but not options --- packages/accounts-facebook/facebook_client.js | 6 ++++++ packages/accounts-github/github_client.js | 6 ++++++ packages/accounts-google/google_client.js | 6 ++++++ packages/accounts-meetup/meetup_client.js | 6 ++++++ packages/accounts-twitter/twitter_client.js | 6 ++++++ packages/accounts-weibo/weibo_client.js | 6 ++++++ 6 files changed, 36 insertions(+) diff --git a/packages/accounts-facebook/facebook_client.js b/packages/accounts-facebook/facebook_client.js index 149bfa8a0e..5f29dbf896 100644 --- a/packages/accounts-facebook/facebook_client.js +++ b/packages/accounts-facebook/facebook_client.js @@ -1,4 +1,10 @@ Meteor.loginWithFacebook = function(options, callback) { + // support a callback without options + if (! callback && typeof options === "function") { + callback = options; + options = null; + } + var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback); Facebook.requestCredential(options, credentialRequestCompleteCallback); }; \ No newline at end of file diff --git a/packages/accounts-github/github_client.js b/packages/accounts-github/github_client.js index 7649f2bebb..a30db6a39b 100644 --- a/packages/accounts-github/github_client.js +++ b/packages/accounts-github/github_client.js @@ -1,4 +1,10 @@ Meteor.loginWithGithub = function(options, callback) { + // support a callback without options + if (! callback && typeof options === "function") { + callback = options; + options = null; + } + var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback); Github.requestCredential(options, credentialRequestCompleteCallback); }; \ No newline at end of file diff --git a/packages/accounts-google/google_client.js b/packages/accounts-google/google_client.js index 96641ac3bd..877c94acc0 100644 --- a/packages/accounts-google/google_client.js +++ b/packages/accounts-google/google_client.js @@ -1,4 +1,10 @@ Meteor.loginWithGoogle = function(options, callback) { + // support a callback without options + if (! callback && typeof options === "function") { + callback = options; + options = null; + } + var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback); Google.requestCredential(options, credentialRequestCompleteCallback); }; \ No newline at end of file diff --git a/packages/accounts-meetup/meetup_client.js b/packages/accounts-meetup/meetup_client.js index 6d0d674a2f..cc5359e45a 100644 --- a/packages/accounts-meetup/meetup_client.js +++ b/packages/accounts-meetup/meetup_client.js @@ -1,4 +1,10 @@ Meteor.loginWithMeetup = function(options, callback) { + // support a callback without options + if (! callback && typeof options === "function") { + callback = options; + options = null; + } + var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback); Meetup.requestCredential(options, credentialRequestCompleteCallback); }; \ No newline at end of file diff --git a/packages/accounts-twitter/twitter_client.js b/packages/accounts-twitter/twitter_client.js index 887d4ad510..c78f47a1c1 100644 --- a/packages/accounts-twitter/twitter_client.js +++ b/packages/accounts-twitter/twitter_client.js @@ -1,4 +1,10 @@ Meteor.loginWithTwitter = function(options, callback) { + // support a callback without options + if (! callback && typeof options === "function") { + callback = options; + options = null; + } + var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback); Twitter.requestCredential(options, credentialRequestCompleteCallback); }; \ No newline at end of file diff --git a/packages/accounts-weibo/weibo_client.js b/packages/accounts-weibo/weibo_client.js index 644c0176e6..d620dbac9f 100644 --- a/packages/accounts-weibo/weibo_client.js +++ b/packages/accounts-weibo/weibo_client.js @@ -1,4 +1,10 @@ Meteor.loginWithWeibo = function(options, callback) { + // support a callback without options + if (! callback && typeof options === "function") { + callback = options; + options = null; + } + var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback); Weibo.requestCredential(options, credentialRequestCompleteCallback); }; \ No newline at end of file From 951fc87213cbbba1c9253c95227f44c40a845230 Mon Sep 17 00:00:00 2001 From: Zoltan Olah Date: Mon, 15 Jul 2013 16:03:50 -0700 Subject: [PATCH 052/175] Added support to force the approval prompt for the google oAuth flow. --- packages/google/google_client.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/google/google_client.js b/packages/google/google_client.js index 83b8c189cd..14313c5297 100644 --- a/packages/google/google_client.js +++ b/packages/google/google_client.js @@ -30,6 +30,7 @@ Google.requestCredential = function (options, credentialRequestCompleteCallback) // https://developers.google.com/accounts/docs/OAuth2WebServer#formingtheurl var accessType = options.requestOfflineToken ? 'offline' : 'online'; + var approvalPrompt = options.forceApprovalPrompt ? 'force' : 'auto'; var loginUrl = 'https://accounts.google.com/o/oauth2/auth' + @@ -38,7 +39,8 @@ Google.requestCredential = function (options, credentialRequestCompleteCallback) '&scope=' + flatScope + '&redirect_uri=' + Meteor.absoluteUrl('_oauth/google?close') + '&state=' + credentialToken + - '&access_type=' + accessType; + '&access_type=' + accessType + + '&approval_prompt=' + approvalPrompt; Oauth.initiateLogin(credentialToken, loginUrl, credentialRequestCompleteCallback); }; From 732482b5ea204885d21dc78d27098ffa5ef09f6f Mon Sep 17 00:00:00 2001 From: Zoltan Olah Date: Tue, 16 Jul 2013 10:56:08 -0700 Subject: [PATCH 053/175] Documentation for forceApprovalPrompt to meteor_loginwithexternalservice. --- docs/client/api.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/client/api.js b/docs/client/api.js index e14de34dab..a85dafa7eb 100644 --- a/docs/client/api.js +++ b/docs/client/api.js @@ -1066,6 +1066,11 @@ Template.api.loginWithExternalService = { name: "requestOfflineToken", type: "Boolean", descr: "If true, asks the user for permission to act on their behalf when offline. This stores an additional offline token in the `services` field of the user document. Currently only supported with Google." + }, + { + name: "forceApprovalPrompt", + type: "Boolean", + descr: "If true, forces the user to approve the app's permissions, even if previously approved. Currently only supported with Google." } ] }; From 6e86bb40962150e3ca347c04a01dffddc8312a89 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Tue, 23 Jul 2013 22:58:01 -0700 Subject: [PATCH 054/175] Fix submit event on old IE. Fixes #1191 Not tested, but it looks right from code inspection and looking at the history. --- packages/universal-events/events-ie.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/universal-events/events-ie.js b/packages/universal-events/events-ie.js index cb8d4503ed..d785c9e697 100644 --- a/packages/universal-events/events-ie.js +++ b/packages/universal-events/events-ie.js @@ -89,7 +89,7 @@ _.extend(UniversalEventListener._impl.ie.prototype, { props = ['onpropertychange']; props.push('oncellchange'); } else if (prop === 'onsubmit') - props.push(node, 'ondatasetcomplete'); + props.push('ondatasetcomplete'); for(var i = 0; i < props.length; i++) node[props[i]] = this.curriedHandler; From e471549677e2c5a7afd18b951a1fa9085d44a78b Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 24 Jul 2013 02:11:33 -0700 Subject: [PATCH 055/175] Refactor argument parsing and 'help' display. argumentParser is now required, and it takes in an optimist object instead of needing to create one. The name of the command is automatically removed from argv._ between argumentParser and func, instead of having to deal with it in func. We no longer are able to cleverly show the "-f" in the help for logs -f only if talking to Galaxy. The logic there is too complex. (The motivation for this commit is that "meteor help logs" and "meteor logs --help" were both broken, in different ways.) --- tools/meteor.js | 632 ++++++++++++++++++++++-------------------------- 1 file changed, 285 insertions(+), 347 deletions(-) diff --git a/tools/meteor.js b/tools/meteor.js index ff76d26379..93ddf5e6f9 100644 --- a/tools/meteor.js +++ b/tools/meteor.js @@ -187,20 +187,20 @@ Fiber(function () { // ssh-identity argument is used to set it up. // 3. Runs the command, and kills the tunnel, if any, when it finishes. var galaxyCommand = function (cmd) { - return function (argv) { - if (argv._[1]) { - argv._[1] = qualifySitename(argv._[1]); - var tunnel = prepareForGalaxy(argv._[1], context, argv["ssh-identity"]); + return function (argv, showUsage) { + if (argv._[0]) { + argv._[0] = qualifySitename(argv._[0]); + var tunnel = prepareForGalaxy(argv._[0], context, argv["ssh-identity"]); var result; try { - result = cmd(argv); + result = cmd(argv, showUsage); } finally { if (tunnel) killTunnel(tunnel); } return result; } else { - return cmd(argv); + return cmd(argv, showUsage); } }; }; @@ -331,12 +331,26 @@ Fiber(function () { process.exit(1); }; - var runCommand = function (cmd, argv) { - var cmdRunner = findCommand(cmd); - if (cmdRunner.argumentParser) - cmdRunner.func(cmdRunner.argumentParser(argv)); - else - cmdRunner.func(argv); + var runCommand = function (cmd, showHelp) { + var cmdRunner = findCommand(cmd || 'run'); + // Reparse args. + var opt = require('optimist')(process.argv.slice(2)); + cmdRunner.argumentParser(opt); + var showUsage = function () { + process.stdout.write(opt.help()); + process.exit(1); + }; + if (showHelp) { + showUsage(); + } else { + // Remove the command name from argv._. Note that argv is a getter, so we + // actually have to save it into a new variable if we want to mutate its + // internals. + var argv = opt.argv; + if (cmd && cmd === argv._[0]) + argv._.shift(); + cmdRunner.func(argv, showUsage); + } }; // XXX when the pass unexpected argument or unrecognized flags, print @@ -345,42 +359,33 @@ Fiber(function () { Commands.push({ name: "run", help: "[default] Run this project in local development mode", - argumentParser: function (argv) { + argumentParser: function (opt) { // reparse args - // This help logic should probably move to run.js eventually - var opt = require('optimist') - .alias('port', 'p').default('port', 3000) - .describe('port', 'Port to listen on. NOTE: Also uses port N+1 and N+2.') - .boolean('production') - .describe('production', 'Run in production mode. Minify and bundle CSS and JS files.') - .describe('settings', 'Set optional data for Meteor.settings on the server') - .describe('release', 'Specify the release of Meteor to use') - .describe('program', 'The program in the app to run (Advanced)') - // #Once - // With --once, meteor does not re-run the project if it crashes and - // does not monitor for file changes. Intentionally undocumented: - // intended for automated testing (eg, cli-test.sh), not end-user - // use. - .boolean('once') - .usage( - "Usage: meteor run [options]\n" + - "\n" + - "Searches upward from the current directory for the root directory of a\n" + - "Meteor project, then runs that project in local development\n" + - "mode. You can use the application by pointing your web browser at\n" + - "localhost:3000. No internet connection is required.\n" + - "\n" + - "Whenever you change any of the application's source files, the changes\n" + - "are automatically detected and applied to the running application.\n" + - "\n" + - "The application's database persists between runs. It's stored under\n" + - "the .meteor directory in the root of the project.\n"); - - if (argv.help) { - process.stdout.write(opt.help()); - process.exit(1); - } - return opt.argv; + opt.alias('port', 'p').default('port', 3000) + .describe('port', 'Port to listen on. NOTE: Also uses port N+1 and N+2.') + .boolean('production') + .describe('production', 'Run in production mode. Minify and bundle CSS and JS files.') + .describe('settings', 'Set optional data for Meteor.settings on the server') + .describe('release', 'Specify the release of Meteor to use') + .describe('program', 'The program in the app to run (Advanced)') + // #Once + // With --once, meteor does not re-run the project if it crashes and does + // not monitor for file changes. Intentionally undocumented: intended for + // automated testing (eg, cli-test.sh), not end-user use. + .boolean('once') + .usage( + "Usage: meteor run [options]\n" + + "\n" + + "Searches upward from the current directory for the root directory of a\n" + + "Meteor project, then runs that project in local development\n" + + "mode. You can use the application by pointing your web browser at\n" + + "localhost:3000. No internet connection is required.\n" + + "\n" + + "Whenever you change any of the application's source files, the changes\n" + + "are automatically detected and applied to the running application.\n" + + "\n" + + "The application's database persists between runs. It's stored under\n" + + "the .meteor directory in the root of the project.\n"); }, func: function (argv) { requireDirInApp("run"); @@ -400,8 +405,14 @@ Fiber(function () { help: "Interact with your galaxy server", // Remove this once Galaxy support is official. hidden: true, + argumentParser: function (opt) { + opt.usage( + "Usage: meteor galaxy configure \n" + + "\n" + + "Allows you to interact with a Galaxy server.\n"); + }, func: function (argv) { - var cmd = argv._.splice(0, 1)[0]; + var cmd = argv._.shift(); switch (cmd) { case "configure": // We don't use galaxyCommand here because we want the tunnel to stay @@ -425,58 +436,33 @@ Fiber(function () { } }); - Commands.push({ - name: "help", - func: function (argv) { - if (!argv._.length || argv.help) - usage(); - var cmd = argv._.splice(0,1)[0]; - argv.help = true; - runCommand(cmd, argv); - } - }); - Commands.push({ name: "create", help: "Create a new project", - argumentParser: function (argv) { - // reparse args - var opt = require('optimist') - .describe('example', 'Example template to use.') - .boolean('list') - .describe('list', 'Show list of available examples.') - .usage( - "Usage: meteor create [--release ] \n" + - " meteor create [--release ] --example []\n" + - " meteor create --list\n" + - "\n" + - "Make a subdirectory named and create a new Meteor project\n" + - "there. You can also pass an absolute or relative path.\n" + - "\n" + - "The project will use the release of Meteor specified with the --release\n" + - "option, or the latest available version if the option is not specified.\n" + - "\n" + - "You can pass --example to start off with a copy of one of the Meteor\n" + - "sample applications. Use --list to see the available examples."); - - var new_argv = opt.argv; - + argumentParser: function (opt) { + opt.describe('example', 'Example template to use.') + .boolean('list') + .describe('list', 'Show list of available examples.') + .usage( + "Usage: meteor create [--release ] \n" + + " meteor create [--release ] --example []\n" + + " meteor create --list\n" + + "\n" + + "Make a subdirectory named and create a new Meteor project\n" + + "there. You can also pass an absolute or relative path.\n" + + "\n" + + "The project will use the release of Meteor specified with the --release\n" + + "option, or the latest available version if the option is not specified.\n" + + "\n" + + "You can pass --example to start off with a copy of one of the Meteor\n" + + "sample applications. Use --list to see the available examples."); + }, + func: function (argv, showUsage) { var appPath; if (argv._.length === 1) appPath = argv._[0]; else if (argv._.length === 0 && argv.example) appPath = argv.example; - if (appPath) { - new_argv.appPath = appPath; - } else if (argv.help) { - process.stdout.write(opt.help()); - process.exit(1); - } - - return new_argv; - }, - func: function (argv) { - var appPath = argv.appPath; var example_dir = path.join(__dirname, '..', 'examples'); var examples = _.reject(fs.readdirSync(example_dir), function (e) { @@ -493,6 +479,10 @@ Fiber(function () { process.exit(1); }; + if (!appPath) { + showUsage(); + } + if (fs.existsSync(appPath)) { process.stderr.write(appPath + ": Already exists\n"); process.exit(1); @@ -553,20 +543,14 @@ Fiber(function () { Commands.push({ name: "update", help: "Upgrade this project to the latest version of Meteor", - argumentParser: function (argv) { - // reparse args - var opt = require('optimist').usage( - "Usage: meteor update [--release ]\n" + - "\n" + - "Sets the version of Meteor to use with the current project. If a\n" + - "release is specified with --release, set the project to use that\n" + - "version. Otherwise download and use the latest release of Meteor."); - - if (argv.help) { - process.stdout.write(opt.help()); - process.exit(1); - } - return opt.argv; + argumentParser: function (opt) { + opt.boolean('dont-fetch-latest') + .usage( + "Usage: meteor update [--release ]\n" + + "\n" + + "Sets the version of Meteor to use with the current project. If a\n" + + "release is specified with --release, set the project to use that\n" + + "version. Otherwise download and use the latest release of Meteor."); }, func: function (argv) { // refuse to update if we're in a git checkout. @@ -671,16 +655,16 @@ Fiber(function () { Commands.push({ name: "add", help: "Add a package to this project", - func: function (argv) { - if (argv.help || !argv._.length) { - process.stdout.write( - "Usage: meteor add [package] [package..]\n" + + argumentParser: function (opt) { + opt.usage("Usage: meteor add [package] [package..]\n" + "\n" + "Adds packages to your Meteor project. You can add multiple\n" + "packages with one command. For a list of the available packages, see\n" + "'meteor list'.\n"); - process.exit(1); - } + }, + func: function (argv, showUsage) { + if (_.isEmpty(argv._)) + showUsage(); requireDirInApp('add'); var all = context.library.list(); @@ -706,16 +690,16 @@ Fiber(function () { Commands.push({ name: "remove", help: "Remove a package from this project", - func: function (argv) { - if (argv.help || !argv._.length) { - process.stdout.write( - "Usage: meteor remove [package] [package..]\n" + - "\n" + - "Removes a package previously added to your Meteor project. For a\n" + - "list of the packages that your application is currently using, see\n" + - "'meteor list --using'.\n"); - process.exit(1); - } + argumentParser: function (opt) { + opt.usage("Usage: meteor remove [package] [package..]\n" + + "\n" + + "Removes a package previously added to your Meteor project. For a\n" + + "list of the packages that your application is currently using, see\n" + + "'meteor list --using'.\n"); + }, + func: function (argv, showUsage) { + if (_.isEmpty(argv._)) + showUsage(); requireDirInApp('remove'); var using = {}; @@ -737,18 +721,16 @@ Fiber(function () { Commands.push({ name: "list", help: "List available packages", + argumentParser: function (opt) { + opt.boolean("using") + .usage("Usage: meteor list [--using]\n" + + "\n" + + "Without arguments, lists all available Meteor packages. To add one\n" + + "of these packages to your project, see 'meteor add'.\n" + + "\n" + + "With --using, list the packages that you have added to your project.\n"); + }, func: function (argv) { - if (argv.help) { - process.stdout.write( - "Usage: meteor list [--using]\n" + - "\n" + - "Without arguments, lists all available Meteor packages. To add one\n" + - "of these packages to your project, see 'meteor add'.\n" + - "\n" + - "With --using, list the packages that you have added to your project.\n"); - process.exit(1); - } - if (argv.using) { requireDirInApp('list --using'); var using = project.get_packages(context.appDir); @@ -783,27 +765,17 @@ Fiber(function () { Commands.push({ name: "bundle", help: "Pack this project up into a tarball", - func: function (argv) { - var usage = function () { - process.stdout.write( - "Usage: meteor bundle \n" + - "\n" + - "Package this project up for deployment. The output is a tarball that\n" + - "includes everything necessary to run the application. See README in\n" + - "the tarball for details.\n"); - process.exit(1); - }; - if (argv.help) - usage(); - - // re-parse the args to this command - // XXX clean up this whole file :) - argv = require("optimist") - .boolean('for-deploy').argv; - argv._.shift(); // pull off the word "bundle" - - if (argv._.length != 1) - usage(); + argumentParser: function (opt) { + opt.boolean('for-deploy') + .usage("Usage: meteor bundle \n" + + "\n" + + "Package this project up for deployment. The output is a tarball that\n" + + "includes everything necessary to run the application. See README in\n" + + "the tarball for details.\n"); + }, + func: function (argv, showUsage) { + if (argv._.length !== 1) + showUsage(); // XXX if they pass a file that doesn't end in .tar.gz or .tgz, // add the former for them @@ -846,47 +818,37 @@ Fiber(function () { Commands.push({ name: "mongo", help: "Connect to the Mongo database for the specified site", - argumentParser: function (argv) { - var opt = require('optimist') - .boolean('url') - .boolean('U') - .alias('url', 'U') - .describe('url', 'return a Mongo database URL') - .usage( - "Usage: meteor mongo [--url] [site]\n" + - "\n" + - "Opens a Mongo shell to view or manipulate collections.\n" + - "\n" + - "If site is specified, this is the hosted Mongo database for the deployed\n" + - "Meteor site.\n" + - "\n" + - "If no site is specified, this is the current project's local development\n" + - "database. In this case, the current working directory must be a\n" + - "Meteor project directory, and the Meteor application must already be\n" + - "running.\n" + - "\n" + - "Instead of opening a shell, specifying --url (-U) will return a URL\n" + - "suitable for an external program to connect to the database. For remote\n" + - "databases on deployed applications, the URL is valid for one minute.\n" - ); - - if (argv.help) { - process.stdout.write(opt.help()); - process.exit(1); - } - - if (opt.argv._.length !== 1 && opt.argv._.length !== 2) { - // usage - process.stdout.write(opt.help()); - process.exit(1); - } - - return opt.argv; + argumentParser: function (opt) { + opt.boolean('url') + .boolean('U') + .alias('url', 'U') + .describe('url', 'return a Mongo database URL') + .usage( + "Usage: meteor mongo [--url] [site]\n" + + "\n" + + "Opens a Mongo shell to view or manipulate collections.\n" + + "\n" + + "If site is specified, this is the hosted Mongo database for the deployed\n" + + "Meteor site.\n" + + "\n" + + "If no site is specified, this is the current project's local development\n" + + "database. In this case, the current working directory must be a\n" + + "Meteor project directory, and the Meteor application must already be\n" + + "running.\n" + + "\n" + + "Instead of opening a shell, specifying --url (-U) will return a URL\n" + + "suitable for an external program to connect to the database. For remote\n" + + "databases on deployed applications, the URL is valid for one minute.\n" + ); }, - func: galaxyCommand(function (argv) { + + func: galaxyCommand(function (argv, showUsage) { + if (argv._.length > 1) + showUsage(); + var mongoUrl; - if (argv._.length === 1) { + if (argv._.length === 0) { // localhost mode var fut = new Future(); find_mongo_port("mongo", function (mongod_port) { @@ -903,8 +865,8 @@ Fiber(function () { }); mongoUrl = fut.wait(); - } else if (argv._.length === 2) { - var site = argv._[1]; + } else { + var site = argv._[0]; // remote mode if (context.galaxy) { mongoUrl = deployGalaxy.temporaryMongoUrl({ @@ -927,66 +889,60 @@ Fiber(function () { Commands.push({ name: "deploy", help: "Deploy this project to Meteor", - argumentParser: function (argv) { - var opt = require('optimist') - .alias('password', 'P') - .boolean('password') - .boolean('P') - .describe('password', 'set a password for this deployment') - .alias('delete', 'D') - .boolean('delete') - .boolean('D') - .describe('delete', "permanently delete this deployment") - .boolean('debug') - .describe('debug', 'deploy in debug mode (don\'t minify, etc)') - .describe('settings', 'set optional data for Meteor.settings') - .alias('ssh-identity', 'i') - .describe('ssh-identity', 'Selects a file from which the identity (private key) is read. See ssh(1) for details.') - .describe('star', 'a star (tarball) to deploy instead of the current meteor app') - .boolean('admin') - // Shouldn't be public until the Galaxy release - //.describe('admin', 'Marks the application as an admin app, it will be available in Galaxy admin interface.') - .usage( - "Usage: meteor deploy [--password] [--settings settings.json] [--debug] [--delete]\n" + - "\n" + - "Deploys the project in your current directory to Meteor's servers.\n" + - "\n" + - "You can deploy to any available name under 'meteor.com'\n" + - "without any additional configuration, for example,\n" + - "'myapp.meteor.com'. If you deploy to a custom domain, such as\n" + - "'myapp.mydomain.com', then you'll also need to configure your domain's\n" + - "DNS records. See the Meteor docs for details.\n" + - "\n" + - "The --settings flag can be used to pass deploy-specific information to\n" + - "the application. It will be available at runtime in Meteor.settings, but only\n" + - "on the server. If the object contains a key named 'public', then\n" + - "Meteor.settings.public will also be available on the client. The argument\n" + - "is the name of a file containing the JSON data to use. The settings will\n" + - "persist across deployments until you again specify a settings file. To\n" + - "unset Meteor.settings, pass an empty settings file.\n" + - "\n" + - "The --delete flag permanently removes a deployed application, including\n" + - "all of its stored data.\n" + - "\n" + - "The --password flag sets an administrative password for the domain. Once\n" + - "set, any subsequent 'deploy', 'logs', or 'mongo' command will prompt for\n" + - "the password. You can change the password with a second 'deploy' command.\n" - // Shouldn't be public until the Galaxy release - //"\n" + - //"The --admin flag marks application as administrative to Galaxy interface.\n" + - //"Application's web-interface will be accessible from admin's panel only.\n" - ); - - var new_argv = opt.argv; - - if (argv.help || new_argv._.length != 2) { - process.stdout.write(opt.help()); - process.exit(1); - } - return new_argv; + argumentParser: function (opt) { + opt.alias('password', 'P') + .boolean('password') + .boolean('P') + .describe('password', 'set a password for this deployment') + .alias('delete', 'D') + .boolean('delete') + .boolean('D') + .describe('delete', "permanently delete this deployment") + .boolean('debug') + .describe('debug', 'deploy in debug mode (don\'t minify, etc)') + .describe('settings', 'set optional data for Meteor.settings') + .alias('ssh-identity', 'i') + .describe('ssh-identity', 'Selects a file from which the identity (private key) is read. See ssh(1) for details.') + .describe('star', 'a star (tarball) to deploy instead of the current meteor app') + .boolean('admin') + // Shouldn't be documented until the Galaxy release + //.describe('admin', 'Marks the application as an admin app, it will be available in Galaxy admin interface.') + .usage( + "Usage: meteor deploy [--password] [--settings settings.json] [--debug] [--delete]\n" + + "\n" + + "Deploys the project in your current directory to Meteor's servers.\n" + + "\n" + + "You can deploy to any available name under 'meteor.com'\n" + + "without any additional configuration, for example,\n" + + "'myapp.meteor.com'. If you deploy to a custom domain, such as\n" + + "'myapp.mydomain.com', then you'll also need to configure your domain's\n" + + "DNS records. See the Meteor docs for details.\n" + + "\n" + + "The --settings flag can be used to pass deploy-specific information to\n" + + "the application. It will be available at runtime in Meteor.settings, but only\n" + + "on the server. If the object contains a key named 'public', then\n" + + "Meteor.settings.public will also be available on the client. The argument\n" + + "is the name of a file containing the JSON data to use. The settings will\n" + + "persist across deployments until you again specify a settings file. To\n" + + "unset Meteor.settings, pass an empty settings file.\n" + + "\n" + + "The --delete flag permanently removes a deployed application, including\n" + + "all of its stored data.\n" + + "\n" + + "The --password flag sets an administrative password for the domain. Once\n" + + "set, any subsequent 'deploy', 'logs', or 'mongo' command will prompt for\n" + + "the password. You can change the password with a second 'deploy' command.\n" + // Shouldn't be documented until the Galaxy release + //"\n" + + //"The --admin flag marks application as administrative to Galaxy interface.\n" + + //"Application's web-interface will be accessible from admin's panel only.\n" + ); }, - func: galaxyCommand(function (argv) { - var site = argv._[1]; + func: galaxyCommand(function (argv, showUsage) { + if (argv._.length !== 1) + showUsage(); + + var site = argv._[0]; if (argv.delete) { if (context.galaxy) @@ -1043,36 +999,24 @@ Fiber(function () { Commands.push({ name: "logs", help: "Show logs for specified site", - argumentParser: function (argv) { - return require('optimist').boolean('f').argv; + argumentParser: function (opt) { + opt.boolean('f') + // XXX once Galaxy is released, document -f + .usage("Usage: meteor logs \n" + + "\n" + + "Retrieves the server logs for the requested site.\n"); }, - func: function (argv) { - var site = argv._[1]; + func: function (argv, showUsage) { + if (argv._.length !== 1) + showUsage(); + // We don't use galaxyCommand here because we want the tunnel to stay // open (galaxyCommand closes the tunnel as soon as the command finishes // running). The tunnel will be cleaned up when the process exits. - site = qualifySitename(site); + var site = qualifySitename(argv._[0]); var tunnel = prepareForGalaxy(site, context, argv["ssh-identity"]); var useGalaxy = !!context.galaxy; - if (argv.help || argv._.length !== 2) { - if (useGalaxy) { - process.stdout.write( - "Usage: meteor logs [-f] \n" + - "\n" + - "Retrieves the server logs for the requested site.\n" + - "\n" + - "Pass -f to see new logs as they come in.\n"); - } else { - process.stdout.write( - "Usage: meteor logs \n" + - "\n" + - "Retrieves the server logs for the requested site.\n"); - } - - process.exit(1); - } - if (useGalaxy) { var streaming = !!argv.f; deployGalaxy.logs({ @@ -1091,15 +1035,14 @@ Fiber(function () { Commands.push({ name: "reset", help: "Reset the project state. Erases the local database.", + argumentParser: function (opt) { + opt.usage("Usage: meteor reset\n" + + "\n" + + "Reset the current project to a fresh state. Removes all local\n" + + "data and kills any running meteor development servers.\n"); + }, func: function (argv) { - if (argv.help) { - process.stdout.write( - "Usage: meteor reset\n" + - "\n" + - "Reset the current project to a fresh state. Removes all local\n" + - "data and kills any running meteor development servers.\n"); - process.exit(1); - } else if (!_.isEmpty(argv._)) { + if (!_.isEmpty(argv._)) { process.stdout.write("meteor reset only affects the locally stored database.\n\n" + "To reset a deployed application use\nmeteor deploy --delete appname\n" + "followed by\nmeteor deploy appname\n"); @@ -1127,51 +1070,38 @@ Fiber(function () { Commands.push({ name: "test-packages", help: "Test one or more packages", - argumentParser: function (argv) { - // reparse args + argumentParser: function (opt) { // This help logic should probably move to run.js eventually - var opt = require('optimist') - .alias('port', 'p').default('port', 3000) - .describe('port', 'Port to listen on. NOTE: Also uses port N+1 and N+2.') - .describe('deploy', 'Optionally, specify a domain to deploy to, rather than running locally.') - .boolean('production') - .describe('production', 'Run in production mode. Minify and bundle CSS and JS files.') - .boolean('once') // See #Once - .describe('settings', 'Set optional data for Meteor.settings on the server') - .usage( - "Usage: meteor test-packages [--release ] [options] [package...]\n" + - "\n" + - "Runs unit tests for one or more packages. The results are shown in\n" + - "a browser dashboard that updates whenever a relevant source file is\n" + - "modified.\n" + - "\n" + - "Packages may be specified by name or by path. If a package argument\n" + - "contains a '/', it is loaded from a directory of that name; otherwise,\n" + - "the package name is resolved according to the usual package search\n" + - "algorithm ('packages' subdirectory of the current app, $PACKAGE_DIRS\n" + - "directories, and core packages in that order). You can test any number\n" + - "of packages simultaneously. If you don't specify any package names\n" + - "then all available packages will be tested.\n" + - "\n" + - "Open the test dashboard in your browser to run the tests and see the\n" + - "results. By default the URL is localhost:3000 but that can be changed\n" + - "with --port. Alternatively, you can deploy the tests onto the 'meteor\n" + - "deploy' server by using --deploy. This gives you a public URL that you\n" + - "can use in conjunction with a service like Browserling or BrowserStack\n" + - "to try the tests against many different browser versions."); - - if (argv.help) { - process.stdout.write(opt.help()); - process.exit(1); - } - return opt.argv; + opt .alias('port', 'p').default('port', 3000) + .describe('port', 'Port to listen on. NOTE: Also uses port N+1 and N+2.') + .describe('deploy', 'Optionally, specify a domain to deploy to, rather than running locally.') + .boolean('production') + .describe('production', 'Run in production mode. Minify and bundle CSS and JS files.') + .boolean('once') // See #Once + .describe('settings', 'Set optional data for Meteor.settings on the server') + .usage( + "Usage: meteor test-packages [--release ] [options] [package...]\n" + + "\n" + + "Runs unit tests for one or more packages. The results are shown in\n" + + "a browser dashboard that updates whenever a relevant source file is\n" + + "modified.\n" + + "\n" + + "Packages may be specified by name or by path. If a package argument\n" + + "contains a '/', it is loaded from a directory of that name; otherwise,\n" + + "the package name is resolved according to the usual package search\n" + + "algorithm ('packages' subdirectory of the current app, $PACKAGE_DIRS\n" + + "directories, and core packages in that order). You can test any number\n" + + "of packages simultaneously. If you don't specify any package names\n" + + "then all available packages will be tested.\n" + + "\n" + + "Open the test dashboard in your browser to run the tests and see the\n" + + "results. By default the URL is localhost:3000 but that can be changed\n" + + "with --port. Alternatively, you can deploy the tests onto the 'meteor\n" + + "deploy' server by using --deploy. This gives you a public URL that you\n" + + "can use in conjunction with a service like Browserling or BrowserStack\n" + + "to try the tests against many different browser versions."); }, func: function (argv) { - // remove 'test-packages'. - // XXX we need to fix up this argv stuff once and for all to provide a - // real interface to commands that isn't terrible. - argv._.shift(); - var testPackages; if (_.isEmpty(argv._)) { // XXX The call to list() here is unfortunate, because list() @@ -1241,21 +1171,21 @@ Fiber(function () { name: "rebuild-all", help: "Rebuild all packages", hidden: true, - func: function (argv) { - if (argv.help || argv._.length !== 0) { - process.stdout.write( -"Usage: meteor rebuild-all\n" + -"\n" + -"Rebuild all source packages in the library. This includes packages found\n" + -"through the PACKAGE_DIRS environment variable, local packages in the \n" + -"current application, and packages in the warehouse (but only those in the\n" + -"currently effective Meteor release.) It doesn't include any packages for\n" + -"which we don't have the source.\n" + -"\n" + -"You should never need to use this command. It is intended for use while\n" + -"debugging the Meteor packaging tools themselves.\n"); - process.exit(1); - } + argumentParser: function (opt) { + opt.usage("Usage: meteor rebuild-all\n" + + "\n" + + "Rebuild all source packages in the library. This includes packages found\n" + + "through the PACKAGE_DIRS environment variable, local packages in the \n" + + "current application, and packages in the warehouse (but only those in the\n" + + "currently effective Meteor release.) It doesn't include any packages for\n" + + "which we don't have the source.\n" + + "\n" + + "You should never need to use this command. It is intended for use while\n" + + "debugging the Meteor packaging tools themselves.\n"); + }, + func: function (argv, showUsage) { + if (argv._.length !== 0) + showUsage(); if (context.appDir) { // The library doesn't know about other programs in your app. Let's blow @@ -1292,10 +1222,15 @@ Fiber(function () { name: "run-command", help: "Build and run a command-line tool", hidden: true, + argumentParser: function (opt) { + // This command does things manually. See below. + }, func: function (argv) { // At this point options such as --help have already been parsed // out.. that's no good. We'll have to go back tho the original // process.argv and parse it ourselves. + // XXX this is wrong, what if you did something like + // "meteor --release foo run-command ..." argv = process.argv.slice(3); if (! argv.length || argv[0] === "--help") { process.stdout.write( @@ -1470,9 +1405,9 @@ Fiber(function () { return; } - if (argv.help) { - argv._.splice(0, 0, "help"); - delete argv.help; + if (argv._[0] === "help") { + argv._.shift(); + argv.help = true; } if (argv['built-by']) { @@ -1490,14 +1425,17 @@ Fiber(function () { return; } - var cmd = 'run'; + var cmd = null; if (argv._.length) - cmd = argv._.splice(0,1)[0]; + cmd = argv._[0]; if (PROFILE_REQUIRE) require('./profile-require.js').printReport(); - runCommand(cmd, argv); + if (argv.help && (!cmd || cmd === "help")) + usage(); + + runCommand(cmd, argv.help); }; main(); From 1ee45c4318d7cd5ea1820fa027f8ec842a75bf6c Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Wed, 24 Jul 2013 13:24:35 -0700 Subject: [PATCH 056/175] Strip trailing dots after adding .meteor.com to appnames. This is a temporary fix while we sort out the difference between appnames and sitenames. --- tools/meteor.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/meteor.js b/tools/meteor.js index 93ddf5e6f9..c906bbf32b 100644 --- a/tools/meteor.js +++ b/tools/meteor.js @@ -168,6 +168,8 @@ Fiber(function () { // some kind. if (site.indexOf(".") === -1) site = site + ".meteor.com"; + while (site[site.length - 1] === ".") + site = site.substring(0, site.length - 1); return site; }; From b68ce6604815a1f8a4b39d53cf299d43619f3212 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Wed, 24 Jul 2013 20:09:31 -0700 Subject: [PATCH 057/175] Patch our local sockjs to use a cachebusting URL for /sockjs/info. This has been submitted upstream too: https://github.com/sockjs/sockjs-client/pull/129 --- packages/livedata/sockjs-0.3.4.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/livedata/sockjs-0.3.4.js b/packages/livedata/sockjs-0.3.4.js index 49ed31445c..a19b0d29d1 100644 --- a/packages/livedata/sockjs-0.3.4.js +++ b/packages/livedata/sockjs-0.3.4.js @@ -1970,7 +1970,17 @@ InfoReceiver.prototype = new EventEmitter(['finish']); InfoReceiver.prototype.doXhr = function(base_url, AjaxObject) { var that = this; var t0 = (new Date()).getTime(); - var xo = new AjaxObject('GET', base_url + '/info'); + +// + // https://github.com/sockjs/sockjs-client/pull/129 + // var xo = new AjaxObject('GET', base_url + '/info'); + + var xo = new AjaxObject( + // add cachebusting parameter to url to work around a chrome bug: + // https://code.google.com/p/chromium/issues/detail?id=263981 + // or misbehaving proxies. + 'GET', base_url + '/info?cb=' + utils.random_string(10)) +// var tref = utils.delay(8000, function(){xo.ontimeout();}); From d1c481f71495b302e58c33bb24fb26903bbb18f2 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 25 Jul 2013 16:29:10 -0700 Subject: [PATCH 058/175] Address EPIPE (etc) crashes from the runner http-proxy during hot code reload. A better fix involves improving http-proxy itself but they're in the middle of a giant Node 0.10 compatibility rewrite. This addresses the bug reported by Tim Haines in a hacky but useful way. --- tools/run.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tools/run.js b/tools/run.js index 12cd5f86e5..79ea43b056 100644 --- a/tools/run.js +++ b/tools/run.js @@ -405,6 +405,29 @@ exports.run = function (context, options) { ("mongodb://127.0.0.1:" + mongoPort + "/meteor"); var firstRun = true; + // node-http-proxy doesn't properly handle errors if it has a problem writing + // to the proxy target. While we try to not proxy requests when we don't think + // the target is listening, there are race conditions here, and in any case + // those attempts don't take effect for pre-existing websocket connections. + // Error handling in node-http-proxy is really convoluted and will change with + // their ongoing Node 0.10.x compatible rewrite, so rather than trying to + // debug and send pull request now, we'll wait for them to finish their + // rewrite. In the meantime, ignore two common exceptions that we sometimes + // see instead of crashing. + // + // See https://github.com/meteor/meteor/issues/513 + // + // That bug is about "meteor deploy"s use of http-proxy, but it also affects + // our use here; see + // https://groups.google.com/d/msg/meteor-core/JgbnfKEa5lA/FJHZtJftfSsJ + // + // XXX remove this once we've upgraded and fixed http-proxy + process.on('uncaughtException', function (e) { + if (e && (e.errno === 'EPIPE' || e.message === "This socket is closed.")) + return; + throw e; + }); + var serverHandle; var watcher; From fbcf4650686cd1a5564de1b54232c7079c1e7469 Mon Sep 17 00:00:00 2001 From: Zoltan Olah Date: Mon, 10 Jun 2013 17:10:29 -0700 Subject: [PATCH 059/175] Meteor.disconnect() initial implementation. --- packages/livedata/client_convenience.js | 3 ++- packages/livedata/livedata_connection.js | 5 +++++ packages/livedata/stream_client_common.js | 14 ++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/livedata/client_convenience.js b/packages/livedata/client_convenience.js index b049d630c4..fabb6ecfd1 100644 --- a/packages/livedata/client_convenience.js +++ b/packages/livedata/client_convenience.js @@ -17,7 +17,8 @@ if (Meteor.isClient) { // Proxy the public methods of Meteor.default_connection so they can // be called directly on Meteor. - _.each(['subscribe', 'methods', 'call', 'apply', 'status', 'reconnect'], + _.each(['subscribe', 'methods', 'call', 'apply', 'status', 'reconnect', + 'disconnect'], function (name) { Meteor[name] = _.bind(Meteor.default_connection[name], Meteor.default_connection); diff --git a/packages/livedata/livedata_connection.js b/packages/livedata/livedata_connection.js index 32779e194e..0fa19ebd05 100644 --- a/packages/livedata/livedata_connection.js +++ b/packages/livedata/livedata_connection.js @@ -822,6 +822,11 @@ _.extend(Meteor._LivedataConnection.prototype, { return self._stream.reconnect.apply(self._stream, arguments); }, + disconnect: function (/*passthrough args*/) { + var self = this; + return self._stream.disconnect.apply(self._stream, arguments); + }, + close: function () { var self = this; return self._stream.forceDisconnect(); diff --git a/packages/livedata/stream_client_common.js b/packages/livedata/stream_client_common.js index 2a387dc2b3..7fe2ad16cb 100644 --- a/packages/livedata/stream_client_common.js +++ b/packages/livedata/stream_client_common.js @@ -176,6 +176,20 @@ _.extend(Meteor._DdpClientStream.prototype, { self.statusChanged(); }, + disconnect: function () { + var self = this; + self._cleanup(); + if (self.retryTimer) { + clearTimeout(self.retryTimer); + self.retryTimer = null; + } + self.currentStatus = { + status: "offline", + connected: false, + retryCount: 0 + }; + self.statusChanged(); + }, _lostConnection: function () { var self = this; From f6c6f891c5f129a45dd4109932ae40a892a51a03 Mon Sep 17 00:00:00 2001 From: Zoltan Olah Date: Tue, 11 Jun 2013 15:38:35 -0700 Subject: [PATCH 060/175] Ensure we don't reconnect when the browser goes 'online' --- packages/livedata/stream_client_common.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/livedata/stream_client_common.js b/packages/livedata/stream_client_common.js index 7fe2ad16cb..d1f011a886 100644 --- a/packages/livedata/stream_client_common.js +++ b/packages/livedata/stream_client_common.js @@ -217,7 +217,9 @@ _.extend(Meteor._DdpClientStream.prototype, { // fired when we detect that we've gone online. try to reconnect // immediately. _online: function () { - this.reconnect(); + // if we've requested to be offline by disconnecting, don't reconnect. + if (self.currentStatus.status != "offline") + this.reconnect(); }, _retryLater: function () { From b434d19bd8cf600a7fd15108b810e0932a439805 Mon Sep 17 00:00:00 2001 From: Zoltan Olah Date: Wed, 12 Jun 2013 14:43:38 -0700 Subject: [PATCH 061/175] Added basic test for disconnect() --- packages/livedata/stream_tests.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/livedata/stream_tests.js b/packages/livedata/stream_tests.js index 880f6e9c67..ddd355b6ae 100644 --- a/packages/livedata/stream_tests.js +++ b/packages/livedata/stream_tests.js @@ -32,6 +32,26 @@ testAsyncMulti("stream - reconnect", [ } ]); +testAsyncMulti("stream - disconnect basic", [ + function (test, expect) { + var stream = new Meteor._DdpClientStream("/"); + + var callback = _.once(expect(function() { + test.equal(stream.status().status, "connected"); + + stream.disconnect(); + test.equal(stream.status().status, "offline"); + + stream.reconnect(); + test.equal(stream.status().status, "connecting"); + })); + + if (stream.status().status !== "connected") + stream.on('reset', callback); + else + callback(); + } +]); Tinytest.add("stream - sockjs urls are computed correctly", function(test) { var testHasSockjsUrl = function(raw, expectedSockjsUrl) { From bf02edaae7c8b3dc56ca0c7a252b61db2521d816 Mon Sep 17 00:00:00 2001 From: Zoltan Olah Date: Thu, 13 Jun 2013 13:10:25 -0700 Subject: [PATCH 062/175] Ooops, pythonism - thanks tests! --- packages/livedata/stream_client_common.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/livedata/stream_client_common.js b/packages/livedata/stream_client_common.js index d1f011a886..c6c79925ae 100644 --- a/packages/livedata/stream_client_common.js +++ b/packages/livedata/stream_client_common.js @@ -218,7 +218,7 @@ _.extend(Meteor._DdpClientStream.prototype, { // immediately. _online: function () { // if we've requested to be offline by disconnecting, don't reconnect. - if (self.currentStatus.status != "offline") + if (this.currentStatus.status != "offline") this.reconnect(); }, From af7e208bd2d6fb2c0aa05dc2f13d200f89fd9761 Mon Sep 17 00:00:00 2001 From: Zoltan Olah Date: Thu, 13 Jun 2013 13:10:53 -0700 Subject: [PATCH 063/175] Better tests for disconnect(). --- packages/livedata/stream_tests.js | 58 ++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/packages/livedata/stream_tests.js b/packages/livedata/stream_tests.js index ddd355b6ae..6cb6aefc76 100644 --- a/packages/livedata/stream_tests.js +++ b/packages/livedata/stream_tests.js @@ -32,24 +32,58 @@ testAsyncMulti("stream - reconnect", [ } ]); -testAsyncMulti("stream - disconnect basic", [ +// Disconnecting and reconnecting transitions through the correct statuses. +testAsyncMulti("stream - basic disconnect", [ function (test, expect) { + var history = []; var stream = new Meteor._DdpClientStream("/"); + var onTestPass = expect(_.identity); - var callback = _.once(expect(function() { - test.equal(stream.status().status, "connected"); + Deps.autorun(function() { + var status = stream.status(); - stream.disconnect(); - test.equal(stream.status().status, "offline"); + if (_.last(history) != status.status) { + history.push(status.status); - stream.reconnect(); - test.equal(stream.status().status, "connecting"); - })); + if (_.isEqual(history, ["connecting", "connected"])) + stream.disconnect(); - if (stream.status().status !== "connected") - stream.on('reset', callback); - else - callback(); + if (_.isEqual(history, ["connecting", "connected", "offline"])) + stream.reconnect(); + + if (_.isEqual(history, ["connecting", "connected", "offline", + "connecting", "connected"])) { + stream.disconnect(); + onTestPass(); + } + } + }); + } +]); + +// Remain offline if the online event is received while offline. +testAsyncMulti("stream - disconnect remains offline", [ + function (test, expect) { + var history = []; + var stream = new Meteor._DdpClientStream("/"); + var onTestComplete = expect(_.identity); + + Deps.autorun(function() { + var status = stream.status(); + + if (_.last(history) != status.status) { + history.push(status.status); + + if (_.isEqual(history, ["connecting", "connected"])) + stream.disconnect(); + + if (_.isEqual(history, ["connecting", "connected", "offline"])) { + stream._online(); + test.isTrue(status.status == "offline"); + onTestComplete(); + } + } + }); } ]); From 853bdb4df21d2de19fde10fb35e91efd2104f152 Mon Sep 17 00:00:00 2001 From: Zoltan Olah Date: Wed, 24 Jul 2013 09:32:45 -0700 Subject: [PATCH 064/175] Documentation for Meteor.disconnect(). --- docs/client/api.html | 8 ++++++-- docs/client/api.js | 8 ++++++++ docs/client/docs.js | 1 + 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index d2c54fa776..40e07082b1 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -422,8 +422,8 @@ the server. The return value is an object with the following fields: values are `connected` (the connection is up and running), `connecting` (disconnected and trying to open a new connection), `failed` (permanently failed to connect; e.g., the client - and server support different versions of DDP) and `waiting` (failed - to connect and waiting to try to reconnect). + and server support different versions of DDP), `waiting` (failed + to connect and waiting to try to reconnect) and `offline` (user has disconnected the connection). {{/dtdd}} {{#dtdd name="retryCount" type="Number"}} @@ -450,6 +450,8 @@ to get realtime updates. {{> api_box reconnect}} +{{> api_box disconnect}} + {{> api_box connect}} To call methods on another Meteor application or subscribe to its data @@ -472,6 +474,8 @@ sets, call `Meteor.connect` with the URL of the application. [Meteor.status](#meteor_status). * `reconnect` - See [Meteor.reconnect](#meteor_reconnect). +* `disconnect` - + See [Meteor.disconnect](#meteor_disconnect). * `onReconnect` - Set this to a function to be called as the first step of reconnecting. This function can call methods which will be executed before any other outstanding methods. For example, this can be used to re-establish diff --git a/docs/client/api.js b/docs/client/api.js index a85dafa7eb..a07d12fd14 100644 --- a/docs/client/api.js +++ b/docs/client/api.js @@ -447,6 +447,14 @@ Template.api.reconnect = { "This method does nothing if the client is already connected."] }; +Template.api.disconnect = { + id: "meteor_disconnect", + name: "Meteor.disconnect()", + locus: "Client", + descr: [ + "Disconnect the connection to the server."] +}; + Template.api.connect = { id: "meteor_connect", name: "Meteor.connect(url)", diff --git a/docs/client/docs.js b/docs/client/docs.js index 60fe71184c..d78b9aef03 100644 --- a/docs/client/docs.js +++ b/docs/client/docs.js @@ -139,6 +139,7 @@ var toc = [ {name: "Server connections", id: "connections"}, [ "Meteor.status", "Meteor.reconnect", + "Meteor.disconnect", "Meteor.connect" ], From 475af49b27c93273142268dae7c772206ad4c4c2 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Wed, 24 Jul 2013 21:35:04 -0700 Subject: [PATCH 065/175] doc tweaks. --- docs/client/api.html | 11 +++++++++++ docs/client/api.js | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/client/api.html b/docs/client/api.html index 40e07082b1..4de42330b9 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -452,6 +452,17 @@ to get realtime updates. {{> api_box disconnect}} +Call this method to temporarily disconnect from the server and stop all +live data updates. While the client is disconnected it will not receive +updates to collections, method calls will be queued until the +connection is reestablished, and hot code push will be disabled. + +Call [Meteor.reconnect](#meteor_reconnect) to reestablish the connection +and resume data transfer. + +This can be used to save battery on mobile devices when real time +updates are not required. + {{> api_box connect}} To call methods on another Meteor application or subscribe to its data diff --git a/docs/client/api.js b/docs/client/api.js index a07d12fd14..f5a6c0eb2a 100644 --- a/docs/client/api.js +++ b/docs/client/api.js @@ -452,7 +452,7 @@ Template.api.disconnect = { name: "Meteor.disconnect()", locus: "Client", descr: [ - "Disconnect the connection to the server."] + "Disconnect the client from the server."] }; Template.api.connect = { From da09ebf774ec575e91512bf02c55dafc3728a4e9 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Wed, 24 Jul 2013 21:37:30 -0700 Subject: [PATCH 066/175] Don't let people escape forced disconnection by called disconnect/reconnect. --- packages/livedata/stream_client_common.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/livedata/stream_client_common.js b/packages/livedata/stream_client_common.js index c6c79925ae..cbf21ee863 100644 --- a/packages/livedata/stream_client_common.js +++ b/packages/livedata/stream_client_common.js @@ -178,6 +178,10 @@ _.extend(Meteor._DdpClientStream.prototype, { disconnect: function () { var self = this; + + if (self._forcedToDisconnect) + return; + self._cleanup(); if (self.retryTimer) { clearTimeout(self.retryTimer); From 2e7c6f1bd03ecfdaa18b00ee71f36609e6a12bb9 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Thu, 25 Jul 2013 15:49:47 -0700 Subject: [PATCH 067/175] Don't need _.identity, bare expect() does the same thing. --- packages/livedata/stream_tests.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/livedata/stream_tests.js b/packages/livedata/stream_tests.js index 6cb6aefc76..e2e250a6a1 100644 --- a/packages/livedata/stream_tests.js +++ b/packages/livedata/stream_tests.js @@ -37,7 +37,7 @@ testAsyncMulti("stream - basic disconnect", [ function (test, expect) { var history = []; var stream = new Meteor._DdpClientStream("/"); - var onTestPass = expect(_.identity); + var onTestPass = expect(); Deps.autorun(function() { var status = stream.status(); @@ -66,7 +66,7 @@ testAsyncMulti("stream - disconnect remains offline", [ function (test, expect) { var history = []; var stream = new Meteor._DdpClientStream("/"); - var onTestComplete = expect(_.identity); + var onTestComplete = expect(); Deps.autorun(function() { var status = stream.status(); From f9c06d8116f646156471bc9e4f3bd31a75c92b14 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Thu, 25 Jul 2013 15:57:38 -0700 Subject: [PATCH 068/175] comments --- packages/livedata/stream_client_common.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/livedata/stream_client_common.js b/packages/livedata/stream_client_common.js index cbf21ee863..4e57ad325b 100644 --- a/packages/livedata/stream_client_common.js +++ b/packages/livedata/stream_client_common.js @@ -157,7 +157,12 @@ _.extend(Meteor._DdpClientStream.prototype, { self._retryNow(); }, - // Permanently disconnect a stream. + // Permanently disconnect a stream. Once a stream is forced to + // disconnect, it can never reconnect. This is for error cases such as + // ddp version mismatch, where trying again won't fix the problem. + // + // XXX this should probably be renamed and possibly unified with + // 'disconnect'. forceDisconnect: function (optionalErrorMessage) { var self = this; self._forcedToDisconnect = true; @@ -176,9 +181,14 @@ _.extend(Meteor._DdpClientStream.prototype, { self.statusChanged(); }, + // Temporarily take the stream offline. reconnect() later to go back + // online. This can be used to pause live updates, save battery on + // mobile devices, etc. disconnect: function () { var self = this; + // Failed is permanent. If we're failed, don't let people go back + // online by calling 'disconnect' then 'reconnect'. if (self._forcedToDisconnect) return; From 9098c94bf1e02e050c72f8268152729601c355cc Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Thu, 25 Jul 2013 16:09:17 -0700 Subject: [PATCH 069/175] refactor to unify forceDisconnect and disconnect --- packages/livedata/livedata_connection.js | 4 +- .../livedata/livedata_connection_tests.js | 2 +- packages/livedata/livedata_tests.js | 2 +- packages/livedata/stream_client_common.js | 45 +++++++------------ 4 files changed, 20 insertions(+), 33 deletions(-) diff --git a/packages/livedata/livedata_connection.js b/packages/livedata/livedata_connection.js index 0fa19ebd05..ffe41a679c 100644 --- a/packages/livedata/livedata_connection.js +++ b/packages/livedata/livedata_connection.js @@ -200,7 +200,7 @@ Meteor._LivedataConnection = function (url, options) { } else { var error = "Version negotiation failed; server requested version " + msg.version; - self._stream.forceDisconnect(error); + self._stream.disconnect({_permanent: true, _error: error}); options.onConnectionFailure(error); } } @@ -829,7 +829,7 @@ _.extend(Meteor._LivedataConnection.prototype, { close: function () { var self = this; - return self._stream.forceDisconnect(); + return self._stream.disconnect({_permanent: true}); }, /// diff --git a/packages/livedata/livedata_connection_tests.js b/packages/livedata/livedata_connection_tests.js index 4aa48723da..a8ee2c7405 100644 --- a/packages/livedata/livedata_connection_tests.js +++ b/packages/livedata/livedata_connection_tests.js @@ -1354,7 +1354,7 @@ Tinytest.addAsync("livedata connection - version negotiation requires renegotiat onConnectionFailure: function () { test.fail(); onComplete(); }, onConnected: function () { test.equal(connection._version, Meteor._SUPPORTED_DDP_VERSIONS[0]); - connection._stream.forceDisconnect(); + connection._stream.disconnect({_permanent: true}); onComplete(); } }); diff --git a/packages/livedata/livedata_tests.js b/packages/livedata/livedata_tests.js index ec754d6a23..14b612ce9f 100644 --- a/packages/livedata/livedata_tests.js +++ b/packages/livedata/livedata_tests.js @@ -572,7 +572,7 @@ if (Meteor.isClient) { // sub.stop does NOT call onError. test.isFalse(gotErrorFromStopper); test.equal(_.size(conn._subscriptions), 0); // white-box test - conn._stream.forceDisconnect(); + conn._stream.disconnect({_permanent: true}); } ];})()); diff --git a/packages/livedata/stream_client_common.js b/packages/livedata/stream_client_common.js index 4e57ad325b..25e5be4bd7 100644 --- a/packages/livedata/stream_client_common.js +++ b/packages/livedata/stream_client_common.js @@ -157,51 +157,38 @@ _.extend(Meteor._DdpClientStream.prototype, { self._retryNow(); }, - // Permanently disconnect a stream. Once a stream is forced to - // disconnect, it can never reconnect. This is for error cases such as - // ddp version mismatch, where trying again won't fix the problem. - // - // XXX this should probably be renamed and possibly unified with - // 'disconnect'. - forceDisconnect: function (optionalErrorMessage) { - var self = this; - self._forcedToDisconnect = true; - self._cleanup(); - if (self.retryTimer) { - clearTimeout(self.retryTimer); - self.retryTimer = null; - } - self.currentStatus = { - status: "failed", - connected: false, - retryCount: 0 - }; - if (optionalErrorMessage) - self.currentStatus.reason = optionalErrorMessage; - self.statusChanged(); - }, - - // Temporarily take the stream offline. reconnect() later to go back - // online. This can be used to pause live updates, save battery on - // mobile devices, etc. - disconnect: function () { + disconnect: function (options) { var self = this; + options = options || {}; // Failed is permanent. If we're failed, don't let people go back // online by calling 'disconnect' then 'reconnect'. if (self._forcedToDisconnect) return; + // If _permanent is set, permanently disconnect a stream. Once a stream + // is forced to disconnect, it can never reconnect. This is for + // error cases such as ddp version mismatch, where trying again + // won't fix the problem. + if (options._permanent) { + self._forcedToDisconnect = true; + } + self._cleanup(); if (self.retryTimer) { clearTimeout(self.retryTimer); self.retryTimer = null; } + self.currentStatus = { - status: "offline", + status: (options._permanent ? "failed" : "offline"), connected: false, retryCount: 0 }; + + if (options._permanent && options._error) + self.currentStatus.reason = options._error; + self.statusChanged(); }, From 8d66f18cbb44e6a0aa5fc21ec41be2147bd46ffa Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Thu, 25 Jul 2013 18:23:15 -0700 Subject: [PATCH 070/175] Allow 'meteor --release X run-command Y' --- tools/meteor.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tools/meteor.js b/tools/meteor.js index c906bbf32b..c76825dd60 100644 --- a/tools/meteor.js +++ b/tools/meteor.js @@ -1231,9 +1231,7 @@ Fiber(function () { // At this point options such as --help have already been parsed // out.. that's no good. We'll have to go back tho the original // process.argv and parse it ourselves. - // XXX this is wrong, what if you did something like - // "meteor --release foo run-command ..." - argv = process.argv.slice(3); + argv = argv._; if (! argv.length || argv[0] === "--help") { process.stdout.write( "Usage: meteor run-command [arguments..]\n" + From 556f6aa979db9c854c3359d6bb9228ca7c1d42c7 Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Thu, 25 Jul 2013 18:49:54 -0700 Subject: [PATCH 071/175] Allow "meteor --release X run-command Y --Z" --- tools/meteor.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/meteor.js b/tools/meteor.js index c76825dd60..0028698326 100644 --- a/tools/meteor.js +++ b/tools/meteor.js @@ -1231,7 +1231,8 @@ Fiber(function () { // At this point options such as --help have already been parsed // out.. that's no good. We'll have to go back tho the original // process.argv and parse it ourselves. - argv = argv._; + argv = process.argv; + argv = argv.slice(argv.indexOf("run-command") + 1); if (! argv.length || argv[0] === "--help") { process.stdout.write( "Usage: meteor run-command [arguments..]\n" + From 3d1c09794f7d87c136dac5ab33ab6cf7b4f9e049 Mon Sep 17 00:00:00 2001 From: Geoff Schmidt Date: Sat, 20 Jul 2013 01:41:05 -0700 Subject: [PATCH 072/175] Comprehensive namespace cleanup. --- packages/accounts-base/accounts_client.js | 18 +- packages/accounts-base/accounts_common.js | 2 + packages/accounts-base/accounts_server.js | 10 +- packages/accounts-base/localstorage_token.js | 3 +- packages/accounts-base/package.js | 3 + packages/accounts-facebook/facebook_client.js | 1 + packages/accounts-github/github_client.js | 1 + packages/accounts-google/google_client.js | 1 + packages/accounts-meetup/meetup_client.js | 1 + .../accounts-password/email_tests_setup.js | 8 +- packages/accounts-password/package.js | 4 +- packages/accounts-password/password_client.js | 11 +- packages/accounts-password/password_server.js | 18 +- packages/accounts-twitter/twitter_client.js | 1 + packages/accounts-weibo/weibo_client.js | 1 + packages/appcache/appcache-client.js | 4 +- .../audit_argument_checks.js | 2 +- packages/autopublish/autopublish.js | 2 +- packages/ctl-helper/ctl-helper.js | 2 +- packages/deps/deps.js | 3 + packages/ejson/base64.js | 11 +- packages/ejson/base64_test.js | 12 +- packages/ejson/ejson.js | 24 ++- packages/email/email.js | 42 ++++- packages/email/email_tests.js | 8 +- packages/http/httpcall_client.js | 14 +- packages/http/httpcall_common.js | 25 +-- packages/http/httpcall_server.js | 19 ++- packages/less/package.js | 1 + packages/livedata/client_convenience.js | 39 +++-- packages/livedata/crossbar.js | 7 +- packages/livedata/crossbar_tests.js | 10 +- packages/livedata/livedata_common.js | 56 ++----- packages/livedata/livedata_connection.js | 68 ++++---- .../livedata/livedata_connection_tests.js | 77 +++++---- packages/livedata/livedata_server.js | 128 ++++++++------ packages/livedata/livedata_tests.js | 21 ++- packages/livedata/package.js | 5 +- packages/livedata/server_convenience.js | 38 +++-- packages/livedata/session_view_tests.js | 2 +- packages/livedata/stream_client_common.js | 27 +-- packages/livedata/stream_client_nodejs.js | 6 +- packages/livedata/stream_client_sockjs.js | 7 +- packages/livedata/stream_server.js | 4 +- packages/livedata/stream_tests.js | 4 +- packages/livedata/writefence.js | 10 +- packages/localstorage/localstorage.js | 3 + packages/logging/package.js | 2 - packages/meteor/client_environment.js | 11 +- packages/{logging => meteor}/debug.js | 4 + packages/{logging => meteor}/debug_test.js | 0 packages/meteor/dynamics_browser.js | 2 + packages/meteor/dynamics_nodejs.js | 3 + packages/meteor/errors.js | 44 +++++ packages/meteor/fiber_helpers.js | 5 + packages/meteor/fiber_stubs_client.js | 4 + packages/meteor/helpers.js | 14 +- packages/meteor/package.js | 5 + packages/meteor/server_environment.js | 11 +- packages/meteor/setimmediate.js | 1 + .../{startup => meteor}/startup_client.js | 1 + .../{startup => meteor}/startup_server.js | 1 + packages/meteor/timers.js | 12 +- packages/meteor/url_common.js | 2 + packages/minimongo/minimongo.js | 13 +- packages/minimongo/selector.js | 2 +- packages/mongo-livedata/collection.js | 23 +-- .../mongo-livedata/local_collection_driver.js | 7 +- packages/mongo-livedata/mongo_driver.js | 72 ++++---- .../mongo-livedata/mongo_livedata_tests.js | 19 ++- .../mongo-livedata/observe_changes_tests.js | 3 +- packages/mongo-livedata/package.js | 5 +- .../remote_collection_driver.js | 13 +- packages/past/package.js | 1 + packages/past/past.js | 22 +++ packages/preserve-inputs/preserve-inputs.js | 3 +- packages/reactive-dict/reactive-dict.js | 7 +- packages/reload/reload.js | 14 +- packages/service-configuration/package.js | 1 + packages/session/package.js | 9 +- packages/session/session.js | 8 +- packages/spark/convenience.js | 2 + packages/spark/patch.js | 28 ++-- packages/spark/patch_tests.js | 4 +- packages/spark/spark.js | 156 +++++++++++------- packages/spark/spark_tests.js | 38 ++--- packages/spark/utils.js | 1 + packages/spiderable/spiderable.js | 2 +- packages/srp/biginteger.js | 4 +- packages/srp/sha256.js | 4 +- packages/srp/srp.js | 76 ++++----- packages/srp/srp_tests.js | 24 +-- packages/startup/package.js | 8 +- packages/stylus/package.js | 1 + packages/templating/deftemplate.js | 25 +-- packages/templating/plugin/html_scanner.js | 4 +- packages/templating/scanner_tests.js | 4 +- packages/templating/templating_tests.js | 2 +- packages/test-helpers/stub_stream.js | 9 +- packages/test-in-browser/driver.js | 10 +- packages/test-in-browser/package.js | 1 + packages/test-in-console/driver.js | 2 +- packages/tinytest/package.js | 1 + packages/tinytest/tinytest.js | 23 +-- packages/tinytest/tinytest_client.js | 10 +- packages/tinytest/tinytest_server.js | 2 +- packages/webapp/webapp_server.js | 6 +- tools/packages.js | 2 +- 108 files changed, 913 insertions(+), 644 deletions(-) rename packages/{logging => meteor}/debug.js (97%) rename packages/{logging => meteor}/debug_test.js (100%) rename packages/{startup => meteor}/startup_client.js (97%) rename packages/{startup => meteor}/startup_server.js (78%) diff --git a/packages/accounts-base/accounts_client.js b/packages/accounts-base/accounts_client.js index b6841af28f..589ec097b1 100644 --- a/packages/accounts-base/accounts_client.js +++ b/packages/accounts-base/accounts_client.js @@ -3,8 +3,9 @@ /// // This is reactive. +// @export Meteor.userId Meteor.userId = function () { - return Meteor.default_connection.userId(); + return Meteor.connection.userId(); }; var loggingIn = false; @@ -18,12 +19,14 @@ Accounts._setLoggingIn = function (x) { loggingInDeps.changed(); } }; +// @export Meteor.loggingIn Meteor.loggingIn = function () { loggingInDeps.depend(); return loggingIn; }; // This calls userId, which is reactive. +// @export Meteor.user Meteor.user = function () { var userId = Meteor.userId(); if (!userId) @@ -45,7 +48,7 @@ Meteor.user = function () { // - Updating the Meteor.loggingIn() reactive data source // - Calling the method in 'wait' mode // - On success, saving the resume token to localStorage -// - On success, calling Meteor.default_connection.setUserId() +// - On success, calling Meteor.connection.setUserId() // - Setting up an onReconnect handler which logs in with // the resume token // @@ -88,9 +91,9 @@ Accounts.callLoginMethod = function (options) { // will occur before the callback from the resume login call.) var onResultReceived = function (err, result) { if (err || !result || !result.token) { - Meteor.default_connection.onReconnect = null; + Meteor.connection.onReconnect = null; } else { - Meteor.default_connection.onReconnect = function() { + Meteor.connection.onReconnect = function() { reconnected = true; Accounts.callLoginMethod({ methodArguments: [{resume: result.token}], @@ -153,15 +156,16 @@ Accounts.callLoginMethod = function (options) { Accounts._makeClientLoggedOut = function() { Accounts._unstoreLoginToken(); - Meteor.default_connection.setUserId(null); - Meteor.default_connection.onReconnect = null; + Meteor.connection.setUserId(null); + Meteor.connection.onReconnect = null; }; Accounts._makeClientLoggedIn = function(userId, token) { Accounts._storeLoginToken(userId, token); - Meteor.default_connection.setUserId(userId); + Meteor.connection.setUserId(userId); }; +// @export Meteor.logout Meteor.logout = function (callback) { Meteor.apply('logout', [], {wait: true}, function(error, result) { if (error) { diff --git a/packages/accounts-base/accounts_common.js b/packages/accounts-base/accounts_common.js index f7a4ebd569..9eecc2e372 100644 --- a/packages/accounts-base/accounts_common.js +++ b/packages/accounts-base/accounts_common.js @@ -45,6 +45,8 @@ Accounts.config = function(options) { // Users table. Don't use the normal autopublish, since we want to hide // some fields. Code to autopublish this is in accounts_server.js. // XXX Allow users to configure this collection name. +// +// @export Meteor.users Meteor.users = new Meteor.Collection("users", {_preventAutopublish: true}); // There is an allow call in accounts_server that restricts this // collection. diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index 19df6a4de8..dde3ab301d 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -2,6 +2,7 @@ /// CURRENT USER /// +// @export Meteor.userId Meteor.userId = function () { // This function only works if called inside a method. In theory, it // could also be called from publish statements, since they also @@ -12,12 +13,13 @@ Meteor.userId = function () { // user expects. The way to make this work in a publish is to do // Meteor.find(this.userId()).observe and recompute when the user // record changes. - var currentInvocation = Meteor._CurrentInvocation.get(); + var currentInvocation = DDP._CurrentInvocation.get(); if (!currentInvocation) throw new Error("Meteor.userId can only be invoked in method calls. Use this.userId in publish functions."); return currentInvocation.userId; }; +// @export Meteor.user Meteor.user = function () { var userId = Meteor.userId(); if (!userId) @@ -324,7 +326,7 @@ Accounts.addAutopublishFields = function(opts) { Accounts._autopublishFields.otherUsers, opts.forOtherUsers); }; -Meteor.default_server.onAutopublish(function () { +Meteor.server.onAutopublish(function () { // ['profile', 'username'] -> {profile: 1, username: 1} var toFieldSelector = function(fields) { return _.object(_.map(fields, function(field) { @@ -332,7 +334,7 @@ Meteor.default_server.onAutopublish(function () { })); }; - Meteor.default_server.publish(null, function () { + Meteor.server.publish(null, function () { if (this.userId) { return Meteor.users.find( {_id: this.userId}, @@ -348,7 +350,7 @@ Meteor.default_server.onAutopublish(function () { // is changed (eg someone logging in). If this is a problem, we can // instead write a manual publish function which filters out fields // based on 'this.userId'. - Meteor.default_server.publish(null, function () { + Meteor.server.publish(null, function () { var selector; if (this.userId) selector = {_id: {$ne: this.userId}}; diff --git a/packages/accounts-base/localstorage_token.js b/packages/accounts-base/localstorage_token.js index 34ac30998e..81a945e23f 100644 --- a/packages/accounts-base/localstorage_token.js +++ b/packages/accounts-base/localstorage_token.js @@ -5,6 +5,7 @@ // Login with a Meteor access token. This is the only public function // here. +// @export Meteor.loginWithToken Meteor.loginWithToken = function (token, callback) { Accounts.callLoginMethod({ methodArguments: [{resume: token}], @@ -74,7 +75,7 @@ if (!Accounts._preventAutoLogin) { // On startup, optimistically present us as logged in while the // request is in flight. This reduces page flicker on startup. var userId = Accounts._storedUserId(); - userId && Meteor.default_connection.setUserId(userId); + userId && Meteor.connection.setUserId(userId); Meteor.loginWithToken(token, function (err) { if (err) { Meteor._debug("Error logging in with token: " + err); diff --git a/packages/accounts-base/package.js b/packages/accounts-base/package.js index d751cd0537..df58686558 100644 --- a/packages/accounts-base/package.js +++ b/packages/accounts-base/package.js @@ -11,6 +11,9 @@ Package.on_use(function (api) { api.use('random', ['client', 'server']); api.use('service-configuration', ['client', 'server']); + // needed for getting the currently logged-in user + api.use('livedata', ['client', 'server']); + // need this because of the Meteor.users collection but in the future // we'd probably want to abstract this away api.use('mongo-livedata', ['client', 'server']); diff --git a/packages/accounts-facebook/facebook_client.js b/packages/accounts-facebook/facebook_client.js index 5f29dbf896..94cf694d3c 100644 --- a/packages/accounts-facebook/facebook_client.js +++ b/packages/accounts-facebook/facebook_client.js @@ -1,3 +1,4 @@ +// @export Meteor.loginWithFacebook Meteor.loginWithFacebook = function(options, callback) { // support a callback without options if (! callback && typeof options === "function") { diff --git a/packages/accounts-github/github_client.js b/packages/accounts-github/github_client.js index a30db6a39b..5d7e25167e 100644 --- a/packages/accounts-github/github_client.js +++ b/packages/accounts-github/github_client.js @@ -1,3 +1,4 @@ +// @export Meteor.loginWithGithub Meteor.loginWithGithub = function(options, callback) { // support a callback without options if (! callback && typeof options === "function") { diff --git a/packages/accounts-google/google_client.js b/packages/accounts-google/google_client.js index 877c94acc0..f18f3827ce 100644 --- a/packages/accounts-google/google_client.js +++ b/packages/accounts-google/google_client.js @@ -1,3 +1,4 @@ +// @export Meteor.loginWithGoogle Meteor.loginWithGoogle = function(options, callback) { // support a callback without options if (! callback && typeof options === "function") { diff --git a/packages/accounts-meetup/meetup_client.js b/packages/accounts-meetup/meetup_client.js index cc5359e45a..2d1fa0fab6 100644 --- a/packages/accounts-meetup/meetup_client.js +++ b/packages/accounts-meetup/meetup_client.js @@ -1,3 +1,4 @@ +// @export Meteor.loginWithMeetup Meteor.loginWithMeetup = function(options, callback) { // support a callback without options if (! callback && typeof options === "function") { diff --git a/packages/accounts-password/email_tests_setup.js b/packages/accounts-password/email_tests_setup.js index 1745e3a909..bd8990a14b 100644 --- a/packages/accounts-password/email_tests_setup.js +++ b/packages/accounts-password/email_tests_setup.js @@ -3,20 +3,20 @@ // the string "intercept", storing them in an array that can then // be retrieved using the getInterceptedEmails method // -var oldEmailSend = Email.send; var interceptedEmails = {}; // (email address) -> (array of contents) -Email.send = function (options) { +Email._hookSend(function (options) { var to = options.to; if (to.indexOf('intercept') === -1) { - oldEmailSend(options); + return true; // go ahead and send } else { if (!interceptedEmails[to]) interceptedEmails[to] = []; interceptedEmails[to].push(options.text); + return false; // skip sending } -}; +}); Meteor.methods({ getInterceptedEmails: function (email) { diff --git a/packages/accounts-password/package.js b/packages/accounts-password/package.js index a8d2c7c16a..3829d5caa7 100644 --- a/packages/accounts-password/package.js +++ b/packages/accounts-password/package.js @@ -11,6 +11,7 @@ Package.on_use(function(api) { api.use('random', ['server']); api.use('check', ['server']); api.use('underscore'); + api.use('livedata', ['client', 'server']); api.add_files('email_templates.js', 'server'); api.add_files('password_server.js', 'server'); @@ -20,7 +21,8 @@ Package.on_use(function(api) { Package.on_test(function(api) { api.use(['accounts-password', 'tinytest', 'test-helpers', 'deps', - 'accounts-base', 'random', 'email', 'underscore', 'check']); + 'accounts-base', 'random', 'email', 'underscore', 'check', + 'livedata']); api.add_files('password_tests_setup.js', 'server'); api.add_files('password_tests.js', ['client', 'server']); api.add_files('email_tests_setup.js', 'server'); diff --git a/packages/accounts-password/password_client.js b/packages/accounts-password/password_client.js index 13612c596c..d9e63f130a 100644 --- a/packages/accounts-password/password_client.js +++ b/packages/accounts-password/password_client.js @@ -1,5 +1,6 @@ // Attempt to log in with a password. // +// @export Meteor.loginWithPassword // @param selector {String|Object} One of the following: // - {username: (username)} // - {email: (email)} @@ -8,7 +9,7 @@ // @param password {String} // @param callback {Function(error|undefined)} Meteor.loginWithPassword = function (selector, password, callback) { - var srp = new Meteor._srp.Client(password); + var srp = new SRP.Client(password); var request = srp.startExchange(); if (typeof selector === 'string') @@ -50,7 +51,7 @@ Accounts.createUser = function (options, callback) { if (!options.password) throw new Error("Must set options.password"); - var verifier = Meteor._srp.generateVerifier(options.password); + var verifier = SRP.generateVerifier(options.password); // strip old password, replacing with the verifier object delete options.password; options.srp = verifier; @@ -77,7 +78,7 @@ Accounts.changePassword = function (oldPassword, newPassword, callback) { return; } - var verifier = Meteor._srp.generateVerifier(newPassword); + var verifier = SRP.generateVerifier(newPassword); if (!oldPassword) { Meteor.apply('changePassword', [{srp: verifier}], function (error, result) { @@ -89,7 +90,7 @@ Accounts.changePassword = function (oldPassword, newPassword, callback) { } }); } else { // oldPassword - var srp = new Meteor._srp.Client(oldPassword); + var srp = new SRP.Client(oldPassword); var request = srp.startExchange(); request.user = {id: Meteor.user()._id}; Meteor.apply('beginPasswordExchange', [request], function (error, result) { @@ -142,7 +143,7 @@ Accounts.resetPassword = function(token, newPassword, callback) { if (!newPassword) throw new Error("Need to pass newPassword"); - var verifier = Meteor._srp.generateVerifier(newPassword); + var verifier = SRP.generateVerifier(newPassword); Accounts.callLoginMethod({ methodName: 'resetPassword', methodArguments: [token, verifier], diff --git a/packages/accounts-password/password_server.js b/packages/accounts-password/password_server.js index 663f7c15b4..070ce4a1f4 100644 --- a/packages/accounts-password/password_server.js +++ b/packages/accounts-password/password_server.js @@ -62,7 +62,7 @@ Meteor.methods({beginPasswordExchange: function (request) { throw new Meteor.Error(403, "User has no password set"); var verifier = user.services.password.srp; - var srp = new Meteor._srp.Server(verifier); + var srp = new SRP.Server(verifier); var challenge = srp.issueChallenge({A: request.A}); // save off results in the current session so we can verify them @@ -82,7 +82,7 @@ Accounts.registerLoginHandler(function (options) { // we're always called from within a 'login' method, so this should // be safe. - var currentInvocation = Meteor._CurrentInvocation.get(); + var currentInvocation = DDP._CurrentInvocation.get(); var serialized = currentInvocation._sessionData.srpChallenge; if (!serialized || serialized.M !== options.srp.M) throw new Meteor.Error(403, "Incorrect password"); @@ -127,7 +127,7 @@ Accounts.registerLoginHandler(function (options) { // Just check the verifier output when the same identity and salt // are passed. Don't bother with a full exchange. var verifier = user.services.password.srp; - var newVerifier = Meteor._srp.generateVerifier(options.password, { + var newVerifier = SRP.generateVerifier(options.password, { identity: verifier.identity, salt: verifier.salt}); if (verifier.verifier !== newVerifier.verifier) @@ -155,7 +155,7 @@ Meteor.methods({changePassword: function (options) { // password. For now, we don't allow changePassword without knowing the old // password. M: String, - srp: Match.Optional(Meteor._srp.matchVerifier), + srp: Match.Optional(SRP.matchVerifier), password: Match.Optional(String) }); @@ -170,7 +170,7 @@ Meteor.methods({changePassword: function (options) { var verifier = options.srp; if (!verifier && options.password) { - verifier = Meteor._srp.generateVerifier(options.password); + verifier = SRP.generateVerifier(options.password); } if (!verifier) throw new Meteor.Error(400, "Invalid verifier"); @@ -192,7 +192,7 @@ Accounts.setPassword = function (userId, newPassword) { var user = Meteor.users.findOne(userId); if (!user) throw new Meteor.Error(403, "User not found"); - var newVerifier = Meteor._srp.generateVerifier(newPassword); + var newVerifier = SRP.generateVerifier(newPassword); Meteor.users.update({_id: user._id}, { $set: {'services.password.srp': newVerifier}}); @@ -293,7 +293,7 @@ Accounts.sendEnrollmentEmail = function (userId, email) { // the users password, and log them in. Meteor.methods({resetPassword: function (token, newVerifier) { check(token, String); - check(newVerifier, Meteor._srp.matchVerifier); + check(newVerifier, SRP.matchVerifier); var user = Meteor.users.findOne({ "services.password.reset.token": ""+token}); @@ -428,7 +428,7 @@ var createUser = function (options) { username: Match.Optional(String), email: Match.Optional(String), password: Match.Optional(String), - srp: Match.Optional(Meteor._srp.matchVerifier) + srp: Match.Optional(SRP.matchVerifier) })); var username = options.username; @@ -442,7 +442,7 @@ var createUser = function (options) { if (options.password) { if (options.srp) throw new Meteor.Error(400, "Don't pass both password and srp in options"); - options.srp = Meteor._srp.generateVerifier(options.password); + options.srp = SRP.generateVerifier(options.password); } var user = {services: {}}; diff --git a/packages/accounts-twitter/twitter_client.js b/packages/accounts-twitter/twitter_client.js index c78f47a1c1..48efc39a02 100644 --- a/packages/accounts-twitter/twitter_client.js +++ b/packages/accounts-twitter/twitter_client.js @@ -1,3 +1,4 @@ +// @export Meteor.loginWithTwitter Meteor.loginWithTwitter = function(options, callback) { // support a callback without options if (! callback && typeof options === "function") { diff --git a/packages/accounts-weibo/weibo_client.js b/packages/accounts-weibo/weibo_client.js index d620dbac9f..54196c5c0f 100644 --- a/packages/accounts-weibo/weibo_client.js +++ b/packages/accounts-weibo/weibo_client.js @@ -1,3 +1,4 @@ +// @export Meteor.loginWithWeibo Meteor.loginWithWeibo = function(options, callback) { // support a callback without options if (! callback && typeof options === "function") { diff --git a/packages/appcache/appcache-client.js b/packages/appcache/appcache-client.js index 3c5afd527c..024b71d01f 100644 --- a/packages/appcache/appcache-client.js +++ b/packages/appcache/appcache-client.js @@ -13,7 +13,7 @@ var updatingAppcache = false; var reloadRetry = null; var appcacheUpdated = false; -Meteor._reload.onMigrate('appcache', function(retry) { +Reload._onMigrate('appcache', function(retry) { if (appcacheUpdated) return [true]; @@ -61,7 +61,7 @@ window.applicationCache.addEventListener('obsolete', (function() { } else { appcacheUpdated = true; - Meteor._reload.reload(); + Reload._reload(); } }), false); diff --git a/packages/audit-argument-checks/audit_argument_checks.js b/packages/audit-argument-checks/audit_argument_checks.js index a1a2afe602..4655d7fb41 100644 --- a/packages/audit-argument-checks/audit_argument_checks.js +++ b/packages/audit-argument-checks/audit_argument_checks.js @@ -1 +1 @@ -Meteor._LivedataServer._auditArgumentChecks = true; +DDP._setAuditArgumentChecks(true); diff --git a/packages/autopublish/autopublish.js b/packages/autopublish/autopublish.js index bedee57223..e45f4dee64 100644 --- a/packages/autopublish/autopublish.js +++ b/packages/autopublish/autopublish.js @@ -1 +1 @@ -Meteor.default_server.autopublish(); +Meteor.server.autopublish(); diff --git a/packages/ctl-helper/ctl-helper.js b/packages/ctl-helper/ctl-helper.js index 1ec6eab249..3f1236e578 100644 --- a/packages/ctl-helper/ctl-helper.js +++ b/packages/ctl-helper/ctl-helper.js @@ -43,7 +43,7 @@ _.extend(Ctl, { process.exit(1); } - return Meteor.connect(process.env['GALAXY']); + return DDP.connect(process.env['GALAXY']); }), jobsCollection: _.once(function () { diff --git a/packages/deps/deps.js b/packages/deps/deps.js index fd41fe323b..d5db7aaa8a 100644 --- a/packages/deps/deps.js +++ b/packages/deps/deps.js @@ -1,3 +1,6 @@ +// We have to export Deps as a single symbol since we want to be able +// to change Deps.active later. +// // @export Deps Deps = {}; Deps.active = false; diff --git a/packages/ejson/base64.js b/packages/ejson/base64.js index 1d4bcdef7b..5b7f309943 100644 --- a/packages/ejson/base64.js +++ b/packages/ejson/base64.js @@ -8,7 +8,7 @@ for (var i = 0; i < BASE_64_CHARS.length; i++) { BASE_64_VALS[BASE_64_CHARS.charAt(i)] = i; }; -EJSON._base64Encode = function (array) { +base64Encode = function (array) { var answer = []; var a = null; var b = null; @@ -62,6 +62,7 @@ var getVal = function (ch) { return BASE_64_VALS[ch]; }; +// @export EJSON.newBinary EJSON.newBinary = function (len) { if (typeof Uint8Array === 'undefined' || typeof ArrayBuffer === 'undefined') { var ret = []; @@ -74,7 +75,7 @@ EJSON.newBinary = function (len) { return new Uint8Array(new ArrayBuffer(len)); }; -EJSON._base64Decode = function (str) { +base64Decode = function (str) { var len = Math.floor((str.length*3)/4); if (str.charAt(str.length - 1) == '=') { len--; @@ -121,3 +122,9 @@ EJSON._base64Decode = function (str) { } return arr; }; + +// @export _EJSONTest.base64Encode +_EJSONTest.base64Encode = base64Encode; + +// @export _EJSONTest.base64Decode +_EJSONTest.base64Decode = base64Decode; diff --git a/packages/ejson/base64_test.js b/packages/ejson/base64_test.js index 09401de0fb..e17519ea40 100644 --- a/packages/ejson/base64_test.js +++ b/packages/ejson/base64_test.js @@ -24,8 +24,8 @@ Tinytest.add("base64 - testing the test", function (test) { }); Tinytest.add("base64 - empty", function (test) { - test.equal(EJSON._base64Encode(EJSON.newBinary(0)), ""); - test.equal(EJSON._base64Decode(""), EJSON.newBinary(0)); + test.equal(_EJSONTest.base64Encode(EJSON.newBinary(0)), ""); + test.equal(_EJSONTest.base64Decode(""), EJSON.newBinary(0)); }); @@ -38,8 +38,8 @@ Tinytest.add("base64 - wikipedia examples", function (test) { {txt: "sure.", res: "c3VyZS4="} ]; _.each(tests, function(t) { - test.equal(EJSON._base64Encode(asciiToArray(t.txt)), t.res); - test.equal(arrayToAscii(EJSON._base64Decode(t.res)), t.txt); + test.equal(_EJSONTest.base64Encode(asciiToArray(t.txt)), t.res); + test.equal(arrayToAscii(_EJSONTest.base64Decode(t.res)), t.txt); }); }); @@ -49,11 +49,11 @@ Tinytest.add("base64 - non-text examples", function (test) { {array: [0, 0, 1], b64: "AAAB"} ]; _.each(tests, function(t) { - test.equal(EJSON._base64Encode(t.array), t.b64); + test.equal(_EJSONTest.base64Encode(t.array), t.b64); var expectedAsBinary = EJSON.newBinary(t.array.length); _.each(t.array, function (val, i) { expectedAsBinary[i] = val; }); - test.equal(EJSON._base64Decode(t.b64), expectedAsBinary); + test.equal(_EJSONTest.base64Decode(t.b64), expectedAsBinary); }); }); diff --git a/packages/ejson/ejson.js b/packages/ejson/ejson.js index 4b4a53cb12..432357f8e9 100644 --- a/packages/ejson/ejson.js +++ b/packages/ejson/ejson.js @@ -1,6 +1,3 @@ -// @export EJSON -EJSON = {}; - var customTypes = {}; // Add a custom type, using a method of your choice to get to and // from a basic JSON-able representation. The factory argument @@ -11,6 +8,8 @@ var customTypes = {}; // - A toJSONValue() method, so that Meteor can serialize it // - a typeName() method, to show how to look it up in our type table. // It is okay if these methods are monkey-patched on. +// +// @export EJSON.addType EJSON.addType = function (name, factory) { if (_.has(customTypes, name)) throw new Error("Type " + name + " already present"); @@ -41,10 +40,10 @@ var builtinConverters = [ || (obj && _.has(obj, '$Uint8ArrayPolyfill')); }, toJSONValue: function (obj) { - return {$binary: EJSON._base64Encode(obj)}; + return {$binary: base64Encode(obj)}; }, fromJSONValue: function (obj) { - return EJSON._base64Decode(obj.$binary); + return base64Decode(obj.$binary); } }, { // Escaping one level @@ -92,6 +91,7 @@ var builtinConverters = [ } ]; +// @export EJSON._isCustomType EJSON._isCustomType = function (obj) { return obj && typeof obj.toJSONValue === 'function' && @@ -100,7 +100,8 @@ EJSON._isCustomType = function (obj) { }; -//for both arrays and objects, in-place modification. +// for both arrays and objects, in-place modification. +// @export EJSON._adjustTypesToJSONValue var adjustTypesToJSONValue = EJSON._adjustTypesToJSONValue = function (obj) { if (obj === null) @@ -135,6 +136,7 @@ var toJSONValueHelper = function (item) { return undefined; }; +// @export EJSON.toJSONValue EJSON.toJSONValue = function (item) { var changed = toJSONValueHelper(item); if (changed !== undefined) @@ -146,9 +148,11 @@ EJSON.toJSONValue = function (item) { return item; }; -//for both arrays and objects. Tries its best to just +// for both arrays and objects. Tries its best to just // use the object you hand it, but may return something // different if the object you hand it itself needs changing. +// +// @export EJSON._adjustTypesFromJSONValue var adjustTypesFromJSONValue = EJSON._adjustTypesFromJSONValue = function (obj) { if (obj === null) @@ -193,6 +197,7 @@ var fromJSONValueHelper = function (value) { return value; }; +// @export EJSON.fromJSONValue EJSON.fromJSONValue = function (item) { var changed = fromJSONValueHelper(item); if (changed === item && typeof item === 'object') { @@ -204,19 +209,23 @@ EJSON.fromJSONValue = function (item) { } }; +// @export EJSON.stringify EJSON.stringify = function (item) { return JSON.stringify(EJSON.toJSONValue(item)); }; +// @export EJSON.parse EJSON.parse = function (item) { return EJSON.fromJSONValue(JSON.parse(item)); }; +// @export EJSON.isBinary EJSON.isBinary = function (obj) { return !!((typeof Uint8Array !== 'undefined' && obj instanceof Uint8Array) || (obj && obj.$Uint8ArrayPolyfill)); }; +// @export EJSON.equals EJSON.equals = function (a, b, options) { var i; var keyOrderSensitive = !!(options && options.keyOrderSensitive); @@ -288,6 +297,7 @@ EJSON.equals = function (a, b, options) { } }; +// @export EJSON.clone EJSON.clone = function (v) { var ret; if (typeof v !== "object") diff --git a/packages/email/email.js b/packages/email/email.js index 3072dcad00..c0fdd00aed 100644 --- a/packages/email/email.js +++ b/packages/email/email.js @@ -1,6 +1,3 @@ -// @export Email -Email = {}; - var Future = Npm.require('fibers/future'); var urlModule = Npm.require('url'); var MailComposer = Npm.require('mailcomposer').MailComposer; @@ -45,18 +42,28 @@ var maybeMakePool = function () { } }; -Email._next_devmode_mail_id = 0; +var next_devmode_mail_id = 0; +var output_stream = process.stdout; -// Overridden by tests. -Email._output_stream = process.stdout; +// Testing hooks +// @export _EmailTest.overrideOutputStream +_EmailTest.overrideOutputStream = function (stream) { + next_devmode_mail_id = 0; + output_stream = stream; +}; + +// @export _EmailTest.restoreOutputStream +_EmailTest.restoreOutputStream = function () { + output_stream = process.stdout; +}; var devModeSend = function (mc) { - var devmode_mail_id = Email._next_devmode_mail_id++; + var devmode_mail_id = next_devmode_mail_id++; // Make sure we use whatever stream was set at the time of the Email.send // call even in the 'end' callback, in case there are multiple concurrent // test runs. - var stream = Email._output_stream; + var stream = output_stream; // This approach does not prevent other writers to stdout from interleaving. stream.write("====== BEGIN MAIL #" + devmode_mail_id + " ======\n"); @@ -74,6 +81,19 @@ var smtpSend = function (mc) { smtpPool._future_wrapped_sendMail(mc).wait(); }; +/** + * Mock out email sending (eg, during a test.) This is private for now. + * + * f receives the arguments to Email.send and should return true to go + * ahead and send the email (or at least, try subsequent hooks), or + * false to skip sending. + */ +// @export Email._hookSend +var sendHooks = []; +Email._hookSend = function (f) { + sendHooks.push(f); +}; + /** * Send an email. * @@ -93,7 +113,12 @@ var smtpSend = function (mc) { * @param options.html {String} RFC5322 mail body (HTML) * @param options.headers {Object} custom RFC5322 headers (dictionary) */ +// @export Email.send Email.send = function (options) { + for (var i = 0; i < sendHooks.length; i++) + if (! sendHooks[i](options)) + return; + var mc = new MailComposer(); // setup message data @@ -122,3 +147,4 @@ Email.send = function (options) { devModeSend(mc); } }; + diff --git a/packages/email/email_tests.js b/packages/email/email_tests.js index f8cdcd4610..ac7aa4e320 100644 --- a/packages/email/email_tests.js +++ b/packages/email/email_tests.js @@ -4,11 +4,9 @@ Tinytest.add("email - dev mode smoke test", function (test) { // This only tests dev mode, so don't run the test if this is deployed. if (process.env.MAIL_URL) return; - var old_stream = Email._output_stream; try { var stream = new streamBuffers.WritableStreamBuffer; - Email._output_stream = stream; - Email._next_devmode_mail_id = 0; + _EmailTest.overrideOutputStream(stream); Email.send({ from: "foo@example.com", to: "bar@example.com", @@ -21,7 +19,7 @@ Tinytest.add("email - dev mode smoke test", function (test) { // in case a concurrent test run mutates Email._output_stream too. // XXX brittle if mailcomposer changes header order, etc test.equal(stream.getContentsAsString("utf8"), - "====== BEGIN MAIL #0 ======\n" + + "====== BEGIN MAIL #0 ======\n" + "MIME-Version: 1.0\r\n" + "X-Meteor-Test: a custom header\r\n" + "From: foo@example.com\r\n" + @@ -36,6 +34,6 @@ Tinytest.add("email - dev mode smoke test", function (test) { "From us.\r\n" + "====== END MAIL #0 ======\n"); } finally { - Email._output_stream = old_stream; + _EmailTest.restoreOutputStream(); } }); diff --git a/packages/http/httpcall_client.js b/packages/http/httpcall_client.js index 96c1e8c0bb..b3cce1367a 100644 --- a/packages/http/httpcall_client.js +++ b/packages/http/httpcall_client.js @@ -1,5 +1,4 @@ -Meteor.http = Meteor.http || {}; - +// @export Meteor.http.call Meteor.http.call = function(method, url, options, callback) { ////////// Process arguments ////////// @@ -33,9 +32,8 @@ Meteor.http.call = function(method, url, options, callback) { params_for_body = options.params; var query_match = /^(.*?)(\?.*)?$/.exec(url); - url = Meteor.http._buildUrl(query_match[1], query_match[2], - options.query, params_for_url); - + url = buildUrl(query_match[1], query_match[2], + options.query, params_for_url); if (options.followRedirects === false) throw new Error("Option followRedirects:false not supported on client."); @@ -50,7 +48,7 @@ Meteor.http.call = function(method, url, options, callback) { } if (params_for_body) { - content = Meteor.http._encodeParams(params_for_body); + content = encodeParams(params_for_body); } _.extend(headers, options.headers || {}); @@ -146,11 +144,11 @@ Meteor.http.call = function(method, url, options, callback) { response.headers[m[1].toLowerCase()] = m[2]; }); - Meteor.http._populateData(response); + populateData(response); var error = null; if (response.statusCode >= 400) - error = Meteor.http._makeErrorByStatus(response.statusCode, response.content); + error = makeErrorByStatus(response.statusCode, response.content); callback(error, response); } diff --git a/packages/http/httpcall_common.js b/packages/http/httpcall_common.js index 11bb1cfc75..de0242c820 100644 --- a/packages/http/httpcall_common.js +++ b/packages/http/httpcall_common.js @@ -1,7 +1,4 @@ - -Meteor.http = Meteor.http || {}; - -Meteor.http._makeErrorByStatus = function(statusCode, content) { +makeErrorByStatus = function(statusCode, content) { var MAX_LENGTH = 160; // if you change this, also change the appropriate test var truncate = function(str, length) { @@ -15,22 +12,21 @@ Meteor.http._makeErrorByStatus = function(statusCode, content) { return new Error(message); }; -Meteor.http._encodeParams = function(params) { +encodeParams = function(params) { var buf = []; _.each(params, function(value, key) { if (buf.length) buf.push('&'); - buf.push(Meteor.http._encodeString(key), '=', - Meteor.http._encodeString(value)); + buf.push(encodeString(key), '=', encodeString(value)); }); return buf.join('').replace(/%20/g, '+'); }; -Meteor.http._encodeString = function(str) { +encodeString = function(str) { return encodeURIComponent(str).replace(/[!'()]/g, escape).replace(/\*/g, "%2A"); }; -Meteor.http._buildUrl = function(before_qmark, from_qmark, opt_query, opt_params) { +buildUrl = function(before_qmark, from_qmark, opt_query, opt_params) { var url_without_query = before_qmark; var query = from_qmark ? from_qmark.slice(1) : null; @@ -39,7 +35,7 @@ Meteor.http._buildUrl = function(before_qmark, from_qmark, opt_query, opt_params if (opt_params) { query = query || ""; - var prms = Meteor.http._encodeParams(opt_params); + var prms = encodeParams(opt_params); if (query && prms) query += '&'; query += prms; @@ -53,7 +49,7 @@ Meteor.http._buildUrl = function(before_qmark, from_qmark, opt_query, opt_params }; // Fill in `response.data` if the content-type is JSON. -Meteor.http._populateData = function(response) { +populateData = function(response) { // Read Content-Type header, up to a ';' if there is one. // A typical header might be "application/json; charset=utf-8" // or just "application/json". @@ -71,15 +67,22 @@ Meteor.http._populateData = function(response) { } }; +// @export Meteor.http.get Meteor.http.get = function (/* varargs */) { return Meteor.http.call.apply(this, ["GET"].concat(_.toArray(arguments))); }; + +// @export Meteor.http.post Meteor.http.post = function (/* varargs */) { return Meteor.http.call.apply(this, ["POST"].concat(_.toArray(arguments))); }; + +// @export Meteor.http.put Meteor.http.put = function (/* varargs */) { return Meteor.http.call.apply(this, ["PUT"].concat(_.toArray(arguments))); }; + +// @export Meteor.http.del Meteor.http.del = function (/* varargs */) { return Meteor.http.call.apply(this, ["DELETE"].concat(_.toArray(arguments))); }; diff --git a/packages/http/httpcall_server.js b/packages/http/httpcall_server.js index 51c8d16502..6c38d0c870 100644 --- a/packages/http/httpcall_server.js +++ b/packages/http/httpcall_server.js @@ -4,9 +4,9 @@ var path = Npm.require('path'); var request = Npm.require('request'); var url_util = Npm.require('url'); -// Meteor.http._call always runs asynchronously; Meteor.http.call, defined -// below, wraps _call and runs synchronously when no callback is provided. -Meteor.http._call = function(method, url, options, callback) { +// _call always runs asynchronously; Meteor.http.call, defined below, +// wraps _call and runs synchronously when no callback is provided. +_call = function(method, url, options, callback) { ////////// Process arguments ////////// @@ -40,8 +40,8 @@ Meteor.http._call = function(method, url, options, callback) { else params_for_body = options.params; - var new_url = Meteor.http._buildUrl( - url_parts.protocol+"//"+url_parts.host+url_parts.pathname, + var new_url = buildUrl( + url_parts.protocol + "//" + url_parts.host + url_parts.pathname, url_parts.search, options.query, params_for_url); if (options.auth) { @@ -52,7 +52,7 @@ Meteor.http._call = function(method, url, options, callback) { } if (params_for_body) { - content = Meteor.http._encodeParams(params_for_body); + content = encodeParams(params_for_body); headers['Content-Type'] = "application/x-www-form-urlencoded"; } @@ -95,10 +95,10 @@ Meteor.http._call = function(method, url, options, callback) { response.content = body; response.headers = res.headers; - Meteor.http._populateData(response); + populateData(response); if (response.statusCode >= 400) - error = Meteor.http._makeErrorByStatus(response.statusCode, response.content); + error = makeErrorByStatus(response.statusCode, response.content); } callback(error, response); @@ -106,4 +106,5 @@ Meteor.http._call = function(method, url, options, callback) { }); }; -Meteor.http.call = Meteor._wrapAsync(Meteor.http._call); +// @export Meteor.http.call +Meteor.http.call = Meteor._wrapAsync(_call); diff --git a/packages/less/package.js b/packages/less/package.js index 57f8dd152a..a6c8d7fe62 100644 --- a/packages/less/package.js +++ b/packages/less/package.js @@ -13,5 +13,6 @@ Package._transitional_registerBuildPlugin({ Package.on_test(function (api) { api.use(['test-helpers', 'tinytest', 'less']); + api.use(['spark']); api.add_files(['less_tests.less', 'less_tests.js'], 'client'); }); diff --git a/packages/livedata/client_convenience.js b/packages/livedata/client_convenience.js index fabb6ecfd1..8323c1d5b8 100644 --- a/packages/livedata/client_convenience.js +++ b/packages/livedata/client_convenience.js @@ -1,8 +1,9 @@ -_.extend(Meteor, { - default_connection: null, - refresh: function (notification) { - } -}); +// @export Meteor.connection +Meteor.connection = null; + +// @export Meteor.refresh +Meteor.refresh = function (notification) { +}; if (Meteor.isClient) { // By default, try to connect back to the same endpoint as the page @@ -12,18 +13,34 @@ if (Meteor.isClient) { if (__meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL) ddpUrl = __meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL; } - Meteor.default_connection = - Meteor.connect(ddpUrl, true /* restart_on_update */); + Meteor.connection = + DDP.connect(ddpUrl, true /* restart_on_update */); - // Proxy the public methods of Meteor.default_connection so they can + // Proxy the public methods of Meteor.connection so they can // be called directly on Meteor. - _.each(['subscribe', 'methods', 'call', 'apply', 'status', 'reconnect', + // @export Meteor.subscribe + // @export Meteor.methods + // @export Meteor.call + // @export Meteor.apply + // @export Meteor.status + // @export Meteor.reconnect + // @export Meteor.disconnect + _.each(['subscribe', 'methods', 'call', 'apply', 'status', 'reconnect', 'disconnect'], function (name) { - Meteor[name] = _.bind(Meteor.default_connection[name], - Meteor.default_connection); + Meteor[name] = _.bind(Meteor.connection[name], Meteor.connection); }); } else { /* Never set up a default connection on the server. Don't even map subscribe/call/etc onto Meteor. */ } + +// Meteor.connection used to be called +// Meteor.default_connection. Provide backcompat as a courtesy even +// though it was never documented. +// XXX remove this after a while +Meteor.default_connection = Meteor.connection; + +// We should transition from Meteor.connect to DDP.connect. +// @export Meteor.connect +Meteor.connect = DDP.connect; diff --git a/packages/livedata/crossbar.js b/packages/livedata/crossbar.js index 327760e730..9153fb18a5 100644 --- a/packages/livedata/crossbar.js +++ b/packages/livedata/crossbar.js @@ -1,4 +1,5 @@ -Meteor._InvalidationCrossbar = function () { +// @export DDP._InvalidationCrossbar +DDP._InvalidationCrossbar = function () { var self = this; self.next_id = 1; @@ -7,7 +8,7 @@ Meteor._InvalidationCrossbar = function () { self.listeners = {}; }; -_.extend(Meteor._InvalidationCrossbar.prototype, { +_.extend(DDP._InvalidationCrossbar.prototype, { // Listen for notification that match 'trigger'. A notification // matches if it has the key-value pairs in trigger as a // subset. When a notification matches, call 'callback', passing two @@ -96,4 +97,4 @@ _.extend(Meteor._InvalidationCrossbar.prototype, { }); // singleton -Meteor._InvalidationCrossbar = new Meteor._InvalidationCrossbar; +DDP._InvalidationCrossbar = new DDP._InvalidationCrossbar; diff --git a/packages/livedata/crossbar_tests.js b/packages/livedata/crossbar_tests.js index 9ac0d01a0d..7d5c01bbdb 100644 --- a/packages/livedata/crossbar_tests.js +++ b/packages/livedata/crossbar_tests.js @@ -6,15 +6,15 @@ // deep meaning to the matching function, and it could be changed later // as long as it preserves that property. Tinytest.add('livedata - crossbar', function (test) { - test.isTrue(Meteor._InvalidationCrossbar._matches( + test.isTrue(DDP._InvalidationCrossbar._matches( {collection: "C"}, {collection: "C"})); - test.isTrue(Meteor._InvalidationCrossbar._matches( + test.isTrue(DDP._InvalidationCrossbar._matches( {collection: "C", id: "X"}, {collection: "C"})); - test.isTrue(Meteor._InvalidationCrossbar._matches( + test.isTrue(DDP._InvalidationCrossbar._matches( {collection: "C"}, {collection: "C", id: "X"})); - test.isTrue(Meteor._InvalidationCrossbar._matches( + test.isTrue(DDP._InvalidationCrossbar._matches( {collection: "C", id: "X"}, {collection: "C"})); - test.isFalse(Meteor._InvalidationCrossbar._matches( + test.isFalse(DDP._InvalidationCrossbar._matches( {collection: "C", id: "X"}, {collection: "C", id: "Y"})); }); diff --git a/packages/livedata/livedata_common.js b/packages/livedata/livedata_common.js index e2543581fc..8ea1edda40 100644 --- a/packages/livedata/livedata_common.js +++ b/packages/livedata/livedata_common.js @@ -1,6 +1,9 @@ -Meteor._SUPPORTED_DDP_VERSIONS = [ 'pre1' ]; +SUPPORTED_DDP_VERSIONS = [ 'pre1' ]; -Meteor._MethodInvocation = function (options) { +// @export _LivedataTest.SUPPORTED_DDP_VERSIONS +_LivedataTest.SUPPORTED_DDP_VERSIONS = SUPPORTED_DDP_VERSIONS; + +MethodInvocation = function (options) { var self = this; // true if we're running not the actual method, but a stub (that is, @@ -35,7 +38,7 @@ Meteor._MethodInvocation = function (options) { this._sessionData = options.sessionData; }; -_.extend(Meteor._MethodInvocation.prototype, { +_.extend(MethodInvocation.prototype, { unblock: function () { var self = this; self._calledUnblock = true; @@ -50,7 +53,7 @@ _.extend(Meteor._MethodInvocation.prototype, { } }); -Meteor._parseDDP = function (stringMessage) { +parseDDP = function (stringMessage) { try { var msg = JSON.parse(stringMessage); } catch (e) { @@ -84,7 +87,7 @@ Meteor._parseDDP = function (stringMessage) { return msg; }; -Meteor._stringifyDDP = function (msg) { +stringifyDDP = function (msg) { var copy = EJSON.clone(msg); // swizzle 'changed' messages from 'fields undefined' rep to 'fields // and cleared' rep @@ -112,39 +115,10 @@ Meteor._stringifyDDP = function (msg) { return JSON.stringify(copy); }; -Meteor._CurrentInvocation = new Meteor.EnvironmentVariable; - -// Note: The DDP server assumes that Meteor.Error EJSON-serializes as an object -// containing 'error' and optionally 'reason' and 'details'. -// The DDP client manually puts these into Meteor.Error objects. (We don't use -// EJSON.addType here because the type is determined by location in the -// protocol, not text on the wire.) -Meteor.Error = Meteor.makeErrorType( - "Meteor.Error", - function (error, reason, details) { - var self = this; - - // Currently, a numeric code, likely similar to a HTTP code (eg, - // 404, 500). That is likely to change though. - self.error = error; - - // Optional: A short human-readable summary of the error. Not - // intended to be shown to end users, just developers. ("Not Found", - // "Internal Server Error") - self.reason = reason; - - // Optional: Additional information about the error, say for - // debugging. It might be a (textual) stack trace if the server is - // willing to provide one. The corresponding thing in HTTP would be - // the body of a 404 or 500 response. (The difference is that we - // never expect this to be shown to end users, only developers, so - // it doesn't need to be pretty.) - self.details = details; - - // This is what gets displayed at the top of a stack trace. Current - // format is "[404]" (if no reason is set) or "File not found [404]" - if (self.reason) - self.message = self.reason + ' [' + self.error + ']'; - else - self.message = '[' + self.error + ']'; - }); +// This is private but it's used in a few places. accounts-base uses +// it to get the current user. accounts-password uses it to stash SRP +// state in the DDP session. Meteor.setTimeout and friends clear +// it. We can probably find a better way to factor this. +// +// @export DDP._CurrentInvocation +DDP._CurrentInvocation = new Meteor.EnvironmentVariable; diff --git a/packages/livedata/livedata_connection.js b/packages/livedata/livedata_connection.js index ffe41a679c..8a0e50534e 100644 --- a/packages/livedata/livedata_connection.js +++ b/packages/livedata/livedata_connection.js @@ -1,5 +1,4 @@ if (Meteor.isServer) { - // XXX namespacing var path = Npm.require('path'); var Fiber = Npm.require('fibers'); var Future = Npm.require(path.join('fibers', 'future')); @@ -11,13 +10,13 @@ if (Meteor.isServer) { // reloadOnUpdate: should we try to reload when the server says // there's new code available? // reloadWithOutstanding: is it OK to reload if there are outstanding methods? -Meteor._LivedataConnection = function (url, options) { +var Connection = function (url, options) { var self = this; options = _.extend({ reloadOnUpdate: false, // The rest of these options are only for testing. reloadWithOutstanding: false, - supportedDDPVersions: Meteor._SUPPORTED_DDP_VERSIONS, + supportedDDPVersions: SUPPORTED_DDP_VERSIONS, onConnectionFailure: function (reason) { Meteor._debug("Failed DDP connection: " + reason); }, @@ -33,7 +32,7 @@ Meteor._LivedataConnection = function (url, options) { if (typeof url === "object") { self._stream = url; } else { - self._stream = new Meteor._DdpClientStream(url); + self._stream = new ClientStream(url); } self._lastSessionId = null; @@ -162,8 +161,8 @@ Meteor._LivedataConnection = function (url, options) { self._userIdDeps = (typeof Deps !== "undefined") && new Deps.Dependency; // Block auto-reload while we're waiting for method responses. - if (Meteor._reload && !options.reloadWithOutstanding) { - Meteor._reload.onMigrate(function (retry) { + if (Package.reload && !options.reloadWithOutstanding) { + Reload._onMigrate(function (retry) { if (!self._readyToMigrate()) { if (self._retryMigrate) throw new Error("Two migrations in progress?"); @@ -177,7 +176,7 @@ Meteor._LivedataConnection = function (url, options) { var onMessage = function (raw_msg) { try { - var msg = Meteor._parseDDP(raw_msg); + var msg = parseDDP(raw_msg); } catch (e) { Meteor._debug("Exception while parsing DDP", e); return; @@ -280,12 +279,12 @@ Meteor._LivedataConnection = function (url, options) { } - if (Meteor._reload && options.reloadOnUpdate) { + if (Package.reload && options.reloadOnUpdate) { self._stream.on('update_available', function () { // Start trying to migrate to a new version. Until all packages // signal that they're ready for a migration, the app will // continue running normally. - Meteor._reload.reload(); + Reload._reload(); }); } @@ -384,7 +383,7 @@ _.extend(MethodInvoker.prototype, { } }); -_.extend(Meteor._LivedataConnection.prototype, { +_.extend(Connection.prototype, { // 'name' is the name of the data on the wire that should go in the // store. 'wrappedStore' should be an object with methods beginUpdate, update, // endUpdate, saveOriginals, retrieveOriginals. see Collection for an example. @@ -629,7 +628,7 @@ _.extend(Meteor._LivedataConnection.prototype, { // to do a RPC, so we use the return value of the stub as our return // value. - var enclosing = Meteor._CurrentInvocation.get(); + var enclosing = DDP._CurrentInvocation.get(); var alreadyInSimulation = enclosing && enclosing.isSimulation; var stub = self._methodHandlers[name]; @@ -637,7 +636,7 @@ _.extend(Meteor._LivedataConnection.prototype, { var setUserId = function(userId) { self.setUserId(userId); }; - var invocation = new Meteor._MethodInvocation({ + var invocation = new MethodInvocation({ isSimulation: true, userId: self.userId(), setUserId: setUserId, sessionData: self._sessionData @@ -649,7 +648,7 @@ _.extend(Meteor._LivedataConnection.prototype, { try { // Note that unlike in the corresponding server code, we never audit // that stubs check() their arguments. - var ret = Meteor._CurrentInvocation.withValue(invocation,function () { + var ret = DDP._CurrentInvocation.withValue(invocation,function () { if (Meteor.isServer) { // Because saveOriginals and retrieveOriginals aren't reentrant, // don't allow stubs to yield. @@ -809,7 +808,7 @@ _.extend(Meteor._LivedataConnection.prototype, { // Sends the DDP stringification of the given message object _send: function (obj) { var self = this; - self._stream.send(Meteor._stringifyDDP(obj)); + self._stream.send(stringifyDDP(obj)); }, status: function (/*passthrough args*/) { @@ -1067,7 +1066,7 @@ _.extend(Meteor._LivedataConnection.prototype, { + msg.id); } serverDoc.document = msg.fields || {}; - serverDoc.document._id = Meteor.idParse(msg.id); + serverDoc.document._id = LocalCollection._idParse(msg.id); } else { self._pushUpdate(updates, msg.collection, msg); } @@ -1384,26 +1383,31 @@ _.extend(Meteor._LivedataConnection.prototype, { } }); -_.extend(Meteor, { - // @param url {String} URL to Meteor app, - // e.g.: - // "subdomain.meteor.com", - // "http://subdomain.meteor.com", - // "/", - // "ddp+sockjs://ddp--****-foo.meteor.com/sockjs" - connect: function (url, _reloadOnUpdate) { - var ret = new Meteor._LivedataConnection( - url, {reloadOnUpdate: _reloadOnUpdate}); - Meteor._LivedataConnection._allConnections.push(ret); // hack. see below. - return ret; - } -}); +// @export _LivedataTest.Connection +_LivedataTest.Connection = Connection; + +// @param url {String} URL to Meteor app, +// e.g.: +// "subdomain.meteor.com", +// "http://subdomain.meteor.com", +// "/", +// "ddp+sockjs://ddp--****-foo.meteor.com/sockjs" +// +// @export DDP.connect +DDP.connect = function (url, _reloadOnUpdate) { + var ret = new Connection( + url, {reloadOnUpdate: _reloadOnUpdate}); + allConnections.push(ret); // hack. see below. + return ret; +}; // Hack for `spiderable` package: a way to see if the page is done // loading all the data it needs. -Meteor._LivedataConnection._allConnections = []; -Meteor._LivedataConnection._allSubscriptionsReady = function () { - return _.all(Meteor._LivedataConnection._allConnections, function (conn) { +// +// @export DDP._allSubscriptionsReady +allConnections = []; +DDP._allSubscriptionsReady = function () { + return _.all(allConnections, function (conn) { return _.all(conn._subscriptions, function (sub) { return sub.ready; }); diff --git a/packages/livedata/livedata_connection_tests.js b/packages/livedata/livedata_connection_tests.js index a8ee2c7405..d0c7035957 100644 --- a/packages/livedata/livedata_connection_tests.js +++ b/packages/livedata/livedata_connection_tests.js @@ -1,16 +1,15 @@ - var newConnection = function (stream) { // Some of these tests leave outstanding methods with no result yet // returned. This should not block us from re-running tests when sources // change. - return new Meteor._LivedataConnection(stream, {reloadWithOutstanding: true}); + return new _LivedataTest.Connection(stream, {reloadWithOutstanding: true}); }; var makeConnectMessage = function (session) { var msg = { msg: 'connect', - version: Meteor._SUPPORTED_DDP_VERSIONS[0], - support: Meteor._SUPPORTED_DDP_VERSIONS + version: _LivedataTest.SUPPORTED_DDP_VERSIONS[0], + support: _LivedataTest.SUPPORTED_DDP_VERSIONS }; if (session) @@ -69,7 +68,7 @@ var startAndConnect = function(test, stream) { var SESSION_ID = '17'; Tinytest.add("livedata stub - receive data", function (test) { - var stream = new Meteor._StubStream(); + var stream = new StubStream(); var conn = newConnection(stream); startAndConnect(test, stream); @@ -97,7 +96,7 @@ Tinytest.add("livedata stub - receive data", function (test) { }); Tinytest.add("livedata stub - subscribe", function (test) { - var stream = new Meteor._StubStream(); + var stream = new StubStream(); var conn = newConnection(stream); startAndConnect(test, stream); @@ -146,7 +145,7 @@ Tinytest.add("livedata stub - subscribe", function (test) { Tinytest.add("livedata stub - reactive subscribe", function (test) { - var stream = new Meteor._StubStream(); + var stream = new StubStream(); var conn = newConnection(stream); startAndConnect(test, stream); @@ -263,7 +262,7 @@ Tinytest.add("livedata stub - reactive subscribe", function (test) { Tinytest.add("livedata stub - this", function (test) { - var stream = new Meteor._StubStream(); + var stream = new StubStream(); var conn = newConnection(stream); startAndConnect(test, stream); @@ -289,7 +288,7 @@ Tinytest.add("livedata stub - this", function (test) { if (Meteor.isClient) { Tinytest.add("livedata stub - methods", function (test) { - var stream = new Meteor._StubStream(); + var stream = new StubStream(); var conn = newConnection(stream); startAndConnect(test, stream); @@ -344,7 +343,7 @@ if (Meteor.isClient) { test.equal(counts, {added: 1, removed: 0, changed: 0, moved: 0}); // data methods do not show up (not quiescent yet) - stream.receive({msg: 'added', collection: collName, id: Meteor.idStringify(docId), + stream.receive({msg: 'added', collection: collName, id: LocalCollection._idStringify(docId), fields: {value: 'tuesday'}}); test.equal(coll.find({}).count(), 1); test.equal(coll.find({value: 'friday!'}).count(), 1); @@ -391,7 +390,7 @@ if (Meteor.isClient) { } Tinytest.add("livedata stub - mutating method args", function (test) { - var stream = new Meteor._StubStream(); + var stream = new StubStream(); var conn = newConnection(stream); startAndConnect(test, stream); @@ -432,7 +431,7 @@ var observeCursor = function (test, cursor) { // method calls another method in simulation. see not sent. if (Meteor.isClient) { Tinytest.add("livedata stub - methods calling methods", function (test) { - var stream = new Meteor._StubStream(); + var stream = new StubStream(); var conn = newConnection(stream); startAndConnect(test, stream); @@ -472,7 +471,7 @@ if (Meteor.isClient) { // get data from the method. data from this doc does not show up yet, but data // from another doc does. - stream.receive({msg: 'added', collection: coll_name, id: Meteor.idStringify(docId), + stream.receive({msg: 'added', collection: coll_name, id: LocalCollection._idStringify(docId), fields: {value: 'tuesday'}}); o.expectCallbacks(); test.equal(coll.findOne(docId), {_id: docId, a: 1}); @@ -495,7 +494,7 @@ if (Meteor.isClient) { }); } Tinytest.add("livedata stub - method call before connect", function (test) { - var stream = new Meteor._StubStream; + var stream = new StubStream; var conn = newConnection(stream); var callbackOutput = []; @@ -516,7 +515,7 @@ Tinytest.add("livedata stub - method call before connect", function (test) { }); Tinytest.add("livedata stub - reconnect", function (test) { - var stream = new Meteor._StubStream(); + var stream = new StubStream(); var conn = newConnection(stream); startAndConnect(test, stream); @@ -645,7 +644,7 @@ Tinytest.add("livedata stub - reconnect", function (test) { if (Meteor.isClient) { Tinytest.add("livedata stub - reconnect method which only got result", function (test) { - var stream = new Meteor._StubStream; + var stream = new StubStream; var conn = newConnection(stream); startAndConnect(test, stream); @@ -685,7 +684,7 @@ if (Meteor.isClient) { // Get some data. stream.receive({msg: 'added', collection: collName, - id: Meteor.idStringify(stubWrittenId), fields: {baz: 42}}); + id: LocalCollection._idStringify(stubWrittenId), fields: {baz: 42}}); // It doesn't show up yet. test.equal(coll.find().count(), 1); test.equal(coll.findOne(stubWrittenId), {_id: stubWrittenId, foo: 'bar'}); @@ -720,7 +719,7 @@ if (Meteor.isClient) { test.equal(callbackOutput, ['bla']); test.equal(onResultReceivedOutput, ['bla']); stream.receive({msg: 'added', collection: collName, - id: Meteor.idStringify(stubWrittenId), fields: {baz: 42}}); + id: LocalCollection._idStringify(stubWrittenId), fields: {baz: 42}}); test.equal(coll.findOne(stubWrittenId), {_id: stubWrittenId, baz: 42}); o.expectCallbacks({added: 1}); @@ -752,7 +751,7 @@ if (Meteor.isClient) { // Get some data. stream.receive({msg: 'added', collection: collName, - id: Meteor.idStringify(stubWrittenId2), fields: {baz: 42}}); + id: LocalCollection._idStringify(stubWrittenId2), fields: {baz: 42}}); // It doesn't show up yet. test.equal(coll.find().count(), 2); test.equal(coll.findOne(stubWrittenId2), {_id: stubWrittenId2, foo: 'bar'}); @@ -796,7 +795,7 @@ if (Meteor.isClient) { // Receive data matching our stub. It doesn't take effect yet. stream.receive({msg: 'added', collection: collName, - id: Meteor.idStringify(stubWrittenId2), fields: {foo: 'bar'}}); + id: LocalCollection._idStringify(stubWrittenId2), fields: {foo: 'bar'}}); o.expectCallbacks(); // slowMethod is done writing, so we get full reconnect quiescence (but no @@ -818,7 +817,7 @@ if (Meteor.isClient) { }); } Tinytest.add("livedata stub - reconnect method which only got data", function (test) { - var stream = new Meteor._StubStream; + var stream = new StubStream; var conn = newConnection(stream); startAndConnect(test, stream); @@ -904,7 +903,7 @@ Tinytest.add("livedata stub - reconnect method which only got data", function (t }); if (Meteor.isClient) { Tinytest.add("livedata stub - multiple stubs same doc", function (test) { - var stream = new Meteor._StubStream; + var stream = new StubStream; var conn = newConnection(stream); startAndConnect(test, stream); @@ -951,7 +950,7 @@ if (Meteor.isClient) { // Get some data... slightly different than what we wrote. stream.receive({msg: 'added', collection: collName, - id: Meteor.idStringify(stubWrittenId), fields: {foo: 'barb', other: 'field', + id: LocalCollection._idStringify(stubWrittenId), fields: {foo: 'barb', other: 'field', other2: 'bla'}}); // It doesn't show up yet. test.equal(coll.find().count(), 1); @@ -969,7 +968,7 @@ if (Meteor.isClient) { // More data. Not quite what we wrote. Also ignored for now. stream.receive({msg: 'changed', collection: collName, - id: Meteor.idStringify(stubWrittenId), fields: {baz: 43}, cleared: ['other']}); + id: LocalCollection._idStringify(stubWrittenId), fields: {baz: 43}, cleared: ['other']}); test.equal(coll.find().count(), 1); test.equal(coll.findOne(stubWrittenId), {_id: stubWrittenId, foo: 'bar', baz: 42}); @@ -991,7 +990,7 @@ if (Meteor.isClient) { Tinytest.add("livedata stub - unsent methods don't block quiescence", function (test) { // This test is for https://github.com/meteor/meteor/issues/555 - var stream = new Meteor._StubStream; + var stream = new StubStream; var conn = newConnection(stream); startAndConnect(test, stream); @@ -1055,7 +1054,7 @@ if (Meteor.isClient) { }); } Tinytest.add("livedata stub - reactive resub", function (test) { - var stream = new Meteor._StubStream(); + var stream = new StubStream(); var conn = newConnection(stream); startAndConnect(test, stream); @@ -1124,7 +1123,7 @@ Tinytest.add("livedata stub - reactive resub", function (test) { Tinytest.add("livedata connection - reactive userId", function (test) { - var stream = new Meteor._StubStream(); + var stream = new StubStream(); var conn = newConnection(stream); test.equal(conn.userId(), null); @@ -1133,7 +1132,7 @@ Tinytest.add("livedata connection - reactive userId", function (test) { }); Tinytest.add("livedata connection - two wait methods", function (test) { - var stream = new Meteor._StubStream(); + var stream = new StubStream(); var conn = newConnection(stream); startAndConnect(test, stream); @@ -1244,7 +1243,7 @@ Tinytest.add("livedata connection - two wait methods", function (test) { }); Tinytest.add("livedata connection - onReconnect prepends messages correctly with a wait method", function(test) { - var stream = new Meteor._StubStream(); + var stream = new StubStream(); var conn = newConnection(stream); startAndConnect(test, stream); @@ -1309,7 +1308,7 @@ if (Meteor.isServer) { testAsyncMulti("livedata connection - reconnect to a different server", [ function (test, expect) { var self = this; - self.conn = Meteor.connect("reverse.meteor.com"); + self.conn = DDP.connect("reverse.meteor.com"); pollUntil(expect, function () { return self.conn.status().connected; }, 5000, 100, true); // poll until connected, but don't fail if we don't connect @@ -1348,12 +1347,12 @@ testAsyncMulti("livedata connection - reconnect to a different server", [ Tinytest.addAsync("livedata connection - version negotiation requires renegotiating", function (test, onComplete) { - var connection = new Meteor._LivedataConnection(getSelfConnectionUrl(), { + var connection = new _LivedataTest.Connection(getSelfConnectionUrl(), { reloadWithOutstanding: true, - supportedDDPVersions: ["garbled", Meteor._SUPPORTED_DDP_VERSIONS[0]], + supportedDDPVersions: ["garbled", _LivedataTest.SUPPORTED_DDP_VERSIONS[0]], onConnectionFailure: function () { test.fail(); onComplete(); }, onConnected: function () { - test.equal(connection._version, Meteor._SUPPORTED_DDP_VERSIONS[0]); + test.equal(connection._version, _LivedataTest.SUPPORTED_DDP_VERSIONS[0]); connection._stream.disconnect({_permanent: true}); onComplete(); } @@ -1362,7 +1361,7 @@ Tinytest.addAsync("livedata connection - version negotiation requires renegotiat Tinytest.addAsync("livedata connection - version negotiation error", function (test, onComplete) { - var connection = new Meteor._LivedataConnection(getSelfConnectionUrl(), { + var connection = new _LivedataTest.Connection(getSelfConnectionUrl(), { reloadWithOutstanding: true, supportedDDPVersions: ["garbled", "more garbled"], onConnectionFailure: function () { @@ -1379,7 +1378,7 @@ Tinytest.addAsync("livedata connection - version negotiation error", }); Tinytest.add("livedata connection - onReconnect prepends messages correctly without a wait method", function(test) { - var stream = new Meteor._StubStream(); + var stream = new StubStream(); var conn = newConnection(stream); startAndConnect(test, stream); @@ -1423,7 +1422,7 @@ Tinytest.add("livedata connection - onReconnect prepends messages correctly with }); Tinytest.add("livedata connection - onReconnect with sent messages", function(test) { - var stream = new Meteor._StubStream(); + var stream = new StubStream(); var conn = newConnection(stream); startAndConnect(test, stream); @@ -1466,7 +1465,7 @@ Tinytest.add("livedata connection - onReconnect with sent messages", function(te Tinytest.add("livedata stub - reconnect double wait method", function (test) { - var stream = new Meteor._StubStream; + var stream = new StubStream; var conn = newConnection(stream); startAndConnect(test, stream); @@ -1530,7 +1529,7 @@ Tinytest.add("livedata stub - reconnect double wait method", function (test) { }); Tinytest.add("livedata stub - subscribe errors", function (test) { - var stream = new Meteor._StubStream(); + var stream = new StubStream(); var conn = newConnection(stream); startAndConnect(test, stream); @@ -1572,7 +1571,7 @@ Tinytest.add("livedata stub - subscribe errors", function (test) { if (Meteor.isClient) { Tinytest.add("livedata stub - stubs before connected", function (test) { - var stream = new Meteor._StubStream(); + var stream = new StubStream(); var conn = newConnection(stream); var collName = Random.id(); diff --git a/packages/livedata/livedata_server.js b/packages/livedata/livedata_server.js index e3331d48ef..bd47965d18 100644 --- a/packages/livedata/livedata_server.js +++ b/packages/livedata/livedata_server.js @@ -1,18 +1,22 @@ var Fiber = Npm.require('fibers'); // This file contains classes: -// * LivedataSession - The server's connection to a single DDP client -// * LivedataSubscription - A single subscription for a single client -// * LivedataServer - An entire server that may talk to > 1 client. A DDP endpoint. +// * Session - The server's connection to a single DDP client +// * Subscription - A single subscription for a single client +// * Server - An entire server that may talk to > 1 client. A DDP endpoint. +// +// Session and Subscription are file scope. For now, until we freeze +// the interface, Server is package scope (in the future it should be +// exported.) // Represents a single document in a SessionCollectionView -Meteor._SessionDocumentView = function () { +var SessionDocumentView = function () { var self = this; self.existsIn = {}; // set of subscriptionHandle self.dataByKey = {}; // key-> [ {subscriptionHandle, value} by precedence] }; -_.extend(Meteor._SessionDocumentView.prototype, { +_.extend(SessionDocumentView.prototype, { getFields: function () { var self = this; @@ -91,14 +95,18 @@ _.extend(Meteor._SessionDocumentView.prototype, { }); // Represents a client's view of a single collection -Meteor._SessionCollectionView = function (collectionName, sessionCallbacks) { +var SessionCollectionView = function (collectionName, sessionCallbacks) { var self = this; self.collectionName = collectionName; self.documents = {}; self.callbacks = sessionCallbacks; }; -_.extend(Meteor._SessionCollectionView.prototype, { +// @export _LivedataTest.SessionCollectionView +_LivedataTest.SessionCollectionView = SessionCollectionView; + + +_.extend(SessionCollectionView.prototype, { isEmpty: function () { var self = this; @@ -144,7 +152,7 @@ _.extend(Meteor._SessionCollectionView.prototype, { var added = false; if (!docView) { added = true; - docView = new Meteor._SessionDocumentView(); + docView = new SessionDocumentView(); self.documents[id] = docView; } docView.existsIn[subscriptionHandle] = true; @@ -198,11 +206,12 @@ _.extend(Meteor._SessionCollectionView.prototype, { } } }); + /******************************************************************************/ -/* LivedataSession */ +/* Session */ /******************************************************************************/ -Meteor._LivedataSession = function (server, version) { +var Session = function (server, version) { var self = this; self.id = Random.id(); @@ -249,7 +258,7 @@ Meteor._LivedataSession = function (server, version) { self._pendingReady = []; }; -_.extend(Meteor._LivedataSession.prototype, { +_.extend(Session.prototype, { sendReady: function (subscriptionIds) { @@ -304,8 +313,8 @@ _.extend(Meteor._LivedataSession.prototype, { if (_.has(self.collectionViews, collectionName)) { return self.collectionViews[collectionName]; } - var ret = new Meteor._SessionCollectionView(collectionName, - self.getSendCallbacks()); + var ret = new SessionCollectionView(collectionName, + self.getSendCallbacks()); self.collectionViews[collectionName] = ret; return ret; }, @@ -343,8 +352,8 @@ _.extend(Meteor._LivedataSession.prototype, { self.last_connect_time = +(new Date); _.each(self.out_queue, function (msg) { if (Meteor._printSentDDP) - Meteor._debug("Sent DDP", Meteor._stringifyDDP(msg)); - self.socket.send(Meteor._stringifyDDP(msg)); + Meteor._debug("Sent DDP", stringifyDDP(msg)); + self.socket.send(stringifyDDP(msg)); }); self.out_queue = []; @@ -361,7 +370,7 @@ _.extend(Meteor._LivedataSession.prototype, { var self = this; // Make a shallow copy of the set of universal handlers and start them. If // additional universal publishers start while we're running them (due to - // yielding), they will run separately as part of _LivedataServer.publish. + // yielding), they will run separately as part of Server.publish. var handlers = _.clone(self.server.universal_publish_handlers); _.each(handlers, function (handler) { self._startSubscription(handler); @@ -426,9 +435,9 @@ _.extend(Meteor._LivedataSession.prototype, { send: function (msg) { var self = this; if (Meteor._printSentDDP) - Meteor._debug("Sent DDP", Meteor._stringifyDDP(msg)); + Meteor._debug("Sent DDP", stringifyDDP(msg)); if (self.socket) - self.socket.send(Meteor._stringifyDDP(msg)); + self.socket.send(stringifyDDP(msg)); else self.out_queue.push(msg); }, @@ -546,7 +555,7 @@ _.extend(Meteor._LivedataSession.prototype, { // set up to mark the method as satisfied once all observers // (and subscriptions) have reacted to any writes that were // done. - var fence = new Meteor._WriteFence; + var fence = new DDP._WriteFence; fence.onAllCommitted(function () { // Retire the fence so that future writes are allowed. // This means that callbacks like timers are free to use @@ -584,15 +593,15 @@ _.extend(Meteor._LivedataSession.prototype, { self._setUserId(userId); }; - var invocation = new Meteor._MethodInvocation({ + var invocation = new MethodInvocation({ isSimulation: false, userId: self.userId, setUserId: setUserId, unblock: unblock, sessionData: self.sessionData }); try { - var result = Meteor._CurrentWriteFence.withValue(fence, function () { - return Meteor._CurrentInvocation.withValue(invocation, function () { + var result = DDP._CurrentWriteFence.withValue(fence, function () { + return DDP._CurrentInvocation.withValue(invocation, function () { return maybeAuditArgumentChecks( handler, invocation, msg.params, "call to '" + msg.method + "'"); }); @@ -714,7 +723,7 @@ _.extend(Meteor._LivedataSession.prototype, { _startSubscription: function (handler, subId, params, name) { var self = this; - var sub = new Meteor._LivedataSubscription( + var sub = new Subscription( self, handler, subId, params, name); if (subId) self._namedSubs[subId] = sub; @@ -761,15 +770,14 @@ _.extend(Meteor._LivedataSession.prototype, { }); /******************************************************************************/ -/* LivedataSubscription */ +/* Subscription */ /******************************************************************************/ // ctor for a sub handle: the input to each publish function -Meteor._LivedataSubscription = function ( +var Subscription = function ( session, handler, subscriptionId, params, name) { var self = this; - // LivedataSession - self._session = session; + self._session = session; // type is Session self._handler = handler; @@ -815,12 +823,12 @@ Meteor._LivedataSubscription = function ( // a ddp consumer that isn't minimongo self._idFilter = { - idStringify: Meteor.idStringify, - idParse: Meteor.idParse + idStringify: LocalCollection._idStringify, + idParse: LocalCollection._idParse }; }; -_.extend(Meteor._LivedataSubscription.prototype, { +_.extend(Subscription.prototype, { _runHandler: function () { var self = this; try { @@ -925,14 +933,14 @@ _.extend(Meteor._LivedataSubscription.prototype, { }); }, - // Returns a new _LivedataSubscription for the same session with the same - // initial creation parameters. This isn't a clone: it doesn't have the same - // _documents cache, stopped state or callbacks; may have a different - // _subscriptionHandle, and gets its userId from the session, not from this - // object. + // Returns a new Subscription for the same session with the same + // initial creation parameters. This isn't a clone: it doesn't have + // the same _documents cache, stopped state or callbacks; may have a + // different _subscriptionHandle, and gets its userId from the + // session, not from this object. _recreate: function () { var self = this; - return new Meteor._LivedataSubscription( + return new Subscription( self._session, self._handler, self._subscriptionId, self._params); }, @@ -1004,11 +1012,10 @@ _.extend(Meteor._LivedataSubscription.prototype, { }); /******************************************************************************/ -/* LivedataServer */ +/* Server */ /******************************************************************************/ - -Meteor._LivedataServer = function () { +Server = function () { var self = this; self.publish_handlers = {}; @@ -1021,7 +1028,7 @@ Meteor._LivedataServer = function () { self.sessions = {}; // map from id to session - self.stream_server = new Meteor._DdpStreamServer; + self.stream_server = new StreamServer; self.stream_server.register(function (socket) { // socket implements the SockJSConnection interface @@ -1031,7 +1038,7 @@ Meteor._LivedataServer = function () { var msg = {msg: 'error', reason: reason}; if (offendingMessage) msg.offendingMessage = offendingMessage; - socket.send(Meteor._stringifyDDP(msg)); + socket.send(stringifyDDP(msg)); }; socket.on('data', function (raw_msg) { @@ -1040,7 +1047,7 @@ Meteor._LivedataServer = function () { } try { try { - var msg = Meteor._parseDDP(raw_msg); + var msg = parseDDP(raw_msg); } catch (err) { sendError('Parse error'); return; @@ -1099,22 +1106,21 @@ Meteor._LivedataServer = function () { }, 1 * 60 * 1000); }; -_.extend(Meteor._LivedataServer.prototype, { +_.extend(Server.prototype, { _handleConnect: function (socket, msg) { var self = this; // In the future, handle session resumption: something like: // socket.meteor_session = self.sessions[msg.session] - var version = Meteor._LivedataServer._calculateVersion( - msg.support, Meteor._SUPPORTED_DDP_VERSIONS); + var version = calculateVersion(msg.support, SUPPORTED_DDP_VERSIONS); if (msg.version === version) { // Creating a new session - socket.meteor_session = new Meteor._LivedataSession(self, version); + socket.meteor_session = new Session(self, version); self.sessions[socket.meteor_session.id] = socket.meteor_session; - socket.send(Meteor._stringifyDDP({msg: 'connected', + socket.send(stringifyDDP({msg: 'connected', session: socket.meteor_session.id})); // will kick off previous connection, if any socket.meteor_session.connect(socket); @@ -1129,11 +1135,11 @@ _.extend(Meteor._LivedataServer.prototype, { // floor. We don't want to confuse things. socket.removeAllListeners('data'); setTimeout(function () { - socket.send(Meteor._stringifyDDP({msg: 'failed', version: version})); + socket.send(stringifyDDP({msg: 'failed', version: version})); socket.close(); }, timeout); } else { - socket.send(Meteor._stringifyDDP({msg: 'failed', version: version})); + socket.send(stringifyDDP({msg: 'failed', version: version})); socket.close(); } }, @@ -1265,7 +1271,7 @@ _.extend(Meteor._LivedataServer.prototype, { var setUserId = function() { throw new Error("Can't call setUserId on a server initiated method call"); }; - var currentInvocation = Meteor._CurrentInvocation.get(); + var currentInvocation = DDP._CurrentInvocation.get(); if (currentInvocation) { userId = currentInvocation.userId; setUserId = function(userId) { @@ -1273,13 +1279,13 @@ _.extend(Meteor._LivedataServer.prototype, { }; } - var invocation = new Meteor._MethodInvocation({ + var invocation = new MethodInvocation({ isSimulation: false, userId: userId, setUserId: setUserId, sessionData: self.sessionData }); try { - var result = Meteor._CurrentInvocation.withValue(invocation, function () { + var result = DDP._CurrentInvocation.withValue(invocation, function () { return maybeAuditArgumentChecks( handler, invocation, args, "internal call to '" + name + "'"); }); @@ -1322,8 +1328,8 @@ _.extend(Meteor._LivedataServer.prototype, { } }); -Meteor._LivedataServer._calculateVersion = function (clientSupportedVersions, - serverSupportedVersions) { +var calculateVersion = function (clientSupportedVersions, + serverSupportedVersions) { var correctVersion = _.find(clientSupportedVersions, function (version) { return _.contains(serverSupportedVersions, version); }); @@ -1333,6 +1339,10 @@ Meteor._LivedataServer._calculateVersion = function (clientSupportedVersions, return correctVersion; }; +// @export _LivedataTest.calculateVersion +_LivedataTest.calculateVersion = calculateVersion; + + // "blind" exceptions other than those that were deliberately thrown to signal // errors to the client var wrapInternalException = function (exception, context) { @@ -1358,9 +1368,17 @@ var wrapInternalException = function (exception, context) { return new Meteor.Error(500, "Internal server error"); }; +// Private interface for 'audit-argument-checks' package. +// +// @export DDP._setAuditArgumentChecks +var shouldAuditArgumentChecks = false; +DDP._setAuditArgumentChecks = function (value) { + shouldAuditArgumentChecks = value; +}; + var maybeAuditArgumentChecks = function (f, context, args, description) { args = args || []; - if (Meteor._LivedataServer._auditArgumentChecks) { + if (shouldAuditArgumentChecks) { return Match._failIfArgumentsAreNotAllChecked( f, context, args, description); } diff --git a/packages/livedata/livedata_tests.js b/packages/livedata/livedata_tests.js index 14b612ce9f..d727994571 100644 --- a/packages/livedata/livedata_tests.js +++ b/packages/livedata/livedata_tests.js @@ -32,8 +32,7 @@ if (Meteor.isServer) { Tinytest.add("livedata - version negotiation", function (test) { var versionCheck = function (clientVersions, serverVersions, expected) { test.equal( - Meteor._LivedataServer._calculateVersion(clientVersions, - serverVersions), + _LivedataTest.calculateVersion(clientVersions, serverVersions), expected); }; @@ -287,9 +286,9 @@ testAsyncMulti("livedata - compound methods", [ } ]); -// Replaces the LivedataConnection's `_livedata_data` method to push -// incoming messages on a given collection to an array. This can be -// used to verify that the right data is sent on the wire +// Replaces the Connection's `_livedata_data` method to push incoming +// messages on a given collection to an array. This can be used to +// verify that the right data is sent on the wire // // @param messages {Array} The array to which to append the messages // @return {Function} A function to call to undo the eavesdropping @@ -321,7 +320,7 @@ if (Meteor.isClient) { function(test, expect) { var messages = []; var undoEavesdrop = eavesdropOnCollection( - Meteor.default_connection, "objectsWithUsers", messages); + Meteor.connection, "objectsWithUsers", messages); // A helper for testing incoming set and unset messages // XXX should this be extracted as a general helper together with @@ -505,8 +504,8 @@ if (Meteor.isClient) { testAsyncMulti("livedata - publisher errors", (function () { // Use a separate connection so that we can safely check to see if // conn._subscriptions is empty. - var conn = new Meteor._LivedataConnection('/', - {reloadWithOutstanding: true}); + var conn = new _LivedataTest.Connection('/', + {reloadWithOutstanding: true}); var collName = Random.id(); var coll = new Meteor.Collection(collName, {connection: conn}); var errorFromRerun; @@ -614,7 +613,7 @@ if (Meteor.isServer) { testAsyncMulti("livedata - connect works from both client and server", [ function (test, expect) { var self = this; - self.conn = Meteor.connect(Meteor.absoluteUrl()); + self.conn = DDP.connect(Meteor.absoluteUrl()); pollUntil(expect, function () { return self.conn.status().connected; }, 10000); @@ -638,7 +637,7 @@ if (Meteor.isServer) { testAsyncMulti("livedata - method call on server blocks in a fiber way", [ function (test, expect) { var self = this; - self.conn = Meteor.connect(Meteor.absoluteUrl()); + self.conn = DDP.connect(Meteor.absoluteUrl()); pollUntil(expect, function () { return self.conn.status().connected; }, 10000); @@ -658,7 +657,7 @@ if (Meteor.isServer) { testAsyncMulti("livedata - connect fails to unknown place", [ function (test, expect) { var self = this; - self.conn = Meteor.connect("example.com"); + self.conn = DDP.connect("example.com"); Meteor.setTimeout(expect(function () { test.isFalse(self.conn.status().connected, "Not connected"); }), 500); diff --git a/packages/livedata/package.js b/packages/livedata/package.js index 50ed7bd94c..bfe256fcfc 100644 --- a/packages/livedata/package.js +++ b/packages/livedata/package.js @@ -24,7 +24,8 @@ Package.on_use(function (api) { api.add_files('stream_client_common.js', ['client', 'server']); api.add_files('stream_server.js', 'server'); - // we depend on LocalCollection._diffObjects and ._applyChanges. + // we depend on LocalCollection._diffObjects, _applyChanges, + // _idParse, _idStringify. api.use('minimongo', ['client', 'server']); api.add_files('writefence.js', 'server'); @@ -45,7 +46,7 @@ Package.on_test(function (api) { api.use('livedata', ['client', 'server']); api.use('mongo-livedata', ['client', 'server']); api.use('test-helpers', ['client', 'server']); - api.use(['underscore', 'tinytest', 'random', 'deps']); + api.use(['underscore', 'tinytest', 'random', 'deps', 'minimongo']); api.add_files('livedata_connection_tests.js', ['client', 'server']); api.add_files('livedata_tests.js', ['client', 'server']); diff --git a/packages/livedata/server_convenience.js b/packages/livedata/server_convenience.js index ba03a585cc..af060641f4 100644 --- a/packages/livedata/server_convenience.js +++ b/packages/livedata/server_convenience.js @@ -1,38 +1,48 @@ -_.extend(Meteor, { - default_server: null, - refresh: function (notification) { - } -}); +// @export Meteor.server +Meteor.server = null; -// Only create a server (and map publish, methods, call, etc onto Meteor) if we -// are in an environment with a HTTP server (as opposed to, eg, a command-line -// tool). +// Note that this is redefined below if we in fact start a server. +// @export Meteor.refresh +Meteor.refresh = function (notification) { +}; + +// Only create a server if we are in an environment with a HTTP server +// (as opposed to, eg, a command-line tool). +// +// @export Meteor.publish +// @export Meteor.methods +// @export Meteor.call +// @export Meteor.apply if (Package.webapp) { if (process.env.DDP_DEFAULT_CONNECTION_URL) { __meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL = process.env.DDP_DEFAULT_CONNECTION_URL; } - Meteor.default_server = new Meteor._LivedataServer; + Meteor.server = new Server; Meteor.refresh = function (notification) { - var fence = Meteor._CurrentWriteFence.get(); + var fence = DDP._CurrentWriteFence.get(); if (fence) { // Block the write fence until all of the invalidations have // landed. var proxy_write = fence.beginWrite(); } - Meteor._InvalidationCrossbar.fire(notification, function () { + DDP._InvalidationCrossbar.fire(notification, function () { if (proxy_write) proxy_write.committed(); }); }; - // Proxy the public methods of Meteor.default_server so they can + // Proxy the public methods of Meteor.server so they can // be called directly on Meteor. _.each(['publish', 'methods', 'call', 'apply'], function (name) { - Meteor[name] = _.bind(Meteor.default_server[name], - Meteor.default_server); + Meteor[name] = _.bind(Meteor.server[name], Meteor.server); }); } + +// Meteor.server used to be called Meteor.default_server. Provide +// backcompat as a courtesy even though it was never documented. +// XXX remove this after a while +Meteor.default_server = Meteor.server; diff --git a/packages/livedata/session_view_tests.js b/packages/livedata/session_view_tests.js index c1daee49f4..6f590c3026 100644 --- a/packages/livedata/session_view_tests.js +++ b/packages/livedata/session_view_tests.js @@ -1,6 +1,6 @@ var newView = function(test) { var results = []; - var view = new Meteor._SessionCollectionView('test', { + var view = new _LivedataTest.SessionCollectionView('test', { added: function (collection, id, fields) { results.push({fun: 'added', id: id, fields: fields}); }, diff --git a/packages/livedata/stream_client_common.js b/packages/livedata/stream_client_common.js index 25e5be4bd7..e03c0e56cd 100644 --- a/packages/livedata/stream_client_common.js +++ b/packages/livedata/stream_client_common.js @@ -60,7 +60,20 @@ var translateUrl = function(url, newSchemeBase, subPath) { return url + "/" + subPath; }; -_.extend(Meteor._DdpClientStream.prototype, { +toSockjsUrl = function (url) { + return translateUrl(url, "http", "sockjs"); +}; + +toWebsocketUrl = function (url) { + var ret = translateUrl(url, "ws", "websocket"); + return ret; +}; + +// @export _LivedataTest.toSockjsUrl +_LivedataTest.toSockjsUrl = toSockjsUrl; + + +_.extend(ClientStream.prototype, { // Register for callbacks. on: function (name, callback) { @@ -261,15 +274,3 @@ _.extend(Meteor._DdpClientStream.prototype, { return self.currentStatus; } }); - -_.extend(Meteor._DdpClientStream, { - - _toSockjsUrl: function (url) { - return translateUrl(url, "http", "sockjs"); - }, - - _toWebsocketUrl: function (url) { - var ret = translateUrl(url, "ws", "websocket"); - return ret; - } -}); diff --git a/packages/livedata/stream_client_nodejs.js b/packages/livedata/stream_client_nodejs.js index 1c4b0f308b..70338a21dd 100644 --- a/packages/livedata/stream_client_nodejs.js +++ b/packages/livedata/stream_client_nodejs.js @@ -9,7 +9,7 @@ // We don't do any heartbeating. (The logic that did this in sockjs was removed, // because it used a built-in sockjs mechanism. We could do it with WebSocket // ping frames or with DDP-level messages.) -Meteor._DdpClientStream = function (endpoint) { +ClientStream = function (endpoint) { var self = this; // WebSocket-Node https://github.com/Worlize/WebSocket-Node @@ -47,7 +47,7 @@ Meteor._DdpClientStream = function (endpoint) { self._launchConnection(); }; -_.extend(Meteor._DdpClientStream.prototype, { +_.extend(ClientStream.prototype, { // data is a utf8 string. Data sent while not connected is dropped on // the floor, and it is up the user of this API to retransmit lost @@ -170,7 +170,7 @@ _.extend(Meteor._DdpClientStream.prototype, { // a protocol and the server doesn't send one back (and sockjs // doesn't). also, related: I guess we have to accept that // 'stream' is ddp-specific - self.client.connect(Meteor._DdpClientStream._toWebsocketUrl(self.endpoint)); + self.client.connect(toWebsocketUrl(self.endpoint)); if (self.connectionTimer) clearTimeout(self.connectionTimer); diff --git a/packages/livedata/stream_client_sockjs.js b/packages/livedata/stream_client_sockjs.js index 07d51781d5..db5aa08ef0 100644 --- a/packages/livedata/stream_client_sockjs.js +++ b/packages/livedata/stream_client_sockjs.js @@ -1,7 +1,7 @@ // @param url {String} URL to Meteor app // "http://subdomain.meteor.com/" or "/" or // "ddp+sockjs://foo-**.meteor.com/sockjs" -Meteor._DdpClientStream = function (url) { +ClientStream = function (url) { var self = this; self._initCommon(); @@ -34,7 +34,7 @@ Meteor._DdpClientStream = function (url) { self._launchConnection(); }; -_.extend(Meteor._DdpClientStream.prototype, { +_.extend(ClientStream.prototype, { // data is a utf8 string. Data sent while not connected is dropped on // the floor, and it is up the user of this API to retransmit lost @@ -168,8 +168,7 @@ _.extend(Meteor._DdpClientStream.prototype, { // can connect to random hostnames and get around browser per-host // connection limits. self.socket = new SockJS( - Meteor._DdpClientStream._toSockjsUrl(self.rawUrl), - undefined, { + toSockjsUrl(self.rawUrl), undefined, { debug: false, protocols_whitelist: self._sockjsProtocolsWhitelist() }); self.socket.onmessage = function (data) { diff --git a/packages/livedata/stream_server.js b/packages/livedata/stream_server.js index 2ee54b17d1..e00a42ada9 100644 --- a/packages/livedata/stream_server.js +++ b/packages/livedata/stream_server.js @@ -9,7 +9,7 @@ __meteor_runtime_config__.serverId = var pathPrefix = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || ""; -Meteor._DdpStreamServer = function () { +StreamServer = function () { var self = this; self.registration_callbacks = []; self.open_sockets = []; @@ -80,7 +80,7 @@ Meteor._DdpStreamServer = function () { }; -_.extend(Meteor._DdpStreamServer.prototype, { +_.extend(StreamServer.prototype, { // call my callback when a new socket connects. // also call it for all current connections. register: function (callback) { diff --git a/packages/livedata/stream_tests.js b/packages/livedata/stream_tests.js index e2e250a6a1..89eaf0f847 100644 --- a/packages/livedata/stream_tests.js +++ b/packages/livedata/stream_tests.js @@ -26,7 +26,7 @@ testAsyncMulti("stream - reconnect", [ })); if (Meteor.status().status !== "connected") - Meteor.default_connection._stream.on('reset', callback); + Meteor.connection._stream.on('reset', callback); else callback(); } @@ -89,7 +89,7 @@ testAsyncMulti("stream - disconnect remains offline", [ Tinytest.add("stream - sockjs urls are computed correctly", function(test) { var testHasSockjsUrl = function(raw, expectedSockjsUrl) { - var actual = Meteor._DdpClientStream._toSockjsUrl(raw); + var actual = _LivedataTest.toSockjsUrl(raw); if (expectedSockjsUrl instanceof RegExp) test.isTrue(actual.match(expectedSockjsUrl), actual); else diff --git a/packages/livedata/writefence.js b/packages/livedata/writefence.js index 6a665ab0e9..0239610236 100644 --- a/packages/livedata/writefence.js +++ b/packages/livedata/writefence.js @@ -4,7 +4,9 @@ var Future = Npm.require(path.join('fibers', 'future')); // A write fence collects a group of writes, and provides a callback // when all of the writes are fully committed and propagated (all // observers have been notified of the write and acknowledged it.) -Meteor._WriteFence = function () { +// +// @export DDP._WriteFence +DDP._WriteFence = function () { var self = this; self.armed = false; @@ -17,9 +19,11 @@ Meteor._WriteFence = function () { // The current write fence. When there is a current write fence, code // that writes to databases should register their writes with it using // beginWrite(). -Meteor._CurrentWriteFence = new Meteor.EnvironmentVariable; +// +// @export DDP._CurrentWriteFence +DDP._CurrentWriteFence = new Meteor.EnvironmentVariable; -_.extend(Meteor._WriteFence.prototype, { +_.extend(DDP._WriteFence.prototype, { // Start tracking a write, and return an object to represent it. The // object has a single method, committed(). This method should be // called when the write is fully committed and propagated. You can diff --git a/packages/localstorage/localstorage.js b/packages/localstorage/localstorage.js index 1cf6e30731..762f224237 100644 --- a/packages/localstorage/localstorage.js +++ b/packages/localstorage/localstorage.js @@ -1,3 +1,6 @@ +// This is not an ideal name, but we can change it later. +// @export Meteor._localStorage + if (window.localStorage) { Meteor._localStorage = { getItem: function (key) { diff --git a/packages/logging/package.js b/packages/logging/package.js index fe3eed719e..3ebb486dd3 100644 --- a/packages/logging/package.js +++ b/packages/logging/package.js @@ -10,13 +10,11 @@ Npm.depends({ Package.on_use(function (api, where) { where = where || ['client', 'server']; api.use(['underscore', 'ejson'], where); - api.add_files('debug.js', where); api.add_files('logging.js', where); }); Package.on_test(function (api) { api.use(['tinytest', 'underscore', 'ejson']); api.use('logging', ['client', 'server']); - api.add_files('debug_test.js', 'client'); api.add_files('logging_test.js', ['server', 'client']); }); diff --git a/packages/meteor/client_environment.js b/packages/meteor/client_environment.js index 7d066fb028..6ceade5328 100644 --- a/packages/meteor/client_environment.js +++ b/packages/meteor/client_environment.js @@ -1,9 +1,10 @@ -// @export Meteor -Meteor = { - isClient: true, - isServer: false -}; +// @export Meteor.isClient +Meteor.isClient = true; +// @export Meteor.isServer +Meteor.isServer = false; + +// @export Meteor.settings if (typeof __meteor_runtime_config__ === 'object' && __meteor_runtime_config__.PUBLIC_SETTINGS) { Meteor.settings = { public: __meteor_runtime_config__.PUBLIC_SETTINGS }; diff --git a/packages/logging/debug.js b/packages/meteor/debug.js similarity index 97% rename from packages/logging/debug.js rename to packages/meteor/debug.js index 0a09dbdcc0..87fb134647 100644 --- a/packages/logging/debug.js +++ b/packages/meteor/debug.js @@ -10,6 +10,8 @@ var suppress = 0; // _debug. the intent is for this message to go to the terminal and // be very visible. if you change _debug to go someplace else, etc, // please fix the autopublish code to do something reasonable. +// +// @export Meteor._debug Meteor._debug = function (/* arguments */) { if (suppress) { suppress--; @@ -55,6 +57,8 @@ Meteor._debug = function (/* arguments */) { // Suppress the next 'count' Meteor._debug messsages. Use this to // stop tests from spamming the console. +// +// @export Meteor._suppress_log Meteor._suppress_log = function (count) { suppress += count; }; diff --git a/packages/logging/debug_test.js b/packages/meteor/debug_test.js similarity index 100% rename from packages/logging/debug_test.js rename to packages/meteor/debug_test.js diff --git a/packages/meteor/dynamics_browser.js b/packages/meteor/dynamics_browser.js index b2b7c34a88..9b358f4c14 100644 --- a/packages/meteor/dynamics_browser.js +++ b/packages/meteor/dynamics_browser.js @@ -3,6 +3,7 @@ var nextSlot = 0; var currentValues = []; +// @export Meteor.EnvironmentVariable Meteor.EnvironmentVariable = function () { this.slot = nextSlot++; }; @@ -24,6 +25,7 @@ _.extend(Meteor.EnvironmentVariable.prototype, { } }); +// @export Meteor.bindEnvironment Meteor.bindEnvironment = function (func, onException, _this) { // needed in order to be able to create closures inside func and // have the closed variables not change back to their original diff --git a/packages/meteor/dynamics_nodejs.js b/packages/meteor/dynamics_nodejs.js index dfef4e1c90..5c39a42b82 100644 --- a/packages/meteor/dynamics_nodejs.js +++ b/packages/meteor/dynamics_nodejs.js @@ -4,6 +4,7 @@ var Fiber = Npm.require('fibers'); var nextSlot = 0; +// @export Meteor.EnvironmentVariable Meteor.EnvironmentVariable = function () { this.slot = nextSlot++; }; @@ -50,6 +51,8 @@ _.extend(Meteor.EnvironmentVariable.prototype, { // If it's called inside a fiber, it works normally (the // return value of the function will be passed through, and no new // fiber will be created.) +// +// @export Meteor.bindEnvironment Meteor.bindEnvironment = function (func, onException, _this) { var boundValues = _.clone(Fiber.current._meteor_dynamics || []); diff --git a/packages/meteor/errors.js b/packages/meteor/errors.js index 7a63778556..a2f93f63f7 100644 --- a/packages/meteor/errors.js +++ b/packages/meteor/errors.js @@ -9,6 +9,8 @@ var inherits = function (child, parent) { // Makes an error subclass which properly contains a stack trace in most // environments. constructor can set fields on `this` (and should probably set // `message`, which is what gets displayed at the top of a stack trace). +// +// @export Meteor.makeErrorType Meteor.makeErrorType = function (name, constructor) { var errorClass = function (/*arguments*/) { var self = this; @@ -37,3 +39,45 @@ Meteor.makeErrorType = function (name, constructor) { return errorClass; }; + +// This should probably be in the livedata package, but we don't want +// to require you to use the livedata package to get it. Eventually we +// should probably rename it to DDP.Error and put it back in the +// 'livedata' package (which we should rename to 'ddp' also.) +// +// Note: The DDP server assumes that Meteor.Error EJSON-serializes as an object +// containing 'error' and optionally 'reason' and 'details'. +// The DDP client manually puts these into Meteor.Error objects. (We don't use +// EJSON.addType here because the type is determined by location in the +// protocol, not text on the wire.) +// +// @export Meteor.Error +Meteor.Error = Meteor.makeErrorType( + "Meteor.Error", + function (error, reason, details) { + var self = this; + + // Currently, a numeric code, likely similar to a HTTP code (eg, + // 404, 500). That is likely to change though. + self.error = error; + + // Optional: A short human-readable summary of the error. Not + // intended to be shown to end users, just developers. ("Not Found", + // "Internal Server Error") + self.reason = reason; + + // Optional: Additional information about the error, say for + // debugging. It might be a (textual) stack trace if the server is + // willing to provide one. The corresponding thing in HTTP would be + // the body of a 404 or 500 response. (The difference is that we + // never expect this to be shown to end users, only developers, so + // it doesn't need to be pretty.) + self.details = details; + + // This is what gets displayed at the top of a stack trace. Current + // format is "[404]" (if no reason is set) or "File not found [404]" + if (self.reason) + self.message = self.reason + ' [' + self.error + ']'; + else + self.message = '[' + self.error + ']'; + }); diff --git a/packages/meteor/fiber_helpers.js b/packages/meteor/fiber_helpers.js index 19b931d370..26f4c61937 100644 --- a/packages/meteor/fiber_helpers.js +++ b/packages/meteor/fiber_helpers.js @@ -2,6 +2,7 @@ var path = Npm.require('path'); var Fiber = Npm.require('fibers'); var Future = Npm.require(path.join('fibers', 'future')); +// @export Meteor._noYieldsAllowed Meteor._noYieldsAllowed = function (f) { // "Fiber" and "yield" are both in the global namespace. The yield function is // at both "yield" and "Fiber.yield". (It's also at require('fibers').yield @@ -35,6 +36,8 @@ Meteor._noYieldsAllowed = function (f) { // XXX break this out into an NPM module? // XXX could maybe use the npm 'schlock' module instead, which would // also support multiple concurrent "read" tasks +// +// @export Meteor._SynchronousQueue Meteor._SynchronousQueue = function () { var self = this; // List of tasks to run (not including a currently-running task if any). Each @@ -168,6 +171,8 @@ _.extend(Meteor._SynchronousQueue.prototype, { // Sleep. Mostly used for debugging (eg, inserting latency into server // methods). +// +// @export Meteor._sleepForMs Meteor._sleepForMs = function (ms) { var fiber = Fiber.current; setTimeout(function() { diff --git a/packages/meteor/fiber_stubs_client.js b/packages/meteor/fiber_stubs_client.js index 4e419c07da..75b571beb2 100644 --- a/packages/meteor/fiber_stubs_client.js +++ b/packages/meteor/fiber_stubs_client.js @@ -2,12 +2,16 @@ // to use a queue too, and also to call noYieldsAllowed. // The client has no ability to yield, so noYieldsAllowed is a noop. +// +// @export Meteor._noYieldsAllowed Meteor._noYieldsAllowed = function (f) { return f(); }; // An even simpler queue of tasks than the fiber-enabled one. This one just // runs all the tasks when you call runTask or flush, synchronously. +// +// @export Meteor._SynchronousQueue Meteor._SynchronousQueue = function () { var self = this; self._tasks = []; diff --git a/packages/meteor/helpers.js b/packages/meteor/helpers.js index 3bd83671d6..054fd0e8dc 100644 --- a/packages/meteor/helpers.js +++ b/packages/meteor/helpers.js @@ -1,15 +1,19 @@ -// XXX namespacing -- find a better home for these? - if (Meteor.isServer) var Future = Npm.require('fibers/future'); +// @export Meteor.release if (typeof __meteor_runtime_config__ === 'object' && __meteor_runtime_config__.meteorRelease) Meteor.release = __meteor_runtime_config__.meteorRelease; +// XXX find a better home for these? Ideally they would be _.get, +// _.ensure, _.delete.. + _.extend(Meteor, { // _get(a,b,c,d) returns a[b][c][d], or else undefined if a[b] or // a[b][c] doesn't exist. + // + // @export Meteor._get _get: function (obj /*, arguments */) { for (var i = 1; i < arguments.length; i++) { if (!(arguments[i] in obj)) @@ -21,6 +25,8 @@ _.extend(Meteor, { // _ensure(a,b,c,d) ensures that a[b][c][d] exists. If it does not, // it is created and set to {}. Either way, it is returned. + // + // @export Meteor._ensure _ensure: function (obj /*, arguments */) { for (var i = 1; i < arguments.length; i++) { var key = arguments[i]; @@ -34,6 +40,8 @@ _.extend(Meteor, { // _delete(a, b, c, d) deletes a[b][c][d], then a[b][c] unless it // isn't empty, then a[b] unless it isn't empty. + // + // @export Meteor._delete _delete: function (obj /*, arguments */) { var stack = [obj]; var leaf = true; @@ -69,6 +77,8 @@ _.extend(Meteor, { // fs.open(pathname, flags, [mode], [callback]) // For maximum effectiveness and least confusion, wrapAsync should be used on // functions where the callback is the only argument of type Function. + // + // @export Meteor._wrapAsync _wrapAsync: function (fn) { return function (/* arguments */) { var self = this; diff --git a/packages/meteor/package.js b/packages/meteor/package.js index 95b38dfe9f..1ce4b05b12 100644 --- a/packages/meteor/package.js +++ b/packages/meteor/package.js @@ -21,6 +21,9 @@ Package.on_use(function (api, where) { api.add_files('errors.js', ['client', 'server']); api.add_files('fiber_helpers.js', 'server'); api.add_files('fiber_stubs_client.js', 'client'); + api.add_files('startup_client.js', ['client']); + api.add_files('startup_server.js', ['server']); + api.add_files('debug.js', ['client', 'server']); // dynamic variables, bindEnvironment // XXX move into a separate package? @@ -49,6 +52,8 @@ Package.on_test(function (api) { api.add_files('timers_tests.js', ['client', 'server']); + api.add_files('debug_test.js', 'client'); + api.add_files('bare_test_setup.js', 'client', {bare: true}); api.add_files('bare_tests.js', 'client'); }); diff --git a/packages/meteor/server_environment.js b/packages/meteor/server_environment.js index 4a9eb01991..12581f47f2 100644 --- a/packages/meteor/server_environment.js +++ b/packages/meteor/server_environment.js @@ -1,9 +1,10 @@ -// @export Meteor -Meteor = { - isClient: false, - isServer: true -}; +// @export Meteor.isClient +Meteor.isClient = false; +// @export Meteor.isServer +Meteor.isServer = true; + +// @export Meteor.settings Meteor.settings = {}; if (process.env.METEOR_SETTINGS) { try { diff --git a/packages/meteor/setimmediate.js b/packages/meteor/setimmediate.js index 8cf75fbdfc..f58231de57 100644 --- a/packages/meteor/setimmediate.js +++ b/packages/meteor/setimmediate.js @@ -135,6 +135,7 @@ function useTimeout() { } +// @export Meteor._setImmediate Meteor._setImmediate = useSetImmediate() || usePostMessage() || diff --git a/packages/startup/startup_client.js b/packages/meteor/startup_client.js similarity index 97% rename from packages/startup/startup_client.js rename to packages/meteor/startup_client.js index 4c4fe9863d..8ca9d0e4f3 100644 --- a/packages/startup/startup_client.js +++ b/packages/meteor/startup_client.js @@ -19,6 +19,7 @@ if (document.addEventListener) { window.attachEvent('load', ready); } +// @export Meteor.startup Meteor.startup = function (cb) { var doScroll = !document.addEventListener && document.documentElement.doScroll; diff --git a/packages/startup/startup_server.js b/packages/meteor/startup_server.js similarity index 78% rename from packages/startup/startup_server.js rename to packages/meteor/startup_server.js index af0ca8126c..09735334b0 100644 --- a/packages/startup/startup_server.js +++ b/packages/meteor/startup_server.js @@ -1,3 +1,4 @@ +// @export Meteor.startup Meteor.startup = function (callback) { __meteor_bootstrap__.startup_hooks.push(callback); }; diff --git a/packages/meteor/timers.js b/packages/meteor/timers.js index 1c3bc1330a..b7366a7546 100644 --- a/packages/meteor/timers.js +++ b/packages/meteor/timers.js @@ -1,8 +1,9 @@ var withoutInvocation = function (f) { - if (Meteor._CurrentInvocation) { - if (Meteor._CurrentInvocation.get() && Meteor._CurrentInvocation.get().isSimulation) + if (Package.livedata) { + var _CurrentInvocation = Package.livedata.DDP._CurrentInvocation; + if (_CurrentInvocation.get() && _CurrentInvocation.get().isSimulation) throw new Error("Can't set timers inside simulations"); - return function () { Meteor._CurrentInvocation.withValue(null, f); }; + return function () { _CurrentInvocation.withValue(null, f); }; } else return f; @@ -20,18 +21,22 @@ _.extend(Meteor, { // inside a server method are not part of the method invocation and // should clear out the CurrentInvocation environment variable. + // @export Meteor.setTimeout setTimeout: function (f, duration) { return setTimeout(bindAndCatch("setTimeout callback", f), duration); }, + // @export Meteor.setInterval setInterval: function (f, duration) { return setInterval(bindAndCatch("setInterval callback", f), duration); }, + // @export Meteor.clearInterval clearInterval: function(x) { return clearInterval(x); }, + // @export Meteor.clearTimeout clearTimeout: function(x) { return clearTimeout(x); }, @@ -40,6 +45,7 @@ _.extend(Meteor, { // Deps.afterFlush or Node's nextTick (in practice). Then tests can do: // callSomethingThatDefersSomeWork(); // Meteor.defer(expect(somethingThatValidatesThatTheWorkHappened)); + // @export Meteor.defer defer: function (f) { Meteor._setImmediate(bindAndCatch("defer callback", f)); } diff --git a/packages/meteor/url_common.js b/packages/meteor/url_common.js index 9fa045a3d5..ef0f53467e 100644 --- a/packages/meteor/url_common.js +++ b/packages/meteor/url_common.js @@ -1,3 +1,4 @@ +// @export Meteor.absoluteUrl Meteor.absoluteUrl = function (path, options) { // path is optional if (!options && typeof path === 'object') { @@ -41,6 +42,7 @@ if (typeof __meteor_runtime_config__ === "object" && Meteor.absoluteUrl.defaultOptions.rootUrl = __meteor_runtime_config__.ROOT_URL; +// @export Meteor._relativeToSiteRootUrl Meteor._relativeToSiteRootUrl = function (link) { if (typeof __meteor_runtime_config__ === "object" && link.substr(0, 1) === "/") diff --git a/packages/minimongo/minimongo.js b/packages/minimongo/minimongo.js index 164217c596..88d3f12e49 100644 --- a/packages/minimongo/minimongo.js +++ b/packages/minimongo/minimongo.js @@ -200,7 +200,10 @@ LocalCollection.Cursor.prototype._publishCursor = function (sub) { if (! self.collection.name) throw new Error("Can't publish a cursor from a collection without a name."); var collection = self.collection.name; - return Meteor.Collection._publishCursor(self, sub, collection); + + // XXX minimongo should not depend on mongo-livedata! + return Packages['mongo-livedata']. + Meteor.Collection._publishCursor(self, sub, collection); }; LocalCollection._isOrderedChanges = function (callbacks) { @@ -825,6 +828,7 @@ LocalCollection.prototype.resumeObservers = function () { }; +// NB: used by livedata LocalCollection._idStringify = function (id) { if (id instanceof LocalCollection._ObjectID) { return id.valueOf(); @@ -849,7 +853,7 @@ LocalCollection._idStringify = function (id) { }; - +// NB: used by livedata LocalCollection._idParse = function (id) { if (id === "") { return id; @@ -866,11 +870,6 @@ LocalCollection._idParse = function (id) { } }; -if (typeof Meteor !== 'undefined') { - Meteor.idParse = LocalCollection._idParse; - Meteor.idStringify = LocalCollection._idStringify; -} - LocalCollection._makeChangedFields = function (newDoc, oldDoc) { var fields = {}; LocalCollection._diffObjects(oldDoc, newDoc, { diff --git a/packages/minimongo/selector.js b/packages/minimongo/selector.js index 617e12c1bf..0596711908 100644 --- a/packages/minimongo/selector.js +++ b/packages/minimongo/selector.js @@ -329,7 +329,7 @@ LocalCollection._f = { return 9; if (EJSON.isBinary(v)) return 5; - if (v instanceof Meteor.Collection.ObjectID) + if (v instanceof LocalCollection._ObjectID) return 7; return 3; // object diff --git a/packages/mongo-livedata/collection.js b/packages/mongo-livedata/collection.js index 13dfe8c100..a458cbf66d 100644 --- a/packages/mongo-livedata/collection.js +++ b/packages/mongo-livedata/collection.js @@ -1,6 +1,7 @@ -// connection, if given, is a LivedataClient or LivedataServer +// options.connection, if given, is a LivedataClient or LivedataServer // XXX presently there is no way to destroy/clean up a Collection +// @export Meteor.Collection Meteor.Collection = function (name, options) { var self = this; if (! (self instanceof Meteor.Collection)) @@ -55,16 +56,16 @@ Meteor.Collection = function (name, options) { else if (options.connection) self._connection = options.connection; else if (Meteor.isClient) - self._connection = Meteor.default_connection; + self._connection = Meteor.connection; else - self._connection = Meteor.default_server; + self._connection = Meteor.server; if (!options._driver) { - if (name && self._connection === Meteor.default_server && - Meteor._getRemoteCollectionDriver) - options._driver = Meteor._getRemoteCollectionDriver(); + if (name && self._connection === Meteor.server && + typeof getRemoteCollectionDriver !== "undefined") + options._driver = getRemoteCollectionDriver(); else - options._driver = Meteor._LocalCollectionDriver; + options._driver = LocalCollectionDriver; } self._collection = options._driver.open(name, self._connection); @@ -101,7 +102,7 @@ Meteor.Collection = function (name, options) { // Apply an update. // XXX better specify this interface (not in terms of a wire message)? update: function (msg) { - var mongoId = Meteor.idParse(msg.id); + var mongoId = LocalCollection._idParse(msg.id); var doc = self._collection.findOne(mongoId); // Is this a "replace the whole doc" message coming from the quiescence @@ -363,11 +364,11 @@ _.each(["insert", "update", "remove"], function (name) { }; } - if (self._connection && self._connection !== Meteor.default_server) { + if (self._connection && self._connection !== Meteor.server) { // just remote to another endpoint, propagate return value or // exception. - var enclosing = Meteor._CurrentInvocation.get(); + var enclosing = DDP._CurrentInvocation.get(); var alreadyInSimulation = enclosing && enclosing.isSimulation; if (!alreadyInSimulation && name !== "insert") { // If we're about to actually send an RPC, we should throw an error if @@ -586,7 +587,7 @@ Meteor.Collection.prototype._defineMutationMethods = function() { // Minimongo on the server gets no stubs; instead, by default // it wait()s until its result is ready, yielding. // This matches the behavior of macromongo on the server better. - if (Meteor.isClient || self._connection === Meteor.default_server) + if (Meteor.isClient || self._connection === Meteor.server) self._connection.methods(m); } }; diff --git a/packages/mongo-livedata/local_collection_driver.js b/packages/mongo-livedata/local_collection_driver.js index bb90392772..ec78544154 100644 --- a/packages/mongo-livedata/local_collection_driver.js +++ b/packages/mongo-livedata/local_collection_driver.js @@ -1,5 +1,4 @@ -// XXX namespacing -Meteor._LocalCollectionDriver = function () { +LocalCollectionDriver = function () { var self = this; self.noConnCollections = {}; }; @@ -10,7 +9,7 @@ var ensureCollection = function (name, collections) { return collections[name]; }; -_.extend(Meteor._LocalCollectionDriver.prototype, { +_.extend(LocalCollectionDriver.prototype, { open: function (name, conn) { var self = this; if (!name) @@ -27,4 +26,4 @@ _.extend(Meteor._LocalCollectionDriver.prototype, { }); // singleton -Meteor._LocalCollectionDriver = new Meteor._LocalCollectionDriver; +LocalCollectionDriver = new LocalCollectionDriver; diff --git a/packages/mongo-livedata/mongo_driver.js b/packages/mongo-livedata/mongo_driver.js index 9311321825..1904e608cb 100644 --- a/packages/mongo-livedata/mongo_driver.js +++ b/packages/mongo-livedata/mongo_driver.js @@ -82,7 +82,7 @@ var replaceTypes = function (document, atomTransformer) { }; -_Mongo = function (url) { +MongoConnection = function (url) { var self = this; self._connectCallbacks = []; self._liveResultsSets = {}; @@ -122,7 +122,7 @@ _Mongo = function (url) { }); }; -_Mongo.prototype.close = function() { +MongoConnection.prototype.close = function() { var self = this; // Use Future.wrap so that errors get thrown. This happens to // work even outside a fiber since the 'close' method is not @@ -130,7 +130,7 @@ _Mongo.prototype.close = function() { Future.wrap(_.bind(self.db.close, self.db))(true).wait(); }; -_Mongo.prototype._withDb = function (callback) { +MongoConnection.prototype._withDb = function (callback) { var self = this; if (self.db) { callback(self.db); @@ -140,7 +140,7 @@ _Mongo.prototype._withDb = function (callback) { }; // Returns the Mongo Collection object; may yield. -_Mongo.prototype._getCollection = function (collectionName) { +MongoConnection.prototype._getCollection = function (collectionName) { var self = this; var future = new Future; @@ -150,7 +150,8 @@ _Mongo.prototype._getCollection = function (collectionName) { return future.wait(); }; -_Mongo.prototype._createCappedCollection = function (collectionName, byteSize) { +MongoConnection.prototype._createCappedCollection = function (collectionName, + byteSize) { var self = this; var future = new Future(); self._withDb(function (db) { @@ -165,9 +166,9 @@ _Mongo.prototype._createCappedCollection = function (collectionName, byteSize) { // the write, and after observers have been notified (or at least, // after the observer notifiers have added themselves to the write // fence), you should call 'committed()' on the object returned. -_Mongo.prototype._maybeBeginWrite = function () { +MongoConnection.prototype._maybeBeginWrite = function () { var self = this; - var fence = Meteor._CurrentWriteFence.get(); + var fence = DDP._CurrentWriteFence.get(); if (fence) return fence.beginWrite(); else @@ -185,7 +186,7 @@ _Mongo.prototype._maybeBeginWrite = function () { // After making a write (with insert, update, remove), observers are // notified asynchronously. If you want to receive a callback once all // of the observer notifications have landed for your write, do the -// writes inside a write fence (set Meteor._CurrentWriteFence to a new +// writes inside a write fence (set DDP._CurrentWriteFence to a new // _WriteFence, and then set a callback on the write fence.) // // Since our execution environment is single-threaded, this is @@ -208,7 +209,8 @@ var writeCallback = function (write, refresh, callback) { }); }; -_Mongo.prototype._insert = function (collection_name, document, callback) { +MongoConnection.prototype._insert = function (collection_name, document, + callback) { var self = this; if (collection_name === "___meteor_failure_test_collection") { var e = new Error("Failure test"); @@ -236,7 +238,7 @@ _Mongo.prototype._insert = function (collection_name, document, callback) { // Cause queries that may be affected by the selector to poll in this write // fence. -_Mongo.prototype._refresh = function (collectionName, selector) { +MongoConnection.prototype._refresh = function (collectionName, selector) { var self = this; var refreshKey = {collection: collectionName}; // If we know which documents we're removing, don't poll queries that are @@ -253,7 +255,8 @@ _Mongo.prototype._refresh = function (collectionName, selector) { } }; -_Mongo.prototype._remove = function (collection_name, selector, callback) { +MongoConnection.prototype._remove = function (collection_name, selector, + callback) { var self = this; if (collection_name === "___meteor_failure_test_collection") { @@ -281,8 +284,8 @@ _Mongo.prototype._remove = function (collection_name, selector, callback) { } }; -_Mongo.prototype._update = function (collection_name, selector, mod, - options, callback) { +MongoConnection.prototype._update = function (collection_name, selector, mod, + options, callback) { var self = this; if (! callback && options instanceof Function) { @@ -330,13 +333,13 @@ _Mongo.prototype._update = function (collection_name, selector, mod, }; _.each(["insert", "update", "remove"], function (method) { - _Mongo.prototype[method] = function (/* arguments */) { + MongoConnection.prototype[method] = function (/* arguments */) { var self = this; return Meteor._wrapAsync(self["_" + method]).apply(self, arguments); }; }); -_Mongo.prototype.find = function (collectionName, selector, options) { +MongoConnection.prototype.find = function (collectionName, selector, options) { var self = this; if (arguments.length === 1) @@ -346,7 +349,8 @@ _Mongo.prototype.find = function (collectionName, selector, options) { self, new CursorDescription(collectionName, selector, options)); }; -_Mongo.prototype.findOne = function (collection_name, selector, options) { +MongoConnection.prototype.findOne = function (collection_name, selector, + options) { var self = this; if (arguments.length === 1) selector = {}; @@ -358,7 +362,8 @@ _Mongo.prototype.findOne = function (collection_name, selector, options) { // We'll actually design an index API later. For now, we just pass through to // Mongo's, but make it synchronous. -_Mongo.prototype._ensureIndex = function (collectionName, index, options) { +MongoConnection.prototype._ensureIndex = function (collectionName, index, + options) { var self = this; options = _.extend({safe: true}, options); @@ -369,7 +374,7 @@ _Mongo.prototype._ensureIndex = function (collectionName, index, options) { var indexName = collection.ensureIndex(index, options, future.resolver()); future.wait(); }; -_Mongo.prototype._dropIndex = function (collectionName, index) { +MongoConnection.prototype._dropIndex = function (collectionName, index) { var self = this; // This function is only used by test code, not within a method, so we don't @@ -474,8 +479,8 @@ Cursor.prototype.observeChanges = function (callbacks) { self._cursorDescription, ordered, callbacks); }; -_Mongo.prototype._createSynchronousCursor = function (cursorDescription, - useTransform) { +MongoConnection.prototype._createSynchronousCursor = function(cursorDescription, + useTransform) { var self = this; var collection = self._getCollection(cursorDescription.collectionName); @@ -539,7 +544,7 @@ _.extend(SynchronousCursor.prototype, { // ignore this one. (Do this before the transform, since transform might // return some unrelated value.) We don't do this for tailable cursors, // because we want to maintain O(1) memory usage. - var strId = Meteor.idStringify(doc._id); + var strId = LocalCollection._idStringify(doc._id); if (self._visitedIds[strId]) continue; self._visitedIds[strId] = true; } @@ -637,7 +642,7 @@ ObserveHandle.prototype.stop = function () { self._liveResultsSet = null; }; -_Mongo.prototype._observeChanges = function ( +MongoConnection.prototype._observeChanges = function ( cursorDescription, ordered, callbacks) { var self = this; @@ -732,12 +737,12 @@ var LiveResultsSet = function (cursorDescription, mongoHandle, ordered, // database for changes. If this selector specifies specific IDs, specify them // here, so that updates to different specific IDs don't cause us to poll. var listenOnTrigger = function (trigger) { - var listener = Meteor._InvalidationCrossbar.listen( + var listener = DDP._InvalidationCrossbar.listen( trigger, function (notification, complete) { // When someone does a transaction that might affect us, schedule a poll // of the database. If that transaction happens inside of a write fence, // block the fence until we've polled and notified observers. - var fence = Meteor._CurrentWriteFence.get(); + var fence = DDP._CurrentWriteFence.get(); if (fence) self._pendingWrites.push(fence.beginWrite()); // Ensure a poll is scheduled... but if we already know that one is, @@ -962,7 +967,7 @@ _.extend(LiveResultsSet.prototype, { if (_.isEmpty(self._observeHandles) && self._addHandleTasksScheduledButNotPerformed === 0) { // The last observe handle was stopped; call our stop callbacks, which: - // - removes us from the _Mongo's _liveResultsSets map + // - removes us from the MongoConnection's _liveResultsSets map // - stops the poll timer // - removes us from the invalidation crossbar _.each(self._stopCallbacks, function (c) { c(); }); @@ -982,9 +987,9 @@ _.extend(LiveResultsSet.prototype, { // - If you disconnect and reconnect from Mongo, it will essentially restart // the query, which will lead to duplicate results. This is pretty bad, // but if you include a field called 'ts' which is inserted as -// new Meteor._Mongo._Timestamp(0, 0) (which is initialized to the current -// Mongo-style timestamp), we'll be able to find the place to restart -// properly. (This field is specifically understood by Mongo with an +// new _MongoLivedataTest.MongoTimestamp(0, 0) (which is initialized to the +// current Mongo-style timestamp), we'll be able to find the place to +// restart properly. (This field is specifically understood by Mongo with an // optimization which allows it to find the right place to start without // an index on ts. It's how the oplog works.) // - No callbacks are triggered synchronously with the call (there's no @@ -1001,7 +1006,7 @@ _.extend(LiveResultsSet.prototype, { // enough to accurately evaluate the query against the write fence, we // should be able to do this... Of course, minimongo doesn't even support // Mongo Timestamps yet. -_Mongo.prototype._observeChangesTailable = function ( +MongoConnection.prototype._observeChangesTailable = function ( cursorDescription, ordered, callbacks) { var self = this; @@ -1069,7 +1074,8 @@ _Mongo.prototype._observeChangesTailable = function ( }; }; -_.extend(Meteor, { - _Mongo: _Mongo -}); -Meteor._Mongo._Timestamp = MongoDB.Timestamp; +// XXX We probably need to find a better way to expose this. Right now +// it's only used by tests, but in fact you need it in normal +// operation to interact with capped collections. +// @export _MongoLivedataTest.MongoTimestamp +_MongoLivedataTest.MongoTimestamp = MongoDB.Timestamp; diff --git a/packages/mongo-livedata/mongo_livedata_tests.js b/packages/mongo-livedata/mongo_livedata_tests.js index ed339bbcff..afbce53379 100644 --- a/packages/mongo-livedata/mongo_livedata_tests.js +++ b/packages/mongo-livedata/mongo_livedata_tests.js @@ -124,8 +124,8 @@ Tinytest.addAsync("mongo-livedata - basics, " + idGeneration, function (test, on if (Meteor.isClient) { f(); } else { - var fence = new Meteor._WriteFence; - Meteor._CurrentWriteFence.withValue(fence, f); + var fence = new DDP._WriteFence; + DDP._CurrentWriteFence.withValue(fence, f); fence.armAndWait(); } @@ -281,8 +281,8 @@ Tinytest.addAsync("mongo-livedata - fuzz test, " + idGeneration, function(test, if (Meteor.isClient) { f(); } else { - var fence = new Meteor._WriteFence; - Meteor._CurrentWriteFence.withValue(fence, f); + var fence = new DDP._WriteFence; + DDP._CurrentWriteFence.withValue(fence, f); fence.armAndWait(); } }; @@ -360,8 +360,8 @@ var runInFence = function (f) { if (Meteor.isClient) { f(); } else { - var fence = new Meteor._WriteFence; - Meteor._CurrentWriteFence.withValue(fence, f); + var fence = new DDP._WriteFence; + DDP._CurrentWriteFence.withValue(fence, f); fence.armAndWait(); } }; @@ -744,7 +744,8 @@ testAsyncMulti('mongo-livedata - document goes through a transform, ' + idGenera testAsyncMulti('mongo-livedata - document with binary data, ' + idGeneration, [ function (test, expect) { - var bin = EJSON._base64Decode( + // XXX probably shouldn't use EJSON's private test symbols + var bin = _EJSONTest.base64Decode( "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyBy" + "ZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJv" + "bSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhl" + @@ -969,7 +970,7 @@ if (Meteor.isServer) { return C.find({a: 0}); }); - self.conn = Meteor.connect(Meteor.absoluteUrl()); + self.conn = DDP.connect(Meteor.absoluteUrl()); pollUntil(expect, function () { return self.conn.status().connected; }, 10000); @@ -1022,7 +1023,7 @@ if (Meteor.isServer) { return self.C.find(); }); - self.conn = Meteor.connect(Meteor.absoluteUrl()); + self.conn = DDP.connect(Meteor.absoluteUrl()); pollUntil(expect, function () { return self.conn.status().connected; }, 10000); diff --git a/packages/mongo-livedata/observe_changes_tests.js b/packages/mongo-livedata/observe_changes_tests.js index 1b1dea01bb..c58bcafece 100644 --- a/packages/mongo-livedata/observe_changes_tests.js +++ b/packages/mongo-livedata/observe_changes_tests.js @@ -206,7 +206,8 @@ if (Meteor.isServer) { self.xs = []; self.expects = []; self.insert = function (fields) { - coll.insert(_.extend({ts: new Meteor._Mongo._Timestamp(0, 0)}, fields)); + coll.insert(_.extend({ts: new _MongoLivedataTest.MongoTimestamp(0, 0)}, + fields)); }; // Tailable observe shouldn't show things that are in the initial diff --git a/packages/mongo-livedata/package.js b/packages/mongo-livedata/package.js index 8f2341b00c..fa782ea451 100644 --- a/packages/mongo-livedata/package.js +++ b/packages/mongo-livedata/package.js @@ -8,7 +8,7 @@ // minutiae. Package.describe({ - summary: "Adaptor for using MongoDB and Minimongo over Livedata", + summary: "Adaptor for using MongoDB and Minimongo over DDP", internal: true }); @@ -29,7 +29,8 @@ Package.on_use(function (api) { Package.on_test(function (api) { api.use('mongo-livedata'); api.use('check'); - api.use(['tinytest', 'underscore', 'test-helpers', 'ejson', 'random']); + api.use(['tinytest', 'underscore', 'test-helpers', 'ejson', 'random', + 'livedata']); // XXX test order dependency: the allow_tests "partial allow" test // fails if it is run before mongo_livedata_tests. api.add_files('mongo_livedata_tests.js', ['client', 'server']); diff --git a/packages/mongo-livedata/remote_collection_driver.js b/packages/mongo-livedata/remote_collection_driver.js index ed4f6a5cf7..a6ede8bdbe 100644 --- a/packages/mongo-livedata/remote_collection_driver.js +++ b/packages/mongo-livedata/remote_collection_driver.js @@ -1,10 +1,9 @@ -// XXX namespacing -Meteor._RemoteCollectionDriver = function (mongo_url) { +var RemoteCollectionDriver = function (mongo_url) { var self = this; - self.mongo = new Meteor._Mongo(mongo_url); + self.mongo = new MongoConnection(mongo_url); }; -_.extend(Meteor._RemoteCollectionDriver.prototype, { +_.extend(RemoteCollectionDriver.prototype, { open: function (name) { var self = this; var ret = {}; @@ -19,10 +18,10 @@ _.extend(Meteor._RemoteCollectionDriver.prototype, { }); -// Create the singleton _RemoteCollectionDriver only on demand, so we +// Create the singleton RemoteCollectionDriver only on demand, so we // only require Mongo configuration if it's actually used (eg, not if // you're only trying to receive data from a remote DDP server.) -Meteor._getRemoteCollectionDriver = _.once(function () { +getRemoteCollectionDriver = _.once(function () { // XXX kind of hacky var mongoUrl = (typeof __meteor_bootstrap__ !== 'undefined' && Meteor._get(__meteor_bootstrap__.deployConfig, @@ -31,5 +30,5 @@ Meteor._getRemoteCollectionDriver = _.once(function () { if (! mongoUrl) throw new Error("MONGO_URL must be set in environment"); - return new Meteor._RemoteCollectionDriver(mongoUrl); + return new RemoteCollectionDriver(mongoUrl); }); diff --git a/packages/past/package.js b/packages/past/package.js index 6d81f62056..84b50ea3ba 100644 --- a/packages/past/package.js +++ b/packages/past/package.js @@ -5,6 +5,7 @@ Package.describe({ Package.on_use(function (api) { api.use('deps'); + api.use('reload', 'client', {weak: true}); api.use('random'); api.add_files('past.js', ['client', 'server']); }); diff --git a/packages/past/past.js b/packages/past/past.js index 7bdffe3263..302737d3de 100644 --- a/packages/past/past.js +++ b/packages/past/past.js @@ -5,6 +5,9 @@ // define the alias in the package it refers to. // Old under_score version of camelCase public API names. +// +// @export Meteor.is_client +// @export Meteor.is_server Meteor.is_client = Meteor.isClient; Meteor.is_server = Meteor.isServer; @@ -15,19 +18,28 @@ Meteor.is_server = Meteor.isServer; // We used to require a special "autosubscribe" call to reactively subscribe to // things. Now, it works with autorun. +// +// @export Meteor.autosubscribe Meteor.autosubscribe = Deps.autorun; // "new deps" back-compat +// +// @export Meteor.flush +// @export Meteor.autorun Meteor.flush = Deps.flush; Meteor.autorun = Deps.autorun; // Deps API that briefly existed in 0.5.9 +// +// @export Deps.depend Deps.depend = function (d) { return d.depend(); }; // Instead of the "random" package with Random.id(), we used to have this // Meteor.uuid() implementing the RFC 4122 v4 UUID. +// +// @export Meteor.uuid Meteor.uuid = function () { var HEX_DIGITS = "0123456789abcdef"; var s = []; @@ -41,3 +53,13 @@ Meteor.uuid = function () { var uuid = s.join(""); return uuid; }; + +// This is an internal API that got renamed. Be nice and try not to +// break code that uses it, even though it's internal. +if (Package.reload) { + Meteor._reload = { + onMigrate: Package.reload.Reload._onMigrate, + migrationData: Package.reload.Reload._migrationData, + reload: Package.reload.Reload._reload + }; +} diff --git a/packages/preserve-inputs/preserve-inputs.js b/packages/preserve-inputs/preserve-inputs.js index e7e6a15643..8d507049ab 100644 --- a/packages/preserve-inputs/preserve-inputs.js +++ b/packages/preserve-inputs/preserve-inputs.js @@ -4,5 +4,4 @@ var selector = _.map(inputTags, function (t) { return t.replace(/^.*$/, '$&[id], $&[name]'); }).join(', '); - -Spark._globalPreserves[selector] = Spark._labelFromIdOrName; +Spark._addGlobalPreserve(selector, Spark._labelFromIdOrName); diff --git a/packages/reactive-dict/reactive-dict.js b/packages/reactive-dict/reactive-dict.js index 5dd70e2109..52467279e5 100644 --- a/packages/reactive-dict/reactive-dict.js +++ b/packages/reactive-dict/reactive-dict.js @@ -62,6 +62,11 @@ _.extend(ReactiveDict.prototype, { equals: function (key, value) { var self = this; + // XXX hardcoded awareness of the 'mongo-livedata' package is not ideal + var ObjectID = + typeof Package !== 'undefined' && Package['mongo-livedata'] && + Package['mongo-livedata'].Meteor.Collection.ObjectID; + // We don't allow objects (or arrays that might include objects) for // .equals, because JSON.stringify doesn't canonicalize object key // order. (We can make equals have the right return value by parsing the @@ -76,7 +81,7 @@ _.extend(ReactiveDict.prototype, { typeof value !== 'boolean' && typeof value !== 'undefined' && !(value instanceof Date) && - !(typeof Meteor.Collection !== 'undefined' && value instanceof Meteor.Collection.ObjectID) && + !(ObjectID && value instanceof ObjectID) && value !== null) throw new Error("ReactiveDict.equals: value must be scalar"); var serializedValue = stringify(value); diff --git a/packages/reload/reload.js b/packages/reload/reload.js index c9fc393626..59d54c6530 100644 --- a/packages/reload/reload.js +++ b/packages/reload/reload.js @@ -26,8 +26,6 @@ * the client's session to render properly. */ -Meteor._reload = {}; - var KEY_NAME = 'Meteor_Reload'; // after how long should we consider this no longer an automatic // reload, but a fresh restart. This only happens if a reload is @@ -86,7 +84,9 @@ var providers = []; // will be polled once again for its migration data. If they are all // ready this time, then the migration will happen. name must be set if there // is migration data. -Meteor._reload.onMigrate = function (name, callback) { +// +// @export Reload._onMigrate +Reload._onMigrate = function (name, callback) { if (!callback) { // name not provided, so first arg is callback. callback = name; @@ -97,7 +97,9 @@ Meteor._reload.onMigrate = function (name, callback) { // Called by packages when they start up. // Returns the object that was saved, or undefined if none saved. -Meteor._reload.migrationData = function (name) { +// +// @export Reload._migrationData +Reload._migrationData = function (name) { return old_data[name]; }; @@ -106,8 +108,10 @@ Meteor._reload.migrationData = function (name) { // migrate it over. This function returns immediately. The reload // will happen at some point in the future once all of the packages // are ready to migrate. +// +// @export Reload._reload var reloading = false; -Meteor._reload.reload = function () { +Reload._reload = function () { if (reloading) return; reloading = true; diff --git a/packages/service-configuration/package.js b/packages/service-configuration/package.js index cf7e246a46..a18e1f8363 100644 --- a/packages/service-configuration/package.js +++ b/packages/service-configuration/package.js @@ -4,5 +4,6 @@ Package.describe({ }); Package.on_use(function(api) { + api.use('mongo-livedata', ['client', 'server']); api.add_files('service_configuration_common.js', ['client', 'server']); }); diff --git a/packages/session/package.js b/packages/session/package.js index 66d393e255..7a879cf945 100644 --- a/packages/session/package.js +++ b/packages/session/package.js @@ -6,10 +6,10 @@ Package.describe({ Package.on_use(function (api) { api.use(['underscore', 'reactive-dict', 'ejson'], 'client'); - // XXX what I really want to do is ensure that if 'reload' is going to - // be loaded, it should be loaded before 'session'. Session can work - // with or without reload. - api.use('reload', 'client'); + // Session can work with or without reload, but if reload is present + // it should load first so we can detect it at startup and populate + // the session. + api.use('reload', 'client', {weak: true}); api.add_files('session.js', 'client'); }); @@ -18,5 +18,6 @@ Package.on_test(function (api) { api.use('tinytest'); api.use('session', 'client'); api.use('deps'); + api.use('mongo-livedata'); api.add_files('session_tests.js', 'client'); }); diff --git a/packages/session/session.js b/packages/session/session.js index e1a37ba780..0520843c0d 100644 --- a/packages/session/session.js +++ b/packages/session/session.js @@ -1,6 +1,6 @@ var migratedKeys = {}; -if (Meteor._reload) { - var migrationData = Meteor._reload.migrationData('session'); +if (Package.reload) { + var migrationData = Package.reload.Reload._migrationData('session'); if (migrationData && migrationData.keys) { migratedKeys = migrationData.keys; } @@ -9,8 +9,8 @@ if (Meteor._reload) { // @export Session Session = new ReactiveDict(migratedKeys); -if (Meteor._reload) { - Meteor._reload.onMigrate('session', function () { +if (Package.reload) { + Package.reload.Reload._onMigrate('session', function () { return [true, {keys: Session.keys}]; }); } diff --git a/packages/spark/convenience.js b/packages/spark/convenience.js index e4b3c3f81a..eec6f993ff 100644 --- a/packages/spark/convenience.js +++ b/packages/spark/convenience.js @@ -1,3 +1,4 @@ +// @export Meteor.render Meteor.render = function (htmlFunc) { return Spark.render(function () { return Spark.isolate( @@ -8,6 +9,7 @@ Meteor.render = function (htmlFunc) { }); }; +// @export Meteor.renderList Meteor.renderList = function (cursor, itemFunc, elseFunc) { return Spark.render(function () { return Spark.list(cursor, function (item) { diff --git a/packages/spark/patch.js b/packages/spark/patch.js index 07a09d5f9e..fa79f28189 100644 --- a/packages/spark/patch.js +++ b/packages/spark/patch.js @@ -1,12 +1,11 @@ - -Spark._patch = function(tgtParent, srcParent, tgtBefore, tgtAfter, preservations, - results) { +patch = function(tgtParent, srcParent, tgtBefore, tgtAfter, preservations, + results) { var copyFunc = function(t, s) { - LiveRange.transplantTag(Spark._TAG, t, s); + LiveRange.transplantTag(TAG, t, s); }; - var patcher = new Spark._Patcher( + var patcher = new Patcher( tgtParent, srcParent, tgtBefore, tgtAfter); @@ -67,7 +66,7 @@ Spark._patch = function(tgtParent, srcParent, tgtBefore, tgtAfter, preservations // initial contents but may affect the tag's .value in IE) or of // SELECT (which is specially handled in _copyAttributes). // Otherwise recurse! - Spark._patch(tgt, src, null, null, preservations); + patch(tgt, src, null, null, preservations); } } return false; // tell visitNodes not to recurse @@ -105,7 +104,7 @@ Spark._patch = function(tgtParent, srcParent, tgtBefore, tgtAfter, preservations // in their place. // // Constructor: -Spark._Patcher = function(tgtParent, srcParent, tgtBefore, tgtAfter) { +Patcher = function(tgtParent, srcParent, tgtBefore, tgtAfter) { this.tgtParent = tgtParent; this.srcParent = srcParent; @@ -161,7 +160,7 @@ Spark._Patcher = function(tgtParent, srcParent, tgtBefore, tgtAfter) { // copyCallback is called on every new matched (tgt, src) pair // right after copying attributes. It's a good time to transplant // liveranges and patch children. -Spark._Patcher.prototype.match = function( +Patcher.prototype.match = function( tgtNode, srcNode, copyCallback, onlyAdvance) { // last nodes "kept" (matched/identified with each other) @@ -246,7 +245,7 @@ Spark._Patcher.prototype.match = function( while (true) { if (! (firstIter && onlyAdvance)) { if (tgt.nodeType === 1) /* ELEMENT */ - Spark._Patcher._copyAttributes(tgt, src); + Patcher._copyAttributes(tgt, src); if (copyCallback) copyCallback(tgt, src); } @@ -275,7 +274,7 @@ Spark._Patcher.prototype.match = function( // After a match, skip ahead to later siblings of the last kept nodes, // without performing any replacements. -Spark._Patcher.prototype.skipToSiblings = function(tgt, src) { +Patcher.prototype.skipToSiblings = function(tgt, src) { var lastTgt = this.lastKeptTgtNode; var lastSrc = this.lastKeptSrcNode; @@ -295,7 +294,7 @@ Spark._Patcher.prototype.skipToSiblings = function(tgt, src) { // // Patchers are single-use, so no more methods can be called // on the Patcher. -Spark._Patcher.prototype.finish = function() { +Patcher.prototype.finish = function() { return this.match(null, null); }; @@ -305,7 +304,7 @@ Spark._Patcher.prototype.finish = function() { // // Precondition: tgtBefore and tgtAfter have same parent; either may be falsy, // but not both, unless optTgtParent is provided. Same with srcBefore/srcAfter. -Spark._Patcher.prototype._replaceNodes = function( +Patcher.prototype._replaceNodes = function( tgtBefore, tgtAfter, srcBefore, srcAfter, optTgtParent, optSrcParent) { var tgtParent = optTgtParent || (tgtBefore || tgtAfter).parentNode; @@ -347,7 +346,7 @@ Spark._Patcher.prototype._replaceNodes = function( // // This is complicated by form controls and the fact that old IE // can't keep the difference straight between properties and attributes. -Spark._Patcher._copyAttributes = function(tgt, src) { +Patcher._copyAttributes = function(tgt, src) { var srcAttrs = src.attributes; var tgtAttrs = tgt.attributes; @@ -571,3 +570,6 @@ Spark._Patcher._copyAttributes = function(tgt, src) { tgt.removeAttribute("checked"); } }; + +// @export _SparkTest.Patcher +_SparkTest.Patcher = Patcher; diff --git a/packages/spark/patch_tests.js b/packages/spark/patch_tests.js index 4d2d698ae8..c3bfd06487 100644 --- a/packages/spark/patch_tests.js +++ b/packages/spark/patch_tests.js @@ -1,6 +1,6 @@ Tinytest.add("spark - patch - basic", function(test) { - var Patcher = Spark._Patcher; + var Patcher = _SparkTest.Patcher; var div = function(html) { var n = document.createElement("DIV"); @@ -144,7 +144,7 @@ Tinytest.add("spark - patch - copyAttributes", function(test) { if (! node) { node = n; } else { - Spark._Patcher._copyAttributes(node, n); + _SparkTest.Patcher._copyAttributes(node, n); } lastAttrs = {}; _.each(allAttrNames, function(v,k) { diff --git a/packages/spark/spark.js b/packages/spark/spark.js index 7117cdc97d..157b44b25a 100644 --- a/packages/spark/spark.js +++ b/packages/spark/spark.js @@ -25,10 +25,8 @@ // timer' button again. the problem is almost certainly in afterFlush // (not hard to see what it is.) -// @export Spark -Spark = {}; -Spark._currentRenderer = (function () { +var currentRenderer = (function () { var current = null; return { get: function () { @@ -43,32 +41,53 @@ Spark._currentRenderer = (function () { }; })(); -Spark._TAG = "_spark_" + Random.id(); +TAG = "_spark_" + Random.id(); +// @export _SparkTest.TAG +_SparkTest.TAG = TAG; + +// We also export this as Spark._TAG due to a historical accident. I +// don't know if anything uses it (possibly some of Chris Mather's +// stuff?) but let's keep exporting it since without it it would be +// very difficult for code outside the spark package to, eg, walk +// spark's liverange hierarchy. +// @export Spark._TAG +Spark._TAG = TAG; + // XXX document contract for each type of annotation? -Spark._ANNOTATION_NOTIFY = "notify"; -Spark._ANNOTATION_DATA = "data"; -Spark._ANNOTATION_ISOLATE = "isolate"; -Spark._ANNOTATION_EVENTS = "events"; -Spark._ANNOTATION_WATCH = "watch"; -Spark._ANNOTATION_LABEL = "label"; -Spark._ANNOTATION_LANDMARK = "landmark"; -Spark._ANNOTATION_LIST = "list"; -Spark._ANNOTATION_LIST_ITEM = "item"; +var ANNOTATION_NOTIFY = "notify"; +var ANNOTATION_DATA = "data"; +var ANNOTATION_ISOLATE = "isolate"; +var ANNOTATION_EVENTS = "events"; +var ANNOTATION_WATCH = "watch"; +var ANNOTATION_LABEL = "label"; +var ANNOTATION_LANDMARK = "landmark"; +var ANNOTATION_LIST = "list"; +var ANNOTATION_LIST_ITEM = "item"; // XXX why do we need, eg, _ANNOTATION_ISOLATE? it has no semantics? -// Set in tests to turn on extra UniversalEventListener sanity checks -Spark._checkIECompliance = false; +// Use from tests to turn on extra UniversalEventListener sanity checks +// @export _SparkTest.setCheckIECompliance +var checkIECompliance = false; +_SparkTest.setCheckIECompliance = function (value) { + checkIECompliance = value; +}; + +// Private interface to 'preserve-inputs' package +// @export Spark._addGlobalPreserve +var globalPreserves = {}; +Spark._addGlobalPreserve = function (selector, value) { + globalPreserves[selector] = value; +}; -Spark._globalPreserves = {}; var makeRange = function (type, start, end, inner) { - var range = new LiveRange(Spark._TAG, start, end, inner); + var range = new LiveRange(TAG, start, end, inner); range.type = type; return range; }; var findRangeOfType = function (type, node) { - var range = LiveRange.findRange(Spark._TAG, node); + var range = LiveRange.findRange(TAG, node); while (range && range.type !== type) range = range.findParent(); @@ -84,9 +103,9 @@ var findParentOfType = function (type, range) { }; var notifyWatchers = function (start, end) { - var tempRange = new LiveRange(Spark._TAG, start, end, true /* innermost */); + var tempRange = new LiveRange(TAG, start, end, true /* innermost */); for (var walk = tempRange; walk; walk = walk.findParent()) - if (walk.type === Spark._ANNOTATION_WATCH) + if (walk.type === ANNOTATION_WATCH) walk.notify(); tempRange.destroy(); }; @@ -105,7 +124,7 @@ var withEventGuard = function (func) { finally { eventGuardActive = previous; } }; -Spark._Renderer = function () { +Renderer = function () { // Map from annotation ID to an annotation function, which is called // at render time and receives (startNode, endNode). this.annotations = {}; @@ -131,7 +150,7 @@ Spark._Renderer = function () { this.pc = new PreservationController; }; -_.extend(Spark._Renderer.prototype, { +_.extend(Renderer.prototype, { // `what` can be a function that takes a LiveRange, or just a set of // attributes to add to the liverange. type and what are optional. // if no type is passed, no liverange will be created. @@ -209,7 +228,7 @@ _.extend(Spark._Renderer.prototype, { materialize: function (htmlFunc) { var self = this; - var html = Spark._currentRenderer.withValue(self, htmlFunc); + var html = currentRenderer.withValue(self, htmlFunc); html = self.annotate(html); // wrap with an anonymous annotation var fragById = {}; @@ -321,7 +340,7 @@ _.extend(Spark._Renderer.prototype, { // if there isn't one returns `html` (the last argument). var withRenderer = function (f) { return function (/* arguments */) { - var renderer = Spark._currentRenderer.get(); + var renderer = currentRenderer.get(); var args = _.toArray(arguments); if (!renderer) return args.pop(); @@ -345,7 +364,7 @@ var withRenderer = function (f) { // can call it when manually inserting nodes? (via, eg, jQuery?) -- of // course in that case 'landmarkRanges' would be empty. var scheduleOnscreenSetup = function (frag, landmarkRanges) { - var renderedRange = new LiveRange(Spark._TAG, frag); + var renderedRange = new LiveRange(TAG, frag); var finalized = false; renderedRange.finalize = function () { finalized = true; @@ -395,7 +414,7 @@ var scheduleOnscreenSetup = function (frag, landmarkRanges) { // future: include an argument in the callback to distinguish this // case from the previous var walk = renderedRange; - while ((walk = findParentOfType(Spark._ANNOTATION_LANDMARK, walk))) + while ((walk = findParentOfType(ANNOTATION_LANDMARK, walk))) walk.rendered.call(walk.landmark); // This code can run several times on the same nodes (if the @@ -407,8 +426,9 @@ var scheduleOnscreenSetup = function (frag, landmarkRanges) { }); }; +// @export Spark.render Spark.render = function (htmlFunc) { - var renderer = new Spark._Renderer; + var renderer = new Renderer; var frag = renderer.materialize(htmlFunc); return frag; }; @@ -489,12 +509,12 @@ _.extend(PreservationController.prototype, { // to temporarily put these in the document as well, because CSS selectors // don't care and we will put them back. `tempRange` will hold our place // in the tree `newRange` came from. - var tempRange = new LiveRange(Spark._TAG, newRange.firstNode(), newRange.lastNode()); + var tempRange = new LiveRange(TAG, newRange.firstNode(), newRange.lastNode()); var commentFrag = document.createDocumentFragment(); commentFrag.appendChild(document.createComment("")); var newRangeFrag = tempRange.replaceContents(commentFrag); // `wrapperRange` will mark where we inserted newRange into the document. - var wrapperRange = new LiveRange(Spark._TAG, newRangeFrag); + var wrapperRange = new LiveRange(TAG, newRangeFrag); existingRange.insertBefore(newRangeFrag); _.each(self.roots, function (root) { @@ -524,13 +544,15 @@ _.extend(PreservationController.prototype, { // XXX debugging var pathForRange = function (r) { var path = [], r; - while ((r = findParentOfType(Spark._ANNOTATION_LABEL, r))) + while ((r = findParentOfType(ANNOTATION_LABEL, r))) path.unshift(r.label); return path.join(' :: '); }; // `range` is a region of `document`. Modify it in-place so that it // matches the result of Spark.render(htmlFunc), preserving landmarks. +// +// @export Spark.renderToRange Spark.renderToRange = function (range, htmlFunc) { // `range` may be out-of-document and we don't check here. // XXX should we? @@ -545,7 +567,7 @@ Spark.renderToRange = function (range, htmlFunc) { if (! startNode || ! startNode.parentNode) return; - var renderer = new Spark._Renderer(); + var renderer = new Renderer(); // Call 'func' for each landmark in 'range'. Pass two arguments to // 'func', the range, and an extra "notes" object such that two @@ -556,12 +578,12 @@ Spark.renderToRange = function (range, htmlFunc) { var stack = renderer.newLabelStack(); range.visit(function (isStart, r) { - if (r.type === Spark._ANNOTATION_LABEL) { + if (r.type === ANNOTATION_LABEL) { if (isStart) stack.pushLabel(r.label); else stack.popLabel(); - } else if (r.type === Spark._ANNOTATION_LANDMARK && isStart) { + } else if (r.type === ANNOTATION_LANDMARK && isStart) { func(r, stack.getNotes()); } }); @@ -588,7 +610,7 @@ Spark.renderToRange = function (range, htmlFunc) { DomUtils.wrapFragmentForContainer(frag, range.containerNode()); - var tempRange = new LiveRange(Spark._TAG, frag); + var tempRange = new LiveRange(TAG, frag); // find preservation roots from matched landmarks inside the // rerendered region @@ -616,11 +638,11 @@ Spark.renderToRange = function (range, htmlFunc) { // on a "malformed" liverange tree break; - if (walk.type === Spark._ANNOTATION_LANDMARK, walk) + if (walk.type === ANNOTATION_LANDMARK, walk) pc.addRoot(walk.preserve, range, tempRange, walk.containerNode()); } - pc.addRoot(Spark._globalPreserves, range, tempRange); + pc.addRoot(globalPreserves, range, tempRange); // compute preservations (must do this before destroying tempRange) var preservations = pc.computePreservations(range, tempRange); @@ -636,8 +658,8 @@ Spark.renderToRange = function (range, htmlFunc) { // inside constant regions whose DOM nodes we are going // to preserve untouched Spark.finalize(start, end); - Spark._patch(start.parentNode, frag, start.previousSibling, - end.nextSibling, preservations, results); + patch(start.parentNode, frag, start.previousSibling, + end.nextSibling, preservations, results); }); }); @@ -654,6 +676,8 @@ Spark.renderToRange = function (range, htmlFunc) { // Delete all of the liveranges in the range of nodes between `start` // and `end`, and call their 'finalize' function if any. Or instead of // `start` and `end` you may pass a fragment in `start`. +// +// @export Spark.finalize Spark.finalize = function (start, end) { if (! start.parentNode && start.nodeType !== 11 /* DocumentFragment */) { // Workaround for LiveRanges' current inability to contain @@ -663,7 +687,7 @@ Spark.finalize = function (start, end) { start = frag; end = null; } - var wrapper = new LiveRange(Spark._TAG, start, end); + var wrapper = new LiveRange(TAG, start, end); wrapper.visit(function (isStart, range) { isStart && range.finalize && range.finalize(); }); @@ -674,13 +698,15 @@ Spark.finalize = function (start, end) { /* Data contexts */ /******************************************************************************/ +// @export Spark.setDataContext Spark.setDataContext = withRenderer(function (dataContext, html, _renderer) { return _renderer.annotate( - html, Spark._ANNOTATION_DATA, { data: dataContext }); + html, ANNOTATION_DATA, { data: dataContext }); }); +// @export Spark.getDataContext Spark.getDataContext = function (node) { - var range = findRangeOfType(Spark._ANNOTATION_DATA, node); + var range = findRangeOfType(ANNOTATION_DATA, node); return range && range.data; }; @@ -705,20 +731,21 @@ var getListener = function () { return; var ranges = []; - var walk = findRangeOfType(Spark._ANNOTATION_EVENTS, + var walk = findRangeOfType(ANNOTATION_EVENTS, event.currentTarget); while (walk) { ranges.push(walk); - walk = findParentOfType(Spark._ANNOTATION_EVENTS, walk); + walk = findParentOfType(ANNOTATION_EVENTS, walk); } _.each(ranges, function (r) { r.handler(event); }); - }, Spark._checkIECompliance); + }, checkIECompliance); return universalListener; }; +// @export Spark.attachEvents Spark.attachEvents = withRenderer(function (eventMap, html, _renderer) { var listener = getListener(); @@ -760,7 +787,7 @@ Spark.attachEvents = withRenderer(function (eventMap, html, _renderer) { }; html = _renderer.annotate( - html, Spark._ANNOTATION_WATCH, { + html, ANNOTATION_WATCH, { notify: function () { installHandlers(this); } @@ -769,7 +796,7 @@ Spark.attachEvents = withRenderer(function (eventMap, html, _renderer) { var finalized = false; html = _renderer.annotate( - html, Spark._ANNOTATION_EVENTS, function (range) { + html, ANNOTATION_EVENTS, function (range) { if (! range) return; @@ -808,7 +835,7 @@ Spark.attachEvents = withRenderer(function (eventMap, html, _renderer) { // Found a matching handler. Call it. var eventData = Spark.getDataContext(event.currentTarget) || {}; var landmarkRange = - findParentOfType(Spark._ANNOTATION_LANDMARK, range); + findParentOfType(ANNOTATION_LANDMARK, range); var landmark = (landmarkRange && landmarkRange.landmark); // Note that the handler can do arbitrary things, like call @@ -834,8 +861,9 @@ Spark.attachEvents = withRenderer(function (eventMap, html, _renderer) { /* Isolate */ /******************************************************************************/ +// @export Spark.isolate Spark.isolate = function (htmlFunc) { - var renderer = Spark._currentRenderer.get(); + var renderer = currentRenderer.get(); if (!renderer) return htmlFunc(); @@ -845,7 +873,7 @@ Spark.isolate = function (htmlFunc) { Deps.autorun(function (handle) { if (firstRun) { retHtml = renderer.annotate( - htmlFunc(), Spark._ANNOTATION_ISOLATE, + htmlFunc(), ANNOTATION_ISOLATE, function (r) { if (! r) { // annotation not used; kill this autorun @@ -897,6 +925,7 @@ if (typeof LocalCollection !== 'undefined') { idStringify = function (id) { return id; }; } +// @export Spark.list Spark.list = function (cursor, itemFunc, elseFunc) { elseFunc = elseFunc || function () { return ''; }; @@ -924,7 +953,7 @@ Spark.list = function (cursor, itemFunc, elseFunc) { var handle = cursor.observeChanges(observerCallbacks); // Get the renderer, if any - var renderer = Spark._currentRenderer.get(); + var renderer = currentRenderer.get(); var maybeAnnotate = renderer ? _.bind(renderer.annotate, renderer) : function (html) { return html; }; @@ -954,7 +983,7 @@ Spark.list = function (cursor, itemFunc, elseFunc) { itemDict.forEach(function (elt) { html += maybeAnnotate( itemFunc(transformedDoc(elt.doc)), - Spark._ANNOTATION_LIST_ITEM, + ANNOTATION_LIST_ITEM, function (range) { elt.liveRange = range; }); @@ -965,7 +994,7 @@ Spark.list = function (cursor, itemFunc, elseFunc) { handle.stop(); stopped = true; }; - html = maybeAnnotate(html, Spark._ANNOTATION_LIST, function (range) { + html = maybeAnnotate(html, ANNOTATION_LIST, function (range) { if (! range) { // We never ended up on the screen (caller discarded our return // value) @@ -989,7 +1018,7 @@ Spark.list = function (cursor, itemFunc, elseFunc) { // Maybe that will make sense if we give render callbacks subrange info. var notifyParentsRendered = function () { var walk = outerRange; - while ((walk = findParentOfType(Spark._ANNOTATION_LANDMARK, walk))) + while ((walk = findParentOfType(ANNOTATION_LANDMARK, walk))) walk.rendered.call(walk.landmark); }; @@ -1008,7 +1037,7 @@ Spark.list = function (cursor, itemFunc, elseFunc) { doc._id = id; var frag = Spark.render(_.bind(itemFunc, null, transformedDoc(doc))); DomUtils.wrapFragmentForContainer(frag, outerRange.containerNode()); - var range = makeRange(Spark._ANNOTATION_LIST_ITEM, frag); + var range = makeRange(ANNOTATION_LIST_ITEM, frag); if (itemDict.empty()) { Spark.finalize(outerRange.replaceContents(frag)); @@ -1071,6 +1100,7 @@ Spark.list = function (cursor, itemFunc, elseFunc) { var nextLandmarkId = 1; +// @export Spark.Landmark Spark.Landmark = function () { this.id = nextLandmarkId++; this._range = null; // will be set when put onscreen @@ -1098,13 +1128,16 @@ _.extend(Spark.Landmark.prototype, { } }); +// @export Spark.UNIQUE_LABEL Spark.UNIQUE_LABEL = ['UNIQUE_LABEL']; // label must be a string. // or pass label === null to not drop a label after all (meaning that // this function is a noop) +// +// @export Spark.labelBranch Spark.labelBranch = function (label, htmlFunc) { - var renderer = Spark._currentRenderer.get(); + var renderer = currentRenderer.get(); if (! renderer || label === null) return htmlFunc(); @@ -1123,7 +1156,7 @@ Spark.labelBranch = function (label, htmlFunc) { return html; return renderer.annotate( - html, Spark._ANNOTATION_LABEL, { label: label }); + html, ANNOTATION_LABEL, { label: label }); // XXX what happens if the user doesn't use the return value, or // doesn't use it directly, eg, swaps the branches of the tree @@ -1139,8 +1172,9 @@ Spark.labelBranch = function (label, htmlFunc) { // nodes?) }; +// @export Spark.createLandmark Spark.createLandmark = function (options, htmlFunc) { - var renderer = Spark._currentRenderer.get(); + var renderer = currentRenderer.get(); if (! renderer) { // no renderer -- create and destroy Landmark inline var landmark = new Spark.Landmark; @@ -1183,7 +1217,7 @@ Spark.createLandmark = function (options, htmlFunc) { var html = htmlFunc(landmark); return renderer.annotate( - html, Spark._ANNOTATION_LANDMARK, function (range) { + html, ANNOTATION_LANDMARK, function (range) { if (! range) { // annotation not used options.destroyed && options.destroyed.call(landmark); @@ -1224,8 +1258,8 @@ Spark.createLandmark = function (options, htmlFunc) { }); }; -// used by unit tests -Spark._getEnclosingLandmark = function (node) { - var range = findRangeOfType(Spark._ANNOTATION_LANDMARK, node); +// @export _SparkTest.getEnclosingLandmark +_SparkTest.getEnclosingLandmark = function (node) { + var range = findRangeOfType(ANNOTATION_LANDMARK, node); return range ? range.landmark : null; }; diff --git a/packages/spark/spark_tests.js b/packages/spark/spark_tests.js index 053fa15666..cb2d081fb6 100644 --- a/packages/spark/spark_tests.js +++ b/packages/spark/spark_tests.js @@ -4,7 +4,7 @@ // XXX test variable wrapping (eg TR vs THEAD) inside each branch of Spark.list? -Spark._checkIECompliance = true; +_SparkTest.setCheckIECompliance(true); // Tests can use {preserve: idNameLabels} or renderWithPreservation // to cause any element with an id or name to be preserved. This effect @@ -74,9 +74,9 @@ Tinytest.add("spark - assembly", function (test) { test.equal(furtherCanon(f.html()), html); var actualGroups = []; - var tempRange = new LiveRange(Spark._TAG, frag); + var tempRange = new LiveRange(_SparkTest.TAG, frag); tempRange.visit(function (isStart, rng) { - if (! isStart && rng.type === Spark._ANNOTATION_DATA) + if (! isStart && rng.type === "data" /* Spark._ANNOTATION_DATA */) actualGroups.push(furtherCanon(canonicalizeHtml( DomUtils.rangeToHtml(rng.firstNode(), rng.lastNode())))); }); @@ -3341,8 +3341,8 @@ Tinytest.add("spark - current landmark", function (test) { test.equal(callbacks, 1); Deps.flush(); test.equal(callbacks, 2); - test.equal(null, Spark._getEnclosingLandmark(d.node())); - var enc = Spark._getEnclosingLandmark(d.node().firstChild); + test.equal(null, _SparkTest.getEnclosingLandmark(d.node())); + var enc = _SparkTest.getEnclosingLandmark(d.node().firstChild); test.equal(enc.a, 9); test.equal(enc.b, 2); test.isFalse('c' in enc); @@ -3358,32 +3358,32 @@ Tinytest.add("spark - current landmark", function (test) { Deps.flush(); test.equal(callbacks, 4); - test.isTrue(Spark._getEnclosingLandmark(findOuter()).outer); - test.isTrue(Spark._getEnclosingLandmark(findInnerA()).innerA); - test.isTrue(Spark._getEnclosingLandmark(findInnerB()).innerB); - test.equal(1, Spark._getEnclosingLandmark(findOuter()).renderCount); - test.equal(1, Spark._getEnclosingLandmark(findInnerA()).renderCount); - test.equal(1, Spark._getEnclosingLandmark(findInnerB()).renderCount); + test.isTrue(_SparkTest.getEnclosingLandmark(findOuter()).outer); + test.isTrue(_SparkTest.getEnclosingLandmark(findInnerA()).innerA); + test.isTrue(_SparkTest.getEnclosingLandmark(findInnerB()).innerB); + test.equal(1, _SparkTest.getEnclosingLandmark(findOuter()).renderCount); + test.equal(1, _SparkTest.getEnclosingLandmark(findInnerA()).renderCount); + test.equal(1, _SparkTest.getEnclosingLandmark(findInnerB()).renderCount); R.set(4) Deps.flush(); test.equal(callbacks, 5); - test.equal(2, Spark._getEnclosingLandmark(findOuter()).renderCount); - test.equal(2, Spark._getEnclosingLandmark(findInnerA()).renderCount); + test.equal(2, _SparkTest.getEnclosingLandmark(findOuter()).renderCount); + test.equal(2, _SparkTest.getEnclosingLandmark(findInnerA()).renderCount); R.set(5) Deps.flush(); test.equal(callbacks, 6); - test.equal(3, Spark._getEnclosingLandmark(findOuter()).renderCount); - test.equal(3, Spark._getEnclosingLandmark(findInnerA()).renderCount); - test.equal(1, Spark._getEnclosingLandmark(findInnerB()).renderCount); + test.equal(3, _SparkTest.getEnclosingLandmark(findOuter()).renderCount); + test.equal(3, _SparkTest.getEnclosingLandmark(findInnerA()).renderCount); + test.equal(1, _SparkTest.getEnclosingLandmark(findInnerB()).renderCount); R.set(6) Deps.flush(); test.equal(callbacks, 7); - test.equal(4, Spark._getEnclosingLandmark(findOuter()).renderCount); - test.equal(4, Spark._getEnclosingLandmark(findInnerA()).renderCount); - test.equal(2, Spark._getEnclosingLandmark(findInnerB()).renderCount); + test.equal(4, _SparkTest.getEnclosingLandmark(findOuter()).renderCount); + test.equal(4, _SparkTest.getEnclosingLandmark(findInnerA()).renderCount); + test.equal(2, _SparkTest.getEnclosingLandmark(findInnerB()).renderCount); d.kill(); Deps.flush(); diff --git a/packages/spark/utils.js b/packages/spark/utils.js index 5d7d1137c3..4d51b82fc3 100644 --- a/packages/spark/utils.js +++ b/packages/spark/utils.js @@ -1,3 +1,4 @@ +// @export Spark._labelFromIdOrName Spark._labelFromIdOrName = function(n) { var label = null; diff --git a/packages/spiderable/spiderable.js b/packages/spiderable/spiderable.js index 34afe99778..2e5bd04c4a 100644 --- a/packages/spiderable/spiderable.js +++ b/packages/spiderable/spiderable.js @@ -42,7 +42,7 @@ WebApp.connectHandlers.use(function (req, res, next) { " && typeof(Meteor.status) !== 'undefined' " + " && Meteor.status().connected) {" + " Deps.flush();" + - " return Meteor._LivedataConnection._allSubscriptionsReady();" + + " return DDP._allSubscriptionsReady();" + " }" + " return false;" + " });" + diff --git a/packages/srp/biginteger.js b/packages/srp/biginteger.js index 676e01f9a8..7566f48663 100644 --- a/packages/srp/biginteger.js +++ b/packages/srp/biginteger.js @@ -1,7 +1,5 @@ /// METEOR WRAPPER -if (typeof Meteor._srp === "undefined") - Meteor._srp = {}; -Meteor._srp.BigInteger = (function () { +BigInteger = (function () { /// BEGIN jsbn.js diff --git a/packages/srp/sha256.js b/packages/srp/sha256.js index 9b34c69f36..4743264b4e 100644 --- a/packages/srp/sha256.js +++ b/packages/srp/sha256.js @@ -2,9 +2,7 @@ // // XXX this should get packaged and moved into the Meteor.crypto // namespace, along with other hash functions. -if (typeof Meteor._srp === "undefined") - Meteor._srp = {}; -Meteor._srp.SHA256 = (function () { +SHA256 = (function () { /** diff --git a/packages/srp/srp.js b/packages/srp/srp.js index 5fbd0cd1ae..3645054357 100644 --- a/packages/srp/srp.js +++ b/packages/srp/srp.js @@ -1,7 +1,3 @@ -if (typeof Meteor._srp === "undefined") - Meteor._srp = {}; - - /////// PUBLIC CLIENT /** @@ -14,14 +10,15 @@ if (typeof Meteor._srp === "undefined") * testing. Random UUID if not provided. * - SRP parameters (see _defaults and paramsFromOptions below) */ -Meteor._srp.generateVerifier = function (password, options) { +// @export SRP.generateVerifier +SRP.generateVerifier = function (password, options) { var params = paramsFromOptions(options); var identity = (options && options.identity) || Random.id(); var salt = (options && options.salt) || Random.id(); var x = params.hash(salt + params.hash(identity + ":" + password)); - var xi = new Meteor._srp.BigInteger(x, 16); + var xi = new BigInteger(x, 16); var v = params.g.modPow(xi, params.N); @@ -33,7 +30,8 @@ Meteor._srp.generateVerifier = function (password, options) { }; // For use with check(). -Meteor._srp.matchVerifier = { +// @export SRP.matchVerifier +SRP.matchVerifier = { identity: String, salt: String, verifier: String @@ -49,7 +47,8 @@ Meteor._srp.matchVerifier = { * passed in for testing. * - SRP parameters (see _defaults and paramsFromOptions below) */ -Meteor._srp.Client = function (password, options) { +// @export SRP.Client +SRP.Client = function (password, options) { var self = this; self.params = paramsFromOptions(options); self.password = password; @@ -62,8 +61,8 @@ Meteor._srp.Client = function (password, options) { var a, A; if (options && options.a) { if (typeof options.a === "string") - a = new Meteor._srp.BigInteger(options.a, 16); - else if (options.a instanceof Meteor._srp.BigInteger) + a = new BigInteger(options.a, 16); + else if (options.a instanceof BigInteger) a = options.a; else throw new Error("Invalid parameter: a"); @@ -91,7 +90,7 @@ Meteor._srp.Client = function (password, options) { * * returns { A: 'client public ephemeral key. hex encoded integer.' } */ -Meteor._srp.Client.prototype.startExchange = function () { +SRP.Client.prototype.startExchange = function () { var self = this; return { @@ -110,7 +109,7 @@ Meteor._srp.Client.prototype.startExchange = function () { * returns { M: 'client proof of password. hex encoded integer.' } * throws an error if it got an invalid challenge. */ -Meteor._srp.Client.prototype.respondToChallenge = function (challenge) { +SRP.Client.prototype.respondToChallenge = function (challenge) { var self = this; // shorthand @@ -123,13 +122,13 @@ Meteor._srp.Client.prototype.respondToChallenge = function (challenge) { self.identity = challenge.identity; self.salt = challenge.salt; self.Bstr = challenge.B; - self.B = new Meteor._srp.BigInteger(self.Bstr, 16); + self.B = new BigInteger(self.Bstr, 16); if (self.B.mod(N) === 0) throw new Error("Server sent invalid key: B mod N == 0."); - var u = new Meteor._srp.BigInteger(H(self.Astr + self.Bstr), 16); - var x = new Meteor._srp.BigInteger( + var u = new BigInteger(H(self.Astr + self.Bstr), 16); + var x = new BigInteger( H(self.salt + H(self.identity + ":" + self.password)), 16); var kgx = k.multiply(g.modPow(x, N)); @@ -155,7 +154,7 @@ Meteor._srp.Client.prototype.respondToChallenge = function (challenge) { * * returns true or false. */ -Meteor._srp.Client.prototype.verifyConfirmation = function (confirmation) { +SRP.Client.prototype.verifyConfirmation = function (confirmation) { var self = this; return (self.HAMK && (confirmation.HAMK === self.HAMK)); @@ -175,7 +174,8 @@ Meteor._srp.Client.prototype.verifyConfirmation = function (confirmation) { * passed in for testing. * - SRP parameters (see _defaults and paramsFromOptions below) */ -Meteor._srp.Server = function (verifier, options) { +// @export SRP.Server +SRP.Server = function (verifier, options) { var self = this; self.params = paramsFromOptions(options); self.verifier = verifier; @@ -184,14 +184,14 @@ Meteor._srp.Server = function (verifier, options) { var N = self.params.N; var g = self.params.g; var k = self.params.k; - var v = new Meteor._srp.BigInteger(self.verifier.verifier, 16); + var v = new BigInteger(self.verifier.verifier, 16); // construct public and private keys. var b, B; if (options && options.b) { if (typeof options.b === "string") - b = new Meteor._srp.BigInteger(options.b, 16); - else if (options.b instanceof Meteor._srp.BigInteger) + b = new BigInteger(options.b, 16); + else if (options.b instanceof BigInteger) b = options.b; else throw new Error("Invalid parameter: b"); @@ -228,12 +228,12 @@ Meteor._srp.Server = function (verifier, options) { * * Throws an error if issued a bad request. */ -Meteor._srp.Server.prototype.issueChallenge = function (request) { +SRP.Server.prototype.issueChallenge = function (request) { var self = this; // XXX check for missing / bad parameters. self.Astr = request.A; - self.A = new Meteor._srp.BigInteger(self.Astr, 16); + self.A = new BigInteger(self.Astr, 16); if (self.A.mod(self.params.N) === 0) throw new Error("Client sent invalid key: A mod N == 0."); @@ -243,8 +243,8 @@ Meteor._srp.Server.prototype.issueChallenge = function (request) { var H = self.params.hash; // Compute M and HAMK in advance. Don't send to client yet. - var u = new Meteor._srp.BigInteger(H(self.Astr + self.Bstr), 16); - var v = new Meteor._srp.BigInteger(self.verifier.verifier, 16); + var u = new BigInteger(H(self.Astr + self.Bstr), 16); + var v = new BigInteger(self.verifier.verifier, 16); var avu = self.A.multiply(v.modPow(u, N)); self.S = avu.modPow(self.b, N); self.M = H(self.Astr + self.Bstr + self.S.toString(16)); @@ -268,7 +268,7 @@ Meteor._srp.Server.prototype.issueChallenge = function (request) { * - HAMK: server proof of password. hex encoded integer. * OR null if the client's proof doesn't match. */ -Meteor._srp.Server.prototype.verifyResponse = function (response) { +SRP.Server.prototype.verifyResponse = function (response) { var self = this; if (response.M !== self.M) @@ -287,15 +287,15 @@ Meteor._srp.Server.prototype.verifyResponse = function (response) { * Default parameter values for SRP. * */ -Meteor._srp._defaults = { - hash: function (x) { return Meteor._srp.SHA256(x).toLowerCase(); }, - N: new Meteor._srp.BigInteger("EEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C9C256576D674DF7496EA81D3383B4813D692C6E0E0D5D8E250B98BE48E495C1D6089DAD15DC7D7B46154D6B6CE8EF4AD69B15D4982559B297BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA9AFD5138FE8376435B9FC61D2FC0EB06E3", 16), - g: new Meteor._srp.BigInteger("2") +var _defaults = { + hash: function (x) { return SHA256(x).toLowerCase(); }, + N: new BigInteger("EEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C9C256576D674DF7496EA81D3383B4813D692C6E0E0D5D8E250B98BE48E495C1D6089DAD15DC7D7B46154D6B6CE8EF4AD69B15D4982559B297BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA9AFD5138FE8376435B9FC61D2FC0EB06E3", 16), + g: new BigInteger("2") }; -Meteor._srp._defaults.k = new Meteor._srp.BigInteger( - Meteor._srp._defaults.hash( - Meteor._srp._defaults.N.toString(16) + - Meteor._srp._defaults.g.toString(16)), +_defaults.k = new BigInteger( + _defaults.hash( + _defaults.N.toString(16) + + _defaults.g.toString(16)), 16); /** @@ -309,15 +309,15 @@ Meteor._srp._defaults.k = new Meteor._srp.BigInteger( */ var paramsFromOptions = function (options) { if (!options) // fast path - return Meteor._srp._defaults; + return _defaults; - var ret = _.extend({}, Meteor._srp._defaults); + var ret = _.extend({}, _defaults); _.each(['N', 'g', 'k'], function (p) { if (options[p]) { if (typeof options[p] === "string") - ret[p] = new Meteor._srp.BigInteger(options[p], 16); - else if (options[p] instanceof Meteor._srp.BigInteger) + ret[p] = new BigInteger(options[p], 16); + else if (options[p] instanceof BigInteger) ret[p] = options[p]; else throw new Error("Invalid parameter: " + p); @@ -336,5 +336,5 @@ var paramsFromOptions = function (options) { var randInt = function () { - return new Meteor._srp.BigInteger(Random.hexString(36), 16); + return new BigInteger(Random.hexString(36), 16); }; diff --git a/packages/srp/srp_tests.js b/packages/srp/srp_tests.js index ea35bd6e59..d1ea3edc35 100644 --- a/packages/srp/srp_tests.js +++ b/packages/srp/srp_tests.js @@ -1,9 +1,9 @@ Tinytest.add("srp - good exchange", function(test) { var password = 'hi there!'; - var verifier = Meteor._srp.generateVerifier(password); + var verifier = SRP.generateVerifier(password); - var C = new Meteor._srp.Client(password); - var S = new Meteor._srp.Server(verifier); + var C = new SRP.Client(password); + var S = new SRP.Server(verifier); var request = C.startExchange(); var challenge = S.issueChallenge(request); @@ -16,10 +16,10 @@ Tinytest.add("srp - good exchange", function(test) { }); Tinytest.add("srp - bad exchange", function(test) { - var verifier = Meteor._srp.generateVerifier('one password'); + var verifier = SRP.generateVerifier('one password'); - var C = new Meteor._srp.Client('another password'); - var S = new Meteor._srp.Server(verifier); + var C = new SRP.Client('another password'); + var S = new SRP.Server(verifier); var request = C.startExchange(); var challenge = S.issueChallenge(request); @@ -43,11 +43,11 @@ Tinytest.add("srp - fixed values", function(test) { var a = "dc99c646fa4cb7c24314bb6f4ca2d391297acd0dacb0430a13bbf1e37dcf8071"; var b = "cf878e00c9f2b6aa48a10f66df9706e64fef2ca399f396d65f5b0a27cb8ae237"; - var verifier = Meteor._srp.generateVerifier( + var verifier = SRP.generateVerifier( password, {identity: identity, salt: salt}); - var C = new Meteor._srp.Client(password, {a: a}); - var S = new Meteor._srp.Server(verifier, {b: b}); + var C = new SRP.Client(password, {a: a}); + var S = new SRP.Server(verifier, {b: b}); var request = C.startExchange(); test.equal(request.A, "8a75aa61471a92d4c3b5d53698c910af5ef013c42799876c40612d1d5e0dc41d01f669bc022fadcd8a704030483401a1b86b8670191bd9dfb1fb506dd11c688b2f08e9946756263954db2040c1df1894af7af5f839c9215bb445268439157e65e8f100469d575d5d0458e19e8bd4dd4ea2c0b30b1b3f4f39264de4ec596e0bb7"); @@ -88,14 +88,14 @@ Tinytest.add("srp - options", function(test) { b: "2" }, baseOptions); - var verifier = Meteor._srp.generateVerifier('c', verifierOptions);; + var verifier = SRP.generateVerifier('c', verifierOptions);; test.equal(verifier.identity, 'a'); test.equal(verifier.salt, 'b'); test.equal(verifier.verifier, '3'); - var C = new Meteor._srp.Client('c', clientOptions); - var S = new Meteor._srp.Server(verifier, serverOptions); + var C = new SRP.Client('c', clientOptions); + var S = new SRP.Server(verifier, serverOptions); var request = C.startExchange(); test.equal(request.A, '4'); diff --git a/packages/startup/package.js b/packages/startup/package.js index 8e9c54a307..94603f3c82 100644 --- a/packages/startup/package.js +++ b/packages/startup/package.js @@ -1,9 +1,11 @@ Package.describe({ - summary: "Provides Meteor.startup", + summary: "Deprecated package (now empty)", internal: true }); Package.on_use(function (api) { - api.add_files('startup_client.js', 'client'); - api.add_files('startup_server.js', 'server'); + // Deprecated -- Meteor.startup has been folded into the main + // 'meteor' package for now, because it seems reasonable to expect + // that Meteor.startup would always be available without having to + // include a special package. }); diff --git a/packages/stylus/package.js b/packages/stylus/package.js index 15831786c1..95e74bac9f 100644 --- a/packages/stylus/package.js +++ b/packages/stylus/package.js @@ -13,5 +13,6 @@ Package._transitional_registerBuildPlugin({ Package.on_test(function (api) { api.use(['tinytest', 'stylus', 'test-helpers']) + api.use('spark'); api.add_files(['stylus_tests.styl', 'stylus_tests.js'], 'client'); }); diff --git a/packages/templating/deftemplate.js b/packages/templating/deftemplate.js index 0ea4896f85..a5a6f9292e 100644 --- a/packages/templating/deftemplate.js +++ b/packages/templating/deftemplate.js @@ -1,12 +1,11 @@ // @export Template Template = {}; -Meteor._partials = {}; +var registeredPartials = {}; // XXX Handlebars hooking is janky and gross - -Meteor._hook_handlebars = function () { - Meteor._hook_handlebars = function(){}; // install the hook only once +var hookHandlebars = function () { + hookHandlebars = function(){}; // install the hook only once var orig = Handlebars._default_helpers.each; Handlebars._default_helpers.each = function (arg, options) { @@ -73,7 +72,7 @@ var templateObjFromLandmark = function (landmark) { }; // XXX forms hooks into this to add "bind"? -Meteor._template_decl_methods = { +var templateBase = { // methods store data here (event map, etc.). initialized per template. _tmpl_data: null, // these functions must be generic (i.e. use `this`) @@ -104,10 +103,12 @@ Meteor._template_decl_methods = { } }; -Meteor._def_template = function (name, raw_func) { - Meteor._hook_handlebars(); +Template.__define__ = function (name, raw_func) { + hookHandlebars(); - window.Template = window.Template || {}; + if (name === '__define__') + throw new Error("Sorry, '__define__' is a special name and " + + "cannot be used as the name of a template"); // Define the function assigned to Template.. @@ -140,7 +141,7 @@ Meteor._def_template = function (name, raw_func) { // (and receive 'landmark') return raw_func(data, { helpers: _.extend({}, partial, tmplData.helpers || {}), - partials: Meteor._partials, + partials: registeredPartials, name: name }); }); @@ -167,7 +168,7 @@ Meteor._def_template = function (name, raw_func) { // support old Template.foo.events = {...} format var events = - (tmpl.events !== Meteor._template_decl_methods.events ? + (tmpl.events !== templateBase.events ? tmpl.events : tmplData.events); // events need to be inside the landmark, not outside, so // that when an event fires, you can retrieve the enclosing @@ -196,10 +197,10 @@ Meteor._def_template = function (name, raw_func) { "'. Each template needs a unique name."); Template[name] = partial; - _.extend(partial, Meteor._template_decl_methods); + _.extend(partial, templateBase); partial._tmpl_data = {}; - Meteor._partials[name] = partial; + registeredPartials[name] = partial; } // useful for unnamed templates, like body diff --git a/packages/templating/plugin/html_scanner.js b/packages/templating/plugin/html_scanner.js index dd4fa69941..59cd386909 100644 --- a/packages/templating/plugin/html_scanner.js +++ b/packages/templating/plugin/html_scanner.js @@ -170,7 +170,7 @@ html_scanner = { if (! name) throwParseError("Template has no 'name' attribute"); - results.js += "Meteor._def_template(" + JSON.stringify(name) + "," + results.js += "Template.__define__(" + JSON.stringify(name) + "," + code + ");\n"; } else { // @@ -178,7 +178,7 @@ html_scanner = { throwParseError("Attributes on not supported"); results.js += "Meteor.startup(function(){" + "document.body.appendChild(Spark.render(" + - "Meteor._def_template(null," + code + ")));});"; + "Template.__define__(null," + code + ")));});"; } } }; diff --git a/packages/templating/scanner_tests.js b/packages/templating/scanner_tests.js index 2e0edafef5..e55d7b8962 100644 --- a/packages/templating/scanner_tests.js +++ b/packages/templating/scanner_tests.js @@ -24,9 +24,9 @@ Tinytest.add("templating - html scanner", function (test) { var BODY_PREAMBLE = "Meteor.startup(function(){" + "document.body.appendChild(Spark.render(" + - "Meteor._def_template(null,"; + "Template.__define__(null,"; var BODY_POSTAMBLE = ")));});"; - var TEMPLATE_PREAMBLE = "Meteor._def_template("; + var TEMPLATE_PREAMBLE = "Template.__define__("; var TEMPLATE_POSTAMBLE = ");\n"; var checkResults = function(results, expectJs, expectHead) { diff --git a/packages/templating/templating_tests.js b/packages/templating/templating_tests.js index cc677c90f5..634c36368b 100644 --- a/packages/templating/templating_tests.js +++ b/packages/templating/templating_tests.js @@ -768,7 +768,7 @@ Tinytest.add("templating - events", function (test) { test.isTrue(_.contains(buf, 'a')); test.isTrue(_.contains(buf, 'b')); div.kill(); - Meteor.flush(); + Deps.flush(); }); Tinytest.add("templating - #each rendered callback", function (test) { diff --git a/packages/test-helpers/stub_stream.js b/packages/test-helpers/stub_stream.js index 351619b854..8aa8c973c5 100644 --- a/packages/test-helpers/stub_stream.js +++ b/packages/test-helpers/stub_stream.js @@ -1,8 +1,5 @@ -// XXX XXX should really '@export Meteor._StubStream' but we're not -// there yet (other packages need to cooperate and also export -// Meteor.foo rather than Meteor) - -Meteor._StubStream = function () { +// @export StubStream +StubStream = function () { var self = this; self.sent = []; @@ -10,7 +7,7 @@ Meteor._StubStream = function () { }; -_.extend(Meteor._StubStream.prototype, { +_.extend(StubStream.prototype, { // Methods from Stream on: function (name, callback) { var self = this; diff --git a/packages/test-in-browser/driver.js b/packages/test-in-browser/driver.js index c66874e309..0d200cdfd3 100644 --- a/packages/test-in-browser/driver.js +++ b/packages/test-in-browser/driver.js @@ -41,13 +41,13 @@ Session.set("rerunScheduled", false); Meteor.startup(function () { Deps.flush(); - Meteor._runTestsEverywhere(reportResults, function () { + Tinytest._runTestsEverywhere(reportResults, function () { running = false; Meteor.onTestsComplete && Meteor.onTestsComplete(); countDep.changed(); Deps.flush(); - Meteor.default_connection._unsubscribeAll(); + Meteor.connection._unsubscribeAll(); }, Session.get("groupPath")); }); @@ -318,7 +318,7 @@ var changeToPath = function (path) { Session.set("rerunScheduled", true); // pretend there's just been a hot code push // so we run the tests completely fresh. - Meteor._reload.reload(); + Reload._reload(); }; Template.groupNav.events({ @@ -327,7 +327,7 @@ Template.groupNav.events({ }, "click .rerun": function () { Session.set("rerunScheduled", true); - Meteor._reload.reload(); + Reload._reload(); } }); @@ -454,7 +454,7 @@ Template.event.events({ // messy. needs to be aggressively refactored. forgetEvents({groupPath: this.cookie.groupPath, test: this.cookie.shortName}); - Meteor._debugTest(this.cookie, reportResults); + Tinytest._debugTest(this.cookie, reportResults); } }); diff --git a/packages/test-in-browser/package.js b/packages/test-in-browser/package.js index 96e19095eb..61eaf8980b 100644 --- a/packages/test-in-browser/package.js +++ b/packages/test-in-browser/package.js @@ -12,6 +12,7 @@ Package.on_use(function (api) { api.use('underscore'); api.use('session'); + api.use('reload'); api.use(['spark', 'livedata', 'templating', 'deps'], 'client'); diff --git a/packages/test-in-console/driver.js b/packages/test-in-console/driver.js index 6335cba63a..aa581a47a7 100644 --- a/packages/test-in-console/driver.js +++ b/packages/test-in-console/driver.js @@ -73,7 +73,7 @@ Meteor.startup(function () { setTimeout(sendReports, 500); setInterval(sendReports, 2000); - Meteor._runTestsEverywhere( + Tinytest._runTestsEverywhere( function (results) { var name = getName(results); if (!_.has(resultSet, name)) { diff --git a/packages/tinytest/package.js b/packages/tinytest/package.js index 8172e430fb..fb71b7ea9d 100644 --- a/packages/tinytest/package.js +++ b/packages/tinytest/package.js @@ -14,6 +14,7 @@ Package.on_use(function (api) { api.add_files('tinytest.js', ['client', 'server']); + api.use('livedata', ['client', 'server']); api.use('mongo-livedata', ['client', 'server']); api.add_files('model.js', ['client', 'server']); diff --git a/packages/tinytest/tinytest.js b/packages/tinytest/tinytest.js index 68e7d9d336..a4aab57d6b 100644 --- a/packages/tinytest/tinytest.js +++ b/packages/tinytest/tinytest.js @@ -491,22 +491,23 @@ _.extend(TestRun.prototype, { /* Public API */ /******************************************************************************/ -// @export Tinytest -Tinytest = { - add: function (name, func) { - TestManager.addCase(new TestCase(name, func)); - }, +// @export Tinytest.add +Tinytest.add = function (name, func) { + TestManager.addCase(new TestCase(name, func)); +}; - addAsync: function (name, func) { - TestManager.addCase(new TestCase(name, func, true)); - } +// @export Tinytest.addAsync +Tinytest.addAsync = function (name, func) { + TestManager.addCase(new TestCase(name, func, true)); }; // Run every test, asynchronously. Runs the test in the current // process only (if called on the server, runs the tests on the // server, and likewise for the client.) Report results via // onReport. Call onComplete when it's done. -Meteor._runTests = function (onReport, onComplete, pathPrefix) { +// +// @export Tinytest._runTests +Tinytest._runTests = function (onReport, onComplete, pathPrefix) { var testRun = TestManager.createRun(onReport, pathPrefix); testRun.run(onComplete); }; @@ -514,7 +515,9 @@ Meteor._runTests = function (onReport, onComplete, pathPrefix) { // Run just one test case, and stop the debugger at a particular // error, all as indicated by 'cookie', which will have come from a // failure event output by _runTests. -Meteor._debugTest = function (cookie, onReport, onComplete) { +// +// @export Tinytest._debugTest +Tinytest._debugTest = function (cookie, onReport, onComplete) { var testRun = TestManager.createRun(onReport); testRun.debug(cookie, onComplete); }; diff --git a/packages/tinytest/tinytest_client.js b/packages/tinytest/tinytest_client.js index b86cbbb83e..debebb953e 100644 --- a/packages/tinytest/tinytest_client.js +++ b/packages/tinytest/tinytest_client.js @@ -1,7 +1,9 @@ -// Like Meteor._runTests, but runs the tests on both the client and +// Like Tinytest._runTests, but runs the tests on both the client and // the server. Sets a 'server' flag on test results that came from the // server. -Meteor._runTestsEverywhere = function (onReport, onComplete, pathPrefix) { +// +// @export Tinytest._runTestsEverywhere +Tinytest._runTestsEverywhere = function (onReport, onComplete, pathPrefix) { var runId = Random.id(); var localComplete = false; var remoteComplete = false; @@ -14,12 +16,12 @@ Meteor._runTestsEverywhere = function (onReport, onComplete, pathPrefix) { } }; - Meteor._runTests(onReport, function () { + Tinytest._runTests(onReport, function () { localComplete = true; maybeDone(); }, pathPrefix); - Meteor.default_connection.registerStore(Meteor._ServerTestResultsCollection, { + Meteor.connection.registerStore(Meteor._ServerTestResultsCollection, { update: function (msg) { // We only should call _runTestsEverywhere once per client-page-load, so // we really only should see one runId here. diff --git a/packages/tinytest/tinytest_server.js b/packages/tinytest/tinytest_server.js index dfd95aeeb3..b99c7758a7 100644 --- a/packages/tinytest/tinytest_server.js +++ b/packages/tinytest/tinytest_server.js @@ -54,7 +54,7 @@ Meteor.methods({ future['return'](); }; - Meteor._runTests(onReport, onComplete, pathPrefix); + Tinytest._runTests(onReport, onComplete, pathPrefix); future.wait(); }, diff --git a/packages/webapp/webapp_server.js b/packages/webapp/webapp_server.js index 9180e36e78..a3a3e7ad76 100644 --- a/packages/webapp/webapp_server.js +++ b/packages/webapp/webapp_server.js @@ -23,7 +23,7 @@ var findGalaxy = _.once(function () { process.exit(1); } - return Meteor.connect(process.env['GALAXY']); + return DDP.connect(process.env['GALAXY']); }); // Keepalives so that when the outer server dies unceremoniously and @@ -510,8 +510,8 @@ Meteor._bindToProxy = function (proxyConfig) { }; // This is run after packages are loaded (in main) so we can use - // Meteor.connect. - var proxy = Meteor.connect(proxyConfig.proxyEndpoint); + // DDP.connect. + var proxy = DDP.connect(proxyConfig.proxyEndpoint); var route = process.env.ROUTE; var host = route.split(":")[0]; var port = +route.split(":")[1]; diff --git a/tools/packages.js b/tools/packages.js index 4f976cc67c..406af5f944 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -1720,7 +1720,7 @@ _.extend(Package.prototype, { // standard client packages for the classic meteor stack. // XXX remove and make everyone explicitly declare all dependencies ['meteor', 'webapp', 'logging', 'deps', 'session', - 'livedata', 'mongo-livedata', 'spark', 'templating', 'startup', + 'livedata', 'mongo-livedata', 'spark', 'templating', 'past', 'check'], project.get_packages(appDir)); From d2024ccee11081c89cbffcf3d42d0ab98a8790c5 Mon Sep 17 00:00:00 2001 From: Geoff Schmidt Date: Sat, 20 Jul 2013 07:18:51 -0700 Subject: [PATCH 073/175] Clean up namespacing in accounts system too. --- packages/accounts-base/accounts_client.js | 23 ++++---- packages/accounts-base/accounts_common.js | 16 +++--- packages/accounts-base/accounts_server.js | 38 +++++++++----- packages/accounts-base/localstorage_token.js | 52 ++++++++++++------- packages/accounts-facebook/facebook_common.js | 3 -- packages/accounts-facebook/package.js | 1 - packages/accounts-github/github_common.js | 3 -- packages/accounts-github/package.js | 1 - packages/accounts-google/google_common.js | 3 -- packages/accounts-google/package.js | 1 - packages/accounts-meetup/meetup_common.js | 3 -- packages/accounts-meetup/package.js | 1 - packages/accounts-oauth/oauth_client.js | 2 + packages/accounts-oauth/oauth_common.js | 1 - packages/accounts-oauth/oauth_server.js | 2 + packages/accounts-oauth/package.js | 1 - packages/accounts-password/email_templates.js | 1 + packages/accounts-password/package.js | 1 - packages/accounts-password/password_client.js | 5 ++ packages/accounts-password/password_common.js | 1 - packages/accounts-password/password_server.js | 11 +++- packages/accounts-twitter/package.js | 1 - packages/accounts-twitter/twitter_common.js | 3 -- packages/accounts-ui-unstyled/accounts_ui.js | 19 +++---- .../accounts-ui-unstyled/login_buttons.js | 43 ++++++--------- .../login_buttons_dialogs.js | 6 +-- .../login_buttons_dropdown.js | 51 ++++++++---------- .../login_buttons_session.js | 6 ++- packages/accounts-urls/url_client.js | 10 ++-- packages/accounts-urls/url_server.js | 10 ++-- packages/accounts-weibo/package.js | 1 - packages/accounts-weibo/weibo_common.js | 3 -- packages/facebook/facebook_client.js | 2 + packages/facebook/facebook_common.js | 2 - packages/facebook/facebook_server.js | 1 + packages/facebook/package.js | 1 - packages/github/github_client.js | 1 + packages/github/github_common.js | 2 - packages/github/github_server.js | 2 + packages/github/package.js | 1 - packages/google/google_client.js | 1 + packages/google/google_common.js | 2 - packages/google/google_server.js | 3 ++ packages/google/package.js | 1 - packages/meetup/meetup_client.js | 1 + packages/meetup/meetup_common.js | 2 - packages/meetup/meetup_server.js | 1 + packages/meetup/package.js | 1 - packages/oauth/oauth_client.js | 1 + packages/oauth/oauth_common.js | 2 - packages/oauth/oauth_server.js | 40 ++++++++++---- packages/oauth/package.js | 1 - packages/oauth1/oauth1_common.js | 2 - packages/oauth1/oauth1_server.js | 11 ++-- packages/oauth1/oauth1_tests.js | 6 +-- packages/oauth1/package.js | 1 - packages/oauth2/oauth2_common.js | 1 - packages/oauth2/oauth2_tests.js | 4 +- packages/oauth2/package.js | 1 - packages/twitter/package.js | 1 - packages/twitter/twitter_client.js | 1 + packages/twitter/twitter_common.js | 9 ---- packages/twitter/twitter_server.js | 12 ++++- packages/webapp/webapp_server.js | 11 ++-- packages/weibo/package.js | 1 - packages/weibo/weibo_client.js | 1 + packages/weibo/weibo_common.js | 2 - packages/weibo/weibo_server.js | 1 + 68 files changed, 237 insertions(+), 219 deletions(-) delete mode 100644 packages/accounts-facebook/facebook_common.js delete mode 100644 packages/accounts-github/github_common.js delete mode 100644 packages/accounts-google/google_common.js delete mode 100644 packages/accounts-meetup/meetup_common.js delete mode 100644 packages/accounts-oauth/oauth_common.js delete mode 100644 packages/accounts-password/password_common.js delete mode 100644 packages/accounts-twitter/twitter_common.js delete mode 100644 packages/accounts-weibo/weibo_common.js delete mode 100644 packages/facebook/facebook_common.js delete mode 100644 packages/github/github_common.js delete mode 100644 packages/google/google_common.js delete mode 100644 packages/meetup/meetup_common.js delete mode 100644 packages/oauth/oauth_common.js delete mode 100644 packages/oauth1/oauth1_common.js delete mode 100644 packages/oauth2/oauth2_common.js delete mode 100644 packages/twitter/twitter_common.js delete mode 100644 packages/weibo/weibo_common.js diff --git a/packages/accounts-base/accounts_client.js b/packages/accounts-base/accounts_client.js index 589ec097b1..57394372ed 100644 --- a/packages/accounts-base/accounts_client.js +++ b/packages/accounts-base/accounts_client.js @@ -13,6 +13,7 @@ var loggingInDeps = new Deps.Dependency; // This is mostly just called within this file, but Meteor.loginWithPassword // also uses it to make loggingIn() be true during the beginPasswordExchange // method call too. +// @export Accounts._setLoggingIn Accounts._setLoggingIn = function (x) { if (loggingIn !== x) { loggingIn = x; @@ -60,6 +61,8 @@ Meteor.user = function () { // its error will be passed to the callback). // - userCallback: Will be called with no arguments once the user is fully // logged in, or with the error on error. +// +// @export Accounts.callLoginMethod Accounts.callLoginMethod = function (options) { options = _.extend({ methodName: 'login', @@ -81,11 +84,11 @@ Accounts.callLoginMethod = function (options) { // getting the results of subscription rerun, we WILL NOT re-send this // method (because we never re-send methods whose results we've received) // but we WILL call loggedInAndDataReadyCallback at "reconnect quiesce" - // time. This will lead to _makeClientLoggedIn(result.id) even though we + // time. This will lead to makeClientLoggedIn(result.id) even though we // haven't actually sent a login method! // // But by making sure that we send this "resume" login in that case (and - // calling _makeClientLoggedOut if it fails), we'll end up with an accurate + // calling makeClientLoggedOut if it fails), we'll end up with an accurate // client-side userId. (It's important that livedata_connection guarantees // that the "reconnect quiesce"-time call to loggedInAndDataReadyCallback // will occur before the callback from the resume login call.) @@ -103,7 +106,7 @@ Accounts.callLoginMethod = function (options) { _suppressLoggingIn: true, userCallback: function (error) { if (error) { - Accounts._makeClientLoggedOut(); + makeClientLoggedOut(); } options.userCallback(error); }}); @@ -141,7 +144,7 @@ Accounts.callLoginMethod = function (options) { } // Make the client logged in. (The user data should already be loaded!) - Accounts._makeClientLoggedIn(result.id, result.token); + makeClientLoggedIn(result.id, result.token); options.userCallback(); }; @@ -154,14 +157,14 @@ Accounts.callLoginMethod = function (options) { loggedInAndDataReadyCallback); }; -Accounts._makeClientLoggedOut = function() { - Accounts._unstoreLoginToken(); +makeClientLoggedOut = function() { + unstoreLoginToken(); Meteor.connection.setUserId(null); Meteor.connection.onReconnect = null; }; -Accounts._makeClientLoggedIn = function(userId, token) { - Accounts._storeLoginToken(userId, token); +makeClientLoggedIn = function(userId, token) { + storeLoginToken(userId, token); Meteor.connection.setUserId(userId); }; @@ -171,7 +174,7 @@ Meteor.logout = function (callback) { if (error) { callback && callback(error); } else { - Accounts._makeClientLoggedOut(); + makeClientLoggedOut(); callback && callback(); } }); @@ -186,6 +189,8 @@ var loginServicesHandle = Meteor.subscribe("meteor.loginServiceConfiguration"); // A reactive function returning whether the loginServiceConfiguration // subscription is ready. Used by accounts-ui to hide the login button // until we have all the configuration loaded +// +// @export Accounts.loginServicesConfigured Accounts.loginServicesConfigured = function () { return loginServicesHandle.ready(); }; diff --git a/packages/accounts-base/accounts_common.js b/packages/accounts-base/accounts_common.js index 9eecc2e372..9a7a0666da 100644 --- a/packages/accounts-base/accounts_common.js +++ b/packages/accounts-base/accounts_common.js @@ -1,10 +1,7 @@ -// @export Accounts -if (typeof Accounts === 'undefined') - Accounts = {}; - -if (!Accounts._options) { - Accounts._options = {}; -} +// Currently this is read directly by packages like accounts-password +// and accounts-ui-unstyled. +// @export Accounts._options +Accounts._options = {}; // Set up config for the accounts system. Call this on both the client // and the server. @@ -21,6 +18,8 @@ if (!Accounts._options) { // client signups. // - forbidClientAccountCreation {Boolean} // Do not allow clients to create accounts directly. +// +// @export Accounts.config Accounts.config = function(options) { // validate option keys var VALID_KEYS = ["sendVerificationEmail", "forbidClientAccountCreation"]; @@ -52,11 +51,14 @@ Meteor.users = new Meteor.Collection("users", {_preventAutopublish: true}); // collection. // loginServiceConfiguration and ConfigError are maintained for backwards compatibility +// @export Accounts.loginServiceConfiguration +// @export Accounts.ConfigError Accounts.loginServiceConfiguration = ServiceConfiguration.configurations; Accounts.ConfigError = ServiceConfiguration.ConfigError; // Thrown when the user cancels the login process (eg, closes an oauth // popup, declines retina scan, etc) +// @export Accounts.LoginCancelledError Accounts.LoginCancelledError = function(description) { this.message = description; }; diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index dde3ab301d..45d91b056c 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -38,20 +38,22 @@ Meteor.user = function () { // - `undefined`, meaning don't handle; // - {id: userId, token: *}, if the user logged in successfully. // - throw an error, if the user failed to log in. +// +// @export Accounts.registerLoginHandler Accounts.registerLoginHandler = function(handler) { - Accounts._loginHandlers.push(handler); + loginHandlers.push(handler); }; // list of all registered handlers. -Accounts._loginHandlers = []; +loginHandlers = []; // Try all of the registered login handlers until one of them doesn' // return `undefined`, meaning it handled this call to `login`. Return // that return value, which ought to be a {id/token} pair. var tryAllLoginHandlers = function (options) { - for (var i = 0; i < Accounts._loginHandlers.length; ++i) { - var handler = Accounts._loginHandlers[i]; + for (var i = 0; i < loginHandlers.length; ++i) { + var handler = loginHandlers[i]; var result = handler(options); if (result !== undefined) return result; @@ -82,7 +84,7 @@ Meteor.methods({ logout: function() { if (this._sessionData.loginToken && this.userId) - Accounts._removeLoginToken(this.userId, this._sessionData.loginToken); + removeLoginToken(this.userId, this._sessionData.loginToken); this.setUserId(null); } }); @@ -111,11 +113,13 @@ Accounts.registerLoginHandler(function(options) { }); // Semi-public. Used by other login methods to generate tokens. +// +// @export Accounts._generateStampedLoginToken Accounts._generateStampedLoginToken = function () { return {token: Random.id(), when: +(new Date)}; }; -Accounts._removeLoginToken = function (userId, loginToken) { +removeLoginToken = function (userId, loginToken) { Meteor.users.update(userId, { $pull: { "services.resume.loginTokens": { "token": loginToken } @@ -127,6 +131,8 @@ Accounts._removeLoginToken = function (userId, loginToken) { /// /// CREATE USER HOOKS /// + +// @export Accounts.onCreateUser var onCreateUserHook = null; Accounts.onCreateUser = function (func) { if (onCreateUserHook) @@ -142,6 +148,9 @@ var defaultCreateUserHook = function (options, user) { user.profile = options.profile; return user; }; + +// Called by accounts-password +// @export Accounts.insertUserDoc Accounts.insertUserDoc = function (options, user) { // - clone user document, to protect from modification // - add createdAt timestamp @@ -205,6 +214,7 @@ Accounts.insertUserDoc = function (options, user) { return result; }; +// @export Accounts.validateNewUser var validateNewUserHooks = []; Accounts.validateNewUser = function (func) { validateNewUserHooks.push(func); @@ -225,6 +235,8 @@ Accounts.validateNewUser = function (func) { // (eg, profile) // @returns {Object} Object with token and id keys, like the result // of the "login" method. +// +// @export Accounts.updateOrCreateUserFromExternalService Accounts.updateOrCreateUserFromExternalService = function( serviceName, serviceData, options) { options = _.clone(options || {}); @@ -308,7 +320,7 @@ Meteor.publish(null, function() { // Accounts.addAutopublishFields Notably, this isn't implemented with // multiple publishes since DDP only merges only across top-level // fields, not subfields (such as 'services.facebook.accessToken') -Accounts._autopublishFields = { +autopublishFields = { loggedInUser: ['profile', 'username', 'emails'], otherUsers: ['profile', 'username'] }; @@ -320,10 +332,10 @@ Accounts._autopublishFields = { // - forLoggedInUser {Array} Array of fields published to the logged-in user // - forOtherUsers {Array} Array of fields published to users that aren't logged in Accounts.addAutopublishFields = function(opts) { - Accounts._autopublishFields.loggedInUser.push.apply( - Accounts._autopublishFields.loggedInUser, opts.forLoggedInUser); - Accounts._autopublishFields.otherUsers.push.apply( - Accounts._autopublishFields.otherUsers, opts.forOtherUsers); + autopublishFields.loggedInUser.push.apply( + autopublishFields.loggedInUser, opts.forLoggedInUser); + autopublishFields.otherUsers.push.apply( + autopublishFields.otherUsers, opts.forOtherUsers); }; Meteor.server.onAutopublish(function () { @@ -338,7 +350,7 @@ Meteor.server.onAutopublish(function () { if (this.userId) { return Meteor.users.find( {_id: this.userId}, - {fields: toFieldSelector(Accounts._autopublishFields.loggedInUser)}); + {fields: toFieldSelector(autopublishFields.loggedInUser)}); } else { return null; } @@ -359,7 +371,7 @@ Meteor.server.onAutopublish(function () { return Meteor.users.find( selector, - {fields: toFieldSelector(Accounts._autopublishFields.otherUsers)}); + {fields: toFieldSelector(autopublishFields.otherUsers)}); }, /*suppress autopublish warning*/{is_auto: true}); }); diff --git a/packages/accounts-base/localstorage_token.js b/packages/accounts-base/localstorage_token.js index 81a945e23f..5afcf24133 100644 --- a/packages/accounts-base/localstorage_token.js +++ b/packages/accounts-base/localstorage_token.js @@ -12,11 +12,20 @@ Meteor.loginWithToken = function (token, callback) { userCallback: callback}); }; +var autoLoginEnabled = true; + +// Semi-internal API. Call at startup to prevent automatic login. +// @export Accounts._disableAutoLogin +Acocunts._disableAutoLogin = function () { + autoLoginEnabled = false; +}; + // Semi-internal API. Call this function to re-enable auto login after // if it was disabled at startup. +// @export Accounts._enableAutoLogin Accounts._enableAutoLogin = function () { - Accounts._preventAutoLogin = false; - Accounts._pollStoredLoginToken(); + autoLoginEnabled = true; + pollStoredLoginToken(); }; @@ -31,34 +40,39 @@ var userIdKey = "Meteor.userId"; // Call this from the top level of the test file for any test that does // logging in and out, to protect multiple tabs running the same tests // simultaneously from interfering with each others' localStorage. +// @export Accounts._isolateLoginTokenForTest Accounts._isolateLoginTokenForTest = function () { loginTokenKey = loginTokenKey + Random.id(); userIdKey = userIdKey + Random.id(); }; -Accounts._storeLoginToken = function(userId, token) { +storeLoginToken = function(userId, token) { Meteor._localStorage.setItem(userIdKey, userId); Meteor._localStorage.setItem(loginTokenKey, token); // to ensure that the localstorage poller doesn't end up trying to // connect a second time - Accounts._lastLoginTokenWhenPolled = token; + lastLoginTokenWhenPolled = token; }; -Accounts._unstoreLoginToken = function() { +unstoreLoginToken = function() { Meteor._localStorage.removeItem(userIdKey); Meteor._localStorage.removeItem(loginTokenKey); // to ensure that the localstorage poller doesn't end up trying to // connect a second time - Accounts._lastLoginTokenWhenPolled = null; + lastLoginTokenWhenPolled = null; }; -Accounts._storedLoginToken = function() { +// This is private, but it is exported for now because it is used by a +// test in accounts-password. +// +// @export Accounts._storedLoginToken +storedLoginToken = Accounts._storedLoginToken = function() { return Meteor._localStorage.getItem(loginTokenKey); }; -Accounts._storedUserId = function() { +storedUserId = function() { return Meteor._localStorage.getItem(userIdKey); }; @@ -67,19 +81,19 @@ Accounts._storedUserId = function() { /// AUTO-LOGIN /// -if (!Accounts._preventAutoLogin) { +if (autoLoginEnabled) { // Immediately try to log in via local storage, so that any DDP // messages are sent after we have established our user account - var token = Accounts._storedLoginToken(); + var token = storedLoginToken(); if (token) { // On startup, optimistically present us as logged in while the // request is in flight. This reduces page flicker on startup. - var userId = Accounts._storedUserId(); + var userId = storedUserId(); userId && Meteor.connection.setUserId(userId); Meteor.loginWithToken(token, function (err) { if (err) { Meteor._debug("Error logging in with token: " + err); - Accounts._makeClientLoggedOut(); + makeClientLoggedOut(); } }); } @@ -87,21 +101,21 @@ if (!Accounts._preventAutoLogin) { // Poll local storage every 3 seconds to login if someone logged in in // another tab -Accounts._lastLoginTokenWhenPolled = token; -Accounts._pollStoredLoginToken = function() { - if (Accounts._preventAutoLogin) +lastLoginTokenWhenPolled = token; +pollStoredLoginToken = function() { + if (! autoLoginEnabled) return; - var currentLoginToken = Accounts._storedLoginToken(); + var currentLoginToken = storedLoginToken(); // != instead of !== just to make sure undefined and null are treated the same - if (Accounts._lastLoginTokenWhenPolled != currentLoginToken) { + if (lastLoginTokenWhenPolled != currentLoginToken) { if (currentLoginToken) Meteor.loginWithToken(currentLoginToken); // XXX should we pass a callback here? else Meteor.logout(); } - Accounts._lastLoginTokenWhenPolled = currentLoginToken; + lastLoginTokenWhenPolled = currentLoginToken; }; -setInterval(Accounts._pollStoredLoginToken, 3000); +setInterval(pollStoredLoginToken, 3000); diff --git a/packages/accounts-facebook/facebook_common.js b/packages/accounts-facebook/facebook_common.js deleted file mode 100644 index 171ca036f6..0000000000 --- a/packages/accounts-facebook/facebook_common.js +++ /dev/null @@ -1,3 +0,0 @@ -if (!Accounts.facebook) { - Accounts.facebook = {}; -} diff --git a/packages/accounts-facebook/package.js b/packages/accounts-facebook/package.js index 667f56bb32..158c5ca581 100644 --- a/packages/accounts-facebook/package.js +++ b/packages/accounts-facebook/package.js @@ -11,7 +11,6 @@ Package.on_use(function(api) { api.add_files('facebook_login_button.css', 'client'); - api.add_files('facebook_common.js', ['client', 'server']); api.add_files('facebook_server.js', 'server'); api.add_files('facebook_client.js', 'client'); }); diff --git a/packages/accounts-github/github_common.js b/packages/accounts-github/github_common.js deleted file mode 100644 index 0e9b508596..0000000000 --- a/packages/accounts-github/github_common.js +++ /dev/null @@ -1,3 +0,0 @@ -if (!Accounts.github) { - Accounts.github = {}; -} diff --git a/packages/accounts-github/package.js b/packages/accounts-github/package.js index 3855b12c23..5573b0360e 100644 --- a/packages/accounts-github/package.js +++ b/packages/accounts-github/package.js @@ -11,7 +11,6 @@ Package.on_use(function(api) { api.add_files('github_login_button.css', 'client'); - api.add_files('github_common.js', ['client', 'server']); api.add_files('github_server.js', 'server'); api.add_files('github_client.js', 'client'); }); diff --git a/packages/accounts-google/google_common.js b/packages/accounts-google/google_common.js deleted file mode 100644 index f3945c2c23..0000000000 --- a/packages/accounts-google/google_common.js +++ /dev/null @@ -1,3 +0,0 @@ -if (!Accounts.google) { - Accounts.google = {}; -} diff --git a/packages/accounts-google/package.js b/packages/accounts-google/package.js index aac4af35c2..bab6125722 100644 --- a/packages/accounts-google/package.js +++ b/packages/accounts-google/package.js @@ -12,7 +12,6 @@ Package.on_use(function(api) { api.add_files('google_login_button.css', 'client'); - api.add_files('google_common.js', ['client', 'server']); api.add_files('google_server.js', 'server'); api.add_files('google_client.js', 'client'); }); diff --git a/packages/accounts-meetup/meetup_common.js b/packages/accounts-meetup/meetup_common.js deleted file mode 100644 index 4eb47fff80..0000000000 --- a/packages/accounts-meetup/meetup_common.js +++ /dev/null @@ -1,3 +0,0 @@ -if (!Accounts.meetup) { - Accounts.meetup = {}; -} diff --git a/packages/accounts-meetup/package.js b/packages/accounts-meetup/package.js index 4616c99246..a5537b1cee 100644 --- a/packages/accounts-meetup/package.js +++ b/packages/accounts-meetup/package.js @@ -11,7 +11,6 @@ Package.on_use(function(api) { api.add_files('meetup_login_button.css', 'client'); - api.add_files('meetup_common.js', ['client', 'server']); api.add_files('meetup_server.js', 'server'); api.add_files('meetup_client.js', 'client'); }); diff --git a/packages/accounts-oauth/oauth_client.js b/packages/accounts-oauth/oauth_client.js index f3d161e8c0..7563044b38 100644 --- a/packages/accounts-oauth/oauth_client.js +++ b/packages/accounts-oauth/oauth_client.js @@ -1,6 +1,7 @@ // Send an OAuth login method to the server. If the user authorized // access in the popup this should log the user in, otherwise // nothing should happen. +// @export Accounts.oauth.tryLoginAfterPopupClosed Accounts.oauth.tryLoginAfterPopupClosed = function(credentialToken, callback) { Accounts.callLoginMethod({ methodArguments: [{oauth: {credentialToken: credentialToken}}], @@ -16,6 +17,7 @@ Accounts.oauth.tryLoginAfterPopupClosed = function(credentialToken, callback) { }}); }; +// @export Accounts.oauth.credentialRequestCompleteHandler Accounts.oauth.credentialRequestCompleteHandler = function(callback) { return function (credentialTokenOrError) { if(credentialTokenOrError && credentialTokenOrError instanceof Error) { diff --git a/packages/accounts-oauth/oauth_common.js b/packages/accounts-oauth/oauth_common.js deleted file mode 100644 index d47da20292..0000000000 --- a/packages/accounts-oauth/oauth_common.js +++ /dev/null @@ -1 +0,0 @@ -Accounts.oauth = {}; \ No newline at end of file diff --git a/packages/accounts-oauth/oauth_server.js b/packages/accounts-oauth/oauth_server.js index 775811289e..4f54942e02 100644 --- a/packages/accounts-oauth/oauth_server.js +++ b/packages/accounts-oauth/oauth_server.js @@ -1,5 +1,6 @@ // Helper for registering OAuth based accounts packages. // Adds an index to the user collection. +// @export Accounts.oauth.registerService Accounts.oauth.registerService = function (name) { // Accounts.updateOrCreateUserFromExternalService does a lookup by this id, // so this should be a unique index. You might want to add indexes for other @@ -12,6 +13,7 @@ Accounts.oauth.registerService = function (name) { // For test cleanup only. (Mongo has a limit as to how many indexes it can have // per collection.) +// @export Accounts.oauth._unregisterService Accounts.oauth._unregisterService = function (name) { var index = {}; index['services.' + name + '.id'] = 1; diff --git a/packages/accounts-oauth/package.js b/packages/accounts-oauth/package.js index d3fb7b984b..8eb2d7755c 100644 --- a/packages/accounts-oauth/package.js +++ b/packages/accounts-oauth/package.js @@ -13,7 +13,6 @@ Package.on_use(function (api) { api.imply('accounts-base', ['client', 'server']); api.use('oauth', 'server'); - api.add_files('oauth_common.js', ['client', 'server']); api.add_files('oauth_client.js', 'client'); api.add_files('oauth_server.js', 'server'); }); diff --git a/packages/accounts-password/email_templates.js b/packages/accounts-password/email_templates.js index fbcca8419a..c52e7de33e 100644 --- a/packages/accounts-password/email_templates.js +++ b/packages/accounts-password/email_templates.js @@ -1,3 +1,4 @@ +// @export Accounts.emailTemplates Accounts.emailTemplates = { from: "Meteor Accounts ", siteName: Meteor.absoluteUrl().replace(/^https?:\/\//, '').replace(/\/$/, ''), diff --git a/packages/accounts-password/package.js b/packages/accounts-password/package.js index 3829d5caa7..676ac1c91f 100644 --- a/packages/accounts-password/package.js +++ b/packages/accounts-password/package.js @@ -16,7 +16,6 @@ Package.on_use(function(api) { api.add_files('email_templates.js', 'server'); api.add_files('password_server.js', 'server'); api.add_files('password_client.js', 'client'); - api.add_files('password_common.js', ['server', 'client']); }); Package.on_test(function(api) { diff --git a/packages/accounts-password/password_client.js b/packages/accounts-password/password_client.js index d9e63f130a..2097d41e93 100644 --- a/packages/accounts-password/password_client.js +++ b/packages/accounts-password/password_client.js @@ -46,6 +46,7 @@ Meteor.loginWithPassword = function (selector, password, callback) { // Attempt to log in as a new user. +// @export Accounts.createUser Accounts.createUser = function (options, callback) { options = _.clone(options); // we'll be modifying options @@ -67,6 +68,7 @@ Accounts.createUser = function (options, callback) { // Change password. Must be logged in. // +// @export Accounts.changePassword // @param oldPassword {String|null} By default servers no longer allow // changing password without the old password, but they could so we // support passing no password to the server and letting it decide. @@ -122,6 +124,7 @@ Accounts.changePassword = function (oldPassword, newPassword, callback) { // Sends an email to a user with a link that can be used to reset // their password // +// @export Accounts.forgotPassword // @param options {Object} // - email: (email) // @param callback (optional) {Function(error|undefined)} @@ -134,6 +137,7 @@ Accounts.forgotPassword = function(options, callback) { // Resets a password based on a token originally created by // Accounts.forgotPassword, and then logs in the matching user. // +// @export Accounts.resetPassword // @param token {String} // @param newPassword {String} // @param callback (optional) {Function(error|undefined)} @@ -153,6 +157,7 @@ Accounts.resetPassword = function(token, newPassword, callback) { // Verifies a user's email address based on a token originally // created by Accounts.sendVerificationEmail // +// @export Accounts.verifyEmail // @param token {String} // @param callback (optional) {Function(error|undefined)} Accounts.verifyEmail = function(token, callback) { diff --git a/packages/accounts-password/password_common.js b/packages/accounts-password/password_common.js deleted file mode 100644 index 7ad0470e16..0000000000 --- a/packages/accounts-password/password_common.js +++ /dev/null @@ -1 +0,0 @@ -Accounts.password = {}; diff --git a/packages/accounts-password/password_server.js b/packages/accounts-password/password_server.js index 070ce4a1f4..e153b36c0f 100644 --- a/packages/accounts-password/password_server.js +++ b/packages/accounts-password/password_server.js @@ -188,6 +188,7 @@ Meteor.methods({changePassword: function (options) { // Force change the users password. +// @export Accounts.setPassword Accounts.setPassword = function (userId, newPassword) { var user = Meteor.users.findOne(userId); if (!user) @@ -217,6 +218,8 @@ Meteor.methods({forgotPassword: function (options) { // send the user an email with a link that when opened allows the user // to set a new password, without the old password. +// +// @export Accounts.sendResetPasswordEmail Accounts.sendResetPasswordEmail = function (userId, email) { // Make sure the user exists, and email is one of their addresses. var user = Meteor.users.findOne(userId); @@ -252,8 +255,10 @@ Accounts.sendResetPasswordEmail = function (userId, email) { // to choose their password. The email must be one of the addresses in the // user's emails field, or undefined to pick the first email automatically. // -// This is not called automatically, it must be called manually if you +// This is not called automatically. It must be called manually if you // want to use enrollment emails. +// +// @export Accounts.sendResetPasswordEmail Accounts.sendEnrollmentEmail = function (userId, email) { // XXX refactor! This is basically identical to sendResetPasswordEmail. @@ -329,6 +334,8 @@ Meteor.methods({resetPassword: function (token, newVerifier) { // send the user an email with a link that when opened marks that // address as verified +// +// @export Accounts.sendVerificationEmail Accounts.sendVerificationEmail = function (userId, address) { // XXX Also generate a link using which someone can delete this // account if they own said address but weren't those who created @@ -493,6 +500,8 @@ Meteor.methods({createUser: function (options) { // which is always empty when called from the createUser method? eg, "admin: // true", which we want to prevent the client from setting, but which a custom // method calling Accounts.createUser could set? +// +// @export Accounts.createUser Accounts.createUser = function (options, callback) { options = _.clone(options); options.generateLoginToken = false; diff --git a/packages/accounts-twitter/package.js b/packages/accounts-twitter/package.js index a29c8f00ef..ce32f38084 100644 --- a/packages/accounts-twitter/package.js +++ b/packages/accounts-twitter/package.js @@ -15,7 +15,6 @@ Package.on_use(function(api) { api.add_files('twitter_login_button.css', 'client'); - api.add_files('twitter_common.js', ['client', 'server']); api.add_files('twitter_server.js', 'server'); api.add_files('twitter_client.js', 'client'); }); diff --git a/packages/accounts-twitter/twitter_common.js b/packages/accounts-twitter/twitter_common.js deleted file mode 100644 index b1428d3bf4..0000000000 --- a/packages/accounts-twitter/twitter_common.js +++ /dev/null @@ -1,3 +0,0 @@ -if (!Accounts.twitter) { - Accounts.twitter = {}; -} diff --git a/packages/accounts-ui-unstyled/accounts_ui.js b/packages/accounts-ui-unstyled/accounts_ui.js index 75ed55e7c4..ad143e9327 100644 --- a/packages/accounts-ui-unstyled/accounts_ui.js +++ b/packages/accounts-ui-unstyled/accounts_ui.js @@ -1,14 +1,11 @@ -if (!Accounts.ui) - Accounts.ui = {}; - -if (!Accounts.ui._options) { - Accounts.ui._options = { - requestPermissions: {}, - requestOfflineToken: {} - }; -} - +// XXX this shouldn't need to be exported +// @export Accoutns.ui._options +Accounts.ui._options = { + requestPermissions: {}, + requestOfflineToken: {} +}; +// @export Accounts.ui.config Accounts.ui.config = function(options) { // validate options keys var VALID_KEYS = ['passwordSignupFields', 'requestPermissions', 'requestOfflineToken']; @@ -62,7 +59,7 @@ Accounts.ui.config = function(options) { } }; -Accounts.ui._passwordSignupFields = function () { +passwordSignupFields = function () { return Accounts.ui._options.passwordSignupFields || "EMAIL_ONLY"; }; diff --git a/packages/accounts-ui-unstyled/login_buttons.js b/packages/accounts-ui-unstyled/login_buttons.js index e7d2e88009..2310c64249 100644 --- a/packages/accounts-ui-unstyled/login_buttons.js +++ b/packages/accounts-ui-unstyled/login_buttons.js @@ -1,6 +1,3 @@ -if (!Accounts._loginButtons) - Accounts._loginButtons = {}; - // for convenience var loginButtonsSession = Accounts._loginButtonsSession; @@ -30,16 +27,12 @@ Template._loginButtons.preserve({ // loginButtonLoggedOut template // -Template._loginButtonsLoggedOut.dropdown = function () { - return Accounts._loginButtons.dropdown(); -}; +Template._loginButtonsLoggedOut.dropdown = dropdown; -Template._loginButtonsLoggedOut.services = function () { - return Accounts._loginButtons.getLoginServices(); -}; +Template._loginButtonsLoggedOut.services = getLoginServices; Template._loginButtonsLoggedOut.singleService = function () { - var services = Accounts._loginButtons.getLoginServices(); + var services = getLoginServices(); if (services.length !== 1) throw new Error( "Shouldn't be rendering this template with more than one configured service"); @@ -57,9 +50,7 @@ Template._loginButtonsLoggedOut.configurationLoaded = function () { // decide whether we should show a dropdown rather than a row of // buttons -Template._loginButtonsLoggedIn.dropdown = function () { - return Accounts._loginButtons.dropdown(); -}; +Template._loginButtonsLoggedIn.dropdown = dropdown; @@ -67,9 +58,7 @@ Template._loginButtonsLoggedIn.dropdown = function () { // loginButtonsLoggedInSingleLogoutButton template // -Template._loginButtonsLoggedInSingleLogoutButton.displayName = function () { - return Accounts._loginButtons.displayName(); -}; +Template._loginButtonsLoggedInSingleLogoutButton.displayName = displayName; @@ -90,16 +79,14 @@ Template._loginButtonsMessages.infoMessage = function () { // loginButtonsLoggingInPadding template // -Template._loginButtonsLoggingInPadding.dropdown = function () { - return Accounts._loginButtons.dropdown(); -}; +Template._loginButtonsLoggingInPadding.dropdown = dropdown; // // helpers // -Accounts._loginButtons.displayName = function () { +displayName = function () { var user = Meteor.user(); if (!user) return ''; @@ -121,7 +108,7 @@ Accounts._loginButtons.displayName = function () { // NOTE: It is very important to have this return password last // because of the way we render the different providers in // login_buttons_dropdown.html -Accounts._loginButtons.getLoginServices = function () { +getLoginServices = function () { var self = this; var services = []; @@ -161,19 +148,19 @@ Accounts._loginButtons.getLoginServices = function () { }); }; -Accounts._loginButtons.hasPasswordService = function () { +hasPasswordService = function () { return Accounts.password; }; -Accounts._loginButtons.dropdown = function () { - return Accounts._loginButtons.hasPasswordService() || Accounts._loginButtons.getLoginServices().length > 1; +dropdown = function () { + return hasPasswordService() || getLoginServices().length > 1; }; // XXX improve these. should this be in accounts-password instead? // // XXX these will become configurable, and will be validated on // the server as well. -Accounts._loginButtons.validateUsername = function (username) { +validateUsername = function (username) { if (username.length >= 3) { return true; } else { @@ -181,8 +168,8 @@ Accounts._loginButtons.validateUsername = function (username) { return false; } }; -Accounts._loginButtons.validateEmail = function (email) { - if (Accounts.ui._passwordSignupFields() === "USERNAME_AND_OPTIONAL_EMAIL" && email === '') +validateEmail = function (email) { + if (passwordSignupFields() === "USERNAME_AND_OPTIONAL_EMAIL" && email === '') return true; if (email.indexOf('@') !== -1) { @@ -192,7 +179,7 @@ Accounts._loginButtons.validateEmail = function (email) { return false; } }; -Accounts._loginButtons.validatePassword = function (password) { +validatePassword = function (password) { if (password.length >= 6) { return true; } else { diff --git a/packages/accounts-ui-unstyled/login_buttons_dialogs.js b/packages/accounts-ui-unstyled/login_buttons_dialogs.js index 67bc3c4c79..59d4ef1365 100644 --- a/packages/accounts-ui-unstyled/login_buttons_dialogs.js +++ b/packages/accounts-ui-unstyled/login_buttons_dialogs.js @@ -53,7 +53,7 @@ Template._resetPasswordDialog.events({ var resetPassword = function () { loginButtonsSession.resetMessages(); var newPassword = document.getElementById('reset-password-new-password').value; - if (!Accounts._loginButtons.validatePassword(newPassword)) + if (!validatePassword(newPassword)) return; Accounts.resetPassword( @@ -94,7 +94,7 @@ Template._enrollAccountDialog.events({ var enrollAccount = function () { loginButtonsSession.resetMessages(); var password = document.getElementById('enroll-account-password').value; - if (!Accounts._loginButtons.validatePassword(password)) + if (!validatePassword(password)) return; Accounts.resetPassword( @@ -141,7 +141,7 @@ Template._loginButtonsMessagesDialog.events({ Template._loginButtonsMessagesDialog.visible = function () { var hasMessage = loginButtonsSession.get('infoMessage') || loginButtonsSession.get('errorMessage'); - return !Accounts._loginButtons.dropdown() && hasMessage; + return !dropdown() && hasMessage; }; diff --git a/packages/accounts-ui-unstyled/login_buttons_dropdown.js b/packages/accounts-ui-unstyled/login_buttons_dropdown.js index 41238c6e58..89f61f4334 100644 --- a/packages/accounts-ui-unstyled/login_buttons_dropdown.js +++ b/packages/accounts-ui-unstyled/login_buttons_dropdown.js @@ -26,9 +26,7 @@ Template._loginButtonsLoggedInDropdown.events({ } }); -Template._loginButtonsLoggedInDropdown.displayName = function () { - return Accounts._loginButtons.displayName(); -}; +Template._loginButtonsLoggedInDropdown.displayName = displayName; Template._loginButtonsLoggedInDropdown.inChangePasswordFlow = function () { return loginButtonsSession.get('inChangePasswordFlow'); @@ -175,26 +173,21 @@ Template._loginButtonsLoggedOutDropdown.dropdownVisible = function () { return loginButtonsSession.get('dropdownVisible'); }; -Template._loginButtonsLoggedOutDropdown.hasPasswordService = function () { - return Accounts._loginButtons.hasPasswordService(); -}; +Template._loginButtonsLoggedOutDropdown.hasPasswordService = hasPasswordService; // return all login services, with password last -Template._loginButtonsLoggedOutAllServices.services = function () { - return Accounts._loginButtons.getLoginServices(); -}; +Template._loginButtonsLoggedOutAllServices.services = getLoginServices; Template._loginButtonsLoggedOutAllServices.isPasswordService = function () { return this.name === 'password'; }; Template._loginButtonsLoggedOutAllServices.hasOtherServices = function () { - return Accounts._loginButtons.getLoginServices().length > 1; + return getLoginServices().length > 1; }; -Template._loginButtonsLoggedOutAllServices.hasPasswordService = function () { - return Accounts._loginButtons.hasPasswordService(); -}; +Template._loginButtonsLoggedOutAllServices.hasPasswordService = + hasPasswordService; Template._loginButtonsLoggedOutPasswordService.fields = function () { var loginFields = [ @@ -202,15 +195,15 @@ Template._loginButtonsLoggedOutPasswordService.fields = function () { visible: function () { return _.contains( ["USERNAME_AND_EMAIL", "USERNAME_AND_OPTIONAL_EMAIL"], - Accounts.ui._passwordSignupFields()); + passwordSignupFields()); }}, {fieldName: 'username', fieldLabel: 'Username', visible: function () { - return Accounts.ui._passwordSignupFields() === "USERNAME_ONLY"; + return passwordSignupFields() === "USERNAME_ONLY"; }}, {fieldName: 'email', fieldLabel: 'Email', inputType: 'email', visible: function () { - return Accounts.ui._passwordSignupFields() === "EMAIL_ONLY"; + return passwordSignupFields() === "EMAIL_ONLY"; }}, {fieldName: 'password', fieldLabel: 'Password', inputType: 'password', visible: function () { @@ -223,17 +216,17 @@ Template._loginButtonsLoggedOutPasswordService.fields = function () { visible: function () { return _.contains( ["USERNAME_AND_EMAIL", "USERNAME_AND_OPTIONAL_EMAIL", "USERNAME_ONLY"], - Accounts.ui._passwordSignupFields()); + passwordSignupFields()); }}, {fieldName: 'email', fieldLabel: 'Email', inputType: 'email', visible: function () { return _.contains( ["USERNAME_AND_EMAIL", "EMAIL_ONLY"], - Accounts.ui._passwordSignupFields()); + passwordSignupFields()); }}, {fieldName: 'email', fieldLabel: 'Email (optional)', inputType: 'email', visible: function () { - return Accounts.ui._passwordSignupFields() === "USERNAME_AND_OPTIONAL_EMAIL"; + return passwordSignupFields() === "USERNAME_AND_OPTIONAL_EMAIL"; }}, {fieldName: 'password', fieldLabel: 'Password', inputType: 'password', visible: function () { @@ -247,7 +240,7 @@ Template._loginButtonsLoggedOutPasswordService.fields = function () { // the "forgot password" flow. return _.contains( ["USERNAME_AND_OPTIONAL_EMAIL", "USERNAME_ONLY"], - Accounts.ui._passwordSignupFields()); + passwordSignupFields()); }} ]; @@ -273,7 +266,7 @@ Template._loginButtonsLoggedOutPasswordService.showCreateAccountLink = function Template._loginButtonsLoggedOutPasswordService.showForgotPasswordLink = function () { return _.contains( ["USERNAME_AND_EMAIL", "USERNAME_AND_OPTIONAL_EMAIL", "EMAIL_ONLY"], - Accounts.ui._passwordSignupFields()); + passwordSignupFields()); }; Template._loginButtonsFormField.inputType = function () { @@ -313,7 +306,7 @@ Template._loginButtonsChangePassword.fields = function () { // the "forgot password" flow. return _.contains( ["USERNAME_AND_OPTIONAL_EMAIL", "USERNAME_ONLY"], - Accounts.ui._passwordSignupFields()); + passwordSignupFields()); }} ]; }; @@ -357,19 +350,19 @@ var login = function () { var loginSelector; if (username !== null) { - if (!Accounts._loginButtons.validateUsername(username)) + if (!validateUsername(username)) return; else loginSelector = {username: username}; } else if (email !== null) { - if (!Accounts._loginButtons.validateEmail(email)) + if (!validateEmail(email)) return; else loginSelector = {email: email}; } else if (usernameOrEmail !== null) { // XXX not sure how we should validate this. but this seems good enough (for now), // since an email must have at least 3 characters anyways - if (!Accounts._loginButtons.validateUsername(usernameOrEmail)) + if (!validateUsername(usernameOrEmail)) return; else loginSelector = usernameOrEmail; @@ -393,7 +386,7 @@ var signup = function () { var username = trimmedElementValueById('login-username'); if (username !== null) { - if (!Accounts._loginButtons.validateUsername(username)) + if (!validateUsername(username)) return; else options.username = username; @@ -401,7 +394,7 @@ var signup = function () { var email = trimmedElementValueById('login-email'); if (email !== null) { - if (!Accounts._loginButtons.validateEmail(email)) + if (!validateEmail(email)) return; else options.email = email; @@ -409,7 +402,7 @@ var signup = function () { // notably not trimmed. a password could (?) start or end with a space var password = elementValueById('login-password'); - if (!Accounts._loginButtons.validatePassword(password)) + if (!validatePassword(password)) return; else options.password = password; @@ -450,7 +443,7 @@ var changePassword = function () { // notably not trimmed. a password could (?) start or end with a space var password = elementValueById('login-password'); - if (!Accounts._loginButtons.validatePassword(password)) + if (!validatePassword(password)) return; if (!matchPasswordAgainIfPresent()) diff --git a/packages/accounts-ui-unstyled/login_buttons_session.js b/packages/accounts-ui-unstyled/login_buttons_session.js index fdf3d2ad25..964bba29c2 100644 --- a/packages/accounts-ui-unstyled/login_buttons_session.js +++ b/packages/accounts-ui-unstyled/login_buttons_session.js @@ -27,7 +27,11 @@ var validateKey = function (key) { var KEY_PREFIX = "Meteor.loginButtons."; -// XXX we should have a better pattern for code private to a package like this one +// XXX This should probably be package scope rather than exported +// (there was even a comment to that effect here from before we had +// namespacing) but accounts-ui-viewer uses it, so leave it as is for +// now +// @export Accounts._loginButtonsSession Accounts._loginButtonsSession = { set: function(key, value) { validateKey(key); diff --git a/packages/accounts-urls/url_client.js b/packages/accounts-urls/url_client.js index 0130250ce2..b6e5107421 100644 --- a/packages/accounts-urls/url_client.js +++ b/packages/accounts-urls/url_client.js @@ -1,7 +1,3 @@ -// @export Accounts -if (typeof Accounts === 'undefined') - Accounts = {}; - // reads a reset password token from the url's hash fragment, if it's // there. if so prevent automatically logging in since it could be // confusing to be logged in as user A while resetting password for @@ -13,7 +9,7 @@ if (typeof Accounts === 'undefined') var match; match = window.location.hash.match(/^\#\/reset-password\/(.*)$/); if (match) { - Accounts._preventAutoLogin = true; + Accounts._disableAutoLogin(); Accounts._resetPasswordToken = match[1]; window.location.hash = ''; } @@ -30,7 +26,7 @@ if (match) { // in line with the hash fragment approach) match = window.location.hash.match(/^\#\/verify-email\/(.*)$/); if (match) { - Accounts._preventAutoLogin = true; + Accounts._disableAutoLogin(); Accounts._verifyEmailToken = match[1]; window.location.hash = ''; } @@ -40,7 +36,7 @@ if (match) { // reset password links. match = window.location.hash.match(/^\#\/enroll-account\/(.*)$/); if (match) { - Accounts._preventAutoLogin = true; + Accounts._disableAutoLogin(); Accounts._enrollAccountToken = match[1]; window.location.hash = ''; } diff --git a/packages/accounts-urls/url_server.js b/packages/accounts-urls/url_server.js index 6cb4e85fb2..ccc63d0fe2 100644 --- a/packages/accounts-urls/url_server.js +++ b/packages/accounts-urls/url_server.js @@ -1,18 +1,16 @@ -// @export Accounts -if (typeof Accounts === 'undefined') - Accounts = {}; - -if (!Accounts.urls) - Accounts.urls = {}; +// XXX These should probably not actually be public? +// @export Accounts.urls.resetPassword Accounts.urls.resetPassword = function (token) { return Meteor.absoluteUrl('#/reset-password/' + token); }; +// @export Accounts.urls.verifyEmail Accounts.urls.verifyEmail = function (token) { return Meteor.absoluteUrl('#/verify-email/' + token); }; +// @export Accounts.urls.enrollAccount Accounts.urls.enrollAccount = function (token) { return Meteor.absoluteUrl('#/enroll-account/' + token); }; diff --git a/packages/accounts-weibo/package.js b/packages/accounts-weibo/package.js index 22329991f2..65d72d3c85 100644 --- a/packages/accounts-weibo/package.js +++ b/packages/accounts-weibo/package.js @@ -11,7 +11,6 @@ Package.on_use(function(api) { api.add_files('weibo_login_button.css', 'client'); - api.add_files('weibo_common.js', ['client', 'server']); api.add_files('weibo_server.js', 'server'); api.add_files('weibo_client.js', 'client'); }); diff --git a/packages/accounts-weibo/weibo_common.js b/packages/accounts-weibo/weibo_common.js deleted file mode 100644 index 19ec575ef6..0000000000 --- a/packages/accounts-weibo/weibo_common.js +++ /dev/null @@ -1,3 +0,0 @@ -if (!Accounts.weibo) { - Accounts.weibo = {}; -} diff --git a/packages/facebook/facebook_client.js b/packages/facebook/facebook_client.js index d9c2511cde..2ab2508c68 100644 --- a/packages/facebook/facebook_client.js +++ b/packages/facebook/facebook_client.js @@ -1,4 +1,6 @@ // Request Facebook credentials for the user +// +// @export Facebook.requestCredential // @param options {optional} // @param credentialRequestCompleteCallback {Function} Callback function to call on // completion. Takes one argument, credentialToken on success, or Error on diff --git a/packages/facebook/facebook_common.js b/packages/facebook/facebook_common.js deleted file mode 100644 index a062b4555b..0000000000 --- a/packages/facebook/facebook_common.js +++ /dev/null @@ -1,2 +0,0 @@ -// @export Facebook -Facebook = {}; diff --git a/packages/facebook/facebook_server.js b/packages/facebook/facebook_server.js index 59ee6cc729..c219933640 100644 --- a/packages/facebook/facebook_server.js +++ b/packages/facebook/facebook_server.js @@ -91,6 +91,7 @@ var getIdentity = function (accessToken) { } }; +// @export Facebook.retrieveCredential Facebook.retrieveCredential = function(credentialToken) { return Oauth.retrieveCredential(credentialToken); }; diff --git a/packages/facebook/package.js b/packages/facebook/package.js index b8a1f5d8d0..db4b579ba5 100644 --- a/packages/facebook/package.js +++ b/packages/facebook/package.js @@ -18,7 +18,6 @@ Package.on_use(function(api) { ['facebook_configure.html', 'facebook_configure.js'], 'client'); - api.add_files('facebook_common.js', ['client', 'server']); api.add_files('facebook_server.js', 'server'); api.add_files('facebook_client.js', 'client'); }); diff --git a/packages/github/github_client.js b/packages/github/github_client.js index 8a74721272..6b2fbd256e 100644 --- a/packages/github/github_client.js +++ b/packages/github/github_client.js @@ -1,4 +1,5 @@ // Request Github credentials for the user +// @export Github.requestCredential // @param options {optional} // @param credentialRequestCompleteCallback {Function} Callback function to call on // completion. Takes one argument, credentialToken on success, or Error on diff --git a/packages/github/github_common.js b/packages/github/github_common.js deleted file mode 100644 index c00d6ea155..0000000000 --- a/packages/github/github_common.js +++ /dev/null @@ -1,2 +0,0 @@ -// @export Github -Github = {}; diff --git a/packages/github/github_server.js b/packages/github/github_server.js index 00841cedf8..ee20d57e19 100644 --- a/packages/github/github_server.js +++ b/packages/github/github_server.js @@ -62,6 +62,8 @@ var getIdentity = function (accessToken) { } }; + +// @export Github.retrieveCredential Github.retrieveCredential = function(credentialToken) { return Oauth.retrieveCredential(credentialToken); }; diff --git a/packages/github/package.js b/packages/github/package.js index 20bf71d284..54da6ec47e 100644 --- a/packages/github/package.js +++ b/packages/github/package.js @@ -18,7 +18,6 @@ Package.on_use(function(api) { ['github_configure.html', 'github_configure.js'], 'client'); - api.add_files('github_common.js', ['client', 'server']); api.add_files('github_server.js', 'server'); api.add_files('github_client.js', 'client'); }); diff --git a/packages/google/google_client.js b/packages/google/google_client.js index 14313c5297..6f09e06cb0 100644 --- a/packages/google/google_client.js +++ b/packages/google/google_client.js @@ -1,4 +1,5 @@ // Request Google credentials for the user +// @export Google.requestCredential // @param options {optional} // @param credentialRequestCompleteCallback {Function} Callback function to call on // completion. Takes one argument, credentialToken on success, or Error on diff --git a/packages/google/google_common.js b/packages/google/google_common.js deleted file mode 100644 index ccc1d27448..0000000000 --- a/packages/google/google_common.js +++ /dev/null @@ -1,2 +0,0 @@ -// @export Google -Google = {}; diff --git a/packages/google/google_server.js b/packages/google/google_server.js index f6f21da9b4..ca3ba28ab1 100644 --- a/packages/google/google_server.js +++ b/packages/google/google_server.js @@ -1,4 +1,5 @@ // https://developers.google.com/accounts/docs/OAuth2Login#userinfocall +// @export Google.whitelistedFields Google.whitelistedFields = ['id', 'email', 'verified_email', 'name', 'given_name', 'family_name', 'picture', 'locale', 'timezone', 'gender']; @@ -73,6 +74,8 @@ var getIdentity = function (accessToken) { } }; + +// @export Google.retrieveCredential Google.retrieveCredential = function(credentialToken) { return Oauth.retrieveCredential(credentialToken); }; \ No newline at end of file diff --git a/packages/google/package.js b/packages/google/package.js index 4ef3c79683..10e0f7027a 100644 --- a/packages/google/package.js +++ b/packages/google/package.js @@ -16,7 +16,6 @@ Package.on_use(function(api) { ['google_configure.html', 'google_configure.js'], 'client'); - api.add_files('google_common.js', ['client', 'server']); api.add_files('google_server.js', 'server'); api.add_files('google_client.js', 'client'); }); diff --git a/packages/meetup/meetup_client.js b/packages/meetup/meetup_client.js index 1f8cbc4ee0..c876b6d0df 100644 --- a/packages/meetup/meetup_client.js +++ b/packages/meetup/meetup_client.js @@ -1,4 +1,5 @@ // Request Meetup credentials for the user +// @export Meetup.requestCredential // @param options {optional} // @param credentialRequestCompleteCallback {Function} Callback function to call on // completion. Takes one argument, credentialToken on success, or Error on diff --git a/packages/meetup/meetup_common.js b/packages/meetup/meetup_common.js deleted file mode 100644 index dd0eb2ab7a..0000000000 --- a/packages/meetup/meetup_common.js +++ /dev/null @@ -1,2 +0,0 @@ -// @export Meetup -Meetup = {}; diff --git a/packages/meetup/meetup_server.js b/packages/meetup/meetup_server.js index aa5890d9c7..6944576be3 100644 --- a/packages/meetup/meetup_server.js +++ b/packages/meetup/meetup_server.js @@ -51,6 +51,7 @@ var getIdentity = function (accessToken) { }; +// @export Meetup.retrieveCredential Meetup.retrieveCredential = function(credentialToken) { return Oauth.retrieveCredential(credentialToken); }; \ No newline at end of file diff --git a/packages/meetup/package.js b/packages/meetup/package.js index ee38bfbb8e..a36260da76 100644 --- a/packages/meetup/package.js +++ b/packages/meetup/package.js @@ -18,7 +18,6 @@ Package.on_use(function(api) { ['meetup_configure.html', 'meetup_configure.js'], 'client'); - api.add_files('meetup_common.js', ['client', 'server']); api.add_files('meetup_server.js', 'server'); api.add_files('meetup_client.js', 'client'); }); diff --git a/packages/oauth/oauth_client.js b/packages/oauth/oauth_client.js index 4d59040721..cbe25a3664 100644 --- a/packages/oauth/oauth_client.js +++ b/packages/oauth/oauth_client.js @@ -1,5 +1,6 @@ // Open a popup window pointing to a OAuth handshake page // +// @export Oauth.initiateLogin // @param credentialToken {String} The OAuth credentialToken generated by the client // @param url {String} url to page // @param credentialRequestCompleteCallback {Function} Callback function to call on diff --git a/packages/oauth/oauth_common.js b/packages/oauth/oauth_common.js deleted file mode 100644 index 187cbcab0b..0000000000 --- a/packages/oauth/oauth_common.js +++ /dev/null @@ -1,2 +0,0 @@ -// @export Oauth -Oauth = {}; diff --git a/packages/oauth/oauth_server.js b/packages/oauth/oauth_server.js index e71d005a23..c3db701577 100644 --- a/packages/oauth/oauth_server.js +++ b/packages/oauth/oauth_server.js @@ -2,9 +2,13 @@ var Fiber = Npm.require('fibers'); RoutePolicy.declare('/_oauth/', 'network'); -Oauth._services = {}; +var registeredServices = {}; -// Maps from service version to handler function. +// Internal: Maps from service version to handler function. The +// 'oauth1' and 'oauth2' packages manipulate this directly to register +// for callbacks. +// +// @export Oauth._requestHandlers Oauth._requestHandlers = {}; @@ -23,11 +27,13 @@ Oauth._requestHandlers = {}; // - {serviceData:, (optional options:)} where serviceData should end // up in the user's services[name] field // - `null` if the user declined to give permissions +// +// @export Oauth.registerService Oauth.registerService = function (name, version, urls, handleOauthRequest) { - if (Oauth._services[name]) + if (registeredServices[name]) throw new Error("Already registered the " + name + " OAuth service"); - Oauth._services[name] = { + registeredServices[name] = { serviceName: name, version: version, urls: urls, @@ -35,9 +41,10 @@ Oauth.registerService = function (name, version, urls, handleOauthRequest) { }; }; -// For test cleanup only. -Oauth._unregisterService = function (name) { - delete Oauth._services[name]; +// For test cleanup. +// @export _OauthTest.unregisterService +_OauthTest.unregisterService = function (name) { + delete registeredServices[name]; }; @@ -46,13 +53,20 @@ Oauth._unregisterService = function (name) { // results are stored in this map which is then read when the login // method is called. Maps credentialToken --> return value of `login` // +// NB: the oauth1 and oauth2 packages manipulate this directly. might +// be nice for them to have a setter instead +// // XXX we should periodically clear old entries +// +// @export Oauth._loginResultForCredentialToken Oauth._loginResultForCredentialToken = {}; +// @export Oauth.hasCredential Oauth.hasCredential = function(credentialToken) { return _.has(Oauth._loginResultForCredentialToken, credentialToken); } +// @export Oauth.retrieveCredential Oauth.retrieveCredential = function(credentialToken) { var result = Oauth._loginResultForCredentialToken[credentialToken]; delete Oauth._loginResultForCredentialToken[credentialToken]; @@ -64,12 +78,11 @@ WebApp.connectHandlers.use(function(req, res, next) { // Need to create a Fiber since we're using synchronous http calls and nothing // else is wrapping this in a fiber automatically Fiber(function () { - Oauth._middleware(req, res, next); + middleware(req, res, next); }).run(); }); - -Oauth._middleware = function (req, res, next) { +middleware = function (req, res, next) { // Make sure to catch any exceptions because otherwise we'd crash // the runner try { @@ -80,7 +93,7 @@ Oauth._middleware = function (req, res, next) { return; } - var service = Oauth._services[serviceName]; + var service = registeredServices[serviceName]; // Skip everything if there's no service set by the oauth middleware if (!service) @@ -119,6 +132,9 @@ Oauth._middleware = function (req, res, next) { } }; +// @export _OauthTest.middleware +_OauthTest.middleware = middleware; + // Handle /_oauth/* paths and extract the service name // // @returns {String|null} e.g. "facebook", or null if this isn't an @@ -145,6 +161,8 @@ var ensureConfigured = function(serviceName) { }; }; +// Internal: used by the oauth1 and oauth2 packages +// @export Oauth._renderOauthResults Oauth._renderOauthResults = function(res, query) { // We support ?close and ?redirect=URL. Any other query should // just serve a blank page diff --git a/packages/oauth/package.js b/packages/oauth/package.js index 53468511c6..0526040f11 100644 --- a/packages/oauth/package.js +++ b/packages/oauth/package.js @@ -8,7 +8,6 @@ Package.on_use(function (api) { api.use('webapp', 'server'); api.use(['underscore', 'service-configuration'], 'server'); - api.add_files('oauth_common.js', ['client', 'server']); api.add_files('oauth_client.js', 'client'); api.add_files('oauth_server.js', 'server'); }); diff --git a/packages/oauth1/oauth1_common.js b/packages/oauth1/oauth1_common.js deleted file mode 100644 index 8518a46fce..0000000000 --- a/packages/oauth1/oauth1_common.js +++ /dev/null @@ -1,2 +0,0 @@ -// @export Oauth1 -Oauth1 = {}; diff --git a/packages/oauth1/oauth1_server.js b/packages/oauth1/oauth1_server.js index a5c9297681..4e5d8c8994 100644 --- a/packages/oauth1/oauth1_server.js +++ b/packages/oauth1/oauth1_server.js @@ -1,5 +1,8 @@ // A place to store request tokens pending verification -Oauth1._requestTokens = {}; +var requestTokens = {}; + +// @export _Oauth1Test.requestTokens +_Oauth1Test.requestTokens = requestTokens; // connect middleware Oauth._requestHandlers['1'] = function (service, query, res) { @@ -20,7 +23,7 @@ Oauth._requestHandlers['1'] = function (service, query, res) { oauthBinding.prepareRequestToken(query.requestTokenAndRedirect); // Keep track of request token so we can verify it on the next step - Oauth1._requestTokens[query.state] = oauthBinding.requestToken; + requestTokens[query.state] = oauthBinding.requestToken; // redirect to provider login, which will redirect back to "step 2" below var redirectUrl = urls.authenticate + '?oauth_token=' + oauthBinding.requestToken; @@ -32,8 +35,8 @@ Oauth._requestHandlers['1'] = function (service, query, res) { // token and access token secret and log in as user // Get the user's request token so we can verify it and clear it - var requestToken = Oauth1._requestTokens[query.state]; - delete Oauth1._requestTokens[query.state]; + var requestToken = requestTokens[query.state]; + delete requestTokens[query.state]; // Verify user authorized access and the oauth_token matches // the requestToken from previous step diff --git a/packages/oauth1/oauth1_tests.js b/packages/oauth1/oauth1_tests.js index 51f286d5ad..d194806c8e 100644 --- a/packages/oauth1/oauth1_tests.js +++ b/packages/oauth1/oauth1_tests.js @@ -40,7 +40,7 @@ Tinytest.add("oauth1 - loginResultForCredentialToken is stored", function (test) }); // simulate logging in using twitterfoo - Oauth1._requestTokens[credentialToken] = twitterfooAccessToken; + _Oauth1Test.requestTokens[credentialToken] = twitterfooAccessToken; var req = { method: "POST", @@ -50,7 +50,7 @@ Tinytest.add("oauth1 - loginResultForCredentialToken is stored", function (test) oauth_token: twitterfooAccessToken } }; - Oauth._middleware(req, new http.ServerResponse(req)); + _OauthTest.middleware(req, new http.ServerResponse(req)); // Test that right data is placed on the loginResult map test.equal( @@ -67,7 +67,7 @@ Tinytest.add("oauth1 - loginResultForCredentialToken is stored", function (test) Oauth._loginResultForCredentialToken[credentialToken].options.option1, twitterOption1); } finally { - Oauth._unregisterService(serviceName); + _OauthTest.unregisterService(serviceName); } }); diff --git a/packages/oauth1/package.js b/packages/oauth1/package.js index 7d234e6ba7..ff4ee2563d 100644 --- a/packages/oauth1/package.js +++ b/packages/oauth1/package.js @@ -10,7 +10,6 @@ Package.on_use(function (api) { api.use('underscore', 'server'); api.add_files('oauth1_binding.js', 'server'); - api.add_files('oauth1_common.js', ['client', 'server']); api.add_files('oauth1_server.js', 'server'); }); diff --git a/packages/oauth2/oauth2_common.js b/packages/oauth2/oauth2_common.js deleted file mode 100644 index a2d3d97127..0000000000 --- a/packages/oauth2/oauth2_common.js +++ /dev/null @@ -1 +0,0 @@ -Oauth2 = {}; diff --git a/packages/oauth2/oauth2_tests.js b/packages/oauth2/oauth2_tests.js index 5bc23e3bb5..b424e0430f 100644 --- a/packages/oauth2/oauth2_tests.js +++ b/packages/oauth2/oauth2_tests.js @@ -20,7 +20,7 @@ Tinytest.add("oauth2 - loginResultForCredentialToken is stored", function (test) var req = {method: "POST", url: "/_oauth/" + serviceName + "?close", query: {state: credentialToken}}; - Oauth._middleware(req, new http.ServerResponse(req)); + _OauthTest.middleware(req, new http.ServerResponse(req)); // Test that the login result for that user is prepared test.equal( @@ -31,6 +31,6 @@ Tinytest.add("oauth2 - loginResultForCredentialToken is stored", function (test) Oauth._loginResultForCredentialToken[credentialToken].options.option1, foobookOption1); } finally { - Oauth._unregisterService(serviceName); + _OauthTest.unregisterService(serviceName); } }); diff --git a/packages/oauth2/package.js b/packages/oauth2/package.js index cdb8db2f84..c2b6f6dccf 100644 --- a/packages/oauth2/package.js +++ b/packages/oauth2/package.js @@ -7,7 +7,6 @@ Package.on_use(function (api) { api.use('service-configuration', ['client', 'server']); api.use('oauth', ['client', 'server']); - api.add_files('oauth2_common.js', ['client', 'server']); api.add_files('oauth2_server.js', 'server'); }); diff --git a/packages/twitter/package.js b/packages/twitter/package.js index 60d1908d53..2b84387234 100644 --- a/packages/twitter/package.js +++ b/packages/twitter/package.js @@ -18,7 +18,6 @@ Package.on_use(function(api) { ['twitter_configure.html', 'twitter_configure.js'], 'client'); - api.add_files('twitter_common.js', ['client', 'server']); api.add_files('twitter_server.js', 'server'); api.add_files('twitter_client.js', 'client'); }); diff --git a/packages/twitter/twitter_client.js b/packages/twitter/twitter_client.js index 7a6ea679de..86ca7205f2 100644 --- a/packages/twitter/twitter_client.js +++ b/packages/twitter/twitter_client.js @@ -1,4 +1,5 @@ // Request Twitter credentials for the user +// @export Twitter.requestCredential // @param options {optional} XXX support options.requestPermissions // @param credentialRequestCompleteCallback {Function} Callback function to call on // completion. Takes one argument, credentialToken on success, or Error on diff --git a/packages/twitter/twitter_common.js b/packages/twitter/twitter_common.js deleted file mode 100644 index 5c41a14894..0000000000 --- a/packages/twitter/twitter_common.js +++ /dev/null @@ -1,9 +0,0 @@ -// @export Twitter -Twitter = {}; - -Twitter._urls = { - requestToken: "https://api.twitter.com/oauth/request_token", - authorize: "https://api.twitter.com/oauth/authorize", - accessToken: "https://api.twitter.com/oauth/access_token", - authenticate: "https://api.twitter.com/oauth/authenticate" -}; diff --git a/packages/twitter/twitter_server.js b/packages/twitter/twitter_server.js index 8d67554229..12c5391df5 100644 --- a/packages/twitter/twitter_server.js +++ b/packages/twitter/twitter_server.js @@ -1,7 +1,16 @@ +var urls = { + requestToken: "https://api.twitter.com/oauth/request_token", + authorize: "https://api.twitter.com/oauth/authorize", + accessToken: "https://api.twitter.com/oauth/access_token", + authenticate: "https://api.twitter.com/oauth/authenticate" +}; + + // https://dev.twitter.com/docs/api/1.1/get/account/verify_credentials +// @export Twitter.whitelistedFields Twitter.whitelistedFields = ['profile_image_url', 'profile_image_url_https', 'lang']; -Oauth.registerService('twitter', 1, Twitter._urls, function(oauthBinding) { +Oauth.registerService('twitter', 1, urls, function(oauthBinding) { var identity = oauthBinding.get('https://api.twitter.com/1.1/account/verify_credentials.json').data; var serviceData = { @@ -26,6 +35,7 @@ Oauth.registerService('twitter', 1, Twitter._urls, function(oauthBinding) { }); +// @export Twitter.retrieveCredential Twitter.retrieveCredential = function(credentialToken) { return Oauth.retrieveCredential(credentialToken); }; diff --git a/packages/webapp/webapp_server.js b/packages/webapp/webapp_server.js index a3a3e7ad76..adb8fd9645 100644 --- a/packages/webapp/webapp_server.js +++ b/packages/webapp/webapp_server.js @@ -12,6 +12,11 @@ var optimist = Npm.require('optimist'); var useragent = Npm.require('useragent'); var send = Npm.require('send'); +// XXX we have to export WebApp as a single symbol because we modify +// it (for example, it has an attribute httpServer which isn't known +// until runWebAppServer-time.) It would be nice to refactor so that +// this isn't the case and we can export the symbols individually. +// // @export WebApp WebApp = {}; @@ -441,7 +446,7 @@ var runWebAppServer = function () { console.log("LISTENING"); // must match run.js var port = httpServer.address().port; if (bind.viaProxy && bind.viaProxy.proxyEndpoint) { - Meteor._bindToProxy(bind.viaProxy); + bindToProxy(bind.viaProxy); } else if (bind.viaProxy) { // bind via the proxy, but we'll have to find it ourselves via // ultraworld. @@ -453,7 +458,7 @@ var runWebAppServer = function () { var doBinding = function (proxyService) { if (proxyService.providers.proxy) { Log("Attempting to bind to proxy at " + proxyService.providers.proxy); - Meteor._bindToProxy(_.extend({ + bindToProxy(_.extend({ proxyEndpoint: proxyService.providers.proxy }, bind.viaProxy)); } @@ -478,7 +483,7 @@ var runWebAppServer = function () { }; }; -Meteor._bindToProxy = function (proxyConfig) { +bindToProxy = function (proxyConfig) { var securePort = proxyConfig.securePort || 4433; var insecurePort = proxyConfig.insecurePort || 8080; diff --git a/packages/weibo/package.js b/packages/weibo/package.js index 4857ad3086..8b3b4f01a5 100644 --- a/packages/weibo/package.js +++ b/packages/weibo/package.js @@ -17,7 +17,6 @@ Package.on_use(function(api) { ['weibo_configure.html', 'weibo_configure.js'], 'client'); - api.add_files('weibo_common.js', ['client', 'server']); api.add_files('weibo_server.js', 'server'); api.add_files('weibo_client.js', 'client'); }); diff --git a/packages/weibo/weibo_client.js b/packages/weibo/weibo_client.js index f70eb831c6..545d225f13 100644 --- a/packages/weibo/weibo_client.js +++ b/packages/weibo/weibo_client.js @@ -1,4 +1,5 @@ // Request Weibo credentials for the user +// @export Weibo.requestCredential // @param options {optional} // @param credentialRequestCompleteCallback {Function} Callback function to call on // completion. Takes one argument, credentialToken on success, or Error on diff --git a/packages/weibo/weibo_common.js b/packages/weibo/weibo_common.js deleted file mode 100644 index fab7ca0854..0000000000 --- a/packages/weibo/weibo_common.js +++ /dev/null @@ -1,2 +0,0 @@ -// @export Weibo -Weibo = {}; diff --git a/packages/weibo/weibo_server.js b/packages/weibo/weibo_server.js index 4c6437899f..709620299d 100644 --- a/packages/weibo/weibo_server.js +++ b/packages/weibo/weibo_server.js @@ -68,6 +68,7 @@ var getIdentity = function (accessToken, userId) { } }; +// @export Weibo.retrieveCredential Weibo.retrieveCredential = function(credentialToken) { return Oauth.retrieveCredential(credentialToken); }; \ No newline at end of file From 25a3afff544fa9e21d85126cbc1bd6e5a7f05f58 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Tue, 23 Jul 2013 18:08:36 -0700 Subject: [PATCH 074/175] - drop `where` from all on_use/on_test (it is no longer being passed in) - allow `api.use(package, {options})` without where - fix showdown to use a weak dependency --- packages/autopublish/package.js | 4 ++-- packages/backbone/package.js | 5 ++--- packages/deps/package.js | 8 +++---- packages/jquery/package.js | 6 +++--- packages/js-analyze/package.js | 2 +- packages/json/package.js | 7 +++--- packages/logging/package.js | 7 +++--- packages/meteor/package.js | 2 +- packages/minifiers/package.js | 2 +- packages/minimongo/package.js | 10 +++------ packages/preserve-inputs/package.js | 2 +- packages/random/package.js | 5 ++--- packages/reactive-dict/package.js | 8 +++---- packages/showdown/package.js | 19 ++++++----------- packages/showdown/template-integration.js | 10 +++++---- packages/test-helpers/package.js | 26 +++++++++++------------ packages/underscore/package.js | 12 +++++------ tools/packages.js | 5 +++++ 18 files changed, 62 insertions(+), 78 deletions(-) diff --git a/packages/autopublish/package.js b/packages/autopublish/package.js index ae8c12f22a..9e824aa799 100644 --- a/packages/autopublish/package.js +++ b/packages/autopublish/package.js @@ -2,7 +2,7 @@ Package.describe({ summary: "Automatically publish the entire database to all clients" }); -Package.on_use(function (api, where) { +Package.on_use(function (api) { api.use('livedata', 'server'); api.add_files("autopublish.js", "server"); -}); \ No newline at end of file +}); diff --git a/packages/backbone/package.js b/packages/backbone/package.js index 30ef0d6166..cbc12b735f 100644 --- a/packages/backbone/package.js +++ b/packages/backbone/package.js @@ -2,10 +2,9 @@ Package.describe({ summary: "A minimalist client-side MVC framework" }); -Package.on_use(function (api, where) { +Package.on_use(function (api) { // XXX Backbone requires either jquery or zepto api.use(["jquery", "json", "underscore"]); - where = where || ['client', 'server']; - api.add_files("backbone.js", where); + api.add_files("backbone.js"); }); diff --git a/packages/deps/package.js b/packages/deps/package.js index 2e13963124..b78f953649 100644 --- a/packages/deps/package.js +++ b/packages/deps/package.js @@ -5,11 +5,9 @@ Package.describe({ internal: true }); -Package.on_use(function (api, where) { - where = where || ['client', 'server']; - - api.use('underscore', where); - api.add_files(['deps.js'], where); +Package.on_use(function (api) { + api.use('underscore'); + api.add_files('deps.js'); }); Package.on_test(function (api) { diff --git a/packages/jquery/package.js b/packages/jquery/package.js index 14b8d7033b..9e408a7dce 100644 --- a/packages/jquery/package.js +++ b/packages/jquery/package.js @@ -2,9 +2,9 @@ Package.describe({ summary: "Manipulate the DOM using CSS selectors" }); -Package.on_use(function (api, where) { +Package.on_use(function (api) { api.add_files('jquery.js', 'client'); - api.exportSymbol('$', where); - api.exportSymbol('jQuery', where); + api.exportSymbol('$'); + api.exportSymbol('jQuery'); }); diff --git a/packages/js-analyze/package.js b/packages/js-analyze/package.js index d417eb58fc..9fb6db1762 100644 --- a/packages/js-analyze/package.js +++ b/packages/js-analyze/package.js @@ -23,6 +23,6 @@ Npm.depends({ // would be impossible to load at link time (or all transitive dependencies // packages would need to function without the analysis provided by this // package). -Package.on_use(function (api, where) { +Package.on_use(function (api) { api.add_files('js_analyze.js', 'server'); }); diff --git a/packages/json/package.js b/packages/json/package.js index a390732c87..87602a5998 100644 --- a/packages/json/package.js +++ b/packages/json/package.js @@ -6,8 +6,7 @@ Package.describe({ // 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?) -Package.on_use(function (api, where) { - where = where || ['client', 'server']; - - api.add_files('json2.js', where); +Package.on_use(function (api) { + // Node always has JSON; we only need this in some browsers. + api.add_files('json2.js', 'client'); }); diff --git a/packages/logging/package.js b/packages/logging/package.js index 3ebb486dd3..246a13451a 100644 --- a/packages/logging/package.js +++ b/packages/logging/package.js @@ -7,10 +7,9 @@ Npm.depends({ "cli-color": "0.2.2" }); -Package.on_use(function (api, where) { - where = where || ['client', 'server']; - api.use(['underscore', 'ejson'], where); - api.add_files('logging.js', where); +Package.on_use(function (api) { + api.use(['underscore', 'ejson']); + api.add_files('logging.js'); }); Package.on_test(function (api) { diff --git a/packages/meteor/package.js b/packages/meteor/package.js index 1ce4b05b12..cada378e34 100644 --- a/packages/meteor/package.js +++ b/packages/meteor/package.js @@ -10,7 +10,7 @@ Package._transitional_registerBuildPlugin({ sources: ['plugin/basic-file-types.js'] }); -Package.on_use(function (api, where) { +Package.on_use(function (api) { api.use('underscore', ['client', 'server']); api.add_files('client_environment.js', 'client'); diff --git a/packages/minifiers/package.js b/packages/minifiers/package.js index 3d2d73fdc6..a4ddc279b2 100644 --- a/packages/minifiers/package.js +++ b/packages/minifiers/package.js @@ -9,6 +9,6 @@ Npm.depends({ "uglify-js": "https://github.com/mishoo/UglifyJS2/tarball/b1febde3e9be32b9d88918ed733efc3796e3f143" }); -Package.on_use(function (api, where) { +Package.on_use(function (api) { api.add_files('minifiers.js', 'server'); }); diff --git a/packages/minimongo/package.js b/packages/minimongo/package.js index afa3ba3f69..32215bcd73 100644 --- a/packages/minimongo/package.js +++ b/packages/minimongo/package.js @@ -3,20 +3,16 @@ Package.describe({ internal: true }); -Package.on_use(function (api, where) { - where = where || ['client', 'server']; - - // It would be sort of nice if minimongo didn't depend on - // underscore, so we could ship it separately. +Package.on_use(function (api) { api.use(['underscore', 'json', 'ejson', 'ordered-dict', 'deps', - 'random', 'ordered-dict'], where); + 'random', 'ordered-dict']); api.add_files([ 'minimongo.js', 'selector.js', 'modify.js', 'diff.js', 'objectid.js' - ], where); + ]); }); Package.on_test(function (api) { diff --git a/packages/preserve-inputs/package.js b/packages/preserve-inputs/package.js index f8eca26f9b..f979a2d5d9 100644 --- a/packages/preserve-inputs/package.js +++ b/packages/preserve-inputs/package.js @@ -2,7 +2,7 @@ Package.describe({ summary: "Automatically preserve all form fields with a unique id" }); -Package.on_use(function (api, where) { +Package.on_use(function (api) { api.use(['underscore', 'spark']); api.add_files("preserve-inputs.js", "client"); }); diff --git a/packages/random/package.js b/packages/random/package.js index 0480c09ac6..848a8d8f41 100644 --- a/packages/random/package.js +++ b/packages/random/package.js @@ -3,10 +3,9 @@ Package.describe({ internal: true }); -Package.on_use(function (api, where) { - where = where || ['client', 'server']; +Package.on_use(function (api) { api.use('underscore'); - api.add_files('random.js', where); + api.add_files('random.js'); }); Package.on_test(function(api) { diff --git a/packages/reactive-dict/package.js b/packages/reactive-dict/package.js index d78973dbfd..c403a859b7 100644 --- a/packages/reactive-dict/package.js +++ b/packages/reactive-dict/package.js @@ -3,11 +3,9 @@ Package.describe({ internal: true }); -Package.on_use(function (api, where) { - where = where || ['client', 'server']; - - api.use(['underscore', 'deps', 'ejson'], where); - api.add_files('reactive-dict.js', where); +Package.on_use(function (api) { + api.use(['underscore', 'deps', 'ejson']); + api.add_files('reactive-dict.js'); }); Package.on_test(function (api) { diff --git a/packages/showdown/package.js b/packages/showdown/package.js index cfc75b3904..22f1cc5fe0 100644 --- a/packages/showdown/package.js +++ b/packages/showdown/package.js @@ -8,18 +8,11 @@ Package.describe({ var _ = Npm.require('underscore'); -Package.on_use(function (api, where) { - where = where || ["client", "server"]; - where = where instanceof Array ? where : [where]; +Package.on_use(function (api) { + api.add_files("showdown.js"); + api.exportSymbol('Showdown'); - api.add_files("showdown.js", where); - api.exportSymbol('Showdown', where); - - // XXX what we really want to do is, load template-integration after - // handlebars, iff handlebars was included in the project. - if (where === "client" || - (where instanceof Array && _.indexOf(where, "client") !== -1)) { - api.use("handlebars", "client"); - api.add_files("template-integration.js", "client"); - } + // Define {{markdown}} if handlebars got included. + api.use("handlebars", "client", {weak: true}); + api.add_files("template-integration.js", "client"); }); diff --git a/packages/showdown/template-integration.js b/packages/showdown/template-integration.js index 77808f6c83..f33acb084d 100644 --- a/packages/showdown/template-integration.js +++ b/packages/showdown/template-integration.js @@ -1,4 +1,6 @@ -Handlebars.registerHelper('markdown', function (options) { - var converter = new Showdown.converter(); - return converter.makeHtml(options.fn(this)); -}); +if (Package.handlebars) { + Package.handlebars.Handlebars.registerHelper('markdown', function (options) { + var converter = new Showdown.converter(); + return converter.makeHtml(options.fn(this)); + }); +} diff --git a/packages/test-helpers/package.js b/packages/test-helpers/package.js index a58cbe988c..523912c88d 100644 --- a/packages/test-helpers/package.js +++ b/packages/test-helpers/package.js @@ -3,24 +3,22 @@ Package.describe({ internal: true }); -Package.on_use(function (api, where) { - where = where || ["client", "server"]; - +Package.on_use(function (api) { api.use(['underscore', 'deps', 'ejson', 'tinytest', 'random', 'domutils']); api.use(['spark'], 'client'); - api.add_files('try_all_permutations.js', where); - api.add_files('async_multi.js', where); - api.add_files('event_simulation.js', where); - api.add_files('seeded_random.js', where); - api.add_files('canonicalize_html.js', where); - api.add_files('stub_stream.js', where); - api.add_files('onscreendiv.js', where); - api.add_files('wrappedfrag.js', where); - api.add_files('current_style.js', where); - api.add_files('reactivevar.js', where); - api.add_files('callback_logger.js', where); + api.add_files('try_all_permutations.js'); + api.add_files('async_multi.js'); + api.add_files('event_simulation.js'); + api.add_files('seeded_random.js'); + api.add_files('canonicalize_html.js'); + api.add_files('stub_stream.js'); + api.add_files('onscreendiv.js'); + api.add_files('wrappedfrag.js'); + api.add_files('current_style.js'); + api.add_files('reactivevar.js'); + api.add_files('callback_logger.js'); }); Package.on_test(function (api) { diff --git a/packages/underscore/package.js b/packages/underscore/package.js index dc6aa9ef6b..08f5a6284a 100644 --- a/packages/underscore/package.js +++ b/packages/underscore/package.js @@ -2,10 +2,8 @@ Package.describe({ summary: "Collection of small helper functions: _.map, _.each, ..." }); -Package.on_use(function (api, where) { - where = where || ['client', 'server']; - - // Like all package, we have an implicit depedency on the 'meteor' +Package.on_use(function (api) { + // Like all packages, we have an implicit depedency on the 'meteor' // package, which provides such things as the *.js file handler. Use // an undocumented API to allow 'meteor' to after us even though we // depend on it. This is necessary since 'meteor' depends on us. One @@ -18,9 +16,9 @@ Package.on_use(function (api, where) { // remove unordered dependency support, though I think it's worth keeping // around for now to keep the possibility of dependency // configuration alive in the codebase. - api.use('meteor', where, {unordered: true}); + api.use('meteor', {unordered: true}); - api.exportSymbol('_', where); + api.exportSymbol('_'); - api.add_files('underscore.js', where); + api.add_files('underscore.js'); }); diff --git a/tools/packages.js b/tools/packages.js index 406af5f944..012760b52f 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -1467,6 +1467,11 @@ _.extend(Package.prototype, { // flag is not tracked per-environment or per-role; this may // change.) use: function (names, where, options) { + // Support `api.use(package, {weak: true})` without where. + if (where.constructor === Object && !options) { + options = where; + where = null; + } options = options || {}; if (!(names instanceof Array)) From 9f38258b54926e820be787798fcf1755bc84e947 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Tue, 23 Jul 2013 19:21:29 -0700 Subject: [PATCH 075/175] Drop all @export lines. Add api.exportSymbol instead. --- packages/accounts-base/accounts_client.js | 7 ---- packages/accounts-base/accounts_common.js | 8 +--- packages/accounts-base/accounts_server.js | 8 ---- packages/accounts-base/localstorage_token.js | 5 --- packages/accounts-base/package.js | 2 + packages/accounts-facebook/facebook_client.js | 3 +- packages/accounts-github/github_client.js | 3 +- packages/accounts-google/google_client.js | 3 +- packages/accounts-meetup/meetup_client.js | 3 +- packages/accounts-oauth/oauth_client.js | 2 - packages/accounts-oauth/oauth_server.js | 2 - packages/accounts-password/email_templates.js | 1 - packages/accounts-password/password_client.js | 6 --- packages/accounts-password/password_server.js | 5 --- packages/accounts-twitter/twitter_client.js | 3 +- packages/accounts-ui-unstyled/accounts_ui.js | 3 -- .../login_buttons_session.js | 1 - packages/accounts-urls/url_server.js | 3 -- packages/accounts-weibo/weibo_client.js | 3 +- packages/check/match.js | 9 ----- packages/check/package.js | 2 + packages/ctl-helper/ctl-helper.js | 1 - packages/ctl-helper/package.js | 1 + packages/ctl/ctl.js | 1 - packages/ctl/package.js | 2 +- packages/deps/deps.js | 4 -- packages/deps/package.js | 1 + packages/dev-bundle-fetcher/dev-bundle.js | 1 - packages/dev-bundle-fetcher/package.js | 1 + packages/domutils/domutils.js | 1 - packages/domutils/package.js | 1 + packages/ejson/base64.js | 3 -- packages/ejson/ejson.js | 14 ++----- packages/ejson/package.js | 2 + packages/email/email.js | 7 ++-- packages/email/package.js | 2 + packages/facebook/facebook_client.js | 3 +- packages/facebook/facebook_server.js | 3 +- packages/facebook/package.js | 2 + packages/github/github_client.js | 3 +- packages/github/github_server.js | 3 +- packages/github/package.js | 2 + packages/google/google_client.js | 3 +- packages/google/google_server.js | 6 +-- packages/google/package.js | 2 + packages/handlebars/evaluate-handlebars.js | 1 - packages/handlebars/package.js | 2 + packages/handlebars/parse-handlebars.js | 1 - packages/htmljs/html.js | 7 ---- packages/htmljs/package.js | 10 +++++ packages/http/httpcall_client.js | 1 - packages/http/httpcall_common.js | 4 -- packages/http/httpcall_server.js | 1 - packages/js-analyze/js_analyze.js | 1 - packages/js-analyze/package.js | 1 + packages/jsparse/lexer.js | 1 - packages/jsparse/package.js | 1 + packages/jsparse/parser.js | 1 - packages/jsparse/parserlib.js | 1 - packages/livedata/client_convenience.js | 10 ----- packages/livedata/crossbar.js | 1 - packages/livedata/livedata_common.js | 6 +-- packages/livedata/livedata_connection.js | 3 -- packages/livedata/livedata_server.js | 5 +-- packages/livedata/package.js | 6 +++ packages/livedata/server_convenience.js | 6 --- packages/livedata/stream_client_common.js | 1 - packages/livedata/writefence.js | 2 - packages/liverange/liverange.js | 2 - packages/liverange/package.js | 1 + packages/localstorage/localstorage.js | 1 - packages/logging/logging.js | 1 - packages/logging/package.js | 1 + packages/meetup/meetup_client.js | 2 +- packages/meetup/meetup_server.js | 5 ++- packages/meetup/package.js | 2 + packages/meteor/client_environment.js | 12 +++--- packages/meteor/debug.js | 2 - packages/meteor/dynamics_browser.js | 2 - packages/meteor/dynamics_nodejs.js | 2 - packages/meteor/errors.js | 2 - packages/meteor/fiber_helpers.js | 3 -- packages/meteor/fiber_stubs_client.js | 2 - packages/meteor/helpers.js | 5 --- packages/meteor/package.js | 2 + packages/meteor/server_environment.js | 10 ++--- packages/meteor/setimmediate.js | 1 - packages/meteor/startup_client.js | 1 - packages/meteor/startup_server.js | 1 - packages/meteor/timers.js | 5 --- packages/meteor/url_common.js | 2 - packages/minifiers/minifiers.js | 3 -- packages/minifiers/package.js | 1 + packages/minimongo/minimongo.js | 1 - packages/minimongo/package.js | 1 + packages/mongo-livedata/collection.js | 1 - packages/mongo-livedata/mongo_driver.js | 3 +- packages/mongo-livedata/package.js | 2 + packages/oauth/oauth_client.js | 3 +- packages/oauth/oauth_server.js | 11 ++---- packages/oauth/package.js | 3 ++ packages/oauth1/oauth1_binding.js | 1 - packages/oauth1/oauth1_server.js | 3 +- packages/oauth1/package.js | 3 ++ packages/ordered-dict/ordered_dict.js | 1 - packages/ordered-dict/package.js | 1 + packages/past/past.js | 7 ---- packages/random/package.js | 1 + packages/random/random.js | 1 - packages/reactive-dict/package.js | 1 + packages/reactive-dict/reactive-dict.js | 4 +- packages/reload/package.js | 1 + packages/reload/reload.js | 5 +-- packages/routepolicy/package.js | 2 + packages/routepolicy/routepolicy.js | 11 +++--- packages/routepolicy/routepolicy_tests.js | 6 +-- packages/service-configuration/package.js | 1 + .../service_configuration_common.js | 1 - packages/session/package.js | 1 + packages/session/session.js | 1 - packages/spark/convenience.js | 2 - packages/spark/package.js | 3 ++ packages/spark/patch.js | 1 - packages/spark/spark.js | 19 +-------- packages/spark/utils.js | 1 - packages/srp/package.js | 1 + packages/srp/srp.js | 6 +-- packages/star-translate/package.js | 1 + packages/star-translate/translator.js | 1 - packages/templating/deftemplate.js | 1 - packages/templating/package.js | 2 + packages/test-helpers/async_multi.js | 2 - packages/test-helpers/callback_logger.js | 1 - packages/test-helpers/canonicalize_html.js | 1 - packages/test-helpers/current_style.js | 1 - packages/test-helpers/event_simulation.js | 4 -- packages/test-helpers/onscreendiv.js | 1 - packages/test-helpers/package.js | 6 +++ packages/test-helpers/reactivevar.js | 1 - packages/test-helpers/seeded_random.js | 1 - packages/test-helpers/stub_stream.js | 1 - packages/test-helpers/try_all_permutations.js | 1 - packages/test-helpers/wrappedfrag.js | 1 - packages/test-in-console/driver.js | 1 - packages/test-in-console/package.js | 10 ++--- packages/tinytest/package.js | 2 + packages/tinytest/tinytest.js | 6 +-- packages/tinytest/tinytest_client.js | 1 - packages/twitter/package.js | 2 + packages/twitter/twitter_client.js | 3 +- packages/twitter/twitter_server.js | 4 +- packages/universal-events/listener.js | 2 - packages/universal-events/package.js | 2 +- packages/webapp/package.js | 3 +- packages/webapp/webapp_server.js | 3 -- packages/weibo/package.js | 2 + packages/weibo/weibo_client.js | 3 +- packages/weibo/weibo_server.js | 5 ++- tools/linker.js | 1 - tools/packages.js | 39 +++++++++---------- .../packages/test-package/package.js | 1 + .../packages/test-package/test-package.js | 1 - 162 files changed, 189 insertions(+), 333 deletions(-) diff --git a/packages/accounts-base/accounts_client.js b/packages/accounts-base/accounts_client.js index 57394372ed..3dd0c5cd35 100644 --- a/packages/accounts-base/accounts_client.js +++ b/packages/accounts-base/accounts_client.js @@ -3,7 +3,6 @@ /// // This is reactive. -// @export Meteor.userId Meteor.userId = function () { return Meteor.connection.userId(); }; @@ -13,21 +12,18 @@ var loggingInDeps = new Deps.Dependency; // This is mostly just called within this file, but Meteor.loginWithPassword // also uses it to make loggingIn() be true during the beginPasswordExchange // method call too. -// @export Accounts._setLoggingIn Accounts._setLoggingIn = function (x) { if (loggingIn !== x) { loggingIn = x; loggingInDeps.changed(); } }; -// @export Meteor.loggingIn Meteor.loggingIn = function () { loggingInDeps.depend(); return loggingIn; }; // This calls userId, which is reactive. -// @export Meteor.user Meteor.user = function () { var userId = Meteor.userId(); if (!userId) @@ -62,7 +58,6 @@ Meteor.user = function () { // - userCallback: Will be called with no arguments once the user is fully // logged in, or with the error on error. // -// @export Accounts.callLoginMethod Accounts.callLoginMethod = function (options) { options = _.extend({ methodName: 'login', @@ -168,7 +163,6 @@ makeClientLoggedIn = function(userId, token) { Meteor.connection.setUserId(userId); }; -// @export Meteor.logout Meteor.logout = function (callback) { Meteor.apply('logout', [], {wait: true}, function(error, result) { if (error) { @@ -190,7 +184,6 @@ var loginServicesHandle = Meteor.subscribe("meteor.loginServiceConfiguration"); // subscription is ready. Used by accounts-ui to hide the login button // until we have all the configuration loaded // -// @export Accounts.loginServicesConfigured Accounts.loginServicesConfigured = function () { return loginServicesHandle.ready(); }; diff --git a/packages/accounts-base/accounts_common.js b/packages/accounts-base/accounts_common.js index 9a7a0666da..0db0271db9 100644 --- a/packages/accounts-base/accounts_common.js +++ b/packages/accounts-base/accounts_common.js @@ -1,6 +1,7 @@ +Accounts = {}; + // Currently this is read directly by packages like accounts-password // and accounts-ui-unstyled. -// @export Accounts._options Accounts._options = {}; // Set up config for the accounts system. Call this on both the client @@ -19,7 +20,6 @@ Accounts._options = {}; // - forbidClientAccountCreation {Boolean} // Do not allow clients to create accounts directly. // -// @export Accounts.config Accounts.config = function(options) { // validate option keys var VALID_KEYS = ["sendVerificationEmail", "forbidClientAccountCreation"]; @@ -45,20 +45,16 @@ Accounts.config = function(options) { // some fields. Code to autopublish this is in accounts_server.js. // XXX Allow users to configure this collection name. // -// @export Meteor.users Meteor.users = new Meteor.Collection("users", {_preventAutopublish: true}); // There is an allow call in accounts_server that restricts this // collection. // loginServiceConfiguration and ConfigError are maintained for backwards compatibility -// @export Accounts.loginServiceConfiguration -// @export Accounts.ConfigError Accounts.loginServiceConfiguration = ServiceConfiguration.configurations; Accounts.ConfigError = ServiceConfiguration.ConfigError; // Thrown when the user cancels the login process (eg, closes an oauth // popup, declines retina scan, etc) -// @export Accounts.LoginCancelledError Accounts.LoginCancelledError = function(description) { this.message = description; }; diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index 45d91b056c..9c26b38daf 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -2,7 +2,6 @@ /// CURRENT USER /// -// @export Meteor.userId Meteor.userId = function () { // This function only works if called inside a method. In theory, it // could also be called from publish statements, since they also @@ -19,7 +18,6 @@ Meteor.userId = function () { return currentInvocation.userId; }; -// @export Meteor.user Meteor.user = function () { var userId = Meteor.userId(); if (!userId) @@ -39,7 +37,6 @@ Meteor.user = function () { // - {id: userId, token: *}, if the user logged in successfully. // - throw an error, if the user failed to log in. // -// @export Accounts.registerLoginHandler Accounts.registerLoginHandler = function(handler) { loginHandlers.push(handler); }; @@ -114,7 +111,6 @@ Accounts.registerLoginHandler(function(options) { // Semi-public. Used by other login methods to generate tokens. // -// @export Accounts._generateStampedLoginToken Accounts._generateStampedLoginToken = function () { return {token: Random.id(), when: +(new Date)}; }; @@ -132,7 +128,6 @@ removeLoginToken = function (userId, loginToken) { /// CREATE USER HOOKS /// -// @export Accounts.onCreateUser var onCreateUserHook = null; Accounts.onCreateUser = function (func) { if (onCreateUserHook) @@ -150,7 +145,6 @@ var defaultCreateUserHook = function (options, user) { }; // Called by accounts-password -// @export Accounts.insertUserDoc Accounts.insertUserDoc = function (options, user) { // - clone user document, to protect from modification // - add createdAt timestamp @@ -214,7 +208,6 @@ Accounts.insertUserDoc = function (options, user) { return result; }; -// @export Accounts.validateNewUser var validateNewUserHooks = []; Accounts.validateNewUser = function (func) { validateNewUserHooks.push(func); @@ -236,7 +229,6 @@ Accounts.validateNewUser = function (func) { // @returns {Object} Object with token and id keys, like the result // of the "login" method. // -// @export Accounts.updateOrCreateUserFromExternalService Accounts.updateOrCreateUserFromExternalService = function( serviceName, serviceData, options) { options = _.clone(options || {}); diff --git a/packages/accounts-base/localstorage_token.js b/packages/accounts-base/localstorage_token.js index 5afcf24133..6a9a6da010 100644 --- a/packages/accounts-base/localstorage_token.js +++ b/packages/accounts-base/localstorage_token.js @@ -5,7 +5,6 @@ // Login with a Meteor access token. This is the only public function // here. -// @export Meteor.loginWithToken Meteor.loginWithToken = function (token, callback) { Accounts.callLoginMethod({ methodArguments: [{resume: token}], @@ -15,14 +14,12 @@ Meteor.loginWithToken = function (token, callback) { var autoLoginEnabled = true; // Semi-internal API. Call at startup to prevent automatic login. -// @export Accounts._disableAutoLogin Acocunts._disableAutoLogin = function () { autoLoginEnabled = false; }; // Semi-internal API. Call this function to re-enable auto login after // if it was disabled at startup. -// @export Accounts._enableAutoLogin Accounts._enableAutoLogin = function () { autoLoginEnabled = true; pollStoredLoginToken(); @@ -40,7 +37,6 @@ var userIdKey = "Meteor.userId"; // Call this from the top level of the test file for any test that does // logging in and out, to protect multiple tabs running the same tests // simultaneously from interfering with each others' localStorage. -// @export Accounts._isolateLoginTokenForTest Accounts._isolateLoginTokenForTest = function () { loginTokenKey = loginTokenKey + Random.id(); userIdKey = userIdKey + Random.id(); @@ -67,7 +63,6 @@ unstoreLoginToken = function() { // This is private, but it is exported for now because it is used by a // test in accounts-password. // -// @export Accounts._storedLoginToken storedLoginToken = Accounts._storedLoginToken = function() { return Meteor._localStorage.getItem(loginTokenKey); }; diff --git a/packages/accounts-base/package.js b/packages/accounts-base/package.js index df58686558..1eb3f60060 100644 --- a/packages/accounts-base/package.js +++ b/packages/accounts-base/package.js @@ -22,6 +22,8 @@ Package.on_use(function (api) { // {{currentUser}}. If not, no biggie. api.use('handlebars', 'client', {weak: true}); + api.exportSymbol('Accounts'); + api.add_files('accounts_common.js', ['client', 'server']); api.add_files('accounts_server.js', 'server'); diff --git a/packages/accounts-facebook/facebook_client.js b/packages/accounts-facebook/facebook_client.js index 94cf694d3c..e05027d7ff 100644 --- a/packages/accounts-facebook/facebook_client.js +++ b/packages/accounts-facebook/facebook_client.js @@ -1,4 +1,3 @@ -// @export Meteor.loginWithFacebook Meteor.loginWithFacebook = function(options, callback) { // support a callback without options if (! callback && typeof options === "function") { @@ -8,4 +7,4 @@ Meteor.loginWithFacebook = function(options, callback) { var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback); Facebook.requestCredential(options, credentialRequestCompleteCallback); -}; \ No newline at end of file +}; diff --git a/packages/accounts-github/github_client.js b/packages/accounts-github/github_client.js index 5d7e25167e..75edcca47d 100644 --- a/packages/accounts-github/github_client.js +++ b/packages/accounts-github/github_client.js @@ -1,4 +1,3 @@ -// @export Meteor.loginWithGithub Meteor.loginWithGithub = function(options, callback) { // support a callback without options if (! callback && typeof options === "function") { @@ -8,4 +7,4 @@ Meteor.loginWithGithub = function(options, callback) { var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback); Github.requestCredential(options, credentialRequestCompleteCallback); -}; \ No newline at end of file +}; diff --git a/packages/accounts-google/google_client.js b/packages/accounts-google/google_client.js index f18f3827ce..b8109ee833 100644 --- a/packages/accounts-google/google_client.js +++ b/packages/accounts-google/google_client.js @@ -1,4 +1,3 @@ -// @export Meteor.loginWithGoogle Meteor.loginWithGoogle = function(options, callback) { // support a callback without options if (! callback && typeof options === "function") { @@ -8,4 +7,4 @@ Meteor.loginWithGoogle = function(options, callback) { var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback); Google.requestCredential(options, credentialRequestCompleteCallback); -}; \ No newline at end of file +}; diff --git a/packages/accounts-meetup/meetup_client.js b/packages/accounts-meetup/meetup_client.js index 2d1fa0fab6..848faae2da 100644 --- a/packages/accounts-meetup/meetup_client.js +++ b/packages/accounts-meetup/meetup_client.js @@ -1,4 +1,3 @@ -// @export Meteor.loginWithMeetup Meteor.loginWithMeetup = function(options, callback) { // support a callback without options if (! callback && typeof options === "function") { @@ -8,4 +7,4 @@ Meteor.loginWithMeetup = function(options, callback) { var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback); Meetup.requestCredential(options, credentialRequestCompleteCallback); -}; \ No newline at end of file +}; diff --git a/packages/accounts-oauth/oauth_client.js b/packages/accounts-oauth/oauth_client.js index 7563044b38..f3d161e8c0 100644 --- a/packages/accounts-oauth/oauth_client.js +++ b/packages/accounts-oauth/oauth_client.js @@ -1,7 +1,6 @@ // Send an OAuth login method to the server. If the user authorized // access in the popup this should log the user in, otherwise // nothing should happen. -// @export Accounts.oauth.tryLoginAfterPopupClosed Accounts.oauth.tryLoginAfterPopupClosed = function(credentialToken, callback) { Accounts.callLoginMethod({ methodArguments: [{oauth: {credentialToken: credentialToken}}], @@ -17,7 +16,6 @@ Accounts.oauth.tryLoginAfterPopupClosed = function(credentialToken, callback) { }}); }; -// @export Accounts.oauth.credentialRequestCompleteHandler Accounts.oauth.credentialRequestCompleteHandler = function(callback) { return function (credentialTokenOrError) { if(credentialTokenOrError && credentialTokenOrError instanceof Error) { diff --git a/packages/accounts-oauth/oauth_server.js b/packages/accounts-oauth/oauth_server.js index 4f54942e02..775811289e 100644 --- a/packages/accounts-oauth/oauth_server.js +++ b/packages/accounts-oauth/oauth_server.js @@ -1,6 +1,5 @@ // Helper for registering OAuth based accounts packages. // Adds an index to the user collection. -// @export Accounts.oauth.registerService Accounts.oauth.registerService = function (name) { // Accounts.updateOrCreateUserFromExternalService does a lookup by this id, // so this should be a unique index. You might want to add indexes for other @@ -13,7 +12,6 @@ Accounts.oauth.registerService = function (name) { // For test cleanup only. (Mongo has a limit as to how many indexes it can have // per collection.) -// @export Accounts.oauth._unregisterService Accounts.oauth._unregisterService = function (name) { var index = {}; index['services.' + name + '.id'] = 1; diff --git a/packages/accounts-password/email_templates.js b/packages/accounts-password/email_templates.js index c52e7de33e..fbcca8419a 100644 --- a/packages/accounts-password/email_templates.js +++ b/packages/accounts-password/email_templates.js @@ -1,4 +1,3 @@ -// @export Accounts.emailTemplates Accounts.emailTemplates = { from: "Meteor Accounts ", siteName: Meteor.absoluteUrl().replace(/^https?:\/\//, '').replace(/\/$/, ''), diff --git a/packages/accounts-password/password_client.js b/packages/accounts-password/password_client.js index 2097d41e93..77731e9ec1 100644 --- a/packages/accounts-password/password_client.js +++ b/packages/accounts-password/password_client.js @@ -1,6 +1,5 @@ // Attempt to log in with a password. // -// @export Meteor.loginWithPassword // @param selector {String|Object} One of the following: // - {username: (username)} // - {email: (email)} @@ -46,7 +45,6 @@ Meteor.loginWithPassword = function (selector, password, callback) { // Attempt to log in as a new user. -// @export Accounts.createUser Accounts.createUser = function (options, callback) { options = _.clone(options); // we'll be modifying options @@ -68,7 +66,6 @@ Accounts.createUser = function (options, callback) { // Change password. Must be logged in. // -// @export Accounts.changePassword // @param oldPassword {String|null} By default servers no longer allow // changing password without the old password, but they could so we // support passing no password to the server and letting it decide. @@ -124,7 +121,6 @@ Accounts.changePassword = function (oldPassword, newPassword, callback) { // Sends an email to a user with a link that can be used to reset // their password // -// @export Accounts.forgotPassword // @param options {Object} // - email: (email) // @param callback (optional) {Function(error|undefined)} @@ -137,7 +133,6 @@ Accounts.forgotPassword = function(options, callback) { // Resets a password based on a token originally created by // Accounts.forgotPassword, and then logs in the matching user. // -// @export Accounts.resetPassword // @param token {String} // @param newPassword {String} // @param callback (optional) {Function(error|undefined)} @@ -157,7 +152,6 @@ Accounts.resetPassword = function(token, newPassword, callback) { // Verifies a user's email address based on a token originally // created by Accounts.sendVerificationEmail // -// @export Accounts.verifyEmail // @param token {String} // @param callback (optional) {Function(error|undefined)} Accounts.verifyEmail = function(token, callback) { diff --git a/packages/accounts-password/password_server.js b/packages/accounts-password/password_server.js index e153b36c0f..9fe9e5ff93 100644 --- a/packages/accounts-password/password_server.js +++ b/packages/accounts-password/password_server.js @@ -188,7 +188,6 @@ Meteor.methods({changePassword: function (options) { // Force change the users password. -// @export Accounts.setPassword Accounts.setPassword = function (userId, newPassword) { var user = Meteor.users.findOne(userId); if (!user) @@ -219,7 +218,6 @@ Meteor.methods({forgotPassword: function (options) { // send the user an email with a link that when opened allows the user // to set a new password, without the old password. // -// @export Accounts.sendResetPasswordEmail Accounts.sendResetPasswordEmail = function (userId, email) { // Make sure the user exists, and email is one of their addresses. var user = Meteor.users.findOne(userId); @@ -258,7 +256,6 @@ Accounts.sendResetPasswordEmail = function (userId, email) { // This is not called automatically. It must be called manually if you // want to use enrollment emails. // -// @export Accounts.sendResetPasswordEmail Accounts.sendEnrollmentEmail = function (userId, email) { // XXX refactor! This is basically identical to sendResetPasswordEmail. @@ -335,7 +332,6 @@ Meteor.methods({resetPassword: function (token, newVerifier) { // send the user an email with a link that when opened marks that // address as verified // -// @export Accounts.sendVerificationEmail Accounts.sendVerificationEmail = function (userId, address) { // XXX Also generate a link using which someone can delete this // account if they own said address but weren't those who created @@ -501,7 +497,6 @@ Meteor.methods({createUser: function (options) { // true", which we want to prevent the client from setting, but which a custom // method calling Accounts.createUser could set? // -// @export Accounts.createUser Accounts.createUser = function (options, callback) { options = _.clone(options); options.generateLoginToken = false; diff --git a/packages/accounts-twitter/twitter_client.js b/packages/accounts-twitter/twitter_client.js index 48efc39a02..c7682333e7 100644 --- a/packages/accounts-twitter/twitter_client.js +++ b/packages/accounts-twitter/twitter_client.js @@ -1,4 +1,3 @@ -// @export Meteor.loginWithTwitter Meteor.loginWithTwitter = function(options, callback) { // support a callback without options if (! callback && typeof options === "function") { @@ -8,4 +7,4 @@ Meteor.loginWithTwitter = function(options, callback) { var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback); Twitter.requestCredential(options, credentialRequestCompleteCallback); -}; \ No newline at end of file +}; diff --git a/packages/accounts-ui-unstyled/accounts_ui.js b/packages/accounts-ui-unstyled/accounts_ui.js index ad143e9327..d2d518acc6 100644 --- a/packages/accounts-ui-unstyled/accounts_ui.js +++ b/packages/accounts-ui-unstyled/accounts_ui.js @@ -1,11 +1,8 @@ -// XXX this shouldn't need to be exported -// @export Accoutns.ui._options Accounts.ui._options = { requestPermissions: {}, requestOfflineToken: {} }; -// @export Accounts.ui.config Accounts.ui.config = function(options) { // validate options keys var VALID_KEYS = ['passwordSignupFields', 'requestPermissions', 'requestOfflineToken']; diff --git a/packages/accounts-ui-unstyled/login_buttons_session.js b/packages/accounts-ui-unstyled/login_buttons_session.js index 964bba29c2..78324d0e07 100644 --- a/packages/accounts-ui-unstyled/login_buttons_session.js +++ b/packages/accounts-ui-unstyled/login_buttons_session.js @@ -31,7 +31,6 @@ var KEY_PREFIX = "Meteor.loginButtons."; // (there was even a comment to that effect here from before we had // namespacing) but accounts-ui-viewer uses it, so leave it as is for // now -// @export Accounts._loginButtonsSession Accounts._loginButtonsSession = { set: function(key, value) { validateKey(key); diff --git a/packages/accounts-urls/url_server.js b/packages/accounts-urls/url_server.js index ccc63d0fe2..4362c4a208 100644 --- a/packages/accounts-urls/url_server.js +++ b/packages/accounts-urls/url_server.js @@ -1,16 +1,13 @@ // XXX These should probably not actually be public? -// @export Accounts.urls.resetPassword Accounts.urls.resetPassword = function (token) { return Meteor.absoluteUrl('#/reset-password/' + token); }; -// @export Accounts.urls.verifyEmail Accounts.urls.verifyEmail = function (token) { return Meteor.absoluteUrl('#/verify-email/' + token); }; -// @export Accounts.urls.enrollAccount Accounts.urls.enrollAccount = function (token) { return Meteor.absoluteUrl('#/enroll-account/' + token); }; diff --git a/packages/accounts-weibo/weibo_client.js b/packages/accounts-weibo/weibo_client.js index 54196c5c0f..f7df20c9b1 100644 --- a/packages/accounts-weibo/weibo_client.js +++ b/packages/accounts-weibo/weibo_client.js @@ -1,4 +1,3 @@ -// @export Meteor.loginWithWeibo Meteor.loginWithWeibo = function(options, callback) { // support a callback without options if (! callback && typeof options === "function") { @@ -8,4 +7,4 @@ Meteor.loginWithWeibo = function(options, callback) { var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback); Weibo.requestCredential(options, credentialRequestCompleteCallback); -}; \ No newline at end of file +}; diff --git a/packages/check/match.js b/packages/check/match.js index 453c731eaf..1e6461fa7d 100644 --- a/packages/check/match.js +++ b/packages/check/match.js @@ -6,7 +6,6 @@ var currentArgumentChecker = new Meteor.EnvironmentVariable; -// @export check check = function (value, pattern) { // Record that check got called, if somebody cared. var argChecker = currentArgumentChecker.get(); @@ -16,26 +15,20 @@ check = function (value, pattern) { }; Match = { - // @export Match.Optional Optional: function (pattern) { return new Optional(pattern); }, - // @export Match.OneOf OneOf: function (/*arguments*/) { return new OneOf(_.toArray(arguments)); }, - // @export Match.Any Any: ['__any__'], - // @export Match.Where Where: function (condition) { return new Where(condition); }, - // @export Match.ObjectIncluding ObjectIncluding: function (pattern) { return new ObjectIncluding(pattern); }, - // @export Match.Error // XXX should we record the path down the tree in the error message? // XXX matchers should know how to describe themselves for errors Error: Meteor.makeErrorType("Match.Error", function (msg) { @@ -45,7 +38,6 @@ Match = { this.sanitizedError = new Meteor.Error(400, "Match failed"); }), - // @export Match.test // Tests to see if value matches pattern. Unlike check, it merely returns true // or false (unless an error other than Match.Error was thrown). It does not // interact with _failIfArgumentsAreNotAllChecked. @@ -68,7 +60,6 @@ Match = { // `args` (either directly or in the first level of an array), throws an error // (using `description` in the message). // - // @export Match._failIfArgumentsAreNotAllChecked _failIfArgumentsAreNotAllChecked: function (f, context, args, description) { var argChecker = new ArgumentChecker(args, description); var result = currentArgumentChecker.withValue(argChecker, function () { diff --git a/packages/check/package.js b/packages/check/package.js index 2a58b7c400..6fcfb6a5bc 100644 --- a/packages/check/package.js +++ b/packages/check/package.js @@ -6,6 +6,8 @@ Package.describe({ Package.on_use(function (api) { api.use(['underscore', 'ejson'], ['client', 'server']); + api.exportSymbol(['check', 'Match']); + api.add_files('match.js', ['client', 'server']); }); diff --git a/packages/ctl-helper/ctl-helper.js b/packages/ctl-helper/ctl-helper.js index 3f1236e578..4b484be28c 100644 --- a/packages/ctl-helper/ctl-helper.js +++ b/packages/ctl-helper/ctl-helper.js @@ -1,7 +1,6 @@ var optimist = Npm.require('optimist'); var Future = Npm.require('fibers/future'); -// @export Ctl Ctl = {}; _.extend(Ctl, { diff --git a/packages/ctl-helper/package.js b/packages/ctl-helper/package.js index 59b9adfa19..606fb78c2d 100644 --- a/packages/ctl-helper/package.js +++ b/packages/ctl-helper/package.js @@ -6,5 +6,6 @@ Npm.depends({optimist: '0.4.0'}); Package.on_use(function (api) { api.use(['underscore', 'livedata', 'mongo-livedata'], 'server'); + api.exportSymbol('Ctl', 'server'); api.add_files('ctl-helper.js', 'server'); }); diff --git a/packages/ctl/ctl.js b/packages/ctl/ctl.js index 8fe4beebd0..b1f022cb4f 100644 --- a/packages/ctl/ctl.js +++ b/packages/ctl/ctl.js @@ -165,7 +165,6 @@ Ctl.Commands.push({ } }); -// @export main main = function (argv) { return Ctl.main(argv); }; diff --git a/packages/ctl/package.js b/packages/ctl/package.js index b088c6884b..b338c09164 100644 --- a/packages/ctl/package.js +++ b/packages/ctl/package.js @@ -4,6 +4,6 @@ Package.describe({ Package.on_use(function (api) { api.use(['underscore', 'livedata', 'mongo-livedata', 'ctl-helper'], 'server'); - + api.exportSymbol('main', 'server'); api.add_files('ctl.js', 'server'); }); diff --git a/packages/deps/deps.js b/packages/deps/deps.js index d5db7aaa8a..21f4afc32b 100644 --- a/packages/deps/deps.js +++ b/packages/deps/deps.js @@ -1,7 +1,3 @@ -// We have to export Deps as a single symbol since we want to be able -// to change Deps.active later. -// -// @export Deps Deps = {}; Deps.active = false; Deps.currentComputation = null; diff --git a/packages/deps/package.js b/packages/deps/package.js index b78f953649..40a5e79a75 100644 --- a/packages/deps/package.js +++ b/packages/deps/package.js @@ -7,6 +7,7 @@ Package.describe({ Package.on_use(function (api) { api.use('underscore'); + api.exportSymbol('Deps'); api.add_files('deps.js'); }); diff --git a/packages/dev-bundle-fetcher/dev-bundle.js b/packages/dev-bundle-fetcher/dev-bundle.js index 3051628f10..486dadc741 100644 --- a/packages/dev-bundle-fetcher/dev-bundle.js +++ b/packages/dev-bundle-fetcher/dev-bundle.js @@ -1,4 +1,3 @@ -// @export DevBundleFetcher DevBundleFetcher = { script: function () { return Assets.getText("dev-bundle"); diff --git a/packages/dev-bundle-fetcher/package.js b/packages/dev-bundle-fetcher/package.js index 08e656ef0f..1f1f0ecf4b 100644 --- a/packages/dev-bundle-fetcher/package.js +++ b/packages/dev-bundle-fetcher/package.js @@ -4,5 +4,6 @@ Package.describe({ }); Package.on_use(function (api) { + api.exportSymbol('DevBundleFetcher', 'server'); api.add_files(['dev-bundle', 'dev-bundle.js'], ['server']); }); diff --git a/packages/domutils/domutils.js b/packages/domutils/domutils.js index 33d1b13415..118fb91e2b 100644 --- a/packages/domutils/domutils.js +++ b/packages/domutils/domutils.js @@ -1,4 +1,3 @@ -// @export DomUtils DomUtils = {}; var qsaFindAllBySelector = function (selector, contextNode) { diff --git a/packages/domutils/package.js b/packages/domutils/package.js index f23c6253e7..6bf5d76a1d 100644 --- a/packages/domutils/package.js +++ b/packages/domutils/package.js @@ -14,6 +14,7 @@ Package.on_use(function (api) { api.use('underscore', 'client'); + api.exportSymbol('DomUtils', 'client'); api.add_files('domutils.js', 'client'); }); diff --git a/packages/ejson/base64.js b/packages/ejson/base64.js index 5b7f309943..478538f917 100644 --- a/packages/ejson/base64.js +++ b/packages/ejson/base64.js @@ -62,7 +62,6 @@ var getVal = function (ch) { return BASE_64_VALS[ch]; }; -// @export EJSON.newBinary EJSON.newBinary = function (len) { if (typeof Uint8Array === 'undefined' || typeof ArrayBuffer === 'undefined') { var ret = []; @@ -123,8 +122,6 @@ base64Decode = function (str) { return arr; }; -// @export _EJSONTest.base64Encode _EJSONTest.base64Encode = base64Encode; -// @export _EJSONTest.base64Decode _EJSONTest.base64Decode = base64Decode; diff --git a/packages/ejson/ejson.js b/packages/ejson/ejson.js index 432357f8e9..b049fa3863 100644 --- a/packages/ejson/ejson.js +++ b/packages/ejson/ejson.js @@ -1,3 +1,6 @@ +EJSON = {}; +_EJSONTest = {}; + var customTypes = {}; // Add a custom type, using a method of your choice to get to and // from a basic JSON-able representation. The factory argument @@ -9,7 +12,6 @@ var customTypes = {}; // - a typeName() method, to show how to look it up in our type table. // It is okay if these methods are monkey-patched on. // -// @export EJSON.addType EJSON.addType = function (name, factory) { if (_.has(customTypes, name)) throw new Error("Type " + name + " already present"); @@ -91,7 +93,6 @@ var builtinConverters = [ } ]; -// @export EJSON._isCustomType EJSON._isCustomType = function (obj) { return obj && typeof obj.toJSONValue === 'function' && @@ -101,7 +102,6 @@ EJSON._isCustomType = function (obj) { // for both arrays and objects, in-place modification. -// @export EJSON._adjustTypesToJSONValue var adjustTypesToJSONValue = EJSON._adjustTypesToJSONValue = function (obj) { if (obj === null) @@ -136,7 +136,6 @@ var toJSONValueHelper = function (item) { return undefined; }; -// @export EJSON.toJSONValue EJSON.toJSONValue = function (item) { var changed = toJSONValueHelper(item); if (changed !== undefined) @@ -152,7 +151,6 @@ EJSON.toJSONValue = function (item) { // use the object you hand it, but may return something // different if the object you hand it itself needs changing. // -// @export EJSON._adjustTypesFromJSONValue var adjustTypesFromJSONValue = EJSON._adjustTypesFromJSONValue = function (obj) { if (obj === null) @@ -197,7 +195,6 @@ var fromJSONValueHelper = function (value) { return value; }; -// @export EJSON.fromJSONValue EJSON.fromJSONValue = function (item) { var changed = fromJSONValueHelper(item); if (changed === item && typeof item === 'object') { @@ -209,23 +206,19 @@ EJSON.fromJSONValue = function (item) { } }; -// @export EJSON.stringify EJSON.stringify = function (item) { return JSON.stringify(EJSON.toJSONValue(item)); }; -// @export EJSON.parse EJSON.parse = function (item) { return EJSON.fromJSONValue(JSON.parse(item)); }; -// @export EJSON.isBinary EJSON.isBinary = function (obj) { return !!((typeof Uint8Array !== 'undefined' && obj instanceof Uint8Array) || (obj && obj.$Uint8ArrayPolyfill)); }; -// @export EJSON.equals EJSON.equals = function (a, b, options) { var i; var keyOrderSensitive = !!(options && options.keyOrderSensitive); @@ -297,7 +290,6 @@ EJSON.equals = function (a, b, options) { } }; -// @export EJSON.clone EJSON.clone = function (v) { var ret; if (typeof v !== "object") diff --git a/packages/ejson/package.js b/packages/ejson/package.js index e8381d0d58..26a33d4b35 100644 --- a/packages/ejson/package.js +++ b/packages/ejson/package.js @@ -5,6 +5,8 @@ Package.describe({ Package.on_use(function (api) { api.use(['json', 'underscore']); + api.exportSymbol('EJSON'); + api.exportSymbol('_EJSONTest'); api.add_files('ejson.js', ['client', 'server']); api.add_files('base64.js', ['client', 'server']); }); diff --git a/packages/email/email.js b/packages/email/email.js index c0fdd00aed..d425133119 100644 --- a/packages/email/email.js +++ b/packages/email/email.js @@ -2,6 +2,9 @@ var Future = Npm.require('fibers/future'); var urlModule = Npm.require('url'); var MailComposer = Npm.require('mailcomposer').MailComposer; +Email = {}; +_EmailTest = {}; + var makePool = function (mailUrlString) { var mailUrl = urlModule.parse(mailUrlString); if (mailUrl.protocol !== 'smtp:') @@ -46,13 +49,11 @@ var next_devmode_mail_id = 0; var output_stream = process.stdout; // Testing hooks -// @export _EmailTest.overrideOutputStream _EmailTest.overrideOutputStream = function (stream) { next_devmode_mail_id = 0; output_stream = stream; }; -// @export _EmailTest.restoreOutputStream _EmailTest.restoreOutputStream = function () { output_stream = process.stdout; }; @@ -88,7 +89,6 @@ var smtpSend = function (mc) { * ahead and send the email (or at least, try subsequent hooks), or * false to skip sending. */ -// @export Email._hookSend var sendHooks = []; Email._hookSend = function (f) { sendHooks.push(f); @@ -113,7 +113,6 @@ Email._hookSend = function (f) { * @param options.html {String} RFC5322 mail body (HTML) * @param options.headers {Object} custom RFC5322 headers (dictionary) */ -// @export Email.send Email.send = function (options) { for (var i = 0; i < sendHooks.length; i++) if (! sendHooks[i](options)) diff --git a/packages/email/package.js b/packages/email/package.js index ae6d05fbdc..a1fa884378 100644 --- a/packages/email/package.js +++ b/packages/email/package.js @@ -8,6 +8,8 @@ Npm.depends({mailcomposer: "0.1.15", simplesmtp: "0.1.25", "stream-buffers": "0. Package.on_use(function (api) { api.use('underscore', 'server'); + api.exportSymbol('Email'); + api.exportSymbol('_EmailTest'); api.add_files('email.js', 'server'); }); diff --git a/packages/facebook/facebook_client.js b/packages/facebook/facebook_client.js index 2ab2508c68..cfbc06fed2 100644 --- a/packages/facebook/facebook_client.js +++ b/packages/facebook/facebook_client.js @@ -1,6 +1,7 @@ +Facebook = {}; + // Request Facebook credentials for the user // -// @export Facebook.requestCredential // @param options {optional} // @param credentialRequestCompleteCallback {Function} Callback function to call on // completion. Takes one argument, credentialToken on success, or Error on diff --git a/packages/facebook/facebook_server.js b/packages/facebook/facebook_server.js index c219933640..0775a4f71f 100644 --- a/packages/facebook/facebook_server.js +++ b/packages/facebook/facebook_server.js @@ -1,3 +1,5 @@ +Facebook = {}; + var querystring = Npm.require('querystring'); @@ -91,7 +93,6 @@ var getIdentity = function (accessToken) { } }; -// @export Facebook.retrieveCredential Facebook.retrieveCredential = function(credentialToken) { return Oauth.retrieveCredential(credentialToken); }; diff --git a/packages/facebook/package.js b/packages/facebook/package.js index db4b579ba5..682f3afcff 100644 --- a/packages/facebook/package.js +++ b/packages/facebook/package.js @@ -14,6 +14,8 @@ Package.on_use(function(api) { api.use('random', 'client'); api.use('service-configuration', ['client', 'server']); + api.exportSymbol('Facebook'); + api.add_files( ['facebook_configure.html', 'facebook_configure.js'], 'client'); diff --git a/packages/github/github_client.js b/packages/github/github_client.js index 6b2fbd256e..4bf7a27079 100644 --- a/packages/github/github_client.js +++ b/packages/github/github_client.js @@ -1,5 +1,6 @@ +Github = {}; + // Request Github credentials for the user -// @export Github.requestCredential // @param options {optional} // @param credentialRequestCompleteCallback {Function} Callback function to call on // completion. Takes one argument, credentialToken on success, or Error on diff --git a/packages/github/github_server.js b/packages/github/github_server.js index ee20d57e19..6e789d6372 100644 --- a/packages/github/github_server.js +++ b/packages/github/github_server.js @@ -1,3 +1,5 @@ +Github = {}; + Oauth.registerService('github', 2, null, function(query) { var accessToken = getAccessToken(query); @@ -63,7 +65,6 @@ var getIdentity = function (accessToken) { }; -// @export Github.retrieveCredential Github.retrieveCredential = function(credentialToken) { return Oauth.retrieveCredential(credentialToken); }; diff --git a/packages/github/package.js b/packages/github/package.js index 54da6ec47e..4217bedd12 100644 --- a/packages/github/package.js +++ b/packages/github/package.js @@ -14,6 +14,8 @@ Package.on_use(function(api) { api.use('random', 'client'); api.use('service-configuration', ['client', 'server']); + api.exportSymbol('Github'); + api.add_files( ['github_configure.html', 'github_configure.js'], 'client'); diff --git a/packages/google/google_client.js b/packages/google/google_client.js index 6f09e06cb0..74c2a0c809 100644 --- a/packages/google/google_client.js +++ b/packages/google/google_client.js @@ -1,5 +1,6 @@ +Google = {}; + // Request Google credentials for the user -// @export Google.requestCredential // @param options {optional} // @param credentialRequestCompleteCallback {Function} Callback function to call on // completion. Takes one argument, credentialToken on success, or Error on diff --git a/packages/google/google_server.js b/packages/google/google_server.js index ca3ba28ab1..d180cfa99f 100644 --- a/packages/google/google_server.js +++ b/packages/google/google_server.js @@ -1,5 +1,6 @@ +Google = {}; + // https://developers.google.com/accounts/docs/OAuth2Login#userinfocall -// @export Google.whitelistedFields Google.whitelistedFields = ['id', 'email', 'verified_email', 'name', 'given_name', 'family_name', 'picture', 'locale', 'timezone', 'gender']; @@ -75,7 +76,6 @@ var getIdentity = function (accessToken) { }; -// @export Google.retrieveCredential Google.retrieveCredential = function(credentialToken) { return Oauth.retrieveCredential(credentialToken); -}; \ No newline at end of file +}; diff --git a/packages/google/package.js b/packages/google/package.js index 10e0f7027a..9dfe3d480d 100644 --- a/packages/google/package.js +++ b/packages/google/package.js @@ -12,6 +12,8 @@ Package.on_use(function(api) { api.use(['underscore', 'service-configuration'], ['client', 'server']); api.use(['random', 'templating'], 'client'); + api.exportSymbol('Google'); + api.add_files( ['google_configure.html', 'google_configure.js'], 'client'); diff --git a/packages/handlebars/evaluate-handlebars.js b/packages/handlebars/evaluate-handlebars.js index 8aa4bf6bfe..fa3df9bcd2 100644 --- a/packages/handlebars/evaluate-handlebars.js +++ b/packages/handlebars/evaluate-handlebars.js @@ -1,4 +1,3 @@ -// @export Handlebars Handlebars = {}; // XXX we probably forgot to implement the #foo case where foo is not diff --git a/packages/handlebars/package.js b/packages/handlebars/package.js index f3a3b08d34..f01917a09f 100644 --- a/packages/handlebars/package.js +++ b/packages/handlebars/package.js @@ -9,6 +9,8 @@ Package.on_use(function (api) { api.use('underscore'); api.use('spark', 'client'); + api.exportSymbol('Handlebars'); + // XXX these should be split up into two different slices, not // different code with totally different APIs that is sent depending // on the architecture diff --git a/packages/handlebars/parse-handlebars.js b/packages/handlebars/parse-handlebars.js index 2fdd132112..90e5319bb3 100644 --- a/packages/handlebars/parse-handlebars.js +++ b/packages/handlebars/parse-handlebars.js @@ -1,4 +1,3 @@ -// @export Handlebars Handlebars = {}; /* Our format: diff --git a/packages/htmljs/html.js b/packages/htmljs/html.js index 231f295315..1325ed1503 100644 --- a/packages/htmljs/html.js +++ b/packages/htmljs/html.js @@ -69,13 +69,6 @@ var tag_names = 'P PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG SUB SUP ' + 'TABLE TBODY TD TEXTAREA TFOOT TH THEAD TR TT U UL VAR').split(' '); -// @export A, ABBR, ACRONYM, B, BDO, BIG, BLOCKQUOTE, BR, BUTTON, CAPTION -// @export CITE, CODE, COL, COLGROUP, DD, DEL, DFN, DIV, DL, DT, EM, FIELDSET -// @export FORM, H1, H2, H3, H4, H5, H6, HR, I, IFRAME, IMG, INPUT, INS, KBD -// @export LABEL, LEGEND, LI, OBJECT, OL, OPTGROUP, OPTION, P, PARAM, PRE, Q -// @export S, SAMP, SCRIPT, SELECT, SMALL, SPAN, STRIKE, STRONG, SUB, SUP -// @export TABLE, TBODY, TD, TEXTAREA, TFOOT, TH, THEAD, TR, TT, U, UL, VAR - for (var i = 0; i < tag_names.length; i++) { var tag = tag_names[i]; diff --git a/packages/htmljs/package.js b/packages/htmljs/package.js index 9bf6b9ac14..9f3cf6f13c 100644 --- a/packages/htmljs/package.js +++ b/packages/htmljs/package.js @@ -5,6 +5,16 @@ Package.describe({ Package.on_use(function (api) { // Note: html.js will optionally use jquery if it's available api.add_files('html.js', 'client'); + api.exportSymbol([ + 'A', 'ABBR', 'ACRONYM', 'B', 'BDO', 'BIG', 'BLOCKQUOTE', 'BR', 'BUTTON', + 'CAPTION', 'CITE', 'CODE', 'COL', 'COLGROUP', 'DD', 'DEL', 'DFN', 'DIV', + 'DL', 'DT', 'EM', 'FIELDSET', 'FORM', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', + 'HR', 'I', 'IFRAME', 'IMG', 'INPUT', 'INS', 'KBD', 'LABEL', 'LEGEND', 'LI', + 'OBJECT', 'OL', 'OPTGROUP', 'OPTION', 'P', 'PARAM', 'PRE', 'Q', 'S', 'SAMP', + 'SCRIPT', 'SELECT', 'SMALL', 'SPAN', 'STRIKE', 'STRONG', 'SUB', 'SUP', + 'TABLE', 'TBODY', 'TD', 'TEXTAREA', 'TFOOT', 'TH', 'THEAD', 'TR', 'TT', 'U', + 'UL', 'VAR' + ], 'client'); }); Package.on_test(function (api) { diff --git a/packages/http/httpcall_client.js b/packages/http/httpcall_client.js index b3cce1367a..1d064c9491 100644 --- a/packages/http/httpcall_client.js +++ b/packages/http/httpcall_client.js @@ -1,4 +1,3 @@ -// @export Meteor.http.call Meteor.http.call = function(method, url, options, callback) { ////////// Process arguments ////////// diff --git a/packages/http/httpcall_common.js b/packages/http/httpcall_common.js index de0242c820..e319196eb2 100644 --- a/packages/http/httpcall_common.js +++ b/packages/http/httpcall_common.js @@ -67,22 +67,18 @@ populateData = function(response) { } }; -// @export Meteor.http.get Meteor.http.get = function (/* varargs */) { return Meteor.http.call.apply(this, ["GET"].concat(_.toArray(arguments))); }; -// @export Meteor.http.post Meteor.http.post = function (/* varargs */) { return Meteor.http.call.apply(this, ["POST"].concat(_.toArray(arguments))); }; -// @export Meteor.http.put Meteor.http.put = function (/* varargs */) { return Meteor.http.call.apply(this, ["PUT"].concat(_.toArray(arguments))); }; -// @export Meteor.http.del Meteor.http.del = function (/* varargs */) { return Meteor.http.call.apply(this, ["DELETE"].concat(_.toArray(arguments))); }; diff --git a/packages/http/httpcall_server.js b/packages/http/httpcall_server.js index 6c38d0c870..159ccae7a2 100644 --- a/packages/http/httpcall_server.js +++ b/packages/http/httpcall_server.js @@ -106,5 +106,4 @@ _call = function(method, url, options, callback) { }); }; -// @export Meteor.http.call Meteor.http.call = Meteor._wrapAsync(_call); diff --git a/packages/js-analyze/js_analyze.js b/packages/js-analyze/js_analyze.js index 3a142fbfb5..5e612b25f0 100644 --- a/packages/js-analyze/js_analyze.js +++ b/packages/js-analyze/js_analyze.js @@ -4,7 +4,6 @@ var estraverse = Npm.require('estraverse'); var Syntax = estraverse.Syntax; -// @export JSAnalyze JSAnalyze = {}; JSAnalyze.READ = 1; diff --git a/packages/js-analyze/package.js b/packages/js-analyze/package.js index 9fb6db1762..68cf16a90e 100644 --- a/packages/js-analyze/package.js +++ b/packages/js-analyze/package.js @@ -24,5 +24,6 @@ Npm.depends({ // packages would need to function without the analysis provided by this // package). Package.on_use(function (api) { + api.exportSymbol('JSAnalyze', 'server'); api.add_files('js_analyze.js', 'server'); }); diff --git a/packages/jsparse/lexer.js b/packages/jsparse/lexer.js index db7e2e1676..6a4fdb75b7 100644 --- a/packages/jsparse/lexer.js +++ b/packages/jsparse/lexer.js @@ -255,7 +255,6 @@ Lexeme.prototype.toString = function () { // Thie flag can be read and set manually to affect the // parsing of the next token. -// @export JSLexer JSLexer = function (code) { this.code = code; this.pos = 0; diff --git a/packages/jsparse/package.js b/packages/jsparse/package.js index 3b09def1be..0f1bfdf5cb 100644 --- a/packages/jsparse/package.js +++ b/packages/jsparse/package.js @@ -4,6 +4,7 @@ Package.describe({ }); Package.on_use(function (api) { + api.exportSymbol(['JSLexer', 'JSParser', 'ParseNode']); api.add_files(['lexer.js', 'parserlib.js', 'stringify.js', 'parser.js'], ['client', 'server']); }); diff --git a/packages/jsparse/parser.js b/packages/jsparse/parser.js index a57c753ad5..7fd4f0ed93 100644 --- a/packages/jsparse/parser.js +++ b/packages/jsparse/parser.js @@ -26,7 +26,6 @@ var makeSet = function (array) { }; -// @export JSParser JSParser = function (code, options) { this.lexer = new JSLexer(code); this.oldToken = null; diff --git a/packages/jsparse/parserlib.js b/packages/jsparse/parserlib.js index 25e50c8f5e..e17a5d9c05 100644 --- a/packages/jsparse/parserlib.js +++ b/packages/jsparse/parserlib.js @@ -6,7 +6,6 @@ var isArray = function (obj) { return obj && (typeof obj === 'object') && (typeof obj.length === 'number'); }; -// @export ParseNode ParseNode = function (name, children) { this.name = name; this.children = children; diff --git a/packages/livedata/client_convenience.js b/packages/livedata/client_convenience.js index 8323c1d5b8..77b023424d 100644 --- a/packages/livedata/client_convenience.js +++ b/packages/livedata/client_convenience.js @@ -1,7 +1,5 @@ -// @export Meteor.connection Meteor.connection = null; -// @export Meteor.refresh Meteor.refresh = function (notification) { }; @@ -18,13 +16,6 @@ if (Meteor.isClient) { // Proxy the public methods of Meteor.connection so they can // be called directly on Meteor. - // @export Meteor.subscribe - // @export Meteor.methods - // @export Meteor.call - // @export Meteor.apply - // @export Meteor.status - // @export Meteor.reconnect - // @export Meteor.disconnect _.each(['subscribe', 'methods', 'call', 'apply', 'status', 'reconnect', 'disconnect'], function (name) { @@ -42,5 +33,4 @@ if (Meteor.isClient) { Meteor.default_connection = Meteor.connection; // We should transition from Meteor.connect to DDP.connect. -// @export Meteor.connect Meteor.connect = DDP.connect; diff --git a/packages/livedata/crossbar.js b/packages/livedata/crossbar.js index 9153fb18a5..1e4a83aa29 100644 --- a/packages/livedata/crossbar.js +++ b/packages/livedata/crossbar.js @@ -1,4 +1,3 @@ -// @export DDP._InvalidationCrossbar DDP._InvalidationCrossbar = function () { var self = this; diff --git a/packages/livedata/livedata_common.js b/packages/livedata/livedata_common.js index 8ea1edda40..fa6ea64abf 100644 --- a/packages/livedata/livedata_common.js +++ b/packages/livedata/livedata_common.js @@ -1,6 +1,8 @@ +DDP = {}; +_LivedataTest = {}; + SUPPORTED_DDP_VERSIONS = [ 'pre1' ]; -// @export _LivedataTest.SUPPORTED_DDP_VERSIONS _LivedataTest.SUPPORTED_DDP_VERSIONS = SUPPORTED_DDP_VERSIONS; MethodInvocation = function (options) { @@ -119,6 +121,4 @@ stringifyDDP = function (msg) { // it to get the current user. accounts-password uses it to stash SRP // state in the DDP session. Meteor.setTimeout and friends clear // it. We can probably find a better way to factor this. -// -// @export DDP._CurrentInvocation DDP._CurrentInvocation = new Meteor.EnvironmentVariable; diff --git a/packages/livedata/livedata_connection.js b/packages/livedata/livedata_connection.js index 8a0e50534e..1e6058cf3e 100644 --- a/packages/livedata/livedata_connection.js +++ b/packages/livedata/livedata_connection.js @@ -1383,7 +1383,6 @@ _.extend(Connection.prototype, { } }); -// @export _LivedataTest.Connection _LivedataTest.Connection = Connection; // @param url {String} URL to Meteor app, @@ -1393,7 +1392,6 @@ _LivedataTest.Connection = Connection; // "/", // "ddp+sockjs://ddp--****-foo.meteor.com/sockjs" // -// @export DDP.connect DDP.connect = function (url, _reloadOnUpdate) { var ret = new Connection( url, {reloadOnUpdate: _reloadOnUpdate}); @@ -1404,7 +1402,6 @@ DDP.connect = function (url, _reloadOnUpdate) { // Hack for `spiderable` package: a way to see if the page is done // loading all the data it needs. // -// @export DDP._allSubscriptionsReady allConnections = []; DDP._allSubscriptionsReady = function () { return _.all(allConnections, function (conn) { diff --git a/packages/livedata/livedata_server.js b/packages/livedata/livedata_server.js index bd47965d18..556c2b3b89 100644 --- a/packages/livedata/livedata_server.js +++ b/packages/livedata/livedata_server.js @@ -1,3 +1,5 @@ +DDPServer = {}; + var Fiber = Npm.require('fibers'); // This file contains classes: @@ -102,7 +104,6 @@ var SessionCollectionView = function (collectionName, sessionCallbacks) { self.callbacks = sessionCallbacks; }; -// @export _LivedataTest.SessionCollectionView _LivedataTest.SessionCollectionView = SessionCollectionView; @@ -1339,7 +1340,6 @@ var calculateVersion = function (clientSupportedVersions, return correctVersion; }; -// @export _LivedataTest.calculateVersion _LivedataTest.calculateVersion = calculateVersion; @@ -1370,7 +1370,6 @@ var wrapInternalException = function (exception, context) { // Private interface for 'audit-argument-checks' package. // -// @export DDP._setAuditArgumentChecks var shouldAuditArgumentChecks = false; DDP._setAuditArgumentChecks = function (value) { shouldAuditArgumentChecks = value; diff --git a/packages/livedata/package.js b/packages/livedata/package.js index bfe256fcfc..f71324634f 100644 --- a/packages/livedata/package.js +++ b/packages/livedata/package.js @@ -16,6 +16,11 @@ Package.on_use(function (api) { // XXX split this package into multiple packages or multiple slices instead api.use(['webapp', 'routepolicy'], 'server', {weak: true}); + api.exportSymbol('DDP'); + api.exportSymbol('DDPServer', 'server'); + + api.exportSymbol('_LivedataTest'); + // Transport api.use('reload', 'client'); api.add_files(['sockjs-0.3.4.js', @@ -28,6 +33,7 @@ Package.on_use(function (api) { // _idParse, _idStringify. api.use('minimongo', ['client', 'server']); + api.add_files('writefence.js', 'server'); api.add_files('crossbar.js', 'server'); diff --git a/packages/livedata/server_convenience.js b/packages/livedata/server_convenience.js index af060641f4..28cf98c66b 100644 --- a/packages/livedata/server_convenience.js +++ b/packages/livedata/server_convenience.js @@ -1,18 +1,12 @@ -// @export Meteor.server Meteor.server = null; // Note that this is redefined below if we in fact start a server. -// @export Meteor.refresh Meteor.refresh = function (notification) { }; // Only create a server if we are in an environment with a HTTP server // (as opposed to, eg, a command-line tool). // -// @export Meteor.publish -// @export Meteor.methods -// @export Meteor.call -// @export Meteor.apply if (Package.webapp) { if (process.env.DDP_DEFAULT_CONNECTION_URL) { __meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL = diff --git a/packages/livedata/stream_client_common.js b/packages/livedata/stream_client_common.js index e03c0e56cd..1633535b0d 100644 --- a/packages/livedata/stream_client_common.js +++ b/packages/livedata/stream_client_common.js @@ -69,7 +69,6 @@ toWebsocketUrl = function (url) { return ret; }; -// @export _LivedataTest.toSockjsUrl _LivedataTest.toSockjsUrl = toSockjsUrl; diff --git a/packages/livedata/writefence.js b/packages/livedata/writefence.js index 0239610236..05e2802e8a 100644 --- a/packages/livedata/writefence.js +++ b/packages/livedata/writefence.js @@ -5,7 +5,6 @@ var Future = Npm.require(path.join('fibers', 'future')); // when all of the writes are fully committed and propagated (all // observers have been notified of the write and acknowledged it.) // -// @export DDP._WriteFence DDP._WriteFence = function () { var self = this; @@ -20,7 +19,6 @@ DDP._WriteFence = function () { // that writes to databases should register their writes with it using // beginWrite(). // -// @export DDP._CurrentWriteFence DDP._CurrentWriteFence = new Meteor.EnvironmentVariable; _.extend(DDP._WriteFence.prototype, { diff --git a/packages/liverange/liverange.js b/packages/liverange/liverange.js index b68d7aaef6..cbedb0194f 100644 --- a/packages/liverange/liverange.js +++ b/packages/liverange/liverange.js @@ -89,8 +89,6 @@ var wrapEndpoints = function (start, end) { // // XXX Should eventually support LiveRanges where start === end // and start.parentNode is null. -// -// @export LiveRange LiveRange = function (tag, start, end, inner) { if (start.nodeType === 11 /* DocumentFragment */) { end = start.lastChild; diff --git a/packages/liverange/package.js b/packages/liverange/package.js index daead7b09b..0f009415f8 100644 --- a/packages/liverange/package.js +++ b/packages/liverange/package.js @@ -4,6 +4,7 @@ Package.describe({ }); Package.on_use(function (api) { + api.exportSymbol('LiveRange', 'client'); api.add_files('liverange.js', 'client'); }); diff --git a/packages/localstorage/localstorage.js b/packages/localstorage/localstorage.js index 762f224237..4dd1e49ba2 100644 --- a/packages/localstorage/localstorage.js +++ b/packages/localstorage/localstorage.js @@ -1,5 +1,4 @@ // This is not an ideal name, but we can change it later. -// @export Meteor._localStorage if (window.localStorage) { Meteor._localStorage = { diff --git a/packages/logging/logging.js b/packages/logging/logging.js index db33a5ca07..e33361c11d 100644 --- a/packages/logging/logging.js +++ b/packages/logging/logging.js @@ -1,4 +1,3 @@ -// @export Log Log = function () { return Log.info.apply(this, arguments); }; diff --git a/packages/logging/package.js b/packages/logging/package.js index 246a13451a..b6b0edd429 100644 --- a/packages/logging/package.js +++ b/packages/logging/package.js @@ -8,6 +8,7 @@ Npm.depends({ }); Package.on_use(function (api) { + api.exportSymbol('Log'); api.use(['underscore', 'ejson']); api.add_files('logging.js'); }); diff --git a/packages/meetup/meetup_client.js b/packages/meetup/meetup_client.js index c876b6d0df..4661f93390 100644 --- a/packages/meetup/meetup_client.js +++ b/packages/meetup/meetup_client.js @@ -1,5 +1,5 @@ +Meetup = {}; // Request Meetup credentials for the user -// @export Meetup.requestCredential // @param options {optional} // @param credentialRequestCompleteCallback {Function} Callback function to call on // completion. Takes one argument, credentialToken on success, or Error on diff --git a/packages/meetup/meetup_server.js b/packages/meetup/meetup_server.js index 6944576be3..e3a2bb7094 100644 --- a/packages/meetup/meetup_server.js +++ b/packages/meetup/meetup_server.js @@ -1,3 +1,5 @@ +Meetup = {}; + Oauth.registerService('meetup', 2, null, function(query) { var accessToken = getAccessToken(query); @@ -51,7 +53,6 @@ var getIdentity = function (accessToken) { }; -// @export Meetup.retrieveCredential Meetup.retrieveCredential = function(credentialToken) { return Oauth.retrieveCredential(credentialToken); -}; \ No newline at end of file +}; diff --git a/packages/meetup/package.js b/packages/meetup/package.js index a36260da76..41a75c91c5 100644 --- a/packages/meetup/package.js +++ b/packages/meetup/package.js @@ -14,6 +14,8 @@ Package.on_use(function(api) { api.use('random', 'client'); api.use('service-configuration', ['client', 'server']); + api.exportSymbol('Meetup'); + api.add_files( ['meetup_configure.html', 'meetup_configure.js'], 'client'); diff --git a/packages/meteor/client_environment.js b/packages/meteor/client_environment.js index 6ceade5328..37305c04e4 100644 --- a/packages/meteor/client_environment.js +++ b/packages/meteor/client_environment.js @@ -1,11 +1,9 @@ -// @export Meteor.isClient -Meteor.isClient = true; +Meteor = { + isClient: true, + isServer: false +}; -// @export Meteor.isServer -Meteor.isServer = false; - -// @export Meteor.settings if (typeof __meteor_runtime_config__ === 'object' && __meteor_runtime_config__.PUBLIC_SETTINGS) { - Meteor.settings = { public: __meteor_runtime_config__.PUBLIC_SETTINGS }; + Meteor.settings = { 'public': __meteor_runtime_config__.PUBLIC_SETTINGS }; } diff --git a/packages/meteor/debug.js b/packages/meteor/debug.js index 87fb134647..a6f20d5f9c 100644 --- a/packages/meteor/debug.js +++ b/packages/meteor/debug.js @@ -11,7 +11,6 @@ var suppress = 0; // be very visible. if you change _debug to go someplace else, etc, // please fix the autopublish code to do something reasonable. // -// @export Meteor._debug Meteor._debug = function (/* arguments */) { if (suppress) { suppress--; @@ -58,7 +57,6 @@ Meteor._debug = function (/* arguments */) { // Suppress the next 'count' Meteor._debug messsages. Use this to // stop tests from spamming the console. // -// @export Meteor._suppress_log Meteor._suppress_log = function (count) { suppress += count; }; diff --git a/packages/meteor/dynamics_browser.js b/packages/meteor/dynamics_browser.js index 9b358f4c14..b2b7c34a88 100644 --- a/packages/meteor/dynamics_browser.js +++ b/packages/meteor/dynamics_browser.js @@ -3,7 +3,6 @@ var nextSlot = 0; var currentValues = []; -// @export Meteor.EnvironmentVariable Meteor.EnvironmentVariable = function () { this.slot = nextSlot++; }; @@ -25,7 +24,6 @@ _.extend(Meteor.EnvironmentVariable.prototype, { } }); -// @export Meteor.bindEnvironment Meteor.bindEnvironment = function (func, onException, _this) { // needed in order to be able to create closures inside func and // have the closed variables not change back to their original diff --git a/packages/meteor/dynamics_nodejs.js b/packages/meteor/dynamics_nodejs.js index 5c39a42b82..e3b7fbb256 100644 --- a/packages/meteor/dynamics_nodejs.js +++ b/packages/meteor/dynamics_nodejs.js @@ -4,7 +4,6 @@ var Fiber = Npm.require('fibers'); var nextSlot = 0; -// @export Meteor.EnvironmentVariable Meteor.EnvironmentVariable = function () { this.slot = nextSlot++; }; @@ -52,7 +51,6 @@ _.extend(Meteor.EnvironmentVariable.prototype, { // return value of the function will be passed through, and no new // fiber will be created.) // -// @export Meteor.bindEnvironment Meteor.bindEnvironment = function (func, onException, _this) { var boundValues = _.clone(Fiber.current._meteor_dynamics || []); diff --git a/packages/meteor/errors.js b/packages/meteor/errors.js index a2f93f63f7..cdeb8a5abb 100644 --- a/packages/meteor/errors.js +++ b/packages/meteor/errors.js @@ -10,7 +10,6 @@ var inherits = function (child, parent) { // environments. constructor can set fields on `this` (and should probably set // `message`, which is what gets displayed at the top of a stack trace). // -// @export Meteor.makeErrorType Meteor.makeErrorType = function (name, constructor) { var errorClass = function (/*arguments*/) { var self = this; @@ -51,7 +50,6 @@ Meteor.makeErrorType = function (name, constructor) { // EJSON.addType here because the type is determined by location in the // protocol, not text on the wire.) // -// @export Meteor.Error Meteor.Error = Meteor.makeErrorType( "Meteor.Error", function (error, reason, details) { diff --git a/packages/meteor/fiber_helpers.js b/packages/meteor/fiber_helpers.js index 26f4c61937..4c42e73391 100644 --- a/packages/meteor/fiber_helpers.js +++ b/packages/meteor/fiber_helpers.js @@ -2,7 +2,6 @@ var path = Npm.require('path'); var Fiber = Npm.require('fibers'); var Future = Npm.require(path.join('fibers', 'future')); -// @export Meteor._noYieldsAllowed Meteor._noYieldsAllowed = function (f) { // "Fiber" and "yield" are both in the global namespace. The yield function is // at both "yield" and "Fiber.yield". (It's also at require('fibers').yield @@ -37,7 +36,6 @@ Meteor._noYieldsAllowed = function (f) { // XXX could maybe use the npm 'schlock' module instead, which would // also support multiple concurrent "read" tasks // -// @export Meteor._SynchronousQueue Meteor._SynchronousQueue = function () { var self = this; // List of tasks to run (not including a currently-running task if any). Each @@ -172,7 +170,6 @@ _.extend(Meteor._SynchronousQueue.prototype, { // Sleep. Mostly used for debugging (eg, inserting latency into server // methods). // -// @export Meteor._sleepForMs Meteor._sleepForMs = function (ms) { var fiber = Fiber.current; setTimeout(function() { diff --git a/packages/meteor/fiber_stubs_client.js b/packages/meteor/fiber_stubs_client.js index 75b571beb2..3babd0e08f 100644 --- a/packages/meteor/fiber_stubs_client.js +++ b/packages/meteor/fiber_stubs_client.js @@ -3,7 +3,6 @@ // The client has no ability to yield, so noYieldsAllowed is a noop. // -// @export Meteor._noYieldsAllowed Meteor._noYieldsAllowed = function (f) { return f(); }; @@ -11,7 +10,6 @@ Meteor._noYieldsAllowed = function (f) { // An even simpler queue of tasks than the fiber-enabled one. This one just // runs all the tasks when you call runTask or flush, synchronously. // -// @export Meteor._SynchronousQueue Meteor._SynchronousQueue = function () { var self = this; self._tasks = []; diff --git a/packages/meteor/helpers.js b/packages/meteor/helpers.js index 054fd0e8dc..0804eff775 100644 --- a/packages/meteor/helpers.js +++ b/packages/meteor/helpers.js @@ -1,7 +1,6 @@ if (Meteor.isServer) var Future = Npm.require('fibers/future'); -// @export Meteor.release if (typeof __meteor_runtime_config__ === 'object' && __meteor_runtime_config__.meteorRelease) Meteor.release = __meteor_runtime_config__.meteorRelease; @@ -13,7 +12,6 @@ _.extend(Meteor, { // _get(a,b,c,d) returns a[b][c][d], or else undefined if a[b] or // a[b][c] doesn't exist. // - // @export Meteor._get _get: function (obj /*, arguments */) { for (var i = 1; i < arguments.length; i++) { if (!(arguments[i] in obj)) @@ -26,7 +24,6 @@ _.extend(Meteor, { // _ensure(a,b,c,d) ensures that a[b][c][d] exists. If it does not, // it is created and set to {}. Either way, it is returned. // - // @export Meteor._ensure _ensure: function (obj /*, arguments */) { for (var i = 1; i < arguments.length; i++) { var key = arguments[i]; @@ -41,7 +38,6 @@ _.extend(Meteor, { // _delete(a, b, c, d) deletes a[b][c][d], then a[b][c] unless it // isn't empty, then a[b] unless it isn't empty. // - // @export Meteor._delete _delete: function (obj /*, arguments */) { var stack = [obj]; var leaf = true; @@ -78,7 +74,6 @@ _.extend(Meteor, { // For maximum effectiveness and least confusion, wrapAsync should be used on // functions where the callback is the only argument of type Function. // - // @export Meteor._wrapAsync _wrapAsync: function (fn) { return function (/* arguments */) { var self = this; diff --git a/packages/meteor/package.js b/packages/meteor/package.js index cada378e34..f05f8684d0 100644 --- a/packages/meteor/package.js +++ b/packages/meteor/package.js @@ -13,6 +13,8 @@ Package._transitional_registerBuildPlugin({ Package.on_use(function (api) { api.use('underscore', ['client', 'server']); + api.exportSymbol('Meteor'); + api.add_files('client_environment.js', 'client'); api.add_files('server_environment.js', 'server'); api.add_files('helpers.js', ['client', 'server']); diff --git a/packages/meteor/server_environment.js b/packages/meteor/server_environment.js index 12581f47f2..2146a87e39 100644 --- a/packages/meteor/server_environment.js +++ b/packages/meteor/server_environment.js @@ -1,10 +1,8 @@ -// @export Meteor.isClient -Meteor.isClient = false; +Meteor = { + isClient: false, + isServer: true +}; -// @export Meteor.isServer -Meteor.isServer = true; - -// @export Meteor.settings Meteor.settings = {}; if (process.env.METEOR_SETTINGS) { try { diff --git a/packages/meteor/setimmediate.js b/packages/meteor/setimmediate.js index f58231de57..8cf75fbdfc 100644 --- a/packages/meteor/setimmediate.js +++ b/packages/meteor/setimmediate.js @@ -135,7 +135,6 @@ function useTimeout() { } -// @export Meteor._setImmediate Meteor._setImmediate = useSetImmediate() || usePostMessage() || diff --git a/packages/meteor/startup_client.js b/packages/meteor/startup_client.js index 8ca9d0e4f3..4c4fe9863d 100644 --- a/packages/meteor/startup_client.js +++ b/packages/meteor/startup_client.js @@ -19,7 +19,6 @@ if (document.addEventListener) { window.attachEvent('load', ready); } -// @export Meteor.startup Meteor.startup = function (cb) { var doScroll = !document.addEventListener && document.documentElement.doScroll; diff --git a/packages/meteor/startup_server.js b/packages/meteor/startup_server.js index 09735334b0..af0ca8126c 100644 --- a/packages/meteor/startup_server.js +++ b/packages/meteor/startup_server.js @@ -1,4 +1,3 @@ -// @export Meteor.startup Meteor.startup = function (callback) { __meteor_bootstrap__.startup_hooks.push(callback); }; diff --git a/packages/meteor/timers.js b/packages/meteor/timers.js index b7366a7546..68411cbaf5 100644 --- a/packages/meteor/timers.js +++ b/packages/meteor/timers.js @@ -21,22 +21,18 @@ _.extend(Meteor, { // inside a server method are not part of the method invocation and // should clear out the CurrentInvocation environment variable. - // @export Meteor.setTimeout setTimeout: function (f, duration) { return setTimeout(bindAndCatch("setTimeout callback", f), duration); }, - // @export Meteor.setInterval setInterval: function (f, duration) { return setInterval(bindAndCatch("setInterval callback", f), duration); }, - // @export Meteor.clearInterval clearInterval: function(x) { return clearInterval(x); }, - // @export Meteor.clearTimeout clearTimeout: function(x) { return clearTimeout(x); }, @@ -45,7 +41,6 @@ _.extend(Meteor, { // Deps.afterFlush or Node's nextTick (in practice). Then tests can do: // callSomethingThatDefersSomeWork(); // Meteor.defer(expect(somethingThatValidatesThatTheWorkHappened)); - // @export Meteor.defer defer: function (f) { Meteor._setImmediate(bindAndCatch("defer callback", f)); } diff --git a/packages/meteor/url_common.js b/packages/meteor/url_common.js index ef0f53467e..9fa045a3d5 100644 --- a/packages/meteor/url_common.js +++ b/packages/meteor/url_common.js @@ -1,4 +1,3 @@ -// @export Meteor.absoluteUrl Meteor.absoluteUrl = function (path, options) { // path is optional if (!options && typeof path === 'object') { @@ -42,7 +41,6 @@ if (typeof __meteor_runtime_config__ === "object" && Meteor.absoluteUrl.defaultOptions.rootUrl = __meteor_runtime_config__.ROOT_URL; -// @export Meteor._relativeToSiteRootUrl Meteor._relativeToSiteRootUrl = function (link) { if (typeof __meteor_runtime_config__ === "object" && link.substr(0, 1) === "/") diff --git a/packages/minifiers/minifiers.js b/packages/minifiers/minifiers.js index 6b0eaf4608..efd8ce0a30 100644 --- a/packages/minifiers/minifiers.js +++ b/packages/minifiers/minifiers.js @@ -1,5 +1,2 @@ -// @export CleanCSSProcess CleanCSSProcess = Npm.require('clean-css').process; - -// @export UglifyJSMinify UglifyJSMinify = Npm.require('uglify-js').minify; diff --git a/packages/minifiers/package.js b/packages/minifiers/package.js index a4ddc279b2..89c5be1f57 100644 --- a/packages/minifiers/package.js +++ b/packages/minifiers/package.js @@ -10,5 +10,6 @@ Npm.depends({ }); Package.on_use(function (api) { + api.exportSymbol(['CleanCSSProcess', 'UglifyJSMinify']); api.add_files('minifiers.js', 'server'); }); diff --git a/packages/minimongo/minimongo.js b/packages/minimongo/minimongo.js index 88d3f12e49..18267e2c9f 100644 --- a/packages/minimongo/minimongo.js +++ b/packages/minimongo/minimongo.js @@ -7,7 +7,6 @@ // LiveResultsSet: the return value of a live query. -// @export LocalCollection LocalCollection = function (name) { this.name = name; this.docs = {}; // _id -> document (also containing id) diff --git a/packages/minimongo/package.js b/packages/minimongo/package.js index 32215bcd73..2efabb381e 100644 --- a/packages/minimongo/package.js +++ b/packages/minimongo/package.js @@ -4,6 +4,7 @@ Package.describe({ }); Package.on_use(function (api) { + api.exportSymbol('LocalCollection'); api.use(['underscore', 'json', 'ejson', 'ordered-dict', 'deps', 'random', 'ordered-dict']); api.add_files([ diff --git a/packages/mongo-livedata/collection.js b/packages/mongo-livedata/collection.js index a458cbf66d..d2b903b1ab 100644 --- a/packages/mongo-livedata/collection.js +++ b/packages/mongo-livedata/collection.js @@ -1,7 +1,6 @@ // options.connection, if given, is a LivedataClient or LivedataServer // XXX presently there is no way to destroy/clean up a Collection -// @export Meteor.Collection Meteor.Collection = function (name, options) { var self = this; if (! (self instanceof Meteor.Collection)) diff --git a/packages/mongo-livedata/mongo_driver.js b/packages/mongo-livedata/mongo_driver.js index 1904e608cb..e39d691f11 100644 --- a/packages/mongo-livedata/mongo_driver.js +++ b/packages/mongo-livedata/mongo_driver.js @@ -7,6 +7,8 @@ * these outside of a fiber they will explode! */ +_MongoLivedataTest = {}; + var path = Npm.require('path'); var MongoDB = Npm.require('mongodb'); var Fiber = Npm.require('fibers'); @@ -1077,5 +1079,4 @@ MongoConnection.prototype._observeChangesTailable = function ( // XXX We probably need to find a better way to expose this. Right now // it's only used by tests, but in fact you need it in normal // operation to interact with capped collections. -// @export _MongoLivedataTest.MongoTimestamp _MongoLivedataTest.MongoTimestamp = MongoDB.Timestamp; diff --git a/packages/mongo-livedata/package.js b/packages/mongo-livedata/package.js index fa782ea451..e8e91fa65d 100644 --- a/packages/mongo-livedata/package.js +++ b/packages/mongo-livedata/package.js @@ -20,6 +20,8 @@ Package.on_use(function (api) { ['client', 'server']); api.use('check', ['client', 'server']); + api.exportSymbol('_MongoLivedataTest'); + api.add_files('mongo_driver.js', 'server'); api.add_files('local_collection_driver.js', ['client', 'server']); api.add_files('remote_collection_driver.js', 'server'); diff --git a/packages/oauth/oauth_client.js b/packages/oauth/oauth_client.js index cbe25a3664..2b3c2a61a0 100644 --- a/packages/oauth/oauth_client.js +++ b/packages/oauth/oauth_client.js @@ -1,6 +1,7 @@ +Oauth = {}; + // Open a popup window pointing to a OAuth handshake page // -// @export Oauth.initiateLogin // @param credentialToken {String} The OAuth credentialToken generated by the client // @param url {String} url to page // @param credentialRequestCompleteCallback {Function} Callback function to call on diff --git a/packages/oauth/oauth_server.js b/packages/oauth/oauth_server.js index c3db701577..aa78475742 100644 --- a/packages/oauth/oauth_server.js +++ b/packages/oauth/oauth_server.js @@ -1,5 +1,8 @@ var Fiber = Npm.require('fibers'); +Oauth = {}; +_OauthTest = {}; + RoutePolicy.declare('/_oauth/', 'network'); var registeredServices = {}; @@ -8,7 +11,6 @@ var registeredServices = {}; // 'oauth1' and 'oauth2' packages manipulate this directly to register // for callbacks. // -// @export Oauth._requestHandlers Oauth._requestHandlers = {}; @@ -28,7 +30,6 @@ Oauth._requestHandlers = {}; // up in the user's services[name] field // - `null` if the user declined to give permissions // -// @export Oauth.registerService Oauth.registerService = function (name, version, urls, handleOauthRequest) { if (registeredServices[name]) throw new Error("Already registered the " + name + " OAuth service"); @@ -42,7 +43,6 @@ Oauth.registerService = function (name, version, urls, handleOauthRequest) { }; // For test cleanup. -// @export _OauthTest.unregisterService _OauthTest.unregisterService = function (name) { delete registeredServices[name]; }; @@ -58,15 +58,12 @@ _OauthTest.unregisterService = function (name) { // // XXX we should periodically clear old entries // -// @export Oauth._loginResultForCredentialToken Oauth._loginResultForCredentialToken = {}; -// @export Oauth.hasCredential Oauth.hasCredential = function(credentialToken) { return _.has(Oauth._loginResultForCredentialToken, credentialToken); } -// @export Oauth.retrieveCredential Oauth.retrieveCredential = function(credentialToken) { var result = Oauth._loginResultForCredentialToken[credentialToken]; delete Oauth._loginResultForCredentialToken[credentialToken]; @@ -132,7 +129,6 @@ middleware = function (req, res, next) { } }; -// @export _OauthTest.middleware _OauthTest.middleware = middleware; // Handle /_oauth/* paths and extract the service name @@ -162,7 +158,6 @@ var ensureConfigured = function(serviceName) { }; // Internal: used by the oauth1 and oauth2 packages -// @export Oauth._renderOauthResults Oauth._renderOauthResults = function(res, query) { // We support ?close and ?redirect=URL. Any other query should // just serve a blank page diff --git a/packages/oauth/package.js b/packages/oauth/package.js index 0526040f11..ad92dc44a0 100644 --- a/packages/oauth/package.js +++ b/packages/oauth/package.js @@ -8,6 +8,9 @@ Package.on_use(function (api) { api.use('webapp', 'server'); api.use(['underscore', 'service-configuration'], 'server'); + api.exportSymbol('Oauth'); + api.exportSymbol('_OauthTest', 'server'); + api.add_files('oauth_client.js', 'client'); api.add_files('oauth_server.js', 'server'); }); diff --git a/packages/oauth1/oauth1_binding.js b/packages/oauth1/oauth1_binding.js index 699e52a9b2..59c0f2b211 100644 --- a/packages/oauth1/oauth1_binding.js +++ b/packages/oauth1/oauth1_binding.js @@ -1,7 +1,6 @@ var crypto = Npm.require("crypto"); var querystring = Npm.require("querystring"); -// @export OAuth1Binding // An OAuth1 wrapper around http calls which helps get tokens and // takes care of HTTP headers // diff --git a/packages/oauth1/oauth1_server.js b/packages/oauth1/oauth1_server.js index 4e5d8c8994..c6736de237 100644 --- a/packages/oauth1/oauth1_server.js +++ b/packages/oauth1/oauth1_server.js @@ -1,8 +1,7 @@ // A place to store request tokens pending verification var requestTokens = {}; -// @export _Oauth1Test.requestTokens -_Oauth1Test.requestTokens = requestTokens; +_Oauth1Test = {requestTokens: requestTokens}; // connect middleware Oauth._requestHandlers['1'] = function (service, query, res) { diff --git a/packages/oauth1/package.js b/packages/oauth1/package.js index ff4ee2563d..16219388ae 100644 --- a/packages/oauth1/package.js +++ b/packages/oauth1/package.js @@ -9,6 +9,9 @@ Package.on_use(function (api) { api.use('oauth', ['client', 'server']); api.use('underscore', 'server'); + api.exportSymbol('Oauth1Binding'); + api.exportSymbol('_Oauth1Test'); + api.add_files('oauth1_binding.js', 'server'); api.add_files('oauth1_server.js', 'server'); }); diff --git a/packages/ordered-dict/ordered_dict.js b/packages/ordered-dict/ordered_dict.js index c757f5d3a7..702c65a2ea 100644 --- a/packages/ordered-dict/ordered_dict.js +++ b/packages/ordered-dict/ordered_dict.js @@ -1,4 +1,3 @@ -// @export OrderedDict // This file defines an ordered dictionary abstraction that is useful for // maintaining a dataset backed by observeChanges. It supports ordering items // by specifying the item they now come before. diff --git a/packages/ordered-dict/package.js b/packages/ordered-dict/package.js index d7e4136f8e..8ae80661cd 100644 --- a/packages/ordered-dict/package.js +++ b/packages/ordered-dict/package.js @@ -5,5 +5,6 @@ Package.describe({ Package.on_use(function (api) { api.use('underscore'); + api.exportSymbol('OrderedDict'); api.add_files('ordered_dict.js', ['client', 'server']); }); diff --git a/packages/past/past.js b/packages/past/past.js index 302737d3de..479573ada5 100644 --- a/packages/past/past.js +++ b/packages/past/past.js @@ -6,8 +6,6 @@ // Old under_score version of camelCase public API names. // -// @export Meteor.is_client -// @export Meteor.is_server Meteor.is_client = Meteor.isClient; Meteor.is_server = Meteor.isServer; @@ -19,19 +17,15 @@ Meteor.is_server = Meteor.isServer; // We used to require a special "autosubscribe" call to reactively subscribe to // things. Now, it works with autorun. // -// @export Meteor.autosubscribe Meteor.autosubscribe = Deps.autorun; // "new deps" back-compat // -// @export Meteor.flush -// @export Meteor.autorun Meteor.flush = Deps.flush; Meteor.autorun = Deps.autorun; // Deps API that briefly existed in 0.5.9 // -// @export Deps.depend Deps.depend = function (d) { return d.depend(); }; @@ -39,7 +33,6 @@ Deps.depend = function (d) { // Instead of the "random" package with Random.id(), we used to have this // Meteor.uuid() implementing the RFC 4122 v4 UUID. // -// @export Meteor.uuid Meteor.uuid = function () { var HEX_DIGITS = "0123456789abcdef"; var s = []; diff --git a/packages/random/package.js b/packages/random/package.js index 848a8d8f41..4525c8be22 100644 --- a/packages/random/package.js +++ b/packages/random/package.js @@ -5,6 +5,7 @@ Package.describe({ Package.on_use(function (api) { api.use('underscore'); + api.exportSymbol('Random'); api.add_files('random.js'); }); diff --git a/packages/random/random.js b/packages/random/random.js index 0e02f3ca73..5faddb9d4c 100644 --- a/packages/random/random.js +++ b/packages/random/random.js @@ -149,7 +149,6 @@ var pid = (typeof process !== 'undefined' && process.pid) || 1; // XXX On the server, use the crypto module (OpenSSL) instead of this PRNG. // (Make Random.fraction be generated from Random.hexString instead of the // other way around, and generate Random.hexString from crypto.randomBytes.) -// @export Random Random = create([ new Date(), height, width, agent, pid, Math.random() ]); diff --git a/packages/reactive-dict/package.js b/packages/reactive-dict/package.js index c403a859b7..286aaa7d36 100644 --- a/packages/reactive-dict/package.js +++ b/packages/reactive-dict/package.js @@ -5,6 +5,7 @@ Package.describe({ Package.on_use(function (api) { api.use(['underscore', 'deps', 'ejson']); + api.exportSymbol('ReactiveDict'); api.add_files('reactive-dict.js'); }); diff --git a/packages/reactive-dict/reactive-dict.js b/packages/reactive-dict/reactive-dict.js index 52467279e5..c0458c6edd 100644 --- a/packages/reactive-dict/reactive-dict.js +++ b/packages/reactive-dict/reactive-dict.js @@ -11,8 +11,8 @@ var parse = function (serialized) { return EJSON.parse(serialized); }; -// migrationData, if present, should be data previously returned from getMigrationData() -// @export ReactiveDict +// migrationData, if present, should be data previously returned from +// getMigrationData() ReactiveDict = function (migrationData) { this.keys = migrationData || {}; // key -> value this.keyDeps = {}; // key -> Dependency diff --git a/packages/reload/package.js b/packages/reload/package.js index 8716bfdc81..f0e52f8306 100644 --- a/packages/reload/package.js +++ b/packages/reload/package.js @@ -5,5 +5,6 @@ Package.describe({ Package.on_use(function (api) { api.use(['underscore', 'logging', 'json'], 'client'); + api.exportSymbol('Reload', 'client'); api.add_files('reload.js', 'client'); }); diff --git a/packages/reload/reload.js b/packages/reload/reload.js index 59d54c6530..2c2f412e7e 100644 --- a/packages/reload/reload.js +++ b/packages/reload/reload.js @@ -71,6 +71,8 @@ var providers = []; ////////// External API ////////// +Reload = {}; + // Packages that support migration should register themselves by // calling this function. When it's time to migrate, callback will // be called with one argument, the "retry function." If the package @@ -85,7 +87,6 @@ var providers = []; // ready this time, then the migration will happen. name must be set if there // is migration data. // -// @export Reload._onMigrate Reload._onMigrate = function (name, callback) { if (!callback) { // name not provided, so first arg is callback. @@ -98,7 +99,6 @@ Reload._onMigrate = function (name, callback) { // Called by packages when they start up. // Returns the object that was saved, or undefined if none saved. // -// @export Reload._migrationData Reload._migrationData = function (name) { return old_data[name]; }; @@ -109,7 +109,6 @@ Reload._migrationData = function (name) { // will happen at some point in the future once all of the packages // are ready to migrate. // -// @export Reload._reload var reloading = false; Reload._reload = function () { if (reloading) diff --git a/packages/routepolicy/package.js b/packages/routepolicy/package.js index b9d4514ce3..8b3db515bc 100644 --- a/packages/routepolicy/package.js +++ b/packages/routepolicy/package.js @@ -8,6 +8,8 @@ Package.on_use(function (api) { // Resolve circular dependency with webapp. We can only use WebApp via // Package.webapp and only after initial load. api.use('webapp', 'server', {unordered: true}); + api.exportSymbol('RoutePolicy', 'server'); + api.exportSymbol('_RoutePolicyTest', 'server'); api.add_files('routepolicy.js', 'server'); }); diff --git a/packages/routepolicy/routepolicy.js b/packages/routepolicy/routepolicy.js index 04edb5122a..f617a60b02 100644 --- a/packages/routepolicy/routepolicy.js +++ b/packages/routepolicy/routepolicy.js @@ -24,14 +24,14 @@ // routes would break tinytest... so allow policy instances to be // constructed for testing. -// XXX export is the only way to share with our tests. annoying! -// @export _RoutePolicyConstructor -_RoutePolicyConstructor = function () { +_RoutePolicyTest = {}; + +var RoutePolicyConstructor = _RoutePolicyTest.Constructor = function () { var self = this; self.urlPrefixTypes = {}; }; -_.extend(_RoutePolicyConstructor.prototype, { +_.extend(RoutePolicyConstructor.prototype, { urlPrefixMatches: function (urlPrefix, url) { return url.substr(0, urlPrefix.length) === urlPrefix; @@ -121,5 +121,4 @@ _.extend(_RoutePolicyConstructor.prototype, { } }); -// @export RoutePolicy -RoutePolicy = new _RoutePolicyConstructor(); +RoutePolicy = new RoutePolicyConstructor(); diff --git a/packages/routepolicy/routepolicy_tests.js b/packages/routepolicy/routepolicy_tests.js index c6efdfe6fc..a4fe220072 100644 --- a/packages/routepolicy/routepolicy_tests.js +++ b/packages/routepolicy/routepolicy_tests.js @@ -1,5 +1,5 @@ Tinytest.add("routepolicy - declare", function (test) { - var policy = new _RoutePolicyConstructor(); + var policy = new _RoutePolicyTest.Constructor(); policy.declare('/sockjs/', 'network'); policy.declare('/bigphoto.jpg', 'static-online'); @@ -37,7 +37,7 @@ Tinytest.add("routepolicy - static conflicts", function (test) { "url": "/bigphoto.jpg" } ]; - var policy = new _RoutePolicyConstructor(); + var policy = new _RoutePolicyTest.Constructor(); test.equal( policy.checkForConflictWithStatic('/sockjs/', 'network', manifest), @@ -51,7 +51,7 @@ Tinytest.add("routepolicy - static conflicts", function (test) { }); Tinytest.add("routepolicy - checkUrlPrefix", function (test) { - var policy = new _RoutePolicyConstructor(); + var policy = new _RoutePolicyTest.Constructor(); policy.declare('/sockjs/', 'network'); test.equal( diff --git a/packages/service-configuration/package.js b/packages/service-configuration/package.js index a18e1f8363..d7e4bee97e 100644 --- a/packages/service-configuration/package.js +++ b/packages/service-configuration/package.js @@ -5,5 +5,6 @@ Package.describe({ Package.on_use(function(api) { api.use('mongo-livedata', ['client', 'server']); + api.exportSymbol('ServiceConfiguration'); api.add_files('service_configuration_common.js', ['client', 'server']); }); diff --git a/packages/service-configuration/service_configuration_common.js b/packages/service-configuration/service_configuration_common.js index e9349565a5..42d5cf75a6 100644 --- a/packages/service-configuration/service_configuration_common.js +++ b/packages/service-configuration/service_configuration_common.js @@ -1,5 +1,4 @@ if (typeof ServiceConfiguration === 'undefined') { - // @export ServiceConfiguration ServiceConfiguration = {}; } diff --git a/packages/session/package.js b/packages/session/package.js index 7a879cf945..4ed5c4a905 100644 --- a/packages/session/package.js +++ b/packages/session/package.js @@ -11,6 +11,7 @@ Package.on_use(function (api) { // the session. api.use('reload', 'client', {weak: true}); + api.exportSymbol('Session', 'client'); api.add_files('session.js', 'client'); }); diff --git a/packages/session/session.js b/packages/session/session.js index 0520843c0d..9426008d1a 100644 --- a/packages/session/session.js +++ b/packages/session/session.js @@ -6,7 +6,6 @@ if (Package.reload) { } } -// @export Session Session = new ReactiveDict(migratedKeys); if (Package.reload) { diff --git a/packages/spark/convenience.js b/packages/spark/convenience.js index eec6f993ff..e4b3c3f81a 100644 --- a/packages/spark/convenience.js +++ b/packages/spark/convenience.js @@ -1,4 +1,3 @@ -// @export Meteor.render Meteor.render = function (htmlFunc) { return Spark.render(function () { return Spark.isolate( @@ -9,7 +8,6 @@ Meteor.render = function (htmlFunc) { }); }; -// @export Meteor.renderList Meteor.renderList = function (cursor, itemFunc, elseFunc) { return Spark.render(function () { return Spark.list(cursor, function (item) { diff --git a/packages/spark/package.js b/packages/spark/package.js index 4c5223dbe8..cad3ed626b 100644 --- a/packages/spark/package.js +++ b/packages/spark/package.js @@ -8,6 +8,9 @@ Package.on_use(function (api) { 'ordered-dict', 'deps', 'ejson'], 'client'); + api.exportSymbol('Spark'); + api.exportSymbol('_SparkTest'); + api.add_files(['spark.js', 'patch.js', 'convenience.js', 'utils.js'], 'client'); }); diff --git a/packages/spark/patch.js b/packages/spark/patch.js index fa79f28189..7c4375dba1 100644 --- a/packages/spark/patch.js +++ b/packages/spark/patch.js @@ -571,5 +571,4 @@ Patcher._copyAttributes = function(tgt, src) { } }; -// @export _SparkTest.Patcher _SparkTest.Patcher = Patcher; diff --git a/packages/spark/spark.js b/packages/spark/spark.js index 157b44b25a..bb616111aa 100644 --- a/packages/spark/spark.js +++ b/packages/spark/spark.js @@ -25,6 +25,8 @@ // timer' button again. the problem is almost certainly in afterFlush // (not hard to see what it is.) +Spark = {}; +_SparkTest = {}; var currentRenderer = (function () { var current = null; @@ -42,7 +44,6 @@ var currentRenderer = (function () { })(); TAG = "_spark_" + Random.id(); -// @export _SparkTest.TAG _SparkTest.TAG = TAG; // We also export this as Spark._TAG due to a historical accident. I @@ -50,7 +51,6 @@ _SparkTest.TAG = TAG; // stuff?) but let's keep exporting it since without it it would be // very difficult for code outside the spark package to, eg, walk // spark's liverange hierarchy. -// @export Spark._TAG Spark._TAG = TAG; // XXX document contract for each type of annotation? @@ -66,14 +66,12 @@ var ANNOTATION_LIST_ITEM = "item"; // XXX why do we need, eg, _ANNOTATION_ISOLATE? it has no semantics? // Use from tests to turn on extra UniversalEventListener sanity checks -// @export _SparkTest.setCheckIECompliance var checkIECompliance = false; _SparkTest.setCheckIECompliance = function (value) { checkIECompliance = value; }; // Private interface to 'preserve-inputs' package -// @export Spark._addGlobalPreserve var globalPreserves = {}; Spark._addGlobalPreserve = function (selector, value) { globalPreserves[selector] = value; @@ -426,7 +424,6 @@ var scheduleOnscreenSetup = function (frag, landmarkRanges) { }); }; -// @export Spark.render Spark.render = function (htmlFunc) { var renderer = new Renderer; var frag = renderer.materialize(htmlFunc); @@ -552,7 +549,6 @@ var pathForRange = function (r) { // `range` is a region of `document`. Modify it in-place so that it // matches the result of Spark.render(htmlFunc), preserving landmarks. // -// @export Spark.renderToRange Spark.renderToRange = function (range, htmlFunc) { // `range` may be out-of-document and we don't check here. // XXX should we? @@ -677,7 +673,6 @@ Spark.renderToRange = function (range, htmlFunc) { // and `end`, and call their 'finalize' function if any. Or instead of // `start` and `end` you may pass a fragment in `start`. // -// @export Spark.finalize Spark.finalize = function (start, end) { if (! start.parentNode && start.nodeType !== 11 /* DocumentFragment */) { // Workaround for LiveRanges' current inability to contain @@ -698,13 +693,11 @@ Spark.finalize = function (start, end) { /* Data contexts */ /******************************************************************************/ -// @export Spark.setDataContext Spark.setDataContext = withRenderer(function (dataContext, html, _renderer) { return _renderer.annotate( html, ANNOTATION_DATA, { data: dataContext }); }); -// @export Spark.getDataContext Spark.getDataContext = function (node) { var range = findRangeOfType(ANNOTATION_DATA, node); return range && range.data; @@ -745,7 +738,6 @@ var getListener = function () { return universalListener; }; -// @export Spark.attachEvents Spark.attachEvents = withRenderer(function (eventMap, html, _renderer) { var listener = getListener(); @@ -861,7 +853,6 @@ Spark.attachEvents = withRenderer(function (eventMap, html, _renderer) { /* Isolate */ /******************************************************************************/ -// @export Spark.isolate Spark.isolate = function (htmlFunc) { var renderer = currentRenderer.get(); if (!renderer) @@ -925,7 +916,6 @@ if (typeof LocalCollection !== 'undefined') { idStringify = function (id) { return id; }; } -// @export Spark.list Spark.list = function (cursor, itemFunc, elseFunc) { elseFunc = elseFunc || function () { return ''; }; @@ -1100,7 +1090,6 @@ Spark.list = function (cursor, itemFunc, elseFunc) { var nextLandmarkId = 1; -// @export Spark.Landmark Spark.Landmark = function () { this.id = nextLandmarkId++; this._range = null; // will be set when put onscreen @@ -1128,14 +1117,12 @@ _.extend(Spark.Landmark.prototype, { } }); -// @export Spark.UNIQUE_LABEL Spark.UNIQUE_LABEL = ['UNIQUE_LABEL']; // label must be a string. // or pass label === null to not drop a label after all (meaning that // this function is a noop) // -// @export Spark.labelBranch Spark.labelBranch = function (label, htmlFunc) { var renderer = currentRenderer.get(); if (! renderer || label === null) @@ -1172,7 +1159,6 @@ Spark.labelBranch = function (label, htmlFunc) { // nodes?) }; -// @export Spark.createLandmark Spark.createLandmark = function (options, htmlFunc) { var renderer = currentRenderer.get(); if (! renderer) { @@ -1258,7 +1244,6 @@ Spark.createLandmark = function (options, htmlFunc) { }); }; -// @export _SparkTest.getEnclosingLandmark _SparkTest.getEnclosingLandmark = function (node) { var range = findRangeOfType(ANNOTATION_LANDMARK, node); return range ? range.landmark : null; diff --git a/packages/spark/utils.js b/packages/spark/utils.js index 4d51b82fc3..5d7d1137c3 100644 --- a/packages/spark/utils.js +++ b/packages/spark/utils.js @@ -1,4 +1,3 @@ -// @export Spark._labelFromIdOrName Spark._labelFromIdOrName = function(n) { var label = null; diff --git a/packages/srp/package.js b/packages/srp/package.js index 6216f24019..7df7cab4dc 100644 --- a/packages/srp/package.js +++ b/packages/srp/package.js @@ -6,6 +6,7 @@ Package.describe({ Package.on_use(function (api) { api.use(['random', 'check'], ['client', 'server']); api.use('underscore'); + api.exportSymbol('SRP'); api.add_files(['biginteger.js', 'sha256.js', 'srp.js'], ['client', 'server']); }); diff --git a/packages/srp/srp.js b/packages/srp/srp.js index 3645054357..e04860d577 100644 --- a/packages/srp/srp.js +++ b/packages/srp/srp.js @@ -1,3 +1,5 @@ +SRP = {}; + /////// PUBLIC CLIENT /** @@ -10,7 +12,6 @@ * testing. Random UUID if not provided. * - SRP parameters (see _defaults and paramsFromOptions below) */ -// @export SRP.generateVerifier SRP.generateVerifier = function (password, options) { var params = paramsFromOptions(options); @@ -30,7 +31,6 @@ SRP.generateVerifier = function (password, options) { }; // For use with check(). -// @export SRP.matchVerifier SRP.matchVerifier = { identity: String, salt: String, @@ -47,7 +47,6 @@ SRP.matchVerifier = { * passed in for testing. * - SRP parameters (see _defaults and paramsFromOptions below) */ -// @export SRP.Client SRP.Client = function (password, options) { var self = this; self.params = paramsFromOptions(options); @@ -174,7 +173,6 @@ SRP.Client.prototype.verifyConfirmation = function (confirmation) { * passed in for testing. * - SRP parameters (see _defaults and paramsFromOptions below) */ -// @export SRP.Server SRP.Server = function (verifier, options) { var self = this; self.params = paramsFromOptions(options); diff --git a/packages/star-translate/package.js b/packages/star-translate/package.js index 3b48baad7b..7d2212e9f4 100644 --- a/packages/star-translate/package.js +++ b/packages/star-translate/package.js @@ -4,6 +4,7 @@ Package.describe({ Package.on_use(function (api) { api.use(['dev-bundle-fetcher']); + api.exportSymbol('StarTranslator'); api.add_files(['translator.js'], 'server'); }); diff --git a/packages/star-translate/translator.js b/packages/star-translate/translator.js index 563da8e625..55220198cc 100644 --- a/packages/star-translate/translator.js +++ b/packages/star-translate/translator.js @@ -2,7 +2,6 @@ var fs = Npm.require('fs'); var path = Npm.require('path'); var ncp = Npm.require('ncp').ncp; -//@export StarTranslator StarTranslator = {}; // Produces a star version of bundlePath in translatedPath, where bundlePath can diff --git a/packages/templating/deftemplate.js b/packages/templating/deftemplate.js index a5a6f9292e..ad27795451 100644 --- a/packages/templating/deftemplate.js +++ b/packages/templating/deftemplate.js @@ -1,4 +1,3 @@ -// @export Template Template = {}; var registeredPartials = {}; diff --git a/packages/templating/package.js b/packages/templating/package.js index 100f519fc8..e101233c6b 100644 --- a/packages/templating/package.js +++ b/packages/templating/package.js @@ -23,6 +23,8 @@ Package.on_use(function (api) { api.use(['underscore', 'spark', 'handlebars'], 'client'); + api.exportSymbol('Template', 'client'); + // provides the runtime logic to instantiate our templates api.add_files('deftemplate.js', 'client'); diff --git a/packages/test-helpers/async_multi.js b/packages/test-helpers/async_multi.js index cc7d1ca84b..ad831def4b 100644 --- a/packages/test-helpers/async_multi.js +++ b/packages/test-helpers/async_multi.js @@ -109,7 +109,6 @@ _.extend(ExpectationManager.prototype, { } }); -// @export testAsyncMulti testAsyncMulti = function (name, funcs) { // XXX Tests on remote browsers are _slow_. We need a better solution. var timeout = 180000; @@ -153,7 +152,6 @@ testAsyncMulti = function (name, funcs) { }); }; -// @export pollUntil pollUntil = function (expect, f, timeout, step, noFail) { noFail = noFail || false; step = step || 100; diff --git a/packages/test-helpers/callback_logger.js b/packages/test-helpers/callback_logger.js index fd864a81d1..8ac1c2575b 100644 --- a/packages/test-helpers/callback_logger.js +++ b/packages/test-helpers/callback_logger.js @@ -10,7 +10,6 @@ var Fiber = Meteor.isServer ? Npm.require('fibers') : null; var TIMEOUT = 1000; -// @export withCallbackLogger // Run the given function, passing it a correctly-set-up callback logger as an // argument. If we're meant to be running asynchronously, the function gets its // own Fiber. diff --git a/packages/test-helpers/canonicalize_html.js b/packages/test-helpers/canonicalize_html.js index d3a8a91442..633c97cce8 100644 --- a/packages/test-helpers/canonicalize_html.js +++ b/packages/test-helpers/canonicalize_html.js @@ -1,4 +1,3 @@ -// @export canonicalizeHtml canonicalizeHtml = function(html) { var h = html; // kill IE-specific comments inserted by Spark diff --git a/packages/test-helpers/current_style.js b/packages/test-helpers/current_style.js index cfe8906f43..c6d2e6e76d 100644 --- a/packages/test-helpers/current_style.js +++ b/packages/test-helpers/current_style.js @@ -1,4 +1,3 @@ -// @export getStyleProperty // Cross-browser implementation of getting the computed style of an element. getStyleProperty = function(n, prop) { if (n.currentStyle) { diff --git a/packages/test-helpers/event_simulation.js b/packages/test-helpers/event_simulation.js index 6a45aa1b2c..a2865d62cd 100644 --- a/packages/test-helpers/event_simulation.js +++ b/packages/test-helpers/event_simulation.js @@ -1,4 +1,3 @@ -// @export simulateEvent simulateEvent = function (node, event, args) { node = (node instanceof $ ? node[0] : node); @@ -14,7 +13,6 @@ simulateEvent = function (node, event, args) { } }; -// @export focusElement focusElement = function(elem) { // This sequence is for benefit of IE 8 and 9; // test there before changing. @@ -27,14 +25,12 @@ focusElement = function(elem) { throw new Error("focus() didn't set activeElement"); }; -// @export blurElement blurElement = function(elem) { elem.blur(); if (document.activeElement === elem) throw new Error("blur() didn't affect activeElement"); }; -// @export clickElement clickElement = function(elem) { if (elem.click) elem.click(); // supported by form controls cross-browser; most native way diff --git a/packages/test-helpers/onscreendiv.js b/packages/test-helpers/onscreendiv.js index 246f02c9f2..dc0296761b 100644 --- a/packages/test-helpers/onscreendiv.js +++ b/packages/test-helpers/onscreendiv.js @@ -1,4 +1,3 @@ -// @export OnscreenDiv // OnscreenDiv is an object that appends a DIV to the document // body and keeps track of it, providing methods that query it, // mutate, and destroy it. diff --git a/packages/test-helpers/package.js b/packages/test-helpers/package.js index 523912c88d..3c594268ce 100644 --- a/packages/test-helpers/package.js +++ b/packages/test-helpers/package.js @@ -8,6 +8,12 @@ Package.on_use(function (api) { 'domutils']); api.use(['spark'], 'client'); + api.exportSymbol([ + 'pollUntil', 'WrappedFrag', 'permutations', 'StubStream', 'SeededRandom', + 'ReactiveVar', 'OnscreenDiv', 'clickElement', 'blurElement', 'focusElement', + 'simulateEvent', 'getStyleProperty', 'canonicalizeHtml', + 'withCallbackLogger', 'testAsyncMulti']); + api.add_files('try_all_permutations.js'); api.add_files('async_multi.js'); api.add_files('event_simulation.js'); diff --git a/packages/test-helpers/reactivevar.js b/packages/test-helpers/reactivevar.js index eeb6eb07d0..7a4184da8c 100644 --- a/packages/test-helpers/reactivevar.js +++ b/packages/test-helpers/reactivevar.js @@ -12,7 +12,6 @@ // Constructor, with optional 'new': // var R = [new] ReactiveVar([initialValue]) -// @export ReactiveVar ReactiveVar = function(initialValue) { if (! (this instanceof ReactiveVar)) return new ReactiveVar(initialValue); diff --git a/packages/test-helpers/seeded_random.js b/packages/test-helpers/seeded_random.js index fcc4315801..0094a20258 100644 --- a/packages/test-helpers/seeded_random.js +++ b/packages/test-helpers/seeded_random.js @@ -1,4 +1,3 @@ -// @export SeededRandom SeededRandom = function(seed) { // seed may be a string or any type if (! (this instanceof SeededRandom)) return new SeededRandom(seed); diff --git a/packages/test-helpers/stub_stream.js b/packages/test-helpers/stub_stream.js index 8aa8c973c5..1442024819 100644 --- a/packages/test-helpers/stub_stream.js +++ b/packages/test-helpers/stub_stream.js @@ -1,4 +1,3 @@ -// @export StubStream StubStream = function () { var self = this; diff --git a/packages/test-helpers/try_all_permutations.js b/packages/test-helpers/try_all_permutations.js index 59d0981ef5..2276c10a01 100644 --- a/packages/test-helpers/try_all_permutations.js +++ b/packages/test-helpers/try_all_permutations.js @@ -31,7 +31,6 @@ // try_all_permutations([X], [A, B], [Y]) // try_all_permutations(X, [A, B], Y) -// @export try_all_permutations try_all_permutations = function () { var args = Array.prototype.slice.call(arguments); diff --git a/packages/test-helpers/wrappedfrag.js b/packages/test-helpers/wrappedfrag.js index 34a52bcd7e..1023890cea 100644 --- a/packages/test-helpers/wrappedfrag.js +++ b/packages/test-helpers/wrappedfrag.js @@ -1,4 +1,3 @@ -// @export WrappedFrag // A WrappedFrag provides utility methods pertaining to a given // DocumentFragment that are helpful in tests. For example, // WrappedFrag(frag).html() constructs a sort of cross-browser diff --git a/packages/test-in-console/driver.js b/packages/test-in-console/driver.js index aa581a47a7..cbbd26b354 100644 --- a/packages/test-in-console/driver.js +++ b/packages/test-in-console/driver.js @@ -3,7 +3,6 @@ DONE = false; // Failure count for phantomjs exit code FAILURES = null; -// @export TEST_STATUS TEST_STATUS = { DONE: false, FAILURES: null diff --git a/packages/test-in-console/package.js b/packages/test-in-console/package.js index 0fca2a1962..35c00fcac5 100644 --- a/packages/test-in-console/package.js +++ b/packages/test-in-console/package.js @@ -8,12 +8,10 @@ Package.on_use(function (api) { api.use(['tinytest', 'underscore', 'random', 'ejson', 'check']); api.use('http'); - api.add_files([ - 'driver.js' - ], "client"); - api.add_files([ - 'reporter.js' - ], "server"); + api.exportSymbol('TEST_STATUS', 'client'); + + api.add_files(['driver.js'], "client"); + api.add_files(['reporter.js'], "server"); // This is to be run by phantomjs, not as part of normal package code. api.add_files('runner.js', 'server', {isAsset: true}); diff --git a/packages/tinytest/package.js b/packages/tinytest/package.js index fb71b7ea9d..964214a2d0 100644 --- a/packages/tinytest/package.js +++ b/packages/tinytest/package.js @@ -12,6 +12,8 @@ Package.on_use(function (api) { api.use('underscore', ['client', 'server']); api.use('random', ['client', 'server']); + api.exportSymbol('Tinytest'); + api.add_files('tinytest.js', ['client', 'server']); api.use('livedata', ['client', 'server']); diff --git a/packages/tinytest/tinytest.js b/packages/tinytest/tinytest.js index a4aab57d6b..9e6efab35b 100644 --- a/packages/tinytest/tinytest.js +++ b/packages/tinytest/tinytest.js @@ -491,12 +491,12 @@ _.extend(TestRun.prototype, { /* Public API */ /******************************************************************************/ -// @export Tinytest.add +Tinytest = {}; + Tinytest.add = function (name, func) { TestManager.addCase(new TestCase(name, func)); }; -// @export Tinytest.addAsync Tinytest.addAsync = function (name, func) { TestManager.addCase(new TestCase(name, func, true)); }; @@ -506,7 +506,6 @@ Tinytest.addAsync = function (name, func) { // server, and likewise for the client.) Report results via // onReport. Call onComplete when it's done. // -// @export Tinytest._runTests Tinytest._runTests = function (onReport, onComplete, pathPrefix) { var testRun = TestManager.createRun(onReport, pathPrefix); testRun.run(onComplete); @@ -516,7 +515,6 @@ Tinytest._runTests = function (onReport, onComplete, pathPrefix) { // error, all as indicated by 'cookie', which will have come from a // failure event output by _runTests. // -// @export Tinytest._debugTest Tinytest._debugTest = function (cookie, onReport, onComplete) { var testRun = TestManager.createRun(onReport); testRun.debug(cookie, onComplete); diff --git a/packages/tinytest/tinytest_client.js b/packages/tinytest/tinytest_client.js index debebb953e..1e38015e72 100644 --- a/packages/tinytest/tinytest_client.js +++ b/packages/tinytest/tinytest_client.js @@ -2,7 +2,6 @@ // the server. Sets a 'server' flag on test results that came from the // server. // -// @export Tinytest._runTestsEverywhere Tinytest._runTestsEverywhere = function (onReport, onComplete, pathPrefix) { var runId = Random.id(); var localComplete = false; diff --git a/packages/twitter/package.js b/packages/twitter/package.js index 2b84387234..0152feaa66 100644 --- a/packages/twitter/package.js +++ b/packages/twitter/package.js @@ -14,6 +14,8 @@ Package.on_use(function(api) { api.use('underscore', 'server'); api.use('service-configuration', ['client', 'server']); + api.exportSymbol('Twitter'); + api.add_files( ['twitter_configure.html', 'twitter_configure.js'], 'client'); diff --git a/packages/twitter/twitter_client.js b/packages/twitter/twitter_client.js index 86ca7205f2..73b02f9232 100644 --- a/packages/twitter/twitter_client.js +++ b/packages/twitter/twitter_client.js @@ -1,5 +1,6 @@ +Twitter = {}; + // Request Twitter credentials for the user -// @export Twitter.requestCredential // @param options {optional} XXX support options.requestPermissions // @param credentialRequestCompleteCallback {Function} Callback function to call on // completion. Takes one argument, credentialToken on success, or Error on diff --git a/packages/twitter/twitter_server.js b/packages/twitter/twitter_server.js index 12c5391df5..ce05ea0119 100644 --- a/packages/twitter/twitter_server.js +++ b/packages/twitter/twitter_server.js @@ -1,3 +1,5 @@ +Twitter = {}; + var urls = { requestToken: "https://api.twitter.com/oauth/request_token", authorize: "https://api.twitter.com/oauth/authorize", @@ -7,7 +9,6 @@ var urls = { // https://dev.twitter.com/docs/api/1.1/get/account/verify_credentials -// @export Twitter.whitelistedFields Twitter.whitelistedFields = ['profile_image_url', 'profile_image_url_https', 'lang']; Oauth.registerService('twitter', 1, urls, function(oauthBinding) { @@ -35,7 +36,6 @@ Oauth.registerService('twitter', 1, urls, function(oauthBinding) { }); -// @export Twitter.retrieveCredential Twitter.retrieveCredential = function(credentialToken) { return Oauth.retrieveCredential(credentialToken); }; diff --git a/packages/universal-events/listener.js b/packages/universal-events/listener.js index 0218f8f3a5..b6863d9d72 100644 --- a/packages/universal-events/listener.js +++ b/packages/universal-events/listener.js @@ -160,8 +160,6 @@ var typeCounts = {}; // in browsers that don't require it. In other words, when the flag // is set, modern browsers will require the same API calls as IE <= // 8. This is only used for tests and is private for now. -// -// @export UniversalEventListener UniversalEventListener = function (handler, _checkIECompliance) { this.handler = handler; this.types = {}; // map from event type name to 'true' diff --git a/packages/universal-events/package.js b/packages/universal-events/package.js index 24df93ba5a..bf29d659fd 100644 --- a/packages/universal-events/package.js +++ b/packages/universal-events/package.js @@ -5,7 +5,7 @@ Package.describe({ Package.on_use(function (api) { api.use(['underscore'], 'client'); - + api.exportSymbol('UniversalEventListener', 'client'); api.add_files(['listener.js', 'events-w3c.js', 'events-ie.js'], 'client'); diff --git a/packages/webapp/package.js b/packages/webapp/package.js index 351762533a..5e8584ece5 100644 --- a/packages/webapp/package.js +++ b/packages/webapp/package.js @@ -8,7 +8,8 @@ Npm.depends({connect: "2.7.10", useragent: "2.0.1"}); Package.on_use(function (api) { - // XXX: Refactor so as not to have to use ctl-helper api.use(['logging', 'underscore', 'routepolicy'], 'server'); + api.exportSymbol('WebApp'); + api.exportSymbol('main'); api.add_files('webapp_server.js', 'server'); }); diff --git a/packages/webapp/webapp_server.js b/packages/webapp/webapp_server.js index adb8fd9645..3086d2e452 100644 --- a/packages/webapp/webapp_server.js +++ b/packages/webapp/webapp_server.js @@ -16,8 +16,6 @@ var send = Npm.require('send'); // it (for example, it has an attribute httpServer which isn't known // until runWebAppServer-time.) It would be nice to refactor so that // this isn't the case and we can export the symbols individually. -// -// @export WebApp WebApp = {}; var findGalaxy = _.once(function () { @@ -424,7 +422,6 @@ var runWebAppServer = function () { // Let the rest of the packages (and Meteor.startup hooks) insert connect // middlewares and update __meteor_runtime_config__, then keep going to set up // actually serving HTML. - // @export main main = function (argv) { argv = optimist(argv).boolean('keepalive').argv; diff --git a/packages/weibo/package.js b/packages/weibo/package.js index 8b3b4f01a5..923fcf4eb0 100644 --- a/packages/weibo/package.js +++ b/packages/weibo/package.js @@ -13,6 +13,8 @@ Package.on_use(function(api) { api.use('random', 'client'); api.use('service-configuration', ['client', 'server']); + api.exportSymbol('Weibo'); + api.add_files( ['weibo_configure.html', 'weibo_configure.js'], 'client'); diff --git a/packages/weibo/weibo_client.js b/packages/weibo/weibo_client.js index 545d225f13..f5ab30c787 100644 --- a/packages/weibo/weibo_client.js +++ b/packages/weibo/weibo_client.js @@ -1,5 +1,6 @@ +Weibo = {}; + // Request Weibo credentials for the user -// @export Weibo.requestCredential // @param options {optional} // @param credentialRequestCompleteCallback {Function} Callback function to call on // completion. Takes one argument, credentialToken on success, or Error on diff --git a/packages/weibo/weibo_server.js b/packages/weibo/weibo_server.js index 709620299d..2699a16e04 100644 --- a/packages/weibo/weibo_server.js +++ b/packages/weibo/weibo_server.js @@ -1,3 +1,5 @@ +Weibo = {}; + Oauth.registerService('weibo', 2, null, function(query) { var response = getTokenResponse(query); @@ -68,7 +70,6 @@ var getIdentity = function (accessToken, userId) { } }; -// @export Weibo.retrieveCredential Weibo.retrieveCredential = function(credentialToken) { return Oauth.retrieveCredential(credentialToken); -}; \ No newline at end of file +}; diff --git a/tools/linker.js b/tools/linker.js index 7299c6ba3c..b0e4451121 100644 --- a/tools/linker.js +++ b/tools/linker.js @@ -267,7 +267,6 @@ var File = function (inputFile, module) { // The Module containing this file. self.module = module; - // symbols mentioned in @export, @require, @provide, or @weak // directives. each is a map from the symbol (given as a string) to // true. (only @export is actually implemented) self.exports = {}; diff --git a/tools/packages.js b/tools/packages.js index 012760b52f..c4ed64e918 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -1435,6 +1435,17 @@ _.extend(Package.prototype, { // one. #OldStylePackageSupport _.each(["use", "test"], function (role) { if (roleHandlers[role]) { + var toArray = function (x) { + if (x instanceof Array) + return x; + return x ? [x] : []; + }; + var toWhereArray = function (where) { + if (where instanceof Array) + return where; + return where ? [where] : ['client', 'server']; + }; + var api = { // Called when this package wants to make another package be // used. Can also take literal package objects, if you have @@ -1474,11 +1485,8 @@ _.extend(Package.prototype, { } options = options || {}; - if (!(names instanceof Array)) - names = names ? [names] : []; - - if (!(where instanceof Array)) - where = where ? [where] : ["client", "server"]; + names = toArray(names); + where = toWhereArray(where); // A normal dependency creates an ordering constraint and a "if I'm // used, use that" constraint. Unordered dependencies lack the @@ -1517,11 +1525,8 @@ _.extend(Package.prototype, { return; } - if (!(names instanceof Array)) - names = names ? [names] : []; - - if (!(where instanceof Array)) - where = where ? [where] : ["client", "server"]; + names = toArray(names); + where = toWhereArray(where); _.each(names, function (name) { _.each(where, function (w) { @@ -1538,11 +1543,8 @@ _.extend(Package.prototype, { // be processed according to its extension (eg, *.coffee // files will be compiled to JavaScript.) add_files: function (paths, where, fileOptions) { - if (!(paths instanceof Array)) - paths = paths ? [paths] : []; - - if (!(where instanceof Array)) - where = where ? [where] : ["client", "server"]; + paths = toArray(paths); + where = toWhereArray(where); _.each(paths, function (path) { _.each(where, function (w) { @@ -1568,11 +1570,8 @@ _.extend(Package.prototype, { // recover by ignoring return; } - if (!(symbols instanceof Array)) - symbols = symbols ? [symbols] : []; - - if (!(where instanceof Array)) - where = where ? [where] : []; + symbols = toArray(symbols); + where = toWhereArray(where); _.each(symbols, function (symbol) { _.each(where, function (w) { diff --git a/tools/tests/app-with-private/packages/test-package/package.js b/tools/tests/app-with-private/packages/test-package/package.js index 6ee9466b04..081da956f6 100644 --- a/tools/tests/app-with-private/packages/test-package/package.js +++ b/tools/tests/app-with-private/packages/test-package/package.js @@ -5,5 +5,6 @@ Package._transitional_registerBuildPlugin({ }); Package.on_use(function (api) { + api.exportSymbol('TestAsset', 'server'); api.add_files(['test-package.js', 'test-package.txt', 'test.notregistered'], 'server'); }); diff --git a/tools/tests/app-with-private/packages/test-package/test-package.js b/tools/tests/app-with-private/packages/test-package/test-package.js index 77c1461c19..9ad080938a 100644 --- a/tools/tests/app-with-private/packages/test-package/test-package.js +++ b/tools/tests/app-with-private/packages/test-package/test-package.js @@ -1,4 +1,3 @@ -// @export TestAsset TestAsset = {}; if (Meteor.isServer) { From 75cad725e2519b1e48c2cc86e872f5042348b535 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Tue, 23 Jul 2013 19:45:35 -0700 Subject: [PATCH 076/175] first stab at dropping the @export comment parser --- tools/linker.js | 152 ++++++++-------------------------------------- tools/packages.js | 56 +++++++---------- 2 files changed, 47 insertions(+), 161 deletions(-) diff --git a/tools/linker.js b/tools/linker.js index b0e4451121..550a4b8d13 100644 --- a/tools/linker.js +++ b/tools/linker.js @@ -14,8 +14,8 @@ var packageDot = function (name) { // Module /////////////////////////////////////////////////////////////////////////////// -// options include name, imports, forceExport, useGlobalNamespace, -// combinedServePath, importStubServePath, and noExports, all of which have the +// options include name, imports, exports, useGlobalNamespace, +// combinedServePath, and importStubServePath, all of which have the // same meaning as they do when passed to import(). var Module = function (options) { var self = this; @@ -27,11 +27,10 @@ var Module = function (options) { self.files = []; // options - self.forceExport = options.forceExport || []; + self.exports = options.exports || []; self.useGlobalNamespace = options.useGlobalNamespace; self.combinedServePath = options.combinedServePath; self.importStubServePath = options.importStubServePath; - self.noExports = !!options.noExports; self.jsAnalyze = options.jsAnalyze; }; @@ -60,10 +59,10 @@ _.extend(Module.prototype, { return _.max(maxInFile); }, - runLinkerFileTransforms: function (exports) { + runLinkerFileTransforms: function () { var self = this; _.each(self.files, function (f) { - f.runLinkerFileTransform(exports); + f.runLinkerFileTransform(self.exports); }); }, @@ -77,17 +76,14 @@ _.extend(Module.prototype, { // and see what your globals are. Probably this means we need to // move the emission of the Package-scope Variables section (but not // the actual static analysis) to the final phase. - computeModuleScopeVars: function (exports) { + computeModuleScopeVars: function () { var self = this; if (!self.jsAnalyze) { // We don't have access to static analysis, probably because we *are* the - // js-analyze package. Let's do a stupid heuristic: any exports that have - // no dots are module scope vars. (This works for - // js-analyze.JSAnalyze...) - return _.filter(exports, function (e) { - return e.indexOf('.') === -1; - }); + // js-analyze package. Let's do a stupid heuristic: the exports are + // the only module scope vars. (This works for js-analyze.JSAnalyze...) + return self.exports; } // Find all global references in any files @@ -102,7 +98,7 @@ _.extend(Module.prototype, { // Output is a list of objects with keys 'source', 'servePath', 'sourceMap', // 'sourcePath' - getPrelinkedFiles: function (moduleExports) { + getPrelinkedFiles: function () { var self = this; if (! self.files.length) @@ -113,8 +109,7 @@ _.extend(Module.prototype, { // preserving the line numbers. if (self.useGlobalNamespace) { return _.map(self.files, function (file) { - var node = file.getPrelinkedOutput({ preserveLineNumbers: true, - exports: moduleExports }); + var node = file.getPrelinkedOutput({ preserveLineNumbers: true }); var results = node.toStringWithSourceMap({ file: file.servePath }); // results has 'code' and 'map' attributes @@ -146,8 +141,7 @@ _.extend(Module.prototype, { _.each(self.files, function (file) { if (!_.isEmpty(chunks)) chunks.push("\n\n\n\n\n\n"); - chunks.push(file.getPrelinkedOutput({ sourceWidth: sourceWidth, - exports: moduleExports })); + chunks.push(file.getPrelinkedOutput({ sourceWidth: sourceWidth }); }); var node = new sourcemap.SourceNode(null, null, null, chunks); @@ -160,21 +154,6 @@ _.extend(Module.prototype, { servePath: self.combinedServePath, sourceMap: results.map.toString() }]; - }, - - // Return our exports as a list of string - getExports: function () { - var self = this; - - if (self.noExports) - return []; - - var exports = {}; - _.each(self.files, function (file) { - _.extend(exports, file.exports); - }); - - return _.union(_.keys(exports), self.forceExport); } }); @@ -266,15 +245,6 @@ var File = function (inputFile, module) { // The Module containing this file. self.module = module; - - // directives. each is a map from the symbol (given as a string) to - // true. (only @export is actually implemented) - self.exports = {}; - self.requires = {}; - self.provides = {}; - self.weaks = {}; - - self._scanForComments(); }; _.extend(File.prototype, { @@ -350,7 +320,6 @@ _.extend(File.prototype, { // numbers don't change between input and output. In this case, // sourceWidth is ignored. // - sourceWidth: width in columns to use for the source code - // - exports: the module's exports // // Returns a SourceNode. getPrelinkedOutput: function (options) { @@ -466,65 +435,6 @@ _.extend(File.prototype, { node.setSourceContent(self._pathForSourceMap(), self.source); return node; - }, - - // If "line" contains nothing but a comment (of either syntax), return the - // body of the comment with leading and trailing spaces trimmed (possibly the - // empty string). Otherwise return null. (We need to support both comment - // syntaxes because the CoffeeScript compiler only emits /**/ comments.) - _getSingleLineCommentBody: function (line) { - var self = this; - var match = /^\s*\/\/(.+)$/.exec(line); - if (match) { - return match[1].trim(); - } - match = /^\s*\/\*(.+)\*\/\s*$/.exec(line); - // Make sure we don't get tricked by lines like - // /* Comment */ var myRegexp = /x*/ - if (match && match[1].indexOf('*/') === -1) - return match[1].trim(); - return null; - }, - - // Scan for @export, etc. - _scanForComments: function () { - var self = this; - var lines = self.source.split("\n"); - - _.each(lines, function (line) { - var commentBody = self._getSingleLineCommentBody(line); - if (!commentBody) - return; - - // XXX overly permissive. should detect errors - var match = /^@(export|require|provide|weak)(\s+.*)$/.exec(commentBody); - if (match) { - var what = match[1]; - var symbols = _.map(match[2].split(/,/), function (s) { - return s.trim(); - }); - - var badSymbols = _.reject(symbols, function (s) { - // XXX should be unicode-friendlier - return s.match(/^([_$a-zA-Z][_$a-zA-Z0-9]*)(\.[_$a-zA-Z][_$a-zA-Z0-9]*)*$/); - }); - if (!_.isEmpty(badSymbols)) { - buildmessage.error("bad symbols for @" + what + ": " + - JSON.stringify(badSymbols), - { file: self.sourcePath }); - // recover by ignoring - } else if (self.module.noExports && what === "export") { - buildmessage.error("@export not allowed in this slice", - { file: self.sourcePath }); - // recover by ignoring - } else { - _.each(symbols, function (s) { - if (s.length) - self[what + "s"][s] = true; - }); - } - } - }); } }); @@ -584,8 +494,7 @@ var bannerPadding = function (bannerWidth) { // and an array of the exports of the module; the file's source will // be replaced by what the function returns. // -// forceExport: an array of symbols (as dotted strings) to force the -// module to export, even if it wouldn't otherwise +// exports: an array of symbols that the module exports // // useGlobalNamespace: make the top level namespace be the same as the // global namespace, so that symbols are accessible from the @@ -599,9 +508,6 @@ var bannerPadding = function (bannerWidth) { // containing import setup code for the global environment. this is // the servePath to use for it. // -// noExports: if true, the module does not export anything (even an empty -// Package.foo object). eg, for test slices. -// // jsAnalyze: if possible, the JSAnalyze object from the js-analyze // package. (This is not possible if we are currently linking the main slice of // the js-analyze package!) @@ -610,20 +516,14 @@ var bannerPadding = function (bannerWidth) { // - files: is an array of output files in the same format as inputFiles // - EXCEPT THAT, for now, sourcePath is omitted and is replaced with // sourceMap (a string) (XXX) -// - exports: the exports, as a list of string ('Foo', 'Thing.Stuff', etc) +// - packageScopeVariables: an array of package-scope variables var prelink = function (options) { - if (options.noExports && options.forceExport && - ! _.isEmpty(options.forceExport)) { - throw new Error("Can't force exports if there are no exports!"); - }; - var module = new Module({ name: options.name, - forceExport: options.forceExport, + exports: options.exports, useGlobalNamespace: options.useGlobalNamespace, importStubServePath: options.importStubServePath, combinedServePath: options.combinedServePath, - noExports: !!options.noExports, jsAnalyze: options.jsAnalyze }); @@ -631,22 +531,19 @@ var prelink = function (options) { module.addFile(inputFile); }); - // 1) Figure out what this entire module exports. - // 2) Run the linkerFileTransforms, which depend on the exports. (This is, eg, - // CoffeeScript arranging to not close over the exports.) - // 3) Do static analysis to compute module-scoped variables; this has to be + // 1) Run the linkerFileTransforms. (This is, eg, CoffeeScript arranging to + // not close over the exports.) + // 2) Do static analysis to compute module-scoped variables; this has to be // done based on the *output* of the transforms. Error recovery from the // static analysis mutates the sources, so this has to be done before // concatenation. - // 4) Finally, concatenate. - var exports = module.getExports(); - module.runLinkerFileTransforms(exports); - var packageScopeVariables = module.computeModuleScopeVars(exports); - var files = module.getPrelinkedFiles(exports); + // 3) Finally, concatenate. + module.runLinkerFileTransforms(); + var packageScopeVariables = module.computeModuleScopeVars(); + var files = module.getPrelinkedFiles(); return { files: files, - exports: exports, packageScopeVariables: packageScopeVariables }; }; @@ -673,7 +570,8 @@ var SOURCE_MAP_INSTRUCTIONS_COMMENT = banner([ // 'Foo', "Foo.bar", etc) to the module from which it should be // imported (which must load before us at runtime) // -// exports: symbols to export, as an array of symbol names as strings +// exports: symbols to export, as an array of symbol names as strings. +// if null, not even Package.name will be defined. // // useGlobalNamespace: must be the same value that was passed to link() // @@ -789,7 +687,7 @@ var getImportCode = function (imports, header, omitvar, exports) { var getFooter = function (options) { var chunks = []; - if (options.name && options.exports && !_.isEmpty(options.exports)) { + if (options.name && options.exports) { chunks.push("\n\n/* Exports */\n"); chunks.push("if (typeof Package === 'undefined') Package = {};\n"); chunks.push(packageDot(options.name), " = "); diff --git a/tools/packages.js b/tools/packages.js index c4ed64e918..59bb9c4325 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -86,10 +86,9 @@ var rejectBadPath = function (p) { // - uses // - implies // - getSourcesFunc -// - forceExport +// - exports // - dependencyInfo // - nodeModulesPath -// - noExports // // Do not include the source files in dependencyInfo. They will be // added at compile time when the sources are actually read. @@ -151,10 +150,10 @@ var Slice = function (pkg, options) { // local plugins in this package) to compute this. self.getSourcesFunc = options.getSourcesFunc || null; - // Symbols that this slice should export even if @export directives - // don't appear in the source code. List of symbols (as strings.) - // Empty if loaded from unipackage. - self.forceExport = options.forceExport || []; + // Symbols that this slice should export. List of symbols (as strings.) Null + // if this slice should not have any exports and in fact should not even + // define `Package.name` (ie, test slices). + self.exports = options.exports; // Files and directories that we want to monitor for changes in // development mode, such as source files and package.js, in the @@ -165,14 +164,6 @@ var Slice = function (pkg, options) { // Has this slice been compiled? self.isBuilt = false; - // All symbols exported from the JavaScript code in this - // package. Array of string symbol (eg "Foo", "Bar.baz".) Set only - // when isBuilt is true. - self.exports = null; - - // Are we allowed to have exports? (eg, test slices don't export.) - self.noExports = !!options.noExports; - // Prelink output. 'prelinkFiles' is the partially linked JavaScript code (an // array of objects with keys 'source' and 'servePath', both strings -- see // prelink() in linker.js) 'packageScopeVariables' are are variables that are @@ -489,8 +480,7 @@ _.extend(Slice.prototype, { "/packages/" + self.pkg.name + (self.sliceName === "main" ? "" : ("." + self.sliceName)) + ".js", name: self.pkg.name || null, - forceExport: self.forceExport, - noExports: self.noExports, + exports: self.exports, jsAnalyze: jsAnalyze }); @@ -511,7 +501,6 @@ _.extend(Slice.prototype, { }); self.prelinkFiles = results.files; - self.exports = results.exports; self.packageScopeVariables = results.packageScopeVariables; self.resources = resources; self.isBuilt = true; @@ -1406,9 +1395,8 @@ _.extend(Package.prototype, { var sources = {use: {client: [], server: []}, test: {client: [], server: []}}; - // symbols force-exported - var forceExport = {use: {client: [], server: []}, - test: {client: [], server: []}}; + // symbols exported + var exports = {client: [], server: []}; // packages used and implied (keys are 'spec', 'unordered', and 'weak'). an // "implied" package is a package that will be used by a slice which uses @@ -1556,12 +1544,9 @@ _.extend(Package.prototype, { }); }, - // Force the export of a symbol from this package. An - // alternative to using @export directives. Possibly helpful - // when you don't want to modify the source code of a third - // party library. + // Export symbols from this package. // - // @param symbols String (eg "Foo", "Foo.bar") or array of String + // @param symbols String (eg "Foo") or array of String // @param where 'client', 'server', or an array of those exportSymbol: function (symbols, where) { if (role === "test") { @@ -1574,8 +1559,15 @@ _.extend(Package.prototype, { where = toWhereArray(where); _.each(symbols, function (symbol) { + // XXX be unicode-friendlier + if (!symbol.match(/^([_$a-zA-Z][_$a-zA-Z0-9]*)$/)) { + buildmessage.error("Bad exported symbol: " + symbol, + { useMyCaller: true }); + // recover by ignoring + return; + } _.each(where, function (w) { - forceExport[role][w].push(symbol); + exports[w].push(symbol); }); }); }, @@ -1691,13 +1683,9 @@ _.extend(Package.prototype, { uses: uses[role][where], implies: role === "use" && implies[where] || undefined, getSourcesFunc: function () { return sources[role][where]; }, - forceExport: forceExport[role][where], + exports: role === "use" ? exports[where] : null, dependencyInfo: dependencyInfo, - nodeModulesPath: arch === nativeArch && nodeModulesPath || undefined, - // test slices don't get used by other packages, so they have nothing - // to export. (And notably, they should NOT stomp on the Package.foo - // object defined by their corresponding use slice.) - noExports: role === "test" + nodeModulesPath: arch === nativeArch && nodeModulesPath || undefined })); }); }); @@ -2021,7 +2009,7 @@ _.extend(Package.prototype, { }); slice.isBuilt = true; - slice.exports = sliceJson.exports || []; + slice.exports = sliceJson.exports || null; slice.packageScopeVariables = sliceJson.packageScopeVariables || []; slice.prelinkFiles = []; slice.resources = []; @@ -2209,7 +2197,7 @@ _.extend(Package.prototype, { // Construct slice metadata var sliceJson = { format: "unipackage-slice-pre1", - exports: slice.exports, + exports: slice.exports || undefined, packageScopeVariables: slice.packageScopeVariables, uses: _.map(slice.uses, function (u) { var specParts = u.spec.split('.'); From 0cbbfb4d5e1f56b1536f6b202d0f18e40599fb55 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Tue, 23 Jul 2013 19:46:09 -0700 Subject: [PATCH 077/175] update awssum --- scripts/admin/publish-release/packages/awssum | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/admin/publish-release/packages/awssum b/scripts/admin/publish-release/packages/awssum index 2b10fd8291..a038e042cf 160000 --- a/scripts/admin/publish-release/packages/awssum +++ b/scripts/admin/publish-release/packages/awssum @@ -1 +1 @@ -Subproject commit 2b10fd82914d83285f03f756fa0239617e60c407 +Subproject commit a038e042cfac3aff307d031dadc4eedd84f08a78 From 060151dd8187f6c1c190733ba79e8fbfe14bbe41 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Tue, 23 Jul 2013 20:17:12 -0700 Subject: [PATCH 078/175] test-packages now has no immediate server-side errors (client-side, sure) eliminate accounts-urls package --- packages/accounts-base/localstorage_token.js | 15 +++++---------- packages/accounts-base/package.js | 6 ++++-- .../url_client.js | 8 +++++--- .../url_server.js | 2 ++ packages/accounts-ui-unstyled/package.js | 2 +- packages/accounts-urls/.gitignore | 1 - packages/accounts-urls/package.js | 9 --------- .../audit_argument_checks.js | 2 +- packages/http/httpcall_common.js | 3 +++ packages/livedata/crossbar.js | 6 +++--- packages/livedata/crossbar_tests.js | 10 +++++----- packages/livedata/livedata_common.js | 1 - packages/livedata/livedata_connection.js | 2 +- packages/livedata/livedata_server.js | 6 +++--- packages/livedata/package.js | 4 ++-- packages/livedata/server_convenience.js | 4 ++-- packages/livedata/stream_client_common.js | 1 + packages/livedata/writefence.js | 6 +++--- packages/logging/.npm/package/npm-shrinkwrap.json | 2 +- packages/mongo-livedata/mongo_driver.js | 8 ++++---- packages/mongo-livedata/mongo_livedata_tests.js | 12 ++++++------ packages/oauth1/package.js | 2 +- packages/spark/package.js | 4 ++-- packages/test-helpers/package.js | 6 +++--- tools/linker.js | 2 +- tools/packages.js | 2 +- 26 files changed, 60 insertions(+), 66 deletions(-) rename packages/{accounts-urls => accounts-base}/url_client.js (93%) rename packages/{accounts-urls => accounts-base}/url_server.js (94%) delete mode 100644 packages/accounts-urls/.gitignore delete mode 100644 packages/accounts-urls/package.js diff --git a/packages/accounts-base/localstorage_token.js b/packages/accounts-base/localstorage_token.js index 6a9a6da010..91205a6742 100644 --- a/packages/accounts-base/localstorage_token.js +++ b/packages/accounts-base/localstorage_token.js @@ -3,6 +3,8 @@ // seconds to synchronize login state between multiple tabs in the same // browser. +var lastLoginTokenWhenPolled; + // Login with a Meteor access token. This is the only public function // here. Meteor.loginWithToken = function (token, callback) { @@ -11,13 +13,6 @@ Meteor.loginWithToken = function (token, callback) { userCallback: callback}); }; -var autoLoginEnabled = true; - -// Semi-internal API. Call at startup to prevent automatic login. -Acocunts._disableAutoLogin = function () { - autoLoginEnabled = false; -}; - // Semi-internal API. Call this function to re-enable auto login after // if it was disabled at startup. Accounts._enableAutoLogin = function () { @@ -63,11 +58,11 @@ unstoreLoginToken = function() { // This is private, but it is exported for now because it is used by a // test in accounts-password. // -storedLoginToken = Accounts._storedLoginToken = function() { +var storedLoginToken = Accounts._storedLoginToken = function() { return Meteor._localStorage.getItem(loginTokenKey); }; -storedUserId = function() { +var storedUserId = function() { return Meteor._localStorage.getItem(userIdKey); }; @@ -97,7 +92,7 @@ if (autoLoginEnabled) { // Poll local storage every 3 seconds to login if someone logged in in // another tab lastLoginTokenWhenPolled = token; -pollStoredLoginToken = function() { +var pollStoredLoginToken = function() { if (! autoLoginEnabled) return; diff --git a/packages/accounts-base/package.js b/packages/accounts-base/package.js index 1eb3f60060..5bb44013fd 100644 --- a/packages/accounts-base/package.js +++ b/packages/accounts-base/package.js @@ -5,7 +5,6 @@ Package.describe({ Package.on_use(function (api) { api.use('underscore', ['client', 'server']); api.use('localstorage', 'client'); - api.use('accounts-urls', ['client', 'server']); api.use('deps', 'client'); api.use('check', 'server'); api.use('random', ['client', 'server']); @@ -26,10 +25,13 @@ Package.on_use(function (api) { api.add_files('accounts_common.js', ['client', 'server']); api.add_files('accounts_server.js', 'server'); + api.add_files('url_client.js', 'client'); + api.add_files('url_server.js', 'server'); // accounts_client must be before localstorage_token, because // localstorage_token attempts to call functions in accounts_client (eg - // Accounts.callLoginMethod) on startup. + // Accounts.callLoginMethod) on startup. And localstorage_token must be after + // url_client, which sets autoLoginEnabled. api.add_files('accounts_client.js', 'client'); api.add_files('localstorage_token.js', 'client'); }); diff --git a/packages/accounts-urls/url_client.js b/packages/accounts-base/url_client.js similarity index 93% rename from packages/accounts-urls/url_client.js rename to packages/accounts-base/url_client.js index b6e5107421..2aad97a986 100644 --- a/packages/accounts-urls/url_client.js +++ b/packages/accounts-base/url_client.js @@ -1,3 +1,5 @@ +autoLoginEnabled = true; + // reads a reset password token from the url's hash fragment, if it's // there. if so prevent automatically logging in since it could be // confusing to be logged in as user A while resetting password for @@ -9,7 +11,7 @@ var match; match = window.location.hash.match(/^\#\/reset-password\/(.*)$/); if (match) { - Accounts._disableAutoLogin(); + autoLoginEnabled = false; Accounts._resetPasswordToken = match[1]; window.location.hash = ''; } @@ -26,7 +28,7 @@ if (match) { // in line with the hash fragment approach) match = window.location.hash.match(/^\#\/verify-email\/(.*)$/); if (match) { - Accounts._disableAutoLogin(); + autoLoginEnabled = false; Accounts._verifyEmailToken = match[1]; window.location.hash = ''; } @@ -36,7 +38,7 @@ if (match) { // reset password links. match = window.location.hash.match(/^\#\/enroll-account\/(.*)$/); if (match) { - Accounts._disableAutoLogin(); + autoLoginEnabled = false; Accounts._enrollAccountToken = match[1]; window.location.hash = ''; } diff --git a/packages/accounts-urls/url_server.js b/packages/accounts-base/url_server.js similarity index 94% rename from packages/accounts-urls/url_server.js rename to packages/accounts-base/url_server.js index 4362c4a208..2aac2cc4fc 100644 --- a/packages/accounts-urls/url_server.js +++ b/packages/accounts-base/url_server.js @@ -1,5 +1,7 @@ // XXX These should probably not actually be public? +Accounts.urls = {}; + Accounts.urls.resetPassword = function (token) { return Meteor.absoluteUrl('#/reset-password/' + token); }; diff --git a/packages/accounts-ui-unstyled/package.js b/packages/accounts-ui-unstyled/package.js index 1acad8282a..e322209ae5 100644 --- a/packages/accounts-ui-unstyled/package.js +++ b/packages/accounts-ui-unstyled/package.js @@ -3,7 +3,7 @@ Package.describe({ }); Package.on_use(function (api) { - api.use(['deps', 'service-configuration', 'accounts-urls', 'accounts-base', + api.use(['deps', 'service-configuration', 'accounts-base', 'underscore', 'templating', 'handlebars', 'spark', 'session'], 'client'); // Export Accounts (etc) to packages using this one. diff --git a/packages/accounts-urls/.gitignore b/packages/accounts-urls/.gitignore deleted file mode 100644 index 677a6fc263..0000000000 --- a/packages/accounts-urls/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.build* diff --git a/packages/accounts-urls/package.js b/packages/accounts-urls/package.js deleted file mode 100644 index e1fd55e3b9..0000000000 --- a/packages/accounts-urls/package.js +++ /dev/null @@ -1,9 +0,0 @@ -Package.describe({ - summary: "Generate and consume reset password and verify account URLs", - internal: true -}); - -Package.on_use(function (api) { - api.add_files('url_client.js', 'client'); - api.add_files('url_server.js', 'server'); -}); diff --git a/packages/audit-argument-checks/audit_argument_checks.js b/packages/audit-argument-checks/audit_argument_checks.js index 4655d7fb41..0c0ad1391a 100644 --- a/packages/audit-argument-checks/audit_argument_checks.js +++ b/packages/audit-argument-checks/audit_argument_checks.js @@ -1 +1 @@ -DDP._setAuditArgumentChecks(true); +DDPServer._setAuditArgumentChecks(true); diff --git a/packages/http/httpcall_common.js b/packages/http/httpcall_common.js index e319196eb2..ddbad0c41c 100644 --- a/packages/http/httpcall_common.js +++ b/packages/http/httpcall_common.js @@ -67,6 +67,9 @@ populateData = function(response) { } }; +// XXX rename to HTTP +Meteor.http = {}; + Meteor.http.get = function (/* varargs */) { return Meteor.http.call.apply(this, ["GET"].concat(_.toArray(arguments))); }; diff --git a/packages/livedata/crossbar.js b/packages/livedata/crossbar.js index 1e4a83aa29..e4c8be26db 100644 --- a/packages/livedata/crossbar.js +++ b/packages/livedata/crossbar.js @@ -1,4 +1,4 @@ -DDP._InvalidationCrossbar = function () { +DDPServer._InvalidationCrossbar = function () { var self = this; self.next_id = 1; @@ -7,7 +7,7 @@ DDP._InvalidationCrossbar = function () { self.listeners = {}; }; -_.extend(DDP._InvalidationCrossbar.prototype, { +_.extend(DDPServer._InvalidationCrossbar.prototype, { // Listen for notification that match 'trigger'. A notification // matches if it has the key-value pairs in trigger as a // subset. When a notification matches, call 'callback', passing two @@ -96,4 +96,4 @@ _.extend(DDP._InvalidationCrossbar.prototype, { }); // singleton -DDP._InvalidationCrossbar = new DDP._InvalidationCrossbar; +DDPServer._InvalidationCrossbar = new DDPServer._InvalidationCrossbar; diff --git a/packages/livedata/crossbar_tests.js b/packages/livedata/crossbar_tests.js index 7d5c01bbdb..d5eed6cedd 100644 --- a/packages/livedata/crossbar_tests.js +++ b/packages/livedata/crossbar_tests.js @@ -6,15 +6,15 @@ // deep meaning to the matching function, and it could be changed later // as long as it preserves that property. Tinytest.add('livedata - crossbar', function (test) { - test.isTrue(DDP._InvalidationCrossbar._matches( + test.isTrue(DDPServer._InvalidationCrossbar._matches( {collection: "C"}, {collection: "C"})); - test.isTrue(DDP._InvalidationCrossbar._matches( + test.isTrue(DDPServer._InvalidationCrossbar._matches( {collection: "C", id: "X"}, {collection: "C"})); - test.isTrue(DDP._InvalidationCrossbar._matches( + test.isTrue(DDPServer._InvalidationCrossbar._matches( {collection: "C"}, {collection: "C", id: "X"})); - test.isTrue(DDP._InvalidationCrossbar._matches( + test.isTrue(DDPServer._InvalidationCrossbar._matches( {collection: "C", id: "X"}, {collection: "C"})); - test.isFalse(DDP._InvalidationCrossbar._matches( + test.isFalse(DDPServer._InvalidationCrossbar._matches( {collection: "C", id: "X"}, {collection: "C", id: "Y"})); }); diff --git a/packages/livedata/livedata_common.js b/packages/livedata/livedata_common.js index fa6ea64abf..b23c8b06c6 100644 --- a/packages/livedata/livedata_common.js +++ b/packages/livedata/livedata_common.js @@ -1,5 +1,4 @@ DDP = {}; -_LivedataTest = {}; SUPPORTED_DDP_VERSIONS = [ 'pre1' ]; diff --git a/packages/livedata/livedata_connection.js b/packages/livedata/livedata_connection.js index 1e6058cf3e..995d8b7fcc 100644 --- a/packages/livedata/livedata_connection.js +++ b/packages/livedata/livedata_connection.js @@ -648,7 +648,7 @@ _.extend(Connection.prototype, { try { // Note that unlike in the corresponding server code, we never audit // that stubs check() their arguments. - var ret = DDP._CurrentInvocation.withValue(invocation,function () { + var ret = DDP._CurrentInvocation.withValue(invocation, function () { if (Meteor.isServer) { // Because saveOriginals and retrieveOriginals aren't reentrant, // don't allow stubs to yield. diff --git a/packages/livedata/livedata_server.js b/packages/livedata/livedata_server.js index 556c2b3b89..77d57231fb 100644 --- a/packages/livedata/livedata_server.js +++ b/packages/livedata/livedata_server.js @@ -556,7 +556,7 @@ _.extend(Session.prototype, { // set up to mark the method as satisfied once all observers // (and subscriptions) have reacted to any writes that were // done. - var fence = new DDP._WriteFence; + var fence = new DDPServer._WriteFence; fence.onAllCommitted(function () { // Retire the fence so that future writes are allowed. // This means that callbacks like timers are free to use @@ -601,7 +601,7 @@ _.extend(Session.prototype, { sessionData: self.sessionData }); try { - var result = DDP._CurrentWriteFence.withValue(fence, function () { + var result = DDPServer._CurrentWriteFence.withValue(fence, function () { return DDP._CurrentInvocation.withValue(invocation, function () { return maybeAuditArgumentChecks( handler, invocation, msg.params, "call to '" + msg.method + "'"); @@ -1371,7 +1371,7 @@ var wrapInternalException = function (exception, context) { // Private interface for 'audit-argument-checks' package. // var shouldAuditArgumentChecks = false; -DDP._setAuditArgumentChecks = function (value) { +DDPServer._setAuditArgumentChecks = function (value) { shouldAuditArgumentChecks = value; }; diff --git a/packages/livedata/package.js b/packages/livedata/package.js index f71324634f..a3ace7c928 100644 --- a/packages/livedata/package.js +++ b/packages/livedata/package.js @@ -34,6 +34,8 @@ Package.on_use(function (api) { api.use('minimongo', ['client', 'server']); + api.add_files('livedata_server.js', 'server'); + api.add_files('writefence.js', 'server'); api.add_files('crossbar.js', 'server'); @@ -41,8 +43,6 @@ Package.on_use(function (api) { api.add_files('livedata_connection.js', ['client', 'server']); - api.add_files('livedata_server.js', 'server'); - api.add_files('client_convenience.js', 'client'); api.add_files('server_convenience.js', 'server'); diff --git a/packages/livedata/server_convenience.js b/packages/livedata/server_convenience.js index 28cf98c66b..50a472d03c 100644 --- a/packages/livedata/server_convenience.js +++ b/packages/livedata/server_convenience.js @@ -16,13 +16,13 @@ if (Package.webapp) { Meteor.server = new Server; Meteor.refresh = function (notification) { - var fence = DDP._CurrentWriteFence.get(); + var fence = DDPServer._CurrentWriteFence.get(); if (fence) { // Block the write fence until all of the invalidations have // landed. var proxy_write = fence.beginWrite(); } - DDP._InvalidationCrossbar.fire(notification, function () { + DDPServer._InvalidationCrossbar.fire(notification, function () { if (proxy_write) proxy_write.committed(); }); diff --git a/packages/livedata/stream_client_common.js b/packages/livedata/stream_client_common.js index 1633535b0d..1ef4165a89 100644 --- a/packages/livedata/stream_client_common.js +++ b/packages/livedata/stream_client_common.js @@ -1,3 +1,4 @@ +_LivedataTest = {}; // XXX from Underscore.String (http://epeli.github.com/underscore.string/) var startsWith = function(str, starts) { diff --git a/packages/livedata/writefence.js b/packages/livedata/writefence.js index 05e2802e8a..3315cbfe3c 100644 --- a/packages/livedata/writefence.js +++ b/packages/livedata/writefence.js @@ -5,7 +5,7 @@ var Future = Npm.require(path.join('fibers', 'future')); // when all of the writes are fully committed and propagated (all // observers have been notified of the write and acknowledged it.) // -DDP._WriteFence = function () { +DDPServer._WriteFence = function () { var self = this; self.armed = false; @@ -19,9 +19,9 @@ DDP._WriteFence = function () { // that writes to databases should register their writes with it using // beginWrite(). // -DDP._CurrentWriteFence = new Meteor.EnvironmentVariable; +DDPServer._CurrentWriteFence = new Meteor.EnvironmentVariable; -_.extend(DDP._WriteFence.prototype, { +_.extend(DDPServer._WriteFence.prototype, { // Start tracking a write, and return an object to represent it. The // object has a single method, committed(). This method should be // called when the write is fully committed and propagated. You can diff --git a/packages/logging/.npm/package/npm-shrinkwrap.json b/packages/logging/.npm/package/npm-shrinkwrap.json index 9ef7e8bdfe..f0eafa1381 100644 --- a/packages/logging/.npm/package/npm-shrinkwrap.json +++ b/packages/logging/.npm/package/npm-shrinkwrap.json @@ -7,7 +7,7 @@ "version": "0.9.2" }, "memoizee": { - "version": "0.2.4", + "version": "0.2.5", "dependencies": { "event-emitter": { "version": "0.2.2" diff --git a/packages/mongo-livedata/mongo_driver.js b/packages/mongo-livedata/mongo_driver.js index e39d691f11..6c8460e951 100644 --- a/packages/mongo-livedata/mongo_driver.js +++ b/packages/mongo-livedata/mongo_driver.js @@ -170,7 +170,7 @@ MongoConnection.prototype._createCappedCollection = function (collectionName, // fence), you should call 'committed()' on the object returned. MongoConnection.prototype._maybeBeginWrite = function () { var self = this; - var fence = DDP._CurrentWriteFence.get(); + var fence = DDPServer._CurrentWriteFence.get(); if (fence) return fence.beginWrite(); else @@ -188,7 +188,7 @@ MongoConnection.prototype._maybeBeginWrite = function () { // After making a write (with insert, update, remove), observers are // notified asynchronously. If you want to receive a callback once all // of the observer notifications have landed for your write, do the -// writes inside a write fence (set DDP._CurrentWriteFence to a new +// writes inside a write fence (set DDPServer._CurrentWriteFence to a new // _WriteFence, and then set a callback on the write fence.) // // Since our execution environment is single-threaded, this is @@ -739,12 +739,12 @@ var LiveResultsSet = function (cursorDescription, mongoHandle, ordered, // database for changes. If this selector specifies specific IDs, specify them // here, so that updates to different specific IDs don't cause us to poll. var listenOnTrigger = function (trigger) { - var listener = DDP._InvalidationCrossbar.listen( + var listener = DDPServer._InvalidationCrossbar.listen( trigger, function (notification, complete) { // When someone does a transaction that might affect us, schedule a poll // of the database. If that transaction happens inside of a write fence, // block the fence until we've polled and notified observers. - var fence = DDP._CurrentWriteFence.get(); + var fence = DDPServer._CurrentWriteFence.get(); if (fence) self._pendingWrites.push(fence.beginWrite()); // Ensure a poll is scheduled... but if we already know that one is, diff --git a/packages/mongo-livedata/mongo_livedata_tests.js b/packages/mongo-livedata/mongo_livedata_tests.js index afbce53379..0f1e6a2bc1 100644 --- a/packages/mongo-livedata/mongo_livedata_tests.js +++ b/packages/mongo-livedata/mongo_livedata_tests.js @@ -124,8 +124,8 @@ Tinytest.addAsync("mongo-livedata - basics, " + idGeneration, function (test, on if (Meteor.isClient) { f(); } else { - var fence = new DDP._WriteFence; - DDP._CurrentWriteFence.withValue(fence, f); + var fence = new DDPServer._WriteFence; + DDPServer._CurrentWriteFence.withValue(fence, f); fence.armAndWait(); } @@ -281,8 +281,8 @@ Tinytest.addAsync("mongo-livedata - fuzz test, " + idGeneration, function(test, if (Meteor.isClient) { f(); } else { - var fence = new DDP._WriteFence; - DDP._CurrentWriteFence.withValue(fence, f); + var fence = new DDPServer._WriteFence; + DDPServer._CurrentWriteFence.withValue(fence, f); fence.armAndWait(); } }; @@ -360,8 +360,8 @@ var runInFence = function (f) { if (Meteor.isClient) { f(); } else { - var fence = new DDP._WriteFence; - DDP._CurrentWriteFence.withValue(fence, f); + var fence = new DDPServer._WriteFence; + DDPServer._CurrentWriteFence.withValue(fence, f); fence.armAndWait(); } }; diff --git a/packages/oauth1/package.js b/packages/oauth1/package.js index 16219388ae..76cf2ddcc4 100644 --- a/packages/oauth1/package.js +++ b/packages/oauth1/package.js @@ -9,7 +9,7 @@ Package.on_use(function (api) { api.use('oauth', ['client', 'server']); api.use('underscore', 'server'); - api.exportSymbol('Oauth1Binding'); + api.exportSymbol('OAuth1Binding'); api.exportSymbol('_Oauth1Test'); api.add_files('oauth1_binding.js', 'server'); diff --git a/packages/spark/package.js b/packages/spark/package.js index cad3ed626b..3088dcbed0 100644 --- a/packages/spark/package.js +++ b/packages/spark/package.js @@ -8,8 +8,8 @@ Package.on_use(function (api) { 'ordered-dict', 'deps', 'ejson'], 'client'); - api.exportSymbol('Spark'); - api.exportSymbol('_SparkTest'); + api.exportSymbol('Spark', 'client'); + api.exportSymbol('_SparkTest', 'client'); api.add_files(['spark.js', 'patch.js', 'convenience.js', 'utils.js'], 'client'); diff --git a/packages/test-helpers/package.js b/packages/test-helpers/package.js index 3c594268ce..4c41cef1d3 100644 --- a/packages/test-helpers/package.js +++ b/packages/test-helpers/package.js @@ -9,9 +9,9 @@ Package.on_use(function (api) { api.use(['spark'], 'client'); api.exportSymbol([ - 'pollUntil', 'WrappedFrag', 'permutations', 'StubStream', 'SeededRandom', - 'ReactiveVar', 'OnscreenDiv', 'clickElement', 'blurElement', 'focusElement', - 'simulateEvent', 'getStyleProperty', 'canonicalizeHtml', + 'pollUntil', 'WrappedFrag', 'try_all_permutations', 'StubStream', + 'SeededRandom', 'ReactiveVar', 'OnscreenDiv', 'clickElement', 'blurElement', + 'focusElement', 'simulateEvent', 'getStyleProperty', 'canonicalizeHtml', 'withCallbackLogger', 'testAsyncMulti']); api.add_files('try_all_permutations.js'); diff --git a/tools/linker.js b/tools/linker.js index 550a4b8d13..660b708c98 100644 --- a/tools/linker.js +++ b/tools/linker.js @@ -141,7 +141,7 @@ _.extend(Module.prototype, { _.each(self.files, function (file) { if (!_.isEmpty(chunks)) chunks.push("\n\n\n\n\n\n"); - chunks.push(file.getPrelinkedOutput({ sourceWidth: sourceWidth }); + chunks.push(file.getPrelinkedOutput({ sourceWidth: sourceWidth })); }); var node = new sourcemap.SourceNode(null, null, null, chunks); diff --git a/tools/packages.js b/tools/packages.js index 59bb9c4325..9c9b519f72 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -1467,7 +1467,7 @@ _.extend(Package.prototype, { // change.) use: function (names, where, options) { // Support `api.use(package, {weak: true})` without where. - if (where.constructor === Object && !options) { + if (_.isObject(where) && !_.isArray(where) && !options) { options = where; where = null; } From 1e36cdb784c26153cd35bb1ff6d14c977914326e Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 24 Jul 2013 00:22:18 -0700 Subject: [PATCH 079/175] most tests pass, just not coffeescript --- packages/accounts-ui-unstyled/accounts_ui.js | 2 ++ packages/email/package.js | 4 ++-- packages/mongo-livedata/package.js | 2 +- packages/reactive-dict/package.js | 2 ++ packages/reactive-dict/reactive-dict.js | 4 +--- packages/webapp/package.js | 4 ++-- 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/accounts-ui-unstyled/accounts_ui.js b/packages/accounts-ui-unstyled/accounts_ui.js index d2d518acc6..5f241ab997 100644 --- a/packages/accounts-ui-unstyled/accounts_ui.js +++ b/packages/accounts-ui-unstyled/accounts_ui.js @@ -1,3 +1,5 @@ +Accounts.ui = {}; + Accounts.ui._options = { requestPermissions: {}, requestOfflineToken: {} diff --git a/packages/email/package.js b/packages/email/package.js index a1fa884378..393fc107ff 100644 --- a/packages/email/package.js +++ b/packages/email/package.js @@ -8,8 +8,8 @@ Npm.depends({mailcomposer: "0.1.15", simplesmtp: "0.1.25", "stream-buffers": "0. Package.on_use(function (api) { api.use('underscore', 'server'); - api.exportSymbol('Email'); - api.exportSymbol('_EmailTest'); + api.exportSymbol('Email', 'server'); + api.exportSymbol('_EmailTest', 'server'); api.add_files('email.js', 'server'); }); diff --git a/packages/mongo-livedata/package.js b/packages/mongo-livedata/package.js index e8e91fa65d..2ddec9ef11 100644 --- a/packages/mongo-livedata/package.js +++ b/packages/mongo-livedata/package.js @@ -20,7 +20,7 @@ Package.on_use(function (api) { ['client', 'server']); api.use('check', ['client', 'server']); - api.exportSymbol('_MongoLivedataTest'); + api.exportSymbol('_MongoLivedataTest', 'server'); api.add_files('mongo_driver.js', 'server'); api.add_files('local_collection_driver.js', ['client', 'server']); diff --git a/packages/reactive-dict/package.js b/packages/reactive-dict/package.js index 286aaa7d36..8036561244 100644 --- a/packages/reactive-dict/package.js +++ b/packages/reactive-dict/package.js @@ -5,6 +5,8 @@ Package.describe({ Package.on_use(function (api) { api.use(['underscore', 'deps', 'ejson']); + // If we are loading mongo-livedata, let you store ObjectIDs in it. + api.use('mongo-livedata', {weak: true}); api.exportSymbol('ReactiveDict'); api.add_files('reactive-dict.js'); }); diff --git a/packages/reactive-dict/reactive-dict.js b/packages/reactive-dict/reactive-dict.js index c0458c6edd..55e68b89dd 100644 --- a/packages/reactive-dict/reactive-dict.js +++ b/packages/reactive-dict/reactive-dict.js @@ -63,9 +63,7 @@ _.extend(ReactiveDict.prototype, { var self = this; // XXX hardcoded awareness of the 'mongo-livedata' package is not ideal - var ObjectID = - typeof Package !== 'undefined' && Package['mongo-livedata'] && - Package['mongo-livedata'].Meteor.Collection.ObjectID; + var ObjectID = Package['mongo-livedata'] && Meteor.Collection.ObjectID; // We don't allow objects (or arrays that might include objects) for // .equals, because JSON.stringify doesn't canonicalize object key diff --git a/packages/webapp/package.js b/packages/webapp/package.js index 5e8584ece5..decfdf8022 100644 --- a/packages/webapp/package.js +++ b/packages/webapp/package.js @@ -9,7 +9,7 @@ Npm.depends({connect: "2.7.10", Package.on_use(function (api) { api.use(['logging', 'underscore', 'routepolicy'], 'server'); - api.exportSymbol('WebApp'); - api.exportSymbol('main'); + api.exportSymbol('WebApp', 'server'); + api.exportSymbol('main', 'server'); api.add_files('webapp_server.js', 'server'); }); From 23d73f07321068d4580885ecec04fb734c5dd4fd Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 24 Jul 2013 00:26:38 -0700 Subject: [PATCH 080/175] fix coffeescript. test-packages passes! --- packages/coffeescript-test-helper/exporting.coffee | 2 -- packages/coffeescript-test-helper/package.js | 3 ++- packages/coffeescript/plugin/compile-coffeescript.js | 5 +++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/coffeescript-test-helper/exporting.coffee b/packages/coffeescript-test-helper/exporting.coffee index 74f105d070..eb75de9f7d 100644 --- a/packages/coffeescript-test-helper/exporting.coffee +++ b/packages/coffeescript-test-helper/exporting.coffee @@ -1,3 +1 @@ -### @export COFFEESCRIPT_EXPORTED ### - COFFEESCRIPT_EXPORTED = 123 diff --git a/packages/coffeescript-test-helper/package.js b/packages/coffeescript-test-helper/package.js index e1a9f2a3f6..c820512e32 100644 --- a/packages/coffeescript-test-helper/package.js +++ b/packages/coffeescript-test-helper/package.js @@ -1,9 +1,10 @@ Package.describe({ - summary: "Used by the coffeescript package's @export tests", + summary: "Used by the coffeescript package's tests", internal: true }); Package.on_use(function (api) { api.use('coffeescript', ['client', 'server']); + api.exportSymbol('COFFEESCRIPT_EXPORTED'); api.add_files("exporting.coffee", ['client', 'server']); }); diff --git a/packages/coffeescript/plugin/compile-coffeescript.js b/packages/coffeescript/plugin/compile-coffeescript.js index 988eb53188..c729f79dc5 100644 --- a/packages/coffeescript/plugin/compile-coffeescript.js +++ b/packages/coffeescript/plugin/compile-coffeescript.js @@ -28,6 +28,8 @@ var stripExportedVars = function (source, exports) { // up on subsequent lines.) // XXX relax these assumptions by doing actual JS parsing (eg with jsparse). // I'd do this now, but there's no easy way to "unparse" a jsparse AST. + // Or alternatively, hack the compiler to allow us to specify unbound + // symbols directly. for (var i = 0; i < lines.length; i++) { var line = lines[i]; @@ -83,8 +85,7 @@ var addSharedHeader = function (source, sourceMap) { // as a var in the package closure, and in "app" mode where it will end up as // a global. // - // This ends in a newline in case the first line is a linker @comment, which - // should be at the beginning of a line. + // This ends in a newline to make the source map easier to adjust. var header = ("__coffeescriptShare = typeof __coffeescriptShare === 'object' " + "? __coffeescriptShare : {}; " + "var share = __coffeescriptShare;\n"); From 95c8384e3bcd930f6f5bc329d1408c1ef6e651c2 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 24 Jul 2013 00:35:46 -0700 Subject: [PATCH 081/175] Get rid of linkerFileTransform. --- .../plugin/compile-coffeescript.js | 11 +++--- tools/linker.js | 38 ++----------------- tools/packages.js | 6 ++- 3 files changed, 13 insertions(+), 42 deletions(-) diff --git a/packages/coffeescript/plugin/compile-coffeescript.js b/packages/coffeescript/plugin/compile-coffeescript.js index c729f79dc5..90bac42fe2 100644 --- a/packages/coffeescript/plugin/compile-coffeescript.js +++ b/packages/coffeescript/plugin/compile-coffeescript.js @@ -141,15 +141,14 @@ var handler = function (compileStep) { ); } + var stripped = stripExportedVars(output.js, compileStep.exports); + var sourceWithMap = addSharedHeader(stripped, output.v3SourceMap); + compileStep.addJavaScript({ path: outputFile, sourcePath: compileStep.inputPath, - data: output.js, - linkerFileTransform: function (sourceWithMap, exports) { - var stripped = stripExportedVars(sourceWithMap.source, exports); - return addSharedHeader(stripped, sourceWithMap.sourceMap); - }, - sourceMap: output.v3SourceMap + data: sourceWithMap.source, + sourceMap: sourceWithMap.sourceMap }); }; diff --git a/tools/linker.js b/tools/linker.js index 660b708c98..482b19f50b 100644 --- a/tools/linker.js +++ b/tools/linker.js @@ -59,13 +59,6 @@ _.extend(Module.prototype, { return _.max(maxInFile); }, - runLinkerFileTransforms: function () { - var self = this; - _.each(self.files, function (f) { - f.runLinkerFileTransform(self.exports); - }); - }, - // Figure out which vars need to be specifically put in the module // scope. // @@ -230,13 +223,6 @@ var File = function (inputFile, module) { // package or app.) Used for source maps, error messages.. self.sourcePath = inputFile.sourcePath; - // A function which transforms the source code once all exports are - // known. (eg, for CoffeeScript.) - self.linkerFileTransform = - inputFile.linkerFileTransform || function (sourceWithMap, exports) { - return sourceWithMap; - }; - // If true, don't wrap this individual file in a closure. self.bare = !!inputFile.bare; @@ -306,15 +292,6 @@ _.extend(File.prototype, { return require('path').basename(self.sourcePath); }, - runLinkerFileTransform: function (exports) { - var self = this; - var sourceAndMap = self.linkerFileTransform( - {source: self.source, sourceMap: self.sourceMap}, - exports); - self.source = sourceAndMap.source; - self.sourceMap = sourceAndMap.sourceMap; - }, - // Options: // - preserveLineNumbers: if true, decorate minimally so that line // numbers don't change between input and output. In this case, @@ -489,10 +466,6 @@ var bannerPadding = function (bannerWidth) { // look good) // - sourcePath: path to use in error messages // - sourceMap: an optional source map (as string) for the input file -// - linkerFileTransform: if given, this function will be called -// when the module is being linked with the source of the file -// and an array of the exports of the module; the file's source will -// be replaced by what the function returns. // // exports: an array of symbols that the module exports // @@ -531,14 +504,9 @@ var prelink = function (options) { module.addFile(inputFile); }); - // 1) Run the linkerFileTransforms. (This is, eg, CoffeeScript arranging to - // not close over the exports.) - // 2) Do static analysis to compute module-scoped variables; this has to be - // done based on the *output* of the transforms. Error recovery from the - // static analysis mutates the sources, so this has to be done before - // concatenation. - // 3) Finally, concatenate. - module.runLinkerFileTransforms(); + // Do static analysis to compute module-scoped variables. Error recovery from + // the static analysis mutates the sources, so this has to be done before + // concatenation. var packageScopeVariables = module.computeModuleScopeVars(); var files = module.getPrelinkedFiles(); diff --git a/tools/packages.js b/tools/packages.js index 9c9b519f72..175fb72d18 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -295,6 +295,10 @@ _.extend(Slice.prototype, { // - fileOptions: any options passed to "api.add_files"; for // use by the plugin. The built-in "js" plugin uses the "bare" // option for files that shouldn't be wrapped in a closure. + // - exports: An array of symbols exported by this slice, or null + // if it may not export any symbols (eg, test slices). This + // is used by CoffeeScript to ensure that it doesn't close over + // those symbols, eg. // - read(n): read from the input file. If n is given it should // be an integer, and you will receive the next n bytes of the // file as a Buffer. If n is omitted you get the rest of the @@ -386,6 +390,7 @@ _.extend(Slice.prototype, { rootOutputPath: self.pkg.serveRoot, arch: self.arch, fileOptions: fileOptions, + exports: self.exports, read: function (n) { if (n === undefined || readOffset + n > contents.length) n = contents.length - readOffset; @@ -429,7 +434,6 @@ _.extend(Slice.prototype, { source: options.data, sourcePath: options.sourcePath, servePath: path.join(self.pkg.serveRoot, options.path), - linkerFileTransform: options.linkerFileTransform, bare: !!options.bare, sourceMap: options.sourceMap }); From 5e9d030f139b9a383ba87b00f254944c9ddde9f7 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 24 Jul 2013 00:41:22 -0700 Subject: [PATCH 082/175] move email's hookSend onto _EmailTest --- packages/accounts-password/email_tests_setup.js | 2 +- packages/email/email.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/accounts-password/email_tests_setup.js b/packages/accounts-password/email_tests_setup.js index bd8990a14b..bf84839c35 100644 --- a/packages/accounts-password/email_tests_setup.js +++ b/packages/accounts-password/email_tests_setup.js @@ -5,7 +5,7 @@ // var interceptedEmails = {}; // (email address) -> (array of contents) -Email._hookSend(function (options) { +_EmailTest.hookSend(function (options) { var to = options.to; if (to.indexOf('intercept') === -1) { return true; // go ahead and send diff --git a/packages/email/email.js b/packages/email/email.js index d425133119..65f2d66399 100644 --- a/packages/email/email.js +++ b/packages/email/email.js @@ -90,7 +90,7 @@ var smtpSend = function (mc) { * false to skip sending. */ var sendHooks = []; -Email._hookSend = function (f) { +_EmailTest.hookSend = function (f) { sendHooks.push(f); }; From ccd697c08f7277e6a24127a01601aa55805dd090 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 24 Jul 2013 00:42:34 -0700 Subject: [PATCH 083/175] rename logging test that is no longer in logging --- packages/meteor/debug_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/meteor/debug_test.js b/packages/meteor/debug_test.js index fcb832a484..9d3db73a05 100644 --- a/packages/meteor/debug_test.js +++ b/packages/meteor/debug_test.js @@ -1,4 +1,4 @@ -Tinytest.add("logging - debug", function (test) { +Tinytest.add("core - debug", function (test) { // Just run a log statement and make sure it doesn't explode. Meteor._suppress_log(3); From 863d0c40b3c1e307d16761148e035ae0ba7089d8 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 24 Jul 2013 11:08:36 -0700 Subject: [PATCH 084/175] clean up server/client_convenience --- packages/livedata/client_convenience.js | 12 +++++++----- packages/livedata/server_convenience.js | 13 ++++++------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/livedata/client_convenience.js b/packages/livedata/client_convenience.js index 77b023424d..b6992d1ac7 100644 --- a/packages/livedata/client_convenience.js +++ b/packages/livedata/client_convenience.js @@ -1,5 +1,5 @@ -Meteor.connection = null; - +// Meteor.refresh can be called on the client (if you're in common code) but it +// only has an effect on the server. Meteor.refresh = function (notification) { }; @@ -22,15 +22,17 @@ if (Meteor.isClient) { Meteor[name] = _.bind(Meteor.connection[name], Meteor.connection); }); } else { - /* Never set up a default connection on the server. Don't even map - subscribe/call/etc onto Meteor. */ + // Never set up a default connection on the server. Don't even map + // subscribe/call/etc onto Meteor. + Meteor.connection = null; } // Meteor.connection used to be called // Meteor.default_connection. Provide backcompat as a courtesy even // though it was never documented. -// XXX remove this after a while +// XXX COMPAT WITH 0.6.4 Meteor.default_connection = Meteor.connection; // We should transition from Meteor.connect to DDP.connect. +// XXX COMPAT WITH 0.6.4 Meteor.connect = DDP.connect; diff --git a/packages/livedata/server_convenience.js b/packages/livedata/server_convenience.js index 50a472d03c..ed1dc6d05a 100644 --- a/packages/livedata/server_convenience.js +++ b/packages/livedata/server_convenience.js @@ -1,9 +1,3 @@ -Meteor.server = null; - -// Note that this is redefined below if we in fact start a server. -Meteor.refresh = function (notification) { -}; - // Only create a server if we are in an environment with a HTTP server // (as opposed to, eg, a command-line tool). // @@ -34,9 +28,14 @@ if (Package.webapp) { function (name) { Meteor[name] = _.bind(Meteor.server[name], Meteor.server); }); +} else { + // No server? Make these empty/no-ops. + Meteor.server = null; + Meteor.refresh = function (notificatio) { + }; } // Meteor.server used to be called Meteor.default_server. Provide // backcompat as a courtesy even though it was never documented. -// XXX remove this after a while +// XXX COMPAT WITH 0.6.4 Meteor.default_server = Meteor.server; From 99a0273fb736d97e522a8bcdc1e1e524a13be2e7 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 24 Jul 2013 11:35:56 -0700 Subject: [PATCH 085/175] preserve the existence of Meteor._Mongo --- packages/mongo-livedata/mongo_driver.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/mongo-livedata/mongo_driver.js b/packages/mongo-livedata/mongo_driver.js index 6c8460e951..c6d8a214f9 100644 --- a/packages/mongo-livedata/mongo_driver.js +++ b/packages/mongo-livedata/mongo_driver.js @@ -124,6 +124,10 @@ MongoConnection = function (url) { }); }; +// Some apps want to use this directly. Maybe they shouldn't, but let's not +// break them yet. +Meteor._Mongo = MongoConnection; + MongoConnection.prototype.close = function() { var self = this; // Use Future.wrap so that errors get thrown. This happens to From a57a40ea2cf9a237ded47cdf374341f0a0506719 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 24 Jul 2013 11:54:07 -0700 Subject: [PATCH 086/175] fix accounts package detection to work again --- packages/accounts-facebook/facebook.js | 27 +++ packages/accounts-facebook/facebook_client.js | 10 - packages/accounts-facebook/facebook_server.js | 13 -- packages/accounts-facebook/package.js | 3 +- packages/accounts-github/github.js | 23 +++ packages/accounts-github/github_client.js | 10 - packages/accounts-github/github_server.js | 9 - packages/accounts-github/package.js | 3 +- packages/accounts-google/google.js | 31 +++ packages/accounts-google/google_client.js | 10 - packages/accounts-google/google_server.js | 17 -- packages/accounts-google/package.js | 3 +- packages/accounts-meetup/meetup.js | 25 +++ packages/accounts-meetup/meetup_client.js | 10 - packages/accounts-meetup/meetup_server.js | 11 - packages/accounts-meetup/package.js | 3 +- packages/accounts-oauth/oauth_common.js | 24 +++ packages/accounts-oauth/oauth_server.js | 10 - packages/accounts-oauth/package.js | 1 + packages/accounts-twitter/package.js | 3 +- packages/accounts-twitter/twitter.js | 25 +++ packages/accounts-twitter/twitter_client.js | 10 - packages/accounts-twitter/twitter_server.js | 11 - .../accounts-ui-unstyled/login_buttons.js | 195 ++++++++---------- packages/accounts-ui-unstyled/package.js | 7 + packages/accounts-weibo/package.js | 3 +- packages/accounts-weibo/weibo.js | 23 +++ packages/accounts-weibo/weibo_client.js | 10 - packages/accounts-weibo/weibo_server.js | 9 - packages/oauth1/oauth1_server.js | 2 +- packages/oauth1/oauth1_tests.js | 2 +- packages/oauth1/package.js | 4 +- 32 files changed, 285 insertions(+), 262 deletions(-) create mode 100644 packages/accounts-facebook/facebook.js delete mode 100644 packages/accounts-facebook/facebook_client.js delete mode 100644 packages/accounts-facebook/facebook_server.js create mode 100644 packages/accounts-github/github.js delete mode 100644 packages/accounts-github/github_client.js delete mode 100644 packages/accounts-github/github_server.js create mode 100644 packages/accounts-google/google.js delete mode 100644 packages/accounts-google/google_client.js delete mode 100644 packages/accounts-google/google_server.js create mode 100644 packages/accounts-meetup/meetup.js delete mode 100644 packages/accounts-meetup/meetup_client.js delete mode 100644 packages/accounts-meetup/meetup_server.js create mode 100644 packages/accounts-oauth/oauth_common.js create mode 100644 packages/accounts-twitter/twitter.js delete mode 100644 packages/accounts-twitter/twitter_client.js delete mode 100644 packages/accounts-twitter/twitter_server.js create mode 100644 packages/accounts-weibo/weibo.js delete mode 100644 packages/accounts-weibo/weibo_client.js delete mode 100644 packages/accounts-weibo/weibo_server.js diff --git a/packages/accounts-facebook/facebook.js b/packages/accounts-facebook/facebook.js new file mode 100644 index 0000000000..1fb297ca25 --- /dev/null +++ b/packages/accounts-facebook/facebook.js @@ -0,0 +1,27 @@ +Accounts.oauth.registerService('facebook'); + +if (Meteor.isClient) { + Meteor.loginWithFacebook = function(options, callback) { + // support a callback without options + if (! callback && typeof options === "function") { + callback = options; + options = null; + } + + var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback); + Facebook.requestCredential(options, credentialRequestCompleteCallback); + }; +} else { + + Accounts.addAutopublishFields({ + // publish all fields including access token, which can legitimately + // be used from the client (if transmitted over ssl or on + // localhost). https://developers.facebook.com/docs/concepts/login/access-tokens-and-types/, + // "Sharing of Access Tokens" + forLoggedInUser: ['services.facebook'], + forOtherUsers: [ + // https://www.facebook.com/help/167709519956542 + 'services.facebook.id', 'services.facebook.username', 'services.facebook.gender' + ] + }); +} diff --git a/packages/accounts-facebook/facebook_client.js b/packages/accounts-facebook/facebook_client.js deleted file mode 100644 index e05027d7ff..0000000000 --- a/packages/accounts-facebook/facebook_client.js +++ /dev/null @@ -1,10 +0,0 @@ -Meteor.loginWithFacebook = function(options, callback) { - // support a callback without options - if (! callback && typeof options === "function") { - callback = options; - options = null; - } - - var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback); - Facebook.requestCredential(options, credentialRequestCompleteCallback); -}; diff --git a/packages/accounts-facebook/facebook_server.js b/packages/accounts-facebook/facebook_server.js deleted file mode 100644 index 2699808eaa..0000000000 --- a/packages/accounts-facebook/facebook_server.js +++ /dev/null @@ -1,13 +0,0 @@ -Accounts.oauth.registerService('facebook'); - -Accounts.addAutopublishFields({ - // publish all fields including access token, which can legitimately - // be used from the client (if transmitted over ssl or on - // localhost). https://developers.facebook.com/docs/concepts/login/access-tokens-and-types/, - // "Sharing of Access Tokens" - forLoggedInUser: ['services.facebook'], - forOtherUsers: [ - // https://www.facebook.com/help/167709519956542 - 'services.facebook.id', 'services.facebook.username', 'services.facebook.gender' - ] -}); diff --git a/packages/accounts-facebook/package.js b/packages/accounts-facebook/package.js index 158c5ca581..cca0adb01f 100644 --- a/packages/accounts-facebook/package.js +++ b/packages/accounts-facebook/package.js @@ -11,6 +11,5 @@ Package.on_use(function(api) { api.add_files('facebook_login_button.css', 'client'); - api.add_files('facebook_server.js', 'server'); - api.add_files('facebook_client.js', 'client'); + api.add_files("facebook.js"); }); diff --git a/packages/accounts-github/github.js b/packages/accounts-github/github.js new file mode 100644 index 0000000000..fb0cb7f1a2 --- /dev/null +++ b/packages/accounts-github/github.js @@ -0,0 +1,23 @@ +Accounts.oauth.registerService('github'); + +if (Meteor.isClient) { + Meteor.loginWithGithub = function(options, callback) { + // support a callback without options + if (! callback && typeof options === "function") { + callback = options; + options = null; + } + + var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback); + Github.requestCredential(options, credentialRequestCompleteCallback); + }; +} else { + + Accounts.addAutopublishFields({ + // not sure whether the github api can be used from the browser, + // thus not sure if we should be sending access tokens; but we do it + // for all other oauth2 providers, and it may come in handy. + forLoggedInUser: ['services.github'], + forOtherUsers: ['services.github.username'] + }); +} diff --git a/packages/accounts-github/github_client.js b/packages/accounts-github/github_client.js deleted file mode 100644 index 75edcca47d..0000000000 --- a/packages/accounts-github/github_client.js +++ /dev/null @@ -1,10 +0,0 @@ -Meteor.loginWithGithub = function(options, callback) { - // support a callback without options - if (! callback && typeof options === "function") { - callback = options; - options = null; - } - - var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback); - Github.requestCredential(options, credentialRequestCompleteCallback); -}; diff --git a/packages/accounts-github/github_server.js b/packages/accounts-github/github_server.js deleted file mode 100644 index ca57098047..0000000000 --- a/packages/accounts-github/github_server.js +++ /dev/null @@ -1,9 +0,0 @@ -Accounts.oauth.registerService('github'); - -Accounts.addAutopublishFields({ - // not sure whether the github api can be used from the browser, - // thus not sure if we should be sending access tokens; but we do it - // for all other oauth2 providers, and it may come in handy. - forLoggedInUser: ['services.github'], - forOtherUsers: ['services.github.username'] -}); diff --git a/packages/accounts-github/package.js b/packages/accounts-github/package.js index 5573b0360e..ed090b2863 100644 --- a/packages/accounts-github/package.js +++ b/packages/accounts-github/package.js @@ -11,6 +11,5 @@ Package.on_use(function(api) { api.add_files('github_login_button.css', 'client'); - api.add_files('github_server.js', 'server'); - api.add_files('github_client.js', 'client'); + api.add_files("github.js"); }); diff --git a/packages/accounts-google/google.js b/packages/accounts-google/google.js new file mode 100644 index 0000000000..0d9a77ab6c --- /dev/null +++ b/packages/accounts-google/google.js @@ -0,0 +1,31 @@ +Accounts.oauth.registerService('google'); + +if (Meteor.isClient) { + Meteor.loginWithGoogle = function(options, callback) { + // support a callback without options + if (! callback && typeof options === "function") { + callback = options; + options = null; + } + + var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback); + Google.requestCredential(options, credentialRequestCompleteCallback); + }; +} else { + + Accounts.addAutopublishFields({ + forLoggedInUser: _.map( + // publish access token since it can be used from the client (if + // transmitted over ssl or on + // localhost). https://developers.google.com/accounts/docs/OAuth2UserAgent + // refresh token probably shouldn't be sent down. + Google.whitelistedFields.concat(['accessToken', 'expiresAt']), // don't publish refresh token + function (subfield) { return 'services.google.' + subfield; }), + + forOtherUsers: _.map( + // even with autopublish, no legitimate web app should be + // publishing all users' emails + _.without(Google.whitelistedFields, 'email', 'verified_email'), + function (subfield) { return 'services.google.' + subfield; }) + }); +} diff --git a/packages/accounts-google/google_client.js b/packages/accounts-google/google_client.js deleted file mode 100644 index b8109ee833..0000000000 --- a/packages/accounts-google/google_client.js +++ /dev/null @@ -1,10 +0,0 @@ -Meteor.loginWithGoogle = function(options, callback) { - // support a callback without options - if (! callback && typeof options === "function") { - callback = options; - options = null; - } - - var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback); - Google.requestCredential(options, credentialRequestCompleteCallback); -}; diff --git a/packages/accounts-google/google_server.js b/packages/accounts-google/google_server.js deleted file mode 100644 index 5c8575fc18..0000000000 --- a/packages/accounts-google/google_server.js +++ /dev/null @@ -1,17 +0,0 @@ -Accounts.oauth.registerService('google'); - -Accounts.addAutopublishFields({ - forLoggedInUser: _.map( - // publish access token since it can be used from the client (if - // transmitted over ssl or on - // localhost). https://developers.google.com/accounts/docs/OAuth2UserAgent - // refresh token probably shouldn't be sent down. - Google.whitelistedFields.concat(['accessToken', 'expiresAt']), // don't publish refresh token - function (subfield) { return 'services.google.' + subfield; }), - - forOtherUsers: _.map( - // even with autopublish, no legitimate web app should be - // publishing all users' emails - _.without(Google.whitelistedFields, 'email', 'verified_email'), - function (subfield) { return 'services.google.' + subfield; }) -}); diff --git a/packages/accounts-google/package.js b/packages/accounts-google/package.js index bab6125722..355f8bb0f1 100644 --- a/packages/accounts-google/package.js +++ b/packages/accounts-google/package.js @@ -12,6 +12,5 @@ Package.on_use(function(api) { api.add_files('google_login_button.css', 'client'); - api.add_files('google_server.js', 'server'); - api.add_files('google_client.js', 'client'); + api.add_files("google.js"); }); diff --git a/packages/accounts-meetup/meetup.js b/packages/accounts-meetup/meetup.js new file mode 100644 index 0000000000..f4ed38e482 --- /dev/null +++ b/packages/accounts-meetup/meetup.js @@ -0,0 +1,25 @@ +Accounts.oauth.registerService('meetup'); + +if (Meteor.isClient) { + Meteor.loginWithMeetup = function(options, callback) { + // support a callback without options + if (! callback && typeof options === "function") { + callback = options; + options = null; + } + + var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback); + Meetup.requestCredential(options, credentialRequestCompleteCallback); + }; +} else { + + Accounts.addAutopublishFields({ + // publish all fields including access token, which can legitimately + // be used from the client (if transmitted over ssl or on + // localhost). http://www.meetup.com/meetup_api/auth/#oauth2implicit + forLoggedInUser: ['services.meetup'], + forOtherUsers: ['services.meetup.id'] + }); + + +} diff --git a/packages/accounts-meetup/meetup_client.js b/packages/accounts-meetup/meetup_client.js deleted file mode 100644 index 848faae2da..0000000000 --- a/packages/accounts-meetup/meetup_client.js +++ /dev/null @@ -1,10 +0,0 @@ -Meteor.loginWithMeetup = function(options, callback) { - // support a callback without options - if (! callback && typeof options === "function") { - callback = options; - options = null; - } - - var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback); - Meetup.requestCredential(options, credentialRequestCompleteCallback); -}; diff --git a/packages/accounts-meetup/meetup_server.js b/packages/accounts-meetup/meetup_server.js deleted file mode 100644 index 4b727ac031..0000000000 --- a/packages/accounts-meetup/meetup_server.js +++ /dev/null @@ -1,11 +0,0 @@ -Accounts.oauth.registerService('meetup'); - -Accounts.addAutopublishFields({ - // publish all fields including access token, which can legitimately - // be used from the client (if transmitted over ssl or on - // localhost). http://www.meetup.com/meetup_api/auth/#oauth2implicit - forLoggedInUser: ['services.meetup'], - forOtherUsers: ['services.meetup.id'] -}); - - diff --git a/packages/accounts-meetup/package.js b/packages/accounts-meetup/package.js index a5537b1cee..52e5e012d0 100644 --- a/packages/accounts-meetup/package.js +++ b/packages/accounts-meetup/package.js @@ -11,6 +11,5 @@ Package.on_use(function(api) { api.add_files('meetup_login_button.css', 'client'); - api.add_files('meetup_server.js', 'server'); - api.add_files('meetup_client.js', 'client'); + api.add_files("meetup.js"); }); diff --git a/packages/accounts-oauth/oauth_common.js b/packages/accounts-oauth/oauth_common.js new file mode 100644 index 0000000000..031cd0eace --- /dev/null +++ b/packages/accounts-oauth/oauth_common.js @@ -0,0 +1,24 @@ +Accounts.oauth = {}; + +var services = {}; + +// Helper for registering OAuth based accounts packages. +// On the server, adds an index to the user collection. +Accounts.oauth.registerService = function (name) { + if (_.has(services, name)) + throw new Error("Duplicate service: " + name); + services[name] = true; + + if (Meteor.server) { + // Accounts.updateOrCreateUserFromExternalService does a lookup by this id, + // so this should be a unique index. You might want to add indexes for other + // fields returned by your service (eg services.github.login) but you can do + // that in your app. + Meteor.users._ensureIndex('services.' + name + '.id', + {unique: 1, sparse: 1}); + } +}; + +Accounts.oauth.serviceNames = function () { + return _.keys(services); +}; diff --git a/packages/accounts-oauth/oauth_server.js b/packages/accounts-oauth/oauth_server.js index 775811289e..5fea88688e 100644 --- a/packages/accounts-oauth/oauth_server.js +++ b/packages/accounts-oauth/oauth_server.js @@ -7,18 +7,8 @@ Accounts.oauth.registerService = function (name) { // that in your app. Meteor.users._ensureIndex('services.' + name + '.id', {unique: 1, sparse: 1}); - }; -// For test cleanup only. (Mongo has a limit as to how many indexes it can have -// per collection.) -Accounts.oauth._unregisterService = function (name) { - var index = {}; - index['services.' + name + '.id'] = 1; - Meteor.users._dropIndex(index); -}; - - // Listen to calls to `login` with an oauth option set. This is where // users actually get logged in to meteor via oauth. Accounts.registerLoginHandler(function (options) { diff --git a/packages/accounts-oauth/package.js b/packages/accounts-oauth/package.js index 8eb2d7755c..1c1d949896 100644 --- a/packages/accounts-oauth/package.js +++ b/packages/accounts-oauth/package.js @@ -13,6 +13,7 @@ Package.on_use(function (api) { api.imply('accounts-base', ['client', 'server']); api.use('oauth', 'server'); + api.add_files('oauth_common.js'); api.add_files('oauth_client.js', 'client'); api.add_files('oauth_server.js', 'server'); }); diff --git a/packages/accounts-twitter/package.js b/packages/accounts-twitter/package.js index ce32f38084..c878e852c0 100644 --- a/packages/accounts-twitter/package.js +++ b/packages/accounts-twitter/package.js @@ -15,6 +15,5 @@ Package.on_use(function(api) { api.add_files('twitter_login_button.css', 'client'); - api.add_files('twitter_server.js', 'server'); - api.add_files('twitter_client.js', 'client'); + api.add_files("twitter.js"); }); diff --git a/packages/accounts-twitter/twitter.js b/packages/accounts-twitter/twitter.js new file mode 100644 index 0000000000..19c4a9e209 --- /dev/null +++ b/packages/accounts-twitter/twitter.js @@ -0,0 +1,25 @@ +Accounts.oauth.registerService('twitter'); + +if (Meteor.isClient) { + Meteor.loginWithTwitter = function(options, callback) { + // support a callback without options + if (! callback && typeof options === "function") { + callback = options; + options = null; + } + + var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback); + Twitter.requestCredential(options, credentialRequestCompleteCallback); + }; +} else { + + var autopublishedFields = _.map( + // don't send access token. https://dev.twitter.com/discussions/5025 + Twitter.whitelistedFields.concat(['id', 'screenName']), + function (subfield) { return 'services.twitter.' + subfield; }); + + Accounts.addAutopublishFields({ + forLoggedInUser: autopublishedFields, + forOtherUsers: autopublishedFields + }); +} diff --git a/packages/accounts-twitter/twitter_client.js b/packages/accounts-twitter/twitter_client.js deleted file mode 100644 index c7682333e7..0000000000 --- a/packages/accounts-twitter/twitter_client.js +++ /dev/null @@ -1,10 +0,0 @@ -Meteor.loginWithTwitter = function(options, callback) { - // support a callback without options - if (! callback && typeof options === "function") { - callback = options; - options = null; - } - - var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback); - Twitter.requestCredential(options, credentialRequestCompleteCallback); -}; diff --git a/packages/accounts-twitter/twitter_server.js b/packages/accounts-twitter/twitter_server.js deleted file mode 100644 index fea172a744..0000000000 --- a/packages/accounts-twitter/twitter_server.js +++ /dev/null @@ -1,11 +0,0 @@ -Accounts.oauth.registerService('twitter'); - -var autopublishedFields = _.map( - // don't send access token. https://dev.twitter.com/discussions/5025 - Twitter.whitelistedFields.concat(['id', 'screenName']), - function (subfield) { return 'services.twitter.' + subfield; }); - -Accounts.addAutopublishFields({ - forLoggedInUser: autopublishedFields, - forOtherUsers: autopublishedFields -}); diff --git a/packages/accounts-ui-unstyled/login_buttons.js b/packages/accounts-ui-unstyled/login_buttons.js index 2310c64249..d173faaeb1 100644 --- a/packages/accounts-ui-unstyled/login_buttons.js +++ b/packages/accounts-ui-unstyled/login_buttons.js @@ -23,6 +23,95 @@ Template._loginButtons.preserve({ 'input[id]': Spark._labelFromIdOrName }); +// +// helpers +// + +displayName = function () { + var user = Meteor.user(); + if (!user) + return ''; + + if (user.profile && user.profile.name) + return user.profile.name; + if (user.username) + return user.username; + if (user.emails && user.emails[0] && user.emails[0].address) + return user.emails[0].address; + + return ''; +}; + +// returns an array of the login services used by this app. each +// element of the array is an object (eg {name: 'facebook'}), since +// that makes it useful in combination with handlebars {{#each}}. +// +// don't cache the output of this function: if called during startup (before +// oauth packages load) it might not include them all. +// +// NOTE: It is very important to have this return password last +// because of the way we render the different providers in +// login_buttons_dropdown.html +getLoginServices = function () { + var self = this; + + // First look for OAuth services. + var services = Package['accounts-oauth'] ? Accounts.oauth.serviceNames() : []; + + // Be equally kind to all login services. This also preserves + // backwards-compatibility. (But maybe order should be + // configurable?) + services.sort(); + + // Add password, if it's there; it must come last. + if (hasPasswordService()) + services.push('password'); + + return _.map(services, function(name) { + return {name: name}; + }); +}; + +hasPasswordService = function () { + return !!Package['accounts-password']; +}; + +dropdown = function () { + return hasPasswordService() || getLoginServices().length > 1; +}; + +// XXX improve these. should this be in accounts-password instead? +// +// XXX these will become configurable, and will be validated on +// the server as well. +validateUsername = function (username) { + if (username.length >= 3) { + return true; + } else { + loginButtonsSession.errorMessage("Username must be at least 3 characters long"); + return false; + } +}; +validateEmail = function (email) { + if (passwordSignupFields() === "USERNAME_AND_OPTIONAL_EMAIL" && email === '') + return true; + + if (email.indexOf('@') !== -1) { + return true; + } else { + loginButtonsSession.errorMessage("Invalid email"); + return false; + } +}; +validatePassword = function (password) { + if (password.length >= 6) { + return true; + } else { + loginButtonsSession.errorMessage("Password must be at least 6 characters long"); + return false; + } +}; + // // loginButtonLoggedOut template // @@ -81,109 +170,3 @@ Template._loginButtonsMessages.infoMessage = function () { Template._loginButtonsLoggingInPadding.dropdown = dropdown; - -// -// helpers -// - -displayName = function () { - var user = Meteor.user(); - if (!user) - return ''; - - if (user.profile && user.profile.name) - return user.profile.name; - if (user.username) - return user.username; - if (user.emails && user.emails[0] && user.emails[0].address) - return user.emails[0].address; - - return ''; -}; - -// returns an array of the login services used by this app. each -// element of the array is an object (eg {name: 'facebook'}), since -// that makes it useful in combination with handlebars {{#each}}. -// -// NOTE: It is very important to have this return password last -// because of the way we render the different providers in -// login_buttons_dropdown.html -getLoginServices = function () { - var self = this; - var services = []; - - // find all methods of the form: `Meteor.loginWithFoo`, where - // `Foo` corresponds to a login service - // - // XXX we should consider having a client-side - // Accounts.oauth.registerService function which records the - // active services and encapsulates boilerplate code now found in - // files such as facebook_client.js. This would have the added - // benefit of allow us to unify facebook_{client,common,server}.js - // into one file, which would encourage people to build more login - // services packages. - _.each(_.keys(Meteor), function(methodName) { - var match; - if ((match = methodName.match(/^loginWith(.*)/))) { - var serviceName = match[1].toLowerCase(); - - // HACKETY HACK. needed to not match - // Meteor.loginWithToken. See XXX above. - if (Accounts[serviceName]) - services.push(match[1].toLowerCase()); - } - }); - - // Be equally kind to all login services. This also preserves - // backwards-compatibility. (But maybe order should be - // configurable?) - services.sort(); - - // ensure password is last - if (_.contains(services, 'password')) - services = _.without(services, 'password').concat(['password']); - - return _.map(services, function(name) { - return {name: name}; - }); -}; - -hasPasswordService = function () { - return Accounts.password; -}; - -dropdown = function () { - return hasPasswordService() || getLoginServices().length > 1; -}; - -// XXX improve these. should this be in accounts-password instead? -// -// XXX these will become configurable, and will be validated on -// the server as well. -validateUsername = function (username) { - if (username.length >= 3) { - return true; - } else { - loginButtonsSession.errorMessage("Username must be at least 3 characters long"); - return false; - } -}; -validateEmail = function (email) { - if (passwordSignupFields() === "USERNAME_AND_OPTIONAL_EMAIL" && email === '') - return true; - - if (email.indexOf('@') !== -1) { - return true; - } else { - loginButtonsSession.errorMessage("Invalid email"); - return false; - } -}; -validatePassword = function (password) { - if (password.length >= 6) { - return true; - } else { - loginButtonsSession.errorMessage("Password must be at least 6 characters long"); - return false; - } -}; diff --git a/packages/accounts-ui-unstyled/package.js b/packages/accounts-ui-unstyled/package.js index e322209ae5..258ae5bb49 100644 --- a/packages/accounts-ui-unstyled/package.js +++ b/packages/accounts-ui-unstyled/package.js @@ -9,6 +9,13 @@ Package.on_use(function (api) { // Export Accounts (etc) to packages using this one. api.imply('accounts-base', ['client', 'server']); + // Allow us to call Accounts.oauth.serviceNames, if there are any OAuth + // services. + api.use('accounts-oauth', {weak: true}); + // Allow us to directly test if accounts-password (which doesn't use + // Accounts.oauth.registerService) exists. + api.use('accounts-password', {weak: true}); + api.add_files([ 'accounts_ui.js', diff --git a/packages/accounts-weibo/package.js b/packages/accounts-weibo/package.js index 65d72d3c85..06e0df739f 100644 --- a/packages/accounts-weibo/package.js +++ b/packages/accounts-weibo/package.js @@ -11,6 +11,5 @@ Package.on_use(function(api) { api.add_files('weibo_login_button.css', 'client'); - api.add_files('weibo_server.js', 'server'); - api.add_files('weibo_client.js', 'client'); + api.add_files("weibo.js"); }); diff --git a/packages/accounts-weibo/weibo.js b/packages/accounts-weibo/weibo.js new file mode 100644 index 0000000000..f1eaf95518 --- /dev/null +++ b/packages/accounts-weibo/weibo.js @@ -0,0 +1,23 @@ +Accounts.oauth.registerService('weibo'); + +if (Meteor.isClient) { + Meteor.loginWithWeibo = function(options, callback) { + // support a callback without options + if (! callback && typeof options === "function") { + callback = options; + options = null; + } + + var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback); + Weibo.requestCredential(options, credentialRequestCompleteCallback); + }; +} else { + + Accounts.addAutopublishFields({ + // publish all fields including access token, which can legitimately + // be used from the client (if transmitted over ssl or on localhost) + forLoggedInUser: ['services.weibo'], + forOtherUsers: ['services.weibo.screenName'] + }); + +} diff --git a/packages/accounts-weibo/weibo_client.js b/packages/accounts-weibo/weibo_client.js deleted file mode 100644 index f7df20c9b1..0000000000 --- a/packages/accounts-weibo/weibo_client.js +++ /dev/null @@ -1,10 +0,0 @@ -Meteor.loginWithWeibo = function(options, callback) { - // support a callback without options - if (! callback && typeof options === "function") { - callback = options; - options = null; - } - - var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback); - Weibo.requestCredential(options, credentialRequestCompleteCallback); -}; diff --git a/packages/accounts-weibo/weibo_server.js b/packages/accounts-weibo/weibo_server.js deleted file mode 100644 index 18a3a12ae7..0000000000 --- a/packages/accounts-weibo/weibo_server.js +++ /dev/null @@ -1,9 +0,0 @@ -Accounts.oauth.registerService('weibo'); - -Accounts.addAutopublishFields({ - // publish all fields including access token, which can legitimately - // be used from the client (if transmitted over ssl or on localhost) - forLoggedInUser: ['services.weibo'], - forOtherUsers: ['services.weibo.screenName'] -}); - diff --git a/packages/oauth1/oauth1_server.js b/packages/oauth1/oauth1_server.js index c6736de237..2cd79e603e 100644 --- a/packages/oauth1/oauth1_server.js +++ b/packages/oauth1/oauth1_server.js @@ -1,7 +1,7 @@ // A place to store request tokens pending verification var requestTokens = {}; -_Oauth1Test = {requestTokens: requestTokens}; +_OAuth1Test = {requestTokens: requestTokens}; // connect middleware Oauth._requestHandlers['1'] = function (service, query, res) { diff --git a/packages/oauth1/oauth1_tests.js b/packages/oauth1/oauth1_tests.js index d194806c8e..41156af438 100644 --- a/packages/oauth1/oauth1_tests.js +++ b/packages/oauth1/oauth1_tests.js @@ -40,7 +40,7 @@ Tinytest.add("oauth1 - loginResultForCredentialToken is stored", function (test) }); // simulate logging in using twitterfoo - _Oauth1Test.requestTokens[credentialToken] = twitterfooAccessToken; + _OAuth1Test.requestTokens[credentialToken] = twitterfooAccessToken; var req = { method: "POST", diff --git a/packages/oauth1/package.js b/packages/oauth1/package.js index 76cf2ddcc4..0edbe0e107 100644 --- a/packages/oauth1/package.js +++ b/packages/oauth1/package.js @@ -9,8 +9,8 @@ Package.on_use(function (api) { api.use('oauth', ['client', 'server']); api.use('underscore', 'server'); - api.exportSymbol('OAuth1Binding'); - api.exportSymbol('_Oauth1Test'); + api.exportSymbol('OAuth1Binding', 'server'); + api.exportSymbol('_OAuth1Test', 'server'); api.add_files('oauth1_binding.js', 'server'); api.add_files('oauth1_server.js', 'server'); From 9755f547a20ce2a394a4d9fdf61931f3e42c599f Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 24 Jul 2013 13:34:49 -0700 Subject: [PATCH 087/175] Don't interact with Reload for server-to-server DDP. --- packages/livedata/livedata_connection.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/livedata/livedata_connection.js b/packages/livedata/livedata_connection.js index 995d8b7fcc..8efecf5af8 100644 --- a/packages/livedata/livedata_connection.js +++ b/packages/livedata/livedata_connection.js @@ -161,7 +161,7 @@ var Connection = function (url, options) { self._userIdDeps = (typeof Deps !== "undefined") && new Deps.Dependency; // Block auto-reload while we're waiting for method responses. - if (Package.reload && !options.reloadWithOutstanding) { + if (Meteor.isClient && Package.reload && !options.reloadWithOutstanding) { Reload._onMigrate(function (retry) { if (!self._readyToMigrate()) { if (self._retryMigrate) @@ -279,7 +279,7 @@ var Connection = function (url, options) { } - if (Package.reload && options.reloadOnUpdate) { + if (Meteor.isClient && Package.reload && options.reloadOnUpdate) { self._stream.on('update_available', function () { // Start trying to migrate to a new version. Until all packages // signal that they're ready for a migration, the app will From 37e2cc4deede5aa5cf9f47bbd14c5b1d981a0892 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 24 Jul 2013 12:51:52 -0700 Subject: [PATCH 088/175] Make audit-argument-checks into an empty package, detected via weak deps. This requires making sure that empty packages work! --- .../audit-argument-checks/audit_argument_checks.js | 1 - packages/audit-argument-checks/package.js | 5 +---- packages/livedata/livedata_server.js | 10 +++------- packages/livedata/package.js | 3 +++ tools/linker.js | 9 ++++++++- 5 files changed, 15 insertions(+), 13 deletions(-) delete mode 100644 packages/audit-argument-checks/audit_argument_checks.js diff --git a/packages/audit-argument-checks/audit_argument_checks.js b/packages/audit-argument-checks/audit_argument_checks.js deleted file mode 100644 index 0c0ad1391a..0000000000 --- a/packages/audit-argument-checks/audit_argument_checks.js +++ /dev/null @@ -1 +0,0 @@ -DDPServer._setAuditArgumentChecks(true); diff --git a/packages/audit-argument-checks/package.js b/packages/audit-argument-checks/package.js index 9252ea10a1..bc7b2b5592 100644 --- a/packages/audit-argument-checks/package.js +++ b/packages/audit-argument-checks/package.js @@ -2,7 +2,4 @@ Package.describe({ summary: "Try to detect inadequate input sanitization" }); -Package.on_use(function (api) { - api.use(['livedata'], ['server']); - api.add_files(['audit_argument_checks.js'], 'server'); -}); +// This package is empty; its presence is detected by livedata. diff --git a/packages/livedata/livedata_server.js b/packages/livedata/livedata_server.js index 77d57231fb..6627159b44 100644 --- a/packages/livedata/livedata_server.js +++ b/packages/livedata/livedata_server.js @@ -1368,16 +1368,12 @@ var wrapInternalException = function (exception, context) { return new Meteor.Error(500, "Internal server error"); }; -// Private interface for 'audit-argument-checks' package. -// -var shouldAuditArgumentChecks = false; -DDPServer._setAuditArgumentChecks = function (value) { - shouldAuditArgumentChecks = value; -}; +// Audit argument checks, if the audit-argument-checks package exists (it is a +// weak dependency of this package). var maybeAuditArgumentChecks = function (f, context, args, description) { args = args || []; - if (shouldAuditArgumentChecks) { + if (Package['audit-argument-checks']) { return Match._failIfArgumentsAreNotAllChecked( f, context, args, description); } diff --git a/packages/livedata/package.js b/packages/livedata/package.js index a3ace7c928..14c0f3c7d3 100644 --- a/packages/livedata/package.js +++ b/packages/livedata/package.js @@ -16,6 +16,9 @@ Package.on_use(function (api) { // XXX split this package into multiple packages or multiple slices instead api.use(['webapp', 'routepolicy'], 'server', {weak: true}); + // Detect whether or not the user wants us to audit argument checks. + api.use(['audit-argument-checks'], 'server', {weak: true}); + api.exportSymbol('DDP'); api.exportSymbol('DDPServer', 'server'); diff --git a/tools/linker.js b/tools/linker.js index 482b19f50b..1b7459e577 100644 --- a/tools/linker.js +++ b/tools/linker.js @@ -94,7 +94,14 @@ _.extend(Module.prototype, { getPrelinkedFiles: function () { var self = this; - if (! self.files.length) + // If there are no files *and* we are a no-exports-at-all slice (eg a test + // slice), then generate no prelink output. + // + // If there are no files, but we are a use slice (and thus self.exports is + // an actual, albeit potentially empty, list), we DON'T want to take this + // path: we want to return an empty prelink file, so that at link time we + // end up at least setting `Package.foo = {}`. + if (_.isEmpty(self.files) && !self.exports) return []; // If we don't want to create a separate scope for this module, From cbcafd0c208f8458f1e734f4b23dc405a2ec775b Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 24 Jul 2013 13:31:42 -0700 Subject: [PATCH 089/175] Make insecure into an empty package, detected via weak deps. --- packages/insecure/insecure.js | 1 - packages/insecure/package.js | 5 +---- packages/mongo-livedata/allow_tests.js | 20 +++++++++++++------- packages/mongo-livedata/collection.js | 10 +++++----- packages/mongo-livedata/package.js | 2 ++ 5 files changed, 21 insertions(+), 17 deletions(-) delete mode 100644 packages/insecure/insecure.js diff --git a/packages/insecure/insecure.js b/packages/insecure/insecure.js deleted file mode 100644 index 22a74ca954..0000000000 --- a/packages/insecure/insecure.js +++ /dev/null @@ -1 +0,0 @@ -Meteor.Collection.insecure = true; diff --git a/packages/insecure/package.js b/packages/insecure/package.js index fe2e744a38..851536e071 100644 --- a/packages/insecure/package.js +++ b/packages/insecure/package.js @@ -2,7 +2,4 @@ Package.describe({ summary: "Allow all database writes by default" }); -Package.on_use(function (api) { - api.use(['mongo-livedata']); - api.add_files(['insecure.js'], 'server'); -}); +// This package is empty; its presence is detected by mongo-livedata. diff --git a/packages/mongo-livedata/allow_tests.js b/packages/mongo-livedata/allow_tests.js index 4529b45352..b9c50f97b1 100644 --- a/packages/mongo-livedata/allow_tests.js +++ b/packages/mongo-livedata/allow_tests.js @@ -754,22 +754,28 @@ if (Meteor.isServer) { }); Tinytest.add("collection - global insecure", function (test) { - // note: This test alters the global insecure status! This may - // collide with itself if run multiple times (but is better than - // the old test which had the same problem) - var oldGlobalInsecure = Meteor.Collection.insecure; + // note: This test alters the global insecure status, by sneakily hacking + // the global Package object! This may collide with itself if run multiple + // times (but is better than the old test which had the same problem) + var insecurePackage = Package.insecure; - Meteor.Collection.insecure = true; + Package.insecure = {}; var collection = new Meteor.Collection(null); test.equal(collection._isInsecure(), true); - Meteor.Collection.insecure = false; + Package.insecure = undefined; + test.equal(collection._isInsecure(), false); + + delete Package.insecure; test.equal(collection._isInsecure(), false); collection._insecure = true; test.equal(collection._isInsecure(), true); - Meteor.Collection.insecure = oldGlobalInsecure; + if (insecurePackage) + Package.insecure = insecurePackage; + else + delete Package.insecure; }); } diff --git a/packages/mongo-livedata/collection.js b/packages/mongo-livedata/collection.js index d2b903b1ab..fa98d62ad2 100644 --- a/packages/mongo-livedata/collection.js +++ b/packages/mongo-livedata/collection.js @@ -508,10 +508,10 @@ Meteor.Collection.prototype._defineMutationMethods = function() { // allow/deny semantics. If false, use insecure mode semantics. self._restricted = false; - // Insecure mode (default to allowing writes). Defaults to 'undefined' - // which means use the global Meteor.Collection.insecure. This - // property can be overriden by tests or packages wishing to change - // insecure mode behavior of their collections. + // Insecure mode (default to allowing writes). Defaults to 'undefined' which + // means insecure iff the insecure package is loaded. This property can be + // overriden by tests or packages wishing to change insecure mode behavior of + // their collections. self._insecure = undefined; self._validators = { @@ -609,7 +609,7 @@ Meteor.Collection.prototype._updateFetch = function (fields) { Meteor.Collection.prototype._isInsecure = function () { var self = this; if (self._insecure === undefined) - return Meteor.Collection.insecure; + return !!Package.insecure; return self._insecure; }; diff --git a/packages/mongo-livedata/package.js b/packages/mongo-livedata/package.js index 2ddec9ef11..33e673dd4c 100644 --- a/packages/mongo-livedata/package.js +++ b/packages/mongo-livedata/package.js @@ -20,6 +20,8 @@ Package.on_use(function (api) { ['client', 'server']); api.use('check', ['client', 'server']); + api.use('insecure', {weak: true}); + api.exportSymbol('_MongoLivedataTest', 'server'); api.add_files('mongo_driver.js', 'server'); From fa2749813989275ebe42fa323fa76cc2ca158116 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 24 Jul 2013 13:46:53 -0700 Subject: [PATCH 090/175] Make autopublish into an empty package, detected via weak deps. --- packages/accounts-base/accounts_server.js | 76 ++++++++++++----------- packages/accounts-base/package.js | 4 ++ packages/accounts-facebook/facebook.js | 1 - packages/accounts-github/github.js | 1 - packages/accounts-google/google.js | 1 - packages/accounts-meetup/meetup.js | 3 - packages/accounts-twitter/twitter.js | 1 - packages/accounts-weibo/weibo.js | 2 - packages/autopublish/autopublish.js | 1 - packages/autopublish/package.js | 6 +- packages/livedata/livedata_connection.js | 4 +- packages/livedata/livedata_server.js | 24 +------ packages/livedata/package.js | 4 ++ packages/mongo-livedata/collection.js | 12 ++-- packages/mongo-livedata/package.js | 4 ++ 15 files changed, 63 insertions(+), 81 deletions(-) delete mode 100644 packages/autopublish/autopublish.js diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index 9c26b38daf..5fdbf1737c 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -312,13 +312,14 @@ Meteor.publish(null, function() { // Accounts.addAutopublishFields Notably, this isn't implemented with // multiple publishes since DDP only merges only across top-level // fields, not subfields (such as 'services.facebook.accessToken') -autopublishFields = { +var autopublishFields = { loggedInUser: ['profile', 'username', 'emails'], otherUsers: ['profile', 'username'] }; // Add to the list of fields or subfields to be automatically -// published if autopublish is on +// published if autopublish is on. Must be called from top-level +// code (ie, before Meteor.startup hooks run). // // @param opts {Object} with: // - forLoggedInUser {Array} Array of fields published to the logged-in user @@ -330,42 +331,45 @@ Accounts.addAutopublishFields = function(opts) { autopublishFields.otherUsers, opts.forOtherUsers); }; -Meteor.server.onAutopublish(function () { - // ['profile', 'username'] -> {profile: 1, username: 1} - var toFieldSelector = function(fields) { - return _.object(_.map(fields, function(field) { - return [field, 1]; - })); - }; +if (Package.autopublish) { + // Use Meteor.startup to give other packages a chance to call + // addAutopublishFields. + Meteor.startup(function () { + // ['profile', 'username'] -> {profile: 1, username: 1} + var toFieldSelector = function(fields) { + return _.object(_.map(fields, function(field) { + return [field, 1]; + })); + }; + + Meteor.server.publish(null, function () { + if (this.userId) { + return Meteor.users.find( + {_id: this.userId}, + {fields: toFieldSelector(autopublishFields.loggedInUser)}); + } else { + return null; + } + }, /*suppress autopublish warning*/{is_auto: true}); + + // XXX this publish is neither dedup-able nor is it optimized by our special + // treatment of queries on a specific _id. Therefore this will have O(n^2) + // run-time performance every time a user document is changed (eg someone + // logging in). If this is a problem, we can instead write a manual publish + // function which filters out fields based on 'this.userId'. + Meteor.server.publish(null, function () { + var selector; + if (this.userId) + selector = {_id: {$ne: this.userId}}; + else + selector = {}; - Meteor.server.publish(null, function () { - if (this.userId) { return Meteor.users.find( - {_id: this.userId}, - {fields: toFieldSelector(autopublishFields.loggedInUser)}); - } else { - return null; - } - }, /*suppress autopublish warning*/{is_auto: true}); - - // XXX this publish is neither dedup-able nor is it optimized by our - // special treatment of queries on a specific _id. Therefore this - // will have O(n^2) run-time performance every time a user document - // is changed (eg someone logging in). If this is a problem, we can - // instead write a manual publish function which filters out fields - // based on 'this.userId'. - Meteor.server.publish(null, function () { - var selector; - if (this.userId) - selector = {_id: {$ne: this.userId}}; - else - selector = {}; - - return Meteor.users.find( - selector, - {fields: toFieldSelector(autopublishFields.otherUsers)}); - }, /*suppress autopublish warning*/{is_auto: true}); -}); + selector, + {fields: toFieldSelector(autopublishFields.otherUsers)}); + }, /*suppress autopublish warning*/{is_auto: true}); + }); +} // Publish all login service configuration fields other than secret. Meteor.publish("meteor.loginServiceConfiguration", function () { diff --git a/packages/accounts-base/package.js b/packages/accounts-base/package.js index 5bb44013fd..ec8f15eb9b 100644 --- a/packages/accounts-base/package.js +++ b/packages/accounts-base/package.js @@ -21,6 +21,10 @@ Package.on_use(function (api) { // {{currentUser}}. If not, no biggie. api.use('handlebars', 'client', {weak: true}); + // Allow us to detect 'autopublish', and publish some Meteor.users fields if + // it's loaded. + api.use('autopublish', 'server', {weak: true}); + api.exportSymbol('Accounts'); api.add_files('accounts_common.js', ['client', 'server']); diff --git a/packages/accounts-facebook/facebook.js b/packages/accounts-facebook/facebook.js index 1fb297ca25..4517bf1d5a 100644 --- a/packages/accounts-facebook/facebook.js +++ b/packages/accounts-facebook/facebook.js @@ -12,7 +12,6 @@ if (Meteor.isClient) { Facebook.requestCredential(options, credentialRequestCompleteCallback); }; } else { - Accounts.addAutopublishFields({ // publish all fields including access token, which can legitimately // be used from the client (if transmitted over ssl or on diff --git a/packages/accounts-github/github.js b/packages/accounts-github/github.js index fb0cb7f1a2..bf71477695 100644 --- a/packages/accounts-github/github.js +++ b/packages/accounts-github/github.js @@ -12,7 +12,6 @@ if (Meteor.isClient) { Github.requestCredential(options, credentialRequestCompleteCallback); }; } else { - Accounts.addAutopublishFields({ // not sure whether the github api can be used from the browser, // thus not sure if we should be sending access tokens; but we do it diff --git a/packages/accounts-google/google.js b/packages/accounts-google/google.js index 0d9a77ab6c..08538f7ae3 100644 --- a/packages/accounts-google/google.js +++ b/packages/accounts-google/google.js @@ -12,7 +12,6 @@ if (Meteor.isClient) { Google.requestCredential(options, credentialRequestCompleteCallback); }; } else { - Accounts.addAutopublishFields({ forLoggedInUser: _.map( // publish access token since it can be used from the client (if diff --git a/packages/accounts-meetup/meetup.js b/packages/accounts-meetup/meetup.js index f4ed38e482..08e8ec0938 100644 --- a/packages/accounts-meetup/meetup.js +++ b/packages/accounts-meetup/meetup.js @@ -12,7 +12,6 @@ if (Meteor.isClient) { Meetup.requestCredential(options, credentialRequestCompleteCallback); }; } else { - Accounts.addAutopublishFields({ // publish all fields including access token, which can legitimately // be used from the client (if transmitted over ssl or on @@ -20,6 +19,4 @@ if (Meteor.isClient) { forLoggedInUser: ['services.meetup'], forOtherUsers: ['services.meetup.id'] }); - - } diff --git a/packages/accounts-twitter/twitter.js b/packages/accounts-twitter/twitter.js index 19c4a9e209..176d82a655 100644 --- a/packages/accounts-twitter/twitter.js +++ b/packages/accounts-twitter/twitter.js @@ -12,7 +12,6 @@ if (Meteor.isClient) { Twitter.requestCredential(options, credentialRequestCompleteCallback); }; } else { - var autopublishedFields = _.map( // don't send access token. https://dev.twitter.com/discussions/5025 Twitter.whitelistedFields.concat(['id', 'screenName']), diff --git a/packages/accounts-weibo/weibo.js b/packages/accounts-weibo/weibo.js index f1eaf95518..f55ec19bc7 100644 --- a/packages/accounts-weibo/weibo.js +++ b/packages/accounts-weibo/weibo.js @@ -12,12 +12,10 @@ if (Meteor.isClient) { Weibo.requestCredential(options, credentialRequestCompleteCallback); }; } else { - Accounts.addAutopublishFields({ // publish all fields including access token, which can legitimately // be used from the client (if transmitted over ssl or on localhost) forLoggedInUser: ['services.weibo'], forOtherUsers: ['services.weibo.screenName'] }); - } diff --git a/packages/autopublish/autopublish.js b/packages/autopublish/autopublish.js deleted file mode 100644 index e45f4dee64..0000000000 --- a/packages/autopublish/autopublish.js +++ /dev/null @@ -1 +0,0 @@ -Meteor.server.autopublish(); diff --git a/packages/autopublish/package.js b/packages/autopublish/package.js index 9e824aa799..960a18afd8 100644 --- a/packages/autopublish/package.js +++ b/packages/autopublish/package.js @@ -2,7 +2,5 @@ Package.describe({ summary: "Automatically publish the entire database to all clients" }); -Package.on_use(function (api) { - api.use('livedata', 'server'); - api.add_files("autopublish.js", "server"); -}); +// This package is empty; its presence is detected by livedata and +// accounts-base. diff --git a/packages/livedata/livedata_connection.js b/packages/livedata/livedata_connection.js index 8efecf5af8..839e9f383a 100644 --- a/packages/livedata/livedata_connection.js +++ b/packages/livedata/livedata_connection.js @@ -906,8 +906,8 @@ _.extend(Connection.prototype, { // Mark all named subscriptions which are ready (ie, we already called the // ready callback) as needing to be revived. - // XXX We should also block reconnect quiescence until autopublish is done - // re-publishing to avoid flicker! + // XXX We should also block reconnect quiescence until unnamed subscriptions + // (eg, autopublish) are done re-publishing to avoid flicker! self._subsBeingRevived = {}; _.each(self._subscriptions, function (sub, id) { if (sub.ready) diff --git a/packages/livedata/livedata_server.js b/packages/livedata/livedata_server.js index 6627159b44..bd9fe3d6b3 100644 --- a/packages/livedata/livedata_server.js +++ b/packages/livedata/livedata_server.js @@ -1024,9 +1024,6 @@ Server = function () { self.method_handlers = {}; - self.on_autopublish = []; // array of func if AP disabled, null if enabled - self.warned_about_autopublish = false; - self.sessions = {}; // map from id to session self.stream_server = new StreamServer; @@ -1176,7 +1173,7 @@ _.extend(Server.prototype, { return; } - if (!self.on_autopublish && !options.is_auto) { + if (Package.autopublish && !options.is_auto) { // They have autopublish on, yet they're trying to manually // picking stuff to publish. They probably should turn off // autopublish. (This check isn't perfect -- if you create a @@ -1307,25 +1304,6 @@ _.extend(Server.prototype, { if (exception) throw exception; return result; - }, - - // A much more elegant way to do this would be: let any autopublish - // provider (eg, mongo-livedata) declare a weak package dependency - // on the autopublish package, then have that package simply set a - // flag that eg the Collection constructor checks, and autopublishes - // if necessary. - autopublish: function () { - var self = this; - _.each(self.on_autopublish || [], function (f) { f(); }); - self.on_autopublish = null; - }, - - onAutopublish: function (f) { - var self = this; - if (self.on_autopublish) - self.on_autopublish.push(f); - else - f(); } }); diff --git a/packages/livedata/package.js b/packages/livedata/package.js index 14c0f3c7d3..a7cb9def76 100644 --- a/packages/livedata/package.js +++ b/packages/livedata/package.js @@ -19,6 +19,10 @@ Package.on_use(function (api) { // Detect whether or not the user wants us to audit argument checks. api.use(['audit-argument-checks'], 'server', {weak: true}); + // Allow us to detect 'autopublish', so we can print a warning if the user + // runs Meteor.publish while it's loaded. + api.use('autopublish', 'server', {weak: true}); + api.exportSymbol('DDP'); api.exportSymbol('DDPServer', 'server'); diff --git a/packages/mongo-livedata/collection.js b/packages/mongo-livedata/collection.js index fa98d62ad2..9288a3d710 100644 --- a/packages/mongo-livedata/collection.js +++ b/packages/mongo-livedata/collection.js @@ -174,12 +174,12 @@ Meteor.Collection = function (name, options) { self._defineMutationMethods(); // autopublish - if (!options._preventAutopublish && - self._connection && self._connection.onAutopublish) - self._connection.onAutopublish(function () { - var handler = function () { return self.find(); }; - self._connection.publish(null, handler, {is_auto: true}); - }); + if (Package.autopublish && !options._preventAutopublish && self._connection + && self._connection.publish) { + self._connection.publish(null, function () { + return self.find(); + }, {is_auto: true}); + } }; /// diff --git a/packages/mongo-livedata/package.js b/packages/mongo-livedata/package.js index 33e673dd4c..d674e56643 100644 --- a/packages/mongo-livedata/package.js +++ b/packages/mongo-livedata/package.js @@ -20,8 +20,12 @@ Package.on_use(function (api) { ['client', 'server']); api.use('check', ['client', 'server']); + // Allow us to detect 'insecure'. api.use('insecure', {weak: true}); + // Allow us to detect 'autopublish', and publish collections if it's loaded. + api.use('autopublish', 'server', {weak: true}); + api.exportSymbol('_MongoLivedataTest', 'server'); api.add_files('mongo_driver.js', 'server'); From db51a3a14c1b7eec46389b99b3360b02514bf407 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 24 Jul 2013 19:42:33 -0700 Subject: [PATCH 091/175] Eliminate the "past" package. This package was always included in apps, and even if it was possible to remove, there wasn't a compelling story about when users would remove and replace it. Plus, not all backwards-compatibility code could even live in it (eg, field names of objects), so it was incomplete. It also introduced odd load order constraints. Instead, we introduce two conventions for backwards-compatibility code: - Special comments of the form "// XXX COMPAT WITH 0.6.4" - When feasible, put backwards-compatibility code in a file called "deprecated.js" in the relevant package. This is documented at: https://github.com/meteor/meteor/wiki/Meteor-Style-Guide#deprecated-code-and-backwards-compatibility Additionally, removed some symbols that existed for backwards compatibility with Meteor 0.4.0 (changes made 10 months ago): Meteor.is_client, Meteor.is_server, and (in a method) this.is_simulation. --- .../unfinished/jsparse-docs/jsparse-docs.js | 4 +- .../parse-inspector/parse-inspector.js | 2 +- packages/deps/deprecated.js | 18 ++++++ packages/deps/package.js | 1 + packages/livedata/livedata_common.js | 3 - .../livedata/livedata_connection_tests.js | 2 - packages/past/.gitignore | 1 - packages/past/client_past_test.js | 4 -- packages/past/package.js | 19 ------ packages/past/past.js | 58 ------------------- packages/past/server_past_test.js | 4 -- packages/random/deprecated.js | 17 ++++++ packages/random/package.js | 1 + packages/reload/deprecated.js | 8 +++ packages/reload/package.js | 1 + packages/tinytest/package.js | 5 -- tools/packages.js | 3 +- 17 files changed, 50 insertions(+), 101 deletions(-) create mode 100644 packages/deps/deprecated.js delete mode 100644 packages/past/.gitignore delete mode 100644 packages/past/client_past_test.js delete mode 100644 packages/past/package.js delete mode 100644 packages/past/past.js delete mode 100644 packages/past/server_past_test.js create mode 100644 packages/random/deprecated.js create mode 100644 packages/reload/deprecated.js diff --git a/examples/unfinished/jsparse-docs/jsparse-docs.js b/examples/unfinished/jsparse-docs/jsparse-docs.js index dc1aee019d..72b161df71 100644 --- a/examples/unfinished/jsparse-docs/jsparse-docs.js +++ b/examples/unfinished/jsparse-docs/jsparse-docs.js @@ -1,6 +1,6 @@ -if (Meteor.is_client) { +if (Meteor.isClient) { Template.page.nodespec = function (fn) { var parts = [fn()]; var replaceParts = function(regex, replacementFunc) { @@ -64,4 +64,4 @@ if (Meteor.is_client) { return new Handlebars.SafeString('
 
'); }; -} \ No newline at end of file +} diff --git a/examples/unfinished/parse-inspector/parse-inspector.js b/examples/unfinished/parse-inspector/parse-inspector.js index ca79543d51..d33a108de1 100644 --- a/examples/unfinished/parse-inspector/parse-inspector.js +++ b/examples/unfinished/parse-inspector/parse-inspector.js @@ -1,6 +1,6 @@ -if (Meteor.is_client) { +if (Meteor.isClient) { Meteor.startup(function () { if (! Session.get("input")) Session.set("input", "var x = 3"); diff --git a/packages/deps/deprecated.js b/packages/deps/deprecated.js new file mode 100644 index 0000000000..6fd2c6516c --- /dev/null +++ b/packages/deps/deprecated.js @@ -0,0 +1,18 @@ +// Deprecated (Deps-recated?) functions. + +// These functions used to be on the Meteor object (and worked slightly +// differently). +// XXX COMPAT WITH 0.5.7 +Meteor.flush = Deps.flush; +Meteor.autorun = Deps.autorun; + +// We used to require a special "autosubscribe" call to reactively subscribe to +// things. Now, it works with autorun. +// XXX COMPAT WITH 0.5.4 +Meteor.autosubscribe = Deps.autosubscribe; + +// This Deps API briefly existed in 0.5.8 and 0.5.9 +// XXX COMPAT WITH 0.5.9 +Deps.depend = function (d) { + return d.depend(); +}; diff --git a/packages/deps/package.js b/packages/deps/package.js index 40a5e79a75..e1ef519f7c 100644 --- a/packages/deps/package.js +++ b/packages/deps/package.js @@ -9,6 +9,7 @@ Package.on_use(function (api) { api.use('underscore'); api.exportSymbol('Deps'); api.add_files('deps.js'); + api.add_files('deprecated.js'); }); Package.on_test(function (api) { diff --git a/packages/livedata/livedata_common.js b/packages/livedata/livedata_common.js index b23c8b06c6..8cff12ac7a 100644 --- a/packages/livedata/livedata_common.js +++ b/packages/livedata/livedata_common.js @@ -16,9 +16,6 @@ MethodInvocation = function (options) { // zero-latency connection to the user. this.isSimulation = options.isSimulation; - // XXX Backwards compatibility only. Remove this before 1.0. - this.is_simulation = this.isSimulation; - // call this function to allow other method invocations (from the // same client) to continue running without waiting for this one to // complete. diff --git a/packages/livedata/livedata_connection_tests.js b/packages/livedata/livedata_connection_tests.js index d0c7035957..c2ff0c2f72 100644 --- a/packages/livedata/livedata_connection_tests.js +++ b/packages/livedata/livedata_connection_tests.js @@ -268,8 +268,6 @@ Tinytest.add("livedata stub - this", function (test) { startAndConnect(test, stream); conn.methods({test_this: function() { test.isTrue(this.isSimulation); - // XXX Backwards compatibility only. Remove this before 1.0. - test.isTrue(this.is_simulation); this.unblock(); // should be a no-op }}); diff --git a/packages/past/.gitignore b/packages/past/.gitignore deleted file mode 100644 index 677a6fc263..0000000000 --- a/packages/past/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.build* diff --git a/packages/past/client_past_test.js b/packages/past/client_past_test.js deleted file mode 100644 index 1f89691fbe..0000000000 --- a/packages/past/client_past_test.js +++ /dev/null @@ -1,4 +0,0 @@ -Tinytest.add("past - client", function (test) { - test.isTrue(Meteor.is_client); - test.isFalse(Meteor.is_server); -}); diff --git a/packages/past/package.js b/packages/past/package.js deleted file mode 100644 index 84b50ea3ba..0000000000 --- a/packages/past/package.js +++ /dev/null @@ -1,19 +0,0 @@ -Package.describe({ - summary: "Backwards compatibility.", - internal: true -}); - -Package.on_use(function (api) { - api.use('deps'); - api.use('reload', 'client', {weak: true}); - api.use('random'); - api.add_files('past.js', ['client', 'server']); -}); - -Package.on_test(function (api) { - api.use('past'); - api.use('tinytest'); - - api.add_files('client_past_test.js', 'client'); - api.add_files('server_past_test.js', 'server'); -}); diff --git a/packages/past/past.js b/packages/past/past.js deleted file mode 100644 index 479573ada5..0000000000 --- a/packages/past/past.js +++ /dev/null @@ -1,58 +0,0 @@ -// This file is used to set up aliases and methods to preserve backwards -// compatibility on some deprecated methods. Care should be taken when -// adding aliases and methods that the target will not be undefined, as -// the past package is loaded early. In some cases, it may be best to -// define the alias in the package it refers to. - -// Old under_score version of camelCase public API names. -// -Meteor.is_client = Meteor.isClient; -Meteor.is_server = Meteor.isServer; - -// See also the "this.is_simulation" assignment in livedata/livedata_common.js -// and the retry_count and retry_time fields of self.current_status in -// stream/stream_client.js. - - -// We used to require a special "autosubscribe" call to reactively subscribe to -// things. Now, it works with autorun. -// -Meteor.autosubscribe = Deps.autorun; - -// "new deps" back-compat -// -Meteor.flush = Deps.flush; -Meteor.autorun = Deps.autorun; - -// Deps API that briefly existed in 0.5.9 -// -Deps.depend = function (d) { - return d.depend(); -}; - -// Instead of the "random" package with Random.id(), we used to have this -// Meteor.uuid() implementing the RFC 4122 v4 UUID. -// -Meteor.uuid = function () { - var HEX_DIGITS = "0123456789abcdef"; - var s = []; - for (var i = 0; i < 36; i++) { - s[i] = Random.choice(HEX_DIGITS); - } - s[14] = "4"; - s[19] = HEX_DIGITS.substr((parseInt(s[19],16) & 0x3) | 0x8, 1); - s[8] = s[13] = s[18] = s[23] = "-"; - - var uuid = s.join(""); - return uuid; -}; - -// This is an internal API that got renamed. Be nice and try not to -// break code that uses it, even though it's internal. -if (Package.reload) { - Meteor._reload = { - onMigrate: Package.reload.Reload._onMigrate, - migrationData: Package.reload.Reload._migrationData, - reload: Package.reload.Reload._reload - }; -} diff --git a/packages/past/server_past_test.js b/packages/past/server_past_test.js deleted file mode 100644 index 412d74c3ea..0000000000 --- a/packages/past/server_past_test.js +++ /dev/null @@ -1,4 +0,0 @@ -Tinytest.add("past - server", function (test) { - test.isFalse(Meteor.is_client); - test.isTrue(Meteor.is_server); -}); diff --git a/packages/random/deprecated.js b/packages/random/deprecated.js new file mode 100644 index 0000000000..977909579c --- /dev/null +++ b/packages/random/deprecated.js @@ -0,0 +1,17 @@ +// Before this package existed, we used to use this Meteor.uuid() +// implementing the RFC 4122 v4 UUID. It is no longer documented +// and will go away. +// XXX COMPAT WITH 0.5.6 +Meteor.uuid = function () { + var HEX_DIGITS = "0123456789abcdef"; + var s = []; + for (var i = 0; i < 36; i++) { + s[i] = Random.choice(HEX_DIGITS); + } + s[14] = "4"; + s[19] = HEX_DIGITS.substr((parseInt(s[19],16) & 0x3) | 0x8, 1); + s[8] = s[13] = s[18] = s[23] = "-"; + + var uuid = s.join(""); + return uuid; +}; diff --git a/packages/random/package.js b/packages/random/package.js index 4525c8be22..4a23e0665b 100644 --- a/packages/random/package.js +++ b/packages/random/package.js @@ -7,6 +7,7 @@ Package.on_use(function (api) { api.use('underscore'); api.exportSymbol('Random'); api.add_files('random.js'); + api.add_files('deprecated.js'); }); Package.on_test(function(api) { diff --git a/packages/reload/deprecated.js b/packages/reload/deprecated.js new file mode 100644 index 0000000000..a815626d42 --- /dev/null +++ b/packages/reload/deprecated.js @@ -0,0 +1,8 @@ +// Reload functionality used to live on Meteor._reload. Be nice and try not to +// break code that uses it, even though it's internal. +// XXX COMPAT WITH 0.6.4 +Meteor._reload = { + onMigrate: Reload._onMigrate, + migrationData: Reload._migrationData, + reload: Reload._reload +}; diff --git a/packages/reload/package.js b/packages/reload/package.js index f0e52f8306..ac16323a00 100644 --- a/packages/reload/package.js +++ b/packages/reload/package.js @@ -7,4 +7,5 @@ Package.on_use(function (api) { api.use(['underscore', 'logging', 'json'], 'client'); api.exportSymbol('Reload', 'client'); api.add_files('reload.js', 'client'); + api.add_files('deprecated.js', 'client'); }); diff --git a/packages/tinytest/package.js b/packages/tinytest/package.js index 964214a2d0..84a1e4f97e 100644 --- a/packages/tinytest/package.js +++ b/packages/tinytest/package.js @@ -4,11 +4,6 @@ Package.describe({ }); Package.on_use(function (api) { - // "past" is always included before app code (see initFromAppDir) but not - // before packages when testing. This makes sure that tests see - // backward-compatibility hooks, at least if they use tinytest. - api.use('past'); - api.use('underscore', ['client', 'server']); api.use('random', ['client', 'server']); diff --git a/tools/packages.js b/tools/packages.js index 175fb72d18..e186a5a9df 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -1716,8 +1716,7 @@ _.extend(Package.prototype, { // standard client packages for the classic meteor stack. // XXX remove and make everyone explicitly declare all dependencies ['meteor', 'webapp', 'logging', 'deps', 'session', - 'livedata', 'mongo-livedata', 'spark', 'templating', - 'past', 'check'], + 'livedata', 'mongo-livedata', 'spark', 'templating', 'check'], project.get_packages(appDir)); var arch = sliceName === "server" ? "native" : "browser"; From ce16794cbb610764752da82e54118be802330f9d Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 24 Jul 2013 19:55:05 -0700 Subject: [PATCH 092/175] Push through on the Meteor.connect -> DDP.connect rename. --- History.md | 5 ++ docs/client/api.html | 9 +-- docs/client/api.js | 2 +- docs/client/docs.js | 2 +- .../client/leaderboard-remote.js | 2 +- packages/ctl-helper/ctl-helper.js | 2 +- packages/madewith/madewith.js | 2 +- tools/deploy-galaxy.js | 73 +++++++++---------- tools/unipackage.js | 6 +- 9 files changed, 48 insertions(+), 55 deletions(-) diff --git a/History.md b/History.md index 629e02939a..5fbc76c49c 100644 --- a/History.md +++ b/History.md @@ -18,6 +18,11 @@ * Delete login tokens from server when user logs out. +* Renames (may require doc updates): + - `Meteor.default_connection` - `Meteor.connection` + - `Meteor.default_server` - `Meteor.server` + - `Meteor.connect` - `DDP.connect` + ## v0.6.4.1 diff --git a/docs/client/api.html b/docs/client/api.html index 4de42330b9..7e3f0600ec 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -466,8 +466,8 @@ updates are not required. {{> api_box connect}} To call methods on another Meteor application or subscribe to its data -sets, call `Meteor.connect` with the URL of the application. -`Meteor.connect` returns an object which provides: +sets, call `DDP.connect` with the URL of the application. +`DDP.connect` returns an object which provides: * `subscribe` - Subscribe to a record set. See @@ -497,11 +497,6 @@ When you call `Meteor.subscribe`, `Meteor.status`, `Meteor.call`, and `Meteor.apply`, you are using a connection back to that default server. -{{#warning}} -In this release, `Meteor.connect` can only be called on the client. -Servers can not yet connect to other servers. -{{/warning}} -

Collections

Meteor stores data in *collections*. To get started, declare a diff --git a/docs/client/api.js b/docs/client/api.js index f5a6c0eb2a..ca22a2b3e4 100644 --- a/docs/client/api.js +++ b/docs/client/api.js @@ -457,7 +457,7 @@ Template.api.disconnect = { Template.api.connect = { id: "meteor_connect", - name: "Meteor.connect(url)", + name: "DDP.connect(url)", locus: "Client", descr: ["Connect to the server of a different Meteor application to subscribe to its document sets and invoke its remote methods."], args: [ diff --git a/docs/client/docs.js b/docs/client/docs.js index d78b9aef03..08a888e911 100644 --- a/docs/client/docs.js +++ b/docs/client/docs.js @@ -140,7 +140,7 @@ var toc = [ "Meteor.status", "Meteor.reconnect", "Meteor.disconnect", - "Meteor.connect" + "DDP.connect" ], {name: "Collections", id: "collections"}, [ diff --git a/examples/unfinished/leaderboard-remote/client/leaderboard-remote.js b/examples/unfinished/leaderboard-remote/client/leaderboard-remote.js index 29825f1ae3..81c4ca9c96 100644 --- a/examples/unfinished/leaderboard-remote/client/leaderboard-remote.js +++ b/examples/unfinished/leaderboard-remote/client/leaderboard-remote.js @@ -1,4 +1,4 @@ -Leaderboard = Meteor.connect("http://leader2.meteor.com/sockjs"); +Leaderboard = DDP.connect("http://leader2.meteor.com/sockjs"); // XXX I'd rather this be Leaderboard.Players.. can this API be easier? Players = new Meteor.Collection("players", {manager: Leaderboard}); diff --git a/packages/ctl-helper/ctl-helper.js b/packages/ctl-helper/ctl-helper.js index 4b484be28c..3e43e5fec3 100644 --- a/packages/ctl-helper/ctl-helper.js +++ b/packages/ctl-helper/ctl-helper.js @@ -86,7 +86,7 @@ _.extend(Ctl, { "\n" + "For now, the GALAXY environment variable must be set to the location of\n" + "your Galaxy management server (Ultraworld.) This string is in the same\n" + - "format as the argument to Meteor.connect().\n" + + "format as the argument to DDP.connect().\n" + "\n" + "Commands:\n"); _.each(Ctl.Commands, function (cmd) { diff --git a/packages/madewith/madewith.js b/packages/madewith/madewith.js index 297d8d9a29..956296a1ad 100644 --- a/packages/madewith/madewith.js +++ b/packages/madewith/madewith.js @@ -4,7 +4,7 @@ var match = hostname.match(/(.*)\.meteor.com$/); var shortname = match ? match[1] : hostname; // connect to madewith and subscribe to my app's record -var server = Meteor.connect("madewith.meteor.com"); +var server = DDP.connect("madewith.meteor.com"); var sub = server.subscribe("myApp", hostname); // minimongo collection to hold my singleton app record. diff --git a/tools/deploy-galaxy.js b/tools/deploy-galaxy.js index 37ebfc6034..09aab921ba 100644 --- a/tools/deploy-galaxy.js +++ b/tools/deploy-galaxy.js @@ -9,47 +9,39 @@ var request = require('request'); var _ = require('underscore'); // a bit of a hack -var _meteor; -var getMeteor = function (context) { - if (! _meteor) { - _meteor = unipackage.load({ - library: context.library, - packages: [ 'livedata', 'mongo-livedata' ], - release: context.releaseVersion - }).meteor.Meteor; +var getPackage = _.once(function (context) { + return unipackage.load({ + library: context.library, + packages: [ 'meteor', 'livedata', 'mongo-livedata' ], + release: context.releaseVersion + }); +}); + +var getGalaxy = _.once(function (context) { + var Package = getPackage(context); + if (!context.galaxy) { + process.stderr.write("Could not find a deploy endpoint. " + + "You can set the GALAXY environment variable, " + + "or configure your site's DNS to resolve to " + + "your Galaxy's proxy.\n"); + process.exit(1); } - return _meteor; -}; - -var _galaxy; -var getGalaxy = function (context) { - if (! _galaxy) { - var Meteor = getMeteor(context); - if (!context.galaxy) { - process.stderr.write("Could not find a deploy endpoint. " + - "You can set the GALAXY environment variable, " + - "or configure your site's DNS to resolve to " + - "your Galaxy's proxy.\n"); + var galaxy = Package.livedata.DDP.connect(context.galaxy.url); + var timeout = Package.meteor.Meteor.setTimeout(function () { + if (galaxy.status().status !== "connected") { + process.stderr.write("Could not connect to galaxy " + context.galaxy.url + + ": " + galaxy.status().status + '\n'); process.exit(1); } - - _galaxy = Meteor.connect(context.galaxy.url); - var timeout = Meteor.setTimeout(function () { - if (_galaxy.status().status !== "connected") { - process.stderr.write("Could not connect to galaxy " + context.galaxy.url + ": " + _galaxy.status().status + '\n'); - process.exit(1); - } - }, 10*1000); - var close = _galaxy.close; - _galaxy.close = function (/*arguments*/) { - Meteor.clearTimeout(timeout); - close.apply(_galaxy, arguments); - }; - } - - return _galaxy; -}; + }, 10*1000); + var close = galaxy.close; + galaxy.close = function (/*arguments*/) { + Package.meteor.Meteor.clearTimeout(timeout); + close.apply(galaxy, arguments); + }; + return galaxy; +}); var exitWithError = function (error, messages) { @@ -136,7 +128,7 @@ exports.deleteApp = function (context) { // in --star mode. exports.deploy = function (options) { var galaxy = getGalaxy(options.context); - var Meteor = getMeteor(options.context); + var Package = getPackage(options.context); var tmpdir = files.mkdtemp('deploy'); var buildDir = path.join(tmpdir, 'build'); @@ -182,7 +174,7 @@ exports.deploy = function (options) { try { galaxy.call('createApp', options.app, appConfig); } catch (e) { - if (e instanceof Meteor.Error && e.error === 'already-exists') { + if (e instanceof Package.meteor.Meteor.Error && e.error === 'already-exists') { // Cool, it already exists. No problem. Just set the settings if they were // passed. We explicitly check for undefined because we want to allow you // to unset settings by passing an empty file. @@ -260,7 +252,8 @@ exports.logs = function (options) { } var lastLogId = null; - var logReader = getMeteor(options.context).connect(logReaderURL); + var logReader = + getPackage(options.context).livedata.DDP.connect(logReaderURL); var Log = unipackage.load({ library: options.context.library, packages: [ 'logging' ], diff --git a/tools/unipackage.js b/tools/unipackage.js index aa13d82f71..ff4d9e5786 100644 --- a/tools/unipackage.js +++ b/tools/unipackage.js @@ -31,12 +31,12 @@ var bundler = require('./bundler.js'); // environment) // // Example usage: -// var Meteor = require('./unipackage.js').load({ +// var DDP = require('./unipackage.js').load({ // library: context.library, // packages: ['livedata'], // release: context.releaseVersion -// }).meteor.Meteor; -// var reverse = Meteor.connect('reverse.meteor.com'); +// }).livedata.DDP; +// var reverse = DDP.connect('reverse.meteor.com'); // console.log(reverse.call('reverse', 'hello world')); var cacheLibrary = null; From a5c061ecdedacd205ca15d3c8c33492a40db97aa Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 24 Jul 2013 20:13:17 -0700 Subject: [PATCH 093/175] Rename Meteor.http to HTTP. Backwards compatible. --- History.md | 1 + docs/client/api.html | 22 +++++++------- docs/client/api.js | 18 ++++++------ docs/client/docs.js | 12 ++++---- packages/facebook/facebook_server.js | 4 +-- packages/facebook/package.js | 2 +- packages/github/github_server.js | 4 +-- packages/github/package.js | 2 +- packages/google/google_server.js | 4 +-- packages/google/package.js | 2 +- packages/http/deprecated.js | 3 ++ packages/http/httpcall_client.js | 2 +- packages/http/httpcall_common.js | 19 ++++++------ packages/http/httpcall_server.js | 8 ++--- packages/http/httpcall_tests.js | 44 ++++++++++++++-------------- packages/http/package.js | 2 ++ packages/livedata/stream_tests.js | 6 ++-- packages/meetup/meetup_server.js | 4 +-- packages/meetup/package.js | 2 +- packages/oauth1/oauth1_binding.js | 2 +- packages/oauth1/package.js | 1 + packages/test-in-console/package.js | 2 +- packages/test-in-console/reporter.js | 2 +- packages/weibo/package.js | 2 +- packages/weibo/weibo_server.js | 4 +-- 25 files changed, 89 insertions(+), 85 deletions(-) create mode 100644 packages/http/deprecated.js diff --git a/History.md b/History.md index 5fbc76c49c..d0c3e0a30a 100644 --- a/History.md +++ b/History.md @@ -22,6 +22,7 @@ - `Meteor.default_connection` - `Meteor.connection` - `Meteor.default_server` - `Meteor.server` - `Meteor.connect` - `DDP.connect` + - `Meteor.http` - `HTTP` ## v0.6.4.1 diff --git a/docs/client/api.html b/docs/client/api.html index 7e3f0600ec..618d2f3a7a 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -2814,9 +2814,9 @@ For example, the `toJSONValue` method for return this.toHexString(); }; -

Meteor.http

+

HTTP

-`Meteor.http` provides an HTTP API on the client and server. To use +`HTTP` provides an HTTP request API on the client and server. To use these functions, add the HTTP package to your project with `$ meteor add http`. @@ -2887,8 +2887,8 @@ Example server method: Meteor.methods({checkTwitter: function (userId) { check(userId, String); this.unblock(); - var result = Meteor.http.call("GET", "http://api.twitter.com/xyz", - {params: {user: userId}}); + var result = HTTP.call("GET", "http://api.twitter.com/xyz", + {params: {user: userId}}); if (result.statusCode === 200) return true return false; @@ -2896,13 +2896,13 @@ Example server method: Example asynchronous HTTP call: - Meteor.http.call("POST", "http://api.twitter.com/xyz", - {data: {some: "json", stuff: 1}}, - function (error, result) { - if (result.statusCode === 200) { - Session.set("twizzled", true); - } - }); + HTTP.call("POST", "http://api.twitter.com/xyz", + {data: {some: "json", stuff: 1}}, + function (error, result) { + if (result.statusCode === 200) { + Session.set("twizzled", true); + } + }); {{> api_box http_get}} diff --git a/docs/client/api.js b/docs/client/api.js index ca22a2b3e4..39503a281e 100644 --- a/docs/client/api.js +++ b/docs/client/api.js @@ -1585,7 +1585,7 @@ Template.api.equals = { Template.api.httpcall = { id: "meteor_http_call", - name: "Meteor.http.call(method, url [, options] [, asyncCallback])", + name: "HTTP.call(method, url [, options] [, asyncCallback])", locus: "Anywhere", descr: ["Perform an outbound HTTP request."], args: [ @@ -1632,30 +1632,30 @@ Template.api.httpcall = { Template.api.http_get = { id: "meteor_http_get", - name: "Meteor.http.get(url, [options], [asyncCallback])", + name: "HTTP.get(url, [options], [asyncCallback])", locus: "Anywhere", - descr: ["Send an HTTP GET request. Equivalent to `Meteor.http.call(\"GET\", ...)`."] + descr: ["Send an HTTP GET request. Equivalent to `HTTP.call(\"GET\", ...)`."] }; Template.api.http_post = { id: "meteor_http_post", - name: "Meteor.http.post(url, [options], [asyncCallback])", + name: "HTTP.post(url, [options], [asyncCallback])", locus: "Anywhere", - descr: ["Send an HTTP POST request. Equivalent to `Meteor.http.call(\"POST\", ...)`."] + descr: ["Send an HTTP POST request. Equivalent to `HTTP.call(\"POST\", ...)`."] }; Template.api.http_put = { id: "meteor_http_put", - name: "Meteor.http.put(url, [options], [asyncCallback])", + name: "HTTP.put(url, [options], [asyncCallback])", locus: "Anywhere", - descr: ["Send an HTTP PUT request. Equivalent to `Meteor.http.call(\"PUT\", ...)`."] + descr: ["Send an HTTP PUT request. Equivalent to `HTTP.call(\"PUT\", ...)`."] }; Template.api.http_del = { id: "meteor_http_del", - name: "Meteor.http.del(url, [options], [asyncCallback])", + name: "HTTP.del(url, [options], [asyncCallback])", locus: "Anywhere", - descr: ["Send an HTTP DELETE request. Equivalent to `Meteor.http.call(\"DELETE\", ...)`. (Named `del` to avoid conflict with JavaScript's `delete`.)"] + descr: ["Send an HTTP DELETE request. Equivalent to `HTTP.call(\"DELETE\", ...)`. (Named `del` to avoid conflict with JavaScript's `delete`.)"] }; diff --git a/docs/client/docs.js b/docs/client/docs.js index 08a888e911..a04f6659a1 100644 --- a/docs/client/docs.js +++ b/docs/client/docs.js @@ -306,12 +306,12 @@ var toc = [ ], - "Meteor.http", [ - "Meteor.http.call", - {name: "Meteor.http.get", id: "meteor_http_get"}, - {name: "Meteor.http.post", id: "meteor_http_post"}, - {name: "Meteor.http.put", id: "meteor_http_put"}, - {name: "Meteor.http.del", id: "meteor_http_del"} + "HTTP", [ + "HTTP.call", + {name: "HTTP.get", id: "meteor_http_get"}, + {name: "HTTP.post", id: "meteor_http_post"}, + {name: "HTTP.put", id: "meteor_http_put"}, + {name: "HTTP.del", id: "meteor_http_del"} ], "Email", [ "Email.send" diff --git a/packages/facebook/facebook_server.js b/packages/facebook/facebook_server.js index 0775a4f71f..088f7eaf85 100644 --- a/packages/facebook/facebook_server.js +++ b/packages/facebook/facebook_server.js @@ -49,7 +49,7 @@ var getTokenResponse = function (query) { var responseContent; try { // Request an access token - responseContent = Meteor.http.get( + responseContent = HTTP.get( "https://graph.facebook.com/oauth/access_token", { params: { client_id: config.appId, @@ -86,7 +86,7 @@ var getTokenResponse = function (query) { var getIdentity = function (accessToken) { try { - return Meteor.http.get("https://graph.facebook.com/me", { + return HTTP.get("https://graph.facebook.com/me", { params: {access_token: accessToken}}).data; } catch (err) { throw new Error("Failed to fetch identity from Facebook. " + err.message); diff --git a/packages/facebook/package.js b/packages/facebook/package.js index 682f3afcff..d9254525c3 100644 --- a/packages/facebook/package.js +++ b/packages/facebook/package.js @@ -8,7 +8,7 @@ Package.describe({ Package.on_use(function(api) { api.use('oauth2', ['client', 'server']); api.use('oauth', ['client', 'server']); - api.use('http', ['client', 'server']); + api.use('http', ['server']); api.use('templating', 'client'); api.use('underscore', 'server'); api.use('random', 'client'); diff --git a/packages/github/github_server.js b/packages/github/github_server.js index 6e789d6372..669b51dd42 100644 --- a/packages/github/github_server.js +++ b/packages/github/github_server.js @@ -28,7 +28,7 @@ var getAccessToken = function (query) { var response; try { - response = Meteor.http.post( + response = HTTP.post( "https://github.com/login/oauth/access_token", { headers: { Accept: 'application/json', @@ -54,7 +54,7 @@ var getAccessToken = function (query) { var getIdentity = function (accessToken) { try { - return Meteor.http.get( + return HTTP.get( "https://api.github.com/user", { headers: {"User-Agent": userAgent}, // http://developer.github.com/v3/#user-agent-required params: {access_token: accessToken} diff --git a/packages/github/package.js b/packages/github/package.js index 4217bedd12..99850e3756 100644 --- a/packages/github/package.js +++ b/packages/github/package.js @@ -8,7 +8,7 @@ Package.describe({ Package.on_use(function(api) { api.use('oauth2', ['client', 'server']); api.use('oauth', ['client', 'server']); - api.use('http', ['client', 'server']); + api.use('http', ['server']); api.use('underscore', 'client'); api.use('templating', 'client'); api.use('random', 'client'); diff --git a/packages/google/google_server.js b/packages/google/google_server.js index d180cfa99f..eba4263e5b 100644 --- a/packages/google/google_server.js +++ b/packages/google/google_server.js @@ -42,7 +42,7 @@ var getTokens = function (query) { var response; try { - response = Meteor.http.post( + response = HTTP.post( "https://accounts.google.com/o/oauth2/token", {params: { code: query.code, client_id: config.clientId, @@ -67,7 +67,7 @@ var getTokens = function (query) { var getIdentity = function (accessToken) { try { - return Meteor.http.get( + return HTTP.get( "https://www.googleapis.com/oauth2/v1/userinfo", {params: {access_token: accessToken}}).data; } catch (err) { diff --git a/packages/google/package.js b/packages/google/package.js index 9dfe3d480d..a88dfe31d8 100644 --- a/packages/google/package.js +++ b/packages/google/package.js @@ -8,7 +8,7 @@ Package.describe({ Package.on_use(function(api) { api.use('oauth2', ['client', 'server']); api.use('oauth', ['client', 'server']); - api.use('http', ['client', 'server']); + api.use('http', ['server']); api.use(['underscore', 'service-configuration'], ['client', 'server']); api.use(['random', 'templating'], 'client'); diff --git a/packages/http/deprecated.js b/packages/http/deprecated.js new file mode 100644 index 0000000000..d49438ab9f --- /dev/null +++ b/packages/http/deprecated.js @@ -0,0 +1,3 @@ +// The HTTP object used to be called Meteor.http. +// XXX COMPAT WITH 0.6.4 +Meteor.http = HTTP; diff --git a/packages/http/httpcall_client.js b/packages/http/httpcall_client.js index 1d064c9491..7c33ca2a09 100644 --- a/packages/http/httpcall_client.js +++ b/packages/http/httpcall_client.js @@ -1,4 +1,4 @@ -Meteor.http.call = function(method, url, options, callback) { +HTTP.call = function(method, url, options, callback) { ////////// Process arguments ////////// diff --git a/packages/http/httpcall_common.js b/packages/http/httpcall_common.js index ddbad0c41c..a2fe4bdd2b 100644 --- a/packages/http/httpcall_common.js +++ b/packages/http/httpcall_common.js @@ -67,21 +67,20 @@ populateData = function(response) { } }; -// XXX rename to HTTP -Meteor.http = {}; +HTTP = {}; -Meteor.http.get = function (/* varargs */) { - return Meteor.http.call.apply(this, ["GET"].concat(_.toArray(arguments))); +HTTP.get = function (/* varargs */) { + return HTTP.call.apply(this, ["GET"].concat(_.toArray(arguments))); }; -Meteor.http.post = function (/* varargs */) { - return Meteor.http.call.apply(this, ["POST"].concat(_.toArray(arguments))); +HTTP.post = function (/* varargs */) { + return HTTP.call.apply(this, ["POST"].concat(_.toArray(arguments))); }; -Meteor.http.put = function (/* varargs */) { - return Meteor.http.call.apply(this, ["PUT"].concat(_.toArray(arguments))); +HTTP.put = function (/* varargs */) { + return HTTP.call.apply(this, ["PUT"].concat(_.toArray(arguments))); }; -Meteor.http.del = function (/* varargs */) { - return Meteor.http.call.apply(this, ["DELETE"].concat(_.toArray(arguments))); +HTTP.del = function (/* varargs */) { + return HTTP.call.apply(this, ["DELETE"].concat(_.toArray(arguments))); }; diff --git a/packages/http/httpcall_server.js b/packages/http/httpcall_server.js index 159ccae7a2..212508367f 100644 --- a/packages/http/httpcall_server.js +++ b/packages/http/httpcall_server.js @@ -1,12 +1,10 @@ -Meteor.http = Meteor.http || {}; - var path = Npm.require('path'); var request = Npm.require('request'); var url_util = Npm.require('url'); -// _call always runs asynchronously; Meteor.http.call, defined below, +// _call always runs asynchronously; HTTP.call, defined below, // wraps _call and runs synchronously when no callback is provided. -_call = function(method, url, options, callback) { +var _call = function(method, url, options, callback) { ////////// Process arguments ////////// @@ -106,4 +104,4 @@ _call = function(method, url, options, callback) { }); }; -Meteor.http.call = Meteor._wrapAsync(_call); +HTTP.call = Meteor._wrapAsync(_call); diff --git a/packages/http/httpcall_tests.js b/packages/http/httpcall_tests.js index fddf48b032..13fd694c1c 100644 --- a/packages/http/httpcall_tests.js +++ b/packages/http/httpcall_tests.js @@ -41,12 +41,12 @@ testAsyncMulti("httpcall - basic", [ }; - Meteor.http.call("GET", url_prefix()+url, options, expect(callback)); + HTTP.call("GET", url_prefix()+url, options, expect(callback)); if (Meteor.isServer) { // test sync version try { - var result = Meteor.http.call("GET", url_prefix()+url, options); + var result = HTTP.call("GET", url_prefix()+url, options); callback(undefined, result); } catch (e) { callback(e, e.response); @@ -89,12 +89,12 @@ testAsyncMulti("httpcall - errors", [ test.isFalse(result); test.isFalse(error.response); }; - Meteor.http.call("GET", "http://asfd.asfd/", expect(unknownServerCallback)); + HTTP.call("GET", "http://asfd.asfd/", expect(unknownServerCallback)); if (Meteor.isServer) { // test sync version try { - var unknownServerResult = Meteor.http.call("GET", "http://asfd.asfd/"); + var unknownServerResult = HTTP.call("GET", "http://asfd.asfd/"); unknownServerCallback(undefined, unknownServerResult); } catch (e) { unknownServerCallback(e, e.response); @@ -119,12 +119,12 @@ testAsyncMulti("httpcall - errors", [ test.isTrue(error.response.content.length > 180); test.isTrue(error.message.length < 180); // make sure we truncate. }; - Meteor.http.call("GET", url_prefix()+"/fail", expect(error500Callback)); + HTTP.call("GET", url_prefix()+"/fail", expect(error500Callback)); if (Meteor.isServer) { // test sync version try { - var error500Result = Meteor.http.call("GET", url_prefix()+"/fail"); + var error500Result = HTTP.call("GET", url_prefix()+"/fail"); error500Callback(undefined, error500Result); } catch (e) { error500Callback(e, e.response); @@ -143,7 +143,7 @@ testAsyncMulti("httpcall - timeout", [ test.isFalse(error.response); }; var timeoutUrl = url_prefix()+"/slow-"+Random.id(); - Meteor.http.call( + HTTP.call( "GET", timeoutUrl, { timeout: 500 }, expect(timeoutCallback)); @@ -151,7 +151,7 @@ testAsyncMulti("httpcall - timeout", [ if (Meteor.isServer) { // test sync version try { - var timeoutResult = Meteor.http.call("GET", timeoutUrl, { timeout: 500 }); + var timeoutResult = HTTP.call("GET", timeoutUrl, { timeout: 500 }); timeoutCallback(undefined, timeoutResult); } catch (e) { timeoutCallback(e, e.response); @@ -168,7 +168,7 @@ testAsyncMulti("httpcall - timeout", [ test.equal(data.method, "GET"); }; var noTimeoutUrl = url_prefix()+"/foo-"+Random.id(); - Meteor.http.call( + HTTP.call( "GET", noTimeoutUrl, { timeout: 2000 }, expect(noTimeoutCallback)); @@ -176,7 +176,7 @@ testAsyncMulti("httpcall - timeout", [ if (Meteor.isServer) { // test sync version try { - var noTimeoutResult = Meteor.http.call("GET", noTimeoutUrl, { timeout: 2000 }); + var noTimeoutResult = HTTP.call("GET", noTimeoutUrl, { timeout: 2000 }); noTimeoutCallback(undefined, noTimeoutResult); } catch (e) { noTimeoutCallback(e, e.response); @@ -189,7 +189,7 @@ testAsyncMulti("httpcall - redirect", [ function(test, expect) { // Test that we follow redirects by default - Meteor.http.call("GET", url_prefix()+"/redirect", expect( + HTTP.call("GET", url_prefix()+"/redirect", expect( function(error, result) { test.isFalse(error); test.isTrue(result); @@ -205,7 +205,7 @@ testAsyncMulti("httpcall - redirect", [ _.each([false, true], function(followRedirects) { var do_it = function(should_work) { var maybe_expect = should_work ? expect : _.identity; - Meteor.http.call( + HTTP.call( "GET", url_prefix()+"/redirect", {followRedirects: followRedirects}, maybe_expect(function(error, result) { @@ -241,7 +241,7 @@ testAsyncMulti("httpcall - methods", [ // non-get methods var test_method = function(meth, func_name) { func_name = func_name || meth.toLowerCase(); - Meteor.http[func_name]( + HTTP[func_name]( url_prefix()+"/foo", expect(function(error, result) { test.isFalse(error); @@ -266,7 +266,7 @@ testAsyncMulti("httpcall - methods", [ function(test, expect) { // contents and data - Meteor.http.call( + HTTP.call( "POST", url_prefix()+"/foo", { content: "Hello World!" }, expect(function(error, result) { @@ -277,7 +277,7 @@ testAsyncMulti("httpcall - methods", [ test.equal(data.body, "Hello World!"); })); - Meteor.http.call( + HTTP.call( "POST", url_prefix()+"/data-test", { data: {greeting: "Hello World!"} }, expect(function(error, result) { @@ -290,7 +290,7 @@ testAsyncMulti("httpcall - methods", [ test.matches(data.headers['content-type'], /^application\/json\b/); })); - Meteor.http.call( + HTTP.call( "POST", url_prefix()+"/data-test-explicit", { data: {greeting: "Hello World!"}, headers: {'Content-Type': 'text/stupid'} }, @@ -319,7 +319,7 @@ testAsyncMulti("httpcall - http auth", [ // https://bugzilla.mozilla.org/show_bug.cgi?id=654348 var password = 'rocks'; //var password = Random.id().replace(/[^0-9a-zA-Z]/g, ''); - Meteor.http.call( + HTTP.call( "GET", url_prefix()+"/login?"+password, { auth: "meteor:"+password }, expect(function(error, result) { @@ -333,7 +333,7 @@ testAsyncMulti("httpcall - http auth", [ // test fail on malformed username:password test.throws(function() { - Meteor.http.call( + HTTP.call( "GET", url_prefix()+"/login?"+password, { auth: "fooooo" }, function() { throw new Error("can't get here"); }); @@ -343,7 +343,7 @@ testAsyncMulti("httpcall - http auth", [ testAsyncMulti("httpcall - headers", [ function(test, expect) { - Meteor.http.call( + HTTP.call( "GET", url_prefix()+"/foo-with-headers", {headers: { "Test-header": "Value", "another": "Value2" } }, @@ -359,7 +359,7 @@ testAsyncMulti("httpcall - headers", [ test.equal(data.headers['another'], "Value2"); })); - Meteor.http.call( + HTTP.call( "GET", url_prefix()+"/headers", expect(function(error, result) { test.isFalse(error); @@ -383,7 +383,7 @@ testAsyncMulti("httpcall - params", [ } else { opts = opt_opts; } - Meteor.http.call( + HTTP.call( method, url_prefix()+url, _.extend({ params: params }, opts), expect(function(error, result) { @@ -431,7 +431,7 @@ if (Meteor.isServer) { WebApp.suppressConnectErrors(); var do_test = function (path, code, match) { - Meteor.http.get( + HTTP.get( url_base() + path, {headers: {'x-suppress-error': 'true'}}, expect(function(error, result) { diff --git a/packages/http/package.js b/packages/http/package.js index 4e35051dee..c77be43c12 100644 --- a/packages/http/package.js +++ b/packages/http/package.js @@ -4,9 +4,11 @@ Package.describe({ Package.on_use(function (api) { api.use('underscore'); + api.exportSymbol('HTTP'); api.add_files('httpcall_common.js', ['client', 'server']); api.add_files('httpcall_client.js', 'client'); api.add_files('httpcall_server.js', 'server'); + api.add_files('deprecated.js', ['client', 'server']); }); Package.on_test(function (api) { diff --git a/packages/livedata/stream_tests.js b/packages/livedata/stream_tests.js index 89eaf0f847..25e9eeada6 100644 --- a/packages/livedata/stream_tests.js +++ b/packages/livedata/stream_tests.js @@ -127,7 +127,7 @@ testAsyncMulti("stream - /websocket is a websocket endpoint", [ // Verify that /websocket and /websocket/ don't return the main page // _.each(['/websocket', '/websocket/'], function(path) { - Meteor.http.get(Meteor._relativeToSiteRootUrl(path), expect(function(error, result) { + HTTP.get(Meteor._relativeToSiteRootUrl(path), expect(function(error, result) { test.isNotNull(error); test.equal('Can "Upgrade" only to "WebSocket".', result.content); })); @@ -145,12 +145,12 @@ testAsyncMulti("stream - /websocket is a websocket endpoint", [ test.equal(pageContent, result.content); }); - Meteor.http.get(Meteor._relativeToSiteRootUrl('/'), expect(function(error, result) { + HTTP.get(Meteor._relativeToSiteRootUrl('/'), expect(function(error, result) { test.isNull(error); pageContent = result.content; _.each(['/websockets', '/websockets/'], function(path) { - Meteor.http.get(Meteor._relativeToSiteRootUrl(path), wrappedCallback); + HTTP.get(Meteor._relativeToSiteRootUrl(path), wrappedCallback); }); })); } diff --git a/packages/meetup/meetup_server.js b/packages/meetup/meetup_server.js index e3a2bb7094..cdd2b49a71 100644 --- a/packages/meetup/meetup_server.js +++ b/packages/meetup/meetup_server.js @@ -21,7 +21,7 @@ var getAccessToken = function (query) { var response; try { - response = Meteor.http.post( + response = HTTP.post( "https://secure.meetup.com/oauth2/access", {headers: {Accept: 'application/json'}, params: { code: query.code, client_id: config.clientId, @@ -43,7 +43,7 @@ var getAccessToken = function (query) { var getIdentity = function (accessToken) { try { - var response = Meteor.http.get( + var response = HTTP.get( "https://secure.meetup.com/2/members", {params: {member_id: 'self', access_token: accessToken}}); return response.data.results && response.data.results[0]; diff --git a/packages/meetup/package.js b/packages/meetup/package.js index 41a75c91c5..188ea07951 100644 --- a/packages/meetup/package.js +++ b/packages/meetup/package.js @@ -8,7 +8,7 @@ Package.describe({ Package.on_use(function(api) { api.use('oauth2', ['client', 'server']); api.use('oauth', ['client', 'server']); - api.use('http', ['client', 'server']); + api.use('http', ['server']); api.use('templating', 'client'); api.use('underscore', 'client'); api.use('random', 'client'); diff --git a/packages/oauth1/oauth1_binding.js b/packages/oauth1/oauth1_binding.js index 59c0f2b211..ddef113359 100644 --- a/packages/oauth1/oauth1_binding.js +++ b/packages/oauth1/oauth1_binding.js @@ -117,7 +117,7 @@ OAuth1Binding.prototype._call = function(method, url, headers, params) { // Make signed request try { - return Meteor.http.call(method, url, { + return HTTP.call(method, url, { params: params, headers: { Authorization: authString diff --git a/packages/oauth1/package.js b/packages/oauth1/package.js index 0edbe0e107..ce76adfaa8 100644 --- a/packages/oauth1/package.js +++ b/packages/oauth1/package.js @@ -8,6 +8,7 @@ Package.on_use(function (api) { api.use('service-configuration', ['client', 'server']); api.use('oauth', ['client', 'server']); api.use('underscore', 'server'); + api.use('http', 'server'); api.exportSymbol('OAuth1Binding', 'server'); api.exportSymbol('_OAuth1Test', 'server'); diff --git a/packages/test-in-console/package.js b/packages/test-in-console/package.js index 35c00fcac5..a75329d9c8 100644 --- a/packages/test-in-console/package.js +++ b/packages/test-in-console/package.js @@ -6,7 +6,7 @@ Package.describe({ Package.on_use(function (api) { api.use(['tinytest', 'underscore', 'random', 'ejson', 'check']); - api.use('http'); + api.use('http', 'server'); api.exportSymbol('TEST_STATUS', 'client'); diff --git a/packages/test-in-console/reporter.js b/packages/test-in-console/reporter.js index 62f56cf3bc..ef7e83734a 100644 --- a/packages/test-in-console/reporter.js +++ b/packages/test-in-console/reporter.js @@ -19,7 +19,7 @@ Meteor.methods({ // XXX Could do a more precise validation here; reports are complex! check(reports, [Object]); if (url) { - Meteor.http.post(url, { + HTTP.post(url, { data: reports }); } diff --git a/packages/weibo/package.js b/packages/weibo/package.js index 923fcf4eb0..6b78ae547d 100644 --- a/packages/weibo/package.js +++ b/packages/weibo/package.js @@ -8,7 +8,7 @@ Package.describe({ Package.on_use(function(api) { api.use('oauth2', ['client', 'server']); api.use('oauth', ['client', 'server']); - api.use('http', ['client', 'server']); + api.use('http', ['server']); api.use('templating', 'client'); api.use('random', 'client'); api.use('service-configuration', ['client', 'server']); diff --git a/packages/weibo/weibo_server.js b/packages/weibo/weibo_server.js index 2699a16e04..c5e11cd475 100644 --- a/packages/weibo/weibo_server.js +++ b/packages/weibo/weibo_server.js @@ -37,7 +37,7 @@ var getTokenResponse = function (query) { var response; try { - response = Meteor.http.post( + response = HTTP.post( "https://api.weibo.com/oauth2/access_token", {params: { code: query.code, client_id: config.clientId, @@ -62,7 +62,7 @@ var getTokenResponse = function (query) { var getIdentity = function (accessToken, userId) { try { - return Meteor.http.get( + return HTTP.get( "https://api.weibo.com/2/users/show.json", {params: {access_token: accessToken, uid: userId}}).data; } catch (err) { From e4c568b1e1905062c5b3a2cf69784ff6fc8ad404 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 24 Jul 2013 22:05:10 -0700 Subject: [PATCH 094/175] Merge exports list with package variables in slice JSON. This implies that all exports are package variables, which made underscore, jquery, and htmljs (which explicitly assigned to fields on the global variable) break. We now properly encapsulate these packages (except for window.jQuery, which we let sneak out because bootstrap wants it). This means that packages that want _ need to use underscore, and packages that want $ need to use jquery. Also, you can't use _ in minimongo $where any more (matching mongod). --- .../plugin/compile-coffeescript.js | 2 +- packages/htmljs/html.js | 99 ++++++++------- packages/htmljs/htmljs_test.js | 5 +- packages/htmljs/package.js | 1 + packages/jquery/package.js | 6 +- packages/jquery/post.js | 4 + packages/liverange/package.js | 3 +- packages/minimongo/minimongo_tests.js | 4 +- packages/spark/package.js | 2 +- packages/test-helpers/package.js | 2 +- packages/underscore/package.js | 2 +- packages/underscore/post.js | 3 + packages/underscore/pre.js | 3 + tools/linker.js | 116 ++++++++---------- tools/packages.js | 96 ++++++++++----- 15 files changed, 192 insertions(+), 156 deletions(-) create mode 100644 packages/jquery/post.js create mode 100644 packages/underscore/post.js create mode 100644 packages/underscore/pre.js diff --git a/packages/coffeescript/plugin/compile-coffeescript.js b/packages/coffeescript/plugin/compile-coffeescript.js index 90bac42fe2..3270a2912e 100644 --- a/packages/coffeescript/plugin/compile-coffeescript.js +++ b/packages/coffeescript/plugin/compile-coffeescript.js @@ -141,7 +141,7 @@ var handler = function (compileStep) { ); } - var stripped = stripExportedVars(output.js, compileStep.exports); + var stripped = stripExportedVars(output.js, compileStep.declaredExports); var sourceWithMap = addSharedHeader(stripped, output.v3SourceMap); compileStep.addJavaScript({ diff --git a/packages/htmljs/html.js b/packages/htmljs/html.js index 1325ed1503..aea248fa87 100644 --- a/packages/htmljs/html.js +++ b/packages/htmljs/html.js @@ -69,59 +69,56 @@ var tag_names = 'P PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG SUB SUP ' + 'TABLE TBODY TD TEXTAREA TFOOT TH THEAD TR TT U UL VAR').split(' '); -for (var i = 0; i < tag_names.length; i++) { - var tag = tag_names[i]; - - // 'this' will end up being the global object (eg, 'window' on the client) - this[tag] = (function (tag) { - return function (arg1, arg2) { - var attrs, contents; - if (arg2) { - attrs = arg1; - contents = arg2; +_.each(tag_names, function (tag) { + var f = function (arg1, arg2) { + var attrs, contents; + if (arg2) { + attrs = arg1; + contents = arg2; + } else { + if (arg1 instanceof Array) { + attrs = {}; + contents = arg1; } else { - if (arg1 instanceof Array) { - attrs = {}; - contents = arg1; - } else { - attrs = arg1; - contents = []; - } + attrs = arg1; + contents = []; } - var elt = document.createElement(tag); - for (var a in attrs) { - if (a === 'cls') - elt.setAttribute('class', attrs[a]); - else if (a === '_for') - elt.setAttribute('for', attrs[a]); - else if (a === 'style' && ! styleGetSetSupport) - elt.style.cssText = String(attrs[a]); - else if (event_names[a]) { - if (typeof $ === "undefined") - throw new Error("Event binding is supported only if " + - "jQuery or similar is available"); - ($(elt)[a])(attrs[a]); - } + } + var elt = document.createElement(tag); + for (var a in attrs) { + if (a === 'cls') + elt.setAttribute('class', attrs[a]); + else if (a === '_for') + elt.setAttribute('for', attrs[a]); + else if (a === 'style' && ! styleGetSetSupport) + elt.style.cssText = String(attrs[a]); + else if (event_names[a]) { + if (typeof $ === "undefined") + throw new Error("Event binding is supported only if " + + "jQuery or similar is available"); + ($(elt)[a])(attrs[a]); + } + else + elt.setAttribute(a, attrs[a]); + } + var addChildren = function (children) { + for (var i = 0; i < children.length; i++) { + var c = children[i]; + if (!c && c !== '') + throw new Error("Bad value for element body: " + c); + else if (c instanceof Array) + addChildren(c); + else if (typeof c === "string") + elt.appendChild(document.createTextNode(c)); + else if ('element' in c) + addChildren([c.element]); else - elt.setAttribute(a, attrs[a]); - } - var addChildren = function (children) { - for (var i = 0; i < children.length; i++) { - var c = children[i]; - if (!c && c !== '') - throw new Error("Bad value for element body: " + c); - else if (c instanceof Array) - addChildren(c); - else if (typeof c === "string") - elt.appendChild(document.createTextNode(c)); - else if ('element' in c) - addChildren([c.element]); - else - elt.appendChild(c); - }; + elt.appendChild(c); }; - addChildren(contents); - return elt; }; - })(tag); -}; + addChildren(contents); + return elt; + }; + // Put the function onto the package-scope variable with this name. + eval(tag + " = f;"); +}); diff --git a/packages/htmljs/htmljs_test.js b/packages/htmljs/htmljs_test.js index 32c6a23f40..b5e99f5288 100644 --- a/packages/htmljs/htmljs_test.js +++ b/packages/htmljs/htmljs_test.js @@ -1,6 +1,7 @@ Tinytest.add("htmljs", function (test) { - + console.log("HI") + console.log(DIV) // Make sure "style" works, which has to be special-cased for IE. test.equal(DIV({style:"display:none"}).style.display, "none"); -}); \ No newline at end of file +}); diff --git a/packages/htmljs/package.js b/packages/htmljs/package.js index 9f3cf6f13c..49407dc80d 100644 --- a/packages/htmljs/package.js +++ b/packages/htmljs/package.js @@ -3,6 +3,7 @@ Package.describe({ }); Package.on_use(function (api) { + api.use('underscore', 'client'); // Note: html.js will optionally use jquery if it's available api.add_files('html.js', 'client'); api.exportSymbol([ diff --git a/packages/jquery/package.js b/packages/jquery/package.js index 9e408a7dce..5a8a2bb6cc 100644 --- a/packages/jquery/package.js +++ b/packages/jquery/package.js @@ -3,8 +3,8 @@ Package.describe({ }); Package.on_use(function (api) { - api.add_files('jquery.js', 'client'); + api.add_files(['jquery.js', 'post.js'], 'client'); - api.exportSymbol('$'); - api.exportSymbol('jQuery'); + api.exportSymbol('$', 'client'); + api.exportSymbol('jQuery', 'client'); }); diff --git a/packages/jquery/post.js b/packages/jquery/post.js new file mode 100644 index 0000000000..c784a3799b --- /dev/null +++ b/packages/jquery/post.js @@ -0,0 +1,4 @@ +// Put jQuery and $ in our exported package-scope variables and remove window.$. +// (Sadly, we don't call noConflict(true), which would also remove +// window.jQuery, because bootstrap very specifically relies on window.jQuery.) +$ = jQuery = window.jQuery.noConflict(); diff --git a/packages/liverange/package.js b/packages/liverange/package.js index 0f009415f8..daf0625e68 100644 --- a/packages/liverange/package.js +++ b/packages/liverange/package.js @@ -10,7 +10,8 @@ Package.on_use(function (api) { Package.on_test(function (api) { api.use(['tinytest']); - api.use(['liverange', 'test-helpers', 'domutils', 'underscore'], 'client'); + api.use(['liverange', 'test-helpers', 'domutils', 'underscore', 'jquery'], + 'client'); api.add_files([ 'liverange_test_helpers.js', diff --git a/packages/minimongo/minimongo_tests.js b/packages/minimongo/minimongo_tests.js index 56bf557b9b..21417165bb 100644 --- a/packages/minimongo/minimongo_tests.js +++ b/packages/minimongo/minimongo_tests.js @@ -850,8 +850,8 @@ Tinytest.add("minimongo - selector_compiler", function (test) { nomatch({$where: "this.a === 1", a: 2}, {a: 1}); match({$where: "this.a === 1", b: 2}, {a: 1, b: 2}); match({$where: "this.a === 1 && this.b === 2"}, {a: 1, b: 2}); - match({$where: "_.isArray(this.a)"}, {a: []}); - nomatch({$where: "_.isArray(this.a)"}, {a: 1}); + match({$where: "this.a instanceof Array"}, {a: []}); + nomatch({$where: "this.a instanceof Array"}, {a: 1}); // reaching into array match({"dogs.0.name": "Fido"}, {dogs: [{name: "Fido"}, {name: "Rex"}]}); diff --git a/packages/spark/package.js b/packages/spark/package.js index 3088dcbed0..77bb36b188 100644 --- a/packages/spark/package.js +++ b/packages/spark/package.js @@ -19,7 +19,7 @@ Package.on_test(function (api) { api.use('webapp', 'server'); api.use(['tinytest', 'underscore', 'liverange', 'deps', 'domutils', 'minimongo', 'random']); - api.use(['spark', 'test-helpers'], 'client'); + api.use(['spark', 'test-helpers', 'jquery'], 'client'); api.add_files('test_form_responder.js', 'server'); diff --git a/packages/test-helpers/package.js b/packages/test-helpers/package.js index 4c41cef1d3..bbdf75c36b 100644 --- a/packages/test-helpers/package.js +++ b/packages/test-helpers/package.js @@ -6,7 +6,7 @@ Package.describe({ Package.on_use(function (api) { api.use(['underscore', 'deps', 'ejson', 'tinytest', 'random', 'domutils']); - api.use(['spark'], 'client'); + api.use(['spark', 'jquery'], 'client'); api.exportSymbol([ 'pollUntil', 'WrappedFrag', 'try_all_permutations', 'StubStream', diff --git a/packages/underscore/package.js b/packages/underscore/package.js index 08f5a6284a..d1c15fe4cb 100644 --- a/packages/underscore/package.js +++ b/packages/underscore/package.js @@ -20,5 +20,5 @@ Package.on_use(function (api) { api.exportSymbol('_'); - api.add_files('underscore.js'); + api.add_files(['pre.js', 'underscore.js', 'post.js']); }); diff --git a/packages/underscore/post.js b/packages/underscore/post.js new file mode 100644 index 0000000000..0dc94565ab --- /dev/null +++ b/packages/underscore/post.js @@ -0,0 +1,3 @@ +// This exports object was created in pre.js. Now copy the `_` object from it +// into the package-scope variable `_`, which will get exported. +_ = exports._; diff --git a/packages/underscore/pre.js b/packages/underscore/pre.js new file mode 100644 index 0000000000..f5cc70801b --- /dev/null +++ b/packages/underscore/pre.js @@ -0,0 +1,3 @@ +// Define an object named exports. This will cause underscore.js to put `_` as a +// field on it, instead of in the global namespace. See also post.js. +exports = {}; diff --git a/tools/linker.js b/tools/linker.js index 1b7459e577..1b1d24fffe 100644 --- a/tools/linker.js +++ b/tools/linker.js @@ -27,7 +27,7 @@ var Module = function (options) { self.files = []; // options - self.exports = options.exports || []; + self.declaredExports = options.declaredExports; self.useGlobalNamespace = options.useGlobalNamespace; self.combinedServePath = options.combinedServePath; self.importStubServePath = options.importStubServePath; @@ -61,32 +61,25 @@ _.extend(Module.prototype, { // Figure out which vars need to be specifically put in the module // scope. - // - // XXX We used to subtract 'import roots' out of this (defined as - // the first part of each imported symbol) but two-phase link - // complicates this. We should really go back to doing it, though, - // because otherwise the output looks ugly and it's harder to skim - // and see what your globals are. Probably this means we need to - // move the emission of the Package-scope Variables section (but not - // the actual static analysis) to the final phase. - computeModuleScopeVars: function () { + computeAssignedVariables: function () { var self = this; if (!self.jsAnalyze) { // We don't have access to static analysis, probably because we *are* the // js-analyze package. Let's do a stupid heuristic: the exports are // the only module scope vars. (This works for js-analyze.JSAnalyze...) - return self.exports; + return self.declaredExports; } // Find all global references in any files - var globalReferences = []; + var assignedVariables = []; _.each(self.files, function (file) { - globalReferences = globalReferences.concat(file.computeGlobalReferences()); + assignedVariables = assignedVariables.concat( + file.computeAssignedVariables()); }); - globalReferences = _.uniq(globalReferences); + assignedVariables = _.uniq(assignedVariables); - return _.isEmpty(globalReferences) ? undefined : globalReferences; + return _.isEmpty(assignedVariables) ? undefined : assignedVariables; }, // Output is a list of objects with keys 'source', 'servePath', 'sourceMap', @@ -97,11 +90,11 @@ _.extend(Module.prototype, { // If there are no files *and* we are a no-exports-at-all slice (eg a test // slice), then generate no prelink output. // - // If there are no files, but we are a use slice (and thus self.exports is - // an actual, albeit potentially empty, list), we DON'T want to take this - // path: we want to return an empty prelink file, so that at link time we - // end up at least setting `Package.foo = {}`. - if (_.isEmpty(self.files) && !self.exports) + // If there are no files, but we are a use slice (and thus + // self.declaredExports is an actual, albeit potentially empty, list), we + // DON'T want to take this path: we want to return an empty prelink file, so + // that at link time we end up at least setting `Package.foo = {}`. + if (_.isEmpty(self.files) && !self.declaredExports) return []; // If we don't want to create a separate scope for this module, @@ -161,16 +154,11 @@ _.extend(Module.prototype, { // return something like // {Foo: 's1', Bar: {Baz: 's2', Quux: {A: 's3', B: 's4'}}} // -// If 'inputTree' is given, it is modified (augumented) instead of -// constructing a new tree. -// // If the value of a symbol in symbolMap is set null, then we just // ensure that its parents exist. For example, {'A.B.C': null} means // to make sure that symbol tree contains at least {A: {B: {}}}. -var buildSymbolTree = function (symbolMap, inputTree) { - // XXX XXX detect and report conflicts, like one file exporting - // Foo and another file exporting Foo.Bar - var ret = inputTree || {}; +var buildSymbolTree = function (symbolMap) { + var ret = {}; _.each(symbolMap, function (value, symbol) { var parts = symbol.split('.'); @@ -245,7 +233,7 @@ _.extend(File.prototype, { // example: if the code references 'Foo.bar.baz' and 'Quux', and // neither are declared in a scope enclosing the point where they're // referenced, then globalReferences would include ["Foo", "Quux"]. - computeGlobalReferences: function () { + computeAssignedVariables: function () { var self = this; var jsAnalyze = self.module.jsAnalyze; @@ -474,7 +462,8 @@ var bannerPadding = function (bannerWidth) { // - sourcePath: path to use in error messages // - sourceMap: an optional source map (as string) for the input file // -// exports: an array of symbols that the module exports +// declaredExports: an array of symbols that the module exports. null +// if our slice isn't allowed to have exports. // // useGlobalNamespace: make the top level namespace be the same as the // global namespace, so that symbols are accessible from the @@ -496,11 +485,12 @@ var bannerPadding = function (bannerWidth) { // - files: is an array of output files in the same format as inputFiles // - EXCEPT THAT, for now, sourcePath is omitted and is replaced with // sourceMap (a string) (XXX) -// - packageScopeVariables: an array of package-scope variables +// - assignedPackageVariables: an array of variables assigned to without +// being declared var prelink = function (options) { var module = new Module({ name: options.name, - exports: options.exports, + declaredExports: options.declaredExports, useGlobalNamespace: options.useGlobalNamespace, importStubServePath: options.importStubServePath, combinedServePath: options.combinedServePath, @@ -514,12 +504,12 @@ var prelink = function (options) { // Do static analysis to compute module-scoped variables. Error recovery from // the static analysis mutates the sources, so this has to be done before // concatenation. - var packageScopeVariables = module.computeModuleScopeVars(); + var assignedVariables = module.computeAssignedVariables(); var files = module.getPrelinkedFiles(); return { files: files, - packageScopeVariables: packageScopeVariables + assignedVariables: assignedVariables }; }; @@ -545,8 +535,12 @@ var SOURCE_MAP_INSTRUCTIONS_COMMENT = banner([ // 'Foo', "Foo.bar", etc) to the module from which it should be // imported (which must load before us at runtime) // -// exports: symbols to export, as an array of symbol names as strings. -// if null, not even Package.name will be defined. +// noExports: if true, don't generate an exports section (don't even create +// `Package.name`). +// +// packageVariables: package-scope variables, some of which may be exports. +// a list of {name, export} objects; any non-falsy value for "export" means +// to export it. // // useGlobalNamespace: must be the same value that was passed to link() // @@ -560,8 +554,7 @@ var link = function (options) { if (!_.isEmpty(options.imports)) { ret.push({ source: getImportCode(options.imports, - "/* Imports for global scope */\n\n", true, - options.exports), + "/* Imports for global scope */\n\n", true), servePath: options.importStubServePath }); } @@ -570,11 +563,18 @@ var link = function (options) { var header = getHeader({ imports: options.imports, - exports: options.exports, - packageScopeVariables: options.packageScopeVariables + packageVariables: options.packageVariables }); + + var exported; + if (!options.noExports) { + exported = _.pluck(_.filter(options.packageVariables, function (v) { + return v.export; + }), 'name'); + } + var footer = getFooter({ - exports: options.exports, + exported: exported, name: options.name }); @@ -616,21 +616,19 @@ var link = function (options) { var getHeader = function (options) { var chunks = []; chunks.push("(function () {\n\n" ); - chunks.push(getImportCode(options.imports, "/* Imports */\n", false, - options.exports)); - if (options.packageScopeVariables - && !_.isEmpty(options.packageScopeVariables)) { + chunks.push(getImportCode(options.imports, "/* Imports */\n", false)); + if (!_.isEmpty(options.packageVariables)) { chunks.push("/* Package-scope variables */\n"); - chunks.push("var " + options.packageScopeVariables.join(', ') + ";\n\n"); + chunks.push("var " + _.pluck(options.packageVariables, 'name').join(', ') + + ";\n\n"); } return chunks.join(''); }; -var getImportCode = function (imports, header, omitvar, exports) { +var getImportCode = function (imports, header, omitvar) { var self = this; - exports = exports || {}; - if (_.isEmpty(imports) && _.isEmpty(exports)) + if (_.isEmpty(imports)) return ""; // Imports @@ -640,14 +638,6 @@ var getImportCode = function (imports, header, omitvar, exports) { }); var tree = buildSymbolTree(scratch); - // Now, if we export a symbol A.B.C, and A.B.* isn't imported, set - // up A.B = {} - scratch = {}; - _.each(exports, function (symbol) { - scratch[symbol] = null; - }); - buildSymbolTree(scratch, tree); - // Generate output var buf = header; _.each(tree, function (node, key) { @@ -662,7 +652,7 @@ var getImportCode = function (imports, header, omitvar, exports) { var getFooter = function (options) { var chunks = []; - if (options.name && options.exports) { + if (options.name && options.exported) { chunks.push("\n\n/* Exports */\n"); chunks.push("if (typeof Package === 'undefined') Package = {};\n"); chunks.push(packageDot(options.name), " = "); @@ -670,18 +660,18 @@ var getFooter = function (options) { // Even if there are no exports, we need to define Package.foo, because the // existence of Package.foo is how another package (eg, one that weakly // depends on foo) can tell if foo is loaded. - if (_.isEmpty(options.exports)) { + if (_.isEmpty(options.exported)) { chunks.push("{};\n"); } else { - // Given exports like Foo, Bar.Baz, Bar.Quux.A, and Bar.Quux.B, - // construct an expression like - // {Foo: Foo, Bar: {Baz: Bar.Baz, Quux: {A: Bar.Quux.A, B: Bar.Quux.B}}} + // A slightly overkill way to print out a properly indented version of + // {Foo: Foo, Bar: Bar, Quux: Quux}. (This was less overkill back when + // you could export dotted symbols.) var scratch = {}; - _.each(options.exports, function (symbol) { + _.each(options.exported, function (symbol) { scratch[symbol] = symbol; }); var exportTree = buildSymbolTree(scratch); - chunks.push(writeSymbolTree(exportTree, 0)); + chunks.push(writeSymbolTree(exportTree)); chunks.push(";\n"); } } diff --git a/tools/packages.js b/tools/packages.js index e186a5a9df..beb5739d09 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -20,7 +20,7 @@ var sourcemap = require('source-map'); // unipackage/slice changes, but this version (which is build-tool-specific) can // change when the the contents (not structure) of the built output changes. So // eg, if we improve the linker's static analysis, this should be bumped. -exports.BUILT_BY = 'meteor/3'; +exports.BUILT_BY = 'meteor/4'; // Find all files under `rootPath` that have an extension in // `extensions` (an array of extensions without leading dot), and @@ -150,10 +150,14 @@ var Slice = function (pkg, options) { // local plugins in this package) to compute this. self.getSourcesFunc = options.getSourcesFunc || null; - // Symbols that this slice should export. List of symbols (as strings.) Null - // if this slice should not have any exports and in fact should not even - // define `Package.name` (ie, test slices). - self.exports = options.exports; + // True if this slice is not permitted to have any exports, and in fact should + // not even define `Package.name` (ie, test slices). + self.noExports = options.noExports || false; + + // Symbols that this slice should export. List of symbols (as strings). Null + // on built packages (see packageVariables instead), or in packages where + // noExports is set. + self.declaredExports = options.declaredExports || null; // Files and directories that we want to monitor for changes in // development mode, such as source files and package.js, in the @@ -164,15 +168,21 @@ var Slice = function (pkg, options) { // Has this slice been compiled? self.isBuilt = false; - // Prelink output. 'prelinkFiles' is the partially linked JavaScript code (an + // Prelink output. + // + // 'prelinkFiles' is the partially linked JavaScript code (an // array of objects with keys 'source' and 'servePath', both strings -- see - // prelink() in linker.js) 'packageScopeVariables' are are variables that are - // syntactically globals in our input files and which we capture with a - // package-scope closure. Both of these are inputs into the final link phase, - // which inserts the final JavaScript resources into 'resources'. Set only - // when isBuilt is true. + // prelink() in linker.js) + // + // 'packageVariables' are are variables that are syntactically globals in our + // input files and which we capture with a package-scope closure. A list of + // objects with keys 'name' (required) and 'export' (true, 'tests', or falsy). + // + // Both of these are saved into slices on disk, and are inputs into the final + // link phase, which inserts the final JavaScript resources into + // 'resources'. Set only when isBuilt is true. self.prelinkFiles = null; - self.packageScopeVariables = null; + self.packageVariables = null; // All of the data provided for eventual inclusion in the bundle, // other than JavaScript that still needs to be fed through the @@ -207,7 +217,7 @@ _.extend(Slice.prototype, { // through the appropriate handlers and run the prelink phase on any // resulting JavaScript. Also add all provided source files to the // package dependencies. Sets fields such as dependencies, exports, - // prelinkFiles, packageScopeVariables, and resources. + // prelinkFiles, packageVariables, and resources. build: function () { var self = this; var isApp = ! self.pkg.name; @@ -295,10 +305,9 @@ _.extend(Slice.prototype, { // - fileOptions: any options passed to "api.add_files"; for // use by the plugin. The built-in "js" plugin uses the "bare" // option for files that shouldn't be wrapped in a closure. - // - exports: An array of symbols exported by this slice, or null - // if it may not export any symbols (eg, test slices). This - // is used by CoffeeScript to ensure that it doesn't close over - // those symbols, eg. + // - declaredExports: An array of symbols exported by this slice, or null + // if it may not export any symbols (eg, test slices). This is used by + // CoffeeScript to ensure that it doesn't close over those symbols, eg. // - read(n): read from the input file. If n is given it should // be an integer, and you will receive the next n bytes of the // file as a Buffer. If n is omitted you get the rest of the @@ -390,7 +399,7 @@ _.extend(Slice.prototype, { rootOutputPath: self.pkg.serveRoot, arch: self.arch, fileOptions: fileOptions, - exports: self.exports, + declaredExports: self.declaredExports, read: function (n) { if (n === undefined || readOffset + n > contents.length) n = contents.length - readOffset; @@ -484,7 +493,7 @@ _.extend(Slice.prototype, { "/packages/" + self.pkg.name + (self.sliceName === "main" ? "" : ("." + self.sliceName)) + ".js", name: self.pkg.name || null, - exports: self.exports, + declaredExports: self.declaredExports, jsAnalyze: jsAnalyze }); @@ -505,7 +514,30 @@ _.extend(Slice.prototype, { }); self.prelinkFiles = results.files; - self.packageScopeVariables = results.packageScopeVariables; + + self.packageVariables = []; + var packageVariableNames = {}; + _.each(self.declaredExports, function (name) { + if (_.has(packageVariableNames, name)) + return; + self.packageVariables.push({ + name: name, + export: true + }); + packageVariableNames[name] = true; + }); + _.each(results.assignedVariables, function (name) { + if (_.has(packageVariableNames, name)) + return; + self.packageVariables.push({ + name: name + }); + packageVariableNames[name] = true; + }); + // Forget about the *declared* exports; what matters is packageVariables + // now. + self.declaredExports = null; + self.resources = resources; self.isBuilt = true; }, @@ -547,8 +579,10 @@ _.extend(Slice.prototype, { bundleArch, {skipWeak: true, skipUnordered: true}, function (otherSlice) { if (! otherSlice.isBuilt) throw new Error("dependency wasn't built?"); - _.each(otherSlice.exports, function (symbol) { - imports[symbol] = otherSlice.pkg.name; + _.each(otherSlice.packageVariables, function (symbol) { + // XXX implement test-only exports + if (symbol.export) + imports[symbol.name] = otherSlice.pkg.name; }); }); @@ -560,8 +594,8 @@ _.extend(Slice.prototype, { // XXX report an error if there is a package called global-imports importStubServePath: isApp && '/packages/global-imports.js', prelinkFiles: self.prelinkFiles, - exports: self.exports, - packageScopeVariables: self.packageScopeVariables, + noExports: self.noExports, + packageVariables: self.packageVariables, includeSourceMapInstructions: archinfo.matches(self.arch, "browser"), name: self.pkg.name || null }); @@ -1687,7 +1721,8 @@ _.extend(Package.prototype, { uses: uses[role][where], implies: role === "use" && implies[where] || undefined, getSourcesFunc: function () { return sources[role][where]; }, - exports: role === "use" ? exports[where] : null, + noExports: role === "test", + declaredExports: role === "use" ? exports[where] : null, dependencyInfo: dependencyInfo, nodeModulesPath: arch === nativeArch && nodeModulesPath || undefined })); @@ -1716,7 +1751,8 @@ _.extend(Package.prototype, { // standard client packages for the classic meteor stack. // XXX remove and make everyone explicitly declare all dependencies ['meteor', 'webapp', 'logging', 'deps', 'session', - 'livedata', 'mongo-livedata', 'spark', 'templating', 'check'], + 'livedata', 'mongo-livedata', 'spark', 'templating', 'check', + 'underscore', 'jquery'], project.get_packages(appDir)); var arch = sliceName === "server" ? "native" : "browser"; @@ -2012,8 +2048,8 @@ _.extend(Package.prototype, { }); slice.isBuilt = true; - slice.exports = sliceJson.exports || null; - slice.packageScopeVariables = sliceJson.packageScopeVariables || []; + slice.noExports = !!sliceJson.noExports; + slice.packageVariables = sliceJson.packageVariables || []; slice.prelinkFiles = []; slice.resources = []; @@ -2200,8 +2236,8 @@ _.extend(Package.prototype, { // Construct slice metadata var sliceJson = { format: "unipackage-slice-pre1", - exports: slice.exports || undefined, - packageScopeVariables: slice.packageScopeVariables, + noExports: slice.noExports || undefined, + packageVariables: slice.packageVariables, uses: _.map(slice.uses, function (u) { var specParts = u.spec.split('.'); if (specParts.length > 2) From bc8f251cd19a507f3cd959bbdee259a8a8442ed2 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 24 Jul 2013 22:46:40 -0700 Subject: [PATCH 095/175] oops, remove debugging code --- packages/htmljs/htmljs_test.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/htmljs/htmljs_test.js b/packages/htmljs/htmljs_test.js index b5e99f5288..c9ded674f7 100644 --- a/packages/htmljs/htmljs_test.js +++ b/packages/htmljs/htmljs_test.js @@ -1,7 +1,5 @@ Tinytest.add("htmljs", function (test) { - console.log("HI") - console.log(DIV) // Make sure "style" works, which has to be special-cased for IE. test.equal(DIV({style:"display:none"}).style.display, "none"); }); From 3a7eac6dcaea065ebc8cd74d45fdeccacf811176 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 24 Jul 2013 22:54:47 -0700 Subject: [PATCH 096/175] Declare _FooTest symbols as testOnly; they are only visible to tests. They are visible to *all* tests, not just the package's own tests. --- packages/ejson/package.js | 2 +- packages/email/package.js | 2 +- packages/livedata/package.js | 2 +- packages/mongo-livedata/package.js | 2 +- packages/oauth/package.js | 2 +- packages/oauth1/package.js | 2 +- packages/routepolicy/package.js | 2 +- packages/spark/package.js | 2 +- tools/linker.js | 4 ++-- tools/packages.js | 32 ++++++++++++++++++++---------- 10 files changed, 31 insertions(+), 21 deletions(-) diff --git a/packages/ejson/package.js b/packages/ejson/package.js index 26a33d4b35..7b4f238aa6 100644 --- a/packages/ejson/package.js +++ b/packages/ejson/package.js @@ -6,7 +6,7 @@ Package.describe({ Package.on_use(function (api) { api.use(['json', 'underscore']); api.exportSymbol('EJSON'); - api.exportSymbol('_EJSONTest'); + api.exportSymbol('_EJSONTest', {testOnly: true}); api.add_files('ejson.js', ['client', 'server']); api.add_files('base64.js', ['client', 'server']); }); diff --git a/packages/email/package.js b/packages/email/package.js index 393fc107ff..27d22beb5f 100644 --- a/packages/email/package.js +++ b/packages/email/package.js @@ -9,7 +9,7 @@ Npm.depends({mailcomposer: "0.1.15", simplesmtp: "0.1.25", "stream-buffers": "0. Package.on_use(function (api) { api.use('underscore', 'server'); api.exportSymbol('Email', 'server'); - api.exportSymbol('_EmailTest', 'server'); + api.exportSymbol('_EmailTest', 'server', {testOnly: true}); api.add_files('email.js', 'server'); }); diff --git a/packages/livedata/package.js b/packages/livedata/package.js index a7cb9def76..3a9eb2bf38 100644 --- a/packages/livedata/package.js +++ b/packages/livedata/package.js @@ -26,7 +26,7 @@ Package.on_use(function (api) { api.exportSymbol('DDP'); api.exportSymbol('DDPServer', 'server'); - api.exportSymbol('_LivedataTest'); + api.exportSymbol('_LivedataTest', {testOnly: true}); // Transport api.use('reload', 'client'); diff --git a/packages/mongo-livedata/package.js b/packages/mongo-livedata/package.js index d674e56643..e3704754be 100644 --- a/packages/mongo-livedata/package.js +++ b/packages/mongo-livedata/package.js @@ -26,7 +26,7 @@ Package.on_use(function (api) { // Allow us to detect 'autopublish', and publish collections if it's loaded. api.use('autopublish', 'server', {weak: true}); - api.exportSymbol('_MongoLivedataTest', 'server'); + api.exportSymbol('_MongoLivedataTest', 'server', {testOnly: true}); api.add_files('mongo_driver.js', 'server'); api.add_files('local_collection_driver.js', ['client', 'server']); diff --git a/packages/oauth/package.js b/packages/oauth/package.js index ad92dc44a0..5c292869e5 100644 --- a/packages/oauth/package.js +++ b/packages/oauth/package.js @@ -9,7 +9,7 @@ Package.on_use(function (api) { api.use(['underscore', 'service-configuration'], 'server'); api.exportSymbol('Oauth'); - api.exportSymbol('_OauthTest', 'server'); + api.exportSymbol('_OauthTest', 'server', {testOnly: true}); api.add_files('oauth_client.js', 'client'); api.add_files('oauth_server.js', 'server'); diff --git a/packages/oauth1/package.js b/packages/oauth1/package.js index ce76adfaa8..0dd304b6b9 100644 --- a/packages/oauth1/package.js +++ b/packages/oauth1/package.js @@ -11,7 +11,7 @@ Package.on_use(function (api) { api.use('http', 'server'); api.exportSymbol('OAuth1Binding', 'server'); - api.exportSymbol('_OAuth1Test', 'server'); + api.exportSymbol('_OAuth1Test', 'server', {testOnly: true}); api.add_files('oauth1_binding.js', 'server'); api.add_files('oauth1_server.js', 'server'); diff --git a/packages/routepolicy/package.js b/packages/routepolicy/package.js index 8b3db515bc..da08d2699c 100644 --- a/packages/routepolicy/package.js +++ b/packages/routepolicy/package.js @@ -9,7 +9,7 @@ Package.on_use(function (api) { // Package.webapp and only after initial load. api.use('webapp', 'server', {unordered: true}); api.exportSymbol('RoutePolicy', 'server'); - api.exportSymbol('_RoutePolicyTest', 'server'); + api.exportSymbol('_RoutePolicyTest', 'server', {testOnly: true}); api.add_files('routepolicy.js', 'server'); }); diff --git a/packages/spark/package.js b/packages/spark/package.js index 77bb36b188..932bc455d7 100644 --- a/packages/spark/package.js +++ b/packages/spark/package.js @@ -9,7 +9,7 @@ Package.on_use(function (api) { 'client'); api.exportSymbol('Spark', 'client'); - api.exportSymbol('_SparkTest', 'client'); + api.exportSymbol('_SparkTest', 'client', {testOnly: true}); api.add_files(['spark.js', 'patch.js', 'convenience.js', 'utils.js'], 'client'); diff --git a/tools/linker.js b/tools/linker.js index 1b1d24fffe..73dfb32302 100644 --- a/tools/linker.js +++ b/tools/linker.js @@ -462,8 +462,8 @@ var bannerPadding = function (bannerWidth) { // - sourcePath: path to use in error messages // - sourceMap: an optional source map (as string) for the input file // -// declaredExports: an array of symbols that the module exports. null -// if our slice isn't allowed to have exports. +// declaredExports: an array of symbols that the module exports. null if our +// slice isn't allowed to have exports. Symbols are {name,testOnly} pairs. // // useGlobalNamespace: make the top level namespace be the same as the // global namespace, so that symbols are accessible from the diff --git a/tools/packages.js b/tools/packages.js index beb5739d09..e3fbd1802c 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -399,7 +399,7 @@ _.extend(Slice.prototype, { rootOutputPath: self.pkg.serveRoot, arch: self.arch, fileOptions: fileOptions, - declaredExports: self.declaredExports, + declaredExports: _.pluck(self.declaredExports, 'name'), read: function (n) { if (n === undefined || readOffset + n > contents.length) n = contents.length - readOffset; @@ -493,7 +493,7 @@ _.extend(Slice.prototype, { "/packages/" + self.pkg.name + (self.sliceName === "main" ? "" : ("." + self.sliceName)) + ".js", name: self.pkg.name || null, - declaredExports: self.declaredExports, + declaredExports: _.pluck(self.declaredExports, 'name'), jsAnalyze: jsAnalyze }); @@ -517,14 +517,14 @@ _.extend(Slice.prototype, { self.packageVariables = []; var packageVariableNames = {}; - _.each(self.declaredExports, function (name) { - if (_.has(packageVariableNames, name)) + _.each(self.declaredExports, function (symbol) { + if (_.has(packageVariableNames, symbol.name)) return; self.packageVariables.push({ - name: name, - export: true + name: symbol.name, + export: symbol.testOnly? "tests" : true }); - packageVariableNames[name] = true; + packageVariableNames[symbol.name] = true; }); _.each(results.assignedVariables, function (name) { if (_.has(packageVariableNames, name)) @@ -580,8 +580,9 @@ _.extend(Slice.prototype, { if (! otherSlice.isBuilt) throw new Error("dependency wasn't built?"); _.each(otherSlice.packageVariables, function (symbol) { - // XXX implement test-only exports - if (symbol.export) + // Slightly hacky implementation of test-only exports. + if (symbol.export === true || + (symbol.export === "tests" && self.sliceName === "tests")) imports[symbol.name] = otherSlice.pkg.name; }); }); @@ -1586,13 +1587,22 @@ _.extend(Package.prototype, { // // @param symbols String (eg "Foo") or array of String // @param where 'client', 'server', or an array of those - exportSymbol: function (symbols, where) { + // @param options 'testOnly', boolean. + exportSymbol: function (symbols, where, options) { if (role === "test") { buildmessage.error("You cannot export symbols from a test.", { useMyCaller: true }); // recover by ignoring return; } + // Support `api.exportSymbol("FooTest", {testOnly: true})` without + // where. + if (_.isObject(where) && !_.isArray(where) && !options) { + options = where; + where = null; + } + options = options || {}; + symbols = toArray(symbols); where = toWhereArray(where); @@ -1605,7 +1615,7 @@ _.extend(Package.prototype, { return; } _.each(where, function (w) { - exports[w].push(symbol); + exports[w].push({name: symbol, testOnly: !!options.testOnly}); }); }); }, From d387e043eae4d30ab2dc9136ba916e38692dc3ea Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 24 Jul 2013 23:03:24 -0700 Subject: [PATCH 097/175] Rename _FooTest symbols to FooTest. They are already test-only. --- .../accounts-password/email_tests_setup.js | 2 +- packages/ejson/base64.js | 4 +-- packages/ejson/base64_test.js | 12 +++---- packages/ejson/ejson.js | 2 +- packages/ejson/package.js | 2 +- packages/email/email.js | 8 ++--- packages/email/email_tests.js | 4 +-- packages/email/package.js | 2 +- packages/livedata/livedata_common.js | 2 +- packages/livedata/livedata_connection.js | 2 +- .../livedata/livedata_connection_tests.js | 14 ++++---- packages/livedata/livedata_server.js | 4 +-- packages/livedata/livedata_tests.js | 4 +-- packages/livedata/package.js | 2 +- packages/livedata/session_view_tests.js | 2 +- packages/livedata/stream_client_common.js | 4 +-- packages/livedata/stream_tests.js | 2 +- packages/mongo-livedata/mongo_driver.js | 6 ++-- .../mongo-livedata/mongo_livedata_tests.js | 2 +- .../mongo-livedata/observe_changes_tests.js | 2 +- packages/mongo-livedata/package.js | 2 +- packages/oauth/oauth_server.js | 6 ++-- packages/oauth/package.js | 2 +- packages/oauth1/oauth1_server.js | 2 +- packages/oauth1/oauth1_tests.js | 6 ++-- packages/oauth1/package.js | 2 +- packages/oauth2/oauth2_tests.js | 4 +-- packages/routepolicy/package.js | 2 +- packages/routepolicy/routepolicy.js | 4 +-- packages/routepolicy/routepolicy_tests.js | 6 ++-- packages/spark/package.js | 2 +- packages/spark/patch.js | 2 +- packages/spark/patch_tests.js | 4 +-- packages/spark/spark.js | 8 ++--- packages/spark/spark_tests.js | 36 +++++++++---------- 35 files changed, 85 insertions(+), 85 deletions(-) diff --git a/packages/accounts-password/email_tests_setup.js b/packages/accounts-password/email_tests_setup.js index bf84839c35..30a542aa12 100644 --- a/packages/accounts-password/email_tests_setup.js +++ b/packages/accounts-password/email_tests_setup.js @@ -5,7 +5,7 @@ // var interceptedEmails = {}; // (email address) -> (array of contents) -_EmailTest.hookSend(function (options) { +EmailTest.hookSend(function (options) { var to = options.to; if (to.indexOf('intercept') === -1) { return true; // go ahead and send diff --git a/packages/ejson/base64.js b/packages/ejson/base64.js index 478538f917..795050bd43 100644 --- a/packages/ejson/base64.js +++ b/packages/ejson/base64.js @@ -122,6 +122,6 @@ base64Decode = function (str) { return arr; }; -_EJSONTest.base64Encode = base64Encode; +EJSONTest.base64Encode = base64Encode; -_EJSONTest.base64Decode = base64Decode; +EJSONTest.base64Decode = base64Decode; diff --git a/packages/ejson/base64_test.js b/packages/ejson/base64_test.js index e17519ea40..4847c2e59e 100644 --- a/packages/ejson/base64_test.js +++ b/packages/ejson/base64_test.js @@ -24,8 +24,8 @@ Tinytest.add("base64 - testing the test", function (test) { }); Tinytest.add("base64 - empty", function (test) { - test.equal(_EJSONTest.base64Encode(EJSON.newBinary(0)), ""); - test.equal(_EJSONTest.base64Decode(""), EJSON.newBinary(0)); + test.equal(EJSONTest.base64Encode(EJSON.newBinary(0)), ""); + test.equal(EJSONTest.base64Decode(""), EJSON.newBinary(0)); }); @@ -38,8 +38,8 @@ Tinytest.add("base64 - wikipedia examples", function (test) { {txt: "sure.", res: "c3VyZS4="} ]; _.each(tests, function(t) { - test.equal(_EJSONTest.base64Encode(asciiToArray(t.txt)), t.res); - test.equal(arrayToAscii(_EJSONTest.base64Decode(t.res)), t.txt); + test.equal(EJSONTest.base64Encode(asciiToArray(t.txt)), t.res); + test.equal(arrayToAscii(EJSONTest.base64Decode(t.res)), t.txt); }); }); @@ -49,11 +49,11 @@ Tinytest.add("base64 - non-text examples", function (test) { {array: [0, 0, 1], b64: "AAAB"} ]; _.each(tests, function(t) { - test.equal(_EJSONTest.base64Encode(t.array), t.b64); + test.equal(EJSONTest.base64Encode(t.array), t.b64); var expectedAsBinary = EJSON.newBinary(t.array.length); _.each(t.array, function (val, i) { expectedAsBinary[i] = val; }); - test.equal(_EJSONTest.base64Decode(t.b64), expectedAsBinary); + test.equal(EJSONTest.base64Decode(t.b64), expectedAsBinary); }); }); diff --git a/packages/ejson/ejson.js b/packages/ejson/ejson.js index b049fa3863..3bce3f802f 100644 --- a/packages/ejson/ejson.js +++ b/packages/ejson/ejson.js @@ -1,5 +1,5 @@ EJSON = {}; -_EJSONTest = {}; +EJSONTest = {}; var customTypes = {}; // Add a custom type, using a method of your choice to get to and diff --git a/packages/ejson/package.js b/packages/ejson/package.js index 7b4f238aa6..bad57cb9fb 100644 --- a/packages/ejson/package.js +++ b/packages/ejson/package.js @@ -6,7 +6,7 @@ Package.describe({ Package.on_use(function (api) { api.use(['json', 'underscore']); api.exportSymbol('EJSON'); - api.exportSymbol('_EJSONTest', {testOnly: true}); + api.exportSymbol('EJSONTest', {testOnly: true}); api.add_files('ejson.js', ['client', 'server']); api.add_files('base64.js', ['client', 'server']); }); diff --git a/packages/email/email.js b/packages/email/email.js index 65f2d66399..3efe822340 100644 --- a/packages/email/email.js +++ b/packages/email/email.js @@ -3,7 +3,7 @@ var urlModule = Npm.require('url'); var MailComposer = Npm.require('mailcomposer').MailComposer; Email = {}; -_EmailTest = {}; +EmailTest = {}; var makePool = function (mailUrlString) { var mailUrl = urlModule.parse(mailUrlString); @@ -49,12 +49,12 @@ var next_devmode_mail_id = 0; var output_stream = process.stdout; // Testing hooks -_EmailTest.overrideOutputStream = function (stream) { +EmailTest.overrideOutputStream = function (stream) { next_devmode_mail_id = 0; output_stream = stream; }; -_EmailTest.restoreOutputStream = function () { +EmailTest.restoreOutputStream = function () { output_stream = process.stdout; }; @@ -90,7 +90,7 @@ var smtpSend = function (mc) { * false to skip sending. */ var sendHooks = []; -_EmailTest.hookSend = function (f) { +EmailTest.hookSend = function (f) { sendHooks.push(f); }; diff --git a/packages/email/email_tests.js b/packages/email/email_tests.js index ac7aa4e320..1c367b2178 100644 --- a/packages/email/email_tests.js +++ b/packages/email/email_tests.js @@ -6,7 +6,7 @@ Tinytest.add("email - dev mode smoke test", function (test) { try { var stream = new streamBuffers.WritableStreamBuffer; - _EmailTest.overrideOutputStream(stream); + EmailTest.overrideOutputStream(stream); Email.send({ from: "foo@example.com", to: "bar@example.com", @@ -34,6 +34,6 @@ Tinytest.add("email - dev mode smoke test", function (test) { "From us.\r\n" + "====== END MAIL #0 ======\n"); } finally { - _EmailTest.restoreOutputStream(); + EmailTest.restoreOutputStream(); } }); diff --git a/packages/email/package.js b/packages/email/package.js index 27d22beb5f..f93cd39dad 100644 --- a/packages/email/package.js +++ b/packages/email/package.js @@ -9,7 +9,7 @@ Npm.depends({mailcomposer: "0.1.15", simplesmtp: "0.1.25", "stream-buffers": "0. Package.on_use(function (api) { api.use('underscore', 'server'); api.exportSymbol('Email', 'server'); - api.exportSymbol('_EmailTest', 'server', {testOnly: true}); + api.exportSymbol('EmailTest', 'server', {testOnly: true}); api.add_files('email.js', 'server'); }); diff --git a/packages/livedata/livedata_common.js b/packages/livedata/livedata_common.js index 8cff12ac7a..b39b65e491 100644 --- a/packages/livedata/livedata_common.js +++ b/packages/livedata/livedata_common.js @@ -2,7 +2,7 @@ DDP = {}; SUPPORTED_DDP_VERSIONS = [ 'pre1' ]; -_LivedataTest.SUPPORTED_DDP_VERSIONS = SUPPORTED_DDP_VERSIONS; +LivedataTest.SUPPORTED_DDP_VERSIONS = SUPPORTED_DDP_VERSIONS; MethodInvocation = function (options) { var self = this; diff --git a/packages/livedata/livedata_connection.js b/packages/livedata/livedata_connection.js index 839e9f383a..5b77ef16a6 100644 --- a/packages/livedata/livedata_connection.js +++ b/packages/livedata/livedata_connection.js @@ -1383,7 +1383,7 @@ _.extend(Connection.prototype, { } }); -_LivedataTest.Connection = Connection; +LivedataTest.Connection = Connection; // @param url {String} URL to Meteor app, // e.g.: diff --git a/packages/livedata/livedata_connection_tests.js b/packages/livedata/livedata_connection_tests.js index c2ff0c2f72..72d56d65cb 100644 --- a/packages/livedata/livedata_connection_tests.js +++ b/packages/livedata/livedata_connection_tests.js @@ -2,14 +2,14 @@ var newConnection = function (stream) { // Some of these tests leave outstanding methods with no result yet // returned. This should not block us from re-running tests when sources // change. - return new _LivedataTest.Connection(stream, {reloadWithOutstanding: true}); + return new LivedataTest.Connection(stream, {reloadWithOutstanding: true}); }; var makeConnectMessage = function (session) { var msg = { msg: 'connect', - version: _LivedataTest.SUPPORTED_DDP_VERSIONS[0], - support: _LivedataTest.SUPPORTED_DDP_VERSIONS + version: LivedataTest.SUPPORTED_DDP_VERSIONS[0], + support: LivedataTest.SUPPORTED_DDP_VERSIONS }; if (session) @@ -1345,12 +1345,12 @@ testAsyncMulti("livedata connection - reconnect to a different server", [ Tinytest.addAsync("livedata connection - version negotiation requires renegotiating", function (test, onComplete) { - var connection = new _LivedataTest.Connection(getSelfConnectionUrl(), { + var connection = new LivedataTest.Connection(getSelfConnectionUrl(), { reloadWithOutstanding: true, - supportedDDPVersions: ["garbled", _LivedataTest.SUPPORTED_DDP_VERSIONS[0]], + supportedDDPVersions: ["garbled", LivedataTest.SUPPORTED_DDP_VERSIONS[0]], onConnectionFailure: function () { test.fail(); onComplete(); }, onConnected: function () { - test.equal(connection._version, _LivedataTest.SUPPORTED_DDP_VERSIONS[0]); + test.equal(connection._version, LivedataTest.SUPPORTED_DDP_VERSIONS[0]); connection._stream.disconnect({_permanent: true}); onComplete(); } @@ -1359,7 +1359,7 @@ Tinytest.addAsync("livedata connection - version negotiation requires renegotiat Tinytest.addAsync("livedata connection - version negotiation error", function (test, onComplete) { - var connection = new _LivedataTest.Connection(getSelfConnectionUrl(), { + var connection = new LivedataTest.Connection(getSelfConnectionUrl(), { reloadWithOutstanding: true, supportedDDPVersions: ["garbled", "more garbled"], onConnectionFailure: function () { diff --git a/packages/livedata/livedata_server.js b/packages/livedata/livedata_server.js index bd9fe3d6b3..5c396c4a00 100644 --- a/packages/livedata/livedata_server.js +++ b/packages/livedata/livedata_server.js @@ -104,7 +104,7 @@ var SessionCollectionView = function (collectionName, sessionCallbacks) { self.callbacks = sessionCallbacks; }; -_LivedataTest.SessionCollectionView = SessionCollectionView; +LivedataTest.SessionCollectionView = SessionCollectionView; _.extend(SessionCollectionView.prototype, { @@ -1318,7 +1318,7 @@ var calculateVersion = function (clientSupportedVersions, return correctVersion; }; -_LivedataTest.calculateVersion = calculateVersion; +LivedataTest.calculateVersion = calculateVersion; // "blind" exceptions other than those that were deliberately thrown to signal diff --git a/packages/livedata/livedata_tests.js b/packages/livedata/livedata_tests.js index d727994571..7ea0f656e8 100644 --- a/packages/livedata/livedata_tests.js +++ b/packages/livedata/livedata_tests.js @@ -32,7 +32,7 @@ if (Meteor.isServer) { Tinytest.add("livedata - version negotiation", function (test) { var versionCheck = function (clientVersions, serverVersions, expected) { test.equal( - _LivedataTest.calculateVersion(clientVersions, serverVersions), + LivedataTest.calculateVersion(clientVersions, serverVersions), expected); }; @@ -504,7 +504,7 @@ if (Meteor.isClient) { testAsyncMulti("livedata - publisher errors", (function () { // Use a separate connection so that we can safely check to see if // conn._subscriptions is empty. - var conn = new _LivedataTest.Connection('/', + var conn = new LivedataTest.Connection('/', {reloadWithOutstanding: true}); var collName = Random.id(); var coll = new Meteor.Collection(collName, {connection: conn}); diff --git a/packages/livedata/package.js b/packages/livedata/package.js index 3a9eb2bf38..17cedb78b7 100644 --- a/packages/livedata/package.js +++ b/packages/livedata/package.js @@ -26,7 +26,7 @@ Package.on_use(function (api) { api.exportSymbol('DDP'); api.exportSymbol('DDPServer', 'server'); - api.exportSymbol('_LivedataTest', {testOnly: true}); + api.exportSymbol('LivedataTest', {testOnly: true}); // Transport api.use('reload', 'client'); diff --git a/packages/livedata/session_view_tests.js b/packages/livedata/session_view_tests.js index 6f590c3026..24c7ad2a8d 100644 --- a/packages/livedata/session_view_tests.js +++ b/packages/livedata/session_view_tests.js @@ -1,6 +1,6 @@ var newView = function(test) { var results = []; - var view = new _LivedataTest.SessionCollectionView('test', { + var view = new LivedataTest.SessionCollectionView('test', { added: function (collection, id, fields) { results.push({fun: 'added', id: id, fields: fields}); }, diff --git a/packages/livedata/stream_client_common.js b/packages/livedata/stream_client_common.js index 1ef4165a89..86de204b3b 100644 --- a/packages/livedata/stream_client_common.js +++ b/packages/livedata/stream_client_common.js @@ -1,4 +1,4 @@ -_LivedataTest = {}; +LivedataTest = {}; // XXX from Underscore.String (http://epeli.github.com/underscore.string/) var startsWith = function(str, starts) { @@ -70,7 +70,7 @@ toWebsocketUrl = function (url) { return ret; }; -_LivedataTest.toSockjsUrl = toSockjsUrl; +LivedataTest.toSockjsUrl = toSockjsUrl; _.extend(ClientStream.prototype, { diff --git a/packages/livedata/stream_tests.js b/packages/livedata/stream_tests.js index 25e9eeada6..0aef16d515 100644 --- a/packages/livedata/stream_tests.js +++ b/packages/livedata/stream_tests.js @@ -89,7 +89,7 @@ testAsyncMulti("stream - disconnect remains offline", [ Tinytest.add("stream - sockjs urls are computed correctly", function(test) { var testHasSockjsUrl = function(raw, expectedSockjsUrl) { - var actual = _LivedataTest.toSockjsUrl(raw); + var actual = LivedataTest.toSockjsUrl(raw); if (expectedSockjsUrl instanceof RegExp) test.isTrue(actual.match(expectedSockjsUrl), actual); else diff --git a/packages/mongo-livedata/mongo_driver.js b/packages/mongo-livedata/mongo_driver.js index c6d8a214f9..b230cfc125 100644 --- a/packages/mongo-livedata/mongo_driver.js +++ b/packages/mongo-livedata/mongo_driver.js @@ -7,7 +7,7 @@ * these outside of a fiber they will explode! */ -_MongoLivedataTest = {}; +MongoLivedataTest = {}; var path = Npm.require('path'); var MongoDB = Npm.require('mongodb'); @@ -993,7 +993,7 @@ _.extend(LiveResultsSet.prototype, { // - If you disconnect and reconnect from Mongo, it will essentially restart // the query, which will lead to duplicate results. This is pretty bad, // but if you include a field called 'ts' which is inserted as -// new _MongoLivedataTest.MongoTimestamp(0, 0) (which is initialized to the +// new MongoLivedataTest.MongoTimestamp(0, 0) (which is initialized to the // current Mongo-style timestamp), we'll be able to find the place to // restart properly. (This field is specifically understood by Mongo with an // optimization which allows it to find the right place to start without @@ -1083,4 +1083,4 @@ MongoConnection.prototype._observeChangesTailable = function ( // XXX We probably need to find a better way to expose this. Right now // it's only used by tests, but in fact you need it in normal // operation to interact with capped collections. -_MongoLivedataTest.MongoTimestamp = MongoDB.Timestamp; +MongoLivedataTest.MongoTimestamp = MongoDB.Timestamp; diff --git a/packages/mongo-livedata/mongo_livedata_tests.js b/packages/mongo-livedata/mongo_livedata_tests.js index 0f1e6a2bc1..d00547dc09 100644 --- a/packages/mongo-livedata/mongo_livedata_tests.js +++ b/packages/mongo-livedata/mongo_livedata_tests.js @@ -745,7 +745,7 @@ testAsyncMulti('mongo-livedata - document goes through a transform, ' + idGenera testAsyncMulti('mongo-livedata - document with binary data, ' + idGeneration, [ function (test, expect) { // XXX probably shouldn't use EJSON's private test symbols - var bin = _EJSONTest.base64Decode( + var bin = EJSONTest.base64Decode( "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyBy" + "ZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJv" + "bSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhl" + diff --git a/packages/mongo-livedata/observe_changes_tests.js b/packages/mongo-livedata/observe_changes_tests.js index c58bcafece..73ebab83b9 100644 --- a/packages/mongo-livedata/observe_changes_tests.js +++ b/packages/mongo-livedata/observe_changes_tests.js @@ -206,7 +206,7 @@ if (Meteor.isServer) { self.xs = []; self.expects = []; self.insert = function (fields) { - coll.insert(_.extend({ts: new _MongoLivedataTest.MongoTimestamp(0, 0)}, + coll.insert(_.extend({ts: new MongoLivedataTest.MongoTimestamp(0, 0)}, fields)); }; diff --git a/packages/mongo-livedata/package.js b/packages/mongo-livedata/package.js index e3704754be..3e70766b84 100644 --- a/packages/mongo-livedata/package.js +++ b/packages/mongo-livedata/package.js @@ -26,7 +26,7 @@ Package.on_use(function (api) { // Allow us to detect 'autopublish', and publish collections if it's loaded. api.use('autopublish', 'server', {weak: true}); - api.exportSymbol('_MongoLivedataTest', 'server', {testOnly: true}); + api.exportSymbol('MongoLivedataTest', 'server', {testOnly: true}); api.add_files('mongo_driver.js', 'server'); api.add_files('local_collection_driver.js', ['client', 'server']); diff --git a/packages/oauth/oauth_server.js b/packages/oauth/oauth_server.js index aa78475742..e61e152582 100644 --- a/packages/oauth/oauth_server.js +++ b/packages/oauth/oauth_server.js @@ -1,7 +1,7 @@ var Fiber = Npm.require('fibers'); Oauth = {}; -_OauthTest = {}; +OauthTest = {}; RoutePolicy.declare('/_oauth/', 'network'); @@ -43,7 +43,7 @@ Oauth.registerService = function (name, version, urls, handleOauthRequest) { }; // For test cleanup. -_OauthTest.unregisterService = function (name) { +OauthTest.unregisterService = function (name) { delete registeredServices[name]; }; @@ -129,7 +129,7 @@ middleware = function (req, res, next) { } }; -_OauthTest.middleware = middleware; +OauthTest.middleware = middleware; // Handle /_oauth/* paths and extract the service name // diff --git a/packages/oauth/package.js b/packages/oauth/package.js index 5c292869e5..a41aeb5dea 100644 --- a/packages/oauth/package.js +++ b/packages/oauth/package.js @@ -9,7 +9,7 @@ Package.on_use(function (api) { api.use(['underscore', 'service-configuration'], 'server'); api.exportSymbol('Oauth'); - api.exportSymbol('_OauthTest', 'server', {testOnly: true}); + api.exportSymbol('OauthTest', 'server', {testOnly: true}); api.add_files('oauth_client.js', 'client'); api.add_files('oauth_server.js', 'server'); diff --git a/packages/oauth1/oauth1_server.js b/packages/oauth1/oauth1_server.js index 2cd79e603e..2e2a530020 100644 --- a/packages/oauth1/oauth1_server.js +++ b/packages/oauth1/oauth1_server.js @@ -1,7 +1,7 @@ // A place to store request tokens pending verification var requestTokens = {}; -_OAuth1Test = {requestTokens: requestTokens}; +OAuth1Test = {requestTokens: requestTokens}; // connect middleware Oauth._requestHandlers['1'] = function (service, query, res) { diff --git a/packages/oauth1/oauth1_tests.js b/packages/oauth1/oauth1_tests.js index 41156af438..d7eb0e1979 100644 --- a/packages/oauth1/oauth1_tests.js +++ b/packages/oauth1/oauth1_tests.js @@ -40,7 +40,7 @@ Tinytest.add("oauth1 - loginResultForCredentialToken is stored", function (test) }); // simulate logging in using twitterfoo - _OAuth1Test.requestTokens[credentialToken] = twitterfooAccessToken; + OAuth1Test.requestTokens[credentialToken] = twitterfooAccessToken; var req = { method: "POST", @@ -50,7 +50,7 @@ Tinytest.add("oauth1 - loginResultForCredentialToken is stored", function (test) oauth_token: twitterfooAccessToken } }; - _OauthTest.middleware(req, new http.ServerResponse(req)); + OauthTest.middleware(req, new http.ServerResponse(req)); // Test that right data is placed on the loginResult map test.equal( @@ -67,7 +67,7 @@ Tinytest.add("oauth1 - loginResultForCredentialToken is stored", function (test) Oauth._loginResultForCredentialToken[credentialToken].options.option1, twitterOption1); } finally { - _OauthTest.unregisterService(serviceName); + OauthTest.unregisterService(serviceName); } }); diff --git a/packages/oauth1/package.js b/packages/oauth1/package.js index 0dd304b6b9..dcecd3825d 100644 --- a/packages/oauth1/package.js +++ b/packages/oauth1/package.js @@ -11,7 +11,7 @@ Package.on_use(function (api) { api.use('http', 'server'); api.exportSymbol('OAuth1Binding', 'server'); - api.exportSymbol('_OAuth1Test', 'server', {testOnly: true}); + api.exportSymbol('OAuth1Test', 'server', {testOnly: true}); api.add_files('oauth1_binding.js', 'server'); api.add_files('oauth1_server.js', 'server'); diff --git a/packages/oauth2/oauth2_tests.js b/packages/oauth2/oauth2_tests.js index b424e0430f..00cd88e7d3 100644 --- a/packages/oauth2/oauth2_tests.js +++ b/packages/oauth2/oauth2_tests.js @@ -20,7 +20,7 @@ Tinytest.add("oauth2 - loginResultForCredentialToken is stored", function (test) var req = {method: "POST", url: "/_oauth/" + serviceName + "?close", query: {state: credentialToken}}; - _OauthTest.middleware(req, new http.ServerResponse(req)); + OauthTest.middleware(req, new http.ServerResponse(req)); // Test that the login result for that user is prepared test.equal( @@ -31,6 +31,6 @@ Tinytest.add("oauth2 - loginResultForCredentialToken is stored", function (test) Oauth._loginResultForCredentialToken[credentialToken].options.option1, foobookOption1); } finally { - _OauthTest.unregisterService(serviceName); + OauthTest.unregisterService(serviceName); } }); diff --git a/packages/routepolicy/package.js b/packages/routepolicy/package.js index da08d2699c..dbe0b433ed 100644 --- a/packages/routepolicy/package.js +++ b/packages/routepolicy/package.js @@ -9,7 +9,7 @@ Package.on_use(function (api) { // Package.webapp and only after initial load. api.use('webapp', 'server', {unordered: true}); api.exportSymbol('RoutePolicy', 'server'); - api.exportSymbol('_RoutePolicyTest', 'server', {testOnly: true}); + api.exportSymbol('RoutePolicyTest', 'server', {testOnly: true}); api.add_files('routepolicy.js', 'server'); }); diff --git a/packages/routepolicy/routepolicy.js b/packages/routepolicy/routepolicy.js index f617a60b02..57e354e15f 100644 --- a/packages/routepolicy/routepolicy.js +++ b/packages/routepolicy/routepolicy.js @@ -24,9 +24,9 @@ // routes would break tinytest... so allow policy instances to be // constructed for testing. -_RoutePolicyTest = {}; +RoutePolicyTest = {}; -var RoutePolicyConstructor = _RoutePolicyTest.Constructor = function () { +var RoutePolicyConstructor = RoutePolicyTest.Constructor = function () { var self = this; self.urlPrefixTypes = {}; }; diff --git a/packages/routepolicy/routepolicy_tests.js b/packages/routepolicy/routepolicy_tests.js index a4fe220072..2c8ccc892e 100644 --- a/packages/routepolicy/routepolicy_tests.js +++ b/packages/routepolicy/routepolicy_tests.js @@ -1,5 +1,5 @@ Tinytest.add("routepolicy - declare", function (test) { - var policy = new _RoutePolicyTest.Constructor(); + var policy = new RoutePolicyTest.Constructor(); policy.declare('/sockjs/', 'network'); policy.declare('/bigphoto.jpg', 'static-online'); @@ -37,7 +37,7 @@ Tinytest.add("routepolicy - static conflicts", function (test) { "url": "/bigphoto.jpg" } ]; - var policy = new _RoutePolicyTest.Constructor(); + var policy = new RoutePolicyTest.Constructor(); test.equal( policy.checkForConflictWithStatic('/sockjs/', 'network', manifest), @@ -51,7 +51,7 @@ Tinytest.add("routepolicy - static conflicts", function (test) { }); Tinytest.add("routepolicy - checkUrlPrefix", function (test) { - var policy = new _RoutePolicyTest.Constructor(); + var policy = new RoutePolicyTest.Constructor(); policy.declare('/sockjs/', 'network'); test.equal( diff --git a/packages/spark/package.js b/packages/spark/package.js index 932bc455d7..eee2564830 100644 --- a/packages/spark/package.js +++ b/packages/spark/package.js @@ -9,7 +9,7 @@ Package.on_use(function (api) { 'client'); api.exportSymbol('Spark', 'client'); - api.exportSymbol('_SparkTest', 'client', {testOnly: true}); + api.exportSymbol('SparkTest', 'client', {testOnly: true}); api.add_files(['spark.js', 'patch.js', 'convenience.js', 'utils.js'], 'client'); diff --git a/packages/spark/patch.js b/packages/spark/patch.js index 7c4375dba1..7fcfbeb72f 100644 --- a/packages/spark/patch.js +++ b/packages/spark/patch.js @@ -571,4 +571,4 @@ Patcher._copyAttributes = function(tgt, src) { } }; -_SparkTest.Patcher = Patcher; +SparkTest.Patcher = Patcher; diff --git a/packages/spark/patch_tests.js b/packages/spark/patch_tests.js index c3bfd06487..a5360b4c5f 100644 --- a/packages/spark/patch_tests.js +++ b/packages/spark/patch_tests.js @@ -1,6 +1,6 @@ Tinytest.add("spark - patch - basic", function(test) { - var Patcher = _SparkTest.Patcher; + var Patcher = SparkTest.Patcher; var div = function(html) { var n = document.createElement("DIV"); @@ -144,7 +144,7 @@ Tinytest.add("spark - patch - copyAttributes", function(test) { if (! node) { node = n; } else { - _SparkTest.Patcher._copyAttributes(node, n); + SparkTest.Patcher._copyAttributes(node, n); } lastAttrs = {}; _.each(allAttrNames, function(v,k) { diff --git a/packages/spark/spark.js b/packages/spark/spark.js index bb616111aa..7c3cf27911 100644 --- a/packages/spark/spark.js +++ b/packages/spark/spark.js @@ -26,7 +26,7 @@ // (not hard to see what it is.) Spark = {}; -_SparkTest = {}; +SparkTest = {}; var currentRenderer = (function () { var current = null; @@ -44,7 +44,7 @@ var currentRenderer = (function () { })(); TAG = "_spark_" + Random.id(); -_SparkTest.TAG = TAG; +SparkTest.TAG = TAG; // We also export this as Spark._TAG due to a historical accident. I // don't know if anything uses it (possibly some of Chris Mather's @@ -67,7 +67,7 @@ var ANNOTATION_LIST_ITEM = "item"; // Use from tests to turn on extra UniversalEventListener sanity checks var checkIECompliance = false; -_SparkTest.setCheckIECompliance = function (value) { +SparkTest.setCheckIECompliance = function (value) { checkIECompliance = value; }; @@ -1244,7 +1244,7 @@ Spark.createLandmark = function (options, htmlFunc) { }); }; -_SparkTest.getEnclosingLandmark = function (node) { +SparkTest.getEnclosingLandmark = function (node) { var range = findRangeOfType(ANNOTATION_LANDMARK, node); return range ? range.landmark : null; }; diff --git a/packages/spark/spark_tests.js b/packages/spark/spark_tests.js index cb2d081fb6..cab0f7ea94 100644 --- a/packages/spark/spark_tests.js +++ b/packages/spark/spark_tests.js @@ -4,7 +4,7 @@ // XXX test variable wrapping (eg TR vs THEAD) inside each branch of Spark.list? -_SparkTest.setCheckIECompliance(true); +SparkTest.setCheckIECompliance(true); // Tests can use {preserve: idNameLabels} or renderWithPreservation // to cause any element with an id or name to be preserved. This effect @@ -74,7 +74,7 @@ Tinytest.add("spark - assembly", function (test) { test.equal(furtherCanon(f.html()), html); var actualGroups = []; - var tempRange = new LiveRange(_SparkTest.TAG, frag); + var tempRange = new LiveRange(SparkTest.TAG, frag); tempRange.visit(function (isStart, rng) { if (! isStart && rng.type === "data" /* Spark._ANNOTATION_DATA */) actualGroups.push(furtherCanon(canonicalizeHtml( @@ -3341,8 +3341,8 @@ Tinytest.add("spark - current landmark", function (test) { test.equal(callbacks, 1); Deps.flush(); test.equal(callbacks, 2); - test.equal(null, _SparkTest.getEnclosingLandmark(d.node())); - var enc = _SparkTest.getEnclosingLandmark(d.node().firstChild); + test.equal(null, SparkTest.getEnclosingLandmark(d.node())); + var enc = SparkTest.getEnclosingLandmark(d.node().firstChild); test.equal(enc.a, 9); test.equal(enc.b, 2); test.isFalse('c' in enc); @@ -3358,32 +3358,32 @@ Tinytest.add("spark - current landmark", function (test) { Deps.flush(); test.equal(callbacks, 4); - test.isTrue(_SparkTest.getEnclosingLandmark(findOuter()).outer); - test.isTrue(_SparkTest.getEnclosingLandmark(findInnerA()).innerA); - test.isTrue(_SparkTest.getEnclosingLandmark(findInnerB()).innerB); - test.equal(1, _SparkTest.getEnclosingLandmark(findOuter()).renderCount); - test.equal(1, _SparkTest.getEnclosingLandmark(findInnerA()).renderCount); - test.equal(1, _SparkTest.getEnclosingLandmark(findInnerB()).renderCount); + test.isTrue(SparkTest.getEnclosingLandmark(findOuter()).outer); + test.isTrue(SparkTest.getEnclosingLandmark(findInnerA()).innerA); + test.isTrue(SparkTest.getEnclosingLandmark(findInnerB()).innerB); + test.equal(1, SparkTest.getEnclosingLandmark(findOuter()).renderCount); + test.equal(1, SparkTest.getEnclosingLandmark(findInnerA()).renderCount); + test.equal(1, SparkTest.getEnclosingLandmark(findInnerB()).renderCount); R.set(4) Deps.flush(); test.equal(callbacks, 5); - test.equal(2, _SparkTest.getEnclosingLandmark(findOuter()).renderCount); - test.equal(2, _SparkTest.getEnclosingLandmark(findInnerA()).renderCount); + test.equal(2, SparkTest.getEnclosingLandmark(findOuter()).renderCount); + test.equal(2, SparkTest.getEnclosingLandmark(findInnerA()).renderCount); R.set(5) Deps.flush(); test.equal(callbacks, 6); - test.equal(3, _SparkTest.getEnclosingLandmark(findOuter()).renderCount); - test.equal(3, _SparkTest.getEnclosingLandmark(findInnerA()).renderCount); - test.equal(1, _SparkTest.getEnclosingLandmark(findInnerB()).renderCount); + test.equal(3, SparkTest.getEnclosingLandmark(findOuter()).renderCount); + test.equal(3, SparkTest.getEnclosingLandmark(findInnerA()).renderCount); + test.equal(1, SparkTest.getEnclosingLandmark(findInnerB()).renderCount); R.set(6) Deps.flush(); test.equal(callbacks, 7); - test.equal(4, _SparkTest.getEnclosingLandmark(findOuter()).renderCount); - test.equal(4, _SparkTest.getEnclosingLandmark(findInnerA()).renderCount); - test.equal(2, _SparkTest.getEnclosingLandmark(findInnerB()).renderCount); + test.equal(4, SparkTest.getEnclosingLandmark(findOuter()).renderCount); + test.equal(4, SparkTest.getEnclosingLandmark(findInnerA()).renderCount); + test.equal(2, SparkTest.getEnclosingLandmark(findInnerB()).renderCount); d.kill(); Deps.flush(); From 2894252578e787fb3e70402319a85a382df9b700 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 24 Jul 2013 23:04:07 -0700 Subject: [PATCH 098/175] Mark test helpers as test-only, too. --- packages/test-helpers/package.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/test-helpers/package.js b/packages/test-helpers/package.js index bbdf75c36b..811c0375a2 100644 --- a/packages/test-helpers/package.js +++ b/packages/test-helpers/package.js @@ -12,7 +12,7 @@ Package.on_use(function (api) { 'pollUntil', 'WrappedFrag', 'try_all_permutations', 'StubStream', 'SeededRandom', 'ReactiveVar', 'OnscreenDiv', 'clickElement', 'blurElement', 'focusElement', 'simulateEvent', 'getStyleProperty', 'canonicalizeHtml', - 'withCallbackLogger', 'testAsyncMulti']); + 'withCallbackLogger', 'testAsyncMulti'], {testOnly: true}); api.add_files('try_all_permutations.js'); api.add_files('async_multi.js'); From ae6f226425cdcf978b3f20aff9034bf8423959ab Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 24 Jul 2013 23:29:28 -0700 Subject: [PATCH 099/175] Make backbone use the _ in its scope. --- packages/backbone/backbone.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/backbone/backbone.js b/packages/backbone/backbone.js index e770b20e51..21bce6cba2 100644 --- a/packages/backbone/backbone.js +++ b/packages/backbone/backbone.js @@ -37,8 +37,10 @@ Backbone.VERSION = '0.9.2'; // Require Underscore, if we're on the server, and it's not already present. - var _ = root._; - if (!_ && (typeof require !== 'undefined')) _ = require('underscore'); + // Commented these lines out; we have _ via api.use. + // var _ = root._; + // if (!_ && (typeof require !== 'undefined')) _ = require('underscore'); + // // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable. var $ = root.jQuery || root.Zepto || root.ender; From 0faf93b1809290146708846a06214722c0c872d5 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 24 Jul 2013 23:35:08 -0700 Subject: [PATCH 100/175] oops, define Meteor.autosubscribe correctly --- packages/deps/deprecated.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/deps/deprecated.js b/packages/deps/deprecated.js index 6fd2c6516c..66971a8b35 100644 --- a/packages/deps/deprecated.js +++ b/packages/deps/deprecated.js @@ -9,7 +9,7 @@ Meteor.autorun = Deps.autorun; // We used to require a special "autosubscribe" call to reactively subscribe to // things. Now, it works with autorun. // XXX COMPAT WITH 0.5.4 -Meteor.autosubscribe = Deps.autosubscribe; +Meteor.autosubscribe = Deps.autorun; // This Deps API briefly existed in 0.5.8 and 0.5.9 // XXX COMPAT WITH 0.5.9 From 691bd63f9cb90a53f5ab38d636cfe1df8c152f3d Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 24 Jul 2013 23:35:21 -0700 Subject: [PATCH 101/175] Include handlebars and random in the standard app packages list. --- tools/packages.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/packages.js b/tools/packages.js index e3fbd1802c..e50a88a6cc 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -1762,7 +1762,7 @@ _.extend(Package.prototype, { // XXX remove and make everyone explicitly declare all dependencies ['meteor', 'webapp', 'logging', 'deps', 'session', 'livedata', 'mongo-livedata', 'spark', 'templating', 'check', - 'underscore', 'jquery'], + 'underscore', 'jquery', 'handlebars', 'random'], project.get_packages(appDir)); var arch = sliceName === "server" ? "native" : "browser"; From 4b3cb9d30580e2b8f8b688f358c777b9619a6263 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 25 Jul 2013 18:47:08 -0700 Subject: [PATCH 102/175] Rename api.exportSymbol -> api.export. --- packages/accounts-base/package.js | 2 +- packages/check/package.js | 2 +- packages/coffeescript-test-helper/package.js | 2 +- packages/ctl-helper/package.js | 2 +- packages/ctl/package.js | 2 +- packages/d3/package.js | 2 +- packages/deps/package.js | 2 +- packages/dev-bundle-fetcher/package.js | 2 +- packages/domutils/package.js | 2 +- packages/ejson/package.js | 4 ++-- packages/email/package.js | 4 ++-- packages/facebook/package.js | 2 +- packages/github/package.js | 2 +- packages/google/package.js | 2 +- packages/handlebars/package.js | 2 +- packages/htmljs/package.js | 2 +- packages/http/package.js | 2 +- packages/jquery/package.js | 4 ++-- packages/js-analyze/package.js | 2 +- packages/jsparse/package.js | 2 +- packages/livedata/package.js | 6 +++--- packages/liverange/package.js | 2 +- packages/logging/package.js | 2 +- packages/meetup/package.js | 2 +- packages/meteor/package.js | 2 +- packages/minifiers/package.js | 2 +- packages/minimongo/package.js | 2 +- packages/mongo-livedata/package.js | 2 +- packages/oauth/package.js | 4 ++-- packages/oauth1/package.js | 4 ++-- packages/ordered-dict/package.js | 2 +- packages/random/package.js | 2 +- packages/reactive-dict/package.js | 2 +- packages/reload/package.js | 2 +- packages/routepolicy/package.js | 4 ++-- packages/service-configuration/package.js | 2 +- packages/session/package.js | 2 +- packages/showdown/package.js | 2 +- packages/spark/package.js | 4 ++-- packages/srp/package.js | 2 +- packages/star-translate/package.js | 2 +- packages/templating/package.js | 2 +- packages/test-helpers/package.js | 2 +- packages/test-in-console/package.js | 2 +- packages/tinytest/package.js | 2 +- packages/twitter/package.js | 2 +- packages/underscore/package.js | 2 +- packages/universal-events/package.js | 2 +- packages/webapp/package.js | 4 ++-- packages/weibo/package.js | 2 +- tools/packages.js | 4 ++-- .../tests/app-with-private/packages/test-package/package.js | 2 +- 52 files changed, 63 insertions(+), 63 deletions(-) diff --git a/packages/accounts-base/package.js b/packages/accounts-base/package.js index ec8f15eb9b..70ec142040 100644 --- a/packages/accounts-base/package.js +++ b/packages/accounts-base/package.js @@ -25,7 +25,7 @@ Package.on_use(function (api) { // it's loaded. api.use('autopublish', 'server', {weak: true}); - api.exportSymbol('Accounts'); + api.export('Accounts'); api.add_files('accounts_common.js', ['client', 'server']); api.add_files('accounts_server.js', 'server'); diff --git a/packages/check/package.js b/packages/check/package.js index 6fcfb6a5bc..ca73cefa5e 100644 --- a/packages/check/package.js +++ b/packages/check/package.js @@ -6,7 +6,7 @@ Package.describe({ Package.on_use(function (api) { api.use(['underscore', 'ejson'], ['client', 'server']); - api.exportSymbol(['check', 'Match']); + api.export(['check', 'Match']); api.add_files('match.js', ['client', 'server']); }); diff --git a/packages/coffeescript-test-helper/package.js b/packages/coffeescript-test-helper/package.js index c820512e32..b420bc4ae3 100644 --- a/packages/coffeescript-test-helper/package.js +++ b/packages/coffeescript-test-helper/package.js @@ -5,6 +5,6 @@ Package.describe({ Package.on_use(function (api) { api.use('coffeescript', ['client', 'server']); - api.exportSymbol('COFFEESCRIPT_EXPORTED'); + api.export('COFFEESCRIPT_EXPORTED'); api.add_files("exporting.coffee", ['client', 'server']); }); diff --git a/packages/ctl-helper/package.js b/packages/ctl-helper/package.js index 606fb78c2d..52a5394237 100644 --- a/packages/ctl-helper/package.js +++ b/packages/ctl-helper/package.js @@ -6,6 +6,6 @@ Npm.depends({optimist: '0.4.0'}); Package.on_use(function (api) { api.use(['underscore', 'livedata', 'mongo-livedata'], 'server'); - api.exportSymbol('Ctl', 'server'); + api.export('Ctl', 'server'); api.add_files('ctl-helper.js', 'server'); }); diff --git a/packages/ctl/package.js b/packages/ctl/package.js index b338c09164..a8966c19cc 100644 --- a/packages/ctl/package.js +++ b/packages/ctl/package.js @@ -4,6 +4,6 @@ Package.describe({ Package.on_use(function (api) { api.use(['underscore', 'livedata', 'mongo-livedata', 'ctl-helper'], 'server'); - api.exportSymbol('main', 'server'); + api.export('main', 'server'); api.add_files('ctl.js', 'server'); }); diff --git a/packages/d3/package.js b/packages/d3/package.js index 170e60641a..eac342c7e3 100644 --- a/packages/d3/package.js +++ b/packages/d3/package.js @@ -3,6 +3,6 @@ Package.describe({ }); Package.on_use(function (api) { - api.exportSymbol('d3', 'client'); + api.export('d3', 'client'); api.add_files('d3.v3.js', 'client'); }); diff --git a/packages/deps/package.js b/packages/deps/package.js index e1ef519f7c..ca7d98b7be 100644 --- a/packages/deps/package.js +++ b/packages/deps/package.js @@ -7,7 +7,7 @@ Package.describe({ Package.on_use(function (api) { api.use('underscore'); - api.exportSymbol('Deps'); + api.export('Deps'); api.add_files('deps.js'); api.add_files('deprecated.js'); }); diff --git a/packages/dev-bundle-fetcher/package.js b/packages/dev-bundle-fetcher/package.js index 1f1f0ecf4b..d0824bc5b9 100644 --- a/packages/dev-bundle-fetcher/package.js +++ b/packages/dev-bundle-fetcher/package.js @@ -4,6 +4,6 @@ Package.describe({ }); Package.on_use(function (api) { - api.exportSymbol('DevBundleFetcher', 'server'); + api.export('DevBundleFetcher', 'server'); api.add_files(['dev-bundle', 'dev-bundle.js'], ['server']); }); diff --git a/packages/domutils/package.js b/packages/domutils/package.js index 6bf5d76a1d..86323a768a 100644 --- a/packages/domutils/package.js +++ b/packages/domutils/package.js @@ -14,7 +14,7 @@ Package.on_use(function (api) { api.use('underscore', 'client'); - api.exportSymbol('DomUtils', 'client'); + api.export('DomUtils', 'client'); api.add_files('domutils.js', 'client'); }); diff --git a/packages/ejson/package.js b/packages/ejson/package.js index bad57cb9fb..f3b93dbc2b 100644 --- a/packages/ejson/package.js +++ b/packages/ejson/package.js @@ -5,8 +5,8 @@ Package.describe({ Package.on_use(function (api) { api.use(['json', 'underscore']); - api.exportSymbol('EJSON'); - api.exportSymbol('EJSONTest', {testOnly: true}); + api.export('EJSON'); + api.export('EJSONTest', {testOnly: true}); api.add_files('ejson.js', ['client', 'server']); api.add_files('base64.js', ['client', 'server']); }); diff --git a/packages/email/package.js b/packages/email/package.js index f93cd39dad..e1f2ee0d49 100644 --- a/packages/email/package.js +++ b/packages/email/package.js @@ -8,8 +8,8 @@ Npm.depends({mailcomposer: "0.1.15", simplesmtp: "0.1.25", "stream-buffers": "0. Package.on_use(function (api) { api.use('underscore', 'server'); - api.exportSymbol('Email', 'server'); - api.exportSymbol('EmailTest', 'server', {testOnly: true}); + api.export('Email', 'server'); + api.export('EmailTest', 'server', {testOnly: true}); api.add_files('email.js', 'server'); }); diff --git a/packages/facebook/package.js b/packages/facebook/package.js index d9254525c3..534ed18a28 100644 --- a/packages/facebook/package.js +++ b/packages/facebook/package.js @@ -14,7 +14,7 @@ Package.on_use(function(api) { api.use('random', 'client'); api.use('service-configuration', ['client', 'server']); - api.exportSymbol('Facebook'); + api.export('Facebook'); api.add_files( ['facebook_configure.html', 'facebook_configure.js'], diff --git a/packages/github/package.js b/packages/github/package.js index 99850e3756..c8b6ac92aa 100644 --- a/packages/github/package.js +++ b/packages/github/package.js @@ -14,7 +14,7 @@ Package.on_use(function(api) { api.use('random', 'client'); api.use('service-configuration', ['client', 'server']); - api.exportSymbol('Github'); + api.export('Github'); api.add_files( ['github_configure.html', 'github_configure.js'], diff --git a/packages/google/package.js b/packages/google/package.js index a88dfe31d8..8bd523c2a2 100644 --- a/packages/google/package.js +++ b/packages/google/package.js @@ -12,7 +12,7 @@ Package.on_use(function(api) { api.use(['underscore', 'service-configuration'], ['client', 'server']); api.use(['random', 'templating'], 'client'); - api.exportSymbol('Google'); + api.export('Google'); api.add_files( ['google_configure.html', 'google_configure.js'], diff --git a/packages/handlebars/package.js b/packages/handlebars/package.js index f01917a09f..90e3631cce 100644 --- a/packages/handlebars/package.js +++ b/packages/handlebars/package.js @@ -9,7 +9,7 @@ Package.on_use(function (api) { api.use('underscore'); api.use('spark', 'client'); - api.exportSymbol('Handlebars'); + api.export('Handlebars'); // XXX these should be split up into two different slices, not // different code with totally different APIs that is sent depending diff --git a/packages/htmljs/package.js b/packages/htmljs/package.js index 49407dc80d..a0cdb22fe2 100644 --- a/packages/htmljs/package.js +++ b/packages/htmljs/package.js @@ -6,7 +6,7 @@ Package.on_use(function (api) { api.use('underscore', 'client'); // Note: html.js will optionally use jquery if it's available api.add_files('html.js', 'client'); - api.exportSymbol([ + api.export([ 'A', 'ABBR', 'ACRONYM', 'B', 'BDO', 'BIG', 'BLOCKQUOTE', 'BR', 'BUTTON', 'CAPTION', 'CITE', 'CODE', 'COL', 'COLGROUP', 'DD', 'DEL', 'DFN', 'DIV', 'DL', 'DT', 'EM', 'FIELDSET', 'FORM', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', diff --git a/packages/http/package.js b/packages/http/package.js index c77be43c12..f70dd7a6b3 100644 --- a/packages/http/package.js +++ b/packages/http/package.js @@ -4,7 +4,7 @@ Package.describe({ Package.on_use(function (api) { api.use('underscore'); - api.exportSymbol('HTTP'); + api.export('HTTP'); api.add_files('httpcall_common.js', ['client', 'server']); api.add_files('httpcall_client.js', 'client'); api.add_files('httpcall_server.js', 'server'); diff --git a/packages/jquery/package.js b/packages/jquery/package.js index 5a8a2bb6cc..5439d096cf 100644 --- a/packages/jquery/package.js +++ b/packages/jquery/package.js @@ -5,6 +5,6 @@ Package.describe({ Package.on_use(function (api) { api.add_files(['jquery.js', 'post.js'], 'client'); - api.exportSymbol('$', 'client'); - api.exportSymbol('jQuery', 'client'); + api.export('$', 'client'); + api.export('jQuery', 'client'); }); diff --git a/packages/js-analyze/package.js b/packages/js-analyze/package.js index 68cf16a90e..09a0cbc200 100644 --- a/packages/js-analyze/package.js +++ b/packages/js-analyze/package.js @@ -24,6 +24,6 @@ Npm.depends({ // packages would need to function without the analysis provided by this // package). Package.on_use(function (api) { - api.exportSymbol('JSAnalyze', 'server'); + api.export('JSAnalyze', 'server'); api.add_files('js_analyze.js', 'server'); }); diff --git a/packages/jsparse/package.js b/packages/jsparse/package.js index 0f1bfdf5cb..9b09d1bef0 100644 --- a/packages/jsparse/package.js +++ b/packages/jsparse/package.js @@ -4,7 +4,7 @@ Package.describe({ }); Package.on_use(function (api) { - api.exportSymbol(['JSLexer', 'JSParser', 'ParseNode']); + api.export(['JSLexer', 'JSParser', 'ParseNode']); api.add_files(['lexer.js', 'parserlib.js', 'stringify.js', 'parser.js'], ['client', 'server']); }); diff --git a/packages/livedata/package.js b/packages/livedata/package.js index 17cedb78b7..de9a09ec39 100644 --- a/packages/livedata/package.js +++ b/packages/livedata/package.js @@ -23,10 +23,10 @@ Package.on_use(function (api) { // runs Meteor.publish while it's loaded. api.use('autopublish', 'server', {weak: true}); - api.exportSymbol('DDP'); - api.exportSymbol('DDPServer', 'server'); + api.export('DDP'); + api.export('DDPServer', 'server'); - api.exportSymbol('LivedataTest', {testOnly: true}); + api.export('LivedataTest', {testOnly: true}); // Transport api.use('reload', 'client'); diff --git a/packages/liverange/package.js b/packages/liverange/package.js index daf0625e68..15bbd5d941 100644 --- a/packages/liverange/package.js +++ b/packages/liverange/package.js @@ -4,7 +4,7 @@ Package.describe({ }); Package.on_use(function (api) { - api.exportSymbol('LiveRange', 'client'); + api.export('LiveRange', 'client'); api.add_files('liverange.js', 'client'); }); diff --git a/packages/logging/package.js b/packages/logging/package.js index b6b0edd429..f3883e7b98 100644 --- a/packages/logging/package.js +++ b/packages/logging/package.js @@ -8,7 +8,7 @@ Npm.depends({ }); Package.on_use(function (api) { - api.exportSymbol('Log'); + api.export('Log'); api.use(['underscore', 'ejson']); api.add_files('logging.js'); }); diff --git a/packages/meetup/package.js b/packages/meetup/package.js index 188ea07951..8a739fe3e5 100644 --- a/packages/meetup/package.js +++ b/packages/meetup/package.js @@ -14,7 +14,7 @@ Package.on_use(function(api) { api.use('random', 'client'); api.use('service-configuration', ['client', 'server']); - api.exportSymbol('Meetup'); + api.export('Meetup'); api.add_files( ['meetup_configure.html', 'meetup_configure.js'], diff --git a/packages/meteor/package.js b/packages/meteor/package.js index f05f8684d0..439d80e6f2 100644 --- a/packages/meteor/package.js +++ b/packages/meteor/package.js @@ -13,7 +13,7 @@ Package._transitional_registerBuildPlugin({ Package.on_use(function (api) { api.use('underscore', ['client', 'server']); - api.exportSymbol('Meteor'); + api.export('Meteor'); api.add_files('client_environment.js', 'client'); api.add_files('server_environment.js', 'server'); diff --git a/packages/minifiers/package.js b/packages/minifiers/package.js index 89c5be1f57..91f474d798 100644 --- a/packages/minifiers/package.js +++ b/packages/minifiers/package.js @@ -10,6 +10,6 @@ Npm.depends({ }); Package.on_use(function (api) { - api.exportSymbol(['CleanCSSProcess', 'UglifyJSMinify']); + api.export(['CleanCSSProcess', 'UglifyJSMinify']); api.add_files('minifiers.js', 'server'); }); diff --git a/packages/minimongo/package.js b/packages/minimongo/package.js index 2efabb381e..7502f33d08 100644 --- a/packages/minimongo/package.js +++ b/packages/minimongo/package.js @@ -4,7 +4,7 @@ Package.describe({ }); Package.on_use(function (api) { - api.exportSymbol('LocalCollection'); + api.export('LocalCollection'); api.use(['underscore', 'json', 'ejson', 'ordered-dict', 'deps', 'random', 'ordered-dict']); api.add_files([ diff --git a/packages/mongo-livedata/package.js b/packages/mongo-livedata/package.js index 3e70766b84..070c19ad19 100644 --- a/packages/mongo-livedata/package.js +++ b/packages/mongo-livedata/package.js @@ -26,7 +26,7 @@ Package.on_use(function (api) { // Allow us to detect 'autopublish', and publish collections if it's loaded. api.use('autopublish', 'server', {weak: true}); - api.exportSymbol('MongoLivedataTest', 'server', {testOnly: true}); + api.export('MongoLivedataTest', 'server', {testOnly: true}); api.add_files('mongo_driver.js', 'server'); api.add_files('local_collection_driver.js', ['client', 'server']); diff --git a/packages/oauth/package.js b/packages/oauth/package.js index a41aeb5dea..85659c45fc 100644 --- a/packages/oauth/package.js +++ b/packages/oauth/package.js @@ -8,8 +8,8 @@ Package.on_use(function (api) { api.use('webapp', 'server'); api.use(['underscore', 'service-configuration'], 'server'); - api.exportSymbol('Oauth'); - api.exportSymbol('OauthTest', 'server', {testOnly: true}); + api.export('Oauth'); + api.export('OauthTest', 'server', {testOnly: true}); api.add_files('oauth_client.js', 'client'); api.add_files('oauth_server.js', 'server'); diff --git a/packages/oauth1/package.js b/packages/oauth1/package.js index dcecd3825d..c57d98f515 100644 --- a/packages/oauth1/package.js +++ b/packages/oauth1/package.js @@ -10,8 +10,8 @@ Package.on_use(function (api) { api.use('underscore', 'server'); api.use('http', 'server'); - api.exportSymbol('OAuth1Binding', 'server'); - api.exportSymbol('OAuth1Test', 'server', {testOnly: true}); + api.export('OAuth1Binding', 'server'); + api.export('OAuth1Test', 'server', {testOnly: true}); api.add_files('oauth1_binding.js', 'server'); api.add_files('oauth1_server.js', 'server'); diff --git a/packages/ordered-dict/package.js b/packages/ordered-dict/package.js index 8ae80661cd..6118b8d588 100644 --- a/packages/ordered-dict/package.js +++ b/packages/ordered-dict/package.js @@ -5,6 +5,6 @@ Package.describe({ Package.on_use(function (api) { api.use('underscore'); - api.exportSymbol('OrderedDict'); + api.export('OrderedDict'); api.add_files('ordered_dict.js', ['client', 'server']); }); diff --git a/packages/random/package.js b/packages/random/package.js index 4a23e0665b..f8d5b5ba25 100644 --- a/packages/random/package.js +++ b/packages/random/package.js @@ -5,7 +5,7 @@ Package.describe({ Package.on_use(function (api) { api.use('underscore'); - api.exportSymbol('Random'); + api.export('Random'); api.add_files('random.js'); api.add_files('deprecated.js'); }); diff --git a/packages/reactive-dict/package.js b/packages/reactive-dict/package.js index 8036561244..df9b3702e5 100644 --- a/packages/reactive-dict/package.js +++ b/packages/reactive-dict/package.js @@ -7,7 +7,7 @@ Package.on_use(function (api) { api.use(['underscore', 'deps', 'ejson']); // If we are loading mongo-livedata, let you store ObjectIDs in it. api.use('mongo-livedata', {weak: true}); - api.exportSymbol('ReactiveDict'); + api.export('ReactiveDict'); api.add_files('reactive-dict.js'); }); diff --git a/packages/reload/package.js b/packages/reload/package.js index ac16323a00..cff2922211 100644 --- a/packages/reload/package.js +++ b/packages/reload/package.js @@ -5,7 +5,7 @@ Package.describe({ Package.on_use(function (api) { api.use(['underscore', 'logging', 'json'], 'client'); - api.exportSymbol('Reload', 'client'); + api.export('Reload', 'client'); api.add_files('reload.js', 'client'); api.add_files('deprecated.js', 'client'); }); diff --git a/packages/routepolicy/package.js b/packages/routepolicy/package.js index dbe0b433ed..745ff13e8a 100644 --- a/packages/routepolicy/package.js +++ b/packages/routepolicy/package.js @@ -8,8 +8,8 @@ Package.on_use(function (api) { // Resolve circular dependency with webapp. We can only use WebApp via // Package.webapp and only after initial load. api.use('webapp', 'server', {unordered: true}); - api.exportSymbol('RoutePolicy', 'server'); - api.exportSymbol('RoutePolicyTest', 'server', {testOnly: true}); + api.export('RoutePolicy', 'server'); + api.export('RoutePolicyTest', 'server', {testOnly: true}); api.add_files('routepolicy.js', 'server'); }); diff --git a/packages/service-configuration/package.js b/packages/service-configuration/package.js index d7e4bee97e..b1e338e30c 100644 --- a/packages/service-configuration/package.js +++ b/packages/service-configuration/package.js @@ -5,6 +5,6 @@ Package.describe({ Package.on_use(function(api) { api.use('mongo-livedata', ['client', 'server']); - api.exportSymbol('ServiceConfiguration'); + api.export('ServiceConfiguration'); api.add_files('service_configuration_common.js', ['client', 'server']); }); diff --git a/packages/session/package.js b/packages/session/package.js index 4ed5c4a905..d0689d05ee 100644 --- a/packages/session/package.js +++ b/packages/session/package.js @@ -11,7 +11,7 @@ Package.on_use(function (api) { // the session. api.use('reload', 'client', {weak: true}); - api.exportSymbol('Session', 'client'); + api.export('Session', 'client'); api.add_files('session.js', 'client'); }); diff --git a/packages/showdown/package.js b/packages/showdown/package.js index 22f1cc5fe0..f062abb9e2 100644 --- a/packages/showdown/package.js +++ b/packages/showdown/package.js @@ -10,7 +10,7 @@ var _ = Npm.require('underscore'); Package.on_use(function (api) { api.add_files("showdown.js"); - api.exportSymbol('Showdown'); + api.export('Showdown'); // Define {{markdown}} if handlebars got included. api.use("handlebars", "client", {weak: true}); diff --git a/packages/spark/package.js b/packages/spark/package.js index eee2564830..afcbc42d58 100644 --- a/packages/spark/package.js +++ b/packages/spark/package.js @@ -8,8 +8,8 @@ Package.on_use(function (api) { 'ordered-dict', 'deps', 'ejson'], 'client'); - api.exportSymbol('Spark', 'client'); - api.exportSymbol('SparkTest', 'client', {testOnly: true}); + api.export('Spark', 'client'); + api.export('SparkTest', 'client', {testOnly: true}); api.add_files(['spark.js', 'patch.js', 'convenience.js', 'utils.js'], 'client'); diff --git a/packages/srp/package.js b/packages/srp/package.js index 7df7cab4dc..f304b8dccc 100644 --- a/packages/srp/package.js +++ b/packages/srp/package.js @@ -6,7 +6,7 @@ Package.describe({ Package.on_use(function (api) { api.use(['random', 'check'], ['client', 'server']); api.use('underscore'); - api.exportSymbol('SRP'); + api.export('SRP'); api.add_files(['biginteger.js', 'sha256.js', 'srp.js'], ['client', 'server']); }); diff --git a/packages/star-translate/package.js b/packages/star-translate/package.js index 7d2212e9f4..35f71360dc 100644 --- a/packages/star-translate/package.js +++ b/packages/star-translate/package.js @@ -4,7 +4,7 @@ Package.describe({ Package.on_use(function (api) { api.use(['dev-bundle-fetcher']); - api.exportSymbol('StarTranslator'); + api.export('StarTranslator'); api.add_files(['translator.js'], 'server'); }); diff --git a/packages/templating/package.js b/packages/templating/package.js index e101233c6b..87b3dfdb11 100644 --- a/packages/templating/package.js +++ b/packages/templating/package.js @@ -23,7 +23,7 @@ Package.on_use(function (api) { api.use(['underscore', 'spark', 'handlebars'], 'client'); - api.exportSymbol('Template', 'client'); + api.export('Template', 'client'); // provides the runtime logic to instantiate our templates api.add_files('deftemplate.js', 'client'); diff --git a/packages/test-helpers/package.js b/packages/test-helpers/package.js index 811c0375a2..04a555bdb5 100644 --- a/packages/test-helpers/package.js +++ b/packages/test-helpers/package.js @@ -8,7 +8,7 @@ Package.on_use(function (api) { 'domutils']); api.use(['spark', 'jquery'], 'client'); - api.exportSymbol([ + api.export([ 'pollUntil', 'WrappedFrag', 'try_all_permutations', 'StubStream', 'SeededRandom', 'ReactiveVar', 'OnscreenDiv', 'clickElement', 'blurElement', 'focusElement', 'simulateEvent', 'getStyleProperty', 'canonicalizeHtml', diff --git a/packages/test-in-console/package.js b/packages/test-in-console/package.js index a75329d9c8..274a4c3d4f 100644 --- a/packages/test-in-console/package.js +++ b/packages/test-in-console/package.js @@ -8,7 +8,7 @@ Package.on_use(function (api) { api.use(['tinytest', 'underscore', 'random', 'ejson', 'check']); api.use('http', 'server'); - api.exportSymbol('TEST_STATUS', 'client'); + api.export('TEST_STATUS', 'client'); api.add_files(['driver.js'], "client"); api.add_files(['reporter.js'], "server"); diff --git a/packages/tinytest/package.js b/packages/tinytest/package.js index 84a1e4f97e..21cda01131 100644 --- a/packages/tinytest/package.js +++ b/packages/tinytest/package.js @@ -7,7 +7,7 @@ Package.on_use(function (api) { api.use('underscore', ['client', 'server']); api.use('random', ['client', 'server']); - api.exportSymbol('Tinytest'); + api.export('Tinytest'); api.add_files('tinytest.js', ['client', 'server']); diff --git a/packages/twitter/package.js b/packages/twitter/package.js index 0152feaa66..5180af2fd8 100644 --- a/packages/twitter/package.js +++ b/packages/twitter/package.js @@ -14,7 +14,7 @@ Package.on_use(function(api) { api.use('underscore', 'server'); api.use('service-configuration', ['client', 'server']); - api.exportSymbol('Twitter'); + api.export('Twitter'); api.add_files( ['twitter_configure.html', 'twitter_configure.js'], diff --git a/packages/underscore/package.js b/packages/underscore/package.js index d1c15fe4cb..c070df7437 100644 --- a/packages/underscore/package.js +++ b/packages/underscore/package.js @@ -18,7 +18,7 @@ Package.on_use(function (api) { // configuration alive in the codebase. api.use('meteor', {unordered: true}); - api.exportSymbol('_'); + api.export('_'); api.add_files(['pre.js', 'underscore.js', 'post.js']); }); diff --git a/packages/universal-events/package.js b/packages/universal-events/package.js index bf29d659fd..55585255e7 100644 --- a/packages/universal-events/package.js +++ b/packages/universal-events/package.js @@ -5,7 +5,7 @@ Package.describe({ Package.on_use(function (api) { api.use(['underscore'], 'client'); - api.exportSymbol('UniversalEventListener', 'client'); + api.export('UniversalEventListener', 'client'); api.add_files(['listener.js', 'events-w3c.js', 'events-ie.js'], 'client'); diff --git a/packages/webapp/package.js b/packages/webapp/package.js index decfdf8022..c17c6f4683 100644 --- a/packages/webapp/package.js +++ b/packages/webapp/package.js @@ -9,7 +9,7 @@ Npm.depends({connect: "2.7.10", Package.on_use(function (api) { api.use(['logging', 'underscore', 'routepolicy'], 'server'); - api.exportSymbol('WebApp', 'server'); - api.exportSymbol('main', 'server'); + api.export('WebApp', 'server'); + api.export('main', 'server'); api.add_files('webapp_server.js', 'server'); }); diff --git a/packages/weibo/package.js b/packages/weibo/package.js index 6b78ae547d..9ea42e1875 100644 --- a/packages/weibo/package.js +++ b/packages/weibo/package.js @@ -13,7 +13,7 @@ Package.on_use(function(api) { api.use('random', 'client'); api.use('service-configuration', ['client', 'server']); - api.exportSymbol('Weibo'); + api.export('Weibo'); api.add_files( ['weibo_configure.html', 'weibo_configure.js'], diff --git a/tools/packages.js b/tools/packages.js index e50a88a6cc..444428b693 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -1588,14 +1588,14 @@ _.extend(Package.prototype, { // @param symbols String (eg "Foo") or array of String // @param where 'client', 'server', or an array of those // @param options 'testOnly', boolean. - exportSymbol: function (symbols, where, options) { + export: function (symbols, where, options) { if (role === "test") { buildmessage.error("You cannot export symbols from a test.", { useMyCaller: true }); // recover by ignoring return; } - // Support `api.exportSymbol("FooTest", {testOnly: true})` without + // Support `api.export("FooTest", {testOnly: true})` without // where. if (_.isObject(where) && !_.isArray(where) && !options) { options = where; diff --git a/tools/tests/app-with-private/packages/test-package/package.js b/tools/tests/app-with-private/packages/test-package/package.js index 081da956f6..e273422703 100644 --- a/tools/tests/app-with-private/packages/test-package/package.js +++ b/tools/tests/app-with-private/packages/test-package/package.js @@ -5,6 +5,6 @@ Package._transitional_registerBuildPlugin({ }); Package.on_use(function (api) { - api.exportSymbol('TestAsset', 'server'); + api.export('TestAsset', 'server'); api.add_files(['test-package.js', 'test-package.txt', 'test.notregistered'], 'server'); }); From 1cafd859841350e24aaa32cfc32fd723c83e7b72 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 25 Jul 2013 18:52:57 -0700 Subject: [PATCH 103/175] Fix test failure after rebase. --- packages/livedata/common.js | 1 + packages/livedata/livedata_connection.js | 2 +- packages/livedata/package.js | 4 ++-- packages/livedata/stream_client_common.js | 4 +--- packages/livedata/stream_client_nodejs.js | 4 ++-- packages/livedata/stream_client_sockjs.js | 4 ++-- packages/livedata/stream_tests.js | 4 ++-- 7 files changed, 11 insertions(+), 12 deletions(-) create mode 100644 packages/livedata/common.js diff --git a/packages/livedata/common.js b/packages/livedata/common.js new file mode 100644 index 0000000000..18663f000e --- /dev/null +++ b/packages/livedata/common.js @@ -0,0 +1 @@ +LivedataTest = {}; diff --git a/packages/livedata/livedata_connection.js b/packages/livedata/livedata_connection.js index 5b77ef16a6..04fb380f90 100644 --- a/packages/livedata/livedata_connection.js +++ b/packages/livedata/livedata_connection.js @@ -32,7 +32,7 @@ var Connection = function (url, options) { if (typeof url === "object") { self._stream = url; } else { - self._stream = new ClientStream(url); + self._stream = new LivedataTest.ClientStream(url); } self._lastSessionId = null; diff --git a/packages/livedata/package.js b/packages/livedata/package.js index de9a09ec39..709ee84e39 100644 --- a/packages/livedata/package.js +++ b/packages/livedata/package.js @@ -30,8 +30,8 @@ Package.on_use(function (api) { // Transport api.use('reload', 'client'); - api.add_files(['sockjs-0.3.4.js', - 'stream_client_sockjs.js'], 'client'); + api.add_files('common.js'); + api.add_files(['sockjs-0.3.4.js', 'stream_client_sockjs.js'], 'client'); api.add_files('stream_client_nodejs.js', 'server'); api.add_files('stream_client_common.js', ['client', 'server']); api.add_files('stream_server.js', 'server'); diff --git a/packages/livedata/stream_client_common.js b/packages/livedata/stream_client_common.js index 86de204b3b..817eb211d3 100644 --- a/packages/livedata/stream_client_common.js +++ b/packages/livedata/stream_client_common.js @@ -1,5 +1,3 @@ -LivedataTest = {}; - // XXX from Underscore.String (http://epeli.github.com/underscore.string/) var startsWith = function(str, starts) { return str.length >= starts.length && @@ -73,7 +71,7 @@ toWebsocketUrl = function (url) { LivedataTest.toSockjsUrl = toSockjsUrl; -_.extend(ClientStream.prototype, { +_.extend(LivedataTest.ClientStream.prototype, { // Register for callbacks. on: function (name, callback) { diff --git a/packages/livedata/stream_client_nodejs.js b/packages/livedata/stream_client_nodejs.js index 70338a21dd..d6a99d1d17 100644 --- a/packages/livedata/stream_client_nodejs.js +++ b/packages/livedata/stream_client_nodejs.js @@ -9,7 +9,7 @@ // We don't do any heartbeating. (The logic that did this in sockjs was removed, // because it used a built-in sockjs mechanism. We could do it with WebSocket // ping frames or with DDP-level messages.) -ClientStream = function (endpoint) { +LivedataTest.ClientStream = function (endpoint) { var self = this; // WebSocket-Node https://github.com/Worlize/WebSocket-Node @@ -47,7 +47,7 @@ ClientStream = function (endpoint) { self._launchConnection(); }; -_.extend(ClientStream.prototype, { +_.extend(LivedataTest.ClientStream.prototype, { // data is a utf8 string. Data sent while not connected is dropped on // the floor, and it is up the user of this API to retransmit lost diff --git a/packages/livedata/stream_client_sockjs.js b/packages/livedata/stream_client_sockjs.js index db5aa08ef0..d8c4bfb810 100644 --- a/packages/livedata/stream_client_sockjs.js +++ b/packages/livedata/stream_client_sockjs.js @@ -1,7 +1,7 @@ // @param url {String} URL to Meteor app // "http://subdomain.meteor.com/" or "/" or // "ddp+sockjs://foo-**.meteor.com/sockjs" -ClientStream = function (url) { +LivedataTest.ClientStream = function (url) { var self = this; self._initCommon(); @@ -34,7 +34,7 @@ ClientStream = function (url) { self._launchConnection(); }; -_.extend(ClientStream.prototype, { +_.extend(LivedataTest.ClientStream.prototype, { // data is a utf8 string. Data sent while not connected is dropped on // the floor, and it is up the user of this API to retransmit lost diff --git a/packages/livedata/stream_tests.js b/packages/livedata/stream_tests.js index 0aef16d515..a73f6cd8f8 100644 --- a/packages/livedata/stream_tests.js +++ b/packages/livedata/stream_tests.js @@ -36,7 +36,7 @@ testAsyncMulti("stream - reconnect", [ testAsyncMulti("stream - basic disconnect", [ function (test, expect) { var history = []; - var stream = new Meteor._DdpClientStream("/"); + var stream = new LivedataTest.ClientStream("/"); var onTestPass = expect(); Deps.autorun(function() { @@ -65,7 +65,7 @@ testAsyncMulti("stream - basic disconnect", [ testAsyncMulti("stream - disconnect remains offline", [ function (test, expect) { var history = []; - var stream = new Meteor._DdpClientStream("/"); + var stream = new LivedataTest.ClientStream("/"); var onTestComplete = expect(); Deps.autorun(function() { From 3dd8c230b3046246042d3dde794e3a48035c3367 Mon Sep 17 00:00:00 2001 From: Tim Haines Date: Fri, 7 Jun 2013 22:06:02 -0700 Subject: [PATCH 104/175] Add callback param to oauth request handler, allowing async calls --- packages/oauth1/oauth1_binding.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/oauth1/oauth1_binding.js b/packages/oauth1/oauth1_binding.js index ddef113359..b3455e26a9 100644 --- a/packages/oauth1/oauth1_binding.js +++ b/packages/oauth1/oauth1_binding.js @@ -51,7 +51,7 @@ OAuth1Binding.prototype.prepareAccessToken = function(query) { self.accessTokenSecret = tokens.oauth_token_secret; }; -OAuth1Binding.prototype.call = function(method, url, params) { +OAuth1Binding.prototype.call = function(method, url, params, callback) { var self = this; var headers = self._buildHeader({ @@ -62,16 +62,15 @@ OAuth1Binding.prototype.call = function(method, url, params) { params = {}; } - var response = self._call(method, url, headers, params); - return response; + return self._call(method, url, headers, params, callback); }; -OAuth1Binding.prototype.get = function(url, params) { - return this.call('GET', url, params); +OAuth1Binding.prototype.get = function(url, params, callback) { + return this.call('GET', url, params, callback); }; -OAuth1Binding.prototype.post = function(url, params) { - return this.call('POST', url, params); +OAuth1Binding.prototype.post = function(url, params, callback) { + return this.call('POST', url, params, callback); }; OAuth1Binding.prototype._buildHeader = function(headers) { @@ -106,7 +105,7 @@ OAuth1Binding.prototype._getSignature = function(method, url, rawHeaders, access return crypto.createHmac('SHA1', signingKey).update(signatureBase).digest('base64'); }; -OAuth1Binding.prototype._call = function(method, url, headers, params) { +OAuth1Binding.prototype._call = function(method, url, headers, params, callback) { var self = this; // Get the signature @@ -122,7 +121,7 @@ OAuth1Binding.prototype._call = function(method, url, headers, params) { headers: { Authorization: authString } - }); + }, callback); } catch (err) { throw new Error("Failed to send OAuth1 request to " + url + ". " + err.message); } From 26163ce69e1d8edc77152ef091a078dc66f290d6 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 26 Jul 2013 14:38:22 -0700 Subject: [PATCH 105/175] Don't use a subclass for minimongo errors; they print poorly. Fixes #1246. --- packages/minimongo/minimongo.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/minimongo/minimongo.js b/packages/minimongo/minimongo.js index 18267e2c9f..8b4fd9c655 100644 --- a/packages/minimongo/minimongo.js +++ b/packages/minimongo/minimongo.js @@ -42,14 +42,12 @@ LocalCollection._applyChanges = function (doc, changeFields) { }); }; -LocalCollection.MinimongoError = function (message) { - var self = this; - self.name = "MinimongoError"; - self.details = message; +var MinimongoError = function (message) { + var e = new Error(message); + e.name = "MinimongoError"; + return e; }; -LocalCollection.MinimongoError.prototype = new Error; - // options may include sort, skip, limit, reactive // sort may be any of these forms: @@ -431,7 +429,7 @@ LocalCollection.prototype.insert = function (doc, callback) { var id = LocalCollection._idStringify(doc._id); if (_.has(self.docs, doc._id)) - throw new LocalCollection.MinimongoError("Duplicate _id '" + doc._id + "'"); + throw MinimongoError("Duplicate _id '" + doc._id + "'"); self._saveOriginal(id, undefined); self.docs[id] = doc; From 307789a2610d1badbf08d6eee8cf6fafc967a031 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Fri, 26 Jul 2013 16:35:35 -0700 Subject: [PATCH 106/175] Implement deploy -D for galaxy apps. Basically just calls stopApp followed by unlistApp. --- tools/deploy-galaxy.js | 37 +++++++++++++++++++++++++++++++++++-- tools/meteor.js | 2 +- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/tools/deploy-galaxy.js b/tools/deploy-galaxy.js index 09aab921ba..f4ff2a5081 100644 --- a/tools/deploy-galaxy.js +++ b/tools/deploy-galaxy.js @@ -112,8 +112,41 @@ exports.discoverGalaxy = function (app) { return fut.wait(); }; -exports.deleteApp = function (context) { - throw new Error("Not implemented"); +exports.deleteApp = function (app, context) { + var Package = getPackage(context); + var galaxy = getGalaxy(context); + + // Subscribe to the jobs for this app so we know when all the jobs have + // finished. + var Jobs = new Package.meteor.Meteor.Collection("jobs", { + connection: galaxy + }); + var readyFut = new Future(); + var jobsSub = galaxy.subscribe("jobsByApp", app, { + onReady: function () { + readyFut['return'](); + }, + onError: function (e) { + readyFut['throw'](e); + } + }); + readyFut.wait(); + + galaxy.call("stopApp", app); + + Package.deps.Deps.autorun(Package.meteor.Meteor.bindEnvironment(function (c) { + var numJobs = Jobs.find({ app: app, done: false }).count(); + if (numJobs === 0) { + c.stop(); + jobsSub.stop(); + // Now that all the app's jobs are done, clean up the app (remove it from + // the db, delete its stars, etc). + galaxy.call("unlistApp", app); + galaxy.close(); + } + }, function (e) { + throw e; + })); }; // options: diff --git a/tools/meteor.js b/tools/meteor.js index 0028698326..cb1c10dc26 100644 --- a/tools/meteor.js +++ b/tools/meteor.js @@ -948,7 +948,7 @@ Fiber(function () { if (argv.delete) { if (context.galaxy) - deployGalaxy.deleteApp(context); + deployGalaxy.deleteApp(site, context); else deploy.delete_app(site); } else { From 4e222d8e26377b6dc91fe6732bc5c82ca0fbe06e Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 26 Jul 2013 18:41:56 -0700 Subject: [PATCH 107/175] The standard app packages are brought in explicitly now. .meteor/packages in new apps now contains "standard-app-packages", which implies the standard set of packages like mongo-livedata. There is no special-casing in initFromAppDir. This line has been added to all the examples, etc. There's a new concept of "upgraders". "meteor run-upgrader app-packages" will add standard-app-packages to the app, as well as all of the package in the app's packages/ directory (an unrelated change since 0.6.4). This will be integrated soon with "meteor update"; run-upgrader is essentially for testing. project.add_package no longer adds packages that are already there. --- docs/.meteor/packages | 1 + examples/leaderboard/.meteor/packages | 1 + .../defer-in-inactive-tab/.meteor/packages | 1 + examples/other/login-demo/.meteor/packages | 1 + examples/other/quiescence/.meteor/packages | 1 + examples/other/template-demo/.meteor/packages | 1 + examples/parties/.meteor/packages | 1 + examples/todos/.meteor/packages | 1 + .../accounts-ui-viewer/.meteor/packages | 1 + examples/unfinished/azrael/.meteor/packages | 1 + .../unfinished/benchmark/.meteor/packages | 1 + .../unfinished/coffeeless/.meteor/packages | 1 + examples/unfinished/controls/.meteor/packages | 1 + .../unfinished/jsparse-docs/.meteor/packages | 1 + .../leaderboard-remote/.meteor/packages | 1 + .../parse-inspector/.meteor/packages | 1 + .../todos-backbone/.meteor/packages | 3 +- .../todos-underscore/.meteor/packages | 1 + examples/wordplay/.meteor/packages | 1 + packages/standard-app-packages/package.js | 43 ++++++++++++++ .../admin/publish-release/.meteor/packages | 1 + tools/library.js | 1 + tools/meteor.js | 24 ++++++++ tools/packages.js | 9 +-- tools/project.js | 2 + tools/skel/.meteor/packages | 1 + tools/test-runner-app/.meteor/packages | 6 +- tools/tests/app-with-package/.meteor/packages | 3 +- tools/tests/app-with-private/.meteor/packages | 1 + tools/tests/app-with-public/.meteor/packages | 1 + tools/tests/empty-app/.meteor/packages | 4 +- tools/upgraders.js | 56 +++++++++++++++++++ 32 files changed, 160 insertions(+), 13 deletions(-) create mode 100644 packages/standard-app-packages/package.js create mode 100644 tools/upgraders.js diff --git a/docs/.meteor/packages b/docs/.meteor/packages index 90b56a07bc..f4302d6960 100644 --- a/docs/.meteor/packages +++ b/docs/.meteor/packages @@ -3,6 +3,7 @@ # 'meteor add' and 'meteor remove' will edit this file for you, # but you can also edit it by hand. +standard-app-packages jquery underscore showdown diff --git a/examples/leaderboard/.meteor/packages b/examples/leaderboard/.meteor/packages index bd558a2ce0..a5eb137152 100644 --- a/examples/leaderboard/.meteor/packages +++ b/examples/leaderboard/.meteor/packages @@ -3,6 +3,7 @@ # 'meteor add' and 'meteor remove' will edit this file for you, # but you can also edit it by hand. +standard-app-packages autopublish insecure preserve-inputs diff --git a/examples/other/defer-in-inactive-tab/.meteor/packages b/examples/other/defer-in-inactive-tab/.meteor/packages index 1a791704ad..5d992a7f9a 100644 --- a/examples/other/defer-in-inactive-tab/.meteor/packages +++ b/examples/other/defer-in-inactive-tab/.meteor/packages @@ -3,3 +3,4 @@ # 'meteor add' and 'meteor remove' will edit this file for you, # but you can also edit it by hand. +standard-app-packages diff --git a/examples/other/login-demo/.meteor/packages b/examples/other/login-demo/.meteor/packages index 89caf7d837..b03b9ed442 100644 --- a/examples/other/login-demo/.meteor/packages +++ b/examples/other/login-demo/.meteor/packages @@ -5,3 +5,4 @@ preserve-inputs accounts-google +standard-app-packages diff --git a/examples/other/quiescence/.meteor/packages b/examples/other/quiescence/.meteor/packages index 19052c13e7..e50106e265 100644 --- a/examples/other/quiescence/.meteor/packages +++ b/examples/other/quiescence/.meteor/packages @@ -6,3 +6,4 @@ insecure preserve-inputs random +standard-app-packages diff --git a/examples/other/template-demo/.meteor/packages b/examples/other/template-demo/.meteor/packages index 12c5f051c0..0aed446952 100644 --- a/examples/other/template-demo/.meteor/packages +++ b/examples/other/template-demo/.meteor/packages @@ -4,3 +4,4 @@ # but you can also edit it by hand. autopublish +standard-app-packages diff --git a/examples/parties/.meteor/packages b/examples/parties/.meteor/packages index b2c4da929f..985d19f9fe 100644 --- a/examples/parties/.meteor/packages +++ b/examples/parties/.meteor/packages @@ -3,6 +3,7 @@ # 'meteor add' and 'meteor remove' will edit this file for you, # but you can also edit it by hand. +standard-app-packages preserve-inputs accounts-ui accounts-password diff --git a/examples/todos/.meteor/packages b/examples/todos/.meteor/packages index abc9ef7fad..001db3bdc2 100644 --- a/examples/todos/.meteor/packages +++ b/examples/todos/.meteor/packages @@ -3,6 +3,7 @@ # 'meteor add' and 'meteor remove' will edit this file for you, # but you can also edit it by hand. +standard-app-packages underscore backbone spiderable diff --git a/examples/unfinished/accounts-ui-viewer/.meteor/packages b/examples/unfinished/accounts-ui-viewer/.meteor/packages index 634f653e55..426d145113 100644 --- a/examples/unfinished/accounts-ui-viewer/.meteor/packages +++ b/examples/unfinished/accounts-ui-viewer/.meteor/packages @@ -13,3 +13,4 @@ accounts-github accounts-password underscore accounts-facebook +standard-app-packages diff --git a/examples/unfinished/azrael/.meteor/packages b/examples/unfinished/azrael/.meteor/packages index b065bfb303..83900fa851 100644 --- a/examples/unfinished/azrael/.meteor/packages +++ b/examples/unfinished/azrael/.meteor/packages @@ -6,3 +6,4 @@ underscore jquery jquery-layout +standard-app-packages diff --git a/examples/unfinished/benchmark/.meteor/packages b/examples/unfinished/benchmark/.meteor/packages index 233dc66a33..fad7faf9d2 100644 --- a/examples/unfinished/benchmark/.meteor/packages +++ b/examples/unfinished/benchmark/.meteor/packages @@ -7,3 +7,4 @@ insecure preserve-inputs bootstrap random +standard-app-packages diff --git a/examples/unfinished/coffeeless/.meteor/packages b/examples/unfinished/coffeeless/.meteor/packages index 4e0a9f67fb..27463e07b2 100644 --- a/examples/unfinished/coffeeless/.meteor/packages +++ b/examples/unfinished/coffeeless/.meteor/packages @@ -5,3 +5,4 @@ less coffeescript +standard-app-packages diff --git a/examples/unfinished/controls/.meteor/packages b/examples/unfinished/controls/.meteor/packages index 12c5f051c0..0aed446952 100644 --- a/examples/unfinished/controls/.meteor/packages +++ b/examples/unfinished/controls/.meteor/packages @@ -4,3 +4,4 @@ # but you can also edit it by hand. autopublish +standard-app-packages diff --git a/examples/unfinished/jsparse-docs/.meteor/packages b/examples/unfinished/jsparse-docs/.meteor/packages index 12c5f051c0..0aed446952 100644 --- a/examples/unfinished/jsparse-docs/.meteor/packages +++ b/examples/unfinished/jsparse-docs/.meteor/packages @@ -4,3 +4,4 @@ # but you can also edit it by hand. autopublish +standard-app-packages diff --git a/examples/unfinished/leaderboard-remote/.meteor/packages b/examples/unfinished/leaderboard-remote/.meteor/packages index 12c5f051c0..0aed446952 100644 --- a/examples/unfinished/leaderboard-remote/.meteor/packages +++ b/examples/unfinished/leaderboard-remote/.meteor/packages @@ -4,3 +4,4 @@ # but you can also edit it by hand. autopublish +standard-app-packages diff --git a/examples/unfinished/parse-inspector/.meteor/packages b/examples/unfinished/parse-inspector/.meteor/packages index 3ca84ae156..1d7becdbfe 100644 --- a/examples/unfinished/parse-inspector/.meteor/packages +++ b/examples/unfinished/parse-inspector/.meteor/packages @@ -6,3 +6,4 @@ autopublish preserve-inputs jsparse +standard-app-packages diff --git a/examples/unfinished/todos-backbone/.meteor/packages b/examples/unfinished/todos-backbone/.meteor/packages index a4ddbca3cd..c24acdbd36 100644 --- a/examples/unfinished/todos-backbone/.meteor/packages +++ b/examples/unfinished/todos-backbone/.meteor/packages @@ -4,4 +4,5 @@ # but you can also edit it by hand. jquery -backbone \ No newline at end of file +backbone +standard-app-packages diff --git a/examples/unfinished/todos-underscore/.meteor/packages b/examples/unfinished/todos-underscore/.meteor/packages index 23fded3598..6bbedad3c8 100644 --- a/examples/unfinished/todos-underscore/.meteor/packages +++ b/examples/unfinished/todos-underscore/.meteor/packages @@ -6,3 +6,4 @@ jquery jquery-layout jquery-history +standard-app-packages diff --git a/examples/wordplay/.meteor/packages b/examples/wordplay/.meteor/packages index 55c9ad0dd1..1c4346821c 100644 --- a/examples/wordplay/.meteor/packages +++ b/examples/wordplay/.meteor/packages @@ -3,6 +3,7 @@ # 'meteor add' and 'meteor remove' will edit this file for you, # but you can also edit it by hand. +standard-app-packages insecure jquery preserve-inputs diff --git a/packages/standard-app-packages/package.js b/packages/standard-app-packages/package.js new file mode 100644 index 0000000000..c4f6aacd73 --- /dev/null +++ b/packages/standard-app-packages/package.js @@ -0,0 +1,43 @@ +Package.describe({ + summary: "Include a standard set of Meteor packages in your app." +}); + +Package.on_use(function(api) { + // The "imply" here means that if your app uses "standard-app-packages", it is + // treated as if it also directly included all of these packages (and it gets + // their exports, plugins, etc). + // + // If you want, you can "meteor remove standard-app-packages" and add some of + // these back in individually. We haven't tested every subset, though :) + api.imply([ + // The normal "every package uses 'meteor'" rule only applies to packages + // built from a package source directory, so we make sure apps get it too. + // Meteor.isServer! The CSS extension handler! And so much more! + 'meteor', + // A standard Meteor app is a web app. (Without this, there will be no + // 'main' function unless you define one yourself.) + 'webapp', + // It's Log! It's better than bad, it's good! + 'logging', + // Deps.autorun and friends. What's Meteor without reactivity? + 'deps', + // The easiest way to get a little reactivity into your app. + 'session', + // DDP: Meteor's client/server protocol. + 'livedata', + // You want to keep your data somewhere? How about MongoDB? + 'mongo-livedata', + // You want some views? How about Handlebars-based templating? + 'templating', + // What, you want to call Handlebars.registerHandler? Sounds good to me. + 'handlebars', + // Easy type assertions? check. + 'check', + // _.isUseful(true) + 'underscore', + // $(".usefulToo") + 'jquery', + // Life isn't always predictable. + 'random' + ]); +}); diff --git a/scripts/admin/publish-release/.meteor/packages b/scripts/admin/publish-release/.meteor/packages index 74931fd95a..578c5ec9a7 100644 --- a/scripts/admin/publish-release/.meteor/packages +++ b/scripts/admin/publish-release/.meteor/packages @@ -7,3 +7,4 @@ autopublish insecure preserve-inputs awssum +standard-app-packages diff --git a/tools/library.js b/tools/library.js index 5d98eb410d..39c276c595 100644 --- a/tools/library.js +++ b/tools/library.js @@ -127,6 +127,7 @@ _.extend(Library.prototype, { if (! packageDir) { for (var i = 0; i < self.localPackageDirs.length; ++i) { var packageDir = path.join(self.localPackageDirs[i], name); + // XXX or unipackage.json? if (fs.existsSync(path.join(packageDir, 'package.js'))) break; packageDir = null; diff --git a/tools/meteor.js b/tools/meteor.js index cb1c10dc26..1368209d9a 100644 --- a/tools/meteor.js +++ b/tools/meteor.js @@ -654,6 +654,30 @@ Fiber(function () { } }); + Commands.push({ + name: "run-upgrader", + help: "Execute a specific upgrader by name. Intended for testing.", + hidden: true, + argumentParser: function (opt) { + opt .usage( + "Usage: meteor run-upgrader \n" + + "\n" + + "Runs a specific upgrader on the current app. This is for testing\n" + + "internal functionality of Meteor."); + }, + func: function (argv, showUsage) { + if (argv._.length !== 1) + showUsage(); + + requireDirInApp("run-upgrader"); + + var upgraders = require("./upgraders.js"); + console.log("%s: running upgrader %s.", + path.basename(context.appDir), argv._[0]); + upgraders.runUpgrader(argv._[0], context.appDir); + } + }); + Commands.push({ name: "add", help: "Add a package to this project", diff --git a/tools/packages.js b/tools/packages.js index 444428b693..ffb75bed4c 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -1757,14 +1757,7 @@ _.extend(Package.prototype, { _.each(["client", "server"], function (sliceName) { // Determine used packages - var names = _.union( - // standard client packages for the classic meteor stack. - // XXX remove and make everyone explicitly declare all dependencies - ['meteor', 'webapp', 'logging', 'deps', 'session', - 'livedata', 'mongo-livedata', 'spark', 'templating', 'check', - 'underscore', 'jquery', 'handlebars', 'random'], - project.get_packages(appDir)); - + var names = project.get_packages(appDir); var arch = sliceName === "server" ? "native" : "browser"; // Create slice diff --git a/tools/project.js b/tools/project.js index d077ef0fa7..b3809f887e 100644 --- a/tools/project.js +++ b/tools/project.js @@ -79,6 +79,8 @@ _.extend(exports, { // detail: if the file starts with a comment, try to keep a single // blank line after the comment (unless the user removes it) var current = project.get_packages(app_dir); + if (_.contains(current, name)) + return; if (!current.length && lines.length) lines.push(''); lines.push(name); diff --git a/tools/skel/.meteor/packages b/tools/skel/.meteor/packages index 2ca3c152a4..240f048420 100644 --- a/tools/skel/.meteor/packages +++ b/tools/skel/.meteor/packages @@ -3,6 +3,7 @@ # 'meteor add' and 'meteor remove' will edit this file for you, # but you can also edit it by hand. +standard-app-packages autopublish insecure preserve-inputs diff --git a/tools/test-runner-app/.meteor/packages b/tools/test-runner-app/.meteor/packages index 69f1bac0f6..006a126776 100644 --- a/tools/test-runner-app/.meteor/packages +++ b/tools/test-runner-app/.meteor/packages @@ -1,2 +1,4 @@ -# This file intentionally left blank. (A driver package will be added by "meteor -# test-packages".) +# In addition to the standard app packages, a driver package will be added by +# "meteor test-packages". + +standard-app-packages diff --git a/tools/tests/app-with-package/.meteor/packages b/tools/tests/app-with-package/.meteor/packages index 8eca235559..da83992817 100644 --- a/tools/tests/app-with-package/.meteor/packages +++ b/tools/tests/app-with-package/.meteor/packages @@ -1 +1,2 @@ -test-package \ No newline at end of file +test-package +standard-app-packages diff --git a/tools/tests/app-with-private/.meteor/packages b/tools/tests/app-with-private/.meteor/packages index 2670629f0c..ab08350a86 100644 --- a/tools/tests/app-with-private/.meteor/packages +++ b/tools/tests/app-with-private/.meteor/packages @@ -7,3 +7,4 @@ autopublish insecure preserve-inputs test-package +standard-app-packages diff --git a/tools/tests/app-with-public/.meteor/packages b/tools/tests/app-with-public/.meteor/packages index 2ca3c152a4..4cd1dcb2d5 100644 --- a/tools/tests/app-with-public/.meteor/packages +++ b/tools/tests/app-with-public/.meteor/packages @@ -6,3 +6,4 @@ autopublish insecure preserve-inputs +standard-app-packages diff --git a/tools/tests/empty-app/.meteor/packages b/tools/tests/empty-app/.meteor/packages index 9c8d080e06..25c1e7842a 100644 --- a/tools/tests/empty-app/.meteor/packages +++ b/tools/tests/empty-app/.meteor/packages @@ -1 +1,3 @@ -# no packages \ No newline at end of file +# no packages + +standard-app-packages diff --git a/tools/upgraders.js b/tools/upgraders.js new file mode 100644 index 0000000000..a64e9cfdd3 --- /dev/null +++ b/tools/upgraders.js @@ -0,0 +1,56 @@ +var _ = require('underscore'); +var fs = require('fs'); +var path = require('path'); +var project = require('./project.js'); + +// This upgrader implements two changes made in 0.6.5 as part of the Linker +// project. +// +// First, linker changed how "app packages" (packages in the "packages" +// directory in an app) are treated. Before 0.6.5, all app packages were +// implicitly "use"d by the app. This meant there was no way to have an app +// package that was intended only to be "use" by the test slices of other app +// packages. In 0.6.5, you have to explicitly "meteor add" app packages to +// .meteor/packages in order for them to be used by your app. This upgrader +// adds all existing packages found in the packages/ directory to +// .meteor/packages. (If you had such test helpers, you can remove them +// afterwards.) +// +// Second, linker changed how the standard set of packages used by apps is +// included. Instead of being hard-coded into initFromAppDir, the standard +// packages are "implied" by the new "standard-app-packages" package, which is +// explicitly listed in .meteor/packages. So we need to add +// "standard-app-packages" to .meteor/packages when upgrading. +var addAppPackagesAndStandardAppPackages = function (appDir) { + project.add_package(appDir, 'standard-app-packages'); + + var appPackageDir = path.join(appDir, 'packages'); + try { + var appPackages = fs.readdirSync(appPackageDir); + } catch (e) { + if (!(e && e.code === 'ENOENT')) + throw e; + } + + _.each(appPackages, function (p) { + // We can ignore empty directories, etc. Packages have to have a + // package.js. (In 0.6.5, they can also be built packages with + // unipackage.json... but that surely is irrelevant for this upgrade.) + if (fs.existsSync(path.join(appPackageDir, p, 'package.js'))) + project.add_package(appDir, p); + }); +}; + + +var upgradersByName = { + "app-packages": addAppPackagesAndStandardAppPackages +}; + +exports.runUpgrader = function (upgraderName, appDir) { + // This should only be called from the hidden run-upgrader command or by + // "meteor update" with an upgrader from one of our releases, so it's OK if + // error handling is just an exception. + if (! _.has(upgradersByName, upgraderName)) + throw new Error("Unknown upgrader: " + upgraderName); + upgradersByName[upgraderName](appDir); +}; From 7f545db79e766c851f59bf0529052afce2cf6163 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 26 Jul 2013 19:04:13 -0700 Subject: [PATCH 108/175] Also include awssum update --- scripts/admin/publish-release/packages/awssum | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/admin/publish-release/packages/awssum b/scripts/admin/publish-release/packages/awssum index a038e042cf..f7d05a344d 160000 --- a/scripts/admin/publish-release/packages/awssum +++ b/scripts/admin/publish-release/packages/awssum @@ -1 +1 @@ -Subproject commit a038e042cfac3aff307d031dadc4eedd84f08a78 +Subproject commit f7d05a344d940e9b8d56cc483f451d91533690bc From 19012cf58a7a5d8d0707009e400bd6f3b9fdb7e5 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 26 Jul 2013 19:33:25 -0700 Subject: [PATCH 109/175] "meteor list": completely ignore internal packages --- tools/library.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/library.js b/tools/library.js index 39c276c595..498506599b 100644 --- a/tools/library.js +++ b/tools/library.js @@ -397,9 +397,10 @@ _.extend(exports, { formatList: function (pkgs) { var longest = ''; _.each(pkgs, function (pkg) { - if (pkg.name.length > longest.length) + if (!pkg.metadata.internal && pkg.name.length > longest.length) longest = pkg.name; }); + var pad = longest.replace(/./g, ' '); // it'd be nice to read the actual terminal width, but I tried // several methods and none of them work (COLUMNS isn't set in From af061120444687289efba5a6b5f5300749e96c90 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 26 Jul 2013 19:34:45 -0700 Subject: [PATCH 110/175] templating implies spark and meteor: generated code needs them. also finish getting rid of deprecated startup package, and some other minor packaging cleanups to templating. --- packages/appcache/package.js | 1 - packages/standard-app-packages/package.js | 2 +- packages/templating/package.js | 11 +++++++---- packages/templating/plugin/html_scanner.js | 5 ----- packages/tinytest/package.js | 1 - 5 files changed, 8 insertions(+), 12 deletions(-) diff --git a/packages/appcache/package.js b/packages/appcache/package.js index bcbca06ff0..27700a35ec 100644 --- a/packages/appcache/package.js +++ b/packages/appcache/package.js @@ -6,7 +6,6 @@ Package.on_use(function (api) { api.use('webapp', 'server'); api.use('reload', 'client'); api.use('routepolicy', 'server'); - api.use('startup', 'client'); api.use('underscore', 'server'); api.add_files('appcache-client.js', 'client'); api.add_files('appcache-server.js', 'server'); diff --git a/packages/standard-app-packages/package.js b/packages/standard-app-packages/package.js index c4f6aacd73..eeb12c1574 100644 --- a/packages/standard-app-packages/package.js +++ b/packages/standard-app-packages/package.js @@ -1,5 +1,5 @@ Package.describe({ - summary: "Include a standard set of Meteor packages in your app." + summary: "Include a standard set of Meteor packages in your app" }); Package.on_use(function(api) { diff --git a/packages/templating/package.js b/packages/templating/package.js index 87b3dfdb11..702ef253d2 100644 --- a/packages/templating/package.js +++ b/packages/templating/package.js @@ -10,13 +10,14 @@ Package.describe({ Package._transitional_registerBuildPlugin({ name: "compileTemplates", - use: ['underscore', 'handlebars'], + use: ['handlebars'], sources: [ 'plugin/html_scanner.js', 'plugin/compile-templates.js' ] }); +// This on_use describes the *runtime* implications of using this package. Package.on_use(function (api) { // XXX would like to do the following only when the first html file // is encountered @@ -28,8 +29,10 @@ Package.on_use(function (api) { // provides the runtime logic to instantiate our templates api.add_files('deftemplate.js', 'client'); - // html_scanner.js emits client code that calls Meteor.startup - api.use('startup', 'client'); + // html_scanner.js emits client code that calls Meteor.startup and + // Spark.render, so anybody using templating (eg apps) need to implicitly use + // 'meteor' and 'spark'. + api.imply(['meteor', 'spark'], 'client'); }); Package.on_test(function (api) { @@ -47,6 +50,6 @@ Package.on_test(function (api) { ], 'client'); api.add_files([ 'plugin/html_scanner.js', - 'scanner_tests.js', + 'scanner_tests.js' ], 'server'); }); diff --git a/packages/templating/plugin/html_scanner.js b/packages/templating/plugin/html_scanner.js index 59cd386909..3960600094 100644 --- a/packages/templating/plugin/html_scanner.js +++ b/packages/templating/plugin/html_scanner.js @@ -182,8 +182,3 @@ html_scanner = { } } }; - -// If we are running at bundle time, set module.exports. -// For unit testing in server environment, don't. -if (typeof module !== 'undefined') - module.exports = html_scanner; diff --git a/packages/tinytest/package.js b/packages/tinytest/package.js index 21cda01131..af83a91b80 100644 --- a/packages/tinytest/package.js +++ b/packages/tinytest/package.js @@ -16,7 +16,6 @@ Package.on_use(function (api) { api.add_files('model.js', ['client', 'server']); api.add_files('tinytest_client.js', 'client'); - api.use('startup', 'server'); api.add_files('tinytest_server.js', 'server'); api.use('check'); From 03105fd4950dc4c46645ee5cc38212796228f3f4 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 26 Jul 2013 19:34:59 -0700 Subject: [PATCH 111/175] Forgot to include standard-app-packages/.gitignore. --- packages/standard-app-packages/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/standard-app-packages/.gitignore diff --git a/packages/standard-app-packages/.gitignore b/packages/standard-app-packages/.gitignore new file mode 100644 index 0000000000..677a6fc263 --- /dev/null +++ b/packages/standard-app-packages/.gitignore @@ -0,0 +1 @@ +.build* From 03e0ca8a49b518ad26ebb6e6a8df99f3e2b8d6a6 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 26 Jul 2013 19:38:28 -0700 Subject: [PATCH 112/175] Remove function moved from server file to common file. Caught by Tim Haines! --- packages/accounts-oauth/oauth_server.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/accounts-oauth/oauth_server.js b/packages/accounts-oauth/oauth_server.js index 5fea88688e..ca6dfa4aff 100644 --- a/packages/accounts-oauth/oauth_server.js +++ b/packages/accounts-oauth/oauth_server.js @@ -1,14 +1,3 @@ -// Helper for registering OAuth based accounts packages. -// Adds an index to the user collection. -Accounts.oauth.registerService = function (name) { - // Accounts.updateOrCreateUserFromExternalService does a lookup by this id, - // so this should be a unique index. You might want to add indexes for other - // fields returned by your service (eg services.github.login) but you can do - // that in your app. - Meteor.users._ensureIndex('services.' + name + '.id', - {unique: 1, sparse: 1}); -}; - // Listen to calls to `login` with an oauth option set. This is where // users actually get logged in to meteor via oauth. Accounts.registerLoginHandler(function (options) { From d9b1172116b915aea58fda5f8782f60a50edddc0 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 26 Jul 2013 19:42:46 -0700 Subject: [PATCH 113/175] Fix configureLoginService. Fixes #1250. --- packages/accounts-base/accounts_server.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index 5fdbf1737c..7ef3d8a4c0 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -384,8 +384,13 @@ Meteor.methods({ // Don't let random users configure a service we haven't added yet (so // that when we do later add it, it's set up with their configuration // instead of ours). - if (!Accounts[options.service]) + // XXX if service configuration is oauth-specific then this code should + // be in accounts-oauth; if it's not then the registry should be + // in this package + if (!(Accounts.oauth + && _.contains(Accounts.oauth.serviceNames(), options.service))) { throw new Meteor.Error(403, "Service unknown"); + } if (ServiceConfiguration.configurations.findOne({service: options.service})) throw new Meteor.Error(403, "Service " + options.service + " already configured"); ServiceConfiguration.configurations.insert(options); From c74e969892e62a8294e6464d48d26d594ee8c8e8 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Fri, 26 Jul 2013 22:08:14 -0700 Subject: [PATCH 114/175] Move delete app logic into galaxy --- tools/deploy-galaxy.js | 36 +++--------------------------------- 1 file changed, 3 insertions(+), 33 deletions(-) diff --git a/tools/deploy-galaxy.js b/tools/deploy-galaxy.js index f4ff2a5081..e147361712 100644 --- a/tools/deploy-galaxy.js +++ b/tools/deploy-galaxy.js @@ -113,40 +113,10 @@ exports.discoverGalaxy = function (app) { }; exports.deleteApp = function (app, context) { - var Package = getPackage(context); var galaxy = getGalaxy(context); - - // Subscribe to the jobs for this app so we know when all the jobs have - // finished. - var Jobs = new Package.meteor.Meteor.Collection("jobs", { - connection: galaxy - }); - var readyFut = new Future(); - var jobsSub = galaxy.subscribe("jobsByApp", app, { - onReady: function () { - readyFut['return'](); - }, - onError: function (e) { - readyFut['throw'](e); - } - }); - readyFut.wait(); - - galaxy.call("stopApp", app); - - Package.deps.Deps.autorun(Package.meteor.Meteor.bindEnvironment(function (c) { - var numJobs = Jobs.find({ app: app, done: false }).count(); - if (numJobs === 0) { - c.stop(); - jobsSub.stop(); - // Now that all the app's jobs are done, clean up the app (remove it from - // the db, delete its stars, etc). - galaxy.call("unlistApp", app); - galaxy.close(); - } - }, function (e) { - throw e; - })); + galaxy.call("destroyApp", app); + galaxy.close(); + process.stdout.write("Deleted.\n"); }; // options: From e3c18578e348058db6b8f9ad0b3991af4067501b Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 29 Jul 2013 10:53:05 -0700 Subject: [PATCH 115/175] Call upgraders at "meteor update" time. Add "app-packages" to the list of upgraders in a release JSON. --- scripts/admin/build-release.sh | 3 ++- tools/meteor.js | 28 ++++++++++++++++++---------- tools/upgraders.js | 14 ++++++++++++++ 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/scripts/admin/build-release.sh b/scripts/admin/build-release.sh index 1bab7907b9..2fc05139b8 100755 --- a/scripts/admin/build-release.sh +++ b/scripts/admin/build-release.sh @@ -45,7 +45,8 @@ cat > "$OUTDIR/release.json" < Date: Mon, 29 Jul 2013 11:12:30 -0700 Subject: [PATCH 116/175] Update awssum. --- scripts/admin/publish-release/packages/awssum | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/admin/publish-release/packages/awssum b/scripts/admin/publish-release/packages/awssum index f7d05a344d..0a42ce805c 160000 --- a/scripts/admin/publish-release/packages/awssum +++ b/scripts/admin/publish-release/packages/awssum @@ -1 +1 @@ -Subproject commit f7d05a344d940e9b8d56cc483f451d91533690bc +Subproject commit 0a42ce805c624bcdfa0e332b35badeb5b4839681 From d21d0bffffca4e9855048f2b816f37ea9b224c26 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 29 Jul 2013 11:14:31 -0700 Subject: [PATCH 117/175] ... and keep updating awssum. --- scripts/admin/publish-release/packages/awssum | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/admin/publish-release/packages/awssum b/scripts/admin/publish-release/packages/awssum index 0a42ce805c..0221218663 160000 --- a/scripts/admin/publish-release/packages/awssum +++ b/scripts/admin/publish-release/packages/awssum @@ -1 +1 @@ -Subproject commit 0a42ce805c624bcdfa0e332b35badeb5b4839681 +Subproject commit 0221218663b12b47f61cd9745da9c2be3f247bdb From 3b03386b63275747f9518fba7b7beec488be54df Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 29 Jul 2013 11:35:25 -0700 Subject: [PATCH 118/175] Fix force-ssl. --- packages/force-ssl/package.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/force-ssl/package.js b/packages/force-ssl/package.js index 2baeb65573..11eddfc516 100644 --- a/packages/force-ssl/package.js +++ b/packages/force-ssl/package.js @@ -4,7 +4,7 @@ Package.describe({ Package.on_use(function (api) { api.use('webapp', 'server'); - api.use('underscore', 'server'); + api.use('underscore'); // make sure we come after livedata, so we load after the sockjs // server has been instantiated. api.use('livedata', 'server'); From 3e76c40ccebb6afebacb891d8094d0a39b088d02 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 29 Jul 2013 11:54:52 -0700 Subject: [PATCH 119/175] Improve data flow from deployConfig to mongo-livedata. (Still ought to be better refactored though.) --- packages/mongo-livedata/package.js | 4 ++++ packages/mongo-livedata/remote_collection_driver.js | 7 ++++--- packages/webapp/webapp_server.js | 4 +++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/mongo-livedata/package.js b/packages/mongo-livedata/package.js index 070c19ad19..7d53239653 100644 --- a/packages/mongo-livedata/package.js +++ b/packages/mongo-livedata/package.js @@ -26,6 +26,10 @@ Package.on_use(function (api) { // Allow us to detect 'autopublish', and publish collections if it's loaded. api.use('autopublish', 'server', {weak: true}); + // RemoteCollectionDriver gets its deployConfig from something that is (for + // questionable reasons) initialized by the webapp package. + api.use('webapp', 'server', {weak: true}); + api.export('MongoLivedataTest', 'server', {testOnly: true}); api.add_files('mongo_driver.js', 'server'); diff --git a/packages/mongo-livedata/remote_collection_driver.js b/packages/mongo-livedata/remote_collection_driver.js index a6ede8bdbe..5996344faf 100644 --- a/packages/mongo-livedata/remote_collection_driver.js +++ b/packages/mongo-livedata/remote_collection_driver.js @@ -23,9 +23,10 @@ _.extend(RemoteCollectionDriver.prototype, { // you're only trying to receive data from a remote DDP server.) getRemoteCollectionDriver = _.once(function () { // XXX kind of hacky - var mongoUrl = (typeof __meteor_bootstrap__ !== 'undefined' && - Meteor._get(__meteor_bootstrap__.deployConfig, - 'packages', 'mongo-livedata', 'url')); + var mongoUrl = ( + typeof __meteor_bootstrap__ !== 'undefined' && + Meteor._get(__meteor_bootstrap__, + 'deployConfig', 'packages', 'mongo-livedata', 'url')); // XXX bad error since it could also be set directly in METEOR_DEPLOY_CONFIG if (! mongoUrl) throw new Error("MONGO_URL must be set in environment"); diff --git a/packages/webapp/webapp_server.js b/packages/webapp/webapp_server.js index 3086d2e452..08f2aa5c1a 100644 --- a/packages/webapp/webapp_server.js +++ b/packages/webapp/webapp_server.js @@ -183,7 +183,9 @@ var runWebAppServer = function () { throw new Error("Unsupported format for client assets: " + JSON.stringify(clientJson.format)); - // XXX change all this config to something more reasonable + // XXX change all this config to something more reasonable. + // and move it out of webapp into a different package so you don't + // have weird things like mongo-livedata weak-dep'ing on webapp var deployConfig = process.env.METEOR_DEPLOY_CONFIG ? JSON.parse(process.env.METEOR_DEPLOY_CONFIG) : {}; From 12970342012db3c9c0ead39e0ef9aeb03b818327 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 29 Jul 2013 12:38:49 -0700 Subject: [PATCH 120/175] Update docs to 0.6.5-rc2. --- docs/.meteor/release | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/.meteor/release b/docs/.meteor/release index 8741567b66..2e41fbd3cc 100644 --- a/docs/.meteor/release +++ b/docs/.meteor/release @@ -1 +1 @@ -linker-pre5 \ No newline at end of file +0.6.5-rc2 From f7d991cc87fa3ed32123a7ca910a13a3ebd5c786 Mon Sep 17 00:00:00 2001 From: Avital Oliver Date: Mon, 29 Jul 2013 11:33:19 -0700 Subject: [PATCH 121/175] Fix Mongo queries with {$regex: /foo/} The bug manifested as incorrect cursor de-duping, making queries on different regular expressions "de-dup" into one, which, combined with hot code reload caused crazy seeming things such as needing to save twice to see a code change executed. --- packages/mongo-livedata/collection.js | 36 +++++++++++++------ .../mongo-livedata/mongo_livedata_tests.js | 28 ++++++++++----- 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/packages/mongo-livedata/collection.js b/packages/mongo-livedata/collection.js index 9288a3d710..0984811555 100644 --- a/packages/mongo-livedata/collection.js +++ b/packages/mongo-livedata/collection.js @@ -261,17 +261,11 @@ Meteor.Collection._rewriteSelector = function (selector) { var ret = {}; _.each(selector, function (value, key) { + // Mongo supports both {field: /foo/} and {field: {$regex: /foo/}} if (value instanceof RegExp) { - ret[key] = {$regex: value.source}; - var regexOptions = ''; - // JS RegExp objects support 'i', 'm', and 'g'. Mongo regex $options - // support 'i', 'm', 'x', and 's'. So we support 'i' and 'm' here. - if (value.ignoreCase) - regexOptions += 'i'; - if (value.multiline) - regexOptions += 'm'; - if (regexOptions) - ret[key].$options = regexOptions; + ret[key] = convertRegexpToMongoSelector(value); + } else if (value && value.$regex instanceof RegExp) { + ret[key] = convertRegexpToMongoSelector(value.$regex); } else if (_.contains(['$or','$and','$nor'], key)) { // Translate lower levels of $and/$or/$nor @@ -279,12 +273,32 @@ Meteor.Collection._rewriteSelector = function (selector) { return Meteor.Collection._rewriteSelector(v); }); } - else + else { ret[key] = value; + } }); return ret; }; +// convert a JS RegExp object to a Mongo {$regex: ..., $options: ...} +// selector +var convertRegexpToMongoSelector = function (regexp) { + check(regexp, RegExp); // safety belt + + var selector = {$regex: regexp.source}; + var regexOptions = ''; + // JS RegExp objects support 'i', 'm', and 'g'. Mongo regex $options + // support 'i', 'm', 'x', and 's'. So we support 'i' and 'm' here. + if (regexp.ignoreCase) + regexOptions += 'i'; + if (regexp.multiline) + regexOptions += 'm'; + if (regexOptions) + selector.$options = regexOptions; + + return selector; +}; + var throwIfSelectorIsNotId = function (selector, methodName) { if (!LocalCollection._selectorIsIdPerhapsAsObject(selector)) { throw new Meteor.Error( diff --git a/packages/mongo-livedata/mongo_livedata_tests.js b/packages/mongo-livedata/mongo_livedata_tests.js index d00547dc09..62178eccc0 100644 --- a/packages/mongo-livedata/mongo_livedata_tests.js +++ b/packages/mongo-livedata/mongo_livedata_tests.js @@ -876,8 +876,12 @@ if (Meteor.isServer) { Tinytest.add('mongo-livedata - rewrite selector', function (test) { test.equal(Meteor.Collection._rewriteSelector({x: /^o+B/im}), {x: {$regex: '^o+B', $options: 'im'}}); + test.equal(Meteor.Collection._rewriteSelector({x: {$regex: /^o+B/im}}), + {x: {$regex: '^o+B', $options: 'im'}}); test.equal(Meteor.Collection._rewriteSelector({x: /^o+B/}), {x: {$regex: '^o+B'}}); + test.equal(Meteor.Collection._rewriteSelector({x: {$regex: /^o+B/}}), + {x: {$regex: '^o+B'}}); test.equal(Meteor.Collection._rewriteSelector('foo'), {_id: 'foo'}); @@ -886,13 +890,15 @@ Tinytest.add('mongo-livedata - rewrite selector', function (test) { {'$or': [ {x: /^o/}, {y: /^p/}, - {z: 'q'} + {z: 'q'}, + {w: {$regex: /^r/}} ]} ), {'$or': [ {x: {$regex: '^o'}}, {y: {$regex: '^p'}}, - {z: 'q'} + {z: 'q'}, + {w: {$regex: '^r'}} ]} ); @@ -901,22 +907,28 @@ Tinytest.add('mongo-livedata - rewrite selector', function (test) { {'$or': [ {'$and': [ {x: /^a/i}, - {y: /^b/} + {y: /^b/}, + {z: {$regex: /^c/i}}, + {w: {$regex: '^[abc]', $options: 'i'}} // make sure we don't break vanilla selectors ]}, {'$nor': [ - {s: /^c/}, - {t: /^d/i} + {s: /^d/}, + {t: /^e/i}, + {u: {$regex: /^f/i}} ]} ]} ), {'$or': [ {'$and': [ {x: {$regex: '^a', $options: 'i'}}, - {y: {$regex: '^b'}} + {y: {$regex: '^b'}}, + {z: {$regex: '^c', $options: 'i'}}, + {w: {$regex: '^[abc]', $options: 'i'}} ]}, {'$nor': [ - {s: {$regex: '^c'}}, - {t: {$regex: '^d', $options: 'i'}} + {s: {$regex: '^d'}}, + {t: {$regex: '^e', $options: 'i'}}, + {u: {$regex: '^f', $options: 'i'}} ]} ]} ); From a9b59ea8672e53f06fd35492884e6bd62a2f65b9 Mon Sep 17 00:00:00 2001 From: Avital Oliver Date: Mon, 29 Jul 2013 12:41:28 -0700 Subject: [PATCH 122/175] Correct mongo selector rewrite for {$regex: /foo/, $options: '...'} --- packages/mongo-livedata/collection.js | 4 ++++ packages/mongo-livedata/mongo_livedata_tests.js | 8 ++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/mongo-livedata/collection.js b/packages/mongo-livedata/collection.js index 0984811555..9a3fcd8f37 100644 --- a/packages/mongo-livedata/collection.js +++ b/packages/mongo-livedata/collection.js @@ -266,6 +266,10 @@ Meteor.Collection._rewriteSelector = function (selector) { ret[key] = convertRegexpToMongoSelector(value); } else if (value && value.$regex instanceof RegExp) { ret[key] = convertRegexpToMongoSelector(value.$regex); + // if value is {$regex: /foo/, $options: ...} then $options + // override the ones set on $regex. + if (value.$options) + ret[key].$options = value.$options; } else if (_.contains(['$or','$and','$nor'], key)) { // Translate lower levels of $and/$or/$nor diff --git a/packages/mongo-livedata/mongo_livedata_tests.js b/packages/mongo-livedata/mongo_livedata_tests.js index 62178eccc0..1f0fc98035 100644 --- a/packages/mongo-livedata/mongo_livedata_tests.js +++ b/packages/mongo-livedata/mongo_livedata_tests.js @@ -909,7 +909,9 @@ Tinytest.add('mongo-livedata - rewrite selector', function (test) { {x: /^a/i}, {y: /^b/}, {z: {$regex: /^c/i}}, - {w: {$regex: '^[abc]', $options: 'i'}} // make sure we don't break vanilla selectors + {w: {$regex: '^[abc]', $options: 'i'}}, // make sure we don't break vanilla selectors + {v: {$regex: /O/, $options: 'i'}}, // $options should override the ones on the RegExp object + {u: {$regex: /O/m, $options: 'i'}} // $options should override the ones on the RegExp object ]}, {'$nor': [ {s: /^d/}, @@ -923,7 +925,9 @@ Tinytest.add('mongo-livedata - rewrite selector', function (test) { {x: {$regex: '^a', $options: 'i'}}, {y: {$regex: '^b'}}, {z: {$regex: '^c', $options: 'i'}}, - {w: {$regex: '^[abc]', $options: 'i'}} + {w: {$regex: '^[abc]', $options: 'i'}}, + {v: {$regex: 'O', $options: 'i'}}, + {u: {$regex: 'O', $options: 'i'}} ]}, {'$nor': [ {s: {$regex: '^d'}}, From b857eb50ab83f6216ce38f2a8eca88b470794b72 Mon Sep 17 00:00:00 2001 From: Avital Oliver Date: Mon, 29 Jul 2013 12:43:50 -0700 Subject: [PATCH 123/175] Update History.md --- History.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/History.md b/History.md index d0c3e0a30a..2821ddcdfa 100644 --- a/History.md +++ b/History.md @@ -1,6 +1,8 @@ ## vNEXT +* Fix Mongo selectors of the form: {$regex: /foo/}. + * Calling `findOne()` on the server no longer loads the full query result into memory. From 541c68a251b1c1abee1929477feedafea2402985 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 29 Jul 2013 13:39:23 -0700 Subject: [PATCH 124/175] Treat {$regex: /foo/i, $options: ''} same as in minimongo. --- packages/minimongo/selector.js | 3 ++- packages/mongo-livedata/collection.js | 2 +- packages/mongo-livedata/mongo_livedata_tests.js | 7 +++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/minimongo/selector.js b/packages/minimongo/selector.js index 0596711908..7cf8fa0a5d 100644 --- a/packages/minimongo/selector.js +++ b/packages/minimongo/selector.js @@ -258,7 +258,8 @@ var VALUE_OPERATORS = { "$regex": function (operand, options) { if (options !== undefined) { // Options passed in $options (even the empty string) always overrides - // options in the RegExp object itself. + // options in the RegExp object itself. (See also + // Meteor.Collection._rewriteSelector.) // Be clear that we only support the JS-supported options, not extended // ones (eg, Mongo supports x and s). Ideally we would implement x and s diff --git a/packages/mongo-livedata/collection.js b/packages/mongo-livedata/collection.js index 9a3fcd8f37..b66b298587 100644 --- a/packages/mongo-livedata/collection.js +++ b/packages/mongo-livedata/collection.js @@ -268,7 +268,7 @@ Meteor.Collection._rewriteSelector = function (selector) { ret[key] = convertRegexpToMongoSelector(value.$regex); // if value is {$regex: /foo/, $options: ...} then $options // override the ones set on $regex. - if (value.$options) + if (value.$options !== undefined) ret[key].$options = value.$options; } else if (_.contains(['$or','$and','$nor'], key)) { diff --git a/packages/mongo-livedata/mongo_livedata_tests.js b/packages/mongo-livedata/mongo_livedata_tests.js index 1f0fc98035..0cc15c0979 100644 --- a/packages/mongo-livedata/mongo_livedata_tests.js +++ b/packages/mongo-livedata/mongo_livedata_tests.js @@ -916,7 +916,9 @@ Tinytest.add('mongo-livedata - rewrite selector', function (test) { {'$nor': [ {s: /^d/}, {t: /^e/i}, - {u: {$regex: /^f/i}} + {u: {$regex: /^f/i}}, + // even empty string overrides built-in flags + {v: {$regex: /^g/i, $options: ''}} ]} ]} ), @@ -932,7 +934,8 @@ Tinytest.add('mongo-livedata - rewrite selector', function (test) { {'$nor': [ {s: {$regex: '^d'}}, {t: {$regex: '^e', $options: 'i'}}, - {u: {$regex: '^f', $options: 'i'}} + {u: {$regex: '^f', $options: 'i'}}, + {v: {$regex: '^g', $options: ''}} ]} ]} ); From 1202b781ef69609142dbb1860b1af038264b6dc5 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 29 Jul 2013 15:35:10 -0700 Subject: [PATCH 125/175] Rename 'native' to 'os', in arch names. Requires a BUILT_BY bump, unless you really want to fail to load your plugins and crash. --- packages/star-translate/translator.js | 6 ++-- tools/archinfo.js | 45 +++++++++++---------------- tools/bundler.js | 36 ++++++++++----------- tools/packages.js | 26 ++++++++-------- 4 files changed, 53 insertions(+), 60 deletions(-) diff --git a/packages/star-translate/translator.js b/packages/star-translate/translator.js index 55220198cc..77d08756de 100644 --- a/packages/star-translate/translator.js +++ b/packages/star-translate/translator.js @@ -80,9 +80,9 @@ StarTranslator._getPlatform = function () { var self = this; // Duplicated from meteor/tools/bundler.js var archToPlatform = { - 'native.linux.x86_32': 'Linux_i686', - 'native.linux.x86_64': 'Linux_x86_64', - 'native.osx.x86_64': 'Darwin_x86_64' + 'os.linux.x86_32': 'Linux_i686', + 'os.linux.x86_64': 'Linux_x86_64', + 'os.osx.x86_64': 'Darwin_x86_64' }; return archToPlatform[self._getArch()]; }; diff --git a/tools/archinfo.js b/tools/archinfo.js index f36a4393e9..f3323ed612 100644 --- a/tools/archinfo.js +++ b/tools/archinfo.js @@ -14,8 +14,8 @@ var files = require('./files.js'); * Old versions of Internet Explorer (not sure yet exactly which * versions to distinguish -- maybe 6 and 8?) * - * native.linux.x86_32 - * native.linux.x86_64 + * os.linux.x86_32 + * os.linux.x86_64 * Linux on Intel x86 architecture. x86_64 means a system that can * run 64-bit images, furnished with 64-bit builds of shared * libraries (there is no guarantee that 32-bit builds of shared @@ -37,16 +37,16 @@ var files = require('./files.js'); * architecture. * * Basically the punchline is: if you installed the 32-bit version - * of Ubuntu, you've got a native.linux.x86_32 system and you will - * use exclusively native.linux.x86_32 packages, and likewise + * of Ubuntu, you've got a os.linux.x86_32 system and you will + * use exclusively os.linux.x86_32 packages, and likewise * 64-bit. They are two parallel universes and which one you're in * is determined by which version of Red Hat or Ubuntu you * installed. * - * native.osx.x86_64 + * os.osx.x86_64 * OS X (technically speaking, Darwin) on Intel x86 architecture, * with a kernel capable of loading 64-bit images, and 64-bit builds - * of shared libraries available. If a native.osx.x86_64 package + * of shared libraries available. If a os.osx.x86_64 package * contains a shared library, it is only required to provide a * 64-bit version of the library (it is not required to provide a * fat binary with both 32-bit and 64-bit builds.) @@ -55,19 +55,19 @@ var files = require('./files.js'); * the kernel can load 64-bit images, and the Apple-supplied shared * libraries are fat binaries that include both 32-bit and 64-bit * builds in a single file. So it is technically fine (but - * discouraged) for a native.osx.x86_64 to include a 32-bit + * discouraged) for a os.osx.x86_64 to include a 32-bit * executable, if it only uses the system's shared libraries, but * you'll run into problems if shared libraries from other packages * are used. * - * There is no native.osx.x86_32. Our experience is that such + * There is no os.osx.x86_32. Our experience is that such * hardware is virtually extinct. Meteor has never supported it and * nobody has asked for it. * - * In the future there will be a native.windows architecture, probably + * In the future there will be a os.windows architecture, probably * also with x86_64 and x86_32 variants. * - * To be (more but far from completely) precise, the ABI for native.* + * To be (more but far from completely) precise, the ABI for os.* * architectures includes a CPU type, a mode in which the code will be * run (eg, 64 bit), an executable file format (eg, ELF), a promise to * make any shared libraries available in a particular architecture, @@ -114,17 +114,11 @@ var files = require('./files.js'); * Intel", because why should it be?) and didn't imply too close of an * equivalence to the precise meanings that other platforms may assign * to some of these strings. - * - * XXX we don't like the word 'native'. 'server'? confusing since this - * is also for command-line tools. 'host'? 'os'? 'pc'? What we're - * really talking about is "something with a hierarchical file system, - * and with processes that take an argument vector and return an - * integer" -- like linux, darwin and windows, but unlike the browser. */ // Returns the fully qualified arch of this host -- something like -// "native.linux.x86_32" or "native.osx.x86_64". Must be called inside +// "os.linux.x86_32" or "os.osx.x86_64". Must be called inside // a fiber. Throws an error if it's not a supported architecture. // // If you change this, also change scripts/admin/launch-meteor @@ -147,15 +141,15 @@ var host = function () { if (run('uname', '-p') !== "i386" || run('sysctl', '-n', 'hw.cpu64bit_capable') !== "1") throw new Error("Only 64-bit Intel processors are supported on OS X"); - _host = "native.osx.x86_64"; + _host = "os.osx.x86_64"; } else if (uname === "Linux") { var machine = run('uname', '-m'); if (_.contains(["i386", "i686", "x86"], machine)) - _host = "native.linux.x86_32"; + _host = "os.linux.x86_32"; else if (_.contains(["x86_64", "amd64", "ia64"], machine)) - _host = "native.linux.x86_64"; + _host = "os.linux.x86_64"; else throw new Error("Unsupported architecture: " + machine); } @@ -167,9 +161,9 @@ var host = function () { return _host; }; -// True if `host` (an architecture name such as 'native.linux.x86_64') -// can run programs of architecture `program` (which might be -// something like 'native', 'native.linux', or 'native.linux.x86_64'.) +// True if `host` (an architecture name such as 'os.linux.x86_64') can run +// programs of architecture `program` (which might be something like 'os', +// 'os.linux', or 'os.linux.x86_64'.) // // `host` and `program` are just mnemonics -- `host` does not // necessariy have to be a fully qualified architecture name. This @@ -207,9 +201,8 @@ var mostSpecificMatch = function (host, programs) { // returns the least specific such architecture. Otherwise (the // architectures are disjoin) raise an exception. // -// For example, for 'native' and 'native.osx', return -// 'native.osx'. For 'native' and 'native.linux.x86_64', return -// 'native.linux.x86_64'. For 'native' and 'browser', throw an +// For example, for 'os' and 'os.osx', return 'os.osx'. For 'os' and +// 'os.linux.x86_64', return 'os.linux.x86_64'. For 'os' and 'browser', throw an // exception. var leastSpecificDescription = function (programs) { if (programs.length === 0) diff --git a/tools/bundler.js b/tools/bundler.js index cad3ab3e51..b3d4b11216 100644 --- a/tools/bundler.js +++ b/tools/bundler.js @@ -10,7 +10,7 @@ // - name: short, unique name for program, for referring to it // programmatically // - arch: architecture that this program targets. Something like -// "native", "native.linux.x86_64", or "browser.w3c". +// "os", "os.linux.x86_64", or "browser.w3c". // - path: directory (relative to star.json) containing this program // // XXX in the future this will also contain instructions for @@ -22,8 +22,8 @@ // - plugins: array of plugins in the star, each an object: // - name: short, unique name for plugin, for referring to it // programmatically -// - arch: typically 'native' (for a portable plugin) or eg -// 'native.linux.x86_64' for one that include native node_modules +// - arch: typically 'os' (for a portable plugin) or eg +// 'os.linux.x86_64' for one that include native node_modules // - path: path (relative to star.json) to the control file (eg, // program.json) for this plugin // @@ -82,7 +82,7 @@ // page is 'app.html'. // // -// == Format of a program when arch is "native.*" == +// == Format of a program when arch is "os.*" == // // Standard: // @@ -145,13 +145,13 @@ // it's fine and it's probably actually cleaner, because it means that // the plugin can be treated as a self-contained unit. // -// Note that while the spec for "native.*" is going to change to +// Note that while the spec for "os.*" is going to change to // represent an arbitrary POSIX (or Windows) process rather than // assuming a nodejs host, these plugins will always refer to // JavaScript code (that potentially might be a plugin to be loaded // into an existing JS VM). But this seems to be a concern that is // somewhat orthogonal to arch (these plugins can still use packages -// of arch "native.*".) There is probably a missing abstraction here +// of arch "os.*".) There is probably a missing abstraction here // somewhere (decoupling target type from architecture) but it can // wait until later. @@ -389,7 +389,7 @@ var Target = function (options) { // Package library to use for resolving package dependenices. self.library = options.library; - // Something like "browser.w3c" or "native" or "native.osx.x86_64" + // Something like "browser.w3c" or "os" or "os.osx.x86_64" self.arch = options.arch; // All of the Slices that are to go into this target, in the order @@ -572,7 +572,7 @@ _.extend(Target.prototype, { var self = this; var isBrowser = archinfo.matches(self.arch, "browser"); - var isNative = archinfo.matches(self.arch, "native"); + var isOs = archinfo.matches(self.arch, "os"); // Copy their resources into the bundle in order _.each(self.slices, function (slice) { @@ -582,7 +582,7 @@ _.extend(Target.prototype, { var resources = slice.getResources(self.arch); // First, find all the assets, so that we can associate them with each js - // resource (for native slices). + // resource (for os slices). var sliceAssets = {}; _.each(resources, function (resource) { if (resource.type !== "asset") @@ -590,7 +590,7 @@ _.extend(Target.prototype, { var f = new File({data: resource.data, cacheable: false}); - var relPath = isNative + var relPath = isOs ? path.join("assets", resource.servePath) : stripLeadingSlash(resource.servePath); f.setTargetPathFromRelPath(relPath); @@ -628,7 +628,7 @@ _.extend(Target.prototype, { f.setUrlFromRelPath(resource.servePath); } - if (resource.type === "js" && isNative) { + if (resource.type === "js" && isOs) { // Hack, but otherwise we'll end up putting app assets on this file. if (resource.servePath !== "/packages/global-imports.js") f.setAssets(sliceAssets); @@ -714,10 +714,10 @@ _.extend(Target.prototype, { // Return the most inclusive architecture with which this target is // compatible. For example, if we set out to build a - // 'native.linux.x86_64' version of this target (by passing that as + // 'os.linux.x86_64' version of this target (by passing that as // the 'arch' argument to the constructor), but ended up not // including anything that was specific to Linux, the return value - // would be 'native'. + // would be 'os'. mostCompatibleArch: function () { var self = this; return archinfo.leastSpecificDescription(_.pluck(self.slices, 'arch')); @@ -1182,7 +1182,7 @@ var JsImageTarget = function (options) { var self = this; Target.apply(this, arguments); - if (! archinfo.matches(self.arch, "native")) + if (! archinfo.matches(self.arch, "os")) // Conceivably we could support targeting the browser as long as // no native node modules were used. No use case for that though. throw new Error("JsImageTarget targeting something unusual?"); @@ -1227,7 +1227,7 @@ var ServerTarget = function (options) { self.releaseStamp = options.releaseStamp; self.library = options.library; - if (! archinfo.matches(self.arch, "native")) + if (! archinfo.matches(self.arch, "os")) throw new Error("ServerTarget targeting something that isn't a server?"); }; @@ -1284,9 +1284,9 @@ _.extend(ServerTarget.prototype, { // Script that fetches the dev_bundle and runs the server bootstrap var archToPlatform = { - 'native.linux.x86_32': 'Linux_i686', - 'native.linux.x86_64': 'Linux_x86_64', - 'native.osx.x86_64': 'Darwin_x86_64' + 'os.linux.x86_32': 'Linux_i686', + 'os.linux.x86_64': 'Linux_x86_64', + 'os.osx.x86_64': 'Darwin_x86_64' }; var arch = archinfo.host(); var platform = archToPlatform[arch]; diff --git a/tools/packages.js b/tools/packages.js index ffb75bed4c..e2e39384fa 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -20,7 +20,7 @@ var sourcemap = require('source-map'); // unipackage/slice changes, but this version (which is build-tool-specific) can // change when the the contents (not structure) of the built output changes. So // eg, if we improve the linker's static analysis, this should be bumped. -exports.BUILT_BY = 'meteor/4'; +exports.BUILT_BY = 'meteor/5'; // Find all files under `rootPath` that have an extension in // `extensions` (an array of extensions without leading dot), and @@ -338,7 +338,7 @@ _.extend(Slice.prototype, { // in the module. // - addAsset({ path: "my/image.png", data: Buffer }) // Add a file to serve as-is over HTTP (browser targets) or - // to include as-is in the bundle (native targets). + // to include as-is in the bundle (os targets). // This time `data` is a Buffer rather than a string. For // browser targets, it will be served at the exact path you // request (concatenated with rootOutputPath). For server @@ -354,7 +354,7 @@ _.extend(Slice.prototype, { // // XXX for now, these handlers must only generate portable code // (code that isn't dependent on the arch, other than 'browser' - // vs 'native') -- they can look at the arch that is provided + // vs 'os') -- they can look at the arch that is provided // but they can't rely on the running on that particular arch // (in the end, an arch-specific slice will be emitted only if // there are native node modules.) Obviously this should @@ -875,7 +875,7 @@ _.extend(Package.prototype, { // Return the slice of the package to use for a given slice name // (eg, 'main' or 'test') and target architecture (eg, - // 'native.linux.x86_64' or 'browser'), or throw an exception if + // 'os.linux.x86_64' or 'browser'), or throw an exception if // that packages can't be loaded under these circumstances. getSingleSlice: function (name, arch) { var self = this; @@ -1109,7 +1109,7 @@ _.extend(Package.prototype, { return source; }); - var arch = isPortable ? "native" : archinfo.host(); + var arch = isPortable ? "os" : archinfo.host(); var slice = new Slice(self, { name: options.sliceName, arch: arch, @@ -1121,7 +1121,7 @@ _.extend(Package.prototype, { }); self.slices.push(slice); - self.defaultSlices = {'native': [options.sliceName]}; + self.defaultSlices = {'os': [options.sliceName]}; }, // Initialize a package from a legacy-style (package.js) package @@ -1696,9 +1696,9 @@ _.extend(Package.prototype, { } // Create slices - var nativeArch = isPortable ? "native" : archinfo.host(); + var osArch = isPortable ? "os" : archinfo.host(); _.each(["use", "test"], function (role) { - _.each(["browser", nativeArch], function (arch) { + _.each(["browser", osArch], function (arch) { var where = (arch === "browser") ? "client" : "server"; // Everything depends on the package 'meteor', which sets up @@ -1734,14 +1734,14 @@ _.extend(Package.prototype, { noExports: role === "test", declaredExports: role === "use" ? exports[where] : null, dependencyInfo: dependencyInfo, - nodeModulesPath: arch === nativeArch && nodeModulesPath || undefined + nodeModulesPath: arch === osArch && nodeModulesPath || undefined })); }); }); // Default slices - self.defaultSlices = { browser: ['main'], 'native': ['main'] }; - self.testSlices = { browser: ['tests'], 'native': ['tests'] }; + self.defaultSlices = { browser: ['main'], 'os': ['main'] }; + self.testSlices = { browser: ['tests'], 'os': ['tests'] }; }, // Initialize a package from a legacy-style application directory @@ -1758,7 +1758,7 @@ _.extend(Package.prototype, { _.each(["client", "server"], function (sliceName) { // Determine used packages var names = project.get_packages(appDir); - var arch = sliceName === "server" ? "native" : "browser"; + var arch = sliceName === "server" ? "os" : "browser"; // Create slice var slice = new Slice(self, { @@ -1902,7 +1902,7 @@ _.extend(Package.prototype, { }; }); - self.defaultSlices = { browser: ['client'], 'native': ['server'] }; + self.defaultSlices = { browser: ['client'], 'os': ['server'] }; }, // Initialize a package from a prebuilt Unipackage on disk. On From e3ab617f5f53902de96d7a14e91bfce568f27561 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 29 Jul 2013 16:14:59 -0700 Subject: [PATCH 126/175] Fix Packages -> Package --- packages/minimongo/minimongo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/minimongo/minimongo.js b/packages/minimongo/minimongo.js index 8b4fd9c655..11c00eb90b 100644 --- a/packages/minimongo/minimongo.js +++ b/packages/minimongo/minimongo.js @@ -199,7 +199,7 @@ LocalCollection.Cursor.prototype._publishCursor = function (sub) { var collection = self.collection.name; // XXX minimongo should not depend on mongo-livedata! - return Packages['mongo-livedata']. + return Package['mongo-livedata']. Meteor.Collection._publishCursor(self, sub, collection); }; From 3fa2a8c0b0fce7fdafa1bb9624509eb09e2d86ab Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 29 Jul 2013 16:30:21 -0700 Subject: [PATCH 127/175] Better fix for minimongo/mongo-livedata dep issue. Still hacky though. They should have a mutual dependency. --- packages/minimongo/minimongo.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/minimongo/minimongo.js b/packages/minimongo/minimongo.js index 11c00eb90b..d50b9de011 100644 --- a/packages/minimongo/minimongo.js +++ b/packages/minimongo/minimongo.js @@ -199,8 +199,7 @@ LocalCollection.Cursor.prototype._publishCursor = function (sub) { var collection = self.collection.name; // XXX minimongo should not depend on mongo-livedata! - return Package['mongo-livedata']. - Meteor.Collection._publishCursor(self, sub, collection); + return Meteor.Collection._publishCursor(self, sub, collection); }; LocalCollection._isOrderedChanges = function (callbacks) { From 0c7954b4761731df415cf48cf46ee39436126de9 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 29 Jul 2013 16:48:22 -0700 Subject: [PATCH 128/175] Expose Mongo timestamp class a little more. --- packages/mongo-livedata/mongo_driver.js | 8 +++----- packages/mongo-livedata/observe_changes_tests.js | 2 +- packages/mongo-livedata/package.js | 2 -- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/mongo-livedata/mongo_driver.js b/packages/mongo-livedata/mongo_driver.js index b230cfc125..b83108aaea 100644 --- a/packages/mongo-livedata/mongo_driver.js +++ b/packages/mongo-livedata/mongo_driver.js @@ -7,8 +7,6 @@ * these outside of a fiber they will explode! */ -MongoLivedataTest = {}; - var path = Npm.require('path'); var MongoDB = Npm.require('mongodb'); var Fiber = Npm.require('fibers'); @@ -993,7 +991,7 @@ _.extend(LiveResultsSet.prototype, { // - If you disconnect and reconnect from Mongo, it will essentially restart // the query, which will lead to duplicate results. This is pretty bad, // but if you include a field called 'ts' which is inserted as -// new MongoLivedataTest.MongoTimestamp(0, 0) (which is initialized to the +// new MongoConnection.MongoTimestamp(0, 0) (which is initialized to the // current Mongo-style timestamp), we'll be able to find the place to // restart properly. (This field is specifically understood by Mongo with an // optimization which allows it to find the right place to start without @@ -1082,5 +1080,5 @@ MongoConnection.prototype._observeChangesTailable = function ( // XXX We probably need to find a better way to expose this. Right now // it's only used by tests, but in fact you need it in normal -// operation to interact with capped collections. -MongoLivedataTest.MongoTimestamp = MongoDB.Timestamp; +// operation to interact with capped collections (eg, Galaxy uses it). +MongoConnection.MongoTimestamp = MongoDB.Timestamp; diff --git a/packages/mongo-livedata/observe_changes_tests.js b/packages/mongo-livedata/observe_changes_tests.js index 73ebab83b9..cf3e514e7d 100644 --- a/packages/mongo-livedata/observe_changes_tests.js +++ b/packages/mongo-livedata/observe_changes_tests.js @@ -206,7 +206,7 @@ if (Meteor.isServer) { self.xs = []; self.expects = []; self.insert = function (fields) { - coll.insert(_.extend({ts: new MongoLivedataTest.MongoTimestamp(0, 0)}, + coll.insert(_.extend({ts: new Meteor._Mongo.MongoTimestamp(0, 0)}, fields)); }; diff --git a/packages/mongo-livedata/package.js b/packages/mongo-livedata/package.js index 7d53239653..f7e54f2d20 100644 --- a/packages/mongo-livedata/package.js +++ b/packages/mongo-livedata/package.js @@ -30,8 +30,6 @@ Package.on_use(function (api) { // questionable reasons) initialized by the webapp package. api.use('webapp', 'server', {weak: true}); - api.export('MongoLivedataTest', 'server', {testOnly: true}); - api.add_files('mongo_driver.js', 'server'); api.add_files('local_collection_driver.js', ['client', 'server']); api.add_files('remote_collection_driver.js', 'server'); From 97eca2bd7c154d5c8c8f6ceab3d74b68578abb74 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 29 Jul 2013 17:11:42 -0700 Subject: [PATCH 129/175] Put Mongo internal stuff onto MongoInternals. --- packages/mongo-livedata/collection.js | 8 +++++--- packages/mongo-livedata/mongo_driver.js | 12 ++++++------ packages/mongo-livedata/observe_changes_tests.js | 2 +- packages/mongo-livedata/package.js | 7 +++++-- packages/mongo-livedata/remote_collection_driver.js | 8 ++++---- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/packages/mongo-livedata/collection.js b/packages/mongo-livedata/collection.js index b66b298587..1cea1d650e 100644 --- a/packages/mongo-livedata/collection.js +++ b/packages/mongo-livedata/collection.js @@ -61,10 +61,12 @@ Meteor.Collection = function (name, options) { if (!options._driver) { if (name && self._connection === Meteor.server && - typeof getRemoteCollectionDriver !== "undefined") - options._driver = getRemoteCollectionDriver(); - else + typeof MongoInternals !== "undefined" && + MongoInternals.defaultRemoteCollectionDriver) { + options._driver = MongoInternals.defaultRemoteCollectionDriver(); + } else { options._driver = LocalCollectionDriver; + } } self._collection = options._driver.open(name, self._connection); diff --git a/packages/mongo-livedata/mongo_driver.js b/packages/mongo-livedata/mongo_driver.js index b83108aaea..e3fcf835d9 100644 --- a/packages/mongo-livedata/mongo_driver.js +++ b/packages/mongo-livedata/mongo_driver.js @@ -12,6 +12,8 @@ var MongoDB = Npm.require('mongodb'); var Fiber = Npm.require('fibers'); var Future = Npm.require(path.join('fibers', 'future')); +MongoInternals = {}; + var replaceNames = function (filter, thing) { if (typeof thing === "object") { if (_.isArray(thing)) { @@ -122,10 +124,6 @@ MongoConnection = function (url) { }); }; -// Some apps want to use this directly. Maybe they shouldn't, but let's not -// break them yet. -Meteor._Mongo = MongoConnection; - MongoConnection.prototype.close = function() { var self = this; // Use Future.wrap so that errors get thrown. This happens to @@ -991,7 +989,7 @@ _.extend(LiveResultsSet.prototype, { // - If you disconnect and reconnect from Mongo, it will essentially restart // the query, which will lead to duplicate results. This is pretty bad, // but if you include a field called 'ts' which is inserted as -// new MongoConnection.MongoTimestamp(0, 0) (which is initialized to the +// new MongoInternals.MongoTimestamp(0, 0) (which is initialized to the // current Mongo-style timestamp), we'll be able to find the place to // restart properly. (This field is specifically understood by Mongo with an // optimization which allows it to find the right place to start without @@ -1081,4 +1079,6 @@ MongoConnection.prototype._observeChangesTailable = function ( // XXX We probably need to find a better way to expose this. Right now // it's only used by tests, but in fact you need it in normal // operation to interact with capped collections (eg, Galaxy uses it). -MongoConnection.MongoTimestamp = MongoDB.Timestamp; +MongoInternals.MongoTimestamp = MongoDB.Timestamp; + +MongoInternals.Connection = MongoConnection; diff --git a/packages/mongo-livedata/observe_changes_tests.js b/packages/mongo-livedata/observe_changes_tests.js index cf3e514e7d..94efe37af8 100644 --- a/packages/mongo-livedata/observe_changes_tests.js +++ b/packages/mongo-livedata/observe_changes_tests.js @@ -206,7 +206,7 @@ if (Meteor.isServer) { self.xs = []; self.expects = []; self.insert = function (fields) { - coll.insert(_.extend({ts: new Meteor._Mongo.MongoTimestamp(0, 0)}, + coll.insert(_.extend({ts: new MongoInternals.MongoTimestamp(0, 0)}, fields)); }; diff --git a/packages/mongo-livedata/package.js b/packages/mongo-livedata/package.js index f7e54f2d20..adc83c3862 100644 --- a/packages/mongo-livedata/package.js +++ b/packages/mongo-livedata/package.js @@ -26,10 +26,13 @@ Package.on_use(function (api) { // Allow us to detect 'autopublish', and publish collections if it's loaded. api.use('autopublish', 'server', {weak: true}); - // RemoteCollectionDriver gets its deployConfig from something that is (for - // questionable reasons) initialized by the webapp package. + // defaultRemoteCollectionDriver gets its deployConfig from something that is + // (for questionable reasons) initialized by the webapp package. api.use('webapp', 'server', {weak: true}); + // Stuff that should be exposed via a real API, but we haven't yet. + api.export('MongoInternals', 'server'); + api.add_files('mongo_driver.js', 'server'); api.add_files('local_collection_driver.js', ['client', 'server']); api.add_files('remote_collection_driver.js', 'server'); diff --git a/packages/mongo-livedata/remote_collection_driver.js b/packages/mongo-livedata/remote_collection_driver.js index 5996344faf..6ba05d0679 100644 --- a/packages/mongo-livedata/remote_collection_driver.js +++ b/packages/mongo-livedata/remote_collection_driver.js @@ -1,9 +1,9 @@ -var RemoteCollectionDriver = function (mongo_url) { +MongoInternals.RemoteCollectionDriver = function (mongo_url) { var self = this; self.mongo = new MongoConnection(mongo_url); }; -_.extend(RemoteCollectionDriver.prototype, { +_.extend(MongoInternals.RemoteCollectionDriver.prototype, { open: function (name) { var self = this; var ret = {}; @@ -21,7 +21,7 @@ _.extend(RemoteCollectionDriver.prototype, { // Create the singleton RemoteCollectionDriver only on demand, so we // only require Mongo configuration if it's actually used (eg, not if // you're only trying to receive data from a remote DDP server.) -getRemoteCollectionDriver = _.once(function () { +MongoInternals.defaultRemoteCollectionDriver = _.once(function () { // XXX kind of hacky var mongoUrl = ( typeof __meteor_bootstrap__ !== 'undefined' && @@ -31,5 +31,5 @@ getRemoteCollectionDriver = _.once(function () { if (! mongoUrl) throw new Error("MONGO_URL must be set in environment"); - return new RemoteCollectionDriver(mongoUrl); + return new MongoInternals.RemoteCollectionDriver(mongoUrl); }); From b04818221e26f86bf0279d33841cbd2e1620c444 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 29 Jul 2013 18:07:12 -0700 Subject: [PATCH 130/175] Move source map instructions comment above imports. --- tools/linker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/linker.js b/tools/linker.js index 73dfb32302..8392d28877 100644 --- a/tools/linker.js +++ b/tools/linker.js @@ -582,7 +582,7 @@ var link = function (options) { _.each(options.prelinkFiles, function (file) { if (file.sourceMap) { if (options.includeSourceMapInstructions) - header += "\n" + SOURCE_MAP_INSTRUCTIONS_COMMENT + "\n\n"; + header = SOURCE_MAP_INSTRUCTIONS_COMMENT + "\n\n" + header; // Bias the source map by the length of the header without // (fully) parsing and re-serializing it. (We used to do this From 719bbb115b517b9da45dcbbfa5a064681180ac4b Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 29 Jul 2013 23:21:08 -0700 Subject: [PATCH 131/175] Move bindToProxy to WebAppInternals; it is needed by the Galaxy proxy. --- packages/webapp/package.js | 3 +-- packages/webapp/webapp_server.js | 12 ++++-------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/webapp/package.js b/packages/webapp/package.js index c17c6f4683..e504c6b3d5 100644 --- a/packages/webapp/package.js +++ b/packages/webapp/package.js @@ -9,7 +9,6 @@ Npm.depends({connect: "2.7.10", Package.on_use(function (api) { api.use(['logging', 'underscore', 'routepolicy'], 'server'); - api.export('WebApp', 'server'); - api.export('main', 'server'); + api.export(['WebApp', 'main', 'WebAppInternals'], 'server'); api.add_files('webapp_server.js', 'server'); }); diff --git a/packages/webapp/webapp_server.js b/packages/webapp/webapp_server.js index 08f2aa5c1a..6cf8998cb1 100644 --- a/packages/webapp/webapp_server.js +++ b/packages/webapp/webapp_server.js @@ -12,11 +12,8 @@ var optimist = Npm.require('optimist'); var useragent = Npm.require('useragent'); var send = Npm.require('send'); -// XXX we have to export WebApp as a single symbol because we modify -// it (for example, it has an attribute httpServer which isn't known -// until runWebAppServer-time.) It would be nice to refactor so that -// this isn't the case and we can export the symbols individually. WebApp = {}; +WebAppInternals = {}; var findGalaxy = _.once(function () { if (!('GALAXY' in process.env)) { @@ -445,7 +442,7 @@ var runWebAppServer = function () { console.log("LISTENING"); // must match run.js var port = httpServer.address().port; if (bind.viaProxy && bind.viaProxy.proxyEndpoint) { - bindToProxy(bind.viaProxy); + WebAppInternals.bindToProxy(bind.viaProxy); } else if (bind.viaProxy) { // bind via the proxy, but we'll have to find it ourselves via // ultraworld. @@ -457,7 +454,7 @@ var runWebAppServer = function () { var doBinding = function (proxyService) { if (proxyService.providers.proxy) { Log("Attempting to bind to proxy at " + proxyService.providers.proxy); - bindToProxy(_.extend({ + WebAppInternals.bindToProxy(_.extend({ proxyEndpoint: proxyService.providers.proxy }, bind.viaProxy)); } @@ -482,8 +479,7 @@ var runWebAppServer = function () { }; }; -bindToProxy = function (proxyConfig) { - +WebAppInternals.bindToProxy = function (proxyConfig) { var securePort = proxyConfig.securePort || 4433; var insecurePort = proxyConfig.insecurePort || 8080; var bindPathPrefix = proxyConfig.bindPathPrefix || ""; From a319aa7f9e21671bc73de1f19d7b257d3dbbb753 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 29 Jul 2013 23:40:55 -0700 Subject: [PATCH 132/175] Implement 'bundle --debug'. Fixes #748. --- tools/meteor.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/meteor.js b/tools/meteor.js index 1a2b1f8e0e..93500596f9 100644 --- a/tools/meteor.js +++ b/tools/meteor.js @@ -801,6 +801,8 @@ Fiber(function () { help: "Pack this project up into a tarball", argumentParser: function (opt) { opt.boolean('for-deploy') + .boolean('debug') + .describe('debug', "bundle in debug mode (don't minify, etc)") .usage("Usage: meteor bundle \n" + "\n" + "Package this project up for deployment. The output is a tarball that\n" + @@ -829,7 +831,7 @@ Fiber(function () { var bundler = require(path.join(__dirname, 'bundler.js')); var bundleResult = bundler.bundle(context.appDir, bundle_path, { nodeModulesMode: argv['for-deploy'] ? 'skip' : 'copy', - minify: true, // XXX allow --debug + minify: !argv.debug, releaseStamp: context.releaseVersion, library: context.library }); From 1d568abc9cf452d3a8bfa03ead683bf003e0d4f7 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 29 Jul 2013 23:44:41 -0700 Subject: [PATCH 133/175] Add newline to end of "no galaxy" error. --- tools/meteor.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tools/meteor.js b/tools/meteor.js index 93500596f9..a483d8b011 100644 --- a/tools/meteor.js +++ b/tools/meteor.js @@ -424,10 +424,9 @@ Fiber(function () { argv._[0] = qualifySitename(argv._[0]); prepareForGalaxy(argv._[0], context, argv["ssh-identity"]); if (! context.galaxy) { - process.stdout.write("You must provide a galaxy to configure " + - "(by setting the GALAXY environment variable " + - "or providing a sitename " + - "(meteor galaxy configure )."); + process.stdout.write( + "You must provide a galaxy to configure (by setting the GALAXY environment variable " + + "or providing a sitename (meteor galaxy configure ).\n"); process.exit(1); } console.log("Visit http://localhost:" + context.galaxy.port + "/panel to configure your galaxy"); From fbfd21c07bce83a0252f42d071862be228b340bd Mon Sep 17 00:00:00 2001 From: David Glasser Date: Tue, 30 Jul 2013 01:00:01 -0700 Subject: [PATCH 134/175] Comment updates. --- tools/packages.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tools/packages.js b/tools/packages.js index e2e39384fa..c8331acbc8 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -709,6 +709,7 @@ _.extend(Slice.prototype, { path: compileStep.inputPath, sourcePath: compileStep.inputPath, // XXX eventually get rid of backward-compatibility "raw" name + // XXX COMPAT WITH 0.6.4 bare: compileStep.fileOptions.bare || compileStep.fileOptions.raw }); } @@ -826,12 +827,12 @@ var Package = function (library) { self.testSlices = {}; // The information necessary to build the plugins in this - // package. Map from plugin name to object with keys 'name', 'us', + // package. Map from plugin name to object with keys 'name', 'use', // 'sources', and 'npmDependencies'. self.pluginInfo = {}; - // Plugins in this package. Map from plugin name to - // bundler.Plugin. Present only when isBuilt is true. + // Plugins in this package. Map from plugin name to JsImage. Present only when + // isBuilt is true. self.plugins = {}; // Dependencies for any plugins in this package. Present only when @@ -1187,6 +1188,7 @@ _.extend(Package.prototype, { roleHandlers.test = f; }, + // XXX COMPAT WITH 0.6.4 // extension doesn't contain a dot register_extension: function (extension, callback) { if (_.has(self.legacyExtensionHandlers, extension)) { @@ -1619,6 +1621,7 @@ _.extend(Package.prototype, { }); }); }, + // XXX COMPAT WITH 0.6.4 error: function () { // I would try to support this but I don't even know what // its signature was supposed to be anymore @@ -1627,6 +1630,7 @@ _.extend(Package.prototype, { { useMyCaller: true }); // recover by ignoring }, + // XXX COMPAT WITH 0.6.4 registered_extensions: function () { buildmessage.error( "api.registered_extensions() is no longer supported", From 62626bf33d741a7f11d48bae1553ecb5825a7113 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Tue, 30 Jul 2013 11:15:15 -0700 Subject: [PATCH 135/175] Set $METEOR_DEBUG_BUILD to see what jobs are run. --- tools/buildmessage.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/tools/buildmessage.js b/tools/buildmessage.js index 4c730a1e81..89c60bb9f3 100644 --- a/tools/buildmessage.js +++ b/tools/buildmessage.js @@ -1,6 +1,8 @@ var _ = require('underscore'); var files = require('./files.js'); +var debugBuild = !!process.env.METEOR_DEBUG_BUILD; + // A job is something like "building package foo". It contains the set // of messages generated by tha job. A given build run could contain // several jobs. Each job has an (absolute) path associated with @@ -160,15 +162,22 @@ var capture = function (options, f) { if (typeof options === "object") { job = new Job(options); currentMessageSet.jobs.push(job); - } else + } else { f = options; // options not actually provided - currentJob = job + } + + currentJob = job; + + if (debugBuild) + console.log("START CAPTURE: " + options.title); try { f(); } finally { currentMessageSet = originalMessageSet; currentJob = originalJob; + if (debugBuild) + console.log("DONE CAPTURE: " + options.title); } return messageSet; @@ -203,9 +212,14 @@ var enterJob = function (options, f) { currentMessageSet.jobs.push(job); currentJob = job; + if (debugBuild) + console.log("START: " + options.title); + try { var ret = f(); } finally { + if (debugBuild) + console.log("DONE: " + options.title); currentJob = originalJob; } From 9b24174e08952e68d5b2a192ee5c6ba8a6cf677a Mon Sep 17 00:00:00 2001 From: David Glasser Date: Tue, 30 Jul 2013 11:16:44 -0700 Subject: [PATCH 136/175] Changing package/foo/package.js rebuilds all packages that use 'foo'. That's because this change could introduce or remove a plugin, which affects how the dependent package is built (eg, could change an extension between being handled vs static). Does not affect transitive dependencies. --- tools/packages.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tools/packages.js b/tools/packages.js index c8331acbc8..95bc45281f 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -835,7 +835,7 @@ var Package = function (library) { // isBuilt is true. self.plugins = {}; - // Dependencies for any plugins in this package. Present only when + // Dependencies for any plugins in this package. Complete only when // isBuilt is true. // XXX Refactor so that slice and plugin dependencies are handled by // the same mechanism. @@ -1154,6 +1154,12 @@ _.extend(Package.prototype, { var code = fs.readFileSync(packageJsPath); var packageJsHash = Builder.sha1(code); + // Any package that depends on us needs to be rebuilt if our package.js file + // changes, because a change to package.js might add or remove a plugin, + // which could change a file from being handled by extension vs treated as + // an asset. + self.pluginDependencyInfo.files[packageJsPath] = packageJsHash; + // == 'Package' object visible in package.js == var Package = { // Set package metadata. Options: From da3cd5d09e1dc1ff9404c6bb67ca41eb315011bf Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Fri, 19 Jul 2013 17:19:23 -0700 Subject: [PATCH 137/175] Merge deployConfig with a deployConfig from db in ctl --- packages/ctl/ctl.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/ctl/ctl.js b/packages/ctl/ctl.js index b1f022cb4f..dd955a5754 100644 --- a/packages/ctl/ctl.js +++ b/packages/ctl/ctl.js @@ -10,6 +10,19 @@ Ctl.Commands.push({ } }); +var mergeObjects = function (obj1, obj2) { + var result = _.clone(obj1); + _.each(obj2, function (v, k) { + // If both objects have an object at this key, then merge those objects. + // Otherwise, choose obj2's value. + if ((v instanceof Object) && (obj1[k] instanceof Object)) + result[k] = mergeObjects(v, obj1[k]); + else + result[k] = v; + }); + return result; +}; + Ctl.Commands.push({ name: "start", @@ -63,6 +76,11 @@ Ctl.Commands.push({ } }; + // Merge in any values that might have been added to the app's config in + // the database. + if (appConfig.deployConfig) + deployConfig = mergeObjects(deployConfig, appConfig.deployConfig); + // XXX args? env? Ctl.prettyCall(Ctl.findGalaxy(), 'run', [Ctl.myAppName(), 'server', { exitPolicy: 'restart', From 36492a23acb3f1277a84be522ea3aeb01fbfe4f9 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Tue, 30 Jul 2013 13:55:55 -0700 Subject: [PATCH 138/175] Serialize dependency info by slice. This makes saveAsUnipackage -> initFromUnipackage less lossy. Also means that, eg, plugins don't end up dependent on tests in packages they use. Use of the json file path as the key in sliceDependencies is kind of hacky, but I'm viewing buildinfo.json as less of a standardized format as unipackage.json. --- tools/packages.js | 84 +++++++++++++++++++++-------------------------- 1 file changed, 38 insertions(+), 46 deletions(-) diff --git a/tools/packages.js b/tools/packages.js index 95bc45281f..519c29af91 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -20,7 +20,7 @@ var sourcemap = require('source-map'); // unipackage/slice changes, but this version (which is build-tool-specific) can // change when the the contents (not structure) of the built output changes. So // eg, if we improve the linker's static analysis, this should be bumped. -exports.BUILT_BY = 'meteor/5'; +exports.BUILT_BY = 'meteor/6'; // Find all files under `rootPath` that have an extension in // `extensions` (an array of extensions without leading dot), and @@ -857,12 +857,6 @@ var Package = function (library) { // building, into two flags. self.pluginsBuilt = false; self.slicesBuilt = false; - - // True if the dependencyInfo in the slices can be taken as as - // accurate. (False if we loaded the package from disk and it didn't - // come with dependency info, eg, it was a release build built by - // someone else.) - self.haveDependencyInfo = false; }; _.extend(Package.prototype, { @@ -1053,7 +1047,6 @@ _.extend(Package.prototype, { slice.build(); }); self.slicesBuilt = true; - self.haveDependencyInfo = true; }, // Programmatically initialized a package from scratch. For now, @@ -1951,21 +1944,16 @@ _.extend(Package.prototype, { // Read the dependency info (if present), and make the strings // back into regexps - var haveDependencyInfo, dependencies; - if (buildInfoJson.dependencies) { - haveDependencyInfo = true; - dependencies = buildInfoJson.dependencies; - _.each(dependencies.directories, function (d) { + var sliceDependencies = buildInfoJson.sliceDependencies || {}; + _.each(sliceDependencies, function (dependencyInfo, sliceTag) { + _.each(dependencyInfo.directories, function (d) { _.each(["include", "exclude"], function (k) { d[k] = _.map(d[k], function (s) { return new RegExp(s); }); }); }); - } else { - haveDependencyInfo = false; - dependencies = { files: {}, directories: {} }; - } + }); // If we're supposed to check the dependencies, go ahead and do so if (options.onlyIfUpToDate) { @@ -1985,7 +1973,7 @@ _.extend(Package.prototype, { return false; } - if (! self.checkUpToDate(dependencies)) + if (! self.checkUpToDate(sliceDependencies)) return false; } @@ -1994,7 +1982,6 @@ _.extend(Package.prototype, { summary: mainJson.summary, internal: mainJson.internal }; - self.haveDependencyInfo = haveDependencyInfo; self.defaultSlices = mainJson.defaultSlices; self.testSlices = mainJson.testSlices; @@ -2044,7 +2031,7 @@ _.extend(Package.prototype, { var slice = new Slice(self, { name: sliceMeta.name, arch: sliceMeta.arch, - dependencyInfo: dependencies, + dependencyInfo: sliceDependencies[sliceMeta.path], nodeModulesPath: nodeModulesPath, uses: _.map(sliceJson.uses, function (u) { return { @@ -2120,29 +2107,35 @@ _.extend(Package.prototype, { // Try to check if this package is up-to-date (that is, whether its // source files have been modified.) True if we have dependency info // and it says that the package is up-to-date. False if a source - // file has changed OR if we loaded the package from disk and it - // didn't have dependency info. + // file has changed. // - // The argument _dependencyInfo is for internal use and should not - // be set by the caller. - checkUpToDate: function (_dependencyInfo) { + // The argument _sliceDependencies is used when reading from disk when there + // are no slices yet; don't pass it from outside this file. + checkUpToDate: function (_sliceDependencies) { var self = this; // Compute the dependency info to use - var dependencyInfo = _dependencyInfo; - if (! dependencyInfo && self.haveDependencyInfo) { - dependencyInfo = { files: {}, directories: {} }; + var dependencyInfo = { files: {}, directories: {} }; + + var merge = function (di) { + // XXX is naive merge sufficient here? + _.extend(dependencyInfo.files, di.files); + _.extend(dependencyInfo.directories, di.directories); + }; + + if (_sliceDependencies) { + _.each(_sliceDependencies, function (dependencyInfo, sliceTag) { + merge(dependencyInfo); + }); + } else { _.each(self.slices, function (slice) { - // XXX is naive merge sufficient here? - _.extend(dependencyInfo.files, - slice.dependencyInfo.files); - _.extend(dependencyInfo.directories, - slice.dependencyInfo.directories); + merge(slice.dependencyInfo); }); } - if (! dependencyInfo) - return false; + // XXX There used to be a concept in this file of "packages loaded from disk + // without having dependencyInfo, but it was unclear when that would happen, + // so this was removed. var isUpToDate = true; var watcher = new watch.Watcher({ @@ -2190,7 +2183,7 @@ _.extend(Package.prototype, { var buildInfoJson = { builtBy: exports.BUILT_BY, - dependencies: { files: {}, directories: {} }, + sliceDependencies: { }, source: options.buildOfPath || undefined }; @@ -2239,12 +2232,9 @@ _.extend(Package.prototype, { path: sliceJsonFile }); - // Merge slice dependencies - // XXX is naive merge sufficient here? - _.extend(buildInfoJson.dependencies.files, - slice.dependencyInfo.files); - _.extend(buildInfoJson.dependencies.directories, - slice.dependencyInfo.directories); + // Save slice dependencies. Keyed by the json path rather than thinking + // too hard about how to encode pair (name, arch). + buildInfoJson.sliceDependencies[sliceJsonFile] = slice.dependencyInfo; // Construct slice metadata var sliceJson = { @@ -2374,10 +2364,12 @@ _.extend(Package.prototype, { // Prep dependencies for serialization by turning regexps into // strings - _.each(buildInfoJson.dependencies.directories, function (d) { - _.each(["include", "exclude"], function (k) { - d[k] = _.map(d[k], function (r) { - return r.source; + _.each(buildInfoJson.sliceDependencies, function (dependencyInfo) { + _.each(dependencyInfo.directories, function (d) { + _.each(["include", "exclude"], function (k) { + d[k] = _.map(d[k], function (r) { + return r.source; + }); }); }); }); From 3ce09d8371bab94412f61a13693dc52702e9b4d3 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Tue, 30 Jul 2013 14:24:32 -0700 Subject: [PATCH 139/175] Factor out dependencyInfo RegExp/string toggling. --- tools/packages.js | 52 +++++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/tools/packages.js b/tools/packages.js index 519c29af91..675e75fe1d 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -1946,13 +1946,8 @@ _.extend(Package.prototype, { // back into regexps var sliceDependencies = buildInfoJson.sliceDependencies || {}; _.each(sliceDependencies, function (dependencyInfo, sliceTag) { - _.each(dependencyInfo.directories, function (d) { - _.each(["include", "exclude"], function (k) { - d[k] = _.map(d[k], function (s) { - return new RegExp(s); - }); - }); - }); + sliceDependencies[sliceTag] = + makeDependencyInfoIntoRegexps(dependencyInfo); }); // If we're supposed to check the dependencies, go ahead and do so @@ -2234,7 +2229,8 @@ _.extend(Package.prototype, { // Save slice dependencies. Keyed by the json path rather than thinking // too hard about how to encode pair (name, arch). - buildInfoJson.sliceDependencies[sliceJsonFile] = slice.dependencyInfo; + buildInfoJson.sliceDependencies[sliceJsonFile] = + makeDependencyInfoSerializable(slice.dependencyInfo); // Construct slice metadata var sliceJson = { @@ -2362,18 +2358,6 @@ _.extend(Package.prototype, { }); }); - // Prep dependencies for serialization by turning regexps into - // strings - _.each(buildInfoJson.sliceDependencies, function (dependencyInfo) { - _.each(dependencyInfo.directories, function (d) { - _.each(["include", "exclude"], function (k) { - d[k] = _.map(d[k], function (r) { - return r.source; - }); - }); - }); - }); - builder.writeJson("unipackage.json", mainJson); builder.writeJson("buildinfo.json", buildInfoJson); builder.complete(); @@ -2384,6 +2368,34 @@ _.extend(Package.prototype, { } }); +// Convert regex to string. +var makeDependencyInfoSerializable = function (dependencyInfo) { + var out = {files: dependencyInfo.files, directories: {}}; + _.each(dependencyInfo.directories, function (d, path) { + var dirInfo = out.directories[path] = {}; + _.each(["include", "exclude"], function (k) { + dirInfo[k] = _.map(d[k], function (r) { + return r.source; + }); + }); + }); + return out; +}; + +// Convert string to regex. +var makeDependencyInfoIntoRegexps = function (dependencyInfo) { + var out = {files: dependencyInfo.files, directories: {}}; + _.each(dependencyInfo.directories, function (d, path) { + var dirInfo = out.directories[path] = {}; + _.each(["include", "exclude"], function (k) { + dirInfo[k] = _.map(d[k], function (s) { + return new RegExp(s); + }); + }); + }); + return out; +}; + var packages = exports; _.extend(exports, { Package: Package From a86a00b11937882f8a3c4f570813505dcf16a661 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Tue, 30 Jul 2013 14:31:08 -0700 Subject: [PATCH 140/175] Error if you try to save an unbuilt package as a unipackage. --- tools/packages.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/packages.js b/tools/packages.js index 675e75fe1d..c52732fa31 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -2159,11 +2159,15 @@ _.extend(Package.prototype, { // then modified. saveAsUnipackage: function (outputPath, options) { var self = this; - var builder = new Builder({ outputPath: outputPath }); + + if (!self.pluginsBuilt || !self.slicesBuilt) + throw new Error("Unbuilt packages cannot be saved"); if (! self.canBeSavedAsUnipackage()) throw new Error("This package can not yet be saved as a unipackage"); + var builder = new Builder({ outputPath: outputPath }); + try { var mainJson = { From 98db63dd1bab50dd48d98091895e34425a226e1d Mon Sep 17 00:00:00 2001 From: David Glasser Date: Tue, 30 Jul 2013 14:44:30 -0700 Subject: [PATCH 141/175] Don't lose pluginDependencyInfo when round-tripping through unipackages. Now, modifying the source of a plugin (or anything that affects it) will recompile all packages that depend on it. --- tools/packages.js | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/tools/packages.js b/tools/packages.js index c52732fa31..db7a320f6a 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -832,11 +832,16 @@ var Package = function (library) { self.pluginInfo = {}; // Plugins in this package. Map from plugin name to JsImage. Present only when - // isBuilt is true. + // pluginsBuilt is true. self.plugins = {}; - // Dependencies for any plugins in this package. Complete only when - // isBuilt is true. + // Full transitive dependencies for all plugins in this package, as well as + // this package's package.js. If any of these dependencies change, not only + // may our plugins need to be rebuilt, but any package that directly uses this + // package needs to be rebuilt in case the change to plugins affected + // compilation. + // + // Complete only when pluginsBuilt is true. // XXX Refactor so that slice and plugin dependencies are handled by // the same mechanism. self.pluginDependencyInfo = { files: {}, directories: {} }; @@ -1950,6 +1955,13 @@ _.extend(Package.prototype, { makeDependencyInfoIntoRegexps(dependencyInfo); }); + self.pluginDependencyInfo = makeDependencyInfoIntoRegexps( + buildInfoJson.pluginDependencies || {}); + // minor hack: sneak the plugin dependency info into the dictionary passed + // to checkUpToDate so that changes to anything compiled into our plugins + // causes us to recompile. + sliceDependencies[''] = self.pluginDependencyInfo; + // If we're supposed to check the dependencies, go ahead and do so if (options.onlyIfUpToDate) { // Do we think we'll generate different contents than the tool that built @@ -2183,6 +2195,8 @@ _.extend(Package.prototype, { var buildInfoJson = { builtBy: exports.BUILT_BY, sliceDependencies: { }, + pluginDependencies: makeDependencyInfoSerializable( + self.pluginDependencyInfo), source: options.buildOfPath || undefined }; @@ -2374,6 +2388,8 @@ _.extend(Package.prototype, { // Convert regex to string. var makeDependencyInfoSerializable = function (dependencyInfo) { + if (!dependencyInfo) + dependencyInfo = { files: {}, directories: {} }; var out = {files: dependencyInfo.files, directories: {}}; _.each(dependencyInfo.directories, function (d, path) { var dirInfo = out.directories[path] = {}; @@ -2388,6 +2404,8 @@ var makeDependencyInfoSerializable = function (dependencyInfo) { // Convert string to regex. var makeDependencyInfoIntoRegexps = function (dependencyInfo) { + if (!dependencyInfo) + dependencyInfo = { files: {}, directories: {} }; var out = {files: dependencyInfo.files, directories: {}}; _.each(dependencyInfo.directories, function (d, path) { var dirInfo = out.directories[path] = {}; From 2a750e64decd923beec7e62492216651c286fe00 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Tue, 30 Jul 2013 15:32:06 -0700 Subject: [PATCH 142/175] build-package-tarballs chooses version based on buildinfo.json. --- scripts/admin/build-package-tarballs.sh | 7 +------ tools/packages.js | 5 +++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/admin/build-package-tarballs.sh b/scripts/admin/build-package-tarballs.sh index 9f33a7dafc..9687b2cfa9 100755 --- a/scripts/admin/build-package-tarballs.sh +++ b/scripts/admin/build-package-tarballs.sh @@ -39,11 +39,6 @@ if [ -e "$TOPDIR/.package_manifest_chunk" ]; then rm "$TOPDIR/.package_manifest_chunk" fi -# The "build version" of the current tools. A change to this should result in a -# change to all package versions; this will only be incremented when it's -# important for all packages to be rebuilt, not on every tools release. -BUILT_BY=$(./meteor --built-by) - FIRST_RUN=true # keep track to place commas correctly cd packages for PACKAGE in * @@ -53,7 +48,7 @@ do echo "," >> "$TOPDIR/.package_manifest_chunk" fi - PACKAGE_VERSION=$(cat <(echo "$BUILT_BY") <(git ls-tree HEAD $PACKAGE) | shasum | cut -f 1 -d " ") # shasum's output looks like: 'SHA -' + PACKAGE_VERSION=$(perl -pe 's/\Q$ENV{TOPDIR}\E//g' $PACKAGE/.build/buildinfo.json | shasum | cut -c 1-10) echo "$PACKAGE version $PACKAGE_VERSION" ROOTDIR="$PACKAGE-${PACKAGE_VERSION}-${PLATFORM}" TARBALL="$OUTDIR/$PACKAGE-${PACKAGE_VERSION}-${PLATFORM}.tar.gz" diff --git a/tools/packages.js b/tools/packages.js index db7a320f6a..4138a449f5 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -2192,6 +2192,11 @@ _.extend(Package.prototype, { plugins: [] }; + // Note: The contents of buildInfoJson (with the root directory of the + // Meteor checkout naively deleted) gets its SHA taken to determine the + // built package's warehouse version. So it should not contain + // platform-dependent data and should contain all sources of change to the + // unipackage's output. See scripts/admin/build-package-tarballs.sh. var buildInfoJson = { builtBy: exports.BUILT_BY, sliceDependencies: { }, From b5c907b03435a8fdb38c17b8cd12112276620952 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Tue, 30 Jul 2013 15:37:06 -0700 Subject: [PATCH 143/175] accounts-twitter doesn't have an HTML file any more --- packages/accounts-twitter/package.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/accounts-twitter/package.js b/packages/accounts-twitter/package.js index c878e852c0..f9b2bb43bd 100644 --- a/packages/accounts-twitter/package.js +++ b/packages/accounts-twitter/package.js @@ -11,7 +11,6 @@ Package.on_use(function(api) { api.use('twitter', ['client', 'server']); api.use('http', ['client', 'server']); - api.use('templating', 'client'); api.add_files('twitter_login_button.css', 'client'); From c9aa2ac16bd2f8c23328080fc8558d2bb650ecd7 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Tue, 30 Jul 2013 19:17:55 -0700 Subject: [PATCH 144/175] release process: export env var used by subcommand --- scripts/admin/build-package-tarballs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/admin/build-package-tarballs.sh b/scripts/admin/build-package-tarballs.sh index 9687b2cfa9..eac918e34b 100755 --- a/scripts/admin/build-package-tarballs.sh +++ b/scripts/admin/build-package-tarballs.sh @@ -16,7 +16,7 @@ set -u # cd to top level dir cd `dirname $0` cd ../.. -TOPDIR=$(pwd) +export TOPDIR=$(pwd) OUTDIR="$TOPDIR/dist/packages" mkdir -p $OUTDIR From 9fba51960c79e5c84b8a5eded5cfcaa9ccabfcb2 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Tue, 30 Jul 2013 19:43:33 -0700 Subject: [PATCH 145/175] Clean up arch name in buildinfo when generating hashes. Publish release manifests separately by platform and check they are the same in publish-release. --- scripts/admin/build-package-tarballs.sh | 2 +- scripts/admin/build-release.sh | 4 +- .../publish-release/server/publish-release.js | 40 ++++++++++++------- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/scripts/admin/build-package-tarballs.sh b/scripts/admin/build-package-tarballs.sh index eac918e34b..39c5b4a490 100755 --- a/scripts/admin/build-package-tarballs.sh +++ b/scripts/admin/build-package-tarballs.sh @@ -48,7 +48,7 @@ do echo "," >> "$TOPDIR/.package_manifest_chunk" fi - PACKAGE_VERSION=$(perl -pe 's/\Q$ENV{TOPDIR}\E//g' $PACKAGE/.build/buildinfo.json | shasum | cut -c 1-10) + PACKAGE_VERSION=$(perl -pe 's/\Q$ENV{TOPDIR}\E//g; s/os\..*\.json/os.json/g' $PACKAGE/.build/buildinfo.json | shasum | cut -c 1-10) echo "$PACKAGE version $PACKAGE_VERSION" ROOTDIR="$PACKAGE-${PACKAGE_VERSION}-${PLATFORM}" TARBALL="$OUTDIR/$PACKAGE-${PACKAGE_VERSION}-${PLATFORM}.tar.gz" diff --git a/scripts/admin/build-release.sh b/scripts/admin/build-release.sh index 2fc05139b8..3f0ce4c95b 100755 --- a/scripts/admin/build-release.sh +++ b/scripts/admin/build-release.sh @@ -40,7 +40,7 @@ MANIFEST_PACKAGE_CHUNK=$(cat "$TOPDIR/.package_manifest_chunk") rm "$TOPDIR/.tools_version" rm "$TOPDIR/.package_manifest_chunk" -cat > "$OUTDIR/release.json" < "$OUTDIR/release.json-$PLATFORM" < Date: Tue, 30 Jul 2013 19:48:34 -0700 Subject: [PATCH 146/175] Remove overzealous ||{}. --- tools/packages.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/packages.js b/tools/packages.js index 4138a449f5..7ae44142d4 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -1956,7 +1956,7 @@ _.extend(Package.prototype, { }); self.pluginDependencyInfo = makeDependencyInfoIntoRegexps( - buildInfoJson.pluginDependencies || {}); + buildInfoJson.pluginDependencies); // minor hack: sneak the plugin dependency info into the dictionary passed // to checkUpToDate so that changes to anything compiled into our plugins // causes us to recompile. From 5d65352c476739edff9b538103038d64d07a8852 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Tue, 30 Jul 2013 19:58:41 -0700 Subject: [PATCH 147/175] publish-release: S3 gives us a Buffer. --- scripts/admin/publish-release/server/publish-release.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/admin/publish-release/server/publish-release.js b/scripts/admin/publish-release/server/publish-release.js index 54bf8334ef..d4db8cbdfa 100644 --- a/scripts/admin/publish-release/server/publish-release.js +++ b/scripts/admin/publish-release/server/publish-release.js @@ -182,7 +182,7 @@ var publishManifest = function(s3, manifestText, release) { var opts = { BucketName: "com.meteor.warehouse", ObjectName: destKey, - ContentLength: Buffer.byteLength(manifestText), + ContentLength: manifestText.length, Body: manifestText, Acl: "public-read" }; From ae965f1aa15cc6befb2ba07c116f9844d679d0ce Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Thu, 25 Jul 2013 14:54:15 -0700 Subject: [PATCH 148/175] Make an environment variable to control where we bind. This only affects the bundled app (eg, what you'd run in production). The dev mode runner has a bunch of '127.0.0.1' addresses hardcoded, and will need more work. --- packages/webapp/webapp_server.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/webapp/webapp_server.js b/packages/webapp/webapp_server.js index 6cf8998cb1..651cd66f95 100644 --- a/packages/webapp/webapp_server.js +++ b/packages/webapp/webapp_server.js @@ -197,6 +197,9 @@ var runWebAppServer = function () { if (process.env.PORT && !_.has(deployConfig.boot.bind, 'localPort')) { deployConfig.boot.bind.localPort = parseInt(process.env.PORT); } + if (process.env.BIND_IP && !_.has(deployConfig.boot.bind, 'localIp')) { + deployConfig.boot.bind.localIp = process.env.BIND_IP; + } copyEnvVarToDeployConfig(deployConfig, "MONGO_URL", "mongo-livedata", "url"); // webserver @@ -437,7 +440,9 @@ var runWebAppServer = function () { // only start listening after all the startup code has run. var bind = deployConfig.boot.bind; - httpServer.listen(bind.localPort || 0, Meteor.bindEnvironment(function() { + var localPort = bind.localPort || 0; + var localIp = bind.localIp || '0.0.0.0'; + httpServer.listen(localPort, localIp, Meteor.bindEnvironment(function() { if (argv.keepalive || true) console.log("LISTENING"); // must match run.js var port = httpServer.address().port; From 097b26151872db16261c6ece19bc3129851c03cf Mon Sep 17 00:00:00 2001 From: David Glasser Date: Tue, 30 Jul 2013 22:48:42 -0700 Subject: [PATCH 149/175] Don't need to merge pluginDependencies into sliceDependencies for checkUpToDate. In fact, each slice's dependencies already contains pluginDependencies (see after the linker.prelink call in Slice.build, and recall that a package's plugins are always active in itself). --- tools/packages.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tools/packages.js b/tools/packages.js index 7ae44142d4..9acb349047 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -859,7 +859,7 @@ var Package = function (library) { // means that doesn't create it in a build state to start with) you // will need to call build() before you can use it. We break down // the two phases of the build process, plugin building and - // building, into two flags. + // slice building, into two flags. self.pluginsBuilt = false; self.slicesBuilt = false; }; @@ -1957,10 +1957,6 @@ _.extend(Package.prototype, { self.pluginDependencyInfo = makeDependencyInfoIntoRegexps( buildInfoJson.pluginDependencies); - // minor hack: sneak the plugin dependency info into the dictionary passed - // to checkUpToDate so that changes to anything compiled into our plugins - // causes us to recompile. - sliceDependencies[''] = self.pluginDependencyInfo; // If we're supposed to check the dependencies, go ahead and do so if (options.onlyIfUpToDate) { From 8202e86a6ae9e0cc79b0aa41a82aa44d054ec89f Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Wed, 31 Jul 2013 19:25:44 -0700 Subject: [PATCH 150/175] Use blank client targets for all "traditional" programs that need it, not just the first. --- tools/bundler.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/bundler.js b/tools/bundler.js index b3d4b11216..6c03b1c2ed 100644 --- a/tools/bundler.js +++ b/tools/bundler.js @@ -1713,6 +1713,8 @@ exports.bundle = function (appDir, outputPath, options) { if (! blankClientTarget) { clientTarget = blankClientTarget = targets._blank = makeBlankClientTarget(); + } else { + clientTarget = blankClientTarget; } } else { clientTarget = targets[p.client]; From 5b76eb7a85f4fb815d76ec7df4ff0799aa7c54ef Mon Sep 17 00:00:00 2001 From: David Glasser Date: Tue, 30 Jul 2013 23:40:07 -0700 Subject: [PATCH 151/175] watcher rewrite: Add WatchSet and readDirectory. --- tools/watch.js | 193 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) diff --git a/tools/watch.js b/tools/watch.js index 2af72c0a09..4a90e73d70 100644 --- a/tools/watch.js +++ b/tools/watch.js @@ -341,3 +341,196 @@ _.extend(Watcher.prototype, { }); exports.Watcher = Watcher; + +var WatchSet = function () { + var self = this; + + // Set this to true if any Watcher built on this WatchSet must immediately + // fire (eg, if this WatchSet was given two different sha1 for the same file). + self.alwaysFire = false; + + // Map from the absolute path to a file, to a sha1 hash, or null if the file + // should not exist. A Watcher created from this set fires when the file + // changes from that sha, or is deleted (if non-null) or created (if null). + self.files = {}; + + // Array of object with keys: + // - 'absPath': absolute path to a directory + // - 'include': array of RegExps + // - 'exclude': array of RegExps + // - 'contents': array of strings + // + // This represents the assertion that 'absPath' is a directory and that + // 'contents' is its immediate contents, as filtered by the regular + // expressions. Entries in 'contents' are file and subdirectory names; + // directory names end with '/'. 'contents' is sorted. An entry is in + // 'contents' if its value (including the slash, for directories) matches at + // least one regular expression in 'include' and no regular expressions in + // 'exclude'. + // + // There is no recursion here: files contained in subdirectories never appear. + // + // A directory may have multiple entries (presumably with different + // include/exclude filters). + self.directories = []; +}; + +_.extend(WatchSet.prototype, { + addFile: function (filePath, hash) { + var self = this; + // No need to update if this is in always-fire mode already. + if (self.alwaysFire) + return; + if (_.has(self.files, filePath)) { + // Redundant? + if (self.files[filePath] === hash) + return; + // Nope, inconsistent. + self.alwaysFire = true; + return; + } + self.files[filePath] = hash; + }, + + // Takes options absPath, include, exclude, and contents, as described + // above. contents does not need to be pre-sorted. + addDirectory: function (options) { + var self = this; + if (self.alwaysFire) + return; + if (_.isEmpty(options.include)) + return; + var contents = _.clone(options.contents || []); + contents.sort(); + + self.directories.push({ + absPath: options.absPath, + include: options.include, + exclude: options.exclude, + contents: contents + }); + }, + + // Merges another WatchSet into this one. This one will now fire if either + // WatchSet would have fired. + merge: function (other) { + var self = this; + if (self.alwaysFire) + return; + if (other.alwaysFire) { + self.alwaysFire = true; + return; + } + _.each(other.files, function (hash, name) { + self.addFile(name, hash); + }); + _.each(other.directories, function (dir) { + // XXX this doesn't deep-clone the directory, but I think these objects + // are never mutated + self.directories.push(dir); + }); + }, + + toJSON: function () { + var self = this; + if (self.alwaysFire) + return {alwaysFire: true}; + var ret = {files: self.files}; + + var reToJSON = function (r) { + var options = ''; + if (r.ignoreCase) + options += 'i'; + if (r.multiline) + options += 'm'; + if (r.global) + options += 'g'; + if (options) + return {$regex: r.source, $options: options}; + return r.source; + }; + + ret.directories = _.map(self.directories, function (d) { + return { + absPath: d.absPath, + include: _.map(d.include, reToJSON), + exclude: _.map(d.exclude, reToJSON), + contents: d.contents + }; + }); + + return ret; + } +}); + +WatchSet.fromJSON = function (json) { + var set = new WatchSet; + if (json.alwaysFire) { + set.alwaysFire = true; + return set; + } + + set.files = _.clone(json.files); + + var reFromJSON = function (j) { + if (j.$regex) + return new RegExp(j.$regex, j.$options); + return new RegExp(j); + }; + + set.directories = _.map(json.directories, function (d) { + return { + absPath: d.absPath, + include: _.map(d.include, reFromJSON), + exclude: _.map(d.exclude, reFromJSON), + contents: d.contents + }; + }); + + return set; +}; + +exports.WatchSet = WatchSet; + +exports.readDirectory = function (options) { + // Read the directory. + try { + var contents = fs.readdirSync(options.absPath); + } catch (e) { + // If the path is not a directory, return null; let other errors through. + if (e && (e.code === 'ENOENT' || e.code === 'ENOTDIR')) + return null; + throw e; + } + + // Add slashes to the end of directories. + var contentsWithSlashes = []; + _.each(contents, function (entry) { + try { + // We do stat instead of lstat here, so that we treat symlinks to + // directories just like directories themselves. + // 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 (stats.isDirectory()) + entry += '/'; + contentsWithSlashes.push(entry); + }); + + // Filter based on regexps. + var filtered = _.filter(contentsWithSlashes, function (entry) { + return _.any(options.include, function (re) { + return re.test(entry); + }) && !_.any(options.exclude, function (re) { + return re.test(entry); + }); + }); + + // Sort it! + filtered.sort(); + return filtered; +}; From 4dc5aaf683951e4ce6821170d03bdf56b98cfaab Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 31 Jul 2013 00:21:13 -0700 Subject: [PATCH 152/175] rewrite Watcher --- tools/watch.js | 502 +++++++++++++++++++++++-------------------------- 1 file changed, 231 insertions(+), 271 deletions(-) diff --git a/tools/watch.js b/tools/watch.js index 4a90e73d70..f07491c2de 100644 --- a/tools/watch.js +++ b/tools/watch.js @@ -2,6 +2,9 @@ var fs = require("fs"); var path = require("path"); var _ = require('underscore'); +// XXX XXX redo this doc + + // Watch for changes to a set of files, and the first time that any of // the files change, call a user-provided callback. (If you want a // second callback, you'll need to create a second Watcher.) @@ -70,277 +73,6 @@ var _ = require('underscore'); // received one argument, the absolute path to a changed or removed // file (potentially not the only one that changed or was removed) // -var Watcher = function (options) { - var self = this; - - // Map from the absolute path to a file, to a sha1 hash. Fire when - // the file changes from that sha. - self.files = options.files || {}; - - // Map from an absolute path to a directory, to an object with keys - // 'include' and 'exclude', both lists of regular expressions. Fire - // when a file is added to that directory whose name matches at - // least one regular expression in 'include' and no regular - // expressions in 'exclude'. Subdirectories are included - // recursively, but not subdirectories that match 'exclude'. The - // most specific rule wins, so you can change the parameters in - // effect in subdirectories simply by specifying additional rules. - self.directories = options.directories || {}; - - // Function to call when a change is detected according to one of - // the above. - self.onChange = options.onChange; - if (! self.onChange) - throw new Error("onChange option is required"); - - // self.directories in a different form. It's an array of objects, - // each with keys 'dir', 'include', 'options', where path is - // guaranteed to not contain a trailing slash (unless it is the root - // directory) and the objects are sorted from longest path to - // shortest (that is, most specific rule to least specific.) - self.rules = _.map(self.directories, function (options, dir) { - return { - dir: path.resolve(dir), - include: options.include || [], - exclude: options.exclude || [] - }; - }); - self.rules = self.rules.sort(function (a, b) { - return a.dir.length < b.dir.length ? 1 : -1; - }); - - self.stopped = false; - self.fileWatches = []; // array of paths - self.directoryWatches = {}; // map from path to watch object - - // We track all of the currently active timers so that we can cancel - // them at stop() time. This stops the process from hanging at - // shutdown until all of the timers have fired.. An alternate - // approach would be to use the unref() timer handle method present - // in modern node. - var nextTimerId = 1; - self.timers = {}; // map from arbitrary number (nextTimerId) to timer handle - - self._startFileWatches(); - _.each(self.rules, function (rule) { - self._watchDirectory(rule.dir); - }); -}; - -_.extend(Watcher.prototype, { - _checkFileChanged: function (absPath) { - var self = this; - - if (! fs.existsSync(absPath)) - return true; - - var crypto = require('crypto'); - var hasher = crypto.createHash('sha1'); - hasher.update(fs.readFileSync(absPath)); - var hash = hasher.digest('hex'); - - return (self.files[absPath] !== hash); - }, - - _startFileWatches: function () { - var self = this; - - // Set up a watch for each file - _.each(self.files, function (hash, absPath) { - // Intentionally not using fs.watch since it doesn't play well with - // vim (https://github.com/joyent/node/issues/3172) - // Note that we poll very frequently (500 ms) - fs.watchFile(absPath, {interval: 500}, function () { - // Fire only if the contents of the file actually changed (eg, - // maybe just its atime changed) - if (self._checkFileChanged(absPath)) - self._fire(absPath); - }); - self.fileWatches.push(absPath); - - // Check for the case where by the time we created the watch, - // the file had already changed from the sha we were provided. - if (self._checkFileChanged(absPath)) - self._fire(absPath); - }); - - // One second later, check the files again, because fs.watchFile - // is actually implemented by polling the file's mtime, and some - // filesystems (OSX HFS+) only keep mtimes to a resolution of one - // second. This handles the case where we check the hash and set - // up the watch, but then the file change before the clock rolls - // over to the next second, and fs.watchFile doesn't notice and - // doesn't call us back. #WorkAroundLowPrecisionMtimes - var timerId = self.nextTimerId++; - self.timers[timerId] = setTimeout(function () { - delete self.timers[timerId]; - if (self.stopped) - return; - - _.each(self.files, function (hash, absPath) { - if (self._checkFileChanged(absPath)) - self._fire(absPath); - }); - }, 1000); - }, - - // Pass true for `include` to include everything (and process only - // excludes) - _matches: function (filename, include, exclude) { - var self = this; - - if (include === true) - include = [/.?/]; - for (var i = 0; i < include.length; i++) - if (include[i].test(filename)) - break; - if (i === include.length) { - return false; // didn't match any includes - } - - for (var i = 0; i < exclude.length; i++) { - if (exclude[i].test(filename)) { - return false; // matched an exclude - } - } - - // Matched an include and didn't match any excludes - return true; - }, - - _watchDirectory: function (absPath) { - var self = this; - - if (absPath in self.directoryWatches) - // Already being taken care of - return; - - // Determine the options that apply to this directory by finding - // the most specific rule. - absPath = path.resolve(absPath); // ensure no trailing slash - for (var i = 0; i < self.rules.length; i++) { - var rule = self.rules[i]; - if (absPath.length >= rule.dir.length && - absPath.substr(0, rule.dir.length) === rule.dir) - break; // found a match - rule = null; - } - if (! rule) - // Huh, doesn't appear that we're supposed to be watching this - // directory. - return; - - var contents = []; - var scanDirectory = function (isDoubleCheck) { - if (self.stopped) - return; - - if (! fs.existsSync(absPath)) { - // Directory was removed. Stop watching. - var watch = self.directoryWatches[absPath]; - watch && watch.close(); - delete self.directoryWatches[absPath]; - return; - } - - // Find previously unknown files and subdirectories. (We don't - // care about removed subdirectories because the logic - // immediately above handles them, and we don't care about - // removed files because the ones we care about will already - // have file watches on them.) - var newContents = fs.readdirSync(absPath); - var added = _.difference(newContents, contents); - contents = newContents; - - // Look at each newly added item - _.each(added, function (addedItem) { - var addedPath = path.join(absPath, addedItem); - - // Is it a directory? - try { - var stats = fs.lstatSync(addedPath); - } catch (e) { - // Can't be found? That's weird. Ignore. - return; - } - var isDirectory = stats.isDirectory(); - - // Does it match the rule? - if (! self._matches(addedItem, - isDirectory ? true : rule.include, - rule.exclude)) - return; // No - - if (! isDirectory) { - if (! (addedPath in self.files)) - // Found a newly added file that we care about. - self._fire(absPath); - } else { - // Found a subdirectory that we care to monitor. - self._watchDirectory(addedPath); - } - }); - - if (! isDoubleCheck) { - // Whenever a directory changes, scan it soon as we notice, - // but then scan it again one secord later just to make sure - // that we haven't missed any changes. See commentary at - // #WorkAroundLowPrecisionMtimes - var timerId = self.nextTimerId++; - self.timers[timerId] = setTimeout(function () { - delete self.timers[timerId]; - if (! self.stopped) - scanDirectory(true); - }, 1000); - } - }; - - // fs.watchFile doesn't work for directories (as tested on ubuntu) - // Notice that we poll very frequently (500 ms) - try { - self.directoryWatches[absPath] = - fs.watch(absPath, {interval: 500}, scanDirectory); - scanDirectory(); - } catch (e) { - // Can happen if the directory doesn't exist, say because a - // nonexistent path was included in self.directories - } - }, - - _fire: function (changedFile) { - var self = this; - - if (self.stopped) - return; - - self.stop(); - self.onChange(changedFile); - }, - - stop: function () { - var self = this; - self.stopped = true; - - // Clean up timers - _.each(self.timers, function (timer, id) { - clearTimeout(timer); - }); - - // Clean up file watches - _.each(self.fileWatches, function (absPath) { - fs.unwatchFile(absPath); - }); - self.fileWatches = []; - - // Clean up directory watches - _.each(self.directoryWatches, function (watch) { - watch.close(); - }); - self.directoryWatches = {}; - } -}); - -exports.Watcher = Watcher; var WatchSet = function () { var self = this; @@ -534,3 +266,231 @@ exports.readDirectory = function (options) { filtered.sort(); return filtered; }; + +var Watcher = function (options) { + var self = this; + + // The set to watch. + self.watchSet = options.watchSet; + if (! self.watchSet) + throw new Error("watchSet option is required"); + + // Function to call when a change is detected according to one of + // the above. + self.onChange = options.onChange; + if (! self.onChange) + throw new Error("onChange option is required"); + + self.stopped = false; + + self.fileWatches = []; // array of paths + self.directoryWatches = {}; // map from path to watch object + + // We track all of the currently active timers so that we can cancel + // them at stop() time. This stops the process from hanging at + // shutdown until all of the timers have fired. An alternate + // approach would be to use the unref() timer handle method present + // in modern node. + var nextTimerId = 1; + self.timers = {}; // map from arbitrary number (nextTimerId) to timer handle + + // Were we given an inconsistent WatchSet? Fire now and be done with it. + if (self.watchSet.alwaysFire) { + self._fire(); + return; + } + + self._startFileWatches(); + self._startDirectoryWatches(); +}; + +_.extend(Watcher.prototype, { + _fireIfFileChanged: function (absPath) { + var self = this; + + if (self.stopped) + return true; + + var oldHash = self.watchSet.files[absPath]; + + if (oldHash === undefined) + throw new Error("Checking unknown file " + absPath); + + try { + var contents = fs.readFileSync(absPath); + } catch (e) { + // Rethrow most errors. + if (!e || (e.code !== 'ENOENT' && e.code !== 'EISDIR')) + throw e; + // File does not exist (or is a directory). + // Is this what we expected? + if (oldHash === null) + return false; + // Nope, not what we expected. + self._fire(); + return true; + } + + // File exists! Is that what we expected? + if (oldHash === null) { + self._fire(); + return true; + } + + var crypto = require('crypto'); + var hasher = crypto.createHash('sha1'); + hasher.update(contents); + var newHash = hasher.digest('hex'); + + // Unchanged? + if (newHash === oldHash) + return false; + + self._fire(); + return true; + }, + + _fireIfDirectoryChanged: function (info, isDoubleCheck) { + var self = this; + + if (self.stopped) + return true; + + var newContents = exports.readDirectory({ + absPath: info.absPath, + include: info.include, + exclude: info.exclude + }); + + // If newContents is null (no directory) or the directory has changed, fire. + if (!_.isEqual(info.contents, newContents)) { + self._fire(); + return true; + } + + if (!isDoubleCheck) { + // Whenever a directory changes, scan it soon as we notice, + // but then scan it again one secord later just to make sure + // that we haven't missed any changes. See commentary at + // #WorkAroundLowPrecisionMtimes + // XXX not sure why this uses a different strategy than files + var timerId = self.nextTimerId++; + self.timers[timerId] = setTimeout(function () { + delete self.timers[timerId]; + if (! self.stopped) + self._fireIfDirectoryChanged(info, true); + }, 1000); + } + + return false; + }, + + _startFileWatches: function () { + var self = this; + + // Set up a watch for each file + _.each(self.watchSet.files, function (hash, absPath) { + if (self.stopped) + return; + + // Check for the case where by the time we created the watch, + // the file had already changed from the sha we were provided. + if (self._fireIfFileChanged(absPath)) + return; + + // Intentionally not using fs.watch since it doesn't play well with + // vim (https://github.com/joyent/node/issues/3172) + // Note that we poll very frequently (500 ms) + fs.watchFile(absPath, {interval: 500}, function () { + // Fire only if the contents of the file actually changed (eg, + // maybe just its atime changed) + self._fireIfFileChanged(absPath); + }); + self.fileWatches.push(absPath); + }); + + if (self.stopped) + return; + + // One second later, check the files again, because fs.watchFile + // is actually implemented by polling the file's mtime, and some + // filesystems (OSX HFS+) only keep mtimes to a resolution of one + // second. This handles the case where we check the hash and set + // up the watch, but then the file change before the clock rolls + // over to the next second, and fs.watchFile doesn't notice and + // doesn't call us back. #WorkAroundLowPrecisionMtimes + var timerId = self.nextTimerId++; + self.timers[timerId] = setTimeout(function () { + delete self.timers[timerId]; + _.each(self.watchSet.files, function (hash, absPath) { + self._fireIfFileChanged(absPath); + }); + }, 1000); + }, + + _startDirectoryWatches: function () { + var self = this; + + // Set up a watch for each directory + _.each(self.watchSet.directories, function (info) { + if (self.stopped) + return; + + // Check for the case where by the time we created the watch, the + // directory has already changed. + if (self._fireIfDirectoryChanged(info)) + return; + + // fs.watchFile doesn't work for directories (as tested on ubuntu) + // Notice that we poll very frequently (500 ms) + try { + self.directoryWatches.push( + fs.watch(info.absPath, {interval: 500}, function () { + self._fireIfDirectoryChanged(info); + }) + ); + } catch (e) { + // Can happen if the directory doesn't exist, in which case we should + // fire. + if (e && e.code === "ENOENT") { + self._fire(); + return; + } + throw e; + } + }); + }, + + _fire: function () { + var self = this; + + if (self.stopped) + return; + + self.stop(); + self.onChange(); + }, + + stop: function () { + var self = this; + self.stopped = true; + + // Clean up timers + _.each(self.timers, function (timer, id) { + clearTimeout(timer); + }); + self.timers = {}; + + // Clean up file watches + _.each(self.fileWatches, function (absPath) { + fs.unwatchFile(absPath); + }); + self.fileWatches = []; + + // Clean up directory watches + _.each(self.directoryWatches, function (watch) { + watch.close(); + }); + self.directoryWatches = []; + } +}); From 5ac411652ab2d1d7c0552a3627ea82c1c6f13a4e Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 31 Jul 2013 00:24:37 -0700 Subject: [PATCH 153/175] twiddle exports --- tools/watch.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tools/watch.js b/tools/watch.js index f07491c2de..0bf0879e04 100644 --- a/tools/watch.js +++ b/tools/watch.js @@ -222,9 +222,7 @@ WatchSet.fromJSON = function (json) { return set; }; -exports.WatchSet = WatchSet; - -exports.readDirectory = function (options) { +var readDirectory = function (options) { // Read the directory. try { var contents = fs.readdirSync(options.absPath); @@ -494,3 +492,9 @@ _.extend(Watcher.prototype, { self.directoryWatches = []; } }); + +_.extend(exports, { + WatchSet: WatchSet, + Watcher: Watcher, + readDirectory: readDirectory +}); From ad5b20990eac0171cdba5be78b655b211ec2da6e Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 31 Jul 2013 13:51:43 -0700 Subject: [PATCH 154/175] get watch-test to pass --- tools/tests/test_watch.js | 243 +++++++++++++++++--------------------- tools/watch.js | 2 +- 2 files changed, 110 insertions(+), 135 deletions(-) diff --git a/tools/tests/test_watch.js b/tools/tests/test_watch.js index 19f042d2b3..b30d6dd8c4 100644 --- a/tools/tests/test_watch.js +++ b/tools/tests/test_watch.js @@ -41,10 +41,11 @@ var go = function (options) { } fired = false; - var files = {}; + var watchSet = new watch.WatchSet(); + _.each(options.files, function (value, file) { file = path.join(tmp, file); - if (typeof value !== "string") { + if (value !== null && typeof value !== "string") { if (fs.existsSync(file)) { var hash = crypto.createHash('sha1'); hash.update(fs.readFileSync(file)); @@ -53,18 +54,23 @@ var go = function (options) { value = 'dummyhash'; } } - files[file] = value; + watchSet.addFile(file, value); }); - var directories = {}; - _.each(options.directories, function (options, dir) { - dir = path.join(tmp, dir); - directories[dir] = options; + _.each(options.directories, function (dir) { + // don't mutate options.directories, since we may reuse it with a no-arg + // go() call + var realDir = { + absPath: path.join(tmp, dir.absPath), + include: dir.include, + exclude: dir.exclude + }; + realDir.contents = dir.contents || watch.readDirectory(realDir); + watchSet.addDirectory(realDir); }); theWatcher = new watch.Watcher({ - files: files, - directories: directories, + watchSet: watchSet, onChange: function () { fired = true; if (firedFuture) @@ -97,11 +103,7 @@ var waitForTopOfSecond = function () { if (msPastSecond < 100) { return; } - var f = new Future; - setTimeout(function () { - f.return(); - }, 25); - f.wait(); + delay(25); } }; @@ -139,15 +141,30 @@ Fiber(function () { files: { '/aa/b': true, '/aa/c': true } }); assert(fires()); // look like /aa/c was removed + go({ + files: { '/aa/b': true, '/aa/c': null } + }); + assert(!fires()); // assert that /aa/c doesn't exist console.log("... directories"); go({ files: {'/aa/b': true }, - directories: {'/aa': { - include: [/yes/, /maybe/, /aa/], - exclude: [/not/, /never/] - }} + directories: [ + {absPath: '/aa', + include: [/yes/, /maybe/, /aa/], + exclude: [/not/, /never/], + contents: [] + }, + {absPath: '/bb', + include: [/.?/], + contents: [] + } + ] }); + assert(fires()); // because /bb doesn't exist + touchDir('/bb'); + go(); + assert(!fires()); touchFile('/aa/c'); assert(!fires()); touchFile('/aa/maybe-not'); @@ -158,9 +175,10 @@ Fiber(function () { assert(!fires()); touchFile('/aa/yes-for-sure'); assert(fires()); + go(); touchFile('/aa/nope'); - assert(fires()); // because yes-for-sure isn't in the file list + assert(fires()); // because yes-for-sure isn't in 'contents' remove('/aa/yes-for-sure'); go(); assert(!fires()); @@ -169,11 +187,18 @@ Fiber(function () { go(); assert(fires()); // maybe-this-time is still there go({ - files: {'/aa/b': true, '/aa/maybe-this-time': true }, - directories: {'/aa': { - include: [/yes/, /maybe/, /aa/], - exclude: [/not/, /never/] - }} + files: {'/aa/b': true}, + directories: [ + {absPath: '/aa', + include: [/yes/, /maybe/, /aa/], + exclude: [/not/, /never/], + contents: ['maybe-this-time'] + }, + {absPath: '/bb', + include: [/.?/], + contents: [] + } + ] }); go(); assert(!fires()); // maybe-this-time is now in the expected file list @@ -183,84 +208,62 @@ Fiber(function () { remove('/aa/maybe-this-time'); go(); assert(fires()); // maybe-this-time is missing - - console.log("... recursive directories"); - touchFile('/aa/b'); + touchFile('/aa/maybe-this-time'); + touchDir('/aa/yes-i-said-yes-i-will-yes'); go({ - files: {'/aa/b': true }, - directories: {'/aa': { - include: [/yes/, /maybe/, /aa/], - exclude: [/not/, /never/] - }} - }); - touchDir('/aa/yess'); - assert(!fires()); - remove('/aa/yess'); - assert(!fires()); - touchFile('/aa/yess/kitten'); - assert(!fires()); - touchFile('/aa/yess/maybe'); - assert(fires()); - remove('/aa/yess'); - go(); - touchFile('/aa/whatever/kitten'); - assert(!fires()); - touchFile('/aa/whatever/maybe'); - assert(fires()); - - remove('/aa/whatever'); - go(); - touchDir('/aa/i/love/subdirectories'); - assert(!fires()); - touchFile('/aa/i/love/subdirectories/yessir'); - assert(fires()); - remove('/aa/i/love/subdirectories/yessir'); - go(); - touchFile('/aa/i/love/subdirectories/every/day'); - assert(!fires()); - remove('/aa/i/love/subdirectories'); - assert(!fires()); - touchFile('/aa/i/love/not/nothing/yes'); - assert(!fires()); - touchFile('/aa/i/love/not/nothing/maybe/yes'); - assert(!fires()); - touchFile('/aa/i/love/maybe'); - assert(fires()); - remove('/aa/i'); - remove('/aa/whatever'); - - remove('/aa'); - touchFile('/aa/b'); - console.log("... nested directories"); - go({ - files: {'/aa/b': true }, - directories: { - '/aa': { - include: [/yes/, /maybe/, /aa/], - exclude: [/not/, /never/] - }, - '/aa/x': { - include: [/kitten/], - exclude: [/puppy/] + directories: [ + {absPath: '/aa', + include: [/yes/, /maybe/, /aa/], + exclude: [/not/, /never/], + contents: ['maybe-this-time'] } - } + ] + }); + assert(fires()); // yes-i-said-yes-i-will-yes/ is missing + go({ + directories: [ + {absPath: '/aa', + include: [/yes/, /maybe/, /aa/], + exclude: [/not/, /never/], + contents: ['maybe-this-time', 'yes-i-said-yes-i-will-yes'] + } + ] + }); + assert(fires()); // yes-i-said-yes-i-will-yes is a dir, not a file + go({ + directories: [ + {absPath: '/aa', + include: [/yes/, /maybe/, /aa/], + exclude: [/not/, /never/], + contents: ['maybe-this-time', 'yes-i-said-yes-i-will-yes/'] + } + ] }); - touchFile('/aa/kitten'); assert(!fires()); - touchFile('/aa/maybe.puppy'); - assert(fires()); - remove('/aa/maybe.puppy'); - go(); - touchFile('/aa/x/kitten'); - assert(fires()); - remove('/aa/x/kitten'); - go(); - touchFile('/aa/x/yes'); + // same directory, different filters + go({ + directories: [ + // dirs + {absPath: '/aa', + include: [/\/$/], + contents: ['yes-i-said-yes-i-will-yes/'] + }, + // files + {absPath: '/aa', + include: [/.?/], + exclude: [/\/$/], + contents: ['b', 'c', 'maybe-not', 'maybe-this-time', 'never', + 'never-yes', 'nope'] + } + ] + }); assert(!fires()); - touchFile('/aa/x/kitten.not'); + touchFile('/aa/bla'); assert(fires()); - remove('/aa'); + // nb: these are supposed to verify that the "wait a second and try again" + // logic works, but I couldn't get them to fail even when I turned that logic + // off. console.log("... rapid changes to file"); touchFile('/aa/x'); waitForTopOfSecond(); @@ -268,52 +271,24 @@ Fiber(function () { files: {'/aa/x': true }}); touchFile('/aa/x'); assert(fires(2000)); + go({ - directories: { - '/aa': { - include: [/yes/, /maybe/, /aa/], - exclude: [/not/, /never/] + directories: [ + {absPath: '/aa', + include: [/yes/, /maybe/, /aa/], + exclude: [/not/, /never/] } - } + ] }); + assert(!fires()); + waitForTopOfSecond(); - touchFile('/aa/thing1/whatever'); - delay(100); - touchFile('/aa/thing2/yes'); + touchFile('/aa/wtf'); + delay(600); + touchFile('/aa/yes-indeed'); assert(fires(2000)); remove('/aa'); - console.log("... rapid changes to directory"); - touchDir('/aa'); - waitForTopOfSecond(); - go({ - directories: {'/aa': { - include: [/yes/, /maybe/, /aa/], - exclude: [/not/, /never/] - }} - }); - touchFile('/aa/x/yes'); - assert(fires(2000)); - remove('/aa/x'); - - waitForTopOfSecond(); - go(); - delay(600); - touchFile('/aa/x/not'); - delay(600); - touchFile('/aa/x/yes'); - assert(fires(2000)); - remove('/aa/x'); - - touchDir('/aa/x'); - go(); - delay(2000); - waitForTopOfSecond(); - touchFile('/aa/x/no'); - delay(600); - touchFile('/aa/x/yes'); - assert(fires(2000)); - console.log("Watcher test passed"); theWatcher.stop(); diff --git a/tools/watch.js b/tools/watch.js index 0bf0879e04..c6446c14f5 100644 --- a/tools/watch.js +++ b/tools/watch.js @@ -282,7 +282,7 @@ var Watcher = function (options) { self.stopped = false; self.fileWatches = []; // array of paths - self.directoryWatches = {}; // map from path to watch object + self.directoryWatches = []; // array of watch object // We track all of the currently active timers so that we can cancel // them at stop() time. This stops the process from hanging at From 3f5edbfae95f6bfbca25d1b1fff8e10380278fc3 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 31 Jul 2013 14:15:08 -0700 Subject: [PATCH 155/175] remove dead builder.copyDirectory({depend:true}) code --- tools/builder.js | 17 +---------------- tools/bundler.js | 6 ++---- tools/packages.js | 3 +-- 3 files changed, 4 insertions(+), 22 deletions(-) diff --git a/tools/builder.js b/tools/builder.js index 7a97ce2427..6a2ff1b510 100644 --- a/tools/builder.js +++ b/tools/builder.js @@ -289,9 +289,7 @@ _.extend(Builder.prototype, { // bundle. But if the symlink option was passed to the Builder // constructor, then make a symlink instead, if possible. // - // Adds dependencies both on the files that were copied, and on the - // contents of the directory tree (respecting 'ignore'.) Disable - // this with depend: false. + // This does NOT add any dependencies. // // Options: // - from: source path on local disk to copy from @@ -300,14 +298,10 @@ _.extend(Builder.prototype, { // - ignore: array of regexps of filenames (that is, basenames) to // ignore (they may still be visible in the output bundle if // symlinks are being used) - // - depend: Should dependencies be added? Defaults to true. copyDirectory: function (options) { var self = this; options = options || {}; - var createDependencies = - ('depend' in options) ? options.depend : true; - var normOptionsTo = options.to; if (normOptionsTo.slice(-1) === path.sep) normOptionsTo = normOptionsTo.slice(0, -1); @@ -343,12 +337,6 @@ _.extend(Builder.prototype, { } var ignore = options.ignore || []; - if (createDependencies) { - self.dependencyInfo.directories[absPathTo] = { - include: [/.?/], - exclude: ignore - }; - } var walk = function (absFrom, relTo) { self._ensureDirectory(relTo); @@ -368,9 +356,6 @@ _.extend(Builder.prototype, { // XXX avoid reading whole file into memory var data = fs.readFileSync(thisAbsFrom); - if (createDependencies) - self.dependencyInfo.files[thisAbsFrom] = sha1(data); - fs.writeFileSync(path.resolve(self.buildPath, thisRelTo), data); self.usedAsFile[thisRelTo] = true; }); diff --git a/tools/bundler.js b/tools/bundler.js index 6c03b1c2ed..9daeb67133 100644 --- a/tools/bundler.js +++ b/tools/bundler.js @@ -1105,8 +1105,7 @@ _.extend(JsImage.prototype, { _.each(nodeModulesDirectories, function (nmd) { builder.copyDirectory({ from: nmd.sourcePath, - to: nmd.preferredBundlePath, - depend: false + to: nmd.preferredBundlePath }); }); @@ -1321,8 +1320,7 @@ _.extend(ServerTarget.prototype, { builder.copyDirectory({ from: path.join(files.get_dev_bundle(), 'lib', 'node_modules'), to: 'node_modules', - ignore: ignoreFiles, - depend: false + ignore: ignoreFiles }); } diff --git a/tools/packages.js b/tools/packages.js index 9acb349047..db5434f9cf 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -2355,8 +2355,7 @@ _.extend(Package.prototype, { if (slice.nodeModulesPath) { builder.copyDirectory({ from: slice.nodeModulesPath, - to: 'npm/node_modules', - depend: false + to: 'npm/node_modules' }); } From ea22b3ab02be8460977188696845d65edf1f44cd Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 31 Jul 2013 15:02:14 -0700 Subject: [PATCH 156/175] Checkpoint for actually using WatchSets. Have not yet touched initFromAppDir. --- tools/builder.js | 17 +++-- tools/bundler.js | 61 ++++++--------- tools/packages.js | 189 ++++++++++++++++------------------------------ tools/run.js | 33 ++------ tools/watch.js | 36 ++++++++- 5 files changed, 135 insertions(+), 201 deletions(-) diff --git a/tools/builder.js b/tools/builder.js index 6a2ff1b510..a258f172ac 100644 --- a/tools/builder.js +++ b/tools/builder.js @@ -1,5 +1,6 @@ var path = require('path'); var files = require(path.join(__dirname, 'files.js')); +var watch = require('./watch.js'); var fs = require('fs'); var _ = require('underscore'); @@ -51,7 +52,7 @@ var Builder = function (options) { files.rm_recursive(self.buildPath); files.mkdir_p(self.buildPath, 0755); - self.dependencyInfo = { directories: {}, files: {} }; + self.watchSet = new watch.WatchSet(); // XXX cleaner error handling. don't make the humans read an // exception (and, make suitable for use in automated systems) @@ -151,7 +152,7 @@ _.extend(Builder.prototype, { // // Returns the final canonicalize relPath that was written to. // - // If `file` is used then a dependency will be added on that file. + // If `file` is used then it will be added to the builder's WatchSet. write: function (relPath, options) { var self = this; options = options || {}; @@ -175,7 +176,7 @@ _.extend(Builder.prototype, { } else if (options.file) { var sourcePath = path.resolve(options.file); data = fs.readFileSync(sourcePath); - self.dependencyInfo.files[sourcePath] = sha1(data); + self.watchSet.addFile(sourcePath, sha1(data)); } self._ensureDirectory(path.dirname(relPath)); @@ -289,7 +290,7 @@ _.extend(Builder.prototype, { // bundle. But if the symlink option was passed to the Builder // constructor, then make a symlink instead, if possible. // - // This does NOT add any dependencies. + // This does NOT add anything to the WatchSet. // // Options: // - from: source path on local disk to copy from @@ -436,11 +437,11 @@ _.extend(Builder.prototype, { files.rm_recursive(self.buildPath); }, - // Return all dependency info that has accumulated, in the format - // expected by watch.Watcher. - getDependencyInfo: function () { + // Returns a WatchSet representing all files that were read from disk by the + // builder. + getWatchSet: function () { var self = this; - return self.dependencyInfo; + return self.watchSet; } }); diff --git a/tools/bundler.js b/tools/bundler.js index 9daeb67133..db2de236ec 100644 --- a/tools/bundler.js +++ b/tools/bundler.js @@ -167,6 +167,7 @@ var _ = require('underscore'); var project = require(path.join(__dirname, 'project.js')); var builder = require(path.join(__dirname, 'builder.js')); var unipackage = require(path.join(__dirname, 'unipackage.js')); +var watch = require('./watch.js'); var Fiber = require('fibers'); var Future = require(path.join('fibers', 'future')); var sourcemap = require('source-map'); @@ -400,9 +401,8 @@ var Target = function (options) { // the order given. self.js = []; - // Files and paths used by this target, in the format used by - // watch.Watcher. - self.dependencyInfo = {files: {}, directories: {}}; + // On-disk dependencies of this target. + self.watchSet = new watch.WatchSet(); // node_modules directories that we need to copy into the target (or // otherwise make available at runtime.) A map from an absolute path @@ -669,13 +669,8 @@ _.extend(Target.prototype, { throw new Error("Unknown type " + resource.type); }); - // Depend on the source files that produced these - // resources. (Since the dependencyInfo.directories should be - // disjoint, it should be OK to merge them this way.) - _.extend(self.dependencyInfo.files, - slice.dependencyInfo.files); - _.extend(self.dependencyInfo.directories, - slice.dependencyInfo.directories); + // Depend on the source files that produced these resources. + self.watchSet.merge(slice.watchSet); }); }, @@ -705,11 +700,10 @@ _.extend(Target.prototype, { }); }, - // Return all dependency info for this target, in the format - // expected by watch.Watcher. - getDependencyInfo: function () { + // Return the WatchSet for this target's dependency info. + getWatchSet: function () { var self = this; - return self.dependencyInfo; + return self.watchSet; }, // Return the most inclusive architecture with which this target is @@ -765,7 +759,7 @@ _.extend(ClientTarget.prototype, { var templatePath = path.join(__dirname, "app.html.in"); var template = fs.readFileSync(templatePath); - self.dependencyInfo.files[templatePath] = Builder.sha1(template); + self.watchSet.addFile(templatePath, Builder.sha1(template)); var f = require('handlebars').compile(template.toString()); return new Buffer(f({ @@ -1350,8 +1344,8 @@ var writeFile = function (file, builder) { // path of a directory that should be created to contain the generated // site archive. // -// Returns dependencyInfo (in the format expected by watch.Watcher) -// for all files and directories that ultimately went into the bundle. +// Returns a watch.WatchSet for all files and directories that ultimately went +// into the bundle. // // options: // - nodeModulesMode: "skip", "symlink", "copy" @@ -1457,25 +1451,17 @@ var writeSiteArchive = function (targets, outputPath, options) { // Control file builder.writeJson('star.json', json); - // Merge the dependencyInfo of everything that went into the - // bundle. A naive merge like this doesn't work in general but - // should work in this case. - var fileDeps = {}, directoryDeps = {}; + // Merge the WatchSet of everything that went into the bundle. + var watchSet = new watch.WatchSet(); var dependencySources = [builder].concat(_.values(targets)); _.each(dependencySources, function (s) { - var info = s.getDependencyInfo(); - _.extend(fileDeps, info.files); - _.extend(directoryDeps, info.directories); + watchSet.merge(s.getWatchSet()); }); // We did it! builder.complete(); - return { - files: fileDeps, - directories: directoryDeps - }; - + return watchSet; } catch (e) { builder.abort(); throw e; @@ -1495,10 +1481,9 @@ var writeSiteArchive = function (targets, outputPath, options) { * * Returns an object with keys: * - errors: A buildmessage.MessageSet, or falsy if bundling succeeded. - * - dependencyInfo: Information about files and paths that were + * - watchSet: Information about files and paths that were * inputs into the bundle and that we may wish to monitor for - * changes when developing interactively. It has two keys, 'files' - * and 'directories', in the format expected by watch.Watcher. + * changes when developing interactively, as a watch.WatchSet. * * On failure ('errors' is truthy), no bundle will be output (in fact, * outputPath will have been removed if it existed.) @@ -1545,7 +1530,7 @@ exports.bundle = function (appDir, outputPath, options) { " " + options.releaseStamp : ""); var success = false; - var dependencyInfo = { files: {}, directories: {} }; + var watchSet = new watch.WatchSet(); var messages = buildmessage.capture({ title: "building the application" }, function () { @@ -1749,7 +1734,7 @@ exports.bundle = function (appDir, outputPath, options) { controlProgram = undefined; // Write to disk - dependencyInfo = writeSiteArchive(targets, outputPath, { + watchSet = writeSiteArchive(targets, outputPath, { nodeModulesMode: options.nodeModulesMode, builtBy: builtBy, controlProgram: controlProgram @@ -1763,8 +1748,8 @@ exports.bundle = function (appDir, outputPath, options) { return { errors: success ? false : messages, - dependencyInfo: dependencyInfo - } ; + watchSet: watchSet + }; }; // Make a JsImage object (a complete, linked, ready-to-go JavaScript @@ -1774,7 +1759,7 @@ exports.bundle = function (appDir, outputPath, options) { // // Returns an object with keys: // - image: The created JsImage object. -// - dependencyInfo: Source file dependency info (see bundle().) +// - watchSet: Source file WatchSet (see bundle().) // // XXX return an 'errors' key for symmetry with bundle(), rather than // letting exceptions escape? @@ -1828,7 +1813,7 @@ exports.buildJsImage = function (options) { return { image: target.toJsImage(), - dependencyInfo: target.getDependencyInfo() + watchSet: target.getWatchSet() }; }; diff --git a/tools/packages.js b/tools/packages.js index db5434f9cf..e676cc47d1 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -87,10 +87,10 @@ var rejectBadPath = function (p) { // - implies // - getSourcesFunc // - exports -// - dependencyInfo +// - watchSet // - nodeModulesPath // -// Do not include the source files in dependencyInfo. They will be +// Do not include the source files in watchSet. They will be // added at compile time when the sources are actually read. var Slice = function (pkg, options) { var self = this; @@ -160,10 +160,8 @@ var Slice = function (pkg, options) { self.declaredExports = options.declaredExports || null; // Files and directories that we want to monitor for changes in - // development mode, such as source files and package.js, in the - // format accepted by watch.Watcher. - self.dependencyInfo = options.dependencyInfo || - { files: {}, directories: {} }; + // development mode, such as source files and package.js, as a watch.WatchSet. + self.watchSet = options.watchSet || new watch.WatchSet(); // Has this slice been compiled? self.isBuilt = false; @@ -268,7 +266,7 @@ _.extend(Slice.prototype, { var ext = path.extname(relPath).substr(1); var handler = !fileOptions.isAsset && self._getSourceHandler(ext); var contents = fs.readFileSync(absPath); - self.dependencyInfo.files[absPath] = Builder.sha1(contents); + self.watchSet.addFile(absPath, Builder.sha1(contents)); if (! handler) { // If we don't have an extension handler, serve this file as a @@ -497,20 +495,16 @@ _.extend(Slice.prototype, { jsAnalyze: jsAnalyze }); - // Add dependencies on the source code to any plugins that we - // could have used (we need to depend even on plugins that we - // didn't use, because if they were changed they might become - // relevant to us) - // - // XXX I guess they're probably properly disjoint since plugins - // probably include only file dependencies? Anyway it would be a - // strange situation if plugin source directories overlapped with - // other parts of your app + // Add dependencies on the source code to any plugins that we could have + // used. We need to depend even on plugins that we didn't use, because if + // they were changed they might become relevant to us. This means that we + // end up depending on every source file contributing to all plugins in the + // packages we use (including source files from other packages that the + // plugin program itself uses), as well as the package.js file from every + // package we directly use (since changing the package.js may add or remove + // a plugin). _.each(self._activePluginPackages(), function (otherPkg) { - _.extend(self.dependencyInfo.files, - otherPkg.pluginDependencyInfo.files); - _.extend(self.dependencyInfo.directories, - otherPkg.pluginDependencyInfo.directories); + self.watchSet.merge(otherPkg.pluginWatchSet); }); self.prelinkFiles = results.files; @@ -835,19 +829,17 @@ var Package = function (library) { // pluginsBuilt is true. self.plugins = {}; - // Full transitive dependencies for all plugins in this package, as well as - // this package's package.js. If any of these dependencies change, not only - // may our plugins need to be rebuilt, but any package that directly uses this - // package needs to be rebuilt in case the change to plugins affected - // compilation. + // A WatchSet for the full transitive dependencies for all plugins in this + // package, as well as this package's package.js. If any of these dependencies + // change, our plugins need to be rebuilt... but also, any package that + // directly uses this package needs to be rebuilt in case the change to + // plugins affected compilation. // // Complete only when pluginsBuilt is true. - // XXX Refactor so that slice and plugin dependencies are handled by - // the same mechanism. - self.pluginDependencyInfo = { files: {}, directories: {} }; + self.pluginWatchSet = new watch.WatchSet(); - // True if plugins have been initialized (if - // _ensurePluginsInitialized has been called) + // True if plugins have been initialized (if _ensurePluginsInitialized has + // been called) self._pluginsInitialized = false; // Source file handlers registered by plugins. Map from extension @@ -1031,16 +1023,10 @@ _.extend(Package.prototype, { info.name)) }); - if (buildResult.dependencyInfo) { - // Merge plugin dependencies - // XXX is naive merge sufficient here? should be, because - // plugins can't (for now) contain directory dependencies? - _.extend(self.pluginDependencyInfo.files, - buildResult.dependencyInfo.files); - _.extend(self.pluginDependencyInfo.directories, - buildResult.dependencyInfo.directories); - } + // Add this plugin's dependencies to our "plugin dependency" WatchSet. + self.pluginWatchSet.merge(buildResult.watchSet); + // Register the built plugin's code. self.plugins[info.name] = buildResult.image; }); }); @@ -1156,7 +1142,7 @@ _.extend(Package.prototype, { // changes, because a change to package.js might add or remove a plugin, // which could change a file from being handled by extension vs treated as // an asset. - self.pluginDependencyInfo.files[packageJsPath] = packageJsHash; + self.pluginWatchSet.addFile(packageJsPath, packageJsHash); // == 'Package' object visible in package.js == var Package = { @@ -1728,10 +1714,12 @@ _.extend(Package.prototype, { uses[role][where].unshift({ spec: "meteor" }); } - // We need to create a separate (non ===) copy of - // dependencyInfo for each slice. - var dependencyInfo = { files: {}, directories: {} }; - dependencyInfo.files[packageJsPath] = packageJsHash; + // Each slice has its own separate WatchSet. This is so that, eg, a test + // slice's dependencies doesn't end up getting merged into the + // pluginWatchSet of a package that uses it: only the use slice's + // dependencies need to go there! + var watchSet = new watch.WatchSet(); + watchSet.addFile(packageJsPath, packageJsHash); self.slices.push(new Slice(self, { name: ({ use: "main", test: "tests" })[role], @@ -1741,7 +1729,7 @@ _.extend(Package.prototype, { getSourcesFunc: function () { return sources[role][where]; }, noExports: role === "test", declaredExports: role === "use" ? exports[where] : null, - dependencyInfo: dependencyInfo, + watchSet: watchSet, nodeModulesPath: arch === osArch && nodeModulesPath || undefined })); }); @@ -1947,16 +1935,22 @@ _.extend(Package.prototype, { // XXX should comprehensively sanitize (eg, typecheck) everything // read from json files - // Read the dependency info (if present), and make the strings - // back into regexps - var sliceDependencies = buildInfoJson.sliceDependencies || {}; - _.each(sliceDependencies, function (dependencyInfo, sliceTag) { - sliceDependencies[sliceTag] = - makeDependencyInfoIntoRegexps(dependencyInfo); + // Read the watch sets for each slice; keep them separate (for passing to + // the Slice constructor below) as well as merging them into one big + // WatchSet. + var mergedWatchSet = new watch.WatchSet(); + var sliceWatchSets = {}; + _.each(buildInfoJson.sliceDependencies, function (watchSetJSON, sliceTag) { + var watchSet = watch.WatchSet.fromJSON(watchSetJSON); + mergedWatchSet.merge(watchSet); + sliceWatchSets[sliceTag] = watchSet; }); - self.pluginDependencyInfo = makeDependencyInfoIntoRegexps( + self.pluginWatchSet = watch.WatchSet.fromJSON( buildInfoJson.pluginDependencies); + // This might be redundant (since pluginWatchSet was probably merged into + // each slice watchSet when it was built) but shouldn't hurt. + mergedWatchSet.merge(self.pluginWatchSet); // If we're supposed to check the dependencies, go ahead and do so if (options.onlyIfUpToDate) { @@ -1976,7 +1970,7 @@ _.extend(Package.prototype, { return false; } - if (! self.checkUpToDate(sliceDependencies)) + if (! self.checkUpToDate(mergedWatchSet)) return false; } @@ -2034,7 +2028,7 @@ _.extend(Package.prototype, { var slice = new Slice(self, { name: sliceMeta.name, arch: sliceMeta.arch, - dependencyInfo: sliceDependencies[sliceMeta.path], + watchSet: sliceWatchSets[sliceMeta.path], nodeModulesPath: nodeModulesPath, uses: _.map(sliceJson.uses, function (u) { return { @@ -2107,50 +2101,26 @@ _.extend(Package.prototype, { return true; }, - // Try to check if this package is up-to-date (that is, whether its - // source files have been modified.) True if we have dependency info - // and it says that the package is up-to-date. False if a source - // file has changed. + // Try to check if this package is up-to-date (that is, whether its source + // files have been modified.) True if we have dependency info and it says that + // the package is up-to-date. False if a source file has changed. // - // The argument _sliceDependencies is used when reading from disk when there - // are no slices yet; don't pass it from outside this file. - checkUpToDate: function (_sliceDependencies) { + // The argument _watchSet is used when reading from disk when there are no + // slices yet; don't pass it from outside this file. + checkUpToDate: function (_watchSet) { var self = this; - // Compute the dependency info to use - var dependencyInfo = { files: {}, directories: {} }; - - var merge = function (di) { - // XXX is naive merge sufficient here? - _.extend(dependencyInfo.files, di.files); - _.extend(dependencyInfo.directories, di.directories); - }; - - if (_sliceDependencies) { - _.each(_sliceDependencies, function (dependencyInfo, sliceTag) { - merge(dependencyInfo); - }); - } else { + if (!_watchSet) { + // This call was on an already-fully-loaded Package and we just want to + // see if it's changed. So we have some watchSets inside ourselves. + _watchSet = new watch.WatchSet(); + _watchSet.merge(self.pluginWatchSet); _.each(self.slices, function (slice) { - merge(slice.dependencyInfo); + _watchSet.merge(slice.watchSet); }); } - // XXX There used to be a concept in this file of "packages loaded from disk - // without having dependencyInfo, but it was unclear when that would happen, - // so this was removed. - - var isUpToDate = true; - var watcher = new watch.Watcher({ - files: dependencyInfo.files, - directories: dependencyInfo.directories, - onChange: function () { - isUpToDate = false; - } - }); - watcher.stop(); - - return isUpToDate; + return watch.isUpToDate(_watchSet); }, // True if this package can be saved as a unipackage @@ -2196,8 +2166,7 @@ _.extend(Package.prototype, { var buildInfoJson = { builtBy: exports.BUILT_BY, sliceDependencies: { }, - pluginDependencies: makeDependencyInfoSerializable( - self.pluginDependencyInfo), + pluginDependencies: self.pluginWatchSet.toJSON(), source: options.buildOfPath || undefined }; @@ -2249,7 +2218,7 @@ _.extend(Package.prototype, { // Save slice dependencies. Keyed by the json path rather than thinking // too hard about how to encode pair (name, arch). buildInfoJson.sliceDependencies[sliceJsonFile] = - makeDependencyInfoSerializable(slice.dependencyInfo); + slice.watchSet.toJSON(); // Construct slice metadata var sliceJson = { @@ -2386,38 +2355,6 @@ _.extend(Package.prototype, { } }); -// Convert regex to string. -var makeDependencyInfoSerializable = function (dependencyInfo) { - if (!dependencyInfo) - dependencyInfo = { files: {}, directories: {} }; - var out = {files: dependencyInfo.files, directories: {}}; - _.each(dependencyInfo.directories, function (d, path) { - var dirInfo = out.directories[path] = {}; - _.each(["include", "exclude"], function (k) { - dirInfo[k] = _.map(d[k], function (r) { - return r.source; - }); - }); - }); - return out; -}; - -// Convert string to regex. -var makeDependencyInfoIntoRegexps = function (dependencyInfo) { - if (!dependencyInfo) - dependencyInfo = { files: {}, directories: {} }; - var out = {files: dependencyInfo.files, directories: {}}; - _.each(dependencyInfo.directories, function (d, path) { - var dirInfo = out.directories[path] = {}; - _.each(["include", "exclude"], function (k) { - dirInfo[k] = _.map(d[k], function (s) { - return new RegExp(s); - }); - }); - }); - return out; -}; - var packages = exports; _.extend(exports, { Package: Package diff --git a/tools/run.js b/tools/run.js index 79ea43b056..3ad0fdf075 100644 --- a/tools/run.js +++ b/tools/run.js @@ -471,7 +471,7 @@ exports.run = function (context, options) { library: context.library }; - var startWatching = function (dependencyInfo) { + var startWatching = function (watchSet) { if (!Status.shouldRestart) return; @@ -479,8 +479,7 @@ exports.run = function (context, options) { watcher.stop(); watcher = new watch.Watcher({ - files: dependencyInfo.files, - directories: dependencyInfo.directories, + watchSet: watchSet, onChange: function () { if (Status.crashing) logToClients({'system': "=> Modified -- restarting."}); @@ -524,12 +523,12 @@ exports.run = function (context, options) { // Bundle up the app var bundleResult = bundler.bundle(context.appDir, bundlePath, bundleOpts); - var dependencyInfo = bundleResult.dependencyInfo; + var watchSet = bundleResult.watchSet; if (bundleResult.errors) { logToClients({stdout: "=> Errors prevented startup:\n\n" + bundleResult.errors.formatMessages() + "\n"}); Status.hardCrashed("has errors"); - startWatching(dependencyInfo); + startWatching(watchSet); return; } @@ -546,32 +545,14 @@ exports.run = function (context, options) { Builder.sha1(fs.readFileSync(options.settingsFile, "utf8")); // Reload if the setting file changes - dependencyInfo.files[path.resolve(options.settingsFile)] = - settingsHash; - } - - // If using a warehouse, don't do dependency monitoring on any of - // the files that are in the warehouse. You should not be editing - // those files directly. - if (files.usesWarehouse()) { - var warehouseDir = path.resolve(warehouse.getWarehouseDir()); - var filterKeys = function (obj) { - _.each(_.keys(obj), function (k) { - k = path.resolve(k); - if (warehouseDir.length <= k.length && - k.substr(0, warehouseDir.length) === warehouseDir) - delete obj[k]; - }); - }; - filterKeys(dependencyInfo.files); - filterKeys(dependencyInfo.directories); + watchSet.addFile(path.resolve(options.settingsFile), settingsHash); } // Start watching for changes for files. There's no hurry to call - // this, since dependencyInfo contains a snapshot of the state of + // this, since watchSet contains a snapshot of the state of // the world at the time of bundling, in the form of hashes and // lists of matching files in each directory. - startWatching(dependencyInfo); + startWatching(watchSet); // Start the server Status.running = true; diff --git a/tools/watch.js b/tools/watch.js index c6446c14f5..92ebfc751a 100644 --- a/tools/watch.js +++ b/tools/watch.js @@ -197,6 +197,10 @@ _.extend(WatchSet.prototype, { WatchSet.fromJSON = function (json) { var set = new WatchSet; + + if (!json) + return set; + if (json.alwaysFire) { set.alwaysFire = true; return set; @@ -265,6 +269,7 @@ var readDirectory = function (options) { return filtered; }; +// All fields are private. var Watcher = function (options) { var self = this; @@ -280,6 +285,7 @@ var Watcher = function (options) { throw new Error("onChange option is required"); self.stopped = false; + self.justCheckOnce = !!options._justCheckOnce; self.fileWatches = []; // array of paths self.directoryWatches = []; // array of watch object @@ -366,7 +372,7 @@ _.extend(Watcher.prototype, { return true; } - if (!isDoubleCheck) { + if (!isDoubleCheck && !self.justCheckOnce) { // Whenever a directory changes, scan it soon as we notice, // but then scan it again one secord later just to make sure // that we haven't missed any changes. See commentary at @@ -396,6 +402,9 @@ _.extend(Watcher.prototype, { if (self._fireIfFileChanged(absPath)) return; + if (self.justCheckOnce) + return; + // Intentionally not using fs.watch since it doesn't play well with // vim (https://github.com/joyent/node/issues/3172) // Note that we poll very frequently (500 ms) @@ -407,7 +416,7 @@ _.extend(Watcher.prototype, { self.fileWatches.push(absPath); }); - if (self.stopped) + if (self.stopped || self.justCheckOnce) return; // One second later, check the files again, because fs.watchFile @@ -439,6 +448,9 @@ _.extend(Watcher.prototype, { if (self._fireIfDirectoryChanged(info)) return; + if (self.stopped || self.justCheckOnce) + return; + // fs.watchFile doesn't work for directories (as tested on ubuntu) // Notice that we poll very frequently (500 ms) try { @@ -493,8 +505,26 @@ _.extend(Watcher.prototype, { } }); +// Given a WatchSet, returns true if it currently describes the state of the +// disk. +var isUpToDate = function (watchSet) { + var upToDate = true; + var watcher = new Watcher({ + watchSet: watchSet, + onChange: function () { + upToDate = false; + }, + // internal flag which prevents us from starting watches and timers that + // we're about to cancel anyway + _justCheckOnce: true + }); + watcher.stop(); + return upToDate; +}; + _.extend(exports, { WatchSet: WatchSet, Watcher: Watcher, - readDirectory: readDirectory + readDirectory: readDirectory, + isUpToDate: isUpToDate }); From f84fc21e8fb4203447db1b8c872c20c20cc60042 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 31 Jul 2013 16:48:37 -0700 Subject: [PATCH 157/175] checkpoint for initFromAppDir rewrite --- tools/builder.js | 19 ++-- tools/bundler.js | 6 +- tools/files.js | 106 ++++---------------- tools/packages.js | 242 +++++++++++++++++++--------------------------- 4 files changed, 136 insertions(+), 237 deletions(-) diff --git a/tools/builder.js b/tools/builder.js index a258f172ac..67b4190966 100644 --- a/tools/builder.js +++ b/tools/builder.js @@ -298,7 +298,8 @@ _.extend(Builder.prototype, { // receive the files // - ignore: array of regexps of filenames (that is, basenames) to // ignore (they may still be visible in the output bundle if - // symlinks are being used) + // symlinks are being used). Like with WatchSets, they match against + // entries that end with a slash if it's a directory. copyDirectory: function (options) { var self = this; options = options || {}; @@ -343,13 +344,19 @@ _.extend(Builder.prototype, { self._ensureDirectory(relTo); _.each(fs.readdirSync(absFrom), function (item) { - if (_.any(ignore, function (pattern) { - return item.match(pattern); - })) return; // skip excluded files - var thisAbsFrom = path.resolve(absFrom, item); var thisRelTo = path.join(relTo, item); - if (fs.statSync(thisAbsFrom).isDirectory()) { + + var isDir = fs.statSync(thisAbsFrom).isDirectory(); + var itemForMatch = item; + if (isDir) + itemForMatch += '/'; + + if (_.any(ignore, function (pattern) { + return itemForMatch.match(pattern); + })) return; // skip excluded files + + if (isDir) { walk(thisAbsFrom, thisRelTo); return; } diff --git a/tools/bundler.js b/tools/bundler.js index db2de236ec..6ab8f312dd 100644 --- a/tools/bundler.js +++ b/tools/bundler.js @@ -175,9 +175,9 @@ var sourcemap = require('source-map'); // files to ignore when bundling. node has no globs, so use regexps var ignoreFiles = [ /~$/, /^\.#/, /^#.*#$/, - /^\.DS_Store$/, /^ehthumbs\.db$/, /^Icon.$/, /^Thumbs\.db$/, - /^\.meteor$/, /* avoids scanning N^2 files when bundling all packages */ - /^\.git$/ /* often has too many files to watch */ + /^\.DS_Store\/?$/, /^ehthumbs\.db$/, /^Icon.$/, /^Thumbs\.db$/, + /^\.meteor\/$/, /* avoids scanning N^2 files when bundling all packages */ + /^\.git\/$/ /* often has too many files to watch */ ]; // http://davidshariff.com/blog/javascript-inheritance-patterns/ diff --git a/tools/files.js b/tools/files.js index a815fc0b20..3db5d821a4 100644 --- a/tools/files.js +++ b/tools/files.js @@ -39,6 +39,20 @@ var files = exports; _.extend(exports, { // A sort comparator to order files into load order. sort: function (a, b) { + // XXX HUGE HACK -- + // push html (template) files ahead of everything else. this is + // important because the user wants to be able to say + // Template.foo.events = { ... } + // + // maybe all of the templates should go in one file? packages should + // probably have a way to request this treatment (load order dependency + // tags?) .. who knows. + var ishtml_a = path.extname(a) === '.html'; + var ishtml_b = path.extname(a) === '.html'; + if (ishtml_a !== ishtml_b) { + return (ishtml_a ? -1 : 1); + } + // main.* loaded last var ismain_a = (path.basename(a).indexOf('main.') === 0); var ismain_b = (path.basename(b).indexOf('main.') === 0); @@ -47,8 +61,10 @@ _.extend(exports, { } // /lib/ loaded first - var islib_a = (a.indexOf(path.sep + 'lib' + path.sep) !== -1); - var islib_b = (b.indexOf(path.sep + 'lib' + path.sep) !== -1); + var islib_a = (a.indexOf(path.sep + 'lib' + path.sep) !== -1 || + a.indexOf('lib' + path.sep) === 0); + var islib_b = (b.indexOf(path.sep + 'lib' + path.sep) !== -1 || + b.indexOf('lib' + path.sep) === 0); if (islib_a !== islib_b) { return (islib_a ? -1 : 1); } @@ -64,84 +80,6 @@ _.extend(exports, { return (a < b ? -1 : 1); }, - // Returns true if this is a file we should maybe care about (stat it, - // descend if it is a directory, etc). - pre_filter: function (filename) { - if (!filename) { return false; } - // no . files - var base = path.basename(filename); - if (base && base[0] === '.') { return false; } - - // XXX - // first, we only want to exclude APP_ROOT/public, not some deeper public - // second, we don't really like this at all - // third, we don't update the app now if anything here changes - if (base === 'public' || base === 'private') { return false; } - - return true; - }, - - // Returns true if this is a file we should monitor. Iterate over - // all the interesting files, applying 'func' to each file - // path. 'extensions' is an array of extensions to include, without - // leading dots (eg ['html', 'js']) - file_list_async: function (filepath, extensions, func) { - if (!files.pre_filter(filepath)) { return; } - fs.stat(filepath, function(err, stats) { - if (err) { - // XXX! - return; - } - - if (stats.isDirectory()) { - fs.readdir(filepath, function(err, fileNames) { - if(err) { - // XXX! - return; - } - - _.each(fileNames, function (fileName) { - files.file_list_async(path.join(filepath, fileName), - extensions, func); - }); - }); - } else if (files.findExtension(extensions, filepath)) { - func(filepath); - } - }); - }, - - file_list_sync: function (filepath, extensions) { - var ret = []; - if (!files.pre_filter(filepath)) { return ret; } - var stats = fs.statSync(filepath); - if (stats.isDirectory()) { - var fileNames = fs.readdirSync(filepath); - _.each(fileNames, function (fileName) { - ret = ret.concat(files.file_list_sync( - path.join(filepath, fileName), extensions)); - }); - } else if (files.findExtension(extensions, filepath)) { - ret.push(filepath); - } - - return ret; - }, - - // given a list of extensions (no leading dots) and a path, return - // the file extension provided in the list. If it doesn't find it, - // return null. - findExtension: function (extensions, filepath) { - var len = filepath.length; - for (var i = 0; i < extensions.length; ++i) { - var ext = "." + extensions[i]; - if (filepath.indexOf(ext, len - ext.length) !== -1){ - return ext; - } - } - return null; - }, - // given a path, returns true if it is a meteor application (has a // .meteor directory with a 'packages' file). false otherwise. is_app_dir: function (filepath) { @@ -161,14 +99,6 @@ _.extend(exports, { } }, - // given a path, returns true if it is a meteor package (is a - // directory with a 'packages.js' file). false otherwise. - // - // Note that a directory can be both a package _and_ an application. - is_package_dir: function (filepath) { - return fs.existsSync(path.join(filepath, 'package.js')); - }, - // given a predicate function and a starting path, traverse upwards // from the path until we find a path that satisfies the predicate. // diff --git a/tools/packages.js b/tools/packages.js index e676cc47d1..fbbca77d4c 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -22,52 +22,10 @@ var sourcemap = require('source-map'); // eg, if we improve the linker's static analysis, this should be bumped. exports.BUILT_BY = 'meteor/6'; -// Find all files under `rootPath` that have an extension in -// `extensions` (an array of extensions without leading dot), and -// return them as a list of paths relative to rootPath. Ignore files -// that match a regexp in the ignoreFiles array, if given. As a -// special case (ugh), push all html files to the head of the list. -var scanForSources = function (rootPath, extensions, ignoreFiles) { - var self = this; - - // find everything in tree, sorted depth-first alphabetically. - var fileList = files.file_list_sync(rootPath, extensions); - fileList = _.reject(fileList, function (file) { - return _.any(ignoreFiles || [], function (pattern) { - return file.match(pattern); - }); - }); - fileList.sort(files.sort); - - // XXX HUGE HACK -- - // push html (template) files ahead of everything else. this is - // important because the user wants to be able to say - // Template.foo.events = { ... } - // - // maybe all of the templates should go in one file? packages - // should probably have a way to request this treatment (load - // order dependency tags?) .. who knows. - var htmls = []; - _.each(fileList, function (filename) { - if (path.extname(filename) === '.html') { - htmls.push(filename); - fileList = _.reject(fileList, function (f) { return f === filename;}); - } - }); - fileList = htmls.concat(fileList); - - // now make everything relative to rootPath - var prefix = rootPath; - if (prefix[prefix.length - 1] !== path.sep) - prefix += path.sep; - - return fileList.map(function (abs) { - if (path.relative(prefix, abs).match(/\.\./)) - // XXX audit to make sure it works in all possible symlink - // scenarios - throw new Error("internal error: source file outside of parent?"); - return abs.substr(prefix.length); - }); +// Like Perl's quotemeta: quotes all regexp metacharacters. See +// https://github.com/substack/quotemeta/blob/master/index.js +var quotemeta = function (str) { + return String(str).replace(/(\W)/g, '\\$1'); }; var rejectBadPath = function (p) { @@ -1767,82 +1725,88 @@ _.extend(Package.prototype, { self.slices.push(slice); // Watch control files for changes - // XXX this read has a race with the actual read that is used + // XXX this read has a race with the actual reads that are is used _.each([path.join(appDir, '.meteor', 'packages'), - path.join(appDir, '.meteor', 'releases')], function (p) { - if (fs.existsSync(p)) { - slice.dependencyInfo.files[p] = - Builder.sha1(fs.readFileSync(p)); + path.join(appDir, '.meteor', 'release')], function (p) { + try { + var hash = Builder.sha1(fs.readFileSync(p)); + } catch (e) { + // If it doesn't exist, just depend on that fact. + if (!e || e.code !== "ENOENT") + throw e; + hash = null; } + slice.watchSet.addFile(p, hash); }); // Determine source files slice.getSourcesFunc = function () { - var allSources = scanForSources( - self.sourceRoot, slice.registeredExtensions(), - ignoreFiles || []); + var sourceInclude = _.map(slice.registeredExtensions(), function (ext) { + return new RegExp('\\.' + quotemeta(ext) + '$'); + }); + var sourceExclude = [/^\./].concat(ignoreFiles); - var withoutAppPackages = _.reject(allSources, function (sourcePath) { - // Skip files that are in app packages; they'll get watched if they - // are actually listed in the .meteor/packages file. (Directories - // named "packages" lower in the tree are OK.) - return sourcePath.match(/^packages\//); + var readAndWatchDirectory = function (relDir, filters) { + filters = filters || {}; + var absPath = path.join(self.sourceRoot, relDir); + var contents = watch.readDirectory({ + absPath: absPath, + include: filters.include, + exclude: filters.exclude + }); + slice.watchSet.addDirectory({ + absPath: absPath, + include: filters.include, + exclude: filters.exclude, + contents: contents + }); + return _.map(contents, function (x) { + return path.join(relDir, x); + }); + }; + + // Read top-level source files. + var sources = readAndWatchDirectory('', { + include: sourceInclude, + exclude: sourceExclude }); - var otherSliceName = (sliceName === "server") ? "client" : "server"; - var withoutOtherSlice = - _.reject(withoutAppPackages, function (sourcePath) { - return (path.sep + sourcePath + path.sep).indexOf( - path.sep + otherSliceName + path.sep) !== -1; - }); + var otherSliceRegExp = + (sliceName === "server" ? /^client\/$/ : /^server\/$/); - var tests = false; /* for now */ - var withoutOtherRole = - _.reject(withoutOtherSlice, function (sourcePath) { - var isTest = - ((path.sep + sourcePath + path.sep).indexOf( - path.sep + 'tests' + path.sep) !== -1); - return isTest !== (!!tests); - }); + // Read top-level subdirectories. Ignore subdirectories that have + // special handling. + var sourceDirectories = readAndWatchDirectory('', { + include: [/\/$/], + exclude: [/^packages\/$/, /^programs\/$/, /^tests\/$/, + /^public\/$/, /^private\/$/, + otherSliceRegExp].concat(sourceExclude) + }); - var withoutOtherPrograms = - _.reject(withoutOtherRole, function (sourcePath) { - return !! sourcePath.match(/^programs\//); - }); + while (!_.isEmpty(sourceDirectories)) { + var dir = sourceDirectories.shift(); + // remove trailing slash + dir = dir.substr(0, dir.length - 1); - // XXX Add directory dependencies to slice at the time that - // getSourcesFunc is called. This is kind of a hack but it'll - // do for the moment. + // Find source files in this directory. + Array.prototype.push.apply(sources, readAndWatchDirectory(dir, { + include: sourceInclude, + exclude: sourceExclude + })); - // XXX nothing here monitors for the no-default-targets file + // Find sub-sourceDirectories. Note that we DON'T need to ignore the + // directory names that are only special at the top level. + Array.prototype.push.apply(sourceDirectories, readAndWatchDirectory(dir, { + include: [/\/$/], + exclude: [/^tests\/$/, otherSliceRegExp].concat(sourceExclude) + })); + } - // Directories to monitor for new files - var appIgnores = _.clone(ignoreFiles); - slice.dependencyInfo.directories[appDir] = { - include: _.map(slice.registeredExtensions(), function (ext) { - return new RegExp('\\.' + ext + "$"); - }), - // XXX This excludes watching under *ANY* packages or programs - // directory, but we should really only care about top-level ones. - // But watcher doesn't let you do that. - exclude: ignoreFiles.concat([/^packages$/, /^programs$/, - /^tests$/]) - }; - - // Inside the programs directory, only look for new program (which we - // can detect by the appearance of a package.js file.) Other than that, - // programs explicitly call out the files they use. - slice.dependencyInfo.directories[path.resolve(appDir, 'programs')] = { - include: [ /^package\.js$/ ], - exclude: ignoreFiles - }; - - // Exclude .meteor/local and everything under it. - slice.dependencyInfo.directories[ - path.resolve(appDir, '.meteor', 'local')] = { exclude: [/.?/] }; + // We've found all the source files. Sort them! + sources.sort(files.sort); // Convert into relPath/fileOptions objects. - var sources = _.map(withoutOtherPrograms, function (relPath) { + sources = _.map(sources, function (relPath) { var sourceObj = {relPath: relPath}; // Special case: on the client, JavaScript files in a @@ -1856,44 +1820,42 @@ _.extend(Package.prototype, { return sourceObj; }); + // Now look for assets for this slice. var assetDir = sliceName === "client" ? "public" : "private"; - var absAssetDir = path.resolve(appDir, assetDir); - slice.dependencyInfo.directories[absAssetDir] - = { include: [/.?/], exclude: ignoreFiles}; - var walkAssetDir = function (subdir) { - var dir = path.join(appDir, subdir); - try { - var items = fs.readdirSync(dir); - } catch (e) { - // OK if the directory (esp the top level asset dir) doesn't exist. - if (e.code === "ENOENT") - return; - throw e; - } - _.each(items, function (item) { - // Skip excluded files - var matchesAnExclude = _.any(ignoreFiles, function (pattern) { - return item.match(pattern); + var assetDirs = readAndWatchDirectory('', { + include: [new RegExp('^' + assetDir + '/$')] + }); + if (_.isEmpty(assetDirs)) { + if (_.isEqual(assetDirs, [assetDir + '/'])) + throw new Error("Surprising assetDirs: " + assetDirs); + + while (!_.isEmpty(assetDirs)) { + dir = assetDirs.shift(); + // remove trailing slash + dir = dir.substr(0, dir.length - 1); + + // Find asset files in this directory. + var assetsAndSubdirs = readAndWatchDirectory(dir, { + // we DO look under dot directories here + exclude: ignoreFiles }); - if (matchesAnExclude) - return; - - var assetAbsPath = path.join(dir, item); - var assetRelPath = path.join(subdir, item); - if (fs.statSync(assetAbsPath).isDirectory()) { - walkAssetDir(assetRelPath); - return; - } - - sources.push({ - relPath: assetRelPath, - fileOptions: { - isAsset: true + _.each(assetsAndSubdirs, function (item) { + if (item[item.length - 1] === '/') { + // Recurse on this directory. + assetDirs.push(item); + } else { + // This file is an asset. + sources.push({ + relPath: item, + fileOptions: { + isAsset: true + } + }); } }); - }); - }; - walkAssetDir(assetDir); + } + } + return sources; }; }); From 1b82376b4b0d26f552b388f45f1c547e0cf7f445 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 31 Jul 2013 17:35:01 -0700 Subject: [PATCH 158/175] in theory, this might work --- tools/builder.js | 4 +- tools/bundler.js | 137 ++++++++++++++++++++++++++-------------------- tools/library.js | 20 ++++++- tools/packages.js | 23 ++------ tools/watch.js | 62 ++++++++++++++++----- 5 files changed, 149 insertions(+), 97 deletions(-) diff --git a/tools/builder.js b/tools/builder.js index 67b4190966..bdba7df95f 100644 --- a/tools/builder.js +++ b/tools/builder.js @@ -174,9 +174,7 @@ _.extend(Builder.prototype, { throw new Error("May only pass one of data and file, not both"); data = options.data; } else if (options.file) { - var sourcePath = path.resolve(options.file); - data = fs.readFileSync(sourcePath); - self.watchSet.addFile(sourcePath, sha1(data)); + data = watch.readAndWatchFile(self.watchSet, path.resolve(options.file)); } self._ensureDirectory(path.dirname(relPath)); diff --git a/tools/bundler.js b/tools/bundler.js index 6ab8f312dd..2eff91765e 100644 --- a/tools/bundler.js +++ b/tools/bundler.js @@ -758,8 +758,7 @@ _.extend(ClientTarget.prototype, { var self = this; var templatePath = path.join(__dirname, "app.html.in"); - var template = fs.readFileSync(templatePath); - self.watchSet.addFile(templatePath, Builder.sha1(template)); + var template = watch.readAndWatchFile(self.watchSet, templatePath); var f = require('handlebars').compile(template.toString()); return new Buffer(f({ @@ -1587,9 +1586,13 @@ exports.bundle = function (appDir, outputPath, options) { return server; }; - var includeDefaultTargets = true; - if (fs.existsSync(path.join(appDir, 'no-default-targets'))) - includeDefaultTargets = false; + // Include default targets, unless there's a no-default-targets file in the + // top level of the app. (This is a very hacky interface which will + // change. Note, eg, that .meteor/packages is confusingly ignored in this + // case.) + + var includeDefaultTargets = watch.readAndWatchFile( + watchSet, path.join(appDir, 'no-default-targets')) === null; if (includeDefaultTargets) { // Create a Package object that represents the app @@ -1605,73 +1608,83 @@ exports.bundle = function (appDir, outputPath, options) { } // Pick up any additional targets in /programs - // Step 1: scan for targets and make a list + // Step 1: scan for targets and make a list. We will reload if you create a + // new subdir in 'programs', or create 'programs' itself. var programsDir = path.join(appDir, 'programs'); var programs = []; - if (fs.existsSync(programsDir)) { - _.each(fs.readdirSync(programsDir), function (item) { - if (item.match(/^\./)) - return; // ignore dotfiles - var itemPath = path.join(programsDir, item); + var programsSubdirs = watch.readAndWatchDirectory(watchSet, { + absPath: programsDir, + include: [/\/$/], + exclude: [/^\./] + }); - if (! fs.statSync(itemPath).isDirectory()) - return; // ignore non-directories + _.each(programsSubdirs, function (item) { + // Remove trailing slash. + item = item.substr(0, item.length - 1); - if (item in targets) { - buildmessage.error("duplicate programs named '" + item + "'"); - // Recover by ignoring this program - return; + if (_.has(targets, item)) { + buildmessage.error("duplicate programs named '" + item + "'"); + // Recover by ignoring this program + return; + } + targets[item] = true; // will be overwritten with actual target later + + // Read attributes.json, if it exists + var attrsJsonAbsPath = path.join(programsDir, item, 'attributes.json'); + var attrsJsonRelPath = path.join('programs', item, 'attributes.json'); + var attrsJsonContents = watch.readAndWatchFile( + watchSet, attrsJsonAbsPath); + + var attrsJson = {}; + if (attrsJsonContents !== null) { + try { + attrsJson = JSON.parse(attrsJsonContents); + } catch (e) { + if (! (e instanceof SyntaxError)) + throw e; + buildmessage.error(e.message, { file: attrsJsonRelPath }); + // recover by ignoring attributes.json } + } - // Read attributes.json, if it exists - var attrsJsonPath = path.join(itemPath, 'attributes.json'); - var attrsJsonRelPath = path.join('programs', item, 'attributes.json'); - var attrsJson = {}; - if (fs.existsSync(attrsJsonPath)) { - try { - attrsJson = JSON.parse(fs.readFileSync(attrsJsonPath)); - } catch (e) { - if (! (e instanceof SyntaxError)) - throw e; - buildmessage.error(e.message, { file: attrsJsonRelPath }); - // recover by ignoring attributes.json - } - } - - var isControlProgram = !! attrsJson.isControlProgram; - if (isControlProgram) { - if (controlProgram !== null) { - buildmessage.error( + var isControlProgram = !! attrsJson.isControlProgram; + if (isControlProgram) { + if (controlProgram !== null) { + buildmessage.error( "there can be only one control program ('" + controlProgram + - "' is also marked as the control program)", - { file: attrsJsonRelPath }); - // recover by ignoring that it wants to be the control - // program - } else { - controlProgram = item; - } + "' is also marked as the control program)", + { file: attrsJsonRelPath }); + // recover by ignoring that it wants to be the control + // program + } else { + controlProgram = item; } + } - // Add to list - programs.push({ - type: attrsJson.type || "server", - name: item, - path: itemPath, - client: attrsJson.client, - attrsJsonRelPath: attrsJsonRelPath - }); + // Add to list + programs.push({ + type: attrsJson.type || "server", + name: item, + path: path.join(programsDir, item), + client: attrsJson.client, + attrsJsonRelPath: attrsJsonRelPath }); - } + }); if (! controlProgram) { - var target = makeServerTarget("ctl"); - targets["ctl"] = target; - controlProgram = "ctl"; + if (_.has(targets, 'ctl')) { + buildmessage.error( + "A program named ctl exists but no program has isControlProgram set"); + // recover by not making a control program + } else { + var target = makeServerTarget("ctl"); + targets["ctl"] = target; + controlProgram = "ctl"; + } } - // Step 2: sort the list so that programs are built first (because - // when we build the servers we need to be able to reference the - // clients) + // Step 2: sort the list so that client programs are built first (because + // when we build the servers we need to be able to reference the clients) programs.sort(function (a, b) { a = (a.type === "client") ? 0 : 1; b = (b.type === "client") ? 0 : 1; @@ -1733,12 +1746,16 @@ exports.bundle = function (appDir, outputPath, options) { if (! (controlProgram in targets)) controlProgram = undefined; + // Make sure notice when somebody adds a package to the app packages dir + // that may override a warehouse package. + library.watchLocalPackageDirs(watchSet); + // Write to disk - watchSet = writeSiteArchive(targets, outputPath, { + watchSet.merge(writeSiteArchive(targets, outputPath, { nodeModulesMode: options.nodeModulesMode, builtBy: builtBy, controlProgram: controlProgram - }); + })); success = true; }); diff --git a/tools/library.js b/tools/library.js index 498506599b..0718b8d45e 100644 --- a/tools/library.js +++ b/tools/library.js @@ -1,6 +1,7 @@ var path = require('path'); var _ = require('underscore'); var files = require('./files.js'); +var watch = require('./watch.js'); var packages = require('./packages.js'); var warehouse = require('./warehouse.js'); var bundler = require('./bundler.js'); @@ -127,7 +128,7 @@ _.extend(Library.prototype, { if (! packageDir) { for (var i = 0; i < self.localPackageDirs.length; ++i) { var packageDir = path.join(self.localPackageDirs[i], name); - // XXX or unipackage.json? + // XXX or unipackage.json? see also watchLocalPackageDirs if (fs.existsSync(path.join(packageDir, 'package.js'))) break; packageDir = null; @@ -274,6 +275,23 @@ _.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. + watchLocalPackageDirs: function (watchSet) { + var self = this; + _.each(self.localPackageDirs, function (packageDir) { + var packages = watch.readAndWatchDirectory(watchSet, { + absPath: packageDir, + include: [/\/$/] + }); + _.each(packages, function (p) { + watch.readAndWatchFile(path.join(packageDir, p, 'package.js')); + // XXX unipackage.json too? + }); + }); + }, + // Get all packages available. Returns a map from the package name // to a Package object. // diff --git a/tools/packages.js b/tools/packages.js index fbbca77d4c..1cbcac8bdc 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -223,8 +223,7 @@ _.extend(Slice.prototype, { var absPath = path.resolve(self.pkg.sourceRoot, relPath); var ext = path.extname(relPath).substr(1); var handler = !fileOptions.isAsset && self._getSourceHandler(ext); - var contents = fs.readFileSync(absPath); - self.watchSet.addFile(absPath, Builder.sha1(contents)); + var contents = watch.readAndWatchFile(self.watchSet, absPath); if (! handler) { // If we don't have an extension handler, serve this file as a @@ -1728,15 +1727,7 @@ _.extend(Package.prototype, { // XXX this read has a race with the actual reads that are is used _.each([path.join(appDir, '.meteor', 'packages'), path.join(appDir, '.meteor', 'release')], function (p) { - try { - var hash = Builder.sha1(fs.readFileSync(p)); - } catch (e) { - // If it doesn't exist, just depend on that fact. - if (!e || e.code !== "ENOENT") - throw e; - hash = null; - } - slice.watchSet.addFile(p, hash); + watch.readAndWatchFile(slice.watchSet, p); }); // Determine source files @@ -1746,20 +1737,16 @@ _.extend(Package.prototype, { }); var sourceExclude = [/^\./].concat(ignoreFiles); + // Wrapper around watch.readAndWatchDirectory which takes in and returns + // sourceRoot-relative directories. var readAndWatchDirectory = function (relDir, filters) { filters = filters || {}; var absPath = path.join(self.sourceRoot, relDir); - var contents = watch.readDirectory({ + var contents = watch.readAndWatchDirectory(slice.watchSet, { absPath: absPath, include: filters.include, exclude: filters.exclude }); - slice.watchSet.addDirectory({ - absPath: absPath, - include: filters.include, - exclude: filters.exclude, - contents: contents - }); return _.map(contents, function (x) { return path.join(relDir, x); }); diff --git a/tools/watch.js b/tools/watch.js index 92ebfc751a..65974721b8 100644 --- a/tools/watch.js +++ b/tools/watch.js @@ -74,6 +74,8 @@ var _ = require('underscore'); // file (potentially not the only one that changed or was removed) // + + var WatchSet = function () { var self = this; @@ -90,7 +92,7 @@ var WatchSet = function () { // - 'absPath': absolute path to a directory // - 'include': array of RegExps // - 'exclude': array of RegExps - // - 'contents': array of strings + // - 'contents': array of strings, or null if the directory should not exist // // This represents the assertion that 'absPath' is a directory and that // 'contents' is its immediate contents, as filtered by the regular @@ -132,8 +134,9 @@ _.extend(WatchSet.prototype, { return; if (_.isEmpty(options.include)) return; - var contents = _.clone(options.contents || []); - contents.sort(); + var contents = _.clone(options.contents); + if (contents) + contents.sort(); self.directories.push({ absPath: options.absPath, @@ -320,12 +323,9 @@ _.extend(Watcher.prototype, { if (oldHash === undefined) throw new Error("Checking unknown file " + absPath); - try { - var contents = fs.readFileSync(absPath); - } catch (e) { - // Rethrow most errors. - if (!e || (e.code !== 'ENOENT' && e.code !== 'EISDIR')) - throw e; + var contents = readFile(absPath); + + if (contents === null) { // File does not exist (or is a directory). // Is this what we expected? if (oldHash === null) @@ -341,10 +341,7 @@ _.extend(Watcher.prototype, { return true; } - var crypto = require('crypto'); - var hasher = crypto.createHash('sha1'); - hasher.update(contents); - var newHash = hasher.digest('hex'); + var newHash = sha1(contents); // Unchanged? if (newHash === oldHash) @@ -366,7 +363,7 @@ _.extend(Watcher.prototype, { exclude: info.exclude }); - // If newContents is null (no directory) or the directory has changed, fire. + // If the directory has changed (including being deleted or created). if (!_.isEqual(info.contents, newContents)) { self._fire(); return true; @@ -522,9 +519,44 @@ var isUpToDate = function (watchSet) { return upToDate; }; +// Options should have absPath/include/exclude. +var readAndWatchDirectory = function (watchSet, options) { + var contents = readDirectory(options); + watchSet.addDirectory(_.extend({contents: contents}, options)); + return contents; +}; + +var readAndWatchFile = function (watchSet, absPath) { + var contents = readFile(absPath); + var hash = contents === null ? null : sha1(contents); + watchSet.addFile(absPath, hash); + return contents; +}; + +var readFile = function (absPath) { + try { + return fs.readFileSync(absPath); + } catch (e) { + // Rethrow most errors. + if (!e || (e.code !== 'ENOENT' && e.code !== 'EISDIR')) + throw e; + // File does not exist (or is a directory). + return null; + } +}; + +var sha1 = function (contents) { + var crypto = require('crypto'); + var hash = crypto.createHash('sha1'); + hash.update(contents); + return hash.digest('hex'); +}; + _.extend(exports, { WatchSet: WatchSet, Watcher: Watcher, readDirectory: readDirectory, - isUpToDate: isUpToDate + isUpToDate: isUpToDate, + readAndWatchDirectory: readAndWatchDirectory, + readAndWatchFile: readAndWatchFile }); From 1a5d695ee433922c62d9b63789fffcbce5c46af9 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 31 Jul 2013 17:43:05 -0700 Subject: [PATCH 159/175] fix minor bugs. bundler-test passes! --- tools/library.js | 3 ++- tools/packages.js | 9 ++++++--- tools/run.js | 3 +++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/tools/library.js b/tools/library.js index 0718b8d45e..5909af630d 100644 --- a/tools/library.js +++ b/tools/library.js @@ -286,7 +286,8 @@ _.extend(Library.prototype, { include: [/\/$/] }); _.each(packages, function (p) { - watch.readAndWatchFile(path.join(packageDir, p, 'package.js')); + watch.readAndWatchFile(watchSet, + path.join(packageDir, p, 'package.js')); // XXX unipackage.json too? }); }); diff --git a/tools/packages.js b/tools/packages.js index 1cbcac8bdc..dc51738bef 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -1812,9 +1812,10 @@ _.extend(Package.prototype, { var assetDirs = readAndWatchDirectory('', { include: [new RegExp('^' + assetDir + '/$')] }); - if (_.isEmpty(assetDirs)) { - if (_.isEqual(assetDirs, [assetDir + '/'])) - throw new Error("Surprising assetDirs: " + assetDirs); + + if (!_.isEmpty(assetDirs)) { + if (!_.isEqual(assetDirs, [assetDir + '/'])) + throw new Error("Surprising assetDirs: " + JSON.stringify(assetDirs)); while (!_.isEmpty(assetDirs)) { dir = assetDirs.shift(); @@ -1823,9 +1824,11 @@ _.extend(Package.prototype, { // Find asset files in this directory. var assetsAndSubdirs = readAndWatchDirectory(dir, { + include: [/.?/], // we DO look under dot directories here exclude: ignoreFiles }); + _.each(assetsAndSubdirs, function (item) { if (item[item.length - 1] === '/') { // Recurse on this directory. diff --git a/tools/run.js b/tools/run.js index 3ad0fdf075..142754a13f 100644 --- a/tools/run.js +++ b/tools/run.js @@ -472,6 +472,9 @@ exports.run = function (context, options) { }; var startWatching = function (watchSet) { + if (process.env.METEOR_DEBUG_WATCHSET) + console.log(JSON.stringify(watchSet, null, 2)); + if (!Status.shouldRestart) return; From a2803fd111cbde09a1d40a0d29f8e5ecec97ab45 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 31 Jul 2013 18:02:10 -0700 Subject: [PATCH 160/175] watch nonexistent dirs better --- tools/watch.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/watch.js b/tools/watch.js index 65974721b8..a31b84f79b 100644 --- a/tools/watch.js +++ b/tools/watch.js @@ -458,9 +458,10 @@ _.extend(Watcher.prototype, { ); } catch (e) { // Can happen if the directory doesn't exist, in which case we should - // fire. + // fire if it should be there. if (e && e.code === "ENOENT") { - self._fire(); + if (info.contents !== null) + self._fire(); return; } throw e; From 52948bed50f0d6dbc014725bba67e8a8967f6590 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 31 Jul 2013 18:14:03 -0700 Subject: [PATCH 161/175] fix HTML sort hack. run-tools-tests passes! --- tools/files.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/files.js b/tools/files.js index 3db5d821a4..935051ad60 100644 --- a/tools/files.js +++ b/tools/files.js @@ -48,7 +48,7 @@ _.extend(exports, { // probably have a way to request this treatment (load order dependency // tags?) .. who knows. var ishtml_a = path.extname(a) === '.html'; - var ishtml_b = path.extname(a) === '.html'; + var ishtml_b = path.extname(b) === '.html'; if (ishtml_a !== ishtml_b) { return (ishtml_a ? -1 : 1); } From fac5e219fd9cf1fc06c7e5842f9d1933533e8f53 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 31 Jul 2013 22:44:57 -0700 Subject: [PATCH 162/175] Don't put the old pluginWatchSet onto a Package until we know we're using the unipackage. --- tools/packages.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/packages.js b/tools/packages.js index dc51738bef..62a42ace1a 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -1898,11 +1898,13 @@ _.extend(Package.prototype, { sliceWatchSets[sliceTag] = watchSet; }); - self.pluginWatchSet = watch.WatchSet.fromJSON( + // We do NOT put this (or anything!) onto self until we've passed the + // onlyIfUpToDate check. + var pluginWatchSet = watch.WatchSet.fromJSON( buildInfoJson.pluginDependencies); // This might be redundant (since pluginWatchSet was probably merged into // each slice watchSet when it was built) but shouldn't hurt. - mergedWatchSet.merge(self.pluginWatchSet); + mergedWatchSet.merge(pluginWatchSet); // If we're supposed to check the dependencies, go ahead and do so if (options.onlyIfUpToDate) { @@ -1933,6 +1935,7 @@ _.extend(Package.prototype, { }; self.defaultSlices = mainJson.defaultSlices; self.testSlices = mainJson.testSlices; + self.pluginWatchSet = pluginWatchSet; _.each(mainJson.plugins, function (pluginMeta) { rejectBadPath(pluginMeta.path); From 974de6bac3b5a23f8fc90b5504642813a08f66c0 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 31 Jul 2013 22:59:46 -0700 Subject: [PATCH 163/175] Update BUILT_BY so everything gets rebuilt. --- tools/packages.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/packages.js b/tools/packages.js index 62a42ace1a..f3a3a07f39 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -20,7 +20,7 @@ var sourcemap = require('source-map'); // unipackage/slice changes, but this version (which is build-tool-specific) can // change when the the contents (not structure) of the built output changes. So // eg, if we improve the linker's static analysis, this should be bumped. -exports.BUILT_BY = 'meteor/6'; +exports.BUILT_BY = 'meteor/7'; // Like Perl's quotemeta: quotes all regexp metacharacters. See // https://github.com/substack/quotemeta/blob/master/index.js From 625d98768f242b487ac8835452198ede93521b42 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 31 Jul 2013 23:17:52 -0700 Subject: [PATCH 164/175] fix comment --- tools/packages.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/packages.js b/tools/packages.js index f3a3a07f39..3c59cd3a0f 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -1724,7 +1724,7 @@ _.extend(Package.prototype, { self.slices.push(slice); // Watch control files for changes - // XXX this read has a race with the actual reads that are is used + // XXX this read has a race with the actual reads that are used _.each([path.join(appDir, '.meteor', 'packages'), path.join(appDir, '.meteor', 'release')], function (p) { watch.readAndWatchFile(slice.watchSet, p); From 1d45b0bfa88f7aa55e2ff8603e8ba145b6c0b4c6 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 31 Jul 2013 23:26:51 -0700 Subject: [PATCH 165/175] update comments --- tools/packages.js | 2 ++ tools/watch.js | 88 ++++++++++++++++++----------------------------- 2 files changed, 36 insertions(+), 54 deletions(-) diff --git a/tools/packages.js b/tools/packages.js index 3c59cd3a0f..a9f368bd7e 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -1770,6 +1770,7 @@ _.extend(Package.prototype, { otherSliceRegExp].concat(sourceExclude) }); + // XXX avoid infinite recursion with bad symlinks while (!_.isEmpty(sourceDirectories)) { var dir = sourceDirectories.shift(); // remove trailing slash @@ -1813,6 +1814,7 @@ _.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)); diff --git a/tools/watch.js b/tools/watch.js index a31b84f79b..d4b05bdb37 100644 --- a/tools/watch.js +++ b/tools/watch.js @@ -2,77 +2,56 @@ var fs = require("fs"); var path = require("path"); var _ = require('underscore'); -// XXX XXX redo this doc - - // Watch for changes to a set of files, and the first time that any of // the files change, call a user-provided callback. (If you want a // second callback, you'll need to create a second Watcher.) // +// You describe the structure you want to watch in a WatchSet; you then create a +// Watcher to watch it. Watcher does not mutate WatchSet, so you can create +// several Watchers from the same WatchSet. WatchSet can be easily converted to +// and from JSON for serialization. +// // You can set up two kinds of watches, file and directory watches. // -// In a file watch, you provide an absolute path to a file and a SHA1 -// (encoded as hex) of the contents of that file. If the file ever -// changes so that its contents no longer match that SHA1, the -// callback triggers. +// In a file watch, you provide an absolute path to a file and a SHA1 (encoded +// as hex) of the contents of that file. If the file ever changes so that its +// contents no longer match that SHA1, the callback triggers. You can also +// provide `null` for the SHA1, which means the file should not exist. // -// In a directory watch, you provide an absolute path to a directory -// and two lists of regular expressions specifying the files to -// include or exclude. If there is ever a file in the directory or its -// children that matches the criteria set up by the regular -// expressions, but that IS NOT present as a file watch, then the -// callback triggers. +// In a directory watch, you provide an absolute path to a directory, +// two lists of regular expressions specifying the entries to +// include and exclude, and an array of which entries to expect // -// For directory watches, the regular expressions work as follows. You -// provide two arrays of regular expressions, an include list and an -// exclude list. A file in the directory matches if it matches at -// least one regular expression in the include list, and doesn't match -// any regular expressions in the exclude list. Subdirectories are -// included recursively, as long as their names do not match any -// regular expression in the exclude list. +// For directory watches, the regular expressions work as follows. You provide +// two arrays of regular expressions, an include list and an exclude list. An +// entry in the directory matches if it matches at least one regular expression +// in the include list, and doesn't match any regular expressions in the exclude +// list. The string that is matched against the regular expression ends with a +// '/' if the entry is directory. There is NO IMPLICIT RECURSION here: a +// directory watch ONLY watches the immediate children of the directory! If you +// want a recursive watch, you need to do the recursive walk while building the +// WatchSet and add a bunch of separate directory watches. // -// When multiple directory watches are set up, say on a directory A -// and its subdirectory B, the most specific watch takes precedence in -// each directory. So only B's include/exclude lists will be checked -// in B. +// There can be multiple directory watches on the same directory. There is no +// relationship between the files found in directory watches and the files +// watched by file watches; they are parallel mechanisms. // -// Regular expressions are checked only against individual path -// components (the actual name of the file or the subdirectory), not -// against the entire path. +// Regular expressions are checked only against individual path components (the +// actual name of the file or the subdirectory) plus the trailing '/' for +// directories, not against the entire path. // // You can call stop() to stop watching and tear down the // watcher. Calling stop() guarantees that you will not receive a // callback (if you have not already.) Calling stop() is unnecessary // if you've received a callback. // -// A limitation of the current implementation is that if you set up a -// directory watch on a directory A, and A does not exist at the time -// the Watcher is created but is then created later, then A will not -// be monitored. (Of course, this limitation only applies to the roots -// of the directory watches. If A exists at the time the watch is -// created, and a subdirectory B is later created, it will be properly -// detected. Likewise if A exists and is then deleted it will be -// detected.) -// -// To do a "one-shot" (to see if any files have been modified, -// compared to the dependencies, at a particular point in time, just -// create a Watcher and see if your onChange function was called -// before the Watcher constructor changed. (Then call stop() as -// usual.) -// -// XXX This should be reengineered so that dependency information from -// multiple sources can be easily merged in a generic way. Possibly in -// this new model subdirectories would be allowed in include/exclude -// patterns, and multiple directory rules would be OR'd rather than -// taking the most specific rule. -// -// Options may include -// - files: see self.files comment below -// - directories: see self.directories comment below -// - onChange: the function to call when the first change is detected. -// received one argument, the absolute path to a changed or removed -// file (potentially not the only one that changed or was removed) +// To do a "one-shot" (to see if any files have been modified, compared to the +// dependencies, at a particular point in time), use the isUpToDate function. // +// XXX Symlinks are currently treated transparently: we treat them as the thing +// they point to (ie, as a directory if they point to a directory, as +// nonexistent if they point to something nonexist, etc). Not sure if this is +// correct. @@ -253,6 +232,7 @@ var readDirectory = function (options) { // it was never there in the first place. return; } + // XXX if we're on windows, I guess it's possible for files to end with '/'. if (stats.isDirectory()) entry += '/'; contentsWithSlashes.push(entry); From f276b3414857df3333b635b2ad9534666a3e8139 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 31 Jul 2013 23:50:31 -0700 Subject: [PATCH 166/175] comment about updating BUILT_BY when js-analyze changes --- packages/js-analyze/package.js | 4 ++++ tools/packages.js | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/packages/js-analyze/package.js b/packages/js-analyze/package.js index 09a0cbc200..71087fefc3 100644 --- a/packages/js-analyze/package.js +++ b/packages/js-analyze/package.js @@ -1,3 +1,7 @@ +// IF YOU MAKE ANY CHANGES TO THIS PACKAGE THAT COULD AFFECT ITS OUTPUT, YOU +// MUST UPDATE BUILT_BY IN tools/packages.js. Otherwise packages may not be +// rebuilt with the new changes. + Package.describe({ summary: "JavaScript code analysis for Meteor", internal: true diff --git a/tools/packages.js b/tools/packages.js index a9f368bd7e..374841c722 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -20,6 +20,12 @@ var sourcemap = require('source-map'); // unipackage/slice changes, but this version (which is build-tool-specific) can // change when the the contents (not structure) of the built output changes. So // eg, if we improve the linker's static analysis, this should be bumped. +// +// You should also update this whenever you update any of the packages used +// directly by the unipackage creation process (eg js-analyze) since they do not +// end up as watched dependencies. (At least for now, packages only used in +// target creation (eg minifiers and dev-bundle-fetcher) don't require you to +// update BUILT_BY, though you will need to quit and rerun "meteor run".) exports.BUILT_BY = 'meteor/7'; // Like Perl's quotemeta: quotes all regexp metacharacters. See From 940a8f32500a58a0ef4f914bf5ccfff4d35218a5 Mon Sep 17 00:00:00 2001 From: Andrew Mao Date: Tue, 30 Jul 2013 18:34:35 -0400 Subject: [PATCH 167/175] added some checks for the where argument --- tools/packages.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tools/packages.js b/tools/packages.js index 374841c722..381e856f9e 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -1422,10 +1422,19 @@ _.extend(Package.prototype, { return x; return x ? [x] : []; }; + + var places = ['client', 'server']; var toWhereArray = function (where) { - if (where instanceof Array) + if (where instanceof Array) { + if (_.difference(where, places).length > 0) + buildmessage.error(where + " is not a valid argument.", + { useMyCaller: true }); return where; - return where ? [where] : ['client', 'server']; + } + if (_.indexOf(places, where) === -1 ) + buildmessage.error(where + " is not a valid argument.", + { useMyCaller: true }); + return where ? [where] : places; }; var api = { From 8aa63bcb3a5732fe30aa846f3b603a83738a282c Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 1 Aug 2013 00:29:14 -0700 Subject: [PATCH 168/175] Cleanups for PR 1263: - Display proper caller location (requires extending useMyCaller to take a number) - Recover by only returning valid 'where's so we don't crash later. --- tools/buildmessage.js | 5 ++++- tools/packages.js | 27 +++++++++++++++++---------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/tools/buildmessage.js b/tools/buildmessage.js index 89c60bb9f3..86e7b3c8cc 100644 --- a/tools/buildmessage.js +++ b/tools/buildmessage.js @@ -325,6 +325,7 @@ var markBoundary = function (f) { // 'file', 'line', and 'column' (overwriting any values passed in // for those.) It also captures the user portion of the stack, // starting at and including the caller's caller. +// If this is a number instead of 'true', skips that many stack frames. // - downcase: if true, the first character of `message` will be // converted to lower case. // - secondary: ignore this error if there are are already other @@ -350,7 +351,9 @@ var error = function (message, options) { if ('useMyCaller' in info) { if (info.useMyCaller) { info.stack = parseStack(new Error()).slice(2); - var caller = info.stack[0]; + var howManyToSkip = ( + typeof info.useMyCaller === "number" ? info.useMyCaller : 0); + var caller = info.stack[howManyToSkip]; info.func = caller.func; info.file = caller.file; info.line = caller.line; diff --git a/tools/packages.js b/tools/packages.js index 381e856f9e..18ea550515 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -1423,18 +1423,25 @@ _.extend(Package.prototype, { return x ? [x] : []; }; - var places = ['client', 'server']; + var allWheres = ['client', 'server']; var toWhereArray = function (where) { - if (where instanceof Array) { - if (_.difference(where, places).length > 0) - buildmessage.error(where + " is not a valid argument.", - { useMyCaller: true }); - return where; + if (!(where instanceof Array)) { + where = where ? [where] : allWheres; } - if (_.indexOf(places, where) === -1 ) - buildmessage.error(where + " is not a valid argument.", - { useMyCaller: true }); - return where ? [where] : places; + where = _.uniq(where); + var realWhere = _.intersection(where, allWheres); + if (realWhere.length !== where.length) { + var badWheres = _.difference(where, allWheres); + // avoid using _.each so as to not add more frames to skip + for (var i = 0; i < badWheres.length; ++i) { + buildmessage.error( + "Invalid 'where' argument: '" + badWheres[i] + "'", + // skip toWhereArray in addition to the actual API function + {useMyCaller: 1}); + }; + // recover by using the real ones only + } + return realWhere; }; var api = { From 360e654f63b155492c561cc6eaa9e76be42a7be6 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 1 Aug 2013 12:17:45 -0700 Subject: [PATCH 169/175] Don't crash when updating unpinned apps. --- tools/meteor.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tools/meteor.js b/tools/meteor.js index a483d8b011..df708d31df 100644 --- a/tools/meteor.js +++ b/tools/meteor.js @@ -641,11 +641,14 @@ Fiber(function () { // APIs). var oldManifest = warehouse.ensureReleaseExistsAndReturnManifest( appRelease); - var upgraders = _.difference(context.releaseManifest.upgraders || [], - oldManifest.upgraders || []); - _.each(upgraders, function (upgrader) { - require("./upgraders.js").runUpgrader(upgrader, context.appDir); - }); + // We can only run upgrades from pinned apps. + if (oldManifest) { + var upgraders = _.difference(context.releaseManifest.upgraders || [], + oldManifest.upgraders || []); + _.each(upgraders, function (upgrader) { + require("./upgraders.js").runUpgrader(upgrader, context.appDir); + }); + } // This is the right spot to do any other changes we need to the app in // order to update it for the new release . From ecd94142c8ab4776d0963c0c6c3ba63b53f76f1f Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 1 Aug 2013 11:13:02 -0700 Subject: [PATCH 170/175] Save to buildinfo the library resolution of all "use"d package. We call these the pluginProviderPackages because these are the packages that, if they change, could affect the set of plugins available to this package (and thus require it to be rebuilt). --- tools/library.js | 2 +- tools/packages.js | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/tools/library.js b/tools/library.js index 5909af630d..0230bb8bea 100644 --- a/tools/library.js +++ b/tools/library.js @@ -174,7 +174,7 @@ _.extend(Library.prototype, { } // Load package from disk - var pkg = new packages.Package(self); + var pkg = new packages.Package(self, packageDir); if (fs.existsSync(path.join(packageDir, 'unipackage.json'))) { // It's an already-built package pkg.initFromUnipackage(name, packageDir); diff --git a/tools/packages.js b/tools/packages.js index 18ea550515..48e06469ec 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -468,6 +468,9 @@ _.extend(Slice.prototype, { // a plugin). _.each(self._activePluginPackages(), function (otherPkg) { self.watchSet.merge(otherPkg.pluginWatchSet); + // XXX this assumes this is not overwriting something different + self.pkg.pluginProviderPackages[otherPkg.name] = + otherPkg.packageDirectoryForBuildInfo; }); self.prelinkFiles = results.files; @@ -729,7 +732,7 @@ _.extend(Slice.prototype, { // (find better names, though.) var nextPackageId = 1; -var Package = function (library) { +var Package = function (library, packageDirectoryForBuildInfo) { var self = this; // A unique ID (guaranteed to not be reused in this process -- if @@ -757,6 +760,14 @@ var Package = function (library) { // it's still nice to get it right.) null if loaded from unipackage. self.serveRoot = null; + // The package's directory. This is used only by other packages that use this + // package in their buildinfo.json (to detect that they need to be rebuilt if + // the library's resolution of the package name changes); it is not used to + // read files or anything else. Notably, it should be the same if a package is + // read from a source tree or read from the .build unipackage inside that + // source tree. + self.packageDirectoryForBuildInfo = packageDirectoryForBuildInfo; + // Package library that should be used to resolve this package's // dependencies self.library = library; @@ -801,6 +812,9 @@ var Package = function (library) { // Complete only when pluginsBuilt is true. self.pluginWatchSet = new watch.WatchSet(); + // XXX DOC + self.pluginProviderPackages = {}; + // True if plugins have been initialized (if _ensurePluginsInitialized has // been called) self._pluginsInitialized = false; @@ -1960,6 +1974,7 @@ _.extend(Package.prototype, { self.defaultSlices = mainJson.defaultSlices; self.testSlices = mainJson.testSlices; self.pluginWatchSet = pluginWatchSet; + self.pluginProviderPackages = buildInfoJson.pluginProviderPackages || {}; _.each(mainJson.plugins, function (pluginMeta) { rejectBadPath(pluginMeta.path); @@ -2146,6 +2161,7 @@ _.extend(Package.prototype, { builtBy: exports.BUILT_BY, sliceDependencies: { }, pluginDependencies: self.pluginWatchSet.toJSON(), + pluginProviderPackages: self.pluginProviderPackages, source: options.buildOfPath || undefined }; From 17699eb399977a9cb0b00fac3063bd4e447fac2a Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 1 Aug 2013 11:59:52 -0700 Subject: [PATCH 171/175] Rebuild a package when its dependencies resolve to a different path. Also simplify a bunch of library code that thought it might have to rebuild warehouse packages, now that the warehouse contains unipackages. --- tools/library.js | 149 ++++++++++++++++++++++------------------------ tools/packages.js | 70 +++++++++++----------- 2 files changed, 108 insertions(+), 111 deletions(-) diff --git a/tools/library.js b/tools/library.js index 0230bb8bea..fc9c039c55 100644 --- a/tools/library.js +++ b/tools/library.js @@ -35,15 +35,13 @@ var Library = function (options) { return stats.isDirectory(); }); - self.loadedPackages = {}; // map from package name to Package self.overrides = {}; // package name to package directory - // map from package name to: + // both map from package name to: // - pkg: cached Package object // - packageDir: directory from which it was loaded - // - revalidate: true if the package needs to have its dependencies - // checked before it can be reused self.softReloadCache = {}; + self.loadedPackages = {}; }; _.extend(Library.prototype, { @@ -54,7 +52,7 @@ _.extend(Library.prototype, { // two overrides for the same packageName. override: function (packageName, packageDir) { var self = this; - if (packageName in self.overrides) + if (_.has(self.overrides, packageName)) throw new Error("Duplicate override for package '" + packageName + "'"); self.overrides[packageName] = path.resolve(packageDir); }, @@ -62,7 +60,7 @@ _.extend(Library.prototype, { // Undo an override previously set up with override(). removeOverride: function (packageName) { var self = this; - if (!(packageName in self.overrides)) + if (!_.has(self.overrides, packageName)) throw new Error("No override present for package '" + packageName + "'"); delete self.loadedPackages[packageName]; delete self.overrides[packageName]; @@ -74,20 +72,62 @@ _.extend(Library.prototype, { // If soft is false, the default, the cache is totally flushed and // all packages are reloaded unconditionally. // - // If soft is true, then packages from the warehouse aren't reloaded - // (they are supposed to be immutable, after all), and if we loaded - // a built package with dependency info, we won't reload it if the - // dependency info says that its source files are still up to - // date. The ideas is that assuming the user is "following the - // rules", this will correctly reload any changed packages while in - // most cases avoiding nearly all reloading. + // If soft is true, then built packages without dependency info (such as those + // from the warehouse) aren't reloaded (there's no way to rebuild them, after + // all), and if we loaded a built package with dependency info, we won't + // reload it if the dependency info says that its source files are still up to + // date. The ideas is that assuming the user is "following the rules", this + // will correctly reload any changed packages while in most cases avoiding + // nearly all reloading. refresh: function (soft) { var self = this; soft = soft || false; + self.softReloadCache = soft ? self.loadedPackages : {}; self.loadedPackages = {}; - if (! soft) - self.softReloadCache = {}; + }, + + // Given a package name as a string, returns the absolute path to the package + // directory (which is the *source* tree in the source-with-built-unipackage + // case, not the .build directory), or null if not found. + // + // Does NOT load the package or make any recursive calls, so can safely be + // called from Package initialization code. Intended primarily for comparison + // to the packageDirForBuildInfo field on a Package object; also used + // internally to implement 'get'. + findPackageDirectory: function (name) { + var self = this; + + // Packages cached from previous calls + if (_.has(self.loadedPackages, name)) { + return self.loadedPackages[name].packageDir; + } + + // If there's an override for this package, use that without + // looking at any other options. + if (_.has(self.overrides, name)) + return self.overrides[name]; + + 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'))) + return packageDir; + } + + // Try the Meteor distribution, if we have one. + var version = self.releaseManifest && self.releaseManifest.packages[name]; + if (version) { + packageDir = path.join(warehouse.getWarehouseDir(), + 'packages', name, version); + if (! fs.existsSync(packageDir)) + throw new Error("Package missing from warehouse: " + name + + " version " + version); + return packageDir; + } + + // Nope! + return null; }, // Given a package name as a string, retrieve a Package object. If @@ -107,8 +147,6 @@ _.extend(Library.prototype, { // refresh(). get: function (name, throwOnError) { var self = this; - var packageDir; - var fromWarehouse = false; // Passed a Package? if (name instanceof packages.Package) @@ -116,35 +154,10 @@ _.extend(Library.prototype, { // Packages cached from previous calls if (_.has(self.loadedPackages, name)) { - return self.loadedPackages[name]; + return self.loadedPackages[name].pkg; } - // If there's an override for this package, use that without - // looking at any other options. - if (name in self.overrides) - packageDir = self.overrides[name]; - - // Try localPackageDirs - if (! packageDir) { - 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'))) - break; - packageDir = null; - } - } - - // Try the Meteor distribution, if we have one. - var version = self.releaseManifest && self.releaseManifest.packages[name]; - if (! packageDir && version) { - var packageDir = path.join(warehouse.getWarehouseDir(), - 'packages', name, version); - if (! fs.existsSync(packageDir)) - throw new Error("Package missing from warehouse: " + name + - " version " + version); - fromWarehouse = true; - } + var packageDir = self.findPackageDirectory(name); if (! packageDir) { if (throwOnError === false) @@ -161,16 +174,16 @@ _.extend(Library.prototype, { if (_.has(self.softReloadCache, name)) { var entry = self.softReloadCache[name]; - if (entry.packageDir === packageDir && - (! entry.revalidate || entry.pkg.checkUpToDate())) { + // Either we will decide that the cache is invalid, or we will "upgrade" + // this entry into loadedPackages. Either way, it's not needed in + // softReloadCache any more. + delete self.softReloadCache[name]; + + if (entry.packageDir === packageDir && entry.pkg.checkUpToDate()) { // Cache hit - self.loadedPackages[name] = entry.pkg; + self.loadedPackages[name] = entry; return entry.pkg; } - - // Package has either changed, or it has been shadowed by a - // package in a different location. - delete self.softReloadCache[name]; } // Load package from disk @@ -178,30 +191,19 @@ _.extend(Library.prototype, { if (fs.existsSync(path.join(packageDir, 'unipackage.json'))) { // It's an already-built package pkg.initFromUnipackage(name, packageDir); - self.loadedPackages[name] = pkg; + self.loadedPackages[name] = {pkg: pkg, packageDir: packageDir}; } else { - // It's a source tree + // It's a source tree. Does it have a built unipackage inside it? var buildDir = path.join(packageDir, '.build'); if (fs.existsSync(buildDir) && pkg.initFromUnipackage(name, buildDir, - { onlyIfUpToDate: ! fromWarehouse, + { onlyIfUpToDate: true, buildOfPath: packageDir })) { // We already had a build and it was up to date. - self.loadedPackages[name] = pkg; + self.loadedPackages[name] = {pkg: pkg, packageDir: packageDir}; } else { - // Either we didn't have a build, or it was out of date (and - // as a transitional matter until the only thing the warehouse - // contains is unipackages, we don't do an up-to-date check on - // warehouse packages, for efficiency.) Build the package. - // - // As a temporary, transitional optimization, assume that any - // source trees in the warehouse have already had their npm - // dependencies fetched. The 0.6.0 installer does this - // (rather, it downloads packages that already have their npm - // dependencies inside of them), and during the transitional - // period where we still have source trees in the warehouse - // AND the unipackage format can't handle packages with - // extensions, this will reduce startup time. + // Either we didn't have a build, or it was out of date. Build the + // package. buildmessage.enterJob({ title: "building package `" + name + "`", rootPath: packageDir @@ -215,9 +217,8 @@ _.extend(Library.prototype, { // forever. (build() needs the dependencies because it needs // to look at the handlers registered by any plugins in the // packages that we use.) - pkg.initFromPackageDir(name, packageDir, - { skipNpmUpdate: fromWarehouse }); - self.loadedPackages[name] = pkg; + pkg.initFromPackageDir(name, packageDir); + self.loadedPackages[name] = {pkg: pkg, packageDir: packageDir}; pkg.build(); if (! buildmessage.jobHasMessages() && // ensure no errors! @@ -230,12 +231,6 @@ _.extend(Library.prototype, { } } - self.softReloadCache[name] = { - packageDir: packageDir, - revalidate: ! fromWarehouse, - pkg: pkg - }; - return pkg; }, diff --git a/tools/packages.js b/tools/packages.js index 48e06469ec..a038cbc484 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -1090,13 +1090,6 @@ _.extend(Package.prototype, { // directory. This function does not retrieve the package's // dependencies from the library, and on return, the package will be // in an unbuilt state. - // - // options: - // - skipNpmUpdate: if true, don't refresh .npm/package/node_modules (for - // packages that use Npm.depend). Only use this when you are - // certain that .npm/package/node_modules was previously created by some - // other means, and you're certain that the package's Npm.depend - // instructions haven't changed since then. initFromPackageDir: function (name, dir, options) { var self = this; var isPortable = true; @@ -1652,31 +1645,25 @@ _.extend(Package.prototype, { // XXX maybe there should be separate NPM dirs for use vs test? var packageNpmDir = path.resolve(path.join(self.sourceRoot, '.npm', 'package')); - var npmOk = true; - if (! options.skipNpmUpdate) { - // If this package was previously built with pre-linker versions, it may - // have files directly inside `.npm` instead of nested inside - // `.npm/package`. Clean them up if they are there. - var preLinkerFiles = [ - 'npm-shrinkwrap.json', 'README', '.gitignore', 'node_modules']; - _.each(preLinkerFiles, function (f) { - files.rm_recursive(path.join(self.sourceRoot, '.npm', f)); - }); - - // go through a specialized npm dependencies update process, - // ensuring we don't get new versions of any - // (sub)dependencies. this process also runs mostly safely - // multiple times in parallel (which could happen if you have - // two apps running locally using the same package) - // We run this even if we have no dependencies, because we might - // need to delete dependencies we used to have. - npmOk = meteorNpm.updateDependencies(name, packageNpmDir, - npmDependencies); - } + // If this package was previously built with pre-linker versions, it may + // have files directly inside `.npm` instead of nested inside + // `.npm/package`. Clean them up if they are there. + var preLinkerFiles = [ + 'npm-shrinkwrap.json', 'README', '.gitignore', 'node_modules']; + _.each(preLinkerFiles, function (f) { + files.rm_recursive(path.join(self.sourceRoot, '.npm', f)); + }); + // go through a specialized npm dependencies update process, + // ensuring we don't get new versions of any + // (sub)dependencies. this process also runs mostly safely + // multiple times in parallel (which could happen if you have + // two apps running locally using the same package) + // We run this even if we have no dependencies, because we might + // need to delete dependencies we used to have. var nodeModulesPath = null; - if (npmOk) { + if (meteorNpm.updateDependencies(name, packageNpmDir, npmDependencies)) { nodeModulesPath = path.join(packageNpmDir, 'node_modules'); if (! meteorNpm.dependenciesArePortable(packageNpmDir)) isPortable = false; @@ -1943,6 +1930,7 @@ _.extend(Package.prototype, { // This might be redundant (since pluginWatchSet was probably merged into // each slice watchSet when it was built) but shouldn't hurt. mergedWatchSet.merge(pluginWatchSet); + var pluginProviderPackages = buildInfoJson.pluginProviderPackages || {}; // If we're supposed to check the dependencies, go ahead and do so if (options.onlyIfUpToDate) { @@ -1962,7 +1950,7 @@ _.extend(Package.prototype, { return false; } - if (! self.checkUpToDate(mergedWatchSet)) + if (! self.checkUpToDate(mergedWatchSet, pluginProviderPackages)) return false; } @@ -1974,7 +1962,7 @@ _.extend(Package.prototype, { self.defaultSlices = mainJson.defaultSlices; self.testSlices = mainJson.testSlices; self.pluginWatchSet = pluginWatchSet; - self.pluginProviderPackages = buildInfoJson.pluginProviderPackages || {}; + self.pluginProviderPackages = pluginProviderPackages; _.each(mainJson.plugins, function (pluginMeta) { rejectBadPath(pluginMeta.path); @@ -2099,9 +2087,10 @@ _.extend(Package.prototype, { // files have been modified.) True if we have dependency info and it says that // the package is up-to-date. False if a source file has changed. // - // The argument _watchSet is used when reading from disk when there are no - // slices yet; don't pass it from outside this file. - checkUpToDate: function (_watchSet) { + // The arguments _watchSet and _pluginProviderPackages are used when reading + // from disk when there are no slices yet; don't pass them from outside this + // file. + checkUpToDate: function (_watchSet, _pluginProviderPackages) { var self = this; if (!_watchSet) { @@ -2113,6 +2102,19 @@ _.extend(Package.prototype, { _watchSet.merge(slice.watchSet); }); } + if (!_pluginProviderPackages) { + _pluginProviderPackages = self.pluginProviderPackages; + } + + // Are all of the packages we directly use (which can provide plugins which + // affect compilation) resolving to the same directory? (eg, have we updated + // our release version to something with a new version of a package?) + var packageResolutionsSame = _.all( + _pluginProviderPackages, function (packageDir, name) { + return self.library.findPackageDirectory(name) === packageDir; + }); + if (!packageResolutionsSame) + return false; return watch.isUpToDate(_watchSet); }, From 2c33d54e5eb855f27bf24b05ddaae0b9f4c57247 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 1 Aug 2013 12:03:58 -0700 Subject: [PATCH 172/175] BUILT_BY bump --- tools/packages.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/packages.js b/tools/packages.js index a038cbc484..6f1c5deb7a 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -26,7 +26,7 @@ var sourcemap = require('source-map'); // end up as watched dependencies. (At least for now, packages only used in // target creation (eg minifiers and dev-bundle-fetcher) don't require you to // update BUILT_BY, though you will need to quit and rerun "meteor run".) -exports.BUILT_BY = 'meteor/7'; +exports.BUILT_BY = 'meteor/8'; // Like Perl's quotemeta: quotes all regexp metacharacters. See // https://github.com/substack/quotemeta/blob/master/index.js From 8de4b48d61902fca803b8fc5562ab579cb148de8 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 1 Aug 2013 12:07:05 -0700 Subject: [PATCH 173/175] document and rename pluginProviderPackageDirs --- tools/packages.js | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/tools/packages.js b/tools/packages.js index 6f1c5deb7a..bec44f5855 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -469,7 +469,7 @@ _.extend(Slice.prototype, { _.each(self._activePluginPackages(), function (otherPkg) { self.watchSet.merge(otherPkg.pluginWatchSet); // XXX this assumes this is not overwriting something different - self.pkg.pluginProviderPackages[otherPkg.name] = + self.pkg.pluginProviderPackageDirs[otherPkg.name] = otherPkg.packageDirectoryForBuildInfo; }); @@ -812,8 +812,11 @@ var Package = function (library, packageDirectoryForBuildInfo) { // Complete only when pluginsBuilt is true. self.pluginWatchSet = new watch.WatchSet(); - // XXX DOC - self.pluginProviderPackages = {}; + // Map from package name to packageDirectoryForBuildInfo of packages that are + // directly used by this package. We use this to figure out that we need to + // rebuild if the resolution of the package changes (eg, an app package is + // added that overshadows a warehouse package, or the release changes). + self.pluginProviderPackageDirs = {}; // True if plugins have been initialized (if _ensurePluginsInitialized has // been called) @@ -1930,7 +1933,7 @@ _.extend(Package.prototype, { // This might be redundant (since pluginWatchSet was probably merged into // each slice watchSet when it was built) but shouldn't hurt. mergedWatchSet.merge(pluginWatchSet); - var pluginProviderPackages = buildInfoJson.pluginProviderPackages || {}; + var pluginProviderPackageDirs = buildInfoJson.pluginProviderPackages || {}; // If we're supposed to check the dependencies, go ahead and do so if (options.onlyIfUpToDate) { @@ -1950,7 +1953,7 @@ _.extend(Package.prototype, { return false; } - if (! self.checkUpToDate(mergedWatchSet, pluginProviderPackages)) + if (! self.checkUpToDate(mergedWatchSet, pluginProviderPackageDirs)) return false; } @@ -1962,7 +1965,7 @@ _.extend(Package.prototype, { self.defaultSlices = mainJson.defaultSlices; self.testSlices = mainJson.testSlices; self.pluginWatchSet = pluginWatchSet; - self.pluginProviderPackages = pluginProviderPackages; + self.pluginProviderPackageDirs = pluginProviderPackageDirs; _.each(mainJson.plugins, function (pluginMeta) { rejectBadPath(pluginMeta.path); @@ -2087,10 +2090,10 @@ _.extend(Package.prototype, { // files have been modified.) True if we have dependency info and it says that // the package is up-to-date. False if a source file has changed. // - // The arguments _watchSet and _pluginProviderPackages are used when reading - // from disk when there are no slices yet; don't pass them from outside this - // file. - checkUpToDate: function (_watchSet, _pluginProviderPackages) { + // The arguments _watchSet and _pluginProviderPackageDirs are used when + // reading from disk when there are no slices yet; don't pass them from + // outside this file. + checkUpToDate: function (_watchSet, _pluginProviderPackageDirs) { var self = this; if (!_watchSet) { @@ -2102,15 +2105,15 @@ _.extend(Package.prototype, { _watchSet.merge(slice.watchSet); }); } - if (!_pluginProviderPackages) { - _pluginProviderPackages = self.pluginProviderPackages; + if (!_pluginProviderPackageDirs) { + _pluginProviderPackageDirs = self.pluginProviderPackageDirs; } // Are all of the packages we directly use (which can provide plugins which // affect compilation) resolving to the same directory? (eg, have we updated // our release version to something with a new version of a package?) var packageResolutionsSame = _.all( - _pluginProviderPackages, function (packageDir, name) { + _pluginProviderPackageDirs, function (packageDir, name) { return self.library.findPackageDirectory(name) === packageDir; }); if (!packageResolutionsSame) @@ -2163,7 +2166,7 @@ _.extend(Package.prototype, { builtBy: exports.BUILT_BY, sliceDependencies: { }, pluginDependencies: self.pluginWatchSet.toJSON(), - pluginProviderPackages: self.pluginProviderPackages, + pluginProviderPackages: self.pluginProviderPackageDirs, source: options.buildOfPath || undefined }; From e4837216024dca3514dfa8eb010c24cdccdd49d9 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 1 Aug 2013 12:41:49 -0700 Subject: [PATCH 174/175] Remember the library resolution of packages used by the plugin program itself. --- tools/bundler.js | 16 +++++++++++++++- tools/packages.js | 5 +++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/tools/bundler.js b/tools/bundler.js index 2eff91765e..bbdbb0bc9f 100644 --- a/tools/bundler.js +++ b/tools/bundler.js @@ -404,6 +404,9 @@ var Target = function (options) { // On-disk dependencies of this target. self.watchSet = new watch.WatchSet(); + // Map from package name to package directory of all packages used. + self.pluginProviderPackageDirs = {}; + // node_modules directories that we need to copy into the target (or // otherwise make available at runtime.) A map from an absolute path // on disk (NodeModulesDirectory.sourcePath) to a @@ -671,6 +674,11 @@ _.extend(Target.prototype, { // Depend on the source files that produced these resources. self.watchSet.merge(slice.watchSet); + // Remember the library resolution of all packages used in these + // resources. + // XXX assumes that this merges cleanly + _.extend(self.pluginProviderPackageDirs, + slice.pkg.pluginProviderPackageDirs) }); }, @@ -706,6 +714,11 @@ _.extend(Target.prototype, { return self.watchSet; }, + getPluginProviderPackageDirs: function () { + var self = this; + return self.pluginProviderPackageDirs; + }, + // Return the most inclusive architecture with which this target is // compatible. For example, if we set out to build a // 'os.linux.x86_64' version of this target (by passing that as @@ -1830,7 +1843,8 @@ exports.buildJsImage = function (options) { return { image: target.toJsImage(), - watchSet: target.getWatchSet() + watchSet: target.getWatchSet(), + pluginProviderPackageDirs: target.getPluginProviderPackageDirs() }; }; diff --git a/tools/packages.js b/tools/packages.js index bec44f5855..7ee8dc248a 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -1006,6 +1006,11 @@ _.extend(Package.prototype, { // Add this plugin's dependencies to our "plugin dependency" WatchSet. self.pluginWatchSet.merge(buildResult.watchSet); + // Remember the library resolution of all packages used by the plugin. + // XXX assumes that this merges cleanly + _.extend(self.pluginProviderPackageDirs, + buildResult.pluginProviderPackageDirs); + // Register the built plugin's code. self.plugins[info.name] = buildResult.image; }); From e823b1e54af990aad627963241d7e4ad24da67e4 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 1 Aug 2013 16:10:56 -0700 Subject: [PATCH 175/175] Don't watch files for reload until after starting the server. Specifically, don't watch until after serverHandle points to the NEW process. This way, if the watcher files, we are sure to kill the new process, not fail to kill it because serverHandle is still pointing at the old process or no process. The old behavior led to sometimes failing to kill the server; while it would eventually die due to failed keepalive, the new servers would also fail due to EADDRINUSE. This change is possible because unlike the only dependencyInfo, WatchSets are completely self-contained (there's no "... and it should look like it did the first time" involved). While we're at it, make restartServer always stops the watcher, and clear some variables after they're used. Fixes #1247. --- tools/run.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/tools/run.js b/tools/run.js index 142754a13f..6982cb9f2a 100644 --- a/tools/run.js +++ b/tools/run.js @@ -498,8 +498,14 @@ exports.run = function (context, options) { var restartServer = inFiber(function () { Status.running = false; Status.listening = false; - if (serverHandle) + if (watcher) { + watcher.stop(); + watcher = null; + } + if (serverHandle) { killServer(serverHandle); + serverHandle = null; + } // If the user did not specify a --release on the command line, and // simultaneously runs `meteor update` during this run, just exit and let @@ -551,12 +557,6 @@ exports.run = function (context, options) { watchSet.addFile(path.resolve(options.settingsFile), settingsHash); } - // Start watching for changes for files. There's no hurry to call - // this, since watchSet contains a snapshot of the state of - // the world at the time of bundling, in the form of hashes and - // lists of matching files in each directory. - startWatching(watchSet); - // Start the server Status.running = true; @@ -607,6 +607,12 @@ exports.run = function (context, options) { settings: settings, program: options.program }); + + // Start watching for changes for files. There's no hurry to call + // this, since watchSet contains a snapshot of the state of + // the world at the time of bundling, in the form of hashes and + // lists of matching files in each directory. + startWatching(watchSet); }); var mongoErrorCount = 0;