Merge branch 'devel' of github.com:meteor/meteor into minimongo-without-underscore

This commit is contained in:
Radosław Miernik
2017-07-19 20:14:59 +02:00
84 changed files with 2293 additions and 1216 deletions

View File

@@ -1,17 +1,51 @@
## v.NEXT
* The `accounts-facebook` and `facebook-oauth` packages have been updated to
use the v2.9 of the Facebook Graph API for the Login Dialog since the v2.2
version will be deprecated by Facebook in July. There shouldn't be a problem
regardless since Facebook simply rolls over to the next active version
(v2.3, in this case) however this should assist in avoiding deprecation
warnings and should enable any new functionality which has become available.
[PR #8858](https://github.com/meteor/meteor/pull/8858)
## v1.5.1, 2017-07-12
* `observe`/`observeChanges` callbacks are now bound using `Meteor.bindEnvironment`.
The same `EnvironmentVariable`s that were present when `observe`/`observeChanges`
was called are now available inside the callbacks.
[PR #8734](https://github.com/meteor/meteor/pull/8734)
* Node has been upgraded to version 4.8.4.
* A new core Meteor package called `server-render` provides generic
support for server-side rendering of HTML, as described in the package's
[`README.md`](https://github.com/meteor/meteor/blob/release-1.5.1/packages/server-render/README.md).
[PR #8841](https://github.com/meteor/meteor/pull/8841)
* To reduce the total number of file descriptors held open by the Meteor
build system, native file watchers will now be started only for files
that have changed at least once. This new policy means you may have to
[wait up to 5000ms](https://github.com/meteor/meteor/blob/6bde360b9c075f1c78c3850eadbdfa7fe271f396/tools/fs/safe-watcher.js#L20-L21)
for changes to be detected when you first edit a file, but thereafter
changes will be detected instantaneously. In return for that small
initial waiting time, the number of open file descriptors will now be
bounded roughly by the number of files you are actively editing, rather
than the number of files involved in the build (often thousands), which
should help with issues like
[#8648](https://github.com/meteor/meteor/issues/8648). If you need to
disable the new behavior for any reason, simply set the
`METEOR_WATCH_PRIORITIZE_CHANGED` environment variable to `"false"`, as
explained in [PR #8866](https://github.com/meteor/meteor/pull/8866).
* All `observe` and `observeChanges` callbacks are now bound using
`Meteor.bindEnvironment`. The same `EnvironmentVariable`s that were
present when `observe` or `observeChanges` was called are now available
inside the callbacks. [PR #8734](https://github.com/meteor/meteor/pull/8734)
* A subscription's `onReady` is now fired again during a re-subscription, even
if the subscription has the same arguments. Previously, when subscribing
to a publication the `onReady` would have only been called if the arguments
were different, creating a confusing difference in functionality. This may be
breaking behavior if an app uses the firing of `onReady` as an assumption
that the data was just received from the server. If such functionality is
still necessary, consider using
[`observe`](https://docs.meteor.com/api/collections.html#Mongo-Cursor-observe)
or
[`observeChanges`](https://docs.meteor.com/api/collections.html#Mongo-Cursor-observeChanges)
[PR #8754](https://github.com/meteor/meteor/pull/8754)
[Issue #1173](https://github.com/meteor/meteor/issues/1173)
* The `minimongo` and `mongo` packages are now compliant with the upsert behavior
of MongoDB 2.6 and higher. **As a result support for MongoDB 2.4 has been dropped.**
This mainly changes the effect of the selector on newly inserted documents.
[PR #8815](https://github.com/meteor/meteor/pull/8815)
* `reactive-dict` now supports setting initial data when defining a named
`ReactiveDict`. No longer run migration logic when used on the server,
@@ -26,6 +60,14 @@
proper domain in all applications.
[PR #8760](https://github.com/meteor/meteor/issues/8760)
* The `accounts-facebook` and `facebook-oauth` packages have been updated to
use the v2.9 of the Facebook Graph API for the Login Dialog since the v2.2
version will be deprecated by Facebook in July. There shouldn't be a problem
regardless since Facebook simply rolls over to the next active version
(v2.3, in this case) however this should assist in avoiding deprecation
warnings and should enable any new functionality which has become available.
[PR #8858](https://github.com/meteor/meteor/pull/8858)
* Add `DDP._CurrentPublicationInvocation` and `DDP._CurrentMethodInvocation`.
`DDP._CurrentInvocation` remains for backwards-compatibility. This change
allows method calls from publications to inherit the `connection` from the
@@ -52,7 +94,14 @@
context and with its `EnvironmentVariable`s bound.
[PR #8629](https://github.com/meteor/meteor/pull/8629)
* The `reify` npm package has been upgraded to version 0.11.22.
* The `minifier-js` package will now replace `process.env.NODE_ENV` with
its string value (or `"development"` if unspecified).
* The `meteor-babel` npm package has been upgraded to version 0.22.0.
* The `reify` npm package has been upgraded to version 0.11.24.
* The `uglify-js` npm package has been upgraded to version 3.0.18.
* Illegal characters in paths written in build output directories will now
be replaced with `_`s rather than removed, so that file and directory
@@ -61,7 +110,7 @@
* Additional "extra" packages (packages that aren't saved in `.meteor/packages`)
can be included temporarily using the `--extra-packages`
option. For example: `meteor run --extra-packages "bundle-visualizer"`.
option. For example: `meteor run --extra-packages bundle-visualizer`.
Both `meteor test` and `meteor test-packages` also support the
`--extra-packages` option and commas separate multiple package names.
[PR #8769](https://github.com/meteor/meteor/pull/8769)
@@ -72,6 +121,22 @@
* The `coffeescript` package has been updated to use CoffeeScript version
1.12.6. [PR #8777](https://github.com/meteor/meteor/pull/8777)
* It's now possible to pipe a series of statements to `meteor shell`,
whereas previously the input had to be an expression; for example:
```sh
> echo 'import pkg from "babel-runtime/package.json";
quote> pkg.version' |
pipe> meteor shell
"6.23.0"
```
[Issue #8823](https://github.com/meteor/meteor/issues/8823)
[PR #8833](https://github.com/meteor/meteor/pull/8833)
* Any `Error` thrown by a DDP method with the `error.isClientSafe`
property set to `true` will now be serialized and displayed to the
client, whereas previously only `Meteor.Error` objects were considered
client-safe. [PR #8756](https://github.com/meteor/meteor/pull/8756)
## v1.5, 2017-05-30
* The `meteor-base` package implies a new `dynamic-import` package, which

7
meteor
View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash
BUNDLE_VERSION=4.8.18
BUNDLE_VERSION=4.8.21
# OS Check. Put here because here is where we download the precompiled
# bundles that are arch specific.
@@ -132,4 +132,7 @@ fi
# the script take precedence over $NODE_PATH; it used to be that users would
# screw up their meteor installs by have a ~/node_modules
exec "$DEV_BUNDLE/bin/node" ${TOOL_NODE_FLAGS} "$METEOR" "$@"
exec "$DEV_BUNDLE/bin/node" \
--expose-gc \
${TOOL_NODE_FLAGS} \
"$METEOR" "$@"

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: "A user account system",
version: "1.3.0"
version: "1.3.1"
});
Package.onUse(function (api) {

View File

@@ -1,6 +1,10 @@
Package.describe({
summary: "Password support for accounts",
version: "2.0.0"
// This version was bumped to 2.0.0 temporarily during the Meteor 1.5.1
// release process, so versions 2.0.0-beta.2 through -beta.5 and -rc.0
// have already been published. The next time this package reaches 2.x
// territory, I would recommend jumping straight to 2.1.0.
version: "1.4.0"
});
Package.onUse(function(api) {

View File

@@ -1,6 +1,6 @@
Package.describe({
name: 'allow-deny',
version: '1.0.5',
version: '1.0.6',
// Brief, one-line summary of the package.
summary: 'Implements functionality for allow/deny and client-side db operations',
// URL to the Git repository containing the source code for this package.

View File

@@ -21,14 +21,14 @@
"from": "babel-code-frame@>=6.22.0 <7.0.0"
},
"babel-core": {
"version": "6.24.1",
"resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.24.1.tgz",
"version": "6.25.0",
"resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.25.0.tgz",
"from": "babel-core@>=6.22.1 <7.0.0"
},
"babel-generator": {
"version": "6.24.1",
"resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.24.1.tgz",
"from": "babel-generator@>=6.24.1 <7.0.0"
"version": "6.25.0",
"resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.25.0.tgz",
"from": "babel-generator@>=6.25.0 <7.0.0"
},
"babel-helper-builder-react-jsx": {
"version": "6.24.1",
@@ -138,8 +138,8 @@
}
},
"babel-plugin-minify-dead-code-elimination": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/babel-plugin-minify-dead-code-elimination/-/babel-plugin-minify-dead-code-elimination-0.1.6.tgz",
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/babel-plugin-minify-dead-code-elimination/-/babel-plugin-minify-dead-code-elimination-0.1.7.tgz",
"from": "babel-plugin-minify-dead-code-elimination@>=0.1.3 <0.2.0"
},
"babel-plugin-minify-flip-comparisons": {
@@ -280,8 +280,8 @@
"from": "babel-plugin-transform-es2015-modules-commonjs@>=6.22.0 <7.0.0"
},
"babel-plugin-transform-es2015-modules-reify": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-reify/-/babel-plugin-transform-es2015-modules-reify-0.11.0.tgz",
"version": "0.11.2",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-reify/-/babel-plugin-transform-es2015-modules-reify-0.11.2.tgz",
"from": "babel-plugin-transform-es2015-modules-reify@>=0.11.0 <0.12.0"
},
"babel-plugin-transform-es2015-object-super": {
@@ -340,13 +340,13 @@
"from": "babel-plugin-transform-inline-consecutive-adds@>=0.0.2 <0.0.3"
},
"babel-plugin-transform-member-expression-literals": {
"version": "6.8.3",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-member-expression-literals/-/babel-plugin-transform-member-expression-literals-6.8.3.tgz",
"version": "6.8.4",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-member-expression-literals/-/babel-plugin-transform-member-expression-literals-6.8.4.tgz",
"from": "babel-plugin-transform-member-expression-literals@>=6.8.1 <7.0.0"
},
"babel-plugin-transform-merge-sibling-variables": {
"version": "6.8.4",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-merge-sibling-variables/-/babel-plugin-transform-merge-sibling-variables-6.8.4.tgz",
"version": "6.8.5",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-merge-sibling-variables/-/babel-plugin-transform-merge-sibling-variables-6.8.5.tgz",
"from": "babel-plugin-transform-merge-sibling-variables@>=6.8.2 <7.0.0"
},
"babel-plugin-transform-minify-booleans": {
@@ -360,13 +360,13 @@
"from": "babel-plugin-transform-object-rest-spread@>=6.22.0 <7.0.0"
},
"babel-plugin-transform-property-literals": {
"version": "6.8.3",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-property-literals/-/babel-plugin-transform-property-literals-6.8.3.tgz",
"version": "6.8.4",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-property-literals/-/babel-plugin-transform-property-literals-6.8.4.tgz",
"from": "babel-plugin-transform-property-literals@>=6.8.1 <7.0.0"
},
"babel-plugin-transform-react-display-name": {
"version": "6.23.0",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.23.0.tgz",
"version": "6.25.0",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz",
"from": "babel-plugin-transform-react-display-name@>=6.23.0 <7.0.0"
},
"babel-plugin-transform-react-jsx": {
@@ -395,13 +395,13 @@
"from": "babel-plugin-transform-regexp-constructors@>=0.0.5 <0.0.6"
},
"babel-plugin-transform-remove-console": {
"version": "6.8.3",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-console/-/babel-plugin-transform-remove-console-6.8.3.tgz",
"version": "6.8.4",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-console/-/babel-plugin-transform-remove-console-6.8.4.tgz",
"from": "babel-plugin-transform-remove-console@>=6.8.0 <7.0.0"
},
"babel-plugin-transform-remove-debugger": {
"version": "6.8.3",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-debugger/-/babel-plugin-transform-remove-debugger-6.8.3.tgz",
"version": "6.8.4",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-debugger/-/babel-plugin-transform-remove-debugger-6.8.4.tgz",
"from": "babel-plugin-transform-remove-debugger@>=6.8.0 <7.0.0"
},
"babel-plugin-transform-remove-undefined": {
@@ -415,8 +415,8 @@
"from": "babel-plugin-transform-runtime@>=6.22.0 <7.0.0"
},
"babel-plugin-transform-simplify-comparison-operators": {
"version": "6.8.3",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-simplify-comparison-operators/-/babel-plugin-transform-simplify-comparison-operators-6.8.3.tgz",
"version": "6.8.4",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-simplify-comparison-operators/-/babel-plugin-transform-simplify-comparison-operators-6.8.4.tgz",
"from": "babel-plugin-transform-simplify-comparison-operators@>=6.8.1 <7.0.0"
},
"babel-plugin-transform-strict-mode": {
@@ -460,33 +460,33 @@
"from": "babel-runtime@>=6.22.0 <7.0.0"
},
"babel-template": {
"version": "6.24.1",
"resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.24.1.tgz",
"version": "6.25.0",
"resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz",
"from": "babel-template@>=6.22.0 <7.0.0"
},
"babel-traverse": {
"version": "6.24.1",
"resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.24.1.tgz",
"version": "6.25.0",
"resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.25.0.tgz",
"from": "babel-traverse@>=6.22.1 <7.0.0"
},
"babel-types": {
"version": "6.24.1",
"resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.24.1.tgz",
"version": "6.25.0",
"resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz",
"from": "babel-types@>=6.22.0 <7.0.0"
},
"babylon": {
"version": "6.17.2",
"resolved": "https://registry.npmjs.org/babylon/-/babylon-6.17.2.tgz",
"version": "6.17.4",
"resolved": "https://registry.npmjs.org/babylon/-/babylon-6.17.4.tgz",
"from": "babylon@>=6.15.0 <7.0.0"
},
"balanced-match": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
"from": "balanced-match@>=0.4.1 <0.5.0"
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"from": "balanced-match@>=1.0.0 <2.0.0"
},
"brace-expansion": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz",
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
"from": "brace-expansion@>=1.1.7 <2.0.0"
},
"chalk": {
@@ -530,8 +530,8 @@
"from": "esutils@>=2.0.2 <3.0.0"
},
"globals": {
"version": "9.17.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-9.17.0.tgz",
"version": "9.18.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
"from": "globals@>=9.0.0 <10.0.0"
},
"has-ansi": {
@@ -555,8 +555,8 @@
"from": "is-finite@>=1.0.0 <2.0.0"
},
"js-tokens": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.1.tgz",
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
"from": "js-tokens@>=3.0.0 <4.0.0"
},
"jsesc": {
@@ -590,9 +590,9 @@
"from": "loose-envify@>=1.0.0 <2.0.0"
},
"meteor-babel": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/meteor-babel/-/meteor-babel-0.21.4.tgz",
"from": "meteor-babel@0.21.4"
"version": "0.22.0",
"resolved": "https://registry.npmjs.org/meteor-babel/-/meteor-babel-0.22.0.tgz",
"from": "meteor-babel@0.22.0"
},
"meteor-babel-helpers": {
"version": "0.0.3",
@@ -610,8 +610,8 @@
"from": "minimist@0.0.8"
},
"minipass": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.0.2.tgz",
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.2.1.tgz",
"from": "minipass@>=2.0.0 <3.0.0"
},
"minizlib": {
@@ -692,8 +692,8 @@
}
},
"reify": {
"version": "0.11.22",
"resolved": "https://registry.npmjs.org/reify/-/reify-0.11.22.tgz",
"version": "0.11.24",
"resolved": "https://registry.npmjs.org/reify/-/reify-0.11.24.tgz",
"from": "reify@>=0.11.18 <0.12.0"
},
"repeating": {

View File

@@ -1,12 +1,15 @@
var meteorBabel = null;
function getMeteorBabel() {
return meteorBabel || (meteorBabel = Npm.require("meteor-babel"));
}
/**
* Returns a new object containing default options appropriate for
*/
function getDefaultOptions(extraFeatures) {
var meteorBabel = Npm.require('meteor-babel');
// See https://github.com/meteor/babel/blob/master/options.js for more
// information about what the default options are.
var options = meteorBabel.getDefaultOptions(extraFeatures);
var options = getMeteorBabel().getDefaultOptions(extraFeatures);
// The sourceMap option should probably be removed from the default
// options returned by meteorBabel.getDefaultOptions.
@@ -22,22 +25,24 @@ Babel = {
validateExtraFeatures: Function.prototype,
parse: function (source) {
return Npm.require('meteor-babel').parse(source);
return getMeteorBabel().parse(source);
},
compile: function (source, options) {
var meteorBabel = Npm.require('meteor-babel');
options = options || getDefaultOptions();
return meteorBabel.compile(source, options);
return getMeteorBabel().compile(source, options);
},
setCacheDir: function (cacheDir) {
Npm.require('meteor-babel').setCacheDir(cacheDir);
getMeteorBabel().setCacheDir(cacheDir);
},
minify: function(source, options) {
var meteorBabel = Npm.require('meteor-babel');
var options = options || meteorBabel.getMinifierOptions();
return meteorBabel.minify(source, options);
minify: function (source, options) {
var options = options || getMeteorBabel().getMinifierOptions();
return getMeteorBabel().minify(source, options);
},
getMinifierOptions: function (extraFeatures) {
return getMeteorBabel().getMinifierOptions(extraFeatures);
}
};

View File

@@ -6,11 +6,11 @@ Package.describe({
// isn't possible because you can't publish a non-recommended
// release with package versions that don't have a pre-release
// identifier at the end (eg, -dev)
version: '6.19.2'
version: '6.19.4'
});
Npm.depends({
'meteor-babel': '0.21.4'
'meteor-babel': '0.22.0'
});
Package.onUse(function (api) {

View File

@@ -0,0 +1 @@
node_modules

View File

@@ -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.

View File

@@ -0,0 +1,9 @@
{
"dependencies": {
"parse5": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.2.tgz",
"from": "parse5@3.0.2"
}
}
}

View File

@@ -0,0 +1,13 @@
Npm.depends({'parse5': '3.0.2'});
Package.describe({
// These tests are in a separate package so that we can Npm.depend on parse5, a html parsing library
summary: "Tests for the boilerplate-generator package",
version: '1.0.0'
});
Package.onTest(function (api) {
api.use('ecmascript');
api.use(['tinytest', 'boilerplate-generator'], 'server');
api.addFiles('web.browser-tests.js', 'server');
api.addFiles('web.cordova-tests.js', 'server');
});

View File

@@ -0,0 +1,54 @@
export function generateHTMLForArch(arch) {
// Use a dummy manifest. None of these paths will be read from the filesystem, but css / js should be handled differently
const manifest = [
{
path: 'packages/bootstrap/css/bootstrap-responsive.css',
where: 'client',
type: 'css',
cacheable: true,
url: '/packages/bootstrap/css/bootstrap-responsive.css?hash=785760fc5ad665d7b54d56a3c2522797bb2cc150&v="1"',
size: 22111,
hash: '785760fc5ad665d7b54d56a3c2522797bb2cc150'
},
{
path: 'packages/templating-runtime.js',
where: 'client',
type: 'js',
cacheable: true,
url: '/packages/templating-runtime.js?hash=c18de19afda6e9f0db7faf3d4382a4c953cabe18&v="1"',
size: 24132,
hash: 'c18de19afda6e9f0db7faf3d4382a4c953cabe18'
},
];
// Set some extra options for boilerplate data.
// webapp_server usually constructs a Boilerplate object similarly
const inline = true;
const inlineScriptsAllowed = true;
const additionalStaticJs = [];
const meteorRuntimeConfig = 'config123';
const rootUrlPathPrefix = 'rootUrlPathPrefix';
const htmlAttributes = {
foo: 'foobar',
gems: '&"',
};
// A dummy rewrite hook to test ampersands
function bundledJsCssUrlRewriteHook(url) {
return url + '+rewritten_url=true';
}
const boilerplate = new Boilerplate(arch, manifest, {
baseDataExtension: {
htmlAttributes,
additionalStaticJs,
meteorRuntimeConfig,
rootUrlPathPrefix,
bundledJsCssUrlRewriteHook,
inlineScriptsAllowed,
inline
},
});
return boilerplate.toHTML();
};

View File

@@ -0,0 +1,41 @@
import { parse, serialize } from 'parse5';
import { generateHTMLForArch } from './test-lib';
const html = generateHTMLForArch('web.browser');
Tinytest.add("boilerplate-generator-tests - web.browser - well-formed html", function (test) {
const formatted = serialize(parse(html));
test.isTrue(formatted.replace(/\s/g, '') === html.replace(/\s/g, ''));
});
Tinytest.add("boilerplate-generator-tests - web.browser - include htmlAttributes", function (test) {
test.matches(html, /foo="foobar"/);
});
Tinytest.add("boilerplate-generator-tests - web.browser - escape htmlAttributes", function (test) {
test.matches(html, /gems="&amp;&quot;"/);
});
Tinytest.add("boilerplate-generator-tests - web.browser - include js", function (test) {
test.matches(html, /<script[^<>]*src="[^<>]*templating[^<>]*">/);
});
Tinytest.add("boilerplate-generator-tests - web.browser - escape js", function (test) {
test.matches(html, /<script[^<>]*src="[^<>]*templating[^<>]*&amp;v=&quot;1&quot;[^<>]*">/);
});
Tinytest.add("boilerplate-generator-tests - web.browser - include css", function (test) {
test.matches(html, /<link[^<>]*href="[^<>]*bootstrap[^<>]*">/);
});
Tinytest.add("boilerplate-generator-tests - web.browser - escape css", function (test) {
test.matches(html, /<link[^<>]*href="[^<>]*bootstrap[^<>]*&amp;v=&quot;1&quot;[^<>]*">/);
});
Tinytest.add("boilerplate-generator-tests - web.browser - call rewriteHook", function (test) {
test.matches(html, /\+rewritten_url=true/);
});
Tinytest.add("boilerplate-generator-tests - web.browser - include runtime config", function (test) {
test.matches(html, /<script[^<>]*>[^<>]*__meteor_runtime_config__ =.*decodeURIComponent\(config123\)/);
});

View File

@@ -0,0 +1,33 @@
import { parse, serialize } from 'parse5';
import { generateHTMLForArch } from './test-lib';
const html = generateHTMLForArch('web.cordova');
Tinytest.add("boilerplate-generator-tests - web.cordova - well-formed html", function (test) {
const formatted = serialize(parse(html));
test.isTrue(formatted.replace(/\s/g, '') === html.replace(/\s/g, ''));
});
Tinytest.add("boilerplate-generator-tests - web.cordova - include js", function (test) {
test.matches(html, /<script[^<>]*src="[^<>]*templating[^<>]*">/);
});
Tinytest.add("boilerplate-generator-tests - web.cordova - escape js", function (test) {
test.matches(html, /<script[^<>]*src="[^<>]*templating[^<>]*&amp;v=&quot;1&quot;[^<>]*">/);
});
Tinytest.add("boilerplate-generator-tests - web.cordova - include css", function (test) {
test.matches(html, /<link[^<>]*href="[^<>]*bootstrap[^<>]*">/);
});
Tinytest.add("boilerplate-generator-tests - web.cordova - escape css", function (test) {
test.matches(html, /<link[^<>]*href="[^<>]*bootstrap[^<>]*&amp;v=&quot;1&quot;[^<>]*">/);
});
Tinytest.add("boilerplate-generator-tests - web.cordova - do not call rewriteHook", function (test) {
test.notMatches(html, /\+rewritten_url=true/);
});
Tinytest.add("boilerplate-generator-tests - web.cordova - include runtime config", function (test) {
test.matches(html, /<script[^<>]*>[^<>]*\n[^<>]*__meteor_runtime_config__ =[^<>]*decodeURIComponent\(config123\)\)/);
});

View File

@@ -1,106 +0,0 @@
var fs = Npm.require('fs');
var path = Npm.require('path');
// Copied from webapp_server
var readUtf8FileSync = function (filename) {
return Meteor.wrapAsync(fs.readFile)(filename, 'utf8');
};
Boilerplate = function (arch, manifest, options) {
var self = this;
options = options || {};
self.template = _getTemplate(arch);
self.baseData = null;
self.func = null;
self._generateBoilerplateFromManifestAndSource(
manifest,
self.template,
options
);
};
// The 'extraData' argument can be used to extend 'self.baseData'. Its
// purpose is to allow you to specify data that you might not know at
// the time that you construct the Boilerplate object. (e.g. it is used
// by 'webapp' to specify data that is only known at request-time).
Boilerplate.prototype.toHTML = function (extraData) {
var self = this;
if (! self.baseData || ! self.func)
throw new Error('Boilerplate did not instantiate correctly.');
return "<!DOCTYPE html>\n" +
Blaze.toHTML(Blaze.With(_.extend({}, self.baseData, extraData),
self.func));
};
// XXX Exported to allow client-side only changes to rebuild the boilerplate
// without requiring a full server restart.
// Produces an HTML string with given manifest and boilerplateSource.
// Optionally takes urlMapper in case urls from manifest need to be prefixed
// or rewritten.
// Optionally takes pathMapper for resolving relative file system paths.
// Optionally allows to override fields of the data context.
Boilerplate.prototype._generateBoilerplateFromManifestAndSource =
function (manifest, boilerplateSource, options) {
var self = this;
// map to the identity by default
var urlMapper = options.urlMapper || _.identity;
var pathMapper = options.pathMapper || _.identity;
var boilerplateBaseData = {
css: [],
js: [],
head: '',
body: '',
meteorManifest: JSON.stringify(manifest)
};
// allow the caller to extend the default base data
_.extend(boilerplateBaseData, options.baseDataExtension);
_.each(manifest, function (item) {
var urlPath = urlMapper(item.url);
var itemObj = { url: urlPath };
if (options.inline) {
itemObj.scriptContent = readUtf8FileSync(
pathMapper(item.path));
itemObj.inline = true;
}
if (item.type === 'css' && item.where === 'client') {
boilerplateBaseData.css.push(itemObj);
}
if (item.type === 'js' && item.where === 'client' &&
// Dynamic JS modules should not be loaded eagerly in the
// initial HTML of the app.
! item.path.startsWith('dynamic/')) {
boilerplateBaseData.js.push(itemObj);
}
if (item.type === 'head') {
boilerplateBaseData.head =
readUtf8FileSync(pathMapper(item.path));
}
if (item.type === 'body') {
boilerplateBaseData.body =
readUtf8FileSync(pathMapper(item.path));
}
});
var boilerplateRenderCode = SpacebarsCompiler.compile(
boilerplateSource, { isBody: true });
// Note that we are actually depending on eval's local environment capture
// so that UI and HTML are visible to the eval'd code.
// XXX the template we are evaluating relies on the fact that UI is globally
// available.
global.UI = UI;
self.func = eval(boilerplateRenderCode);
self.baseData = boilerplateBaseData;
};
var _getTemplate = _.memoize(function (arch) {
var filename = 'boilerplate_' + arch + '.html';
return Assets.getText(filename);
});

View File

@@ -1,28 +0,0 @@
<html {{htmlAttributes}}>
<head>
{{#each css}} <link rel="stylesheet" type="text/css" class="__meteor-css__" href="{{../bundledJsCssUrlRewriteHook url}}">{{/each}}
{{{head}}}
{{{dynamicHead}}}
</head>
<body>
{{{body}}}
{{{dynamicBody}}}
{{#if inlineScriptsAllowed}}
<script type='text/javascript'>__meteor_runtime_config__ = JSON.parse(decodeURIComponent({{meteorRuntimeConfig}}));</script>
{{else}}
<script type='text/javascript' src='{{rootUrlPathPrefix}}/meteor_runtime_config.js'></script>
{{/if}}
{{#each js}} <script type="text/javascript" src="{{../bundledJsCssUrlRewriteHook url}}"></script>
{{/each}}
{{#each additionalStaticJs}}
{{#if ../inlineScriptsAllowed}}
<script type='text/javascript'>
{{contents}}
</script>
{{else}}
<script type='text/javascript'
src='{{rootUrlPathPrefix}}{{pathname}}'></script>
{{/if}}
{{/each}}
</body>
</html>

View File

@@ -1,46 +0,0 @@
<html>
<head>
<meta charset="utf-8">
<meta name="format-detection" content="telephone=no">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height">
<meta name="msapplication-tap-highlight" content="no">
<meta http-equiv="Content-Security-Policy" content="default-src * gap: data: blob: 'unsafe-inline' 'unsafe-eval' ws: wss:;">
{{! We are explicitly not using bundledJsCssUrlRewriteHook: in cordova we serve assets up directly from disk, so rewriting the URL does not make sense }}
{{#each css}} <link rel="stylesheet" type="text/css" class="__meteor-css__" href="{{url}}">{{/each}}
<script type='text/javascript'>
__meteor_runtime_config__ = JSON.parse(decodeURIComponent({{meteorRuntimeConfig}}));
if (/Android/i.test(navigator.userAgent)) {
// When Android app is emulated, it cannot connect to localhost,
// instead it should connect to 10.0.2.2
// (unless we're using an http proxy; then it works!)
if (!__meteor_runtime_config__.httpProxyPort) {
__meteor_runtime_config__.ROOT_URL = (__meteor_runtime_config__.ROOT_URL || '').replace(/localhost/i, '10.0.2.2');
__meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL = (__meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL || '').replace(/localhost/i, '10.0.2.2');
}
}
</script>
<script type="text/javascript" src="/cordova.js"></script>
{{#each js}} <script type="text/javascript" src="{{url}}"></script>
{{/each}}
{{#each additionalStaticJs}}
{{#if ../inlineScriptsAllowed}}
<script type='text/javascript'>
{{contents}}
</script>
{{else}}
<script type='text/javascript'
src='{{rootUrlPathPrefix}}{{pathname}}'></script>
{{/if}}
{{/each}}
{{{head}}}
</head>
<body>
{{{body}}}
</body>
</html>

View File

@@ -0,0 +1,102 @@
import { readFile } from 'fs';
import WebBrowserTemplate from './template-web.browser';
import WebCordovaTemplate from './template-web.cordova';
// Copied from webapp_server
const readUtf8FileSync = filename => Meteor.wrapAsync(readFile)(filename, 'utf8');
export class Boilerplate {
constructor(arch, manifest, options = {}) {
this.template = _getTemplate(arch);
this.baseData = null;
this._generateBoilerplateFromManifest(
manifest,
options
);
}
// The 'extraData' argument can be used to extend 'self.baseData'. Its
// purpose is to allow you to specify data that you might not know at
// the time that you construct the Boilerplate object. (e.g. it is used
// by 'webapp' to specify data that is only known at request-time).
toHTML(extraData) {
if (!this.baseData || !this.template) {
throw new Error('Boilerplate did not instantiate correctly.');
}
return "<!DOCTYPE html>\n" +
this.template({ ...this.baseData, ...extraData });
}
// XXX Exported to allow client-side only changes to rebuild the boilerplate
// without requiring a full server restart.
// Produces an HTML string with given manifest and boilerplateSource.
// Optionally takes urlMapper in case urls from manifest need to be prefixed
// or rewritten.
// Optionally takes pathMapper for resolving relative file system paths.
// Optionally allows to override fields of the data context.
_generateBoilerplateFromManifest(manifest, {
urlMapper = _.identity,
pathMapper = _.identity,
baseDataExtension,
inline,
} = {}) {
const boilerplateBaseData = {
css: [],
js: [],
head: '',
body: '',
meteorManifest: JSON.stringify(manifest),
...baseDataExtension,
};
_.each(manifest, item => {
const urlPath = urlMapper(item.url);
const itemObj = { url: urlPath };
if (inline) {
itemObj.scriptContent = readUtf8FileSync(
pathMapper(item.path));
itemObj.inline = true;
}
if (item.type === 'css' && item.where === 'client') {
boilerplateBaseData.css.push(itemObj);
}
if (item.type === 'js' && item.where === 'client' &&
// Dynamic JS modules should not be loaded eagerly in the
// initial HTML of the app.
!item.path.startsWith('dynamic/')) {
boilerplateBaseData.js.push(itemObj);
}
if (item.type === 'head') {
boilerplateBaseData.head =
readUtf8FileSync(pathMapper(item.path));
}
if (item.type === 'body') {
boilerplateBaseData.body =
readUtf8FileSync(pathMapper(item.path));
}
});
this.baseData = boilerplateBaseData;
}
};
// Returns a template function that, when called, produces the boilerplate
// html as a string.
const _getTemplate = arch => {
if (arch === 'web.browser') {
return WebBrowserTemplate;
} else if (arch === 'web.cordova') {
return WebCordovaTemplate;
} else {
throw new Error('Unsupported arch: ' + arch);
}
};

View File

@@ -1,24 +1,13 @@
Package.describe({
summary: "Generates the boilerplate html from program's manifest",
version: '1.1.1'
version: '1.1.2'
});
Package.onUse(function (api) {
Package.onUse(api => {
api.use('ecmascript');
api.use([
'underscore',
'spacebars-compiler',
'spacebars',
'htmljs',
'ui',
], 'server');
api.addFiles(['boilerplate-generator.js'], 'server');
api.export(['Boilerplate'], 'server');
// These are spacebars templates, but we process them manually with the
// spacebars compiler rather than letting the 'templating' package (which
// isn't fully supported on the server yet) handle it. That also means that
// they don't contain the outer "<template>" tag.
api.addAssets([
'boilerplate_web.browser.html',
'boilerplate_web.cordova.html'
], 'server');
api.mainModule('generator.js', 'server');
api.export('Boilerplate', 'server');
});

View File

@@ -0,0 +1,76 @@
// Template function for rendering the boilerplate html for browsers
export default function({
meteorRuntimeConfig,
rootUrlPathPrefix,
inlineScriptsAllowed,
css,
js,
additionalStaticJs,
htmlAttributes,
bundledJsCssUrlRewriteHook,
head,
body,
dynamicHead,
dynamicBody,
}) {
return [].concat(
[
'<html' +_.map(htmlAttributes, (value, key) =>
_.template(' <%= attrName %>="<%- attrValue %>"')({
attrName: key,
attrValue: value
})
).join('') + '>',
'<head>'
],
_.map(css, ({url}) =>
_.template(' <link rel="stylesheet" type="text/css" class="__meteor-css__" href="<%- href %>">')({
href: bundledJsCssUrlRewriteHook(url)
})
),
[
head,
dynamicHead,
'</head>',
'<body>',
body,
dynamicBody,
'',
(inlineScriptsAllowed
? _.template(' <script type="text/javascript">__meteor_runtime_config__ = JSON.parse(decodeURIComponent(<%= conf %>))</script>')({
conf: meteorRuntimeConfig
})
: _.template(' <script type="text/javascript" src="<%- src %>/meteor_runtime_config.js"></script>')({
src: rootUrlPathPrefix
})
) ,
''
],
_.map(js, ({url}) =>
_.template(' <script type="text/javascript" src="<%- src %>"></script>')({
src: bundledJsCssUrlRewriteHook(url)
})
),
_.map(additionalStaticJs, ({contents, pathname}) => (
(inlineScriptsAllowed
? _.template(' <script><%= contents %></script>')({
contents: contents
})
: _.template(' <script type="text/javascript" src="<%- src %>"></script>')({
src: rootUrlPathPrefix + pathname
}))
)),
[
'', '',
'</body>',
'</html>'
],
).join('\n');
}

View File

@@ -0,0 +1,79 @@
// Template function for rendering the boilerplate html for cordova
export default function({
meteorRuntimeConfig,
rootUrlPathPrefix,
inlineScriptsAllowed,
css,
js,
additionalStaticJs,
htmlAttributes,
bundledJsCssUrlRewriteHook,
head,
body,
dynamicHead,
dynamicBody,
}) {
return [].concat(
[
'<html>',
'<head>',
' <meta charset="utf-8">',
' <meta name="format-detection" content="telephone=no">',
' <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height">',
' <meta name="msapplication-tap-highlight" content="no">',
' <meta http-equiv="Content-Security-Policy" content="default-src * gap: data: blob: \'unsafe-inline\' \'unsafe-eval\' ws: wss:;">',
],
// We are explicitly not using bundledJsCssUrlRewriteHook: in cordova we serve assets up directly from disk, so rewriting the URL does not make sense
_.map(css, ({url}) =>
_.template(' <link rel="stylesheet" type="text/css" class="__meteor-css__" href="<%- href %>">')({
href: url
})
),
[
' <script type="text/javascript">',
_.template(' __meteor_runtime_config__ = JSON.parse(decodeURIComponent(<%= conf %>));')({
conf: meteorRuntimeConfig
}),
' if (/Android/i.test(navigator.userAgent)) {',
// When Android app is emulated, it cannot connect to localhost,
// instead it should connect to 10.0.2.2
// (unless we\'re using an http proxy; then it works!)
' if (!__meteor_runtime_config__.httpProxyPort) {',
' __meteor_runtime_config__.ROOT_URL = (__meteor_runtime_config__.ROOT_URL || \'\').replace(/localhost/i, \'10.0.2.2\');',
' __meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL = (__meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL || \'\').replace(/localhost/i, \'10.0.2.2\');',
' }',
' }',
' </script>',
'',
' <script type="text/javascript" src="/cordova.js"></script>'
],
_.map(js, ({url}) =>
_.template(' <script type="text/javascript" src="<%- src %>"></script>')({
src: url
})
),
_.map(additionalStaticJs, ({contents, pathname}) => (
(inlineScriptsAllowed
? _.template(' <script><%= contents %></script>')({
contents: contents
})
: _.template(' <script type="text/javascript" src="<%- src %>"></script>')({
src: rootUrlPathPrefix + pathname
}))
)),
[
'',
head,
'</head>',
'',
'<body>',
body,
'</body>',
'</html>'
],
).join('\n');
}

View File

@@ -582,8 +582,15 @@ _.extend(Connection.prototype, {
// an onReady callback inside an autorun; the semantics we provide is
// that at the time the sub first becomes ready, we call the last
// onReady callback provided, if any.)
if (!existing.ready)
// If the sub is already ready, run the ready callback right away.
// It seems that users would expect an onReady callback inside an
// autorun to trigger once the the sub first becomes ready and also
// when re-subs happens.
if (existing.ready) {
callbacks.onReady();
} else {
existing.readyCallback = callbacks.onReady;
}
}
// XXX COMPAT WITH 1.0.3.1 we used to have onError but now we call

View File

@@ -291,11 +291,11 @@ Tinytest.add("livedata stub - reactive subscribe", function (test) {
// Change the foo subscription and flush. We should sub to the new foo
// subscription, re-sub to the stopper subscription, and then unsub from the old
// foo subscription. The bar subscription should be unaffected. The completer
// subscription should *NOT* call its new onReady callback, because we only
// call at most one onReady for a given reactively-saved subscription.
// foo subscription. The bar subscription should be unaffected. The completer
// subscription should call its new onReady callback, because we always
// call onReady for a given reactively-saved subscription.
// The completerHandle should have been reestablished to the ready handle.
rFoo.set("foo2");
rFoo.set("foo2");
Tracker.flush();
test.length(stream.sent, 3);
@@ -312,17 +312,17 @@ Tinytest.add("livedata stub - reactive subscribe", function (test) {
message = JSON.parse(stream.sent.shift());
test.equal(message, {msg: 'unsub', id: idFoo1});
test.equal(onReadyCount, {completer: 1});
test.equal(onReadyCount, {completer: 2});
test.isTrue(completerReady);
// Ready the stopper and bar subs. Completing stopper should call only the
// onReady from the new subscription because they were separate subscriptions
// started at different times and the first one was explicitly torn down by
// the client; completing bar should call only the onReady from the new
// subscription because we only call at most one onReady per reactively-saved
// the client; completing bar should call the onReady from the new
// subscription because we always call onReady for a given reactively-saved
// subscription.
stream.receive({msg: 'ready', 'subs': [idStopperAgain, idBar1]});
test.equal(onReadyCount, {completer: 1, bar1: 1, stopper: 1});
test.equal(onReadyCount, {completer: 2, bar1: 1, stopper: 1});
// Shut down the autorun. This should unsub us from all current subs at flush
// time.
@@ -1310,15 +1310,24 @@ Tinytest.add("livedata stub - reactive resub", function (test) {
});
markAllReady();
var message = JSON.parse(stream.sent.shift());
delete message.id;
test.equal(message, {msg: 'sub', name: 'foo-sub', params: ['A']});
test.equal(fooReady, 1);
// Rerun the inner autorun with different subscription
// arguments. Detect the re-sub via onReady.
// arguments.
fooArg.set('B');
test.isTrue(inner.invalidated);
Tracker.flush();
test.isFalse(inner.invalidated);
markAllReady();
message = JSON.parse(stream.sent.shift());
delete message.id;
test.equal(message, {msg: 'sub', name: 'foo-sub', params: ['B']});
message = JSON.parse(stream.sent.shift());
delete message.id;
test.equal(message, {msg: 'unsub'});
test.equal(fooReady, 2);
// Rerun inner again with same args; should be no re-sub.
@@ -1327,7 +1336,8 @@ Tinytest.add("livedata stub - reactive resub", function (test) {
Tracker.flush();
test.isFalse(inner.invalidated);
markAllReady();
test.equal(fooReady, 2);
test.isUndefined(stream.sent.shift()); test.isUndefined(stream.sent.shift());
test.equal(fooReady, 3);
// Rerun outer! Should still be no re-sub even though
// the inner computation is stopped and a new one is
@@ -1337,13 +1347,20 @@ Tinytest.add("livedata stub - reactive resub", function (test) {
Tracker.flush();
test.isFalse(inner.invalidated);
markAllReady();
test.equal(fooReady, 2);
test.isUndefined(stream.sent.shift());
test.equal(fooReady, 4);
// Change the subscription. Now we should get an onReady.
fooArg.set('C');
Tracker.flush();
markAllReady();
test.equal(fooReady, 3);
message = JSON.parse(stream.sent.shift());
delete message.id;
test.equal(message, {msg: 'sub', name: 'foo-sub', params: ['C']});
message = JSON.parse(stream.sent.shift());
delete message.id;
test.equal(message, {msg: 'unsub'});
test.equal(fooReady, 5);
});

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: "Meteor's latency-compensated distributed data client",
version: '1.3.4',
version: '2.0.0',
documentation: null
});

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: "Code shared beween ddp-client and ddp-server",
version: '1.2.8',
version: '1.2.9',
documentation: null
});

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: "Meteor's latency-compensated distributed data server",
version: '1.3.14',
version: '2.0.0',
documentation: null
});

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: "Meteor's latency-compensated distributed data framework",
version: '1.2.5'
version: '1.3.0'
});
Package.onUse(function (api) {

View File

@@ -1,6 +1,6 @@
Package.describe({
name: "ecmascript-runtime-client",
version: "0.4.2",
version: "0.4.3",
summary: "Polyfills for new ECMAScript 2015 APIs like Map and Set",
git: "https://github.com/meteor/meteor/tree/devel/packages/ecmascript-runtime-client",
documentation: "README.md"

View File

@@ -31,6 +31,7 @@ require("core-js/es6/array");
require("core-js/es6/function");
require("core-js/es6/math");
require("core-js/es6/object");
require("core-js/es6/regexp");
require("core-js/es6/string");
require("core-js/es6/weak-map");
require("core-js/es6/weak-set");

View File

@@ -1,6 +1,6 @@
Package.describe({
name: 'ecmascript',
version: '0.8.0',
version: '0.8.2',
summary: 'Compiler plugin that supports ES2015+ in all .js files',
documentation: 'README.md'
});

View File

@@ -12,7 +12,7 @@ var storage;
try {
storage = global.localStorage;
if (storage) {
storage.setItem(key, key);
retrieved = storage.getItem(key);
@@ -21,7 +21,23 @@ try {
} catch (ignored) {}
if (key === retrieved) {
Meteor._localStorage = storage;
if (Meteor.isServer) {
Meteor._localStorage = storage;
} else {
// Some browsers (e.g. IE11) don't properly handle attempts to change
// window.localStorage methods. By using proxy methods to expose
// window.localStorage functionality, developers can change the
// behavior of Meteor._localStorage methods without breaking
// window.localStorage.
["getItem",
"setItem",
"removeItem",
].forEach(function (name) {
this[name] = function () {
return storage[name].apply(storage, arguments);
};
}, Meteor._localStorage = {});
}
}
if (! Meteor._localStorage) {

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: "Simulates local storage on IE 6,7 using userData",
version: "1.1.0"
version: "1.1.1"
});
Package.onUse(function (api) {

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: "The Meteor command-line tool",
version: '1.5.0'
version: '1.5.1'
});
Package.includeTool();

View File

@@ -16,9 +16,9 @@
"from": "source-map@>=0.5.1 <0.6.0"
},
"uglify-js": {
"version": "3.0.13",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.0.13.tgz",
"from": "uglify-js@3.0.13"
"version": "3.0.18",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.0.18.tgz",
"from": "uglify-js@3.0.18"
}
}
}

View File

@@ -2,6 +2,8 @@ var uglify;
meteorJsMinify = function (source) {
var result = {};
var NODE_ENV = process.env.NODE_ENV || "development";
uglify = uglify || Npm.require("uglify-js");
try {
@@ -9,7 +11,10 @@ meteorJsMinify = function (source) {
compress: {
drop_debugger: false,
unused: false,
dead_code: false
dead_code: false,
global_defs: {
"process.env.NODE_ENV": NODE_ENV
}
}
});
@@ -24,7 +29,14 @@ meteorJsMinify = function (source) {
// Although Babel.minify can handle a wider variety of ECMAScript
// 2015+ syntax, it is substantially slower than UglifyJS, so we use
// it only as a fallback.
result.code = Babel.minify(source).code;
if (Babel.getMinifierOptions) {
var options = Babel.getMinifierOptions({
inlineNodeEnv: NODE_ENV
});
result.code = Babel.minify(source, options).code;
} else {
result.code = Babel.minify(source).code;
}
}
return result;

View File

@@ -1,10 +1,10 @@
Package.describe({
summary: "JavaScript minifier",
version: "2.1.0"
version: "2.1.1"
});
Npm.depends({
"uglify-js": "3.0.13"
"uglify-js": "3.0.18"
});
Package.onUse(function (api) {

View File

@@ -650,6 +650,24 @@ function getValueBitmask(value, length) {
return false;
}
// Actually inserts a key value into the selector document
// However, this checks there is no ambiguity in setting
// the value for the given key, throws otherwise
function insertIntoDocument(document, key, value) {
Object.keys(document).forEach(existingKey => {
if (
(existingKey.length > key.length && existingKey.indexOf(key) === 0) ||
(key.length > existingKey.length && key.indexOf(existingKey) === 0)
) {
throw new Error(`cannot infer query fields to set, both paths '${existingKey}' and '${key}' are matched`);
} else if (existingKey === key) {
throw new Error(`cannot infer query fields to set, path '${key}' is matched twice`);
}
});
document[key] = value;
}
// Returns a branched matcher that matches iff the given matcher does not.
// Note that this implicitly "deMorganizes" the wrapped function. ie, it
// means that ALL branch values need to fail to match innerBranchedMatcher.
@@ -979,6 +997,87 @@ function pointToArray(point) {
return Array.isArray(point) ? point.slice() : [point.x, point.y];
}
// Creating a document from an upsert is quite tricky.
// E.g. this selector: {"$or": [{"b.foo": {"$all": ["bar"]}}]}, should result in: {"b.foo": "bar"}
// But this selector: {"$or": [{"b": {"foo": {"$all": ["bar"]}}}]} should throw an error
// Some rules (found mainly with trial & error, so there might be more):
// - handle all childs of $and (or implicit $and)
// - handle $or nodes with exactly 1 child
// - ignore $or nodes with more than 1 child
// - ignore $nor and $not nodes
// - throw when a value can not be set unambiguously
// - every value for $all should be dealt with as separate $eq-s
// - threat all children of $all as $eq setters (=> set if $all.length === 1, otherwise throw error)
// - you can not mix '$'-prefixed keys and non-'$'-prefixed keys
// - you can only have dotted keys on a root-level
// - you can not have '$'-prefixed keys more than one-level deep in an object
// Handles one key/value pair to put in the selector document
function populateDocumentWithKeyValue(document, key, value) {
if (value && Object.getPrototypeOf(value) === Object.prototype) {
populateDocumentWithObject(document, key, value);
} else if (!(value instanceof RegExp)) {
insertIntoDocument(document, key, value);
}
}
// Handles a key, value pair to put in the selector document
// if the value is an object
function populateDocumentWithObject(document, key, value) {
const keys = Object.keys(value);
const unprefixedKeys = keys.filter(op => op[0] !== '$');
if (unprefixedKeys.length > 0 || !keys.length) {
// Literal (possibly empty) object ( or empty object )
// Don't allow mixing '$'-prefixed with non-'$'-prefixed fields
if (keys.length !== unprefixedKeys.length)
throw new Error(`unknown operator: ${unprefixedKeys[0]}`);
validateObject(value, key);
insertIntoDocument(document, key, value);
} else {
Object.keys(value).forEach(op => {
const object = value[op];
if (op === '$eq') {
populateDocumentWithKeyValue(document, key, object);
} else if (op === '$all') {
// every value for $all should be dealt with as separate $eq-s
object.forEach(element => populateDocumentWithKeyValue(document, key, element));
}
});
}
}
// Fills a document with certain fields from an upsert selector
export function populateDocumentWithQueryFields(query, document = {}) {
if (Object.getPrototypeOf(query) === Object.prototype) {
// handle implicit $and
Object.keys(query).forEach(key => {
const value = query[key];
if (key === '$and') {
// handle explicit $and
value.forEach(element => populateDocumentWithQueryFields(element, document));
} else if (key === '$or') {
// handle $or nodes with exactly 1 child
if (value.length === 1)
populateDocumentWithQueryFields(value[0], document);
} else if (key[0] !== '$') {
// Ignore other '$'-prefixed logical selectors
populateDocumentWithKeyValue(document, key, value);
}
})
} else {
// Handle meteor-specific shortcut for selecting _id
if (LocalCollection._selectorIsId(query))
insertIntoDocument(document, '_id', query);
}
return document;
}
// Traverses the keys of passed projection and constructs a tree where all
// leaves are either all True or all False
// @returns Object:
@@ -1062,3 +1161,23 @@ export function regexpElementMatcher(regexp) {
return regexp.test(value);
};
}
// Validates the key in a path.
// Objects that are nested more then 1 level cannot have dotted fields
// or fields starting with '$'
function validateKeyInPath(key, path) {
if (key.includes('.'))
throw new Error(`The dotted field '${key}' in '${path}.${key} is not valid for storage.`);
if (key[0] === '$')
throw new Error(`The dollar ($) prefixed field '${path}.${key} is not valid for storage.`);
}
// Recursively validates an object that is nested more than one level deep
function validateObject(object, path) {
if (object && Object.getPrototypeOf(object) === Object.prototype) {
Object.keys(object).forEach(key => {
validateKeyInPath(key, path);
validateObject(object[key], path + '.' + key);
});
}
}

View File

@@ -4,6 +4,7 @@ import {
isIndexable,
isNumericKey,
isOperatorObject,
populateDocumentWithQueryFields,
projectionDetails,
} from './common.js';
@@ -405,19 +406,7 @@ export default class LocalCollection {
// generate an id for it.
let insertedId;
if (updateCount === 0 && options.upsert) {
const selectorModifier = LocalCollection._removeDollarOperators(LocalCollection._selectorIsId(selector) ? {_id: selector} : selector);
const doc = {};
if (selectorModifier._id) {
doc._id = selectorModifier._id;
delete selectorModifier._id;
}
// This double _modify call is made to help work around an issue where collection
// upserts won't work properly, with nested properties (see issue #8631).
LocalCollection._modify(doc, {$set: selectorModifier});
LocalCollection._modify(doc, mod, {isInsert: true});
const doc = LocalCollection._createUpsertDocument(selector, mod);
if (! doc._id && options.insertedId)
doc._id = options.insertedId;
@@ -828,6 +817,37 @@ LocalCollection._compileProjection = fields => {
};
};
// Calculates the document to insert in case we're doing an upsert and the selector
// does not match any elements
LocalCollection._createUpsertDocument = (selector, modifier) => {
const selectorDocument = populateDocumentWithQueryFields(selector);
const isModify = LocalCollection._isModificationMod(modifier);
const newDoc = {};
if (selectorDocument._id) {
newDoc._id = selectorDocument._id;
delete selectorDocument._id;
}
// This double _modify call is made to help with nested properties (see issue #8631).
// We do this even if it's a replacement for validation purposes (e.g. ambiguous id's)
LocalCollection._modify(newDoc, {$set: selectorDocument});
LocalCollection._modify(newDoc, modifier, {isInsert: true});
if (isModify) {
return newDoc;
}
// Replacement can take _id from query document
const replacement = Object.assign({}, modifier);
if (newDoc._id) {
replacement._id = newDoc._id;
}
return replacement;
};
LocalCollection._diffObjects = (left, right, callbacks) => {
return DiffSequence.diffObjects(left, right, callbacks);
};
@@ -945,6 +965,25 @@ LocalCollection._insertInSortedList = (cmp, array, value) => {
return i;
};
LocalCollection._isModificationMod = mod => {
let isModify = false;
let isReplace = false;
for (const key in mod) {
if (key.substr(0, 1) === '$') {
isModify = true;
} else {
isReplace = true;
}
}
if (isModify && isReplace) {
throw new Error('Update parameter cannot have both modifier and non-modifier fields.');
}
return isModify;
};
// XXX maybe this should be EJSON.isObject, though EJSON doesn't know about
// RegExp
// XXX note that _type(undefined) === 3!!!!
@@ -990,9 +1029,6 @@ LocalCollection._modify = (doc, modifier, options = {}) => {
if (keypath === '')
throw MinimongoError('An empty update path is not valid.');
if (keypath === '_id' && operator !== '$setOnInsert')
throw MinimongoError('Mod on _id not allowed');
const keyparts = keypath.split('.');
if (!keyparts.every(Boolean))
@@ -1009,9 +1045,12 @@ LocalCollection._modify = (doc, modifier, options = {}) => {
modFunc(target, keyparts.pop(), arg, keypath, newDoc);
});
});
if (doc._id && !EJSON.equals(doc._id, newDoc._id))
throw MinimongoError(`After applying the update to the document {_id: "${doc._id}", ...}, the (immutable) field '_id' was found to have been altered to _id: "${newDoc._id}"`);
} else {
if (modifier._id && !EJSON.equals(doc._id, modifier._id))
throw MinimongoError('Cannot change the _id of a document');
throw MinimongoError(`The _id field cannot be changed from {_id: "${doc._id}"} to {_id: "${modifier._id}"}`);
// replace the whole document
assertHasValidFieldNames(modifier);
@@ -1151,31 +1190,6 @@ LocalCollection._observeChangesCallbacksAreOrdered = callbacks => {
return !!(callbacks.addedBefore || callbacks.movedBefore);
};
// When performing an upsert, the incoming selector object can be re-used as
// the upsert modifier object, as long as Mongo query and projection
// operators (prefixed with a $ character) are removed from the newly
// created modifier object. This function attempts to strip all $ based Mongo
// operators when creating the upsert modifier object.
// NOTE: There is a known issue here in that some Mongo $ based opeartors
// should not actually be stripped.
// See https://github.com/meteor/meteor/issues/8806.
LocalCollection._removeDollarOperators = selector => {
const cleansed = {};
Object.keys(selector).forEach((key) => {
const value = selector[key];
if (key.charAt(0) !== '$' && !objectOnlyHasDollarKeys(value)) {
if (value !== null && value.constructor && Object.getPrototypeOf(value) === Object.prototype)
cleansed[key] = LocalCollection._removeDollarOperators(value);
else
cleansed[key] = value;
}
});
return cleansed;
};
LocalCollection._removeFromResults = (query, doc) => {
if (query.ordered) {
const i = LocalCollection._findInOrderedResults(query, doc);
@@ -1688,8 +1702,3 @@ function findModTarget(doc, keyparts, options = {}) {
// notreached
}
function objectOnlyHasDollarKeys(object) {
const keys = Object.keys(object);
return keys.length > 0 && keys.every(key => key.charAt(0) === '$');
}

View File

@@ -2325,7 +2325,10 @@ Tinytest.add('minimongo - modify', test => {
// The query is relevant for 'a.$.b'.
coll.update(query, mod);
const actual = coll.findOne();
delete actual._id; // added by insert
if (!expected._id) {
delete actual._id; // added by insert
}
if (typeof expected === 'function') {
expected(actual, EJSON.stringify({input: doc, mod}));
@@ -2363,6 +2366,21 @@ Tinytest.add('minimongo - modify', test => {
test.equal(actual, expected);
};
const upsertUpdate = (initialDoc, query, mod, expected) => {
const collection = new LocalCollection;
collection.insert(initialDoc);
const result = collection.upsert(query, mod);
const actual = collection.findOne();
if (!expected._id) {
delete actual._id;
}
test.equal(actual, expected);
};
const upsertException = (query, mod) => {
const coll = new LocalCollection;
test.throws(() => {
@@ -2592,8 +2610,11 @@ Tinytest.add('minimongo - modify', test => {
modify({a: [1], b: 2}, {$set: {'a.2': 9}}, {a: [1, null, 9], b: 2});
modify({a: {b: 1}}, {$set: {'a.c': 9}}, {a: {b: 1, c: 9}});
modify({}, {$set: {'x._id': 4}}, {x: {_id: 4}});
// Changing _id is disallowed
exception({}, {$set: {_id: 4}});
exception({_id: 4}, {$set: {_id: 4}}); // even not-changing _id is bad
exception({_id: 1}, {$set: {_id: 4}});
modify({_id: 4}, {$set: {_id: 4}}, {_id: 4}); // not-changing _id is not bad
// restricted field names
exception({a: {}}, {$set: {a: {$a: 1}}});
exception({ a: {} }, { $set: { a: { c:
@@ -2895,6 +2916,69 @@ Tinytest.add('minimongo - modify', test => {
{ foo: {}, bar: 'baz' }
);
// Tests for https://github.com/meteor/meteor/issues/8806
upsert({"a": {"b": undefined, "c": null}}, {"$set": {"c": "foo"}}, {"a": {"b": undefined, "c": null}, "c": "foo"})
upsert({"a": {"$eq": "bar" }}, {"$set": {"c": "foo"}}, {"a": "bar", "c": "foo"})
// $all with 1 statement is similar to $eq
upsert({"a": {"$all": ["bar"] }}, {"$set": {"c": "foo"}}, {"a": "bar", "c": "foo"})
upsert({"a": {"$eq": "bar" }, "b": "baz"}, {"$set": {"c": "foo"}}, {"a": "bar", "b": "baz", "c": "foo"})
upsert({"a": {"$exists": true}}, {"$set": {"c": "foo"}}, {"c": "foo"})
upsert({"a": {"$exists": true, "$eq": "foo"}}, {"$set": {"c": "foo"}}, {"a": "foo", "c": "foo"})
upsert({"a": {"$gt": 3, "$eq": 2}}, {"$set": {"c": "foo"}}, {"a": 2, "c": "foo"})
// $and
upsert({"$and": [{"a": {"$eq": "bar"}}]}, {"$set": {"c": "foo"}}, {"a": "bar", "c": "foo"})
upsert({"$and": [{"a": {"$all": ["bar"]}}]}, {"$set": {"c": "foo"}}, {"a": "bar", "c": "foo"})
upsert({"$and": [{"a": {"$all": ["bar"]}}]}, {"$set": {"c": "foo"}}, {"a": "bar", "c": "foo"})
// $or with one statement is handled similar to $and
upsert({"$or": [{"a": "bar"}]}, {"$set": {"c": "foo"}}, {"a": "bar", "c": "foo"})
// $or with multiple statements is ignored
upsert({"$or": [{"a": "bar"}, {"b": "baz"}]}, {"$set": {"c": "foo"}}, {"c": "foo"})
// Negative logical operators are ignored
upsert({"$nor": [{"a": "bar"}]}, {"$set": {"c": "foo"}}, {"c": "foo"})
// Filter out empty objects after filtering out operators
upsert({"a": {"$exists": true}}, {"$set": {"c": "foo"}}, {"c": "foo"})
// But leave actual empty objects
upsert({"a": {}}, {"$set": {"c": "foo"}}, {"a": {}, "c": "foo"})
// Also filter out shorthand regexp notation
upsert({"a": /a/}, {"$set": {"c": "foo"}}, {"c": "foo"})
// Test nested fields
upsert({"$and": [{"a.a": "foo"}, {"$or": [{"a.b": "baz"}]}]}, {"$set": {"c": "foo"}}, {"a": {"a": "foo", "b": "baz"}, "c": "foo"})
// Test for https://github.com/meteor/meteor/issues/5294
upsert({"a": {"$ne": 444}}, {"$push": {"a": 123}}, {"a": [123]})
// Mod takes precedence over query
upsert({"a": "foo"}, {"a": "bar"}, {"a": "bar"})
upsert({"a": "foo"}, {"$set":{"a": "bar"}}, {"a": "bar"})
// Replacement can take _id from query
upsert({"_id": "foo", "foo": "bar"}, {"bar": "foo"}, {"_id": "foo", "bar": "foo"})
// Replacement update keeps _id
upsertUpdate({"_id": "foo", "bar": "baz"}, {"_id":"foo"}, {"bar": "crow"}, {"_id": "foo", "bar": "crow"});
// Nested fields don't work with literal objects
upsertException({"a": {}, "a.b": "foo"}, {});
// You can't have an ambiguious ID
upsertException({"_id":"foo"}, {"_id":"bar"});
upsertException({"_id":"foo"}, {"$set":{"_id":"bar"}});
// You can't set the same field twice
upsertException({"$and": [{"a": "foo"}, {"a": "foo"}]}, {}); //not even with same value
upsertException({"a": {"$all": ["foo", "bar"]}}, {});
upsertException({"$and": [{"a": {"$eq": "foo"}}, {"$or": [{"a": {"$all": ["bar"]}}]}]}, {});
// You can't have nested dotted fields
upsertException({"a": {"foo.bar": "baz"}}, {});
// You can't have dollar-prefixed fields above the first level (logical operators not counted)
upsertException({"a": {"a": {"$eq": "foo"}}}, {});
upsertException({"a": {"a": {"$exists": true}}}, {});
// You can't mix operators with other fields
upsertException({"a": {"$eq": "bar", "b": "foo"}}, {})
upsertException({"a": {"b": "foo", "$eq": "bar"}}, {})
const mongoIdForUpsert = new MongoID.ObjectID('44915733af80844fa1cef07a');
upsert({_id: mongoIdForUpsert}, {$setOnInsert: {a: 123}}, {a: 123})
// Test for https://github.com/meteor/meteor/issues/7758
upsert({n_id: mongoIdForUpsert, c_n: "bar"},
{$set: { t_t_o: "foo"}},
{n_id: mongoIdForUpsert, t_t_o: "foo", c_n: "bar"});
exception({}, {$set: {_id: 'bad'}});
// $bit

View File

@@ -16,9 +16,9 @@
"from": "minizlib@>=1.0.3 <2.0.0"
},
"reify": {
"version": "0.11.22",
"resolved": "https://registry.npmjs.org/reify/-/reify-0.11.22.tgz",
"from": "reify@0.11.22"
"version": "0.11.24",
"resolved": "https://registry.npmjs.org/reify/-/reify-0.11.24.tgz",
"from": "reify@0.11.24"
},
"semver": {
"version": "5.3.0",

View File

@@ -1,12 +1,12 @@
Package.describe({
name: "modules",
version: "0.9.1",
version: "0.9.2",
summary: "CommonJS module system",
documentation: "README.md"
});
Npm.depends({
reify: "0.11.22"
reify: "0.11.24"
});
Package.onUse(function(api) {

View File

@@ -562,6 +562,7 @@ Mongo.Collection.prototype.update = function update(selector, modifier, ...optio
insertedId = options.insertedId;
} else if (!selector || !selector._id) {
insertedId = this._makeNewID();
options.generatedId = true;
options.insertedId = insertedId;
}
}

View File

@@ -511,10 +511,9 @@ MongoConnection.prototype._update = function (collection_name, selector, mod,
var mongoSelector = replaceTypes(selector, replaceMeteorAtomWithMongo);
var mongoMod = replaceTypes(mod, replaceMeteorAtomWithMongo);
var isModify = isModificationMod(mongoMod);
var knownId = selector._id || mod._id;
var isModify = LocalCollection._isModificationMod(mongoMod);
if (options._forbidReplace && ! isModify) {
if (options._forbidReplace && !isModify) {
var err = new Error("Invalid modifier. Replacements are forbidden.");
if (callback) {
return callback(err);
@@ -523,22 +522,45 @@ MongoConnection.prototype._update = function (collection_name, selector, mod,
}
}
if (options.upsert && (! knownId) && options.insertedId) {
// XXX If we know we're using Mongo 2.6 (and this isn't a replacement)
// we should be able to just use $setOnInsert instead of this
// simulated upsert thing. (We can't use $setOnInsert with
// replacements because there's nowhere to write it, and $setOnInsert
// can't set _id on Mongo 2.4.)
//
// Also, in the future we could do a real upsert for the mongo id
// generation case, if the the node mongo driver gives us back the id
// of the upserted doc (which our current version does not).
//
// For more context, see
// https://github.com/meteor/meteor/issues/2278#issuecomment-64252706
// We've already run replaceTypes/replaceMeteorAtomWithMongo on
// selector and mod. We assume it doesn't matter, as far as
// the behavior of modifiers is concerned, whether `_modify`
// is run on EJSON or on mongo-converted EJSON.
// Run this code up front so that it fails fast if someone uses
// a Mongo update operator we don't support.
let knownId;
if (options.upsert) {
try {
let newDoc = LocalCollection._createUpsertDocument(selector, mod);
knownId = newDoc._id;
} catch (err) {
if (callback) {
return callback(err);
} else {
throw err;
}
}
}
if (options.upsert &&
! isModify &&
! knownId &&
options.insertedId &&
! (options.insertedId instanceof Mongo.ObjectID &&
options.generatedId)) {
// In case of an upsert with a replacement, where there is no _id defined
// in either the query or the replacement doc, mongo will generate an id itself.
// Therefore we need this special strategy if we want to control the id ourselves.
// We don't need to do this when:
// - This is not a replacement, so we can add an _id to $setOnInsert
// - The id is defined by query or mod we can just add it to the replacement doc
// - The user did not specify any id preference and the id is a Mongo ObjectId,
// then we can just let Mongo generate the id
simulateUpsertWithInsertedId(
collection, mongoSelector, mongoMod,
isModify, options,
collection, mongoSelector, mongoMod, options,
// This callback does not need to be bindEnvironment'ed because
// simulateUpsertWithInsertedId() wraps it and then passes it through
// bindEnvironmentForWrite.
@@ -554,6 +576,15 @@ MongoConnection.prototype._update = function (collection_name, selector, mod,
}
);
} else {
if (options.upsert && !knownId && options.insertedId && isModify) {
if (!mongoMod.hasOwnProperty('$setOnInsert')) {
mongoMod.$setOnInsert = {};
}
knownId = options.insertedId;
Object.assign(mongoMod.$setOnInsert, replaceTypes({_id: options.insertedId}, replaceMeteorAtomWithMongo));
}
collection.update(
mongoSelector, mongoMod, mongoOpts,
bindEnvironmentForWrite(function (err, result) {
@@ -563,10 +594,14 @@ MongoConnection.prototype._update = function (collection_name, selector, mod,
// If this was an upsert() call, and we ended up
// inserting a new doc and we know its id, then
// return that id as well.
if (options.upsert && meteorResult.insertedId && knownId) {
meteorResult.insertedId = knownId;
if (options.upsert && meteorResult.insertedId) {
if (knownId) {
meteorResult.insertedId = knownId;
} else if (meteorResult.insertedId instanceof MongoDB.ObjectID) {
meteorResult.insertedId = new Mongo.ObjectID(meteorResult.insertedId.toHexString());
}
}
callback(err, meteorResult);
} else {
callback(err, meteorResult.numberAffected);
@@ -582,23 +617,6 @@ MongoConnection.prototype._update = function (collection_name, selector, mod,
}
};
var isModificationMod = function (mod) {
var isReplace = false;
var isModify = false;
for (var k in mod) {
if (k.substr(0, 1) === '$') {
isModify = true;
} else {
isReplace = true;
}
}
if (isModify && isReplace) {
throw new Error(
"Update parameter cannot have both modifier and non-modifier fields.");
}
return isModify;
};
var transformResult = function (driverResult) {
var meteorResult = { numberAffected: 0 };
if (driverResult) {
@@ -626,26 +644,18 @@ var NUM_OPTIMISTIC_TRIES = 3;
// exposed for testing
MongoConnection._isCannotChangeIdError = function (err) {
// First check for what this error looked like in Mongo 2.4. Either of these
// checks should work, but just to be safe...
if (err.code === 13596) {
return true;
}
// Mongo 3.2.* returns error as next Object:
// {name: String, code: Number, err: String}
// Older Mongo returns:
// {name: String, code: Number, errmsg: String}
// Older Mongo returns:
// {name: String, code: Number, err: String}
var error = err.errmsg || err.err;
if (error.indexOf('cannot change _id of a document') === 0) {
return true;
}
// Now look for what it looks like in Mongo 2.6. We don't use the error code
// here, because the error code we observed it producing (16837) appears to be
// We don't use the error code here
// because the error code we observed it producing (16837) appears to be
// a far more generic error code based on examining the source.
if (error.indexOf('The _id field cannot be changed') === 0) {
if (error.indexOf('The _id field cannot be changed') === 0
|| error.indexOf("the (immutable) field '_id' was found to have been altered to _id") !== -1) {
return true;
}
@@ -653,66 +663,20 @@ MongoConnection._isCannotChangeIdError = function (err) {
};
var simulateUpsertWithInsertedId = function (collection, selector, mod,
isModify, options, callback) {
// STRATEGY: First try doing a plain update. If it affected 0 documents,
// then without affecting the database, we know we should probably do an
// insert. We then do a *conditional* insert that will fail in the case
// of a race condition. This conditional insert is actually an
// upsert-replace with an _id, which will never successfully update an
// existing document. If this upsert fails with an error saying it
// couldn't change an existing _id, then we know an intervening write has
// caused the query to match something. We go back to step one and repeat.
options, callback) {
// STRATEGY: First try doing an upsert with a generated ID.
// If this throws an error about changing the ID on an existing document
// then without affecting the database, we know we should probably try
// an update without the generated ID. If it affected 0 documents,
// then without affecting the database, we the document that first
// gave the error is probably removed and we need to try an insert again
// We go back to step one and repeat.
// Like all "optimistic write" schemes, we rely on the fact that it's
// unlikely our writes will continue to be interfered with under normal
// circumstances (though sufficiently heavy contention with writers
// disagreeing on the existence of an object will cause writes to fail
// in theory).
var newDoc;
// Run this code up front so that it fails fast if someone uses
// a Mongo update operator we don't support.
if (isModify) {
// We've already run replaceTypes/replaceMeteorAtomWithMongo on
// selector and mod. We assume it doesn't matter, as far as
// the behavior of modifiers is concerned, whether `_modify`
// is run on EJSON or on mongo-converted EJSON.
var selectorDoc = LocalCollection._removeDollarOperators(selector);
newDoc = selectorDoc;
// Convert dotted keys into objects. (Resolves issue #4522).
_.each(newDoc, function (value, key) {
var trail = key.split(".");
if (trail.length > 1) {
//Key is dotted. Convert it into an object.
delete newDoc[key];
var obj = newDoc,
leaf = trail.pop();
// XXX It is not quite certain what should be done if there are clashing
// keys on the trail of the dotted key. For now we will just override it
// It wouldn't be a very sane query in the first place, but should look
// up what mongo does in this case.
while ((key = trail.shift())) {
if (typeof obj[key] !== "object") {
obj[key] = {};
}
obj = obj[key];
}
obj[leaf] = value;
}
});
LocalCollection._modify(newDoc, mod, {isInsert: true});
} else {
newDoc = mod;
}
var insertedId = options.insertedId; // must exist
var mongoOptsForUpdate = {
safe: true,
@@ -723,6 +687,10 @@ var simulateUpsertWithInsertedId = function (collection, selector, mod,
upsert: true
};
var replacementWithId = Object.assign(
replaceTypes({_id: insertedId}, replaceMeteorAtomWithMongo),
mod);
var tries = NUM_OPTIMISTIC_TRIES;
var doUpdate = function () {
@@ -746,9 +714,6 @@ var simulateUpsertWithInsertedId = function (collection, selector, mod,
};
var doConditionalInsert = function () {
var replacementWithId = _.extend(
replaceTypes({_id: insertedId}, replaceMeteorAtomWithMongo),
newDoc);
collection.update(selector, replacementWithId, mongoOptsForInsert,
bindEnvironmentForWrite(function (err, result) {
if (err) {

View File

@@ -1646,10 +1646,10 @@ if (Meteor.isServer) {
var run = test.runId();
var coll = new Mongo.Collection("livedata_upsert_errorparse_collection_"+run, collectionOptions);
coll.insert({_id: 'foobar'});
coll.insert({_id:'foobar', foo: 'bar'});
var err;
try {
coll.update({_id: 'foobar'}, {_id: 'cowbar'});
coll.update({foo: 'bar'}, {_id: 'cowbar'});
} catch (e) {
err = e;
}

View File

@@ -9,7 +9,7 @@
Package.describe({
summary: "Adaptor for using MongoDB and Minimongo over DDP",
version: '1.1.18'
version: '1.1.19'
});
Npm.depends({

View File

@@ -0,0 +1 @@
node_modules

View File

@@ -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.

View File

@@ -0,0 +1,19 @@
{
"dependencies": {
"magic-string": {
"version": "0.21.3",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.21.3.tgz",
"from": "magic-string@0.21.3"
},
"parse5": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.2.tgz",
"from": "parse5@3.0.2"
},
"vlq": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.2.tgz",
"from": "vlq@>=0.2.1 <0.3.0"
}
}
}

View File

@@ -0,0 +1,109 @@
# server-render
[Source code of released version](https://github.com/meteor/meteor/tree/master/packages/server-render) | [Source code of development version](https://github.com/meteor/meteor/tree/devel/packages/server-render)
***
This package implements generic support for server-side rendering in
Meteor apps, by providing a mechanism for injecting fragments of HTML into
the `<head>` and/or `<body>` of the application's initial HTML response.
### Usage
This package exports a function named `onPageLoad` which takes a callback
function that will be called at page load (on the client) or whenever a
new request happens (on the server).
The callback receives a `sink` object, which is an instance of either
`ClientSink` or `ServerSink` depending on the environment. Both types of
`sink` have the same methods, though the server version accepts only HTML
strings as content, whereas the client version also accepts DOM nodes.
The current interface of `{Client,Server}Sink` objects is as follows:
```js
class Sink {
// Appends content to the <head>.
appendToHead(content)
// Appends content to the <body>.
appendToBody(content)
// Appends content to the identified element.
appendToElementById(id, content)
// Replaces the content of the identified element.
renderIntoElementById(id, content)
}
```
The `sink` object may also expose additional properties depending on the
environment. For example, on the server, `sink.request` provides access to
the current `request` object, and `sink.arch` identifies the target
architecture of the pending HTTP response (e.g. "web.browser").
Here is a basic example of `onPageLoad` usage on the server:
```js
import React from "react";
import { renderToString } from "react-dom/server";
import { onPageLoad } from "meteor/server-render";
import App from "/imports/Server.js";
onPageLoad(sink => {
sink.renderIntoElementById("app", renderToString(
<App location={sink.request.url} />
));
});
```
Likewise on the client:
```js
import React from "react";
import ReactDOM from "react-dom";
import { onPageLoad } from "meteor/server-render";
onPageLoad(async sink => {
const App = (await import("/imports/Client.js")).default;
ReactDOM.render(
<App />,
document.getElementById("app")
);
});
```
Note that the `onPageLoad` callback function is allowed to return a
`Promise` if it needs to do any asynchronous work, and thus may be
implemented by an `async` function (as in the client case above).
Note also that the client example does not end up calling any methods of
the `sink` object, because `ReactDOM.render` has its own similar API. In
fact, you are not even required to use the `onPageLoad` API on the client,
if you have your own ideas about how the client should do its rendering.
Here is a more complicated example of `onPageLoad` usage on the server,
involving the [`styled-components`](https://www.styled-components.com/docs/advanced#server-side-rendering) npm package:
```js
import React from "react";
import { onPageLoad } from "meteor/server-render";
import { renderToString } from "react-dom/server";
import { ServerStyleSheet } from "styled-components"
import App from "/imports/Server";
onPageLoad(sink => {
const sheet = new ServerStyleSheet();
const html = renderToString(sheet.collectStyles(
<App location={sink.request.url} />
));
sink.renderIntoElementById("app", html);
sink.appendToHead(sheet.getStyleTags());
});
```
In this example, the callback not only renders the `<App />` element into
the element with `id="app"`, but also appends any `<style>` tag(s)
generated during rendering to the `<head>` of the response document.
Although these examples have all involved React, the `onPageLoad` API is
designed to be generically useful for any kind of server-side rendering.

View File

@@ -0,0 +1,43 @@
const doc = document;
const head = doc.getElementsByTagName("head")[0];
const body = doc.body;
export class ClientSink {
appendToHead(nodeOrHtml) {
appendContent(head, nodeOrHtml);
}
appendToBody(nodeOrHtml) {
appendContent(body, nodeOrHtml);
}
appendToElementById(id, nodeOrHtml) {
appendContent(doc.getElementById(id), nodeOrHtml);
}
renderIntoElementById(id, nodeOrHtml) {
const element = doc.getElementById(id);
while (element.lastChild) {
element.removeChild(element.lastChild);
}
appendContent(element, nodeOrHtml);
}
}
function appendContent(destination, nodeOrHtml) {
if (typeof nodeOrHtml === "string") {
// Make a shallow clone of the destination node to ensure the new
// children can legally be appended to it.
const container = destination.cloneNode(false);
// Parse the HTML into the container, allowing for multiple children.
container.innerHTML = nodeOrHtml;
// Transplant the children to the destination.
while (container.firstChild) {
destination.appendChild(container.firstChild);
}
} else if (Array.isArray(nodeOrHtml)) {
nodeOrHtml.forEach(elem => appendContent(destination, elem));
} else {
destination.appendChild(nodeOrHtml);
}
}

View File

@@ -0,0 +1,9 @@
import { Meteor } from "meteor/meteor";
import { ClientSink } from "./client-sink.js";
let promise = new Promise(Meteor.startup);
let sink = new ClientSink();
export function onPageLoad(callback) {
promise = promise.then(() => callback(sink));
}

View File

@@ -0,0 +1,25 @@
Package.describe({
name: "server-render",
version: "0.1.0",
summary: "Generic support for server-side rendering in Meteor apps",
documentation: "README.md"
});
Npm.depends({
"magic-string": "0.21.3",
"parse5": "3.0.2"
});
Package.onUse(function(api) {
api.use("ecmascript");
api.use("webapp@1.3.17");
api.mainModule("client.js", "client");
api.mainModule("server.js", "server");
});
Package.onTest(function(api) {
api.use("ecmascript");
api.use("tinytest");
api.use("server-render");
api.mainModule("server-render-tests.js", "server");
});

View File

@@ -0,0 +1,70 @@
import { WebAppInternals } from "meteor/webapp";
import { SAXParser } from "parse5";
import MagicString from "magic-string";
import { ServerSink } from "./server-sink.js";
import { onPageLoad } from "./server.js";
WebAppInternals.registerBoilerplateDataCallback(
"meteor/server-render",
(request, data, arch) => {
const sink = new ServerSink(request, arch);
return onPageLoad.chain(
callback => callback(sink, request)
).then(() => {
if (! sink.maybeMadeChanges) {
return false;
}
let reallyMadeChanges = false;
function rewrite(property) {
const html = data[property];
if (typeof html !== "string") {
return;
}
const magic = new MagicString(html);
const parser = new SAXParser({
locationInfo: true
});
parser.on("startTag", (name, attrs, selfClosing, loc) => {
attrs.some(attr => {
if (attr.name === "id") {
const html = sink.htmlById[attr.value];
if (html) {
magic.appendRight(loc.endOffset, html);
reallyMadeChanges = true;
}
return true;
}
});
});
parser.write(html);
data[property] = magic.toString();
}
if (sink.head) {
data.dynamicHead = (data.dynamicHead || "") + sink.head;
reallyMadeChanges = true;
}
if (Object.keys(sink.htmlById).length > 0) {
// We don't currently allow injecting HTML into the <head> except
// by calling sink.appendHead(html).
rewrite("body");
rewrite("dynamicBody");
}
if (sink.body) {
data.dynamicBody = (data.dynamicBody || "") + sink.body;
reallyMadeChanges = true;
}
return reallyMadeChanges;
});
}
);

View File

@@ -0,0 +1,104 @@
import { Tinytest } from "meteor/tinytest";
import { WebAppInternals } from "meteor/webapp";
import { onPageLoad } from "meteor/server-render";
import { parse } from "parse5";
const skeleton = `
<h1>Look, Ma... static HTML!</h1>
<div id="container-2"></div>
<p>
<div id="container-1">
</div>
</p>`;
Tinytest.add('server-render - boilerplate', function (test) {
// This test is not a very good demonstration of the server-render
// abstraction. In normal usage, you would call renderIntoElementById
// and not think about the rest of this stuff. The extra complexity owes
// to the trickiness of testing this package without using a real
// browser to parse the resulting HTTP response.
const realCallback =
// Use the underlying abstraction to set the static HTML skeleton.
WebAppInternals.registerBoilerplateDataCallback(
"meteor/server-render",
(request, data, arch) => {
if (request.isServerRenderTest) {
test.equal(arch, "web.browser");
test.equal(request.url, "/server-render/test");
data.body = skeleton;
}
return realCallback.call(this, request, data, arch);
}
);
const callback1 = onPageLoad(sink => {
sink.renderIntoElementById("container-1", "<oyez/>");
});
// This callback is async, and that's fine because
// WebAppInternals.getBoilerplate is able to yield. Internally the
// webapp package uses a function called getBoilerplateAsync, so the
// Fiber power-tools need not be involved in typical requests.
const callback2 = onPageLoad(async sink => {
sink.renderIntoElementById(
"container-2",
(await "oy") + (await "ez")
);
});
try {
const boilerplate = WebAppInternals.getBoilerplate({
isServerRenderTest: true,
browser: { name: "fake" },
url: "/server-render/test"
}, "web.browser");
const ids = [];
const seen = new Set;
function walk(node) {
if (node && ! seen.has(node)) {
seen.add(node);
if (node.nodeName === "div" && node.attrs) {
node.attrs.some(attr => {
if (attr.name === "id") {
const id = attr.value;
if (id === "container-1") {
test.equal(node.childNodes[0].nodeName, "oyez");
ids.push(id);
} else if (id === "container-2") {
const child = node.childNodes[0];
test.equal(child.nodeName, "#text");
test.equal(child.value.trim(), "oyez");
ids.push(id);
}
return true;
}
});
}
if (node.childNodes) {
node.childNodes.forEach(walk)
}
}
}
walk(parse(boilerplate));
test.equal(ids, ["container-2", "container-1"]);
} finally {
// Cleanup to minimize interference with other tests:
WebAppInternals.registerBoilerplateDataCallback(
"meteor/server-render",
realCallback
);
onPageLoad.remove(callback1);
onPageLoad.remove(callback2);
}
});

View File

@@ -0,0 +1,50 @@
export class ServerSink {
constructor(request, arch) {
this.request = request;
this.arch = arch;
this.head = "";
this.body = "";
this.htmlById = Object.create(null);
this.maybeMadeChanges = false;
}
appendToHead(html) {
if (appendContent(this, "head", html)) {
this.maybeMadeChanges = true;
}
}
appendToBody(html) {
if (appendContent(this, "body", html)) {
this.maybeMadeChanges = true;
}
}
appendToElementById(id, html) {
if (appendContent(this.htmlById, id, html)) {
this.maybeMadeChanges = true;
}
}
renderIntoElementById(id, html) {
this.htmlById[id] = "";
this.appendToElementById(id, html);
}
}
function appendContent(object, property, content) {
let madeChanges = false;
if (Array.isArray(content)) {
content.forEach(elem => {
if (appendContent(object, property, elem)) {
madeChanges = true;
}
});
} else if ((content = content && content.toString("utf8"))) {
object[property] = (object[property] || "") + content;
madeChanges = true;
}
return madeChanges;
}

View File

@@ -0,0 +1,32 @@
import { Meteor } from "meteor/meteor";
import "./server-register.js";
const startupPromise = new Promise(Meteor.startup);
const pageLoadCallbacks = new Set;
export function onPageLoad(callback) {
if (typeof callback === "function") {
pageLoadCallbacks.add(callback);
}
// Return the callback so that it can be more easily removed later.
return callback;
}
onPageLoad.remove = function (callback) {
pageLoadCallbacks.delete(callback);
};
onPageLoad.clear = function () {
pageLoadCallbacks.clear();
};
onPageLoad.chain = function (handler) {
return startupPromise.then(() => {
let promise = Promise.resolve();
pageLoadCallbacks.forEach(callback => {
promise = promise.then(() => handler(callback));
});
return promise;
});
};

View File

@@ -1,6 +1,6 @@
Package.describe({
name: "shell-server",
version: "0.2.3",
version: "0.2.4",
summary: "Server-side component of the `meteor shell` command.",
documentation: "README.md"
});

View File

@@ -418,17 +418,14 @@ function evalCommand(command, context, filename, callback) {
}
evalCommandPromise.then(function () {
if (repl.input) {
callback(null, script.runInThisContext());
} else {
// If repl didn't start, `require` and `module` are not visible
// in the vm context
// in the vm context.
setRequireAndModule(global);
callback(null, script.runInThisContext());
}
}).catch(callback);
}
@@ -474,7 +471,6 @@ function isRecoverableError(e, repl) {
}
function setRequireAndModule(context) {
if (Package.modules) {
// Use the same `require` function and `module` object visible to the
// application.

View File

@@ -1,6 +1,6 @@
Package.describe({
name: 'standard-minifier-js',
version: '2.1.0',
version: '2.1.1',
summary: 'Standard javascript minifiers used with Meteor apps by default.',
documentation: 'README.md',
});

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: "Serves a Meteor app over HTTP",
version: '1.3.16'
version: '1.3.17'
});
Npm.depends({connect: "2.30.2",
@@ -30,15 +30,19 @@ Package.onUse(function (api) {
// (browser-policy depends on webapp). So we don't explicitly depend in any
// way on browser-policy here, but we use it when it is loaded, and it can be
// loaded after webapp.
api.export(['WebApp', 'main', 'WebAppInternals'], 'server');
api.export(['WebApp'], 'client');
api.addFiles('webapp_server.js', 'server');
api.addFiles('webapp_client.js', 'client');
api.addFiles('webapp_cordova.js', 'web.cordova');
api.mainModule('webapp_server.js', 'server');
api.export('WebApp', 'server');
api.export('WebAppInternals', 'server');
api.export('main', 'server');
api.mainModule('webapp_client.js', 'client');
api.export('WebApp', 'client');
api.mainModule('webapp_cordova.js', 'web.cordova');
});
Package.onTest(function (api) {
api.use(['tinytest', 'webapp', 'http', 'underscore']);
api.use(['tinytest', 'ecmascript', 'webapp', 'http', 'underscore']);
api.addFiles('webapp_tests.js', 'server');
api.addFiles('webapp_client_tests.js', 'client');
});

View File

@@ -1,15 +1,18 @@
WebApp = {
_isCssLoaded: function () {
if (document.styleSheets.length === 0)
export const WebApp = {
_isCssLoaded() {
if (document.styleSheets.length === 0) {
return true;
}
return _.find(document.styleSheets, function (sheet) {
if (sheet.cssText && !sheet.cssRules) // IE8
return !sheet.cssText.match(/meteor-css-not-found-error/);
return !_.find(sheet.cssRules, function (rule) {
return rule.selectorText === '.meteor-css-not-found-error';
});
return _.find(document.styleSheets, sheet => {
if (sheet.cssText && ! sheet.cssRules) { // IE8
return ! sheet.cssText.match(/meteor-css-not-found-error/);
}
return ! _.find(
sheet.cssRules,
rule => rule.selectorText === '.meteor-css-not-found-error'
);
});
}
};

View File

@@ -1,25 +1,22 @@
////////// Requires //////////
var fs = Npm.require("fs");
var http = Npm.require("http");
var os = Npm.require("os");
var path = Npm.require("path");
var url = Npm.require("url");
var crypto = Npm.require("crypto");
var connect = Npm.require('connect');
var parseurl = Npm.require('parseurl');
var useragent = Npm.require('useragent');
var send = Npm.require('send');
var Future = Npm.require('fibers/future');
var Fiber = Npm.require('fibers');
import assert from "assert";
import { readFile } from "fs";
import { createServer } from "http";
import {
join as pathJoin,
dirname as pathDirname,
} from "path";
import { parse as parseUrl } from "url";
import { createHash } from "crypto";
import connect from "connect";
import parseRequest from "parseurl";
import { lookup as lookupUserAgent } from "useragent";
import send from "send";
var SHORT_SOCKET_TIMEOUT = 5*1000;
var LONG_SOCKET_TIMEOUT = 120*1000;
WebApp = {};
WebAppInternals = {};
export const WebApp = {};
export const WebAppInternals = {};
WebAppInternals.NpmModules = {
connect: {
@@ -43,13 +40,13 @@ var bundledJsCssUrlRewriteHook = function (url) {
};
var sha1 = function (contents) {
var hash = crypto.createHash('sha1');
var hash = createHash('sha1');
hash.update(contents);
return hash.digest('hex');
};
var readUtf8FileSync = function (filename) {
return Meteor.wrapAsync(fs.readFile)(filename, 'utf8');
return Meteor.wrapAsync(readFile)(filename, 'utf8');
};
// #BrowserIdentification
@@ -98,7 +95,7 @@ var camelCase = function (name) {
};
var identifyBrowser = function (userAgentString) {
var userAgent = useragent.lookup(userAgentString);
var userAgent = lookupUserAgent(userAgentString);
return {
name: camelCase(userAgent.family),
major: +userAgent.major,
@@ -113,7 +110,7 @@ WebAppInternals.identifyBrowser = identifyBrowser;
WebApp.categorizeRequest = function (req) {
return _.extend({
browser: identifyBrowser(req.headers['user-agent']),
url: url.parse(req.url, true)
url: parseUrl(req.url, true)
}, _.pick(req, 'dynamicHead', 'dynamicBody'));
};
@@ -241,6 +238,29 @@ WebApp._timeoutAdjustmentRequestCallback = function (req, res) {
// - baseData: XXX
var boilerplateByArch = {};
// Register a callback function that can selectively modify boilerplate
// data given arguments (request, data, arch). The key should be a unique
// identifier, to prevent accumulating duplicate callbacks from the same
// call site over time. Callbacks will be called in the order they were
// registered. A callback should return false if it did not make any
// changes affecting the boilerplate. Passing null deletes the callback.
// Any previous callback registered for this key will be returned.
const boilerplateDataCallbacks = Object.create(null);
WebAppInternals.registerBoilerplateDataCallback = function (key, callback) {
const previousCallback = boilerplateDataCallbacks[key];
if (typeof callback === "function") {
boilerplateDataCallbacks[key] = callback;
} else {
assert.strictEqual(callback, null);
delete boilerplateDataCallbacks[key];
}
// Return the previous callback in case the new callback needs to call
// it; for example, when the new callback is a wrapper for the old.
return previousCallback || null;
};
// Given a request (as returned from `categorizeRequest`), return the
// boilerplate HTML to serve for that request.
//
@@ -250,35 +270,61 @@ var boilerplateByArch = {};
// scripts are currently allowed.
// XXX so far this function is always called with arch === 'web.browser'
var memoizedBoilerplate = {};
var getBoilerplate = function (request, arch) {
var useMemoized = ! (request.dynamicHead || request.dynamicBody);
var htmlAttributes = getHtmlAttributes(request);
if (useMemoized) {
// The only thing that changes from request to request (unless extra
// content is added to the head or body) are the HTML attributes
// (used by, eg, appcache) and whether inline scripts are allowed, so we
// can memoize based on that.
function getBoilerplate(request, arch) {
return getBoilerplateAsync(request, arch).await();
}
function getBoilerplateAsync(request, arch) {
const boilerplate = boilerplateByArch[arch];
const data = Object.assign({}, boilerplate.baseData, {
htmlAttributes: getHtmlAttributes(request),
}, _.pick(request, "dynamicHead", "dynamicBody"));
let madeChanges = false;
let promise = Promise.resolve();
Object.keys(boilerplateDataCallbacks).forEach(key => {
promise = promise.then(() => {
const callback = boilerplateDataCallbacks[key];
return callback(request, data, arch);
}).then(result => {
// Callbacks should return false if they did not make any changes.
if (result !== false) {
madeChanges = true;
}
});
});
return promise.then(() => {
const useMemoized = ! (
data.dynamicHead ||
data.dynamicBody ||
madeChanges
);
if (! useMemoized) {
return boilerplate.toHTML(data);
}
// The only thing that changes from request to request (unless extra
// content is added to the head or body, or boilerplateDataCallbacks
// modified the data) are the HTML attributes (used by, eg, appcache)
// and whether inline scripts are allowed, so memoize based on that.
var memHash = JSON.stringify({
inlineScriptsAllowed: inlineScriptsAllowed,
htmlAttributes: htmlAttributes,
arch: arch
inlineScriptsAllowed,
htmlAttributes: data.htmlAttributes,
arch,
});
if (! memoizedBoilerplate[memHash]) {
memoizedBoilerplate[memHash] = boilerplateByArch[arch].toHTML({
htmlAttributes: htmlAttributes
});
memoizedBoilerplate[memHash] =
boilerplateByArch[arch].toHTML(data);
}
return memoizedBoilerplate[memHash];
}
var boilerplateOptions = _.extend({
htmlAttributes: htmlAttributes
}, _.pick(request, 'dynamicHead', 'dynamicBody'));
return boilerplateByArch[arch].toHTML(boilerplateOptions);
};
});
}
WebAppInternals.generateBoilerplateInstance = function (arch,
manifest,
@@ -292,7 +338,7 @@ WebAppInternals.generateBoilerplateInstance = function (arch,
return new Boilerplate(arch, manifest,
_.extend({
pathMapper: function (itemPath) {
return path.join(archPath[arch], itemPath); },
return pathJoin(archPath[arch], itemPath); },
baseDataExtension: {
additionalStaticJs: _.map(
additionalStaticJs || [],
@@ -338,7 +384,7 @@ WebAppInternals.staticFilesMiddleware = function (staticFiles, req, res, next) {
next();
return;
}
var pathname = parseurl(req).pathname;
var pathname = parseRequest(req).pathname;
try {
pathname = decodeURIComponent(pathname);
} catch (e) {
@@ -449,12 +495,12 @@ WebAppInternals.parsePort = function (port) {
return parseInt(port);
};
var runWebAppServer = function () {
function runWebAppServer() {
var shuttingDown = false;
var syncQueue = new Meteor._SynchronousQueue();
var getItemPathname = function (itemUrl) {
return decodeURIComponent(url.parse(itemUrl).pathname);
return decodeURIComponent(parseUrl(itemUrl).pathname);
};
WebAppInternals.reloadClientPrograms = function () {
@@ -462,9 +508,9 @@ var runWebAppServer = function () {
staticFiles = {};
var generateClientProgram = function (clientPath, arch) {
// read the control for the client we'll be serving up
var clientJsonPath = path.join(__meteor_bootstrap__.serverDir,
var clientJsonPath = pathJoin(__meteor_bootstrap__.serverDir,
clientPath);
var clientDir = path.dirname(clientJsonPath);
var clientDir = pathDirname(clientJsonPath);
var clientJson = JSON.parse(readUtf8FileSync(clientJsonPath));
if (clientJson.format !== "web-program-pre1")
throw new Error("Unsupported format for client assets: " +
@@ -479,7 +525,7 @@ var runWebAppServer = function () {
_.each(manifest, function (item) {
if (item.url && item.where === "client") {
staticFiles[urlPrefix + getItemPathname(item.url)] = {
absolutePath: path.join(clientDir, item.path),
absolutePath: pathJoin(clientDir, item.path),
cacheable: item.cacheable,
hash: item.hash,
// Link from source to its map
@@ -491,7 +537,7 @@ var runWebAppServer = function () {
// Serve the source map too, under the specified URL. We assume all
// source maps are cacheable.
staticFiles[urlPrefix + getItemPathname(item.sourceMapUrl)] = {
absolutePath: path.join(clientDir, item.sourceMap),
absolutePath: pathJoin(clientDir, item.sourceMap),
cacheable: true
};
}
@@ -526,7 +572,7 @@ var runWebAppServer = function () {
try {
var clientPaths = __meteor_bootstrap__.configJson.clientPaths;
_.each(clientPaths, function (clientPath, arch) {
archPath[arch] = path.dirname(clientPath);
archPath[arch] = pathDirname(clientPath);
generateClientProgram(clientPath, arch);
});
@@ -643,9 +689,9 @@ var runWebAppServer = function () {
// Serve static files from the manifest.
// This is inspired by the 'static' middleware.
app.use(function (req, res, next) {
Fiber(function () {
WebAppInternals.staticFilesMiddleware(staticFiles, req, res, next);
}).run();
Promise.resolve().then(() => {
WebAppInternals.staticFilesMiddleware(staticFiles, req, res, next);
});
});
// Packages and apps can add handlers to this via WebApp.connectHandlers.
@@ -667,15 +713,18 @@ var runWebAppServer = function () {
});
app.use(function (req, res, next) {
Fiber(function () {
if (!appUrl(req.url))
Promise.resolve().then(() => {
if (! appUrl(req.url)) {
return next();
}
var headers = {
'Content-Type': 'text/html; charset=utf-8'
};
if (shuttingDown)
if (shuttingDown) {
headers['Connection'] = 'Close';
}
var request = WebApp.categorizeRequest(req);
@@ -692,7 +741,7 @@ var runWebAppServer = function () {
res.writeHead(200, headers);
res.write(".meteor-css-not-found-error { width: 0px;}");
res.end();
return undefined;
return;
}
if (request.url.query && request.url.query['meteor_js_resource']) {
@@ -703,7 +752,7 @@ var runWebAppServer = function () {
headers['Cache-Control'] = 'no-cache';
res.writeHead(404, headers);
res.end("404 Not Found");
return undefined;
return;
}
if (request.url.query && request.url.query['meteor_dont_serve_index']) {
@@ -714,11 +763,11 @@ var runWebAppServer = function () {
headers['Cache-Control'] = 'no-cache';
res.writeHead(404, headers);
res.end("404 Not Found");
return undefined;
return;
}
// /packages/asdfsad ... /__cordova/dafsdf.js
var pathname = parseurl(req).pathname;
var pathname = parseRequest(req).pathname;
var archKey = pathname.split('/')[1];
var archKeyCleaned = 'web.' + archKey.replace(/^__/, '');
@@ -728,22 +777,20 @@ var runWebAppServer = function () {
archKey = archKeyCleaned;
}
var boilerplate;
try {
boilerplate = getBoilerplate(request, archKey);
} catch (e) {
Log.error("Error running template: " + e.stack);
return getBoilerplateAsync(
request,
archKey
).then(boilerplate => {
var statusCode = res.statusCode ? res.statusCode : 200;
res.writeHead(statusCode, headers);
res.write(boilerplate);
res.end();
}, error => {
Log.error("Error running template: " + error.stack);
res.writeHead(500, headers);
res.end();
return undefined;
}
var statusCode = res.statusCode ? res.statusCode : 200;
res.writeHead(statusCode, headers);
res.write(boilerplate);
res.end();
return undefined;
}).run();
});
});
});
// Return 404 by default, if no other handlers serve this URL.
@@ -753,7 +800,7 @@ var runWebAppServer = function () {
});
var httpServer = http.createServer(app);
var httpServer = createServer(app);
var onListeningCallbacks = [];
// After 5 seconds w/o data on a socket, kill it. On the other hand, if
@@ -809,7 +856,7 @@ 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.
main = function (argv) {
exports.main = function (argv) {
WebAppInternals.generateBoilerplate();
// only start listening after all the startup code has run.
@@ -817,8 +864,9 @@ var runWebAppServer = function () {
var host = process.env.BIND_IP;
var localIp = host || '0.0.0.0';
httpServer.listen(localPort, localIp, Meteor.bindEnvironment(function() {
if (process.env.METEOR_PRINT_ON_LISTEN)
if (process.env.METEOR_PRINT_ON_LISTEN) {
console.log("LISTENING"); // must match run-app.js
}
var callbacks = onListeningCallbacks;
onListeningCallbacks = null;
@@ -831,7 +879,7 @@ var runWebAppServer = function () {
return 'DAEMON';
};
};
}
runWebAppServer();

View File

@@ -78,7 +78,7 @@ Tinytest.add("webapp - additional static javascript", function (test) {
// before settng it back to what it was originally.
WebAppInternals.setInlineScriptsAllowed(true);
Meteor._noYieldsAllowed(function () {
(function () {
var boilerplate = WebAppInternals.getBoilerplate({
browser: "doesn't-matter",
url: "also-doesnt-matter"
@@ -100,14 +100,14 @@ Tinytest.add("webapp - additional static javascript", function (test) {
nextCalled = true;
});
test.isTrue(nextCalled);
});
})();
// When inline scripts are disallowed, the script body should not be
// inlined, and the script should be included in a <script src="..">
// tag.
WebAppInternals.setInlineScriptsAllowed(false);
Meteor._noYieldsAllowed(function () {
(function () {
var boilerplate = WebAppInternals.getBoilerplate({
browser: "doesn't-matter",
url: "also-doesnt-matter"
@@ -129,7 +129,7 @@ Tinytest.add("webapp - additional static javascript", function (test) {
var resBody = res.getBody();
test.isTrue(resBody.indexOf(additionalScript) !== -1);
test.equal(res.statusCode, 200);
});
})();
WebAppInternals.setInlineScriptsAllowed(origInlineScriptsAllowed);
});
@@ -156,6 +156,44 @@ Tinytest.add("webapp - generating boilerplate should not change runtime config",
test.isFalse(__meteor_runtime_config__.WEBAPP_TEST_KEY);
});
Tinytest.add("webapp - WebAppInternals.registerBoilerplateDataCallback", function (test) {
const key = "from webapp_tests.js";
let callCount = 0;
function callback(request, data, arch) {
test.equal(arch, "web.browser");
test.equal(request.url, "http://example.com");
test.equal(data.dynamicHead, "so dynamic");
test.equal(data.body, "");
data.body = "<div>oyez</div>";
++callCount;
}
WebAppInternals.registerBoilerplateDataCallback(key, callback);
test.equal(callCount, 0);
const req = new http.IncomingMessage();
req.url = "http://example.com";
req.browser = { name: "headless" };
req.dynamicHead = "so dynamic";
const html = WebAppInternals.getBoilerplate(req, "web.browser");
test.equal(callCount, 1);
test.isTrue(html.indexOf([
"<body>",
"<div>oyez</div>"
].join("\n")) >= 0);
test.equal(
// Make sure this callback doesn't get called again after this test.
WebAppInternals.registerBoilerplateDataCallback(key, null),
callback
);
});
// Support 'named pipes' (strings) as ports for support of Windows Server / Azure deployments
Tinytest.add("webapp - port should be parsed as int unless it is a named pipe", function(test){
// Named pipes on Windows Server follow the format: \\.\pipe\{randomstring} or \\{servername}\pipe\{randomstring}

View File

@@ -1,6 +1,6 @@
{
"track": "METEOR",
"version": "1.5-rc.13",
"version": "1.5.1-rc.5",
"recommended": false,
"official": false,
"description": "Meteor"

View File

@@ -1,6 +1,6 @@
{
"track": "METEOR",
"version": "1.5",
"version": "1.5.1",
"recommended": false,
"official": true,
"description": "The Official Meteor Distribution"

View File

@@ -6,7 +6,7 @@ set -u
UNAME=$(uname)
ARCH=$(uname -m)
MONGO_VERSION=3.2.12
NODE_VERSION=4.8.3
NODE_VERSION=4.8.4
NPM_VERSION=4.6.1
if [ "$UNAME" == "Linux" ] ; then

View File

@@ -10,7 +10,7 @@
# A regex or list of additional regexes to skip.
# Export this one so it's available in the node environment.
export TIMEOUT_SCALE_FACTOR=${TIMEOUT_SCALE_FACTOR:-15}
export TIMEOUT_SCALE_FACTOR=${TIMEOUT_SCALE_FACTOR:-4}
# Skip these tests always. Add other tests with ADDL_SELF_TEST_EXCLUDE.
SELF_TEST_EXCLUDE="^old cli tests|^minifiers can't register non-js|^minifiers: apps can't use|^compiler plugins - addAssets"

View File

@@ -14,8 +14,8 @@ var packageJson = {
npm: "4.6.1",
"node-gyp": "3.6.0",
"node-pre-gyp": "0.6.34",
"meteor-babel": "0.21.4",
reify: "0.11.22",
"meteor-babel": "0.22.0",
reify: "0.11.24",
"meteor-promise": "0.8.4",
fibers: "1.0.15",
promise: "7.1.1",

View File

@@ -660,8 +660,8 @@ main.registerCommand({
// We do mind if there are non-hidden directories, because we don't want
// to recursively check everything to do some crazy heuristic to see if
// we should try to creat an app.
var stats = files.stat(filePath);
// we should try to create an app.
var stats = files.stat(files.pathJoin(appPath, filePath));
if (stats.isDirectory()) {
// Could contain code
return true;

File diff suppressed because it is too large Load Diff

View File

@@ -17,6 +17,15 @@ if (process.platform === "win32") {
WATCHER_ENABLED = false;
}
// Default to prioritizing changed files, but disable that behavior (and
// thus prioritize all files equally) if METEOR_WATCH_PRIORITIZE_CHANGED
// is explicitly set to a string that parses to a falsy value.
var PRIORITIZE_CHANGED = true;
if (process.env.METEOR_WATCH_PRIORITIZE_CHANGED &&
! JSON.parse(process.env.METEOR_WATCH_PRIORITIZE_CHANGED)) {
PRIORITIZE_CHANGED = false;
}
var DEFAULT_POLLING_INTERVAL =
~~process.env.METEOR_WATCH_POLLING_INTERVAL_MS || 5000;
@@ -27,16 +36,31 @@ var NO_WATCHER_POLLING_INTERVAL =
// file watchers, but it's to our advantage if they survive restarts.
const WATCHER_CLEANUP_DELAY_MS = 30000;
const watchers = Object.create(null);
const entries = Object.create(null);
// Pathwatcher complains (using console.error, ugh) if you try to watch
// two files with the same stat.ino number but different paths, so we have
// to deduplicate files by ino.
const watchersByIno = new Map;
const entriesByIno = new Map;
// Set of paths for which a change event has been fired, watched with
// watchLibrary.watch if available. This could be an LRU cache, but in
// practice it should never grow large enough for that to matter.
const changedPaths = new Set;
function hasPriority(absPath) {
// If we're not prioritizing changed files, then all files have
// priority, which means they should be watched with native file
// watchers if the platform supports them. If we are prioritizing
// changed files, then only changed files get priority.
return PRIORITIZE_CHANGED
? changedPaths.has(absPath)
: true;
}
function acquireWatcher(absPath, callback) {
const entry = watchers[absPath] || (
watchers[absPath] = startNewWatcher(absPath));
const entry = entries[absPath] || (
entries[absPath] = startNewWatcher(absPath));
// Watches successfully established in the past may have become invalid
// because the watched file was deleted or renamed, so we need to make
@@ -53,8 +77,8 @@ function acquireWatcher(absPath, callback) {
function startNewWatcher(absPath) {
const stat = statOrNull(absPath);
const ino = stat && stat.ino;
if (ino > 0 && watchersByIno.has(ino)) {
return watchersByIno.get(ino);
if (ino > 0 && entriesByIno.has(ino)) {
return entriesByIno.get(ino);
}
function safeUnwatch() {
@@ -62,7 +86,7 @@ function startNewWatcher(absPath) {
watcher.close();
watcher = null;
if (ino > 0) {
watchersByIno.delete(ino);
entriesByIno.delete(ino);
}
}
}
@@ -71,7 +95,22 @@ function startNewWatcher(absPath) {
const callbacks = new Set;
let watcherCleanupTimer = null;
let watcher;
let pollingInterval;
function getPollingInterval() {
if (watcher) {
return DEFAULT_POLLING_INTERVAL;
}
if (hasPriority(absPath)) {
return NO_WATCHER_POLLING_INTERVAL;
}
if (WATCHER_ENABLED) {
return DEFAULT_POLLING_INTERVAL;
}
return NO_WATCHER_POLLING_INTERVAL;
}
function fire(event) {
if (event !== "change") {
@@ -86,6 +125,10 @@ function startNewWatcher(absPath) {
// "delete" or "rename" event, since it is now our only reliable
// source of file change notifications.
lastWatcherEventTime = 0;
} else {
changedPaths.add(absPath);
rewatch();
}
callbacks.forEach(cb => cb.call(this, event));
@@ -104,17 +147,16 @@ function startNewWatcher(absPath) {
}
function rewatch() {
if (watcher) {
// Already watching; nothing to do.
return;
if (hasPriority(absPath)) {
if (watcher) {
// Already watching; nothing to do.
return;
}
watcher = watchLibraryWatch(absPath, watchWrapper);
} else if (watcher) {
safeUnwatch();
}
watcher = watchLibraryWatch(absPath, watchWrapper);
pollingInterval = watcher
? DEFAULT_POLLING_INTERVAL
: NO_WATCHER_POLLING_INTERVAL;
// This is a no-op if we're not watching the file.
unwatchFile(absPath, watchFileWrapper);
@@ -125,7 +167,7 @@ function startNewWatcher(absPath) {
// CPU cycles.
watchFile(absPath, {
persistent: false,
interval: pollingInterval,
interval: getPollingInterval(),
}, watchFileWrapper);
}
@@ -142,19 +184,17 @@ function startNewWatcher(absPath) {
// If a watcher event fired in the last polling interval, ignore
// this event.
if (new Date - lastWatcherEventTime > pollingInterval) {
if (new Date - lastWatcherEventTime > getPollingInterval()) {
fire.call(this, "change");
}
}
rewatch();
const entry = {
callbacks,
rewatch,
release(callback) {
if (! watchers[absPath]) {
if (! entries[absPath]) {
return;
}
@@ -177,8 +217,8 @@ function startNewWatcher(absPath) {
},
close() {
if (watchers[absPath] !== entry) return;
watchers[absPath] = null;
if (entries[absPath] !== entry) return;
entries[absPath] = null;
if (watcherCleanupTimer) {
clearTimeout(watcherCleanupTimer);
@@ -192,15 +232,15 @@ function startNewWatcher(absPath) {
};
if (ino > 0) {
watchersByIno.set(ino, entry);
entriesByIno.set(ino, entry);
}
return entry;
}
export function closeAllWatchers() {
Object.keys(watchers).forEach(absPath => {
const entry = watchers[absPath];
Object.keys(entries).forEach(absPath => {
const entry = entries[absPath];
if (entry) {
entry.close();
}

View File

@@ -6,21 +6,21 @@
meteor-base@1.1.0 # Packages every Meteor app needs to have
mobile-experience@1.0.4 # Packages for a great mobile UX
mongo@1.1.18 # The database Meteor supports right now
mongo@1.1.19 # The database Meteor supports right now
blaze-html-templates@1.0.4 # Compile .html files into Meteor Blaze views
reactive-var@1.0.11 # Reactive variable for tracker
jquery@1.11.10 # Helpful client-side library
tracker@1.1.3 # Meteor's client-side reactive programming library
standard-minifier-css@1.3.4 # CSS minifier run for production mode
standard-minifier-js@2.1.0 # JS minifier run for production mode
standard-minifier-js@2.1.1 # JS minifier run for production mode
es5-shim@4.6.15 # ECMAScript 5 compatibility for older browsers.
ecmascript@0.8.0 # Enable ECMAScript2015+ syntax in app code
shell-server@0.2.3 # Server-side component of the `meteor shell` command
ecmascript@0.8.2 # Enable ECMAScript2015+ syntax in app code
shell-server@0.2.4 # Server-side component of the `meteor shell` command
autopublish@1.0.7 # Publish all data to the clients (for prototyping)
insecure@1.0.7 # Allow all DB writes from clients (for prototyping)
dynamic-import
dynamic-import@0.1.1
dispatch:mocha-phantomjs
dispatch:mocha-browser
lazy-test-package

View File

@@ -1 +1 @@
METEOR@1.5
METEOR@1.5.1

View File

@@ -6,22 +6,22 @@
meteor-base@1.1.0 # Packages every Meteor app needs to have
mobile-experience@1.0.4 # Packages for a great mobile UX
mongo@1.1.18 # The database Meteor supports right now
mongo@1.1.19 # The database Meteor supports right now
blaze-html-templates # Compile .html files into Meteor Blaze views
session@1.1.7 # Client-side reactive dictionary for your app
jquery@1.11.10 # Helpful client-side library
tracker@1.1.3 # Meteor's client-side reactive programming library
es5-shim@4.6.15 # ECMAScript 5 compatibility for older browsers.
ecmascript@0.8.0 # Enable ECMAScript2015+ syntax in app code
ecmascript@0.8.2 # Enable ECMAScript2015+ syntax in app code
coffeescript
modules-test-package
dispatch:mocha-phantomjs
dispatch:mocha-browser
standard-minifier-css@1.3.4
standard-minifier-js@2.1.0
standard-minifier-js@2.1.1
client-only-ecmascript
modules-test-plugin
shell-server@0.2.3
dynamic-import
shell-server@0.2.4
dynamic-import@0.1.1

View File

@@ -1 +1 @@
METEOR@1.5
METEOR@1.5.1

View File

@@ -22,6 +22,7 @@ function startRun(sandbox) {
// Tests the actual cache logic used by coffeescript.
selftest.define("compiler plugin caching - coffee", () => {
var s = new Sandbox({ fakeMongo: true });
s.set("METEOR_WATCH_PRIORITIZE_CHANGED", "false");
s.createApp("myapp", "caching-coffee");
s.cd("myapp");
@@ -91,6 +92,7 @@ selftest.define("compiler plugin caching - coffee", () => {
selftest.define("compiler plugin caching - " + packageName, () => {
var s = new Sandbox({ fakeMongo: true });
s.set("METEOR_WATCH_PRIORITIZE_CHANGED", "false");
s.createApp("myapp", "caching-" + packageName);
s.cd("myapp");
@@ -278,6 +280,7 @@ selftest.define("compiler plugin caching - local plugin", function () {
// Test error on duplicate compiler plugins.
selftest.define("compiler plugins - duplicate extension", () => {
const s = new Sandbox({ fakeMongo: true });
s.set("METEOR_WATCH_PRIORITIZE_CHANGED", "false");
s.createApp("myapp", "duplicate-compiler-extensions");
s.cd("myapp");

View File

@@ -135,6 +135,8 @@ selftest.define("change cordova plugins", ["cordova"], function () {
var s = new Sandbox();
var run;
s.set("METEOR_WATCH_PRIORITIZE_CHANGED", "false");
// Starting a run
s.createApp("myapp", "package-tests");
s.cd("myapp");

View File

@@ -7,6 +7,8 @@ selftest.define("css hot code push", function (options) {
clients: options.clients
});
s.set("METEOR_WATCH_PRIORITIZE_CHANGED", "false");
s.createApp("myapp", "css-injection-test");
s.cd("myapp");
s.testWithAllClients(function (run) {

View File

@@ -100,6 +100,8 @@ selftest.define("change packages during hot code push", [], function () {
var s = new Sandbox();
var run;
s.set("METEOR_WATCH_PRIORITIZE_CHANGED", "false");
// Starting a run
s.createApp("myapp", "package-tests");
s.cd("myapp");

View File

@@ -22,6 +22,8 @@ selftest.define("run", function () {
var s = new Sandbox({ fakeMongo: true });
var run;
s.set("METEOR_WATCH_PRIORITIZE_CHANGED", "false");
// Starting a run
s.createApp("myapp", "standard-app");
s.cd("myapp");
@@ -239,6 +241,8 @@ selftest.define("update during run", ["checkout", 'custom-warehouse'], function
});
var run;
s.set("METEOR_WATCH_PRIORITIZE_CHANGED", "false");
s.createApp("myapp", "packageless", { release: DEFAULT_RELEASE_TRACK + '@v1' });
s.cd("myapp");
@@ -249,6 +253,7 @@ selftest.define("update during run", ["checkout", 'custom-warehouse'], function
run.match('localhost:3000');
s.write('.meteor/release', DEFAULT_RELEASE_TRACK + '@v2');
run.matchErr('to Meteor v2 from Meteor v1');
run.waitSecs(10);
run.expectExit(254);
// But not if the release was forced (case 1)
@@ -261,6 +266,7 @@ selftest.define("update during run", ["checkout", 'custom-warehouse'], function
s.write('empty.js', '');
run.waitSecs(2);
run.match('restarted');
run.waitSecs(10);
run.stop();
run.forbidAll("updated");
@@ -274,6 +280,7 @@ selftest.define("update during run", ["checkout", 'custom-warehouse'], function
s.write('empty.js', '');
run.waitSecs(2);
run.match('restarted');
run.waitSecs(10);
run.stop();
run.forbidAll("updated");
@@ -287,10 +294,12 @@ selftest.define("update during run", ["checkout", 'custom-warehouse'], function
run.tellMongo(MONGO_LISTENING);
run.waitSecs(2);
run.match('localhost:3000');
run.waitSecs(10);
s.write('.meteor/release', DEFAULT_RELEASE_TRACK + '@v2');
s.write('empty.js', '');
run.waitSecs(2);
run.match('restarted');
run.waitSecs(10);
run.stop();
run.forbidAll("updated");
});

View File

@@ -1045,7 +1045,13 @@ _.extend(PhantomClient.prototype, {
files.convertToOSPath(scriptPath), self.url],
{}, function (error, stdout, stderr) {
if (self._logError && error) {
console.log("PhantomJS exited with error ", error, "\nstdout:\n", stdout, "\nstderr:\n", stderr);
console.log(
"PhantomJS exited with error ", error,
"\nstdout:\n", stdout,
"\nstderr:\n", stderr
);
} else if (stderr) {
console.log("PhantomJS stderr:\n", stderr);
}
});
},

View File

@@ -1,12 +1,10 @@
// For this global function to be defined, the --expose-gc flag must have
// been passed to node at the bottom of the ../../meteor script, probably
// via the TOOL_NODE_FLAGS environment variable.
const gc = global.gc;
import { throttle } from "underscore";
// In the future, this function may become smarter about how often it
// actually calls the gc function, but that's an implementation detail.
export function requestGarbageCollection() {
if (typeof gc === "function") {
gc();
}
}
export const requestGarbageCollection =
// For this global function to be defined, the --expose-gc flag must
// have been passed to node at the bottom of the ../../meteor script,
// probably via the TOOL_NODE_FLAGS environment variable.
typeof global.gc === "function"
// Restrict actual garbage collections to once per 500ms.
? throttle(global.gc, 500)
: function () {};