mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Merge branch 'release-1.10.3' into cache-build-before-deploy
This commit is contained in:
40
History.md
40
History.md
@@ -1,3 +1,43 @@
|
||||
## vNEXT, unreleased
|
||||
|
||||
### Breaking changes
|
||||
|
||||
N/A
|
||||
|
||||
### Migration steps
|
||||
|
||||
N/A
|
||||
|
||||
### Changes
|
||||
|
||||
* `email` package now exposes `hookSend` that runs before emails are sent.
|
||||
|
||||
## v1.10.3, TBD
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `email` package dependencies have been update and package version has been bumped to 2.0.0
|
||||
There is a potential breaking change as the underlying package started to use `dns.resolve()`
|
||||
instead of `dns.lookup()` which might be breaking on some environments.
|
||||
See [nodemailer changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md) for more information.
|
||||
|
||||
### Migration steps
|
||||
|
||||
N/A
|
||||
|
||||
### Changes
|
||||
|
||||
* Fixes error when removing cordova plugin that depends on cli variables. PR [#10976](https://github.com/meteor/meteor/pull/11052)
|
||||
|
||||
* `email` package now exposes `hookSend` that runs before emails are send.
|
||||
|
||||
* The version of MongoDB used by Meteor in development has been updated
|
||||
from 4.2.5 to 4.2.8
|
||||
|
||||
* Node.js has been updated to version
|
||||
[12.18.2](https://nodejs.org/en/blog/release/v12.18.2/)
|
||||
|
||||
* Updated npm to version 6.14.5
|
||||
|
||||
## v1.10.2, 2020-04-21
|
||||
|
||||
|
||||
2
meteor
2
meteor
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
BUNDLE_VERSION=12.16.1.8
|
||||
BUNDLE_VERSION=12.18.2.1
|
||||
|
||||
# OS Check. Put here because here is where we download the precompiled
|
||||
# bundles that are arch specific.
|
||||
|
||||
@@ -71,9 +71,9 @@ export class AccountsServer extends AccountsCommon {
|
||||
resetPassword: token => Meteor.absoluteUrl(`#/reset-password/${token}`),
|
||||
verifyEmail: token => Meteor.absoluteUrl(`#/verify-email/${token}`),
|
||||
enrollAccount: token => Meteor.absoluteUrl(`#/enroll-account/${token}`),
|
||||
}
|
||||
};
|
||||
|
||||
this.addDefaultRateLimit()
|
||||
this.addDefaultRateLimit();
|
||||
}
|
||||
|
||||
///
|
||||
@@ -117,6 +117,19 @@ export class AccountsServer extends AccountsCommon {
|
||||
this._validateNewUserHooks.push(func);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Validate login from external service
|
||||
* @locus Server
|
||||
* @param {Function} func Called whenever login/user creation from external service is attempted. Login or user creation based on this login can be aborted by passing a falsy value or throwing an exception.
|
||||
*/
|
||||
beforeExternalLogin(func) {
|
||||
if (this._beforeExternalLoginHook) {
|
||||
throw new Error("Can only call beforeExternalLogin once");
|
||||
}
|
||||
|
||||
this._beforeExternalLoginHook = func;
|
||||
}
|
||||
|
||||
///
|
||||
/// CREATE USER HOOKS
|
||||
///
|
||||
@@ -1211,6 +1224,11 @@ export class AccountsServer extends AccountsCommon {
|
||||
|
||||
let user = this.users.findOne(selector, {fields: this._options.defaultFieldSelector});
|
||||
|
||||
// Before continuing, run user hook to see if we should continue
|
||||
if (this._beforeExternalLoginHook && !this._beforeExternalLoginHook(serviceName, serviceData, user)) {
|
||||
throw new Meteor.Error(403, "Login forbidden");
|
||||
}
|
||||
|
||||
// When creating a new user we pass through all options. When updating an
|
||||
// existing user, by default we only process/pass through the serviceData
|
||||
// (eg, so that we keep an unexpired access token and don't cache old email
|
||||
|
||||
@@ -623,3 +623,44 @@ Tinytest.add(
|
||||
Accounts._options = accountsOptions;
|
||||
}
|
||||
);
|
||||
|
||||
Tinytest.add(
|
||||
'accounts - verify beforeExternalLogin hook can stop user login',
|
||||
test => {
|
||||
// Verify user data is saved properly when not using the
|
||||
// beforeExternalLogin hook.
|
||||
let facebookId = Random.id();
|
||||
const uid1 = Accounts.updateOrCreateUserFromExternalService(
|
||||
'facebook',
|
||||
{ id: facebookId },
|
||||
{ profile: { foo: 1 } },
|
||||
).userId;
|
||||
const ignoreFieldName = "bigArray";
|
||||
const c = Meteor.users.update(uid1, {$set: {[ignoreFieldName]: [1]}});
|
||||
let users =
|
||||
Meteor.users.find({ 'services.facebook.id': facebookId }).fetch();
|
||||
test.length(users, 1);
|
||||
test.equal(users[0].profile.foo, 1);
|
||||
test.isNotUndefined(users[0][ignoreFieldName], 'ignoreField - before limit fields');
|
||||
|
||||
// Verify that when beforeExternalLogin returns false
|
||||
// that an error throws and user is not saved
|
||||
Accounts.beforeExternalLogin((serviceName, serviceData, user) => {
|
||||
// Check that we get the correct data
|
||||
test.equal(serviceName, 'facebook');
|
||||
test.equal(serviceData, { id: facebookId });
|
||||
test.equal(user._id, uid1);
|
||||
return false
|
||||
});
|
||||
|
||||
test.throws(() => Accounts.updateOrCreateUserFromExternalService(
|
||||
'facebook',
|
||||
{ id: facebookId },
|
||||
{ profile: { foo: 1 } },
|
||||
));
|
||||
|
||||
// Cleanup
|
||||
Meteor.users.remove(uid1);
|
||||
Accounts._beforeExternalLoginHook = null;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
summary: "A user account system",
|
||||
version: "1.6.0",
|
||||
version: "1.7.0-beta1103.6",
|
||||
});
|
||||
|
||||
Package.onUse(api => {
|
||||
|
||||
@@ -20,7 +20,7 @@ Accounts.emailTemplates.headers = {
|
||||
'My-Custom-Header' : 'Cool'
|
||||
};
|
||||
|
||||
EmailTest.hookSend(options => {
|
||||
Email.hookSend(options => {
|
||||
const { to } = options;
|
||||
if (!to || !to.toUpperCase().includes('INTERCEPT')) {
|
||||
return true; // go ahead and send
|
||||
|
||||
@@ -5,7 +5,7 @@ Package.describe({
|
||||
// 2.2.x in the future. The version was also 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.
|
||||
version: "1.6.1"
|
||||
version: "1.6.2-beta1103.6"
|
||||
});
|
||||
|
||||
Package.onUse(api => {
|
||||
|
||||
@@ -20,7 +20,7 @@ const reportError = (error, callback) => {
|
||||
/**
|
||||
* @summary Log the user in with a password.
|
||||
* @locus Client
|
||||
* @param {Object | String} user
|
||||
* @param {Object | String} selector
|
||||
* Either a string interpreted as a username or an email; or an object with a
|
||||
* single key: `email`, `username` or `id`. Username or email match in a case
|
||||
* insensitive manner.
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"lolex": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/lolex/-/lolex-2.3.1.tgz",
|
||||
"integrity": "sha512-mQuW55GhduF3ppo+ZRUTz1PRjEh1hS5BbqU7d8D0ez2OKxHDod7StPPeAVKisZR5aLkHZjdGWSL42LSONUJsZw=="
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/lolex/-/lolex-2.3.2.tgz",
|
||||
"integrity": "sha512-A5pN2tkFj7H0dGIAM6MFvHKMJcPnjZsOMvR7ujCjfgW5TbV6H9vb1PgxLtHvjqNZTHsUolz+6/WEO0N1xNx2ng=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
name: 'ddp-rate-limiter',
|
||||
version: '1.0.7',
|
||||
version: '1.0.9-beta1103.6',
|
||||
// Brief, one-line summary of the package.
|
||||
summary: 'The DDPRateLimiter allows users to add rate limits to DDP' +
|
||||
' methods and subscriptions.',
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"core-js": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.2.1.tgz",
|
||||
"integrity": "sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw=="
|
||||
"version": "3.6.5",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz",
|
||||
"integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
Package.describe({
|
||||
name: "ecmascript-runtime-client",
|
||||
version: "0.10.0",
|
||||
version: "0.11.0-beta1103.6",
|
||||
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"
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
"core-js": "3.2.1"
|
||||
"core-js": "3.6.5"
|
||||
});
|
||||
|
||||
Package.onUse(function(api) {
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"core-js": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.2.1.tgz",
|
||||
"integrity": "sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw=="
|
||||
"version": "3.6.5",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz",
|
||||
"integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
Package.describe({
|
||||
name: "ecmascript-runtime-server",
|
||||
version: "0.9.0",
|
||||
version: "0.10.0-beta1103.6",
|
||||
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"
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
"core-js": "3.2.1"
|
||||
"core-js": "3.6.5"
|
||||
});
|
||||
|
||||
Package.onUse(function(api) {
|
||||
|
||||
14
packages/email/.npm/package/npm-shrinkwrap.json
generated
14
packages/email/.npm/package/npm-shrinkwrap.json
generated
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"node4mailer": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/node4mailer/-/node4mailer-4.0.3.tgz",
|
||||
"integrity": "sha1-jwx6ZzdSehKFMBhaFMLoeZiaiRA="
|
||||
"nodemailer": {
|
||||
"version": "6.4.6",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.6.tgz",
|
||||
"integrity": "sha512-/kJ+FYVEm2HuUlw87hjSqTss+GU35D4giOpdSfGp7DO+5h6RlJj7R94YaYHOkoxu1CSaM0d3WRBtCzwXrY6MKA=="
|
||||
},
|
||||
"stream-buffers": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-0.2.5.tgz",
|
||||
"integrity": "sha1-+TBTnTzwjXSKNArWE5+Vss60jwU="
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-3.0.2.tgz",
|
||||
"integrity": "sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
var Future = Npm.require('fibers/future');
|
||||
var urlModule = Npm.require('url');
|
||||
var nodemailer = Npm.require('node4mailer');
|
||||
var nodemailer = Npm.require('nodemailer');
|
||||
|
||||
Email = {};
|
||||
EmailTest = {};
|
||||
@@ -8,12 +8,12 @@ EmailTest = {};
|
||||
EmailInternals = {
|
||||
NpmModules: {
|
||||
mailcomposer: {
|
||||
version: Npm.require('node4mailer/package.json').version,
|
||||
module: Npm.require('node4mailer/lib/mail-composer')
|
||||
version: Npm.require('nodemailer/package.json').version,
|
||||
module: Npm.require('nodemailer/lib/mail-composer')
|
||||
},
|
||||
nodemailer: {
|
||||
version: Npm.require('node4mailer/package.json').version,
|
||||
module: Npm.require('node4mailer')
|
||||
version: Npm.require('nodemailer/package.json').version,
|
||||
module: Npm.require('nodemailer')
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -98,15 +98,15 @@ var smtpSend = function (transport, mail) {
|
||||
transport._syncSendMail(mail);
|
||||
};
|
||||
|
||||
var sendHooks = [];
|
||||
/**
|
||||
* Mock out email sending (eg, during a test.) This is private for now.
|
||||
* Hook that runs before email is sent.
|
||||
*
|
||||
* f receives the arguments to Email.send and should return true to go
|
||||
* @param f {function} receives the arguments to Email.send and should return true to go
|
||||
* ahead and send the email (or at least, try subsequent hooks), or
|
||||
* false to skip sending.
|
||||
*/
|
||||
var sendHooks = [];
|
||||
EmailTest.hookSend = function (f) {
|
||||
Email.hookSend = function (f) {
|
||||
sendHooks.push(f);
|
||||
};
|
||||
|
||||
@@ -118,9 +118,9 @@ EmailTest.hookSend = function (f) {
|
||||
* If the `MAIL_URL` environment variable is set, actually sends the email.
|
||||
* Otherwise, prints the contents of the email to standard out.
|
||||
*
|
||||
* Note that this package is based on **mailcomposer 4**, so make sure to refer to
|
||||
* [the documentation](https://github.com/nodemailer/mailcomposer/blob/v4.0.1/README.md)
|
||||
* for that version when using the `attachments` or `mailComposer` options.
|
||||
* Note that this package is based on **nodemailer**, so make sure to refer to
|
||||
* [the documentation](http://nodemailer.com/)
|
||||
* when using the `attachments` or `mailComposer` options.
|
||||
*
|
||||
* @locus Server
|
||||
* @param {Object} options
|
||||
@@ -136,7 +136,7 @@ EmailTest.hookSend = function (f) {
|
||||
* @param {String} [options.icalEvent] iCalendar event attachment
|
||||
* @param {Object} [options.headers] Dictionary of custom headers - e.g. `{ "header name": "header value" }`. To set an object under a header name, use `JSON.stringify` - e.g. `{ "header name": JSON.stringify({ tracking: { level: 'full' } }) }`.
|
||||
* @param {Object[]} [options.attachments] Array of attachment objects, as
|
||||
* described in the [mailcomposer documentation](https://github.com/nodemailer/mailcomposer/blob/v4.0.1/README.md#attachments).
|
||||
* described in the [nodemailer documentation](https://nodemailer.com/message/attachments/).
|
||||
* @param {MailComposer} [options.mailComposer] A [MailComposer](https://nodemailer.com/extras/mailcomposer/#e-mail-message-fields)
|
||||
* object representing the message to be sent. Overrides all other options.
|
||||
* You can create a `MailComposer` object via
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Package.describe({
|
||||
summary: "Send email messages",
|
||||
version: "1.2.3"
|
||||
version: "2.0.0-beta1103.6"
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
node4mailer: "4.0.3",
|
||||
"stream-buffers": "0.2.5"
|
||||
nodemailer: "6.4.6",
|
||||
"stream-buffers": "3.0.2"
|
||||
});
|
||||
|
||||
Package.onUse(function (api) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
summary: "The Meteor command-line tool",
|
||||
version: '1.10.2'
|
||||
version: '1.10.3-beta.6'
|
||||
});
|
||||
|
||||
Package.includeTool();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"track": "METEOR",
|
||||
"version": "1.10.2-rc.0",
|
||||
"version": "1.10.3-beta.6",
|
||||
"recommended": false,
|
||||
"official": false,
|
||||
"description": "Meteor"
|
||||
|
||||
@@ -5,10 +5,10 @@ set -u
|
||||
|
||||
UNAME=$(uname)
|
||||
ARCH=$(uname -m)
|
||||
NODE_VERSION=12.16.1
|
||||
NODE_VERSION=12.18.2
|
||||
MONGO_VERSION_64BIT=4.2.5
|
||||
MONGO_VERSION_32BIT=3.2.22
|
||||
NPM_VERSION=6.14.0
|
||||
NPM_VERSION=6.14.6
|
||||
|
||||
# If we built Node from source on Jenkins, this is the build number.
|
||||
NODE_BUILD_NUMBER=
|
||||
|
||||
@@ -10,7 +10,7 @@ var packageJson = {
|
||||
dependencies: {
|
||||
// Explicit dependency because we are replacing it with a bundled version
|
||||
// and we want to make sure there are no dependencies on a higher version
|
||||
npm: "6.14.0",
|
||||
npm: "6.14.6",
|
||||
pacote: "https://github.com/meteor/pacote/tarball/a81b0324686e85d22c7688c47629d4009000e8b8",
|
||||
"node-gyp": "6.0.1",
|
||||
"node-pre-gyp": "0.14.0",
|
||||
|
||||
@@ -517,6 +517,7 @@ main.registerCommand({
|
||||
minimal: { type: Boolean },
|
||||
full: { type: Boolean },
|
||||
react: { type: Boolean },
|
||||
vue: { type: Boolean },
|
||||
typescript: { type: Boolean },
|
||||
},
|
||||
catalogRefresh: new catalog.Refresh.Never()
|
||||
@@ -770,6 +771,8 @@ main.registerCommand({
|
||||
skelName += "-full";
|
||||
} else if (options.react) {
|
||||
skelName += "-react";
|
||||
} else if (options.vue) {
|
||||
skelName += "-vue";
|
||||
} else if (options.typescript) {
|
||||
skelName += "-typescript";
|
||||
}
|
||||
@@ -886,8 +889,9 @@ main.registerCommand({
|
||||
! options.minimal &&
|
||||
! options.full &&
|
||||
! options.react &&
|
||||
! options.vue &&
|
||||
! options.typescript) {
|
||||
// Notify people about --bare, --minimal, --full, --react, and --typescript.
|
||||
// Notify people about --bare, --minimal, --full, --react, --vue, and --typescript.
|
||||
Console.info([
|
||||
"",
|
||||
"To start with a different app template, try one of the following:",
|
||||
@@ -898,6 +902,7 @@ main.registerCommand({
|
||||
cmd("meteor create --minimal # to create an app with as few Meteor packages as possible");
|
||||
cmd("meteor create --full # to create a more complete scaffolded app");
|
||||
cmd("meteor create --react # to create a basic React-based app");
|
||||
cmd("meteor create --vue # to create a basic Vue-based app");
|
||||
cmd("meteor create --typescript # to create an app using TypeScript and React");
|
||||
}
|
||||
|
||||
|
||||
52
tools/cordova/project.js
vendored
52
tools/cordova/project.js
vendored
@@ -74,6 +74,34 @@ const pinnedPluginVersions = {
|
||||
"cordova-plugin-wkwebview-engine": "1.1.3"
|
||||
}
|
||||
|
||||
/**
|
||||
* To fix Cordova error: Variable(s) missing we convert the cli_variables
|
||||
* when removing plugins we want to convert for each plugin, for instance,
|
||||
* cordova-plugin-facebook4:
|
||||
* commandOptions {
|
||||
* ...
|
||||
* cli_variables: {
|
||||
* 'cordova-plugin-googleplus': {
|
||||
* REVERSED_CLIENT_ID: 'com.googleusercontent.apps.11111111-xxkodsuusaiusixuaix'
|
||||
* },
|
||||
* 'cordova-plugin-facebook4': { APP_ID: '1111111111111111', APP_NAME: 'appname' }
|
||||
* }
|
||||
* }
|
||||
* into this
|
||||
* commandOptions {
|
||||
* ...
|
||||
* cli_variables: { APP_ID: '1111111111111111', APP_NAME: 'appname' }
|
||||
* }
|
||||
*
|
||||
* @param plugin
|
||||
* @param commandOptions
|
||||
*/
|
||||
const getCommandOptionsForPlugin = (plugin, commandOptions = {}) => {
|
||||
const cli_variables = commandOptions && commandOptions.cli_variables
|
||||
&& commandOptions.cli_variables[plugin] || {};
|
||||
return {...commandOptions, cli_variables};
|
||||
}
|
||||
|
||||
export class CordovaProject {
|
||||
constructor(projectContext, options = {}) {
|
||||
|
||||
@@ -521,7 +549,7 @@ from Cordova project`, async () => {
|
||||
buildmessage.assertInJob();
|
||||
|
||||
if (utils.isUrlWithSha(version)) {
|
||||
return convertToGitUrl(version);
|
||||
return `${id}@${convertToGitUrl(version)}`;
|
||||
} else if (utils.isUrlWithFileScheme(version)) {
|
||||
// Strip file:// and resolve the path relative to the cordova-build
|
||||
// directory
|
||||
@@ -560,18 +588,28 @@ from Cordova project`, async () => {
|
||||
{ cli_variables: config, link: utils.isUrlWithFileScheme(version) });
|
||||
|
||||
this.runCommands(`adding plugin ${target} \
|
||||
to Cordova project`, cordova_lib.plugin.bind(undefined, 'add', [target], commandOptions));
|
||||
to Cordova project`, cordova_lib.plugin.bind(undefined, 'add', [target],
|
||||
commandOptions));
|
||||
}
|
||||
}
|
||||
|
||||
// plugins is an array of plugin IDs.
|
||||
removePlugins(plugins) {
|
||||
removePlugins(plugins, config = {}) {
|
||||
if (_.isEmpty(plugins)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.runCommands(`removing plugins ${plugins} \
|
||||
from Cordova project`, cordova_lib.plugin.bind(undefined, 'rm', plugins, this.defaultOptions));
|
||||
const commandOptions = _.extend(this.defaultOptions,
|
||||
{ cli_variables: config });
|
||||
|
||||
plugins.forEach(plugin => {
|
||||
const commandOptionsPlugin = getCommandOptionsForPlugin(plugin,
|
||||
commandOptions);
|
||||
|
||||
this.runCommands(`removing plugin ${plugin} \
|
||||
from Cordova project`, cordova_lib.plugin.bind(undefined, 'rm --force', [plugin],
|
||||
commandOptionsPlugin));
|
||||
});
|
||||
}
|
||||
|
||||
// Ensures that the Cordova plugins are synchronized with the app-level
|
||||
@@ -699,7 +737,7 @@ perform cordova plugins reinstall`);
|
||||
Object.keys(installedPluginVersions));
|
||||
}
|
||||
|
||||
this.removePlugins(pluginsToRemove);
|
||||
this.removePlugins(pluginsToRemove, pluginsConfiguration);
|
||||
|
||||
let pluginVersionsToInstall;
|
||||
|
||||
@@ -735,7 +773,7 @@ perform cordova plugins reinstall`);
|
||||
// cordova-plugin-whitelist@1.3.2 => { 'cordova-plugin-whitelist': '1.3.2' }
|
||||
// com.cordova.plugin@file://.cordova-plugins/plugin => { 'com.cordova.plugin': 'file://.cordova-plugins/plugin' }
|
||||
// @scope/plugin@1.0.0 => { 'com.cordova.plugin': 'scope/plugin' }
|
||||
const installed = this.listInstalledPluginVersions(true);
|
||||
const installed = this.listInstalledPluginVersions();
|
||||
const installedPluginsNames = Object.keys(installed);
|
||||
const installedPluginsVersions = Object.values(installed);
|
||||
const missingPlugins = {};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
///
|
||||
|
||||
import assert from "assert";
|
||||
import fs, { PathLike, Stats } from "fs";
|
||||
import fs, { PathLike, Stats, Dirent } from "fs";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import { spawn, execFile } from "child_process";
|
||||
@@ -1757,6 +1757,14 @@ wrapFsFunc<[string], string[]>("readdir", fs.readdirSync, [0], {
|
||||
},
|
||||
});
|
||||
|
||||
export const readdirWithTypes = wrapFsFunc<[string], Dirent[]>("readdirWithTypes", (dir) => {
|
||||
return fs.readdirSync(dir, {
|
||||
withFileTypes: true
|
||||
});
|
||||
}, [0], {
|
||||
cached: true
|
||||
});
|
||||
|
||||
export const appendFile = wrapDestructiveFsFunc("appendFile", fs.appendFileSync);
|
||||
export const chmod = wrapDestructiveFsFunc("chmod", fs.chmodSync);
|
||||
export const close = wrapFsFunc("close", fs.closeSync, []);
|
||||
|
||||
@@ -35,6 +35,14 @@ var NO_WATCHER_POLLING_INTERVAL =
|
||||
// file watchers, but it's to our advantage if they survive restarts.
|
||||
const WATCHER_CLEANUP_DELAY_MS = 30000;
|
||||
|
||||
// Pathwatcher complains (using console.error, ugh) if you try to watch
|
||||
// two files with the same stat.ino number but different paths on linux, so we have
|
||||
// to deduplicate files by ino.
|
||||
const DEDUPLICATE_BY_INO = process.platform !== "win32";
|
||||
|
||||
const entriesByIno = new Map;
|
||||
|
||||
|
||||
export type SafeWatcher = {
|
||||
close: () => void;
|
||||
}
|
||||
@@ -49,11 +57,6 @@ interface Entry extends SafeWatcher {
|
||||
|
||||
const entries: Record<string, Entry | null> = 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 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.
|
||||
@@ -86,10 +89,19 @@ function acquireWatcher(absPath: string, callback: EntryCallback) {
|
||||
}
|
||||
|
||||
function startNewWatcher(absPath: string): Entry {
|
||||
const stat = statOrNull(absPath);
|
||||
if (stat && stat.ino > 0 && entriesByIno.has(stat.ino)) {
|
||||
const entry = entriesByIno.get(stat.ino);
|
||||
if (entries[absPath] === entry) {
|
||||
let stat: Stats | null = null;
|
||||
|
||||
if (DEDUPLICATE_BY_INO) {
|
||||
stat = statOrNull(absPath);
|
||||
if (stat && stat.ino > 0 && entriesByIno.has(stat.ino)) {
|
||||
const entry = entriesByIno.get(stat.ino);
|
||||
if (entries[absPath] === entry) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let entry = entries[absPath];
|
||||
if (entry) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Stats, FSWatcher } from "fs";
|
||||
import { Stats, FSWatcher, Dirent } from "fs";
|
||||
import * as files from "./files";
|
||||
import * as safeWatcher from "./safe-watcher";
|
||||
import { createHash } from "crypto";
|
||||
@@ -339,7 +339,7 @@ export const sha512 = Profile("sha512", function (...args: (string | Buffer)[])
|
||||
function readAndStatDirectory(absPath: string) {
|
||||
// Read the directory.
|
||||
try {
|
||||
var contents = files.readdir(absPath);
|
||||
var contents = files.readdirWithTypes(absPath);
|
||||
} catch (e) {
|
||||
// If the path is not a directory, return null; let other errors through.
|
||||
if (e && (e.code === 'ENOENT' || e.code === 'ENOTDIR')) {
|
||||
@@ -351,9 +351,14 @@ function readAndStatDirectory(absPath: string) {
|
||||
// Add slashes to the end of directories.
|
||||
const contentsWithSlashes: string[] = [];
|
||||
contents.forEach(entry => {
|
||||
// We do stat instead of lstat here, so that we treat symlinks to
|
||||
// directories just like directories themselves.
|
||||
const stat = optimisticStatOrNull(files.pathJoin(absPath, entry));
|
||||
let stat: Dirent | Stats | null = entry;
|
||||
let name = entry.name;
|
||||
|
||||
if (entry.isSymbolicLink()) {
|
||||
// We do stat instead of lstat here, so that we treat symlinks to
|
||||
// directories just like directories themselves.
|
||||
stat = optimisticStatOrNull(files.pathJoin(absPath, entry.name));
|
||||
}
|
||||
if (! stat) {
|
||||
// Disappeared after the readdir (or a dangling symlink)?
|
||||
// Eh, pretend it was never there in the first place.
|
||||
@@ -361,10 +366,10 @@ function readAndStatDirectory(absPath: string) {
|
||||
}
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
entry += '/';
|
||||
name += '/';
|
||||
}
|
||||
|
||||
contentsWithSlashes.push(entry);
|
||||
contentsWithSlashes.push(name);
|
||||
});
|
||||
|
||||
return contentsWithSlashes;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import assert from "assert";
|
||||
import {WatchSet, readAndWatchFile, sha1} from '../fs/watch';
|
||||
import files, {
|
||||
symlinkWithOverwrite,
|
||||
symlinkWithOverwrite, realpath,
|
||||
} from '../fs/files';
|
||||
import NpmDiscards from './npm-discards.js';
|
||||
import {Profile} from '../tool-env/profile';
|
||||
@@ -540,7 +540,7 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}`
|
||||
// as well as node_modules/meteor and the parent directories of any
|
||||
// scoped npm packages.
|
||||
this._ensureAllNonPackageDirectories(
|
||||
files.realpath(options.from),
|
||||
realpath(options.from),
|
||||
options.to
|
||||
);
|
||||
}
|
||||
@@ -637,7 +637,7 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}`
|
||||
});
|
||||
}
|
||||
|
||||
const rootDir = files.realpath(from);
|
||||
const rootDir = realpath(from);
|
||||
|
||||
const walk = (absFrom, relTo) => {
|
||||
if (symlink && ! (relTo in this.usedAsFile)) {
|
||||
@@ -661,7 +661,7 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}`
|
||||
return;
|
||||
}
|
||||
|
||||
// Returns files.realpath(thisAbsFrom), iff it is external to
|
||||
// Returns files.realpath(thisAbsFrom), if it is external to
|
||||
// rootDir, using caching because this function might be called
|
||||
// more than once.
|
||||
let cachedExternalPath;
|
||||
@@ -671,7 +671,7 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}`
|
||||
}
|
||||
|
||||
try {
|
||||
var real = files.realpath(thisAbsFrom);
|
||||
var real = realpath(thisAbsFrom);
|
||||
} catch (e) {
|
||||
if (e.code !== "ENOENT" &&
|
||||
e.code !== "ELOOP") {
|
||||
|
||||
@@ -173,6 +173,7 @@ import { loadIsopackage } from '../tool-env/isopackets.js';
|
||||
import { CORDOVA_PLATFORM_VERSIONS } from '../cordova';
|
||||
import { gzipSync } from "zlib";
|
||||
import { PackageRegistry } from "../../packages/meteor/define-package.js";
|
||||
import { optimisticLStatOrNull } from '../fs/optimistic';
|
||||
|
||||
const SOURCE_URL_PREFIX = "meteor://\u{1f4bb}app";
|
||||
|
||||
@@ -486,12 +487,11 @@ export class NodeModulesDirectory {
|
||||
return true;
|
||||
}
|
||||
|
||||
const real = files.realpathOrNull(path);
|
||||
if (typeof real === "string" &&
|
||||
real !== path) {
|
||||
const fileStatus = optimisticLStatOrNull(path);
|
||||
if (fileStatus && fileStatus.isSymbolicLink()) {
|
||||
// If node_modules/.bin/command is a symlink, determine the
|
||||
// answer by calling isWithinProdPackage(real).
|
||||
return isWithinProdPackage(real);
|
||||
return isWithinProdPackage(files.realpathOrNull(path));
|
||||
}
|
||||
|
||||
// If node_modules/.bin/command is not a symlink, then it's hard
|
||||
@@ -2844,20 +2844,18 @@ var writeTargetToPath = Profile(
|
||||
previousBuilder = null,
|
||||
buildMode,
|
||||
minifyMode,
|
||||
forceInPlaceBuild
|
||||
}) {
|
||||
var builder = new Builder({
|
||||
outputPath: files.pathJoin(outputPath, 'programs', name),
|
||||
previousBuilder,
|
||||
// We do not force an in-place build for individual targets like
|
||||
// .meteor/local/build/programs/web.browser.legacy, because they
|
||||
// tend to be written atomically, and it's important on Windows to
|
||||
// avoid overwriting files that might be open currently in the build
|
||||
// or server process.
|
||||
// Server builds do use an in-place build since the server is always stopped
|
||||
// during the build.
|
||||
// If client in-place builds were safer on Windows, they
|
||||
// would be much quicker than from-scratch rebuilds.
|
||||
forceInPlaceBuild: name === 'server',
|
||||
// We do not force an in-place build for individual targets
|
||||
// like .meteor/local/build/programs/web.browser.legacy, because they tend
|
||||
// to be written atomically, and it's important on Windows to avoid
|
||||
// overwriting files that might be open currently in the server
|
||||
// process. There are some exceptions when we know the server process
|
||||
// is not using the files, such as during a full build when it is stopped.
|
||||
forceInPlaceBuild
|
||||
});
|
||||
|
||||
var targetBuild = target.write(builder, {
|
||||
@@ -2910,6 +2908,7 @@ var writeSiteArchive = Profile("bundler writeSiteArchive", function (
|
||||
buildMode,
|
||||
minifyMode,
|
||||
sourceRoot,
|
||||
forceInPlaceBuild,
|
||||
}) {
|
||||
|
||||
const builders = {};
|
||||
@@ -3004,7 +3003,8 @@ Find out more about Meteor at meteor.com.
|
||||
releaseName,
|
||||
previousBuilder: previousBuilders[name] || null,
|
||||
buildMode,
|
||||
minifyMode
|
||||
minifyMode,
|
||||
forceInPlaceBuild
|
||||
});
|
||||
|
||||
builders[name] = targetBuilder;
|
||||
@@ -3083,6 +3083,10 @@ Find out more about Meteor at meteor.com.
|
||||
* - hasCachedBundle: true if we already have a cached bundle stored in
|
||||
* /build. When true, we only build the new client targets in the bundle.
|
||||
*
|
||||
* - forceInPlaceBuild On Windows, in place builds are disabled by default
|
||||
* since they are only safe when the output files from the previous build
|
||||
* are not being used. This can be set to true when it is safe.
|
||||
*
|
||||
* Returns an object with keys:
|
||||
* - errors: A buildmessage.MessageSet, or falsy if bundling succeeded.
|
||||
* - serverWatchSet: Information about server files and paths that were
|
||||
@@ -3117,6 +3121,7 @@ function bundle({
|
||||
previousBuilders = Object.create(null),
|
||||
hasCachedBundle,
|
||||
allowDelayedClientBuilds = false,
|
||||
forceInPlaceBuild,
|
||||
}) {
|
||||
buildOptions = buildOptions || {};
|
||||
|
||||
@@ -3271,6 +3276,7 @@ function bundle({
|
||||
builtBy,
|
||||
releaseName,
|
||||
minifyMode,
|
||||
forceInPlaceBuild,
|
||||
};
|
||||
|
||||
function writeClientTarget(target) {
|
||||
|
||||
@@ -1714,7 +1714,7 @@ export class PackageSourceBatch {
|
||||
if (cacheFilename) {
|
||||
let diskCached = null;
|
||||
try {
|
||||
diskCached = optimisticReadJsonOrNull(cacheFilename);
|
||||
diskCached = files.readJSONOrNull(cacheFilename);
|
||||
} catch (e) {
|
||||
// Ignore JSON parse errors; pretend there was no cache.
|
||||
if (!(e instanceof SyntaxError)) {
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
convertToPosixPath,
|
||||
realpathOrNull,
|
||||
writeFileAtomically,
|
||||
readFile,
|
||||
} from "../fs/files";
|
||||
|
||||
const { SourceNode, SourceMapConsumer } = require("source-map");
|
||||
@@ -74,9 +75,18 @@ const reifyCompileWithCache = Profile("reifyCompileWithCache", wrap(function (
|
||||
source,
|
||||
_hash,
|
||||
bundleArch,
|
||||
cacheFilePath,
|
||||
) {
|
||||
if (cacheFilePath) {
|
||||
try {
|
||||
return readFile(cacheFilePath, "utf8");
|
||||
} catch (e) {
|
||||
if (e.code !== "ENOENT") throw e;
|
||||
}
|
||||
}
|
||||
|
||||
const isLegacy = isLegacyArch(bundleArch);
|
||||
return reifyCompile(stripHashBang(source), {
|
||||
let result = reifyCompile(stripHashBang(source), {
|
||||
parse: reifyBabelParse,
|
||||
generateLetDeclarations: !isLegacy,
|
||||
avoidModernSyntax: isLegacy,
|
||||
@@ -84,6 +94,14 @@ const reifyCompileWithCache = Profile("reifyCompileWithCache", wrap(function (
|
||||
dynamicImport: true,
|
||||
ast: false,
|
||||
}).code;
|
||||
|
||||
if (cacheFilePath) {
|
||||
Promise.resolve().then(
|
||||
() => writeFileAtomically(cacheFilePath, result),
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}, {
|
||||
makeCacheKey(_source, hash, bundleArch) {
|
||||
return JSON.stringify([hash, bundleArch]);
|
||||
@@ -132,29 +150,16 @@ class DefaultHandlers {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.cacheDir) {
|
||||
const cacheFileName = this.getCacheFileName(file)!;
|
||||
try {
|
||||
return optimisticReadFile(cacheFileName, "utf8");
|
||||
} catch (e) {
|
||||
if (e.code !== "ENOENT") throw e;
|
||||
const code = reifyCompileWithCache(
|
||||
file.dataString,
|
||||
file.hash,
|
||||
this.bundleArch,
|
||||
);
|
||||
Promise.resolve().then(
|
||||
() => writeFileAtomically(cacheFileName, code),
|
||||
);
|
||||
return code;
|
||||
}
|
||||
} else {
|
||||
return reifyCompileWithCache(
|
||||
file.dataString,
|
||||
file.hash,
|
||||
this.bundleArch,
|
||||
);
|
||||
}
|
||||
const cacheFileName = this.cacheDir ?
|
||||
this.getCacheFileName(file) :
|
||||
null;
|
||||
|
||||
return reifyCompileWithCache(
|
||||
file.dataString,
|
||||
file.hash,
|
||||
this.bundleArch,
|
||||
cacheFileName
|
||||
)
|
||||
}
|
||||
|
||||
// Files with an .mjs extension are just JavaScript plus module syntax.
|
||||
|
||||
@@ -79,6 +79,9 @@ var loadOrderSort = function (sourceProcessorSet, arch) {
|
||||
});
|
||||
|
||||
return function (a, b) {
|
||||
const aBasename = files.pathBasename(a);
|
||||
const bBasename = files.pathBasename(b);
|
||||
|
||||
// XXX MODERATELY SIZED HACK --
|
||||
// push template files ahead of everything else. this is
|
||||
// important because the user wants to be able to say
|
||||
@@ -87,15 +90,15 @@ var loadOrderSort = function (sourceProcessorSet, arch) {
|
||||
// before the corresponding .html file.
|
||||
//
|
||||
// maybe all of the templates should go in one file?
|
||||
var isTemplate_a = isTemplate(files.pathBasename(a));
|
||||
var isTemplate_b = isTemplate(files.pathBasename(b));
|
||||
var isTemplate_a = isTemplate(aBasename);
|
||||
var isTemplate_b = isTemplate(bBasename);
|
||||
if (isTemplate_a !== isTemplate_b) {
|
||||
return (isTemplate_a ? -1 : 1);
|
||||
}
|
||||
|
||||
// main.* loaded last
|
||||
var ismain_a = (files.pathBasename(a).indexOf('main.') === 0);
|
||||
var ismain_b = (files.pathBasename(b).indexOf('main.') === 0);
|
||||
var ismain_a = (aBasename.indexOf('main.') === 0);
|
||||
var ismain_b = (bBasename.indexOf('main.') === 0);
|
||||
if (ismain_a !== ismain_b) {
|
||||
return (ismain_a ? 1 : -1);
|
||||
}
|
||||
@@ -213,14 +216,44 @@ var getExcerptFromReadme = function (text) {
|
||||
class SymlinkLoopChecker {
|
||||
constructor(sourceRoot) {
|
||||
this.sourceRoot = sourceRoot;
|
||||
this._realSourceRoot = files.realpath(sourceRoot);
|
||||
this._seenPaths = {};
|
||||
this._cache = new Map();
|
||||
}
|
||||
|
||||
// Avoids running realpath unless necessary
|
||||
// since it is relatively slow on windows
|
||||
_realpath = Profile('_realpath', function (relDir) {
|
||||
const absPath = files.pathJoin(this._realSourceRoot, relDir);
|
||||
|
||||
if (files.lstat(absPath).isSymbolicLink()) {
|
||||
const result = files.realpath(absPath);
|
||||
this._cache.set(relDir, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
let result;
|
||||
const parentDir = files.pathDirname(relDir);
|
||||
const parentEntry = this._cache.get(parentDir);
|
||||
if (parentDir === '.') {
|
||||
result = absPath;
|
||||
} else if (parentEntry) {
|
||||
result = files.pathJoin(parentEntry, files.pathBasename(relDir));
|
||||
} else {
|
||||
// The parent dir was never checked, which prevents us from
|
||||
// skipping realpath
|
||||
result = files.realpath(absPath);
|
||||
}
|
||||
|
||||
this._cache.set(relDir, result);
|
||||
return result;
|
||||
})
|
||||
|
||||
check(relDir, quietly = true) {
|
||||
const absPath = files.pathJoin(this.sourceRoot, relDir);
|
||||
|
||||
try {
|
||||
var realPath = files.realpath(absPath);
|
||||
var realPath = this._realpath(relDir);
|
||||
} catch (e) {
|
||||
if (!e || e.code !== 'ELOOP') {
|
||||
throw e;
|
||||
@@ -834,10 +867,21 @@ _.extend(PackageSource.prototype, {
|
||||
}),
|
||||
|
||||
_readAndWatchDirectory(relDir, watchSet, {include, exclude, names}) {
|
||||
return watch.readAndWatchDirectory(watchSet, {
|
||||
const options = {
|
||||
absPath: files.pathJoin(this.sourceRoot, relDir),
|
||||
include, exclude, names
|
||||
}).map(name => files.pathJoin(relDir, name));
|
||||
};
|
||||
|
||||
const contents = watch.readDirectory(options);
|
||||
|
||||
if (watchSet) {
|
||||
watchSet.addDirectory({
|
||||
contents,
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
return contents.map(name => files.pathJoin(relDir, name));
|
||||
},
|
||||
|
||||
// Initialize a package from an application directory (has .meteor/packages).
|
||||
@@ -1099,7 +1143,7 @@ _.extend(PackageSource.prototype, {
|
||||
// complete list of source files for directories within node_modules.
|
||||
_findSourcesCache: Object.create(null),
|
||||
|
||||
_findSources: Profile("PackageSource#_findSources", function ({
|
||||
_findSources: Profile(({ sourceArch }) => `PackageSource#_findSources for ${sourceArch.arch}`, function ({
|
||||
sourceProcessorSet,
|
||||
watchSet,
|
||||
isApp,
|
||||
@@ -1216,9 +1260,10 @@ _.extend(PackageSource.prototype, {
|
||||
|
||||
const baseCacheKey = JSON.stringify({
|
||||
isApp,
|
||||
arch,
|
||||
sourceRoot: self.sourceRoot,
|
||||
excludes: anyLevelExcludes,
|
||||
names: sourceReadOptions.names,
|
||||
include: sourceReadOptions.include
|
||||
}, (key, value) => {
|
||||
if (_.isRegExp(value)) {
|
||||
return [value.source, value.flags];
|
||||
@@ -1260,13 +1305,13 @@ _.extend(PackageSource.prototype, {
|
||||
return array;
|
||||
}
|
||||
|
||||
function find(dir, depth, inNodeModules) {
|
||||
function find(dir, depth, { inNodeModules = false, cache = false } = {}) {
|
||||
// Remove trailing slash.
|
||||
dir = dir.replace(/\/$/, "");
|
||||
|
||||
// If we're in a node_modules directory, cache the results of the
|
||||
// find function for the duration of the process.
|
||||
let cacheKey = inNodeModules && makeCacheKey(dir);
|
||||
let cacheKey = inNodeModules && cache && makeCacheKey(dir);
|
||||
if (cacheKey &&
|
||||
cacheKey in self._findSourcesCache) {
|
||||
return self._findSourcesCache[cacheKey];
|
||||
@@ -1305,13 +1350,16 @@ _.extend(PackageSource.prototype, {
|
||||
}
|
||||
|
||||
const sources = _.difference(
|
||||
self._readAndWatchDirectory(dir, watchSet, readOptions),
|
||||
self._readAndWatchDirectory(dir, inNodeModules ? null : watchSet, readOptions),
|
||||
depth > 0 ? [] : controlFiles
|
||||
);
|
||||
|
||||
const subdirectories = self._readAndWatchDirectory(dir, watchSet, {
|
||||
include: [/\/$/],
|
||||
exclude: depth > 0
|
||||
const subdirectories = self._readAndWatchDirectory(
|
||||
dir,
|
||||
inNodeModules ? null : watchSet,
|
||||
{
|
||||
include: [/\/$/],
|
||||
exclude: depth > 0
|
||||
? anyLevelExcludes
|
||||
: topLevelExcludes
|
||||
});
|
||||
@@ -1342,7 +1390,7 @@ _.extend(PackageSource.prototype, {
|
||||
}
|
||||
|
||||
} else {
|
||||
sources.push(...find(subdir, depth + 1, inNodeModules));
|
||||
sources.push(...find(subdir, depth + 1, { inNodeModules, cache: !inNodeModules }));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1353,7 +1401,7 @@ _.extend(PackageSource.prototype, {
|
||||
// subdirectories, continue searching this node_modules directory,
|
||||
// so that any non-.js(on) files it contains can be imported by
|
||||
// the app (#6037).
|
||||
sources.push(...find(nodeModulesDir, depth + 1, true));
|
||||
sources.push(...find(nodeModulesDir, depth + 1, { inNodeModules: true, cache: !inNodeModules}));
|
||||
}
|
||||
|
||||
delete dotMeteorIgnoreFiles[dir];
|
||||
|
||||
@@ -579,6 +579,10 @@ _.extend(AppRunner.prototype, {
|
||||
// Permit delayed bundling of client architectures if the
|
||||
// console is interactive.
|
||||
allowDelayedClientBuilds: ! Console.isHeadless(),
|
||||
|
||||
// None of the targets are used during full rebuilds
|
||||
// so we can safely build in place on Windows
|
||||
forceInPlaceBuild: !cachedServerWatchSet
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -938,6 +938,10 @@ _.extend(MRp, {
|
||||
"Looks like you are out of free disk space under .meteor/local.";
|
||||
} else if (explanation) {
|
||||
message += "\n" + explanation.longText;
|
||||
} else if (process.platform === 'win32') {
|
||||
message += "\n\n" +
|
||||
"Check how to troubleshoot here " +
|
||||
"https://docs.meteor.com/windows.html#cant-start-mongo-server";
|
||||
}
|
||||
|
||||
if (explanation && explanation.symbol === 'EXIT_NET_ERROR') {
|
||||
|
||||
1
tools/static-assets/skel-vue/.gitignore
vendored
Normal file
1
tools/static-assets/skel-vue/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
node_modules/
|
||||
1
tools/static-assets/skel-vue/.meteor/.gitignore
vendored
Normal file
1
tools/static-assets/skel-vue/.meteor/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
local
|
||||
24
tools/static-assets/skel-vue/.meteor/packages
Normal file
24
tools/static-assets/skel-vue/.meteor/packages
Normal file
@@ -0,0 +1,24 @@
|
||||
# Meteor packages used by this project, one per line.
|
||||
# Check this file (and the other files in this directory) into your repository.
|
||||
#
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
meteor-base # Packages every Meteor app needs to have
|
||||
mobile-experience # Packages for a great mobile UX
|
||||
mongo # The database Meteor supports right now
|
||||
reactive-var # Reactive variable for tracker
|
||||
|
||||
standard-minifier-css # CSS minifier run for production mode
|
||||
standard-minifier-js # JS minifier run for production mode
|
||||
es5-shim # ECMAScript 5 compatibility for older browsers
|
||||
ecmascript # Enable ECMAScript2015+ syntax in app code
|
||||
typescript # Enable TypeScript syntax in .ts and .tsx modules
|
||||
shell-server # Server-side component of the `meteor shell` command
|
||||
|
||||
tracker # Dependency tracker to allow reactive callbacks
|
||||
static-html # Define static page content in .html files
|
||||
akryum:vue-component # Vue-CLI template to publish components
|
||||
|
||||
meteortesting:mocha # A package for writing and running your meteor app and package tests with mocha
|
||||
johanbrook:publication-collector # Test a Meteor publication by collecting its output
|
||||
2
tools/static-assets/skel-vue/.meteor/platforms
Normal file
2
tools/static-assets/skel-vue/.meteor/platforms
Normal file
@@ -0,0 +1,2 @@
|
||||
server
|
||||
browser
|
||||
23
tools/static-assets/skel-vue/package.json
Normal file
23
tools/static-assets/skel-vue/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "skel",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "meteor run",
|
||||
"test": "meteor test --once --driver-package meteortesting:mocha",
|
||||
"test-app": "TEST_WATCH=1 meteor test --full-app --driver-package meteortesting:mocha",
|
||||
"visualize": "meteor --production --extra-packages bundle-visualizer"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.8.3",
|
||||
"meteor-node-stubs": "^1.0.0",
|
||||
"vue": "^2.6.11",
|
||||
"vue-meteor-tracker": "^2.0.0-beta.5"
|
||||
},
|
||||
"meteor": {
|
||||
"mainModule": {
|
||||
"client": "src/client.js",
|
||||
"server": "src/server.js"
|
||||
},
|
||||
"testModule": "tests/main.js"
|
||||
}
|
||||
}
|
||||
26
tools/static-assets/skel-vue/src/App.vue
Normal file
26
tools/static-assets/skel-vue/src/App.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Welcome to Meteor!</h1>
|
||||
<hello/>
|
||||
<info/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Hello from './components/Hello.vue'
|
||||
import Info from './components/Info.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Hello,
|
||||
Info,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
12
tools/static-assets/skel-vue/src/client.js
Normal file
12
tools/static-assets/skel-vue/src/client.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import Vue from 'vue'
|
||||
|
||||
import './plugins'
|
||||
|
||||
import App from './App.vue'
|
||||
|
||||
Meteor.startup(() => {
|
||||
new Vue({
|
||||
el: '#app',
|
||||
...App,
|
||||
})
|
||||
})
|
||||
3
tools/static-assets/skel-vue/src/collections/Links.js
Normal file
3
tools/static-assets/skel-vue/src/collections/Links.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import { Mongo } from 'meteor/mongo';
|
||||
|
||||
export default new Mongo.Collection('links');
|
||||
24
tools/static-assets/skel-vue/src/collections/Links.tests.js
Normal file
24
tools/static-assets/skel-vue/src/collections/Links.tests.js
Normal file
@@ -0,0 +1,24 @@
|
||||
// Tests for the behavior of the links collection
|
||||
//
|
||||
// https://guide.meteor.com/testing.html
|
||||
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { assert } from 'chai';
|
||||
import Links from './links.js';
|
||||
|
||||
if (Meteor.isServer) {
|
||||
describe('links collection', function () {
|
||||
it('insert correctly', function () {
|
||||
const linkId = Links.insert({
|
||||
title: 'meteor homepage',
|
||||
url: 'https://www.meteor.com',
|
||||
});
|
||||
const added = Links.find({ _id: linkId });
|
||||
const collectionName = added._getCollectionName();
|
||||
const count = added.count();
|
||||
|
||||
assert.equal(collectionName, 'links');
|
||||
assert.equal(count, 1);
|
||||
});
|
||||
});
|
||||
}
|
||||
27
tools/static-assets/skel-vue/src/components/Hello.vue
Normal file
27
tools/static-assets/skel-vue/src/components/Hello.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<div>
|
||||
<button @click="increment">Click Me</button>
|
||||
<p>You've pressed the button {{counter}} times.</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
counter: 0,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
increment() {
|
||||
this.counter += 1
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
p {
|
||||
font-family: serif;
|
||||
}
|
||||
</style>
|
||||
55
tools/static-assets/skel-vue/src/components/Info.vue
Normal file
55
tools/static-assets/skel-vue/src/components/Info.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>Learn Meteor!</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<form class="info-link-add">
|
||||
<input type="text" v-model="title" name="title" placeholder="Title" required>
|
||||
<input type="url" v-model="url" name="url" placeholder="Url" required>
|
||||
<input type="submit" name="submit" @click="submit($event)" value="Add new link">
|
||||
</form>
|
||||
</li>
|
||||
<li v-for="link in links"><a :href="link.url" target="_blank">{{link.title}}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Links from '../collections/Links'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
title: "",
|
||||
url: "",
|
||||
}
|
||||
},
|
||||
meteor: {
|
||||
$subscribe: {
|
||||
'links': [],
|
||||
},
|
||||
links () {
|
||||
return Links.find({})
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
submit(event) {
|
||||
event.preventDefault()
|
||||
Meteor.call('createLink', this.title, this.url, (error) => {
|
||||
if (error) {
|
||||
alert(error.error)
|
||||
} else {
|
||||
this.title = ''
|
||||
this.url = ''
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
ul {
|
||||
font-family: monospace;
|
||||
}
|
||||
</style>
|
||||
32
tools/static-assets/skel-vue/src/fixtures.js
Normal file
32
tools/static-assets/skel-vue/src/fixtures.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import Links from './collections/Links.js';
|
||||
|
||||
Meteor.startup(() => {
|
||||
// if the Links collection is empty
|
||||
if (Links.find().count() === 0) {
|
||||
const data = [
|
||||
{
|
||||
title: 'Do the Tutorial',
|
||||
url: 'https://www.meteor.com/try',
|
||||
createdAt: new Date(),
|
||||
},
|
||||
{
|
||||
title: 'Follow the Guide',
|
||||
url: 'http://guide.meteor.com',
|
||||
createdAt: new Date(),
|
||||
},
|
||||
{
|
||||
title: 'Read the Docs',
|
||||
url: 'https://docs.meteor.com',
|
||||
createdAt: new Date(),
|
||||
},
|
||||
{
|
||||
title: 'Discussions',
|
||||
url: 'https://forums.meteor.com',
|
||||
createdAt: new Date(),
|
||||
},
|
||||
];
|
||||
|
||||
data.forEach(link => Links.insert(link));
|
||||
}
|
||||
});
|
||||
7
tools/static-assets/skel-vue/src/main.html
Normal file
7
tools/static-assets/skel-vue/src/main.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<head>
|
||||
<title>~name~</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
16
tools/static-assets/skel-vue/src/methods/createLink.js
Normal file
16
tools/static-assets/skel-vue/src/methods/createLink.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import Links from '../collections/Links.js';
|
||||
|
||||
Meteor.methods({
|
||||
'createLink'(title, url) {
|
||||
check(url, String);
|
||||
check(title, String);
|
||||
|
||||
return Links.insert({
|
||||
url,
|
||||
title,
|
||||
createdAt: new Date(),
|
||||
});
|
||||
},
|
||||
});
|
||||
20
tools/static-assets/skel-vue/src/methods/createLink.tests.js
Normal file
20
tools/static-assets/skel-vue/src/methods/createLink.tests.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { assert } from 'chai';
|
||||
import Links from '../collections/Links.js';
|
||||
import './methods.js';
|
||||
|
||||
if (Meteor.isServer) {
|
||||
describe('method: createLink', function () {
|
||||
beforeEach(function () {
|
||||
Links.remove({});
|
||||
});
|
||||
|
||||
it('can add a new link', function () {
|
||||
const addLink = Meteor.server.method_handlers['createLink'];
|
||||
|
||||
addLink.apply({}, ['meteor.com', 'https://www.meteor.com']);
|
||||
|
||||
assert.equal(Links.find().count(), 1);
|
||||
});
|
||||
});
|
||||
}
|
||||
1
tools/static-assets/skel-vue/src/methods/index.js
Normal file
1
tools/static-assets/skel-vue/src/methods/index.js
Normal file
@@ -0,0 +1 @@
|
||||
import './createLink'
|
||||
4
tools/static-assets/skel-vue/src/plugins.js
Normal file
4
tools/static-assets/skel-vue/src/plugins.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import Vue from 'vue'
|
||||
import VueMeteorTracker from 'vue-meteor-tracker'
|
||||
|
||||
Vue.use(VueMeteorTracker)
|
||||
1
tools/static-assets/skel-vue/src/publications/index.js
Normal file
1
tools/static-assets/skel-vue/src/publications/index.js
Normal file
@@ -0,0 +1 @@
|
||||
import './links'
|
||||
6
tools/static-assets/skel-vue/src/publications/links.js
Normal file
6
tools/static-assets/skel-vue/src/publications/links.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import Links from '../collections/Links.js';
|
||||
|
||||
Meteor.publish('links', function () {
|
||||
return Links.find();
|
||||
});
|
||||
22
tools/static-assets/skel-vue/src/publications/links.tests.js
Normal file
22
tools/static-assets/skel-vue/src/publications/links.tests.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { assert } from 'chai'
|
||||
import { PublicationCollector } from 'meteor/johanbrook:publication-collector'
|
||||
import Links from '../collections/Links.js'
|
||||
import './publications.js'
|
||||
|
||||
describe('Publish links', function () {
|
||||
beforeEach(function () {
|
||||
Links.remove({})
|
||||
Links.insert({
|
||||
title: 'meteor homepage',
|
||||
url: 'https://www.meteor.com'
|
||||
})
|
||||
})
|
||||
|
||||
it('sends all links', function (done) {
|
||||
const collector = new PublicationCollector()
|
||||
collector.collect('links', (collections) => {
|
||||
assert.equal(collections.links.length, 1)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
3
tools/static-assets/skel-vue/src/server.js
Normal file
3
tools/static-assets/skel-vue/src/server.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import './fixtures'
|
||||
import './methods'
|
||||
import './publications'
|
||||
20
tools/static-assets/skel-vue/tests/main.js
Normal file
20
tools/static-assets/skel-vue/tests/main.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import assert from "assert";
|
||||
|
||||
describe("skel", function () {
|
||||
it("package.json has correct name", async function () {
|
||||
const { name } = await import("../package.json");
|
||||
assert.strictEqual(name, "skel");
|
||||
});
|
||||
|
||||
if (Meteor.isClient) {
|
||||
it("client is not server", function () {
|
||||
assert.strictEqual(Meteor.isServer, false);
|
||||
});
|
||||
}
|
||||
|
||||
if (Meteor.isServer) {
|
||||
it("server is not client", function () {
|
||||
assert.strictEqual(Meteor.isClient, false);
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user