From 8aaf229569e365258101bd9c17949d106f9b0786 Mon Sep 17 00:00:00 2001 From: Dhananjay Date: Tue, 5 Oct 2021 17:34:07 +0530 Subject: [PATCH 001/393] #11640 Email.send fix minor fix Added null check using a simple regex for 'from' address in email options. --- packages/email/email.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/email/email.js b/packages/email/email.js index 650f80aa1f..785ea767db 100644 --- a/packages/email/email.js +++ b/packages/email/email.js @@ -214,7 +214,9 @@ Email.send = function (options) { if (options.mailComposer) { options = options.mailComposer.mail; } - + if (!/^\S+@\S+$/.test(options.from)) { + throw new Error("Email options passed to send function must contain a valid 'from' address"); + } let send = true; sendHooks.each(hook => { send = hook(options); From 47ecf143c3d2de955330f126ea56c6f8324907be Mon Sep 17 00:00:00 2001 From: Dhananjay Date: Tue, 5 Oct 2021 23:23:46 +0530 Subject: [PATCH 002/393] Using Meteor.Error Using invalid-mail-options as the unique error code --- packages/email/email.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/email/email.js b/packages/email/email.js index 785ea767db..8fcb018a11 100644 --- a/packages/email/email.js +++ b/packages/email/email.js @@ -215,7 +215,7 @@ Email.send = function (options) { options = options.mailComposer.mail; } if (!/^\S+@\S+$/.test(options.from)) { - throw new Error("Email options passed to send function must contain a valid 'from' address"); + throw new Meteor.Error("invalid-mail-options", "Email options passed to send function must contain a valid 'from' address"); } let send = true; sendHooks.each(hook => { From f68dda1e1c66d0670b8c0d97f229c547821da5d8 Mon Sep 17 00:00:00 2001 From: Dhananjay Date: Wed, 6 Oct 2021 18:23:12 +0530 Subject: [PATCH 003/393] Updated 'from' check in Email.send The function checks for the 'from' address to be a string including the character '@' --- packages/email/email.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/email/email.js b/packages/email/email.js index 8fcb018a11..30e9c04c2f 100644 --- a/packages/email/email.js +++ b/packages/email/email.js @@ -1,6 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Log } from 'meteor/logging'; import { Hook } from 'meteor/callback-hook'; +import { check } from 'meteor/check'; import Future from 'fibers/future'; import url from 'url'; @@ -214,9 +215,11 @@ Email.send = function (options) { if (options.mailComposer) { options = options.mailComposer.mail; } - if (!/^\S+@\S+$/.test(options.from)) { + check(options.from, String); + if (!options.from.includes('@')) { throw new Meteor.Error("invalid-mail-options", "Email options passed to send function must contain a valid 'from' address"); } + let send = true; sendHooks.each(hook => { send = hook(options); From 00fcebf66b865d03ff0d5d9729bf6de3815ebf38 Mon Sep 17 00:00:00 2001 From: Jan Dvorak Date: Thu, 7 Oct 2021 14:47:35 +0200 Subject: [PATCH 004/393] Add checking for `to` prop in email and add history entry --- History.md | 3 ++ packages/email/email.js | 102 +++++++++++++++++++++++++------------- packages/email/package.js | 14 +++--- 3 files changed, 78 insertions(+), 41 deletions(-) diff --git a/History.md b/History.md index 1ed240278e..a32fe6abb9 100644 --- a/History.md +++ b/History.md @@ -24,6 +24,9 @@ * `ecmascript-runtime-client@0.12.1` - Revert `core-js` to v3.15.2 due to issues in legacy build with arrays, [see issue for more details](https://github.com/meteor/meteor/issues/11662) +* `email@2.2.1` + - Added checking that `to` and `from` email params are present if not using MailComposer object and throw error early + ## v2.4, 2021-09-15 #### Highlights diff --git a/packages/email/email.js b/packages/email/email.js index 30e9c04c2f..b723c10c9f 100644 --- a/packages/email/email.js +++ b/packages/email/email.js @@ -15,29 +15,34 @@ export const EmailInternals = { NpmModules: { mailcomposer: { version: Npm.require('nodemailer/package.json').version, - module: Npm.require('nodemailer/lib/mail-composer') + module: Npm.require('nodemailer/lib/mail-composer'), }, nodemailer: { version: Npm.require('nodemailer/package.json').version, - module: Npm.require('nodemailer') - } - } + module: Npm.require('nodemailer'), + }, + }, }; const MailComposer = EmailInternals.NpmModules.mailcomposer.module; -const makeTransport = function (mailUrlString) { +const makeTransport = function(mailUrlString) { const mailUrl = new URL(mailUrlString); if (mailUrl.protocol !== 'smtp:' && mailUrl.protocol !== 'smtps:') { - throw new Error("Email protocol in $MAIL_URL (" + - mailUrlString + ") must be 'smtp' or 'smtps'"); + throw new Error( + 'Email protocol in $MAIL_URL (' + + mailUrlString + + ") must be 'smtp' or 'smtps'" + ); } if (mailUrl.protocol === 'smtp:' && mailUrl.port === '465') { - Log.debug("The $MAIL_URL is 'smtp://...:465'. " + - "You probably want 'smtps://' (The 's' enables TLS/SSL) " + - "since '465' is typically a secure port."); + Log.debug( + "The $MAIL_URL is 'smtp://...:465'. " + + "You probably want 'smtps://' (The 's' enables TLS/SSL) " + + "since '465' is typically a secure port." + ); } // Allow overriding pool setting, but default to true. @@ -85,15 +90,17 @@ const knownHostsTransport = function(settings = undefined, url = undefined) { } if (!wellKnow(settings?.service || service)) { - throw new Error('Could not recognize e-mail service. See list at https://nodemailer.com/smtp/well-known/ for services that we can configure for you.'); + throw new Error( + 'Could not recognize e-mail service. See list at https://nodemailer.com/smtp/well-known/ for services that we can configure for you.' + ); } const transport = nodemailer.createTransport({ service: settings?.service || service, auth: { user: settings?.user || user, - pass: settings?.password || password - } + pass: settings?.password || password, + }, }); transport._syncSendMail = Meteor.wrapAsync(transport.sendMail, transport); @@ -107,8 +114,17 @@ const getTransport = function() { // set process.env.MAIL_URL in startup code. Then we store in a cache until // process.env.MAIL_URL changes. const url = process.env.MAIL_URL; - if (this.cacheKey === undefined || (this.cacheKey !== url || this.cacheKey !== packageSettings?.service || 'settings')) { - if ((packageSettings?.service && wellKnow(packageSettings.service)) || (url && wellKnow(new URL(url).hostname) || wellKnow(url?.split(':')[0] || ''))) { + if ( + this.cacheKey === undefined || + this.cacheKey !== url || + this.cacheKey !== packageSettings?.service || + 'settings' + ) { + if ( + (packageSettings?.service && wellKnow(packageSettings.service)) || + (url && wellKnow(new URL(url).hostname)) || + wellKnow(url?.split(':')[0] || '') + ) { this.cacheKey = packageSettings.service || 'settings'; this.cache = knownHostsTransport(packageSettings, url); } else { @@ -123,35 +139,37 @@ let nextDevModeMailId = 0; let output_stream = process.stdout; // Testing hooks -EmailTest.overrideOutputStream = function (stream) { +EmailTest.overrideOutputStream = function(stream) { nextDevModeMailId = 0; output_stream = stream; }; -EmailTest.restoreOutputStream = function () { +EmailTest.restoreOutputStream = function() { output_stream = process.stdout; }; -const devModeSend = function (mail) { +const devModeSend = function(mail) { let devModeMailId = nextDevModeMailId++; const stream = output_stream; // This approach does not prevent other writers to stdout from interleaving. - stream.write("====== BEGIN MAIL #" + devModeMailId + " ======\n"); - stream.write("(Mail not sent; to enable sending, set the MAIL_URL " + - "environment variable.)\n"); + stream.write('====== BEGIN MAIL #' + devModeMailId + ' ======\n'); + stream.write( + '(Mail not sent; to enable sending, set the MAIL_URL ' + + 'environment variable.)\n' + ); const readStream = new MailComposer(mail).compile().createReadStream(); - readStream.pipe(stream, {end: false}); - const future = new Future; - readStream.on('end', function () { - stream.write("====== END MAIL #" + devModeMailId + " ======\n"); + readStream.pipe(stream, { end: false }); + const future = new Future(); + readStream.on('end', function() { + stream.write('====== END MAIL #' + devModeMailId + ' ======\n'); future.return(); }); future.wait(); }; -const smtpSend = function (transport, mail) { +const smtpSend = function(transport, mail) { transport._syncSendMail(mail); }; @@ -166,7 +184,7 @@ const sendHooks = new Hook(); * false to skip sending. * @returns {{ stop: function, callback: function }} */ -Email.hookSend = function (f) { +Email.hookSend = function(f) { return sendHooks.register(f); }; @@ -211,17 +229,29 @@ Email.customTransport = undefined; * You can create a `MailComposer` object via * `new EmailInternals.NpmModules.mailcomposer.module`. */ -Email.send = function (options) { +Email.send = function(options) { if (options.mailComposer) { options = options.mailComposer.mail; - } - check(options.from, String); - if (!options.from.includes('@')) { - throw new Meteor.Error("invalid-mail-options", "Email options passed to send function must contain a valid 'from' address"); + } else { + // Check that we have the needed params + check(options.from, String); + check(options.to, String); + if (!options.from.includes('@')) { + throw new Meteor.Error( + 'invalid-mail-options', + "Email options passed to send function must contain a valid 'from' address" + ); + } + if (!options.to.includes('@')) { + throw new Meteor.Error( + 'invalid-mail-options', + "Email options passed to send function must contain a valid 'to' address" + ); + } } let send = true; - sendHooks.each(hook => { + sendHooks.forEach(hook => { send = hook(options); return send; }); @@ -233,7 +263,11 @@ Email.send = function (options) { customTransport({ packageSettings, ...options }); return; } - if (Meteor.isProduction || process.env.MAIL_URL || Meteor.settings.packages?.email) { + if ( + Meteor.isProduction || + process.env.MAIL_URL || + Meteor.settings.packages?.email + ) { const transport = getTransport(); smtpSend(transport, options); return; diff --git a/packages/email/package.js b/packages/email/package.js index db1348857a..3fb07dae92 100644 --- a/packages/email/package.js +++ b/packages/email/package.js @@ -1,21 +1,21 @@ Package.describe({ - summary: "Send email messages", - version: "2.2.0" + summary: 'Send email messages', + version: '2.2.1', }); Npm.depends({ - nodemailer: "6.6.3", - "stream-buffers": "3.0.2" + nodemailer: '6.6.3', + 'stream-buffers': '3.0.2', }); -Package.onUse(function (api) { +Package.onUse(function(api) { api.use(['ecmascript', 'logging', 'callback-hook'], 'server'); api.mainModule('email.js', 'server'); api.export(['Email', 'EmailInternals'], 'server'); - api.export('EmailTest', 'server', {testOnly: true}); + api.export('EmailTest', 'server', { testOnly: true }); }); -Package.onTest(function (api) { +Package.onTest(function(api) { api.use('email', 'server'); api.use(['tinytest', 'ecmascript']); api.addFiles('email_tests.js', 'server'); From 4c345f9a931e5cfca8f31a493478a16eef253f8a Mon Sep 17 00:00:00 2001 From: Jan Dvorak Date: Thu, 7 Oct 2021 14:54:09 +0200 Subject: [PATCH 005/393] Remove `check` from email package --- packages/email/email.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/email/email.js b/packages/email/email.js index b723c10c9f..797d7f11ec 100644 --- a/packages/email/email.js +++ b/packages/email/email.js @@ -1,7 +1,6 @@ import { Meteor } from 'meteor/meteor'; import { Log } from 'meteor/logging'; import { Hook } from 'meteor/callback-hook'; -import { check } from 'meteor/check'; import Future from 'fibers/future'; import url from 'url'; @@ -234,15 +233,13 @@ Email.send = function(options) { options = options.mailComposer.mail; } else { // Check that we have the needed params - check(options.from, String); - check(options.to, String); - if (!options.from.includes('@')) { + if (!options.from?.includes('@')) { throw new Meteor.Error( 'invalid-mail-options', "Email options passed to send function must contain a valid 'from' address" ); } - if (!options.to.includes('@')) { + if (!options.to?.includes('@')) { throw new Meteor.Error( 'invalid-mail-options', "Email options passed to send function must contain a valid 'to' address" From b372ff2dd086b340cceeaaab8393a17c6a2c733b Mon Sep 17 00:00:00 2001 From: Jan Dvorak Date: Thu, 7 Oct 2021 15:30:30 +0200 Subject: [PATCH 006/393] Fixed email cache logic --- packages/email/email.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/email/email.js b/packages/email/email.js index 797d7f11ec..38a32769d6 100644 --- a/packages/email/email.js +++ b/packages/email/email.js @@ -117,7 +117,7 @@ const getTransport = function() { this.cacheKey === undefined || this.cacheKey !== url || this.cacheKey !== packageSettings?.service || - 'settings' + this.cacheKey !== 'settings' ) { if ( (packageSettings?.service && wellKnow(packageSettings.service)) || From ed5c340fc779da8fcd3b71a942c614ef1429aedd Mon Sep 17 00:00:00 2001 From: Jan Dvorak Date: Wed, 20 Oct 2021 10:03:28 +0200 Subject: [PATCH 007/393] Allow setting Accounts.ui.config from settings --- History.md | 1 + docs/source/api/accounts.md | 4 +- packages/accounts-ui-unstyled/accounts_ui.js | 69 ++++++++++++++------ 3 files changed, 52 insertions(+), 22 deletions(-) diff --git a/History.md b/History.md index 6a85357a22..411890253d 100644 --- a/History.md +++ b/History.md @@ -39,6 +39,7 @@ * `accounts-ui-unstyled@1.6.0` - Add support for `accounts-passwordless`. + - `Accounts.ui.config` can now be set via `Meteor.settings.public.packages.accounts-ui-unstyled`. * `service-configuration@1.3.0` - You can now define services configuration via `Meteor.settings.packages.service-configuration` by adding keys as service names and their objects being the service settings. You will need to refer to the specific service for the settings that are expected, most commonly those will be `secret` and `appId`. diff --git a/docs/source/api/accounts.md b/docs/source/api/accounts.md index f09edd7718..38d31772ad 100644 --- a/docs/source/api/accounts.md +++ b/docs/source/api/accounts.md @@ -316,4 +316,6 @@ Accounts.ui.config({ }, passwordSignupFields: 'USERNAME_AND_OPTIONAL_EMAIL' }); -``` \ No newline at end of file +``` + +Since Meteor 2.5 you can configure these in your Meteor settings under `Meteor.settings.public.packages.accounts-ui-unstyled`. diff --git a/packages/accounts-ui-unstyled/accounts_ui.js b/packages/accounts-ui-unstyled/accounts_ui.js index fbf9413834..a078ab0d98 100644 --- a/packages/accounts-ui-unstyled/accounts_ui.js +++ b/packages/accounts-ui-unstyled/accounts_ui.js @@ -20,18 +20,18 @@ const VALID_OPTIONS = new Set() .add('passwordlessSignupFields'); const VALID_PASSWORD_SIGNUP_FIELDS = new Set() - .add("USERNAME_AND_EMAIL") - .add("USERNAME_AND_OPTIONAL_EMAIL") - .add("USERNAME_ONLY") - .add("EMAIL_ONLY"); + .add('USERNAME_AND_EMAIL') + .add('USERNAME_AND_OPTIONAL_EMAIL') + .add('USERNAME_ONLY') + .add('EMAIL_ONLY'); function isValidPasswordSignupField(field) { return VALID_PASSWORD_SIGNUP_FIELDS.has(field); } const VALID_PASSWORDLESS_SIGNUP_FIELDS = new Set() - .add("USERNAME_AND_EMAIL") - .add("EMAIL_ONLY") + .add('USERNAME_AND_EMAIL') + .add('EMAIL_ONLY'); function isValidPasswordlessSignupField(field) { return VALID_PASSWORDLESS_SIGNUP_FIELDS.has(field); @@ -62,15 +62,25 @@ Accounts.ui.config = options => { handleForceApprovalPrompt(options); }; +Meteor.startup(function() { + const settings = Meteor.settings.public?.packages?.['accounts-ui-unstyled']; + + if (settings) { + Accounts.ui.config(settings); + } +}); + function handlePasswordlessSignupFields(options) { let { passwordlessSignupFields } = options; if (passwordlessSignupFields) { const reportInvalid = () => { - throw new Error(`Accounts.ui.config: Invalid option for \`passwordlessSignupFields\`: ${passwordlessSignupFields}`); + throw new Error( + `Accounts.ui.config: Invalid option for \`passwordlessSignupFields\`: ${passwordlessSignupFields}` + ); }; - if (typeof passwordlessSignupFields === "string") { + if (typeof passwordlessSignupFields === 'string') { passwordlessSignupFields = [passwordlessSignupFields]; } else if (!Array.isArray(passwordlessSignupFields)) { reportInvalid(); @@ -78,7 +88,9 @@ function handlePasswordlessSignupFields(options) { if (passwordlessSignupFields.every(isValidPasswordlessSignupField)) { if (Accounts.ui._options.passwordlessSignupFields) { - throw new Error("Accounts.ui.config: Can't set `passwordlessSignupFields` more than once"); + throw new Error( + "Accounts.ui.config: Can't set `passwordlessSignupFields` more than once" + ); } Object.assign(Accounts.ui._options, { passwordlessSignupFields }); return; @@ -93,10 +105,12 @@ function handlePasswordSignupFields(options) { if (passwordSignupFields) { const reportInvalid = () => { - throw new Error(`Accounts.ui.config: Invalid option for \`passwordSignupFields\`: ${passwordSignupFields}`); + throw new Error( + `Accounts.ui.config: Invalid option for \`passwordSignupFields\`: ${passwordSignupFields}` + ); }; - if (typeof passwordSignupFields === "string") { + if (typeof passwordSignupFields === 'string') { passwordSignupFields = [passwordSignupFields]; } else if (!Array.isArray(passwordSignupFields)) { reportInvalid(); @@ -104,7 +118,9 @@ function handlePasswordSignupFields(options) { if (passwordSignupFields.every(isValidPasswordSignupField)) { if (Accounts.ui._options.passwordSignupFields) { - throw new Error("Accounts.ui.config: Can't set `passwordSignupFields` more than once"); + throw new Error( + "Accounts.ui.config: Can't set `passwordSignupFields` more than once" + ); } Object.assign(Accounts.ui._options, { passwordSignupFields }); return; @@ -125,7 +141,7 @@ export function passwordSignupFields() { return [passwordSignupFields]; } - return ["EMAIL_ONLY"]; + return ['EMAIL_ONLY']; } export function passwordlessSignupFields() { @@ -139,21 +155,24 @@ export function passwordlessSignupFields() { return [passwordlessSignupFields]; } - return ["EMAIL_ONLY"]; + return ['EMAIL_ONLY']; } - function handleRequestPermissions({ requestPermissions }) { if (requestPermissions) { Object.keys(requestPermissions).forEach(service => { if (Accounts.ui._options.requestPermissions[service]) { - throw new Error(`Accounts.ui.config: Can't set \`requestPermissions\` more than once for ${service}`); + throw new Error( + `Accounts.ui.config: Can't set \`requestPermissions\` more than once for ${service}` + ); } const scope = requestPermissions[service]; if (!Array.isArray(scope)) { - throw new Error("Accounts.ui.config: Value for `requestPermissions` must be an array"); + throw new Error( + 'Accounts.ui.config: Value for `requestPermissions` must be an array' + ); } Accounts.ui._options.requestPermissions[service] = scope; @@ -165,11 +184,15 @@ function handleRequestOfflineToken({ requestOfflineToken }) { if (requestOfflineToken) { Object.keys(requestOfflineToken).forEach(service => { if (service !== 'google') { - throw new Error("Accounts.ui.config: `requestOfflineToken` only supported for Google login at the moment."); + throw new Error( + 'Accounts.ui.config: `requestOfflineToken` only supported for Google login at the moment.' + ); } if (Accounts.ui._options.requestOfflineToken[service]) { - throw new Error(`Accounts.ui.config: Can't set \`requestOfflineToken\` more than once for ${service}`); + throw new Error( + `Accounts.ui.config: Can't set \`requestOfflineToken\` more than once for ${service}` + ); } Accounts.ui._options.requestOfflineToken[service] = @@ -182,11 +205,15 @@ function handleForceApprovalPrompt({ forceApprovalPrompt }) { if (forceApprovalPrompt) { Object.keys(forceApprovalPrompt).forEach(service => { if (service !== 'google') { - throw new Error("Accounts.ui.config: `forceApprovalPrompt` only supported for Google login at the moment."); + throw new Error( + 'Accounts.ui.config: `forceApprovalPrompt` only supported for Google login at the moment.' + ); } if (Accounts.ui._options.forceApprovalPrompt[service]) { - throw new Error(`Accounts.ui.config: Can't set \`forceApprovalPrompt\` more than once for ${service}`); + throw new Error( + `Accounts.ui.config: Can't set \`forceApprovalPrompt\` more than once for ${service}` + ); } Accounts.ui._options.forceApprovalPrompt[service] = From c03255aa56dede282ffa196b97bdef067bab0344 Mon Sep 17 00:00:00 2001 From: Jan Dvorak Date: Mon, 25 Oct 2021 20:03:58 +0200 Subject: [PATCH 008/393] Initial work on updating Cordova guide --- guide/content/cordova.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/guide/content/cordova.md b/guide/content/cordova.md index ae0dc45020..22b0e58bb9 100644 --- a/guide/content/cordova.md +++ b/guide/content/cordova.md @@ -91,7 +91,7 @@ A shortcut is to run `sudo xcodebuild -license accept` from the command line. (Y

Enabling Xcode command line tools

-After installing Xcode from the Mac App Store, it is still necesssary to enable those tools in the terminal environment. This can be accompilshed by running the following from the command prompt: +After installing Xcode from the Mac App Store, it is still necessary to enable those tools in the terminal environment. This can be accomplished by running the following from the command prompt: ``` sudo xcode-select -s /Applications/Xcode.app/Contents/Developer ``` @@ -123,15 +123,15 @@ In order to build and run Android apps, you will need to:

Installing Android Studio

-The easiest way to get a working Android development environment is by installing [Android Studio](http://developer.android.com/sdk/index.html), which offers a setup wizard on first launch that installs the Android SDK for you, and downloads a default set of tools, platforms, and other components that you will need to start developing. +The easiest way to get a working Android development environment is by installing [Android Studio](https://developer.android.com/studio), which offers a setup wizard on first launch that installs the Android SDK for you, and downloads a default set of tools, platforms, and other components that you will need to start developing. -Please refer to [the Android Studio installation instructions](http://developer.android.com/sdk/installing/index.html?pkg=studio) for more details on the exact steps to follow. +Please refer to [the Android Studio installation instructions](https://developer.android.com/studio/install) for more details on the exact steps to follow. -> There is no need to use Android Studio if you prefer a stand-alone install. Just make sure you install the most recent versions of the [Android SDK Tools](http://developer.android.com/sdk/index.html#Other) and download the required [additional packages](http://developer.android.com/sdk/installing/adding-packages.html) yourself using the [Android SDK Manager](http://developer.android.com/tools/help/sdk-manager.html). +> There is no need to use Android Studio if you prefer a stand-alone install. Just make sure you install the most recent versions of the [Android Command Line Tools](https://developer.android.com/studio#command-tools). -Make sure to select the correct version of the [Android Studio SDK Tools](https://developer.android.com/studio/releases/sdk-tools.html): +Make sure to select the correct version of the [Android Studio SDK Tools](https://developer.android.com/studio/intro/update): - * Meteor 1.4.3.1 or later: Android SDK Tools v.25.**2**.x ([mac](https://dl.google.com/android/repository/tools_r25.2.3-macosx.zip), [linux](https://dl.google.com/android/repository/tools_r25.2.3-linux.zip), [windows](https://dl.google.com/android/repository/tools_r25.2.3-windows.zip)) or v.26.0.0 or later + * Meteor 1.4.3.1 onward: Android SDK Tools v.25.**2**.x ([mac](https://dl.google.com/android/repository/tools_r25.2.3-macosx.zip), [linux](https://dl.google.com/android/repository/tools_r25.2.3-linux.zip), [windows](https://dl.google.com/android/repository/tools_r25.2.3-windows.zip)) or v.26.0.0 or later * v.25.**3.0** **will not work** due to [extensive changes](https://developer.android.com/studio/releases/sdk-tools.html). See [issue #8464](https://github.com/meteor/meteor/issues/8464) for more information. * Meteor 1.4.2.x or before: Android SDK Tools v.23 ([mac](https://dl.google.com/android/repository/tools_r23.0.1-macosx.zip), [linux](https://dl.google.com/android/repository/tools_r23.0.1-linux.zip), [windows](https://dl.google.com/android/repository/tools_r23.0.1-windows.zip)) From 0727bcee75a2f6aefb2d375f3bdf6dd23e5e7e07 Mon Sep 17 00:00:00 2001 From: Jan Dvorak Date: Tue, 26 Oct 2021 13:51:28 +0200 Subject: [PATCH 009/393] Add video to Android setup --- guide/package.json | 2 +- guide/source/cordova.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/guide/package.json b/guide/package.json index 35e610d9b6..13142b4947 100644 --- a/guide/package.json +++ b/guide/package.json @@ -23,4 +23,4 @@ "test": "npm run clean; npm run build", "start": "npm run build && chexo meteor-hexo-config -- server" } -} +} \ No newline at end of file diff --git a/guide/source/cordova.md b/guide/source/cordova.md index 22b0e58bb9..a7168d2584 100644 --- a/guide/source/cordova.md +++ b/guide/source/cordova.md @@ -97,6 +97,8 @@ After installing Xcode from the Mac App Store, it is still necessary to enable t ```

Android

+{% youtube vhlNO0dVvjE %} + In order to build and run Android apps, you will need to: - Install a Java Development Kit (JDK) From bbc829f7349155c2f4a9d411b2a81dabdb50fed1 Mon Sep 17 00:00:00 2001 From: Jan Dvorak Date: Tue, 26 Oct 2021 13:55:17 +0200 Subject: [PATCH 010/393] Fix netlify preview actions --- .github/workflows/docs.yml | 30 ------------------------------ .github/workflows/guide.yml | 35 ++--------------------------------- 2 files changed, 2 insertions(+), 63 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 8a71461b55..168b487427 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -30,33 +30,3 @@ jobs: env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DOCS_SITE_ID }} - deploy-branch: - runs-on: ubuntu-latest - defaults: - run: - working-directory: docs/ - if: - contains(' - refs/heads/release- - ', github.ref) - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 12.x - - name: Build the Docs - run: npm ci && npm run build - - name: Deploy to Netlify for preview - uses: nwtgck/actions-netlify@v1.2.2 - with: - publish-dir: './docs/public' - production-branch: devel - github-token: ${{ secrets.GITHUB_TOKEN }} - deploy-message: Deploy from GitHub Actions ${{ github.event.pull_request.title }} - netlify-config-path: './docs/netlify.toml' - alias: ${{ github.head_ref }} - enable-pull-request-comment: false - enable-commit-comment: false - env: - NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} - NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DOCS_SITE_ID }} diff --git a/.github/workflows/guide.yml b/.github/workflows/guide.yml index b4c00822e5..4a8a7f1ce5 100644 --- a/.github/workflows/guide.yml +++ b/.github/workflows/guide.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest defaults: run: - working-directory: guide/site/ + working-directory: guide/ steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 @@ -19,7 +19,7 @@ jobs: - name: Deploy to Netlify for preview uses: nwtgck/actions-netlify@v1.2.2 with: - publish-dir: './guide/site/public' + publish-dir: './guide/public' production-branch: devel github-token: ${{ secrets.GITHUB_TOKEN }} deploy-message: Deploy from GitHub Actions ${{ github.event.pull_request.title }} @@ -30,34 +30,3 @@ jobs: env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_GUIDE_SITE_ID }} - deploy-branch: - runs-on: ubuntu-latest - defaults: - run: - working-directory: guide/site/ - if: - contains(' - refs/heads/release- - ', github.ref) - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 - with: - node-version: 12.x - - name: Build the Guide - run: npm ci && npm run build - - name: Deploy to Netlify for preview - uses: nwtgck/actions-netlify@v1.2.2 - with: - publish-dir: './guide/site/public' - production-branch: devel - github-token: ${{ secrets.GITHUB_TOKEN }} - deploy-message: Deploy from GitHub Actions ${{ github.event.pull_request.title }} - netlify-config-path: './guide/netlify.toml' - alias: ${{ github.head_ref }} - enable-pull-request-comment: false - enable-commit-comment: false - env: - NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} - NETLIFY_SITE_ID: ${{ secrets.NETLIFY_GUIDE_SITE_ID }} - From ca50ef17e397569800261efd6e40a2089df969e7 Mon Sep 17 00:00:00 2001 From: Jan Dvorak Date: Wed, 27 Oct 2021 17:55:39 +0200 Subject: [PATCH 011/393] GitHub save more data retrieved from GitHub --- History.md | 3 +++ packages/github-oauth/github_server.js | 6 ++++++ packages/github-oauth/package.js | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index b63e454505..d3f49e010b 100644 --- a/History.md +++ b/History.md @@ -6,6 +6,9 @@ #### Meteor Version Release +* `github-oauth@1.4.0` + - More data will be retrieved and saved under `services.github` on the user account. + #### Independent Releases * `github-oauth@1.3.2` diff --git a/packages/github-oauth/github_server.js b/packages/github-oauth/github_server.js index 2991fdb0c9..b71995d1c0 100644 --- a/packages/github-oauth/github_server.js +++ b/packages/github-oauth/github_server.js @@ -15,6 +15,12 @@ OAuth.registerService('github', 2, null, (query) => { accessToken: OAuth.sealSecret(accessToken), email: identity.email || (primaryEmail && primaryEmail.email) || '', username: identity.login, + name: identity.name, + avatar: identity.avatar_url, + company: identity.company, + blog: identity.blog, + location: identity.location, + bio: identity.bio, emails }, options: { profile: { name: identity.name } } diff --git a/packages/github-oauth/package.js b/packages/github-oauth/package.js index 959a69a86b..de8e9415cb 100644 --- a/packages/github-oauth/package.js +++ b/packages/github-oauth/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: 'GitHub OAuth flow', - version: '1.3.2' + version: '1.4.0' }); Package.onUse(api => { From 2415d616484cc94140d59b30fab616cf46df8ecd Mon Sep 17 00:00:00 2001 From: Jan Dvorak Date: Wed, 27 Oct 2021 19:53:45 +0200 Subject: [PATCH 012/393] Add allow signup option to GitHub OAuth --- History.md | 1 + packages/github-oauth/github_client.js | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index d3f49e010b..b27678de9a 100644 --- a/History.md +++ b/History.md @@ -8,6 +8,7 @@ * `github-oauth@1.4.0` - More data will be retrieved and saved under `services.github` on the user account. + - Add option to disallow sign-up on GitHub using `allow_signup` [parameter](https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps#parameters), this will be activated based on your Accounts settings, specifically if the option `forbidClientAccountCreation` is set to `true`. #### Independent Releases diff --git a/packages/github-oauth/github_client.js b/packages/github-oauth/github_client.js index d51a44ce71..a085f39d8a 100644 --- a/packages/github-oauth/github_client.js +++ b/packages/github-oauth/github_client.js @@ -25,12 +25,17 @@ Github.requestCredential = (options, credentialRequestCompleteCallback) => { const loginStyle = OAuth._loginStyle('github', config, options); + let allowSignup = ''; + if (Accounts._options?.forbidClientAccountCreation) + allowSignup = '&allow_signup=false'; // https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps#parameters + const loginUrl = 'https://github.com/login/oauth/authorize' + `?client_id=${config.clientId}` + `&scope=${flatScope}` + `&redirect_uri=${OAuth._redirectUri('github', config)}` + - `&state=${OAuth._stateParam(loginStyle, credentialToken, options && options.redirectUrl)}`; + `&state=${OAuth._stateParam(loginStyle, credentialToken, options && options.redirectUrl)}` + + allowSignup; OAuth.launchLogin({ loginService: "github", From 0278f218bf62fafb7bc7c81f361720d85391a7ee Mon Sep 17 00:00:00 2001 From: Jan Dvorak Date: Fri, 12 Nov 2021 14:25:02 +0100 Subject: [PATCH 013/393] Use Facebook API v12 --- History.md | 3 +++ packages/facebook-oauth/facebook_client.js | 2 +- packages/facebook-oauth/facebook_server.js | 2 +- packages/facebook-oauth/package.js | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/History.md b/History.md index b63e454505..3e8b0be4f2 100644 --- a/History.md +++ b/History.md @@ -6,6 +6,9 @@ #### Meteor Version Release +* `facebook-oauth@1.11.0` + - Updated Facebook API to version 12.0 + #### Independent Releases * `github-oauth@1.3.2` diff --git a/packages/facebook-oauth/facebook_client.js b/packages/facebook-oauth/facebook_client.js index e6f78f320d..7c693574d1 100644 --- a/packages/facebook-oauth/facebook_client.js +++ b/packages/facebook-oauth/facebook_client.js @@ -30,7 +30,7 @@ Facebook.requestCredential = (options, credentialRequestCompleteCallback) => { const loginStyle = OAuth._loginStyle('facebook', config, options); - const API_VERSION = Meteor.settings?.public?.packages?.['facebook-oauth']?.apiVersion || '10.0'; + const API_VERSION = Meteor.settings?.public?.packages?.['facebook-oauth']?.apiVersion || '12.0'; let loginUrl = `https://www.facebook.com/v${API_VERSION}/dialog/oauth?client_id=${config.appId}` + diff --git a/packages/facebook-oauth/facebook_server.js b/packages/facebook-oauth/facebook_server.js index 1138cf7586..272e5f9cc0 100644 --- a/packages/facebook-oauth/facebook_server.js +++ b/packages/facebook-oauth/facebook_server.js @@ -2,7 +2,7 @@ Facebook = {}; import crypto from 'crypto'; import { Accounts } from 'meteor/accounts-base'; -const API_VERSION = Meteor.settings?.public?.packages?.['facebook-oauth']?.apiVersion || '10.0'; +const API_VERSION = Meteor.settings?.public?.packages?.['facebook-oauth']?.apiVersion || '12.0'; Facebook.handleAuthFromAccessToken = (accessToken, expiresAt) => { // include basic fields from facebook diff --git a/packages/facebook-oauth/package.js b/packages/facebook-oauth/package.js index 384d24e4b1..67536065cb 100644 --- a/packages/facebook-oauth/package.js +++ b/packages/facebook-oauth/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Facebook OAuth flow", - version: "1.10.0" + version: "1.11.0" }); Package.onUse(api => { From ddc01d04e0abe7bd1052473da4295bc80140827d Mon Sep 17 00:00:00 2001 From: Bruce Johnson Date: Tue, 23 Nov 2021 22:07:55 -0800 Subject: [PATCH 014/393] FIX firing onLogin twice --- packages/accounts-base/accounts_client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/accounts-base/accounts_client.js b/packages/accounts-base/accounts_client.js index 92ad0303dd..caacc2c50a 100644 --- a/packages/accounts-base/accounts_client.js +++ b/packages/accounts-base/accounts_client.js @@ -443,7 +443,7 @@ export class AccountsClient extends AccountsCommon { // before callbacks are registered see #10157 _startupCallback(callback) { // Are we already logged in? - if (this.connection._userId) { + if (Meteor.user()) { // If already logged in before handler is registered, it's safe to // assume type is a 'resume', so we execute the callback at the end // of the queue so that Meteor.startup can complete before any From e79b89a722d476081b0cc830a2c95362d94c0a0a Mon Sep 17 00:00:00 2001 From: Bruce Johnson Date: Wed, 24 Nov 2021 00:01:35 -0800 Subject: [PATCH 015/393] introduce loginCallbacks_called member variable --- packages/accounts-base/accounts_client.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/accounts-base/accounts_client.js b/packages/accounts-base/accounts_client.js index caacc2c50a..c48a093211 100644 --- a/packages/accounts-base/accounts_client.js +++ b/packages/accounts-base/accounts_client.js @@ -31,6 +31,10 @@ export class AccountsClient extends AccountsCommon { // This is for .registerClientLoginFunction & .callLoginFunction. this._loginFuncs = {}; + + // This tracks whether callbacks registered with + // Accounts.onLogin have been called + this.loginCallbacks_called = false; } /// @@ -217,10 +221,9 @@ export class AccountsClient extends AccountsCommon { }) // Prepare callbacks: user provided and onLogin/onLoginFailure hooks. - let called; const loginCallbacks = ({ error, loginDetails }) => { - if (!called) { - called = true; + if (!this.loginCallbacks_called) { + this.loginCallbacks_called = true; if (!error) { this._onLoginHook.each(callback => { callback(loginDetails); @@ -443,7 +446,7 @@ export class AccountsClient extends AccountsCommon { // before callbacks are registered see #10157 _startupCallback(callback) { // Are we already logged in? - if (Meteor.user()) { + if (this.loginCallbacks_called) { // If already logged in before handler is registered, it's safe to // assume type is a 'resume', so we execute the callback at the end // of the queue so that Meteor.startup can complete before any From f7f9588117bdaa84ff7e71fb6673bba97eb4b5b4 Mon Sep 17 00:00:00 2001 From: Bruce Johnson Date: Wed, 24 Nov 2021 00:16:02 -0800 Subject: [PATCH 016/393] Use internal member variable name convention begin with _ --- packages/accounts-base/accounts_client.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/accounts-base/accounts_client.js b/packages/accounts-base/accounts_client.js index c48a093211..aa30d4d233 100644 --- a/packages/accounts-base/accounts_client.js +++ b/packages/accounts-base/accounts_client.js @@ -34,7 +34,7 @@ export class AccountsClient extends AccountsCommon { // This tracks whether callbacks registered with // Accounts.onLogin have been called - this.loginCallbacks_called = false; + this._loginCallbacks_called = false; } /// @@ -222,8 +222,8 @@ export class AccountsClient extends AccountsCommon { // Prepare callbacks: user provided and onLogin/onLoginFailure hooks. const loginCallbacks = ({ error, loginDetails }) => { - if (!this.loginCallbacks_called) { - this.loginCallbacks_called = true; + if (!this._loginCallbacks_called) { + this._loginCallbacks_called = true; if (!error) { this._onLoginHook.each(callback => { callback(loginDetails); @@ -446,7 +446,7 @@ export class AccountsClient extends AccountsCommon { // before callbacks are registered see #10157 _startupCallback(callback) { // Are we already logged in? - if (this.loginCallbacks_called) { + if (this._loginCallbacks_called) { // If already logged in before handler is registered, it's safe to // assume type is a 'resume', so we execute the callback at the end // of the queue so that Meteor.startup can complete before any From cafeaec8bdc4f046e73e4ed65731062c156f2106 Mon Sep 17 00:00:00 2001 From: Bruce Johnson Date: Wed, 24 Nov 2021 00:16:02 -0800 Subject: [PATCH 017/393] Use internal member variable name convention begin with _ --- packages/accounts-base/accounts_client.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/accounts-base/accounts_client.js b/packages/accounts-base/accounts_client.js index c48a093211..aa30d4d233 100644 --- a/packages/accounts-base/accounts_client.js +++ b/packages/accounts-base/accounts_client.js @@ -34,7 +34,7 @@ export class AccountsClient extends AccountsCommon { // This tracks whether callbacks registered with // Accounts.onLogin have been called - this.loginCallbacks_called = false; + this._loginCallbacks_called = false; } /// @@ -222,8 +222,8 @@ export class AccountsClient extends AccountsCommon { // Prepare callbacks: user provided and onLogin/onLoginFailure hooks. const loginCallbacks = ({ error, loginDetails }) => { - if (!this.loginCallbacks_called) { - this.loginCallbacks_called = true; + if (!this._loginCallbacks_called) { + this._loginCallbacks_called = true; if (!error) { this._onLoginHook.each(callback => { callback(loginDetails); @@ -446,7 +446,7 @@ export class AccountsClient extends AccountsCommon { // before callbacks are registered see #10157 _startupCallback(callback) { // Are we already logged in? - if (this.loginCallbacks_called) { + if (this._loginCallbacks_called) { // If already logged in before handler is registered, it's safe to // assume type is a 'resume', so we execute the callback at the end // of the queue so that Meteor.startup can complete before any From 691fe801ece195836ae568d7d0a54bf20df3d865 Mon Sep 17 00:00:00 2001 From: Bruce Johnson Date: Tue, 30 Nov 2021 17:49:56 -0800 Subject: [PATCH 018/393] _loginCallbacks_called=true after callbacks when logged in --- packages/accounts-base/accounts_client.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/accounts-base/accounts_client.js b/packages/accounts-base/accounts_client.js index aa30d4d233..da0d9eecfa 100644 --- a/packages/accounts-base/accounts_client.js +++ b/packages/accounts-base/accounts_client.js @@ -34,6 +34,8 @@ export class AccountsClient extends AccountsCommon { // This tracks whether callbacks registered with // Accounts.onLogin have been called + // Only set to true when logged in after callbacks + // have been called this._loginCallbacks_called = false; } @@ -123,6 +125,7 @@ export class AccountsClient extends AccountsCommon { wait: true }, (error, result) => { this._loggingOut.set(false); + this._loginCallbacks_called = false; if (error) { callback && callback(error); } else { @@ -220,6 +223,7 @@ export class AccountsClient extends AccountsCommon { options[f] = () => null; }) + this._loginCallbacks_called = false; // Prepare callbacks: user provided and onLogin/onLoginFailure hooks. const loginCallbacks = ({ error, loginDetails }) => { if (!this._loginCallbacks_called) { From ce2f34a5e872d6186ea93a0454c969935da1c29e Mon Sep 17 00:00:00 2001 From: Bruce Johnson Date: Wed, 1 Dec 2021 20:38:46 -0800 Subject: [PATCH 019/393] Need to go back to local variable to determine whether to run loginCallbacks, cannot use a shared variable. --- packages/accounts-base/accounts_client.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/accounts-base/accounts_client.js b/packages/accounts-base/accounts_client.js index da0d9eecfa..c8aad1ef81 100644 --- a/packages/accounts-base/accounts_client.js +++ b/packages/accounts-base/accounts_client.js @@ -34,8 +34,6 @@ export class AccountsClient extends AccountsCommon { // This tracks whether callbacks registered with // Accounts.onLogin have been called - // Only set to true when logged in after callbacks - // have been called this._loginCallbacks_called = false; } @@ -223,10 +221,11 @@ export class AccountsClient extends AccountsCommon { options[f] = () => null; }) - this._loginCallbacks_called = false; + let called; // Prepare callbacks: user provided and onLogin/onLoginFailure hooks. const loginCallbacks = ({ error, loginDetails }) => { - if (!this._loginCallbacks_called) { + if (!called) { + called = true; this._loginCallbacks_called = true; if (!error) { this._onLoginHook.each(callback => { From 77fa50ebb185d09bed537e63cf171d561721d9e0 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Mon, 6 Dec 2021 01:12:53 -0300 Subject: [PATCH 020/393] Change how we process the splash images for cordova (fixes #11555): - Change splash keys to enable storyboard images for iOS; change splash values for android. - Allow user to pass dark mode images for both android and iOS. - Updating some code around launchScreens. --- tools/cordova/builder.js | 192 ++++++++++++++++++++++++--------------- 1 file changed, 119 insertions(+), 73 deletions(-) diff --git a/tools/cordova/builder.js b/tools/cordova/builder.js index 765da00f50..11a429ebf7 100644 --- a/tools/cordova/builder.js +++ b/tools/cordova/builder.js @@ -50,33 +50,30 @@ const iconsAndroidSizes = { 'android_xxxhdpi': '192x192' }; -const launchIosSizes = { - 'iphone5': '640x1136', - 'iphone6': '750x1334', - 'iphone6p_portrait': '1242x2208', - 'iphone6p_landscape': '2208x1242', - 'iphoneX_portrait': '1125x2436', - 'iphoneX_landscape': '2436x1125', - 'ipad_portrait_2x': '1536x2048', - 'ipad_landscape_2x': '2048x1536', - // Legacy - 'iphone': '320x480', - 'iphone_2x': '640x960', - 'ipad_portrait': '768x1024', - 'ipad_landscape': '1024x768' +const splashIosKeys = { + 'ios_universal': 'Default@2x~universal~anyany.png', + 'ios_universal_3x': 'Default@3x~universal~anyany.png', + 'Default@2x~iphone~anyany': 'Default@2x~iphone~anyany.png', + 'Default@2x~iphone~comany': 'Default@2x~iphone~comany.png', + 'Default@2x~iphone~comcom': 'Default@2x~iphone~comcom.png', + 'Default@3x~iphone~anyanyg': 'Default@3x~iphone~anyanyg.png', + 'Default@3x~iphone~anycom': 'Default@3x~iphone~anycom.png', + 'Default@3x~iphone~comany': 'Default@3x~iphone~comany.png', + 'Default@2x~ipad~anyany': 'Default@2x~ipad~anyany.png', + 'Default@2x~ipad~comany': 'Default@2x~ipad~comany.png' }; -const launchAndroidSizes = { - 'android_mdpi_portrait': '320x480', - 'android_mdpi_landscape': '480x320', - 'android_hdpi_portrait': '480x800', - 'android_hdpi_landscape': '800x480', - 'android_xhdpi_portrait': '720x1280', - 'android_xhdpi_landscape': '1280x720', - 'android_xxhdpi_portrait': '960x1600', - 'android_xxhdpi_landscape': '1600x960', - 'android_xxxhdpi_portrait': '1280x1920', - 'android_xxxhdpi_landscape': '1920x1280' +const splashAndroidKeys = { + 'android_mdpi_portrait': 'port-mdpi', + 'android_mdpi_landscape': 'land-mdpi', + 'android_hdpi_portrait': 'port-hdpi', + 'android_hdpi_landscape': 'land-hdpi', + 'android_xhdpi_portrait': 'port-xhdpi', + 'android_xhdpi_landscape': 'land-xhdpi', + 'android_xxhdpi_portrait': 'port-xxhdpi', + 'android_xxhdpi_landscape': 'land-xxhdpi', + 'android_xxxhdpi_portrait': 'port-xxxhdpi', + 'android_xxxhdpi_landscape': 'land-xxxhdpi' }; export class CordovaBuilder { @@ -167,7 +164,7 @@ export class CordovaBuilder { } // Default access rules. - // Rules can be extended with App.accesRule() in mobile-config.js. + // Rules can be extended with App.accessRule() in mobile-config.js. this.accessRules = { // Allow the app to ask the system to open these types of URLs. // (e.g. in the phone app or an email client) @@ -233,8 +230,9 @@ export class CordovaBuilder { _.each(iconsIosSizes, setDefaultIcon); _.each(iconsAndroidSizes, setDefaultIcon); - _.each(launchIosSizes, setDefaultLaunchScreen); - _.each(launchAndroidSizes, setDefaultLaunchScreen); + //TODO -> Fix default. + _.each(splashIosKeys, setDefaultLaunchScreen); + _.each(splashAndroidKeys, setDefaultLaunchScreen); this.pluginsConfiguration = {}; } @@ -348,10 +346,10 @@ export class CordovaBuilder { Console.debug('Copying resources for mobile apps'); - this.configureAndCopyImages(iconsIosSizes, platformElement.ios, 'icon'); - this.configureAndCopyImages(iconsAndroidSizes, platformElement.android, 'icon'); - this.configureAndCopyImages(launchIosSizes, platformElement.ios, 'splash'); - this.configureAndCopyImages(launchAndroidSizes, platformElement.android, 'splash'); + this._configureAndCopyIcon(iconsIosSizes, platformElement.ios); + this._configureAndCopyIcon(iconsAndroidSizes, platformElement.android); + this._configureAndCopySplashImages(splashIosKeys, platformElement.ios); + this._configureAndCopySplashImages(splashAndroidKeys, platformElement.android); } this.configureAndCopyResourceFiles( @@ -367,52 +365,100 @@ export class CordovaBuilder { files.writeFile(configXmlPath, formattedXmlConfig, 'utf8'); } - configureAndCopyImages(sizes, xmlElement, tag) { - const imageAttributes = (name, width, height, src) => { - const androidMatch = /android_(.?.dpi)_(landscape|portrait)/g.exec(name); + _copyImageToBuildFolderAndAppendToXmlNode(suppliedPath, newFilename, xmlElement, tag, attributes = {}) { + const src = files.pathJoin('resources', newFilename); - let attributes = { - src: src, - width: width, - height: height - }; + files.copyFile( + files.pathResolve(this.projectContext.projectDir, suppliedPath), + files.pathJoin(this.resourcesPath, newFilename)); - // XXX special case for Android - if (androidMatch) { - attributes.density = - androidMatch[2].substr(0, 4) + '-' + androidMatch[1]; + // Set it to the xml tree + xmlElement.ele(tag, { src, ...attributes }); + } + + _resolveFilenameForImages = (suppliedPath, key, tag) => { + const suppliedFilename = _.last(suppliedPath.split(files.pathSep)); + let extension = _.last(suppliedFilename.split('.')); + + // XXX special case for 9-patch png's + if (suppliedFilename.match(/\.9\.png$/)) { + extension = '9.png'; + } + + return `${key}.${tag}.${extension}`; + } + + _configureAndCopyIcon(sizes, xmlElement) { + if (!sizes || !xmlElement) { + throw new Error("Invalid parameters") + } + + Object.entries(sizes).forEach(([key, size]) => { + const suppliedPath = this.imagePaths['icon'][key]; + if (!suppliedPath) return; + + const [width, height] = size.split('x'); + const filename = this._resolveFilenameForImages(suppliedPath, key, 'icon'); + this._copyImageToBuildFolderAndAppendToXmlNode(suppliedPath, filename, xmlElement, 'icon', { width, height }) + }) + } + + _configureAndCopySplashImages(allowedValues, xmlElement) { + const isIos = xmlElement.name === 'ios'; + const appendDarkMode = (stringValue , { separator = '.', withChar = '~' } = {}) => { + if (!stringValue) { + throw new Error("No string was passed."); } - return attributes; - }; + const darkModeIdentifier = isIos ? 'dark' : 'night'; + const lastIndexOfSeparator = stringValue.lastIndexOf(separator); - _.each(sizes, (size, name) => { - const [width, height] = size.split('x'); + if (!lastIndexOfSeparator) { + throw new Error("Invalid src value!"); + } - const suppliedPath = this.imagePaths[tag][name]; - if (!suppliedPath) { + return stringValue.substring(0, lastIndexOfSeparator) + withChar + darkModeIdentifier + stringValue.substring(lastIndexOfSeparator); + } + + Object.entries(allowedValues).forEach(([key, value]) => { + const suppliedValue = this.imagePaths['splash'][key]; + if (!suppliedValue) return; + + let suppliedPath = suppliedValue; + let suppliedPathDarkMode = null; + if (typeof suppliedValue === 'object') { + suppliedPath = suppliedValue.src; + suppliedPathDarkMode = suppliedValue.srcDarkMode; + } + + if (isIos) { + if (suppliedPathDarkMode) { + this._copyImageToBuildFolderAndAppendToXmlNode(suppliedPathDarkMode, + appendDarkMode(value), + xmlElement, + 'splash'); + } + this._copyImageToBuildFolderAndAppendToXmlNode(suppliedPath, + value, + xmlElement, + 'splash'); return; } - const suppliedFilename = _.last(suppliedPath.split(files.pathSep)); - let extension = _.last(suppliedFilename.split('.')); - - // XXX special case for 9-patch png's - if (suppliedFilename.match(/\.9\.png$/)) { - extension = '9.png'; + const filename = this._resolveFilenameForImages(suppliedPath, key, 'splash'); + if (suppliedPathDarkMode) { + this._copyImageToBuildFolderAndAppendToXmlNode(suppliedPathDarkMode, + appendDarkMode(filename, { withChar: '_' }), + xmlElement, 'splash', + { density: appendDarkMode(value, { separator: '-', withChar: '-' })} + ); } - - const filename = name + '.' + tag + '.' + extension; - const src = files.pathJoin('resources', filename); - - // Copy the file to the build folder with a standardized name - files.copyFile( - files.pathResolve(this.projectContext.projectDir, suppliedPath), - files.pathJoin(this.resourcesPath, filename)); - - // Set it to the xml tree - xmlElement.ele(tag, imageAttributes(name, width, height, src)); - }); + this._copyImageToBuildFolderAndAppendToXmlNode(suppliedPath, + filename, + xmlElement, + 'splash', + { density: value }); + }) } configureAndCopyResourceFiles(resourceFiles, iosElement, androidElement) { @@ -742,15 +788,15 @@ configuration. The key may be deprecated.`); * @memberOf App */ launchScreens: function (launchScreens) { - var validDevices = - Object.keys(launchIosSizes).concat(Object.keys(launchAndroidSizes)); + const validDevices = + Object.keys(splashIosKeys).concat(Object.keys(splashAndroidKeys)); - _.each(launchScreens, function (value, key) { - if (!_.include(validDevices, key)) { + Object.keys(launchScreens).forEach((key) => { + if (!key in validDevices) { Console.labelWarn(`${key}: unknown key in App.launchScreens \ configuration. The key may be deprecated.`); } - }); + }) Object.assign(builder.imagePaths.splash, launchScreens); }, From 931a31c74f851bc81a6e0c4e515b0f191610c468 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Mon, 6 Dec 2021 01:29:35 -0300 Subject: [PATCH 021/393] Change how we process the splash images for cordova (fixes #11555): - Set correctly default images for launch/splash screen (ios_universal and android_mdpi_portrait) - Remove unused graphics. --- tools/cordova/assets/launchscreens/1024x768.png | Bin 12093 -> 0 bytes .../cordova/assets/launchscreens/1080x1440.png | Bin 19014 -> 0 bytes .../cordova/assets/launchscreens/1242x2208.png | Bin 38390 -> 0 bytes .../cordova/assets/launchscreens/1440x1080.png | Bin 19493 -> 0 bytes .../cordova/assets/launchscreens/1536x2048.png | Bin 31521 -> 0 bytes .../cordova/assets/launchscreens/2048x1536.png | Bin 31120 -> 0 bytes .../cordova/assets/launchscreens/2208x1242.png | Bin 40421 -> 0 bytes tools/cordova/assets/launchscreens/320x470.png | Bin 7493 -> 0 bytes tools/cordova/assets/launchscreens/470x320.png | Bin 7259 -> 0 bytes tools/cordova/assets/launchscreens/480x640.png | Bin 11442 -> 0 bytes tools/cordova/assets/launchscreens/640x1136.png | Bin 17156 -> 0 bytes tools/cordova/assets/launchscreens/640x480.png | Bin 11332 -> 0 bytes tools/cordova/assets/launchscreens/640x960.png | Bin 16415 -> 0 bytes tools/cordova/assets/launchscreens/720x960.png | Bin 13333 -> 0 bytes tools/cordova/assets/launchscreens/750x1334.png | Bin 20952 -> 0 bytes tools/cordova/assets/launchscreens/768x1024.png | Bin 12346 -> 0 bytes tools/cordova/assets/launchscreens/960x720.png | Bin 12918 -> 0 bytes .../{320x480.png => android_mdpi_portrait.png} | Bin .../assets/launchscreens/ios_universal.png | Bin 0 -> 63225 bytes tools/cordova/builder.js | 12 ++++++------ 20 files changed, 6 insertions(+), 6 deletions(-) delete mode 100644 tools/cordova/assets/launchscreens/1024x768.png delete mode 100644 tools/cordova/assets/launchscreens/1080x1440.png delete mode 100644 tools/cordova/assets/launchscreens/1242x2208.png delete mode 100644 tools/cordova/assets/launchscreens/1440x1080.png delete mode 100644 tools/cordova/assets/launchscreens/1536x2048.png delete mode 100644 tools/cordova/assets/launchscreens/2048x1536.png delete mode 100644 tools/cordova/assets/launchscreens/2208x1242.png delete mode 100644 tools/cordova/assets/launchscreens/320x470.png delete mode 100644 tools/cordova/assets/launchscreens/470x320.png delete mode 100644 tools/cordova/assets/launchscreens/480x640.png delete mode 100644 tools/cordova/assets/launchscreens/640x1136.png delete mode 100644 tools/cordova/assets/launchscreens/640x480.png delete mode 100644 tools/cordova/assets/launchscreens/640x960.png delete mode 100644 tools/cordova/assets/launchscreens/720x960.png delete mode 100644 tools/cordova/assets/launchscreens/750x1334.png delete mode 100644 tools/cordova/assets/launchscreens/768x1024.png delete mode 100644 tools/cordova/assets/launchscreens/960x720.png rename tools/cordova/assets/launchscreens/{320x480.png => android_mdpi_portrait.png} (100%) create mode 100644 tools/cordova/assets/launchscreens/ios_universal.png diff --git a/tools/cordova/assets/launchscreens/1024x768.png b/tools/cordova/assets/launchscreens/1024x768.png deleted file mode 100644 index fbfcbb20e2e22b56d8fe17d75e5258dee7e08e5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12093 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 diff --git a/tools/cordova/assets/launchscreens/1080x1440.png b/tools/cordova/assets/launchscreens/1080x1440.png deleted file mode 100644 index 9de5882760cffdb6722094e7f04dbd6f521ae4a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19014 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 diff --git a/tools/cordova/assets/launchscreens/1440x1080.png b/tools/cordova/assets/launchscreens/1440x1080.png deleted file mode 100644 index 9afe79a69ca6d31e88638d29534aa1a1939204a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19493 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

PATH management

+ +By default, the Meteor installer adds its install path (by default, `~/.meteor/`) to your PATH by updating either your `.bashrc`, `.bash_profile`, or `.zshrc` as appropriate. To disable this behavior, install Meteor by running: + +```bash +npm install -g meteor --ignore-meteor-setup-exec-path +``` + +(or by setting the environment variable `npm_config_ignore_meteor_setup_exec_path=true`) +

Old Versions on Apple M1

For Apple M1 computers, you can append Rosetta prefix as following, if you need to run older versions of Meteor (before 2.5.1): diff --git a/npm-packages/meteor-installer/README.md b/npm-packages/meteor-installer/README.md index 466053de88..16bdce19fd 100644 --- a/npm-packages/meteor-installer/README.md +++ b/npm-packages/meteor-installer/README.md @@ -27,3 +27,13 @@ npm install -g meteor ### Important note This npm package is not Meteor itself, this npm package is just an installer. You should not include it as a dependency in your project. If you do your deploy is going to be broken. + +### Path management + +By default, the Meteor installer adds its install path (by default, `~/.meteor/`) to your PATH by updating either your `.bashrc`, `.bash_profile`, or `.zshrc` as appropriate. To disable this behavior, install Meteor by running: + +```bash +npm install -g meteor --ignore-meteor-setup-exec-path +``` + +(or by setting the environment variable `npm_config_ignore_meteor_setup_exec_path=true`) \ No newline at end of file diff --git a/npm-packages/meteor-installer/config.js b/npm-packages/meteor-installer/config.js index 10cf679463..b338daec96 100644 --- a/npm-packages/meteor-installer/config.js +++ b/npm-packages/meteor-installer/config.js @@ -31,6 +31,10 @@ if (isWindows() && !localAppData) { throw new Error('LOCALAPPDATA env var is not set.'); } +const shouldSetupExecPath = () => { + return !process.env.npm_config_ignore_meteor_setup_exec_path; +} + const meteorLocalFolder = '.meteor'; const meteorPath = path.resolve(rootPath, meteorLocalFolder); @@ -45,5 +49,6 @@ module.exports = { isWindows, isMac, isRoot, - isSudo + isSudo, + shouldSetupExecPath, }; diff --git a/npm-packages/meteor-installer/install.js b/npm-packages/meteor-installer/install.js index fc48fc85d3..810c764a47 100644 --- a/npm-packages/meteor-installer/install.js +++ b/npm-packages/meteor-installer/install.js @@ -19,6 +19,7 @@ const { isSudo, isMac, METEOR_LATEST_VERSION, + shouldSetupExecPath, } = require('./config.js'); const { uninstall } = require('./uninstall'); const { @@ -246,7 +247,10 @@ async function extract() { } async function setup() { fs.unlinkSync(startedPath); - await setupExecPath(); + if (shouldSetupExecPath()) { + await setupExecPath(); + } + await fixOwnership(); showGettingStarted(); } async function setupExecPath() { @@ -267,8 +271,9 @@ async function setupExecPath() { await appendPathToFile('.bashrc'); await appendPathToFile('.bash_profile'); } - - if (isSudo()) { +} +async function fixOwnership() { + if (!isWindows() && isSudo()) { // if we identified sudo is being used, we need to change the ownership of the meteorpath folder child_process.execSync(`chown -R ${sudoUser} "${meteorPath}"`); } From a35b3e78d1bce68af6e624fae5b9e41666301c8b Mon Sep 17 00:00:00 2001 From: Harry Adel Date: Mon, 20 Dec 2021 13:42:37 +0200 Subject: [PATCH 029/393] Add OWASP cheet sheet for Node.js --- guide/source/security.md | 1 + 1 file changed, 1 insertion(+) diff --git a/guide/source/security.md b/guide/source/security.md index 81dbb96ce3..7c2ea0364c 100644 --- a/guide/source/security.md +++ b/guide/source/security.md @@ -695,6 +695,7 @@ This is a collection of points to check about your app that might catch common e 1. Secure the data, not the UI - redirecting away from a client-side route does nothing for security, it's a nice UX feature. 1. [Don't ever trust user IDs passed from the client.](http://guide.meteor.com/security.html#user-id-client) Use `this.userId` inside Methods and publications. 1. Set up secure [HTTP headers](https://guide.meteor.com/security.html#httpheaders) using [Helmet](https://www.npmjs.com/package/helmet), but know that not all browsers support it so it provides an extra layer of security to users with modern browsers. +1. At the end of the day, Meteor is nothing but a Node.js app so make sure to also follow the [best practises](https://cheatsheetseries.owasp.org/cheatsheets/Nodejs_Security_Cheat_Sheet.html) to ensure maximum security.

App Protection

App Protection on Galaxy Hosting is a feature in our proxy server layer that sits in front of every request to your application. This means that all requests across servers are analyzed and measured against expected limits. This will help protect against DoS and DDoS attacks that aimed to overload servers and make your app unavailable for legitimate requests. From 73a2d0078eed32cd507940c33f145c955f169fe9 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Mon, 20 Dec 2021 13:47:21 -0300 Subject: [PATCH 030/393] Change how we process the splash images for cordova (fixes #11555): - Improving documentation. - Added universal mode keys. --- History.md | 7 +++- docs/source/api/mobile-config.md | 36 ++++++++++++++++++-- tools/cordova/builder.js | 56 +++++++++++++++++++------------- 3 files changed, 73 insertions(+), 26 deletions(-) diff --git a/History.md b/History.md index 9ce556c798..1f260ed75e 100644 --- a/History.md +++ b/History.md @@ -1,11 +1,16 @@ ## vNext, UNRELEASED #### Highlights - +* You are now able to use dark theme specific splash screens for both iOS and Android by passing an object `{src: 'light-image-src-here.png', srcDarkMode: 'dark-mode-src-here.png'}` to the corresponding key in `App.launchScreens` #### Breaking Changes * Legacy launch screens keys for iOS on `App.launchScreens` are now deprecated in favor of new storyboard compliant keys [PR #11797](https://github.com/meteor/meteor/pull/11797). + - This will drop the following keys we have: `['iphone5','iphone6','iphone6p_portrait','iphone6p_landscape','iphoneX_portrait','iphoneX_landscape','ipad_portrait_2x','ipad_landscape_2x','iphone','iphone_2x','ipad_portrait','ipad_landscape']` +#### Migration Steps +* Replace the deprecated keys `['iphone5','iphone6','iphone6p_portrait','iphone6p_landscape','iphoneX_portrait','iphoneX_landscape','ipad_portrait_2x','ipad_landscape_2x','iphone','iphone_2x','ipad_portrait','ipad_landscape']` with the +corresponding new key: `['ios_universal','ios_universal_3x','Default@2x~universal~comany','Default@2x~universal~comcom','Default@3x~universal~anycom','Default@3x~universal~comany','Default@2x~iphone~anyany','Default@2x~iphone~comany','Default@2x~iphone~comcom','Default@3x~iphone~anyany','Default@3x~iphone~anycom','Default@3x~iphone~comany','Default@2x~ipad~anyany','Default@2x~ipad~comany']` +and adapt necessary splash images to the new dimensions asked by Apple. You can get more info [here](https://docs.meteor.com/api/mobile-config.html#App-launchScreens). #### Meteor Version Release #### Independent Releases diff --git a/docs/source/api/mobile-config.md b/docs/source/api/mobile-config.md index 8d69412956..c32b33791a 100644 --- a/docs/source/api/mobile-config.md +++ b/docs/source/api/mobile-config.md @@ -30,10 +30,40 @@ App.icons({ // More screen sizes and platforms... }); +// Before Meteor 2.6 we had to pass device specific splash screens for iOS, but this behavior was dropped in favor of story board images. App.launchScreens({ - 'ios_universal': { src: 'splash/Default@2x.png', srcDarkMode: 'splash/Default@2x~dark.png' }, - 'ios_universal_3x': 'splash/Default@3x.png', - // More screen sizes and platforms... + // iOS + // For most cases you will only need to use the 'ios_universal' and 'ios_universal_3x'. + 'ios_universal': { src: 'splash/Default@2x.png', srcDarkMode: 'splash/Default@2x~dark.png' }, // (2732x2732) - All @2x devices, if device/mode specific is not declared + 'ios_universal_3x': 'splash/Default@3x.png', // (2208x2208) - All @3x devices, if device/mode specific is not declared + + // If you still want to use a universal splash, but want to fine-tune for the device mode (landscape, portrait), then use the following keys: + 'Default@2x~universal~comany': 'splash/Default@2x~universal~comany.png', // (1278x2732) - All @2x devices in portrait mode. + 'Default@2x~universal~comcom': 'splash/Default@2x~universal~comcom.png', // (1334x750) - All @2x devices in landscape (narrow) mode. + 'Default@3x~universal~anycom': 'splash/Default@3x~universal~anycom.png', // (2208x1242) - All @3x devices in landscape (wide) mode. + 'Default@3x~universal~comany': 'splash/Default@3x~universal~comany.png', // (1242x2208) - All @3x devices in portrait mode. + + // However, if you need to fine tune the splash screens for the device idiom (iPhone, iPad, etc). + 'Default@2x~iphone~anyany': 'splash/Default@2xiphoneanyany.png', // (1334x1334) - iPhone SE/6s/7/8/XR + 'Default@2x~iphone~comany': 'splash/Default@2xiphonecomany.png', // (750x1334) - iPhone SE/6s/7/8/XR - portrait mode + 'Default@2x~iphone~comcom': 'splash/Default@2xiphonecomcom.png', // (1334x750) - iPhone SE/6s/7/8/XR - landscape (narrow) mode + 'Default@3x~iphone~anyany': 'Default@3xiphoneanyany.png', // (2208x2208) - iPhone 6s Plus/7 Plus/8 Plus/X/XS/XS Max + 'Default@3x~iphone~anycom': { src: 'splash/Default@3xiphoneanycom.png', srcDarkMode: 'splash/Default@3xiphoneanycom~dark.png' }, // (2208x1242) - iPhone 6s Plus/7 Plus/8 Plus/X/XS/XS Max - landscape (wide) mode + 'Default@3x~iphone~comany': 'Default@3xiphonecomany.png', // (1242x2208) - iPhone 6s Plus/7 Plus/8 Plus/X/XS/XS Max - portrait mode + 'Default@2x~ipad~anyany': 'Default@2xipadanyany.png', // (2732x2732) - iPad Pro 12.9"/11"/10.5"/9.7"/7.9" + 'Default@2x~ipad~comany': 'Default@2xipadcomany.png', // (1278x2732) - iPad Pro 12.9"/11"/10.5"/9.7"/7.9" - portrait mode + + // Android + 'android_mdpi_portrait': 'splash/android_mdpi_portrait.png', // (320x480) + 'android_mdpi_landscape': { src: 'splash/android_mdpi_landscape.png', srcDarkMode: 'splash/android_mdpi_landscape-night.png' }, // (480x320) + 'android_hdpi_portrait': 'splash/android_hdpi_portrait.png', // (480x800) + 'android_hdpi_landscape': 'splash/android_hdpi_landscape.png', // (800x480) + 'android_xhdpi_portrait': 'splash/android_xhdpi_portrait.png', // (720x1280) + 'android_xhdpi_landscape': 'splash/android_xhdpi_landscape.png', // (1280x720) + 'android_xxhdpi_portrait': { src: 'splash/android_xxhdpi_portrait.png', srcDarkMode: 'splash/android_xxhdpi_portrait-night.png'}, // (960x1600) + 'android_xxhdpi_landscape': 'splash/android_xxhdpi_landscape.png', // (1600x960) + 'android_xxxhdpi_portrait': 'splash/android_xxxhdpi_portrait.png', // (1280x1920) + 'android_xxxhdpi_landscape': 'splash/android_xxxhdpi_landscape.png', // (1920x1280) }); // Set PhoneGap/Cordova preferences. diff --git a/tools/cordova/builder.js b/tools/cordova/builder.js index 87e18f1ff1..f002bdc095 100644 --- a/tools/cordova/builder.js +++ b/tools/cordova/builder.js @@ -53,10 +53,14 @@ const iconsAndroidSizes = { const splashIosKeys = { 'ios_universal': 'Default@2x~universal~anyany.png', 'ios_universal_3x': 'Default@3x~universal~anyany.png', + 'Default@2x~universal~comany': 'Default@2x~universal~comany.png', + 'Default@2x~universal~comcom': 'Default@2x~universal~comcom.png', + 'Default@3x~universal~anycom': 'Default@3x~universal~anycom.png', + 'Default@3x~universal~comany': 'Default@3x~universal~comany.png', 'Default@2x~iphone~anyany': 'Default@2x~iphone~anyany.png', 'Default@2x~iphone~comany': 'Default@2x~iphone~comany.png', 'Default@2x~iphone~comcom': 'Default@2x~iphone~comcom.png', - 'Default@3x~iphone~anyanyg': 'Default@3x~iphone~anyanyg.png', + 'Default@3x~iphone~anyany': 'Default@3x~iphone~anyany.png', 'Default@3x~iphone~anycom': 'Default@3x~iphone~anycom.png', 'Default@3x~iphone~comany': 'Default@3x~iphone~comany.png', 'Default@2x~ipad~anyany': 'Default@2x~ipad~anyany.png', @@ -761,29 +765,37 @@ configuration. The key may be deprecated.`); * stretched. See the [Android docs](https://developer.android.com/guide/topics/graphics/2d-graphics.html#nine-patch). * * For best practices when developing a splash image, see the [Apple Guidelines](https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/launch-screen/). - * + * To learn more about size classes for iOS, check out the [documentation](https://cordova.apache.org/docs/en/10.x/reference/cordova-plugin-splashscreen/#size-classes) from Cordova. * * Valid key values: - * - `ios_universal` (Default@2xuniversalanyany.png - 2732x2732) - All @2x devices, if device specific is not declared. - * - `ios_universal_3x` (Default@3xuniversalanyany.png - 2208x2208) - All @3x devices, if device specific is not declared. - * - `Default@2xiphoneanyany` (1334x1334) - iPhone SE/6s/7/8/XR - * - `Default@2xiphonecomany` (750x1334) - iPhone SE/6s/7/8/XR - * - `Default@2xiphonecomcom` (1334x750) - iPhone SE/6s/7/8/XR - * - `Default@3xiphoneanyanyg` (2208x2208) - iPhone 6s Plus/7 Plus/8 Plus/X/XS/XS Max - * - `Default@3xiphoneanycom` (2208x1242) - iPhone 6s Plus/7 Plus/8 Plus/X/XS/XS Max - * - `Default@3xiphonecomany` (1242x2208) - iPhone 6s Plus/7 Plus/8 Plus/X/XS/XS Max - * - `Default@2xipadanyany` (2732x2732) - iPad Pro 12.9"/11"/10.5"/9.7"/7.9" - * - `Default@2xipadcomany` (1278x2732) - iPad Pro 12.9"/11"/10.5"/9.7"/7.9" - * - `android_mdpi_portrait` (320x480) - * - `android_mdpi_landscape` (480x320) - * - `android_hdpi_portrait` (480x800) - * - `android_hdpi_landscape` (800x480) - * - `android_xhdpi_portrait` (720x1280) - * - `android_xhdpi_landscape` (1280x720) - * - `android_xxhdpi_portrait` (960x1600) - * - `android_xxhdpi_landscape` (1600x960) - * - `android_xxxhdpi_portrait` (1280x1920) - * - `android_xxxhdpi_landscape` (1920x1280) + * + * iOS: + * - `ios_universal` (Default@2xuniversalanyany.png - 2732x2732) - All @2x devices, if device/mode specific is not declared + * - `ios_universal_3x` (Default@3xuniversalanyany.png - 2208x2208) - All @3x devices, if device/mode specific is not declared + * - `Default@2x~universal~comany` (1278x2732) - All @2x devices in portrait mode + * - `Default@2x~universal~comcom` (1334x750) - All @2x devices in landscape (narrow) mode + * - `Default@3x~universal~anycom` (2208x1242) - All @3x devices in landscape (wide) mode + * - `Default@3x~universal~comany` (1242x2208) - All @3x devices in portrait mode + * - `Default@2x~iphone~anyany` (1334x1334) - iPhone SE/6s/7/8/XR + * - `Default@2x~iphone~comany` (750x1334) - iPhone SE/6s/7/8/XR - portrait mode + * - `Default@2x~iphone~comcom` (1334x750) - iPhone SE/6s/7/8/XR - landscape (narrow) mode + * - `Default@3x~iphone~anyany` (2208x2208) - iPhone 6s Plus/7 Plus/8 Plus/X/XS/XS Max + * - `Default@3x~iphone~anycom` (2208x1242) - iPhone 6s Plus/7 Plus/8 Plus/X/XS/XS Max - landscape (wide) mode + * - `Default@3x~iphone~comany` (1242x2208) - iPhone 6s Plus/7 Plus/8 Plus/X/XS/XS Max - portrait mode + * - `Default@2x~ipad~anyany` (2732x2732) - iPad Pro 12.9"/11"/10.5"/9.7"/7.9" + * - `Default@2x~ipad~comany` (1278x2732) - iPad Pro 12.9"/11"/10.5"/9.7"/7.9" - portrait mode + * + * Android: + * - `android_mdpi_portrait` (320x480) + * - `android_mdpi_landscape` (480x320) + * - `android_hdpi_portrait` (480x800) + * - `android_hdpi_landscape` (800x480) + * - `android_xhdpi_portrait` (720x1280) + * - `android_xhdpi_landscape` (1280x720) + * - `android_xxhdpi_portrait` (960x1600) + * - `android_xxhdpi_landscape` (1600x960) + * - `android_xxxhdpi_portrait` (1280x1920) + * - `android_xxxhdpi_landscape` (1920x1280) * * @memberOf App */ From 0d80244ef5fa9e61d3d3f8d995e20899109a8352 Mon Sep 17 00:00:00 2001 From: Harry Adel Date: Wed, 29 Dec 2021 17:12:22 +0200 Subject: [PATCH 031/393] Remove 'nothing but' --- guide/source/security.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/source/security.md b/guide/source/security.md index 7c2ea0364c..8eca5f4500 100644 --- a/guide/source/security.md +++ b/guide/source/security.md @@ -695,7 +695,7 @@ This is a collection of points to check about your app that might catch common e 1. Secure the data, not the UI - redirecting away from a client-side route does nothing for security, it's a nice UX feature. 1. [Don't ever trust user IDs passed from the client.](http://guide.meteor.com/security.html#user-id-client) Use `this.userId` inside Methods and publications. 1. Set up secure [HTTP headers](https://guide.meteor.com/security.html#httpheaders) using [Helmet](https://www.npmjs.com/package/helmet), but know that not all browsers support it so it provides an extra layer of security to users with modern browsers. -1. At the end of the day, Meteor is nothing but a Node.js app so make sure to also follow the [best practises](https://cheatsheetseries.owasp.org/cheatsheets/Nodejs_Security_Cheat_Sheet.html) to ensure maximum security. +1. At the end of the day, Meteor is a Node.js app so make sure to also follow the [best practises](https://cheatsheetseries.owasp.org/cheatsheets/Nodejs_Security_Cheat_Sheet.html) to ensure maximum security.

App Protection

App Protection on Galaxy Hosting is a feature in our proxy server layer that sits in front of every request to your application. This means that all requests across servers are analyzed and measured against expected limits. This will help protect against DoS and DDoS attacks that aimed to overload servers and make your app unavailable for legitimate requests. From b656720d33a38d34402654e0b7d5c3afab1f2ec4 Mon Sep 17 00:00:00 2001 From: Jan Dvorak Date: Sat, 1 Jan 2022 11:24:34 +0100 Subject: [PATCH 032/393] Fix #11817 add try/catch to end of redirect response --- packages/oauth/end_of_redirect_response.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/oauth/end_of_redirect_response.js b/packages/oauth/end_of_redirect_response.js index 051159e0a2..af302df79d 100644 --- a/packages/oauth/end_of_redirect_response.js +++ b/packages/oauth/end_of_redirect_response.js @@ -4,8 +4,13 @@ var config = JSON.parse(document.getElementById("config").innerHTML); if (config.setCredentialToken) { - sessionStorage[config.storagePrefix + config.credentialToken] = - config.credentialSecret; + try { + sessionStorage[config.storagePrefix + config.credentialToken] = + config.credentialSecret; + } catch (err) { + // We can't do much else, but at least close the popup instead + // of having it hang around on a blank page. + } } window.location = From c8afd3b1d24d655350bfc4f0354c00eb29edf560 Mon Sep 17 00:00:00 2001 From: Jan Dvorak Date: Sat, 1 Jan 2022 11:28:06 +0100 Subject: [PATCH 033/393] Improve comment for empty catch --- packages/oauth/end_of_redirect_response.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/oauth/end_of_redirect_response.js b/packages/oauth/end_of_redirect_response.js index af302df79d..65df37525c 100644 --- a/packages/oauth/end_of_redirect_response.js +++ b/packages/oauth/end_of_redirect_response.js @@ -8,8 +8,7 @@ sessionStorage[config.storagePrefix + config.credentialToken] = config.credentialSecret; } catch (err) { - // We can't do much else, but at least close the popup instead - // of having it hang around on a blank page. + // We can't do much else, but at least the redirects goes on. } } From e75235a35dca11af8847c5cdb038364826f21ec2 Mon Sep 17 00:00:00 2001 From: Andrey M Date: Tue, 4 Jan 2022 01:08:56 +0300 Subject: [PATCH 034/393] Fix markdown link parsing error Add an empty line to fix the issue when the markdown parser ignores link markup. --- guide/source/vue.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/guide/source/vue.md b/guide/source/vue.md index def2b223e4..831fb1d6f5 100644 --- a/guide/source/vue.md +++ b/guide/source/vue.md @@ -20,6 +20,7 @@ This documentation is purely focused on integrating it with Meteor. > There is also a [Vue tutorial](https://vue-tutorial.meteor.com/) which covers the basics of this section.

Introduction

+ [Vue](https://vuejs.org/v2/guide/) (pronounced /vjuː/, like view) is a progressive framework for building user interfaces. Unlike other monolithic frameworks, Vue is designed from the ground up to be incrementally adoptable. The core library is focused on the view layer only, and is easy to @@ -338,9 +339,11 @@ Application Structure is documented here:

SSR and Code Splitting

+ Vue has [an excellent guide on how to render your Vue application on the server](https://vuejs.org/v2/guide/ssr.html). It includes code splitting, async data fetching and many other practices that are used in most apps that require this.

Basic Example

+ Making Vue SSR to work with Meteor is not more complex then for example with [Express](https://expressjs.com/). However instead of defining a wildcard route, Meteor uses its own [server-render](https://docs.meteor.com/packages/server-render.html) package that exposes an `onPageLoad` function. Every time a call is made to the server side, this function is triggered. This is where we should put our code like how its described on the [VueJS SSR Guide](https://ssr.vuejs.org/guide/#integrating-with-a-server). From b194793ec5359a0970c09cbe30f7e8458010350b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Tue, 4 Jan 2022 20:21:14 -0400 Subject: [PATCH 035/393] Provides a better error message when we have invalid package.json files Also provides new env vars to change the default behavior --- tools/isobuild/package-source.js | 34 +++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/tools/isobuild/package-source.js b/tools/isobuild/package-source.js index 6ce5f17caf..79a66c3677 100644 --- a/tools/isobuild/package-source.js +++ b/tools/isobuild/package-source.js @@ -37,6 +37,10 @@ import { optimisticLookupPackageJsonArray, } from "../fs/optimistic"; +// resolve package includes malformed package.json intentionally +// https://forums.meteor.com/t/unable-to-run-after-update-to-2-5-2/57266/6 +const EXPECTED_INVALID_PACKAGE_JSON_PATHS_TO_IGNORE = ['resolve/test/resolver/malformed_package_json'] + // XXX: This is a medium-term hack, to avoid having the user set a package name // & test-name in package.describe. We will change this in the new control file // world in some way. @@ -1341,7 +1345,35 @@ Object.assign(PackageSource.prototype, { if (inNodeModules) { // This is an array because (in some rare cases) an npm package // may have nested package.json files with additional properties. - const pkgJsonArray = optimisticLookupPackageJsonArray(self.sourceRoot, dir); + let pkgJsonArray = []; + try { + pkgJsonArray = optimisticLookupPackageJsonArray(self.sourceRoot, dir); + } catch (e) { + const message = `Error reading package.json from dir "${dir}", this may cause important errors in your project like modules not being found. You should fix this dependency or find an alternative`; + if ( + EXPECTED_INVALID_PACKAGE_JSON_PATHS_TO_IGNORE.find(path => + dir.includes(path) + ) + ) { + if (process.env.METEOR_WARN_ON_INVALID_EXPECTED_PACKAGE_JSON_ERRORS) { + console.warn(message, e); + } + // Pretend we found no files but in reality this package.json was ignored + return []; + } + if (process.env.METEOR_IGNORE_INVALID_PACKAGE_JSON_ERRORS) { + // Pretend we found no files but in reality an error happened reading this package.json + return []; + } + if (process.env.METEOR_WARN_ON_INVALID_PACKAGE_JSON_ERRORS) { + console.warn(message, e); + // Pretend we found no files but in reality an error happened reading this package.json + return []; + } + // This is going to break the run but at least with a clear error indicating what is the problematic package.json + console.error(message, e); + throw new Error(message); + } // If a package.json file with a "name" property is found, it will // always be the first in the array. From 0e8124c6ea908b49ed19fab3cfa8b627aa1d2351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Tue, 4 Jan 2022 20:23:35 -0400 Subject: [PATCH 036/393] Provides a better error message when we have invalid package.json files Also provides new env vars to change the default behavior --- tools/isobuild/package-source.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/isobuild/package-source.js b/tools/isobuild/package-source.js index 79a66c3677..ceab1acc39 100644 --- a/tools/isobuild/package-source.js +++ b/tools/isobuild/package-source.js @@ -1372,7 +1372,7 @@ Object.assign(PackageSource.prototype, { } // This is going to break the run but at least with a clear error indicating what is the problematic package.json console.error(message, e); - throw new Error(message); + throw e; } // If a package.json file with a "name" property is found, it will From 5733fd2886daaa37a614e9db2f4a8dba9e6d67e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Tue, 4 Jan 2022 21:00:10 -0400 Subject: [PATCH 037/393] Meteor version to 2.5.3 :tada: --- History.md | 21 +++++++++++++++++++++ packages/meteor-tool/package.js | 2 +- scripts/admin/meteor-release-official.json | 2 +- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/History.md b/History.md index ab1e0ce878..397de335c6 100644 --- a/History.md +++ b/History.md @@ -10,6 +10,27 @@ #### Independent Releases +## v2.5.3, 2022-01-04 + +#### Highlights + +* Fixes invalid package.json error with `resolve` + +#### Breaking Changes + +- N/A + +#### Migration Steps + +- N/A + +#### Meteor Version Release + +* `meteor-tool@2.5.3` + - Fixes invalid package.json files breaking Meteor run. [PR](https://github.com/meteor/meteor/pull/11832) and [Issue](https://github.com/meteor/meteor/issues/11830) + +#### Independent Releases + ## v2.5.2, 2021-12-21 #### Highlights diff --git a/packages/meteor-tool/package.js b/packages/meteor-tool/package.js index 372dd424d5..6fb03d8fd7 100644 --- a/packages/meteor-tool/package.js +++ b/packages/meteor-tool/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: 'The Meteor command-line tool', - version: '2.5.2', + version: '2.5.3', }); Package.includeTool(); diff --git a/scripts/admin/meteor-release-official.json b/scripts/admin/meteor-release-official.json index 136ebc917e..94d480daf1 100644 --- a/scripts/admin/meteor-release-official.json +++ b/scripts/admin/meteor-release-official.json @@ -1,6 +1,6 @@ { "track": "METEOR", - "version": "2.5.2", + "version": "2.5.3", "recommended": false, "official": true, "description": "The Official Meteor Distribution" From bb99eeb8fb6edf76ebd945af641aa8aa85c0c1c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Tue, 4 Jan 2022 21:32:55 -0400 Subject: [PATCH 038/393] Meteor installer for version to 2.5.3 --- npm-packages/meteor-installer/README.md | 1 + npm-packages/meteor-installer/config.js | 2 +- npm-packages/meteor-installer/package.json | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/npm-packages/meteor-installer/README.md b/npm-packages/meteor-installer/README.md index 34132ba829..dde995f685 100644 --- a/npm-packages/meteor-installer/README.md +++ b/npm-packages/meteor-installer/README.md @@ -25,6 +25,7 @@ npm install -g meteor | 2.5.1 | 2.5.1 | | 2.5.2 | 2.5.1 | | 2.5.3 | 2.5.2 | +| 2.5.4 | 2.5.3 | ### Important note diff --git a/npm-packages/meteor-installer/config.js b/npm-packages/meteor-installer/config.js index d50760a6d5..68c8f59cb2 100644 --- a/npm-packages/meteor-installer/config.js +++ b/npm-packages/meteor-installer/config.js @@ -1,7 +1,7 @@ const path = require('path'); const os = require('os'); -const METEOR_LATEST_VERSION = '2.5.2'; +const METEOR_LATEST_VERSION = '2.5.3'; const sudoUser = process.env.SUDO_USER || ''; function isRoot() { return process.getuid && process.getuid() === 0; diff --git a/npm-packages/meteor-installer/package.json b/npm-packages/meteor-installer/package.json index 4f39e3d8ce..9a348c36b8 100644 --- a/npm-packages/meteor-installer/package.json +++ b/npm-packages/meteor-installer/package.json @@ -1,6 +1,6 @@ { "name": "meteor", - "version": "2.5.3", + "version": "2.5.4", "description": "Install Meteor", "main": "install.js", "scripts": { From f5a8bb89bc1034cd4d22086ac476caa2edbc6788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Tue, 4 Jan 2022 22:35:43 -0400 Subject: [PATCH 039/393] Trigger docs deploy --- docs/source/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/index.md b/docs/source/index.md index ad257ef281..b8c660f282 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -34,4 +34,4 @@ Meteor is a full-stack JavaScript platform for developing modern web and mobile {% oldRedirects %} - + From 3ae22ca82096e500c4c29f8eba0799e55d428bd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Wed, 5 Jan 2022 16:09:44 -0400 Subject: [PATCH 040/393] Bump oauth package to 2.1.1 --- History.md | 3 +++ docs/source/index.md | 2 +- packages/oauth/package.js | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/History.md b/History.md index 397de335c6..1427e14970 100644 --- a/History.md +++ b/History.md @@ -10,6 +10,9 @@ #### Independent Releases +* `oauth@2.1.1` + - Fixes end of redirect response for oauth inside iframes. [PR](https://github.com/meteor/meteor/pull/11825) and [Issue](https://github.com/meteor/meteor/issues/11817) + ## v2.5.3, 2022-01-04 #### Highlights diff --git a/docs/source/index.md b/docs/source/index.md index b8c660f282..ad257ef281 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -34,4 +34,4 @@ Meteor is a full-stack JavaScript platform for developing modern web and mobile {% oldRedirects %} - + diff --git a/packages/oauth/package.js b/packages/oauth/package.js index a4f7e4540c..4011e93577 100644 --- a/packages/oauth/package.js +++ b/packages/oauth/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Common code for OAuth-based services", - version: "2.1.0" + version: "2.1.1" }); Package.onUse(api => { From fdcc05a72721b752b8b044fd7cdb2172e5997aee Mon Sep 17 00:00:00 2001 From: filipenevola Date: Wed, 3 Nov 2021 18:40:49 -0400 Subject: [PATCH 041/393] MongoDB 5.0 Support - Supporting new error name for BulkWrite --- packages/allow-deny/allow-deny.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/allow-deny/allow-deny.js b/packages/allow-deny/allow-deny.js index be5483feed..e1bff9f9ab 100644 --- a/packages/allow-deny/allow-deny.js +++ b/packages/allow-deny/allow-deny.js @@ -194,7 +194,10 @@ CollectionPrototype._defineMutationMethods = function(options) { } catch (e) { if ( e.name === 'MongoError' || + // for old versions of MongoDB (probably not necessary but it's here just in case) e.name === 'BulkWriteError' || + // for newer versions of MongoDB (https://docs.mongodb.com/drivers/node/current/whats-new/#bulkwriteerror---mongobulkwriteerror) + e.name === 'MongoBulkWriteError' || e.name === 'MinimongoError' ) { throw new Meteor.Error(409, e.toString()); From 3ae11f9100101e3b7a57c7b2b3fb516438715684 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Tue, 7 Dec 2021 16:36:09 -0300 Subject: [PATCH 042/393] useUnifiedTopology is not an option anymore on Mongo 4.2.1 - update history --- History.md | 6 ++++++ packages/mongo/mongo_driver.js | 15 --------------- packages/npm-mongo/package.js | 4 ++-- 3 files changed, 8 insertions(+), 17 deletions(-) diff --git a/History.md b/History.md index 1427e14970..d7d6fb3041 100644 --- a/History.md +++ b/History.md @@ -96,6 +96,12 @@ * `standard-minifier-js@2.7.3` - Using `minifier-js@2.7.3` + +* `mongo@1.14.0` + - useUnifiedTopology is not an option anymore, it defaults to true. + +* `npm-mongo@4.2.1` + - Update MongoDB driver version to 4.2.1 ## v2.5.1, 2021-11-17 diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 7997ddec73..72d9108d55 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -146,23 +146,8 @@ MongoConnection = function (url, options) { var mongoOptions = Object.assign({ ignoreUndefined: true, - // (node:59240) [MONGODB DRIVER] Warning: Current Server Discovery and - // Monitoring engine is deprecated, and will be removed in a future version. - // To use the new Server Discover and Monitoring engine, pass option - // { useUnifiedTopology: true } to the MongoClient constructor. - useUnifiedTopology: true, }, userOptions); - // The autoReconnect and reconnectTries options are incompatible with - // useUnifiedTopology: https://github.com/meteor/meteor/pull/10861#commitcomment-37525845 - if (!mongoOptions.useUnifiedTopology) { - // Reconnect on error. This defaults to true, but it never hurts to be - // explicit about it. - mongoOptions.autoReconnect = true; - // Try to reconnect forever, instead of stopping after 30 tries (the - // default), with each attempt separated by 1000ms. - mongoOptions.reconnectTries = Infinity; - } // Disable the native parser by default, unless specifically enabled // in the mongo URL. diff --git a/packages/npm-mongo/package.js b/packages/npm-mongo/package.js index 600d277450..ff62ade192 100644 --- a/packages/npm-mongo/package.js +++ b/packages/npm-mongo/package.js @@ -3,12 +3,12 @@ Package.describe({ summary: "Wrapper around the mongo npm package", - version: "3.9.1", + version: "4.2.1", documentation: null }); Npm.depends({ - mongodb: "3.6.10" + mongodb: "4.2.1" }); Package.onUse(function (api) { From 962d0705d38f1fa22700cf5fc6cdcdd249261df8 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Tue, 7 Dec 2021 17:01:07 -0300 Subject: [PATCH 043/393] fields option is deprecated on mongo queries, projection is the default now --- History.md | 2 ++ packages/mongo/mongo_driver.js | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/History.md b/History.md index d7d6fb3041..da9adf7a1a 100644 --- a/History.md +++ b/History.md @@ -99,6 +99,8 @@ * `mongo@1.14.0` - useUnifiedTopology is not an option anymore, it defaults to true. + - fields option is deprecated, we are maintaining a translation layer to "projection" field (now prefered) until the next minor version, where we will start showing alerts. + * `npm-mongo@4.2.1` - Update MongoDB driver version to 4.2.1 diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 72d9108d55..715164a9de 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -877,6 +877,12 @@ CursorDescription = function (collectionName, selector, options) { self.collectionName = collectionName; self.selector = Mongo.Collection._rewriteSelector(selector); self.options = options || {}; + // transform fields key in projection + const { fields, projection, ...otherOptions } = self.options; + // TODO: enable this comment when deprecating the fields option + // Log.debug(`fields option has been deprecated, please use the new 'projection' instead`) + + self.options = { ...otherOptions, ...(projection || fields ? {projection: fields || projection} : {}) }; }; Cursor = function (mongo, cursorDescription) { From a32e0a9f04854ac622fe625169d1b6946f2b7f52 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Tue, 7 Dec 2021 17:03:55 -0300 Subject: [PATCH 044/393] Show deprecation warning for _ensureIndex --- History.md | 2 ++ packages/mongo/collection.js | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/History.md b/History.md index da9adf7a1a..6eb75e0f09 100644 --- a/History.md +++ b/History.md @@ -100,6 +100,8 @@ * `mongo@1.14.0` - useUnifiedTopology is not an option anymore, it defaults to true. - fields option is deprecated, we are maintaining a translation layer to "projection" field (now prefered) until the next minor version, where we will start showing alerts. + - _ensureIndex is now showing a deprecation message + * `npm-mongo@4.2.1` diff --git a/packages/mongo/collection.js b/packages/mongo/collection.js index ad5ab93c6f..eca28116c5 100644 --- a/packages/mongo/collection.js +++ b/packages/mongo/collection.js @@ -726,9 +726,8 @@ Object.assign(Mongo.Collection.prototype, { var self = this; if (!self._collection._ensureIndex || !self._collection.createIndex) throw new Error('Can only call createIndex on server collections'); - // TODO enable this message a release before we will remove this function - // import { Log } from 'meteor/logging'; - // Log.debug(`_ensureIndex has been deprecated, please use the new 'createIndex' instead${options?.name ? `, index name: ${options.name}` : `, index: ${JSON.stringify(index)}`}`) + import { Log } from 'meteor/logging'; + Log.debug(`_ensureIndex has been deprecated, please use the new 'createIndex' instead${options?.name ? `, index name: ${options.name}` : `, index: ${JSON.stringify(index)}`}`) if (self._collection.createIndex) { self._collection.createIndex(index, options); } else { From 89e040f4f3afff4ab3e3acafba54843acbeddf08 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Tue, 7 Dec 2021 21:07:58 -0300 Subject: [PATCH 045/393] Update connect method on mongo 5 --- packages/mongo/mongo_driver.js | 5 +++-- packages/mongo/mongo_livedata_tests.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 715164a9de..2dcd4d7d95 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -190,9 +190,10 @@ MongoConnection = function (url, options) { var connectFuture = new Future; - MongoDB.connect( + new MongoDB.MongoClient( url, - mongoOptions, + mongoOptions + ).connect( Meteor.bindEnvironment( function (err, client) { if (err) { diff --git a/packages/mongo/mongo_livedata_tests.js b/packages/mongo/mongo_livedata_tests.js index 8f77d1c822..a19edd3a72 100644 --- a/packages/mongo/mongo_livedata_tests.js +++ b/packages/mongo/mongo_livedata_tests.js @@ -3318,7 +3318,7 @@ Meteor.isServer && Tinytest.add("mongo-livedata - npm modules", function (test) var c = new Mongo.Collection(Random.id()); var rawCollection = c.rawCollection(); test.isTrue(rawCollection); - test.isTrue(rawCollection.findAndModify); + test.isTrue(rawCollection.findOneAndUpdate); var rawDb = c.rawDatabase(); test.isTrue(rawDb); test.isTrue(rawDb.admin); From b699b38af4fa3befce1d5572a0204c3eee34ae4a Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Tue, 7 Dec 2021 21:11:44 -0300 Subject: [PATCH 046/393] Update mongo version to 5.0.5 on dev-bundle --- meteor | 2 +- scripts/build-dev-bundle-common.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/meteor b/meteor index 3d8b5e6bd4..fb2f73f60d 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/usr/bin/env bash -BUNDLE_VERSION=14.18.2.3 +BUNDLE_VERSION=14.18.2.4 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. diff --git a/scripts/build-dev-bundle-common.sh b/scripts/build-dev-bundle-common.sh index 386cb631e1..0ae15acb75 100644 --- a/scripts/build-dev-bundle-common.sh +++ b/scripts/build-dev-bundle-common.sh @@ -6,7 +6,7 @@ set -u UNAME=$(uname) ARCH=$(uname -m) NODE_VERSION=14.18.2 -MONGO_VERSION_64BIT=4.4.4 +MONGO_VERSION_64BIT=5.0.5 MONGO_VERSION_32BIT=3.2.22 NPM_VERSION=6.14.15 From add143b7287c611163706d8ed9c30009fa7bdfda Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Tue, 7 Dec 2021 22:12:43 -0300 Subject: [PATCH 047/393] waitForStepDownOnNonCommandShutdown is not needed anymore --- tools/runners/run-mongo.js | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/tools/runners/run-mongo.js b/tools/runners/run-mongo.js index 0d2fae2ade..1ad2b0693f 100644 --- a/tools/runners/run-mongo.js +++ b/tools/runners/run-mongo.js @@ -71,19 +71,7 @@ function spawnMongod(mongodPath, port, dbPath, replSetName) { '8', '--replSet', replSetName, - '--noauth', - - // Starting with version 4.0.8/4.1.10, MongoDB performs a step down - // procedure if the primary receives a SIGTERM signal - // (https://jira.mongodb.org/browse/SERVER-38994). During this procedure, - // the process doesn't shut down for up to ten seconds until a secondary - // becomes the new primary. Since Meteor starts a single-node replica set, - // this is unnecessary because there are no secondaries. The following - // parameter disables the step down. This will be the default for single- - // node replica sets in MongoDB 4.3 (relevant commit: https://git.io/JeNkT), - // so the parameter can be removed in the future. - '--setParameter', - 'waitForStepDownOnNonCommandShutdown=false', + '--noauth' ]; // Use mmapv1 on 32bit platforms, as our binary doesn't support WT @@ -104,6 +92,7 @@ function spawnMongod(mongodPath, port, dbPath, replSetName) { args = ['-x86_64', mongodPath, ...args]; mongodPath = 'arch'; } + console.log(args.join(' ')) return child_process.spawn(mongodPath, args, { // Apparently in some contexts, Mongo crashes if your locale isn't set up // right. I wasn't able to reproduce it, but many people on #4019 From 83ca8dece0055797f16c5a10cc0b0e7429f58cb5 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Wed, 8 Dec 2021 08:08:21 -0300 Subject: [PATCH 048/393] native_parser is no longer an option, and we cant connect anymore with new Server() --- packages/mongo/mongo_driver.js | 11 ----------- tools/runners/run-mongo.js | 4 ++-- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 2dcd4d7d95..127df81f15 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -149,17 +149,6 @@ MongoConnection = function (url, options) { }, userOptions); - // Disable the native parser by default, unless specifically enabled - // in the mongo URL. - // - The native driver can cause errors which normally would be - // thrown, caught, and handled into segfaults that take down the - // whole app. - // - Binary modules don't yet work when you bundle and move the bundle - // to a different platform (aka deploy) - // We should revisit this after binary npm module support lands. - if (!(/[\?&]native_?[pP]arser=/.test(url))) { - mongoOptions.native_parser = false; - } // Internally the oplog connections specify their own poolSize // which we don't want to overwrite with any user defined value diff --git a/tools/runners/run-mongo.js b/tools/runners/run-mongo.js index 1ad2b0693f..d5f7260800 100644 --- a/tools/runners/run-mongo.js +++ b/tools/runners/run-mongo.js @@ -688,12 +688,12 @@ var launchMongo = function(options) { // Connect to the intended primary and start a replset. const client = new MongoClient( - new Server('127.0.0.1', options.port, { + `mongodb://127.0.0.1:${options.port}`, { poolSize: 1, socketOptions: { connectTimeoutMS: 60000, }, - }) + } ); yieldingMethod(client, 'connect'); From f4b21b3741222d1b2d5f0ba7c17cc3e2c6c502cf Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Wed, 8 Dec 2021 10:50:38 -0300 Subject: [PATCH 049/393] poolSize is no longer an option db.serverConfig is no longer available, topology events are emmited from MongoClient --- packages/mongo/mongo_driver.js | 8 ++++---- tools/runners/run-mongo.js | 26 ++++++++++++++------------ 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 127df81f15..fc869d1b2c 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -190,13 +190,13 @@ MongoConnection = function (url, options) { } var db = client.db(); - + const helloDocument = db.admin().command( { hello: 1 } ).await(); // First, figure out what the current primary is, if any. - if (db.serverConfig.isMasterDoc) { - self._primary = db.serverConfig.isMasterDoc.primary; + if (helloDocument.isWritablePrimary) { + self._primary = helloDocument.primary; } - db.serverConfig.on( + client.topology.on( 'joined', Meteor.bindEnvironment(function (kind, doc) { if (kind === 'primary') { if (doc.primary !== self._primary) { diff --git a/tools/runners/run-mongo.js b/tools/runners/run-mongo.js index d5f7260800..443a8dcc6e 100644 --- a/tools/runners/run-mongo.js +++ b/tools/runners/run-mongo.js @@ -679,21 +679,21 @@ var launchMongo = function(options) { stopOrReadyPromise.await(); }; - var initiateReplSetAndWaitForReady = function() { + var initiateReplSetAndWaitForReady = function () { try { // Load mongo so we'll be able to talk to it. - const { MongoClient, Server } = loadIsopackage( - 'npm-mongo' + const {MongoClient} = loadIsopackage( + 'npm-mongo' ).NpmModuleMongodb; // Connect to the intended primary and start a replset. const client = new MongoClient( - `mongodb://127.0.0.1:${options.port}`, { - poolSize: 1, - socketOptions: { - connectTimeoutMS: 60000, - }, - } + `mongodb://127.0.0.1:${options.port}`, { + minPoolSize: 1, + maxPoolSize: 1, + socketTimeoutMS: 60000, + directConnection: true + } ); yieldingMethod(client, 'connect'); @@ -707,7 +707,7 @@ var launchMongo = function(options) { _id: replSetName, version: 1, protocolVersion: 1, - members: [{ _id: 0, host: '127.0.0.1:' + options.port, priority: 100 }], + members: [{_id: 0, host: '127.0.0.1:' + options.port, priority: 100}], }; try { @@ -762,7 +762,7 @@ var launchMongo = function(options) { // Wait until the primary is writable. If it isn't writable after one // minute, throw an error and report the replica set status. while (!stopped) { - const { ismaster } = yieldingMethod(db.admin(), 'command', { + const {ismaster} = yieldingMethod(db.admin(), 'command', { isMaster: 1, }); @@ -774,7 +774,7 @@ var launchMongo = function(options) { }); throw new Error( - 'Primary not writable after one minute. Last replica set status: ' + + 'Primary not writable after one minute. Last replica set status: ' + JSON.stringify(status) ); } @@ -791,6 +791,7 @@ var launchMongo = function(options) { } } }; + console.log("DONE! - Enabling rplset") try { if (options.multiple) { @@ -812,6 +813,7 @@ var launchMongo = function(options) { var portFile = !noOplog && files.pathJoin(dbPath, 'METEOR-PORT'); launchOneMongoAndWaitForReadyForInitiate(dbPath, options.port, portFile); if (!stopped && !noOplog) { + console.log("got here"); initiateReplSetAndWaitForReady(); if (!stopped) { // Write down that we configured the database properly. From dbd1be3a1ee323f756366e29326840736c70d811 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Wed, 8 Dec 2021 10:51:19 -0300 Subject: [PATCH 050/393] poolSize is no longer an option, use maxPoolSize instead --- packages/mongo/mongo_driver.js | 6 +++--- packages/mongo/oplog_tailing.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index fc869d1b2c..82cd70e22c 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -150,12 +150,12 @@ MongoConnection = function (url, options) { - // Internally the oplog connections specify their own poolSize + // Internally the oplog connections specify their own maxPoolSize // which we don't want to overwrite with any user defined value - if (_.has(options, 'poolSize')) { + if (_.has(options, 'maxPoolSize')) { // If we just set this for "server", replSet will override it. If we just // set it for replSet, it will be ignored if we're not using a replSet. - mongoOptions.poolSize = options.poolSize; + mongoOptions.maxPoolSize = options.maxPoolSize; } // Transform options like "tlsCAFileAsset": "filename.pem" into diff --git a/packages/mongo/oplog_tailing.js b/packages/mongo/oplog_tailing.js index fabbd1a3c1..fc702318db 100644 --- a/packages/mongo/oplog_tailing.js +++ b/packages/mongo/oplog_tailing.js @@ -206,12 +206,12 @@ Object.assign(OplogHandle.prototype, { // The tail connection will only ever be running a single tail command, so // it only needs to make one underlying TCP connection. self._oplogTailConnection = new MongoConnection( - self._oplogUrl, {poolSize: 1}); + self._oplogUrl, {maxPoolSize: 1}); // XXX better docs, but: it's to get monotonic results // XXX is it safe to say "if there's an in flight query, just use its // results"? I don't think so but should consider that self._oplogLastEntryConnection = new MongoConnection( - self._oplogUrl, {poolSize: 1}); + self._oplogUrl, {maxPoolSize: 1}); // Now, make sure that there actually is a repl set here. If not, oplog // tailing won't ever find anything! From 8083c638f23d2a2b5d806e5045ea0f7ff59e7f39 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Wed, 8 Dec 2021 10:53:27 -0300 Subject: [PATCH 051/393] db.collection is no longer async, and the callback option is no longer available --- packages/mongo/mongo_driver.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 82cd70e22c..463572ea24 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -258,9 +258,7 @@ MongoConnection.prototype.rawCollection = function (collectionName) { if (! self.db) throw Error("rawCollection called before Connection created?"); - var future = new Future; - self.db.collection(collectionName, future.resolver()); - return future.wait(); + return self.db.collection(collectionName); }; MongoConnection.prototype._createCappedCollection = function ( From 583214b63c1a55f7e81c9b6308d16c71fd83026e Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Wed, 8 Dec 2021 11:02:29 -0300 Subject: [PATCH 052/393] Find count() no longers accept applySkipLimit, instead it accepts an option object --- packages/mongo/mongo_driver.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 463572ea24..041a17e307 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -1148,9 +1148,9 @@ _.extend(SynchronousCursor.prototype, { return self.map(_.identity); }, - count: function (applySkipLimit = false) { + count: function (options = {}) { var self = this; - return self._synchronousCount(applySkipLimit).wait(); + return self._synchronousCount(options).wait(); }, // This method is NOT wrapped in Cursor. From c476e190ee32354b2851c6e00ffd8192b1ca2395 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Wed, 8 Dec 2021 11:16:42 -0300 Subject: [PATCH 053/393] Find count() no longers accept applySkipLimit, instead it takes no options anymore Use insertOne/insertMany --- History.md | 2 +- packages/minimongo/cursor.js | 9 +- packages/mongo/mongo_driver.js | 6 +- packages/mongo/mongo_livedata_tests.js | 6 +- packages/mongo/oplog_tests.js | 2 +- .../.npm/package/npm-shrinkwrap.json | 128 ++++++++---------- tools/runners/run-mongo.js | 3 - 7 files changed, 66 insertions(+), 90 deletions(-) diff --git a/History.md b/History.md index 6eb75e0f09..6fe4e82bd2 100644 --- a/History.md +++ b/History.md @@ -101,7 +101,7 @@ - useUnifiedTopology is not an option anymore, it defaults to true. - fields option is deprecated, we are maintaining a translation layer to "projection" field (now prefered) until the next minor version, where we will start showing alerts. - _ensureIndex is now showing a deprecation message - + - applySkipLimit option for count() on find cursors is no longer supported. * `npm-mongo@4.2.1` diff --git a/packages/minimongo/cursor.js b/packages/minimongo/cursor.js index 7c000af170..8d5a938be2 100644 --- a/packages/minimongo/cursor.js +++ b/packages/minimongo/cursor.js @@ -41,16 +41,11 @@ export default class Cursor { * @summary Returns the number of documents that match a query. * @memberOf Mongo.Cursor * @method count - * @param {boolean} [applySkipLimit=true] If set to `false`, the value - * returned will reflect the total - * number of matching documents, - * ignoring any value supplied for - * limit * @instance * @locus Anywhere * @returns {Number} */ - count(applySkipLimit = true) { + count() { if (this.reactive) { // allow the observe to be unordered this._depend({added: true, removed: true}, true); @@ -58,7 +53,7 @@ export default class Cursor { return this._getRawObjects({ ordered: true, - applySkipLimit + applySkipLimit: true }).length; } diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 041a17e307..b858220a2e 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -374,7 +374,7 @@ MongoConnection.prototype._insert = function (collection_name, document, callback = bindEnvironmentForWrite(writeCallback(write, refresh, callback)); try { var collection = self.rawCollection(collection_name); - collection.insert(replaceTypes(document, replaceMeteorAtomWithMongo), + collection.insertOne(replaceTypes(document, replaceMeteorAtomWithMongo), {safe: true}, callback); } catch (err) { write.committed(); @@ -1148,9 +1148,9 @@ _.extend(SynchronousCursor.prototype, { return self.map(_.identity); }, - count: function (options = {}) { + count: function () { var self = this; - return self._synchronousCount(options).wait(); + return self._synchronousCount().wait(); }, // This method is NOT wrapped in Cursor. diff --git a/packages/mongo/mongo_livedata_tests.js b/packages/mongo/mongo_livedata_tests.js index a19edd3a72..470bc6e8c7 100644 --- a/packages/mongo/mongo_livedata_tests.js +++ b/packages/mongo/mongo_livedata_tests.js @@ -311,13 +311,11 @@ Tinytest.addAsync("mongo-livedata - basics, " + idGeneration, function (test, on // Regression test for https://github.com/meteor/meteor/issues/7436 - // - ensure applySkipLimit defaults to false for count() + // - ensure applySkipLimit defaults to true for count() // Note that the current behavior is inconsistent on the client. // (https://github.com/meteor/meteor/issues/1201) if (Meteor.isServer) { - test.equal(coll.find({run: run}, {limit: 1}).count(), 2); - test.equal(coll.find({run: run}, {limit: 1}).count(true), 1); - test.equal(coll.find({run: run}, {limit: 1}).count(false), 2); + test.equal(coll.find({run: run}, {limit: 1}).count(), 1); } var cur = coll.find({run: run}, {sort: ["x"]}); diff --git a/packages/mongo/oplog_tests.js b/packages/mongo/oplog_tests.js index 7789f54a9f..e327c2321e 100644 --- a/packages/mongo/oplog_tests.js +++ b/packages/mongo/oplog_tests.js @@ -91,7 +91,7 @@ process.env.MONGO_OPLOG_URL && testAsyncMulti( } // XXX implement bulk insert #1255 var rawCollection = self.collection.rawCollection(); - rawCollection.insert(docs, Meteor.bindEnvironment(expect(function (err) { + rawCollection.insertMany(docs, Meteor.bindEnvironment(expect(function (err) { test.isFalse(err); }))); }, diff --git a/packages/npm-mongo/.npm/package/npm-shrinkwrap.json b/packages/npm-mongo/.npm/package/npm-shrinkwrap.json index 3f30a25eaa..fa086c41c6 100644 --- a/packages/npm-mongo/.npm/package/npm-shrinkwrap.json +++ b/packages/npm-mongo/.npm/package/npm-shrinkwrap.json @@ -1,35 +1,45 @@ { "lockfileVersion": 1, "dependencies": { - "bl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", - "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==" + "@types/node": { + "version": "16.11.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", + "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==" + }, + "@types/webidl-conversions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz", + "integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q==" + }, + "@types/whatwg-url": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz", + "integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==" + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "bson": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", - "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==" + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.0.tgz", + "integrity": "sha512-8jw1NU1hglS+Da1jDOUYuNcBJ4cNHCFIqzlwoFNnsTOg2R/ox0aTYcTiBN4dzRa9q7Cvy6XErh3L8ReTEb9AQQ==" }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==" }, "denque": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz", - "integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz", + "integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==" }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "memory-pager": { "version": "1.5.0", @@ -37,41 +47,19 @@ "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" }, "mongodb": { - "version": "3.6.10", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.10.tgz", - "integrity": "sha512-fvIBQBF7KwCJnDZUnFFy4WqEFP8ibdXeFANnylW19+vOwdjOAvqIzPdsNCEMT6VKTHnYu4K64AWRih0mkFms6Q==" + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.2.1.tgz", + "integrity": "sha512-nDC+ulM/Ea3Q2VG5eemuGfB7T4ORwrtKegH2XW9OLlUBgQF6OTNrzFCS1Z3SJGVA+T0Sr1xBYV6DMnp0A7us0g==" }, - "optional-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.1.tgz", - "integrity": "sha512-EnUe33GTAltyZlIsQ2l93KzBC9zi8BsxLvKP3wxALOsz/YIakVojyuZsv5PFFk8y8e6r+SbaPIsNmyPoSK0OHw==" + "mongodb-connection-string-url": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.2.0.tgz", + "integrity": "sha512-U0cDxLUrQrl7DZA828CA+o69EuWPWEJTwdMPozyd7cy/dbtncUZczMw7wRHcwMD7oKOn0NM2tF9jdf5FFVW9CA==" }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "require-at": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz", - "integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==" - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "saslprep": { "version": "1.0.3", @@ -83,22 +71,20 @@ "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=" }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } + "tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==" }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" + }, + "whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==" } } } diff --git a/tools/runners/run-mongo.js b/tools/runners/run-mongo.js index 443a8dcc6e..f61f1bffcd 100644 --- a/tools/runners/run-mongo.js +++ b/tools/runners/run-mongo.js @@ -92,7 +92,6 @@ function spawnMongod(mongodPath, port, dbPath, replSetName) { args = ['-x86_64', mongodPath, ...args]; mongodPath = 'arch'; } - console.log(args.join(' ')) return child_process.spawn(mongodPath, args, { // Apparently in some contexts, Mongo crashes if your locale isn't set up // right. I wasn't able to reproduce it, but many people on #4019 @@ -791,7 +790,6 @@ var launchMongo = function(options) { } } }; - console.log("DONE! - Enabling rplset") try { if (options.multiple) { @@ -813,7 +811,6 @@ var launchMongo = function(options) { var portFile = !noOplog && files.pathJoin(dbPath, 'METEOR-PORT'); launchOneMongoAndWaitForReadyForInitiate(dbPath, options.port, portFile); if (!stopped && !noOplog) { - console.log("got here"); initiateReplSetAndWaitForReady(); if (!stopped) { // Write down that we configured the database properly. From c83ad3075656365373d3c620ade618ed1fca4eae Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Tue, 4 Jan 2022 14:17:48 -0300 Subject: [PATCH 054/393] Use updateMany/deleteMany/replaceOne - we are sticking with Meteor default "update" for deciding which internal mongodb method to call based in the existence of "$" operators --- packages/mongo/mongo_driver.js | 38 ++++++++++++++++---------- packages/mongo/mongo_livedata_tests.js | 2 +- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index b858220a2e..5a8c754406 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -422,11 +422,15 @@ MongoConnection.prototype._remove = function (collection_name, selector, try { var collection = self.rawCollection(collection_name); - var wrappedCallback = function(err, driverResult) { - callback(err, transformResult(driverResult).numberAffected); - }; - collection.remove(replaceTypes(selector, replaceMeteorAtomWithMongo), - {safe: true}, wrappedCallback); + const result = {}; + try { + const {deletedCount} = collection.deleteMany(replaceTypes(selector, replaceMeteorAtomWithMongo), + {safe: true}).await(); + result.numberAffected = deletedCount; + }catch(err){ + callback(err) + } + callback(null, result) } catch (err) { write.committed(); throw err; @@ -602,11 +606,14 @@ MongoConnection.prototype._update = function (collection_name, selector, mod, Object.assign(mongoMod.$setOnInsert, replaceTypes({_id: options.insertedId}, replaceMeteorAtomWithMongo)); } - collection.update( + const strings = Object.keys(mongoMod).filter((key) => !key.startsWith("$")); + let updateMethod = strings.length > 0 ? 'replaceOne' : 'updateMany'; + updateMethod = updateMethod === 'updateMany' && !mongoOpts.multi ? 'updateOne' : updateMethod; + collection[updateMethod]( mongoSelector, mongoMod, mongoOpts, bindEnvironmentForWrite(function (err, result) { if (! err) { - var meteorResult = transformResult(result); + var meteorResult = transformResult({result}); if (meteorResult && options._returnObject) { // If this was an upsert() call, and we ended up // inserting a new doc and we know its id, then @@ -638,15 +645,14 @@ var transformResult = function (driverResult) { var meteorResult = { numberAffected: 0 }; if (driverResult) { var mongoResult = driverResult.result; - // On updates with upsert:true, the inserted values come as a list of // upserted values -- even with options.multi, when the upsert does insert, // it only inserts one element. - if (mongoResult.upserted) { - meteorResult.numberAffected += mongoResult.upserted.length; + if (mongoResult.upsertedCount) { + meteorResult.numberAffected += mongoResult.upsertedCount.length; - if (mongoResult.upserted.length == 1) { - meteorResult.insertedId = mongoResult.upserted[0]._id; + if (mongoResult.upsertedCount.length === 1) { + meteorResult.insertedId = mongoResult.upsertedId; } } else { meteorResult.numberAffected = mongoResult.n; @@ -715,7 +721,11 @@ var simulateUpsertWithInsertedId = function (collection, selector, mod, if (! tries) { callback(new Error("Upsert failed after " + NUM_OPTIMISTIC_TRIES + " tries.")); } else { - collection.update(selector, mod, mongoOptsForUpdate, + let method = collection.updateMany; + if(!Object.keys(mod).some(key => key.startsWith("$"))){ + method = collection.replaceOne; + } + method(selector, mod, {upsert:true,...mongoOptsForUpdate}, bindEnvironmentForWrite(function (err, result) { if (err) { callback(err); @@ -731,7 +741,7 @@ var simulateUpsertWithInsertedId = function (collection, selector, mod, }; var doConditionalInsert = function () { - collection.update(selector, replacementWithId, mongoOptsForInsert, + collection.updateMany(selector, replacementWithId, mongoOptsForInsert, bindEnvironmentForWrite(function (err, result) { if (err) { // figure out if this is a diff --git a/packages/mongo/mongo_livedata_tests.js b/packages/mongo/mongo_livedata_tests.js index 470bc6e8c7..4ad47bb2fa 100644 --- a/packages/mongo/mongo_livedata_tests.js +++ b/packages/mongo/mongo_livedata_tests.js @@ -3546,7 +3546,7 @@ if (Meteor.isServer) { session.withTransaction(session => { let promise = Promise.resolve(); ["a", "b"].forEach((id, index) => { - promise = promise.then(() => rawCollection.update( + promise = promise.then(() => rawCollection.updateMany( { _id: id }, { $set: { field: `updated${index + 1}` } }, { session } From 856921cfa162a959aa56283d905dbd425a6e0d0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Tue, 4 Jan 2022 15:26:47 -0400 Subject: [PATCH 055/393] Fixing tests in mongo livedata and observe changes --- packages/mongo/collection.js | 19 ++++++++++++++++--- packages/mongo/mongo_driver.js | 20 ++++++++++---------- packages/mongo/mongo_livedata_tests.js | 21 +++++++++++++++------ packages/mongo/mongo_utils.js | 11 +++++++++++ packages/mongo/observe_changes_tests.js | 9 ++++----- packages/tinytest/tinytest.js | 6 +++--- 6 files changed, 59 insertions(+), 27 deletions(-) create mode 100644 packages/mongo/mongo_utils.js diff --git a/packages/mongo/collection.js b/packages/mongo/collection.js index eca28116c5..44c1fdfcad 100644 --- a/packages/mongo/collection.js +++ b/packages/mongo/collection.js @@ -1,6 +1,8 @@ // options.connection, if given, is a LivedataClient or LivedataServer // XXX presently there is no way to destroy/clean up a Collection +import { normalizeProjection } from "./mongo_utils"; + /** * @summary Namespace for MongoDB-related items * @namespace @@ -320,15 +322,18 @@ Object.assign(Mongo.Collection.prototype, { }, _getFindOptions(args) { + const [, options] = args || []; + const newOptions = normalizeProjection(options); + var self = this; if (args.length < 2) { return { transform: self._transform }; } else { check( - args[1], + newOptions, Match.Optional( Match.ObjectIncluding({ - fields: Match.Optional(Match.OneOf(Object, undefined)), + projection: Match.Optional(Match.OneOf(Object, undefined)), sort: Match.Optional( Match.OneOf(Object, Array, Function, undefined) ), @@ -338,9 +343,17 @@ Object.assign(Mongo.Collection.prototype, { ) ); + const { projection } = newOptions; + + // this error: "Cannot do exclusion on field _id in inclusion projection" + // happens on MongoDB CLI but doesn't happen in the Node.js Driver for MongoDB 5.0+ + if (projection && projection._id != null && !projection._id) { + throw new Error(`Cannot do exclusion on field _id in inclusion projection, collectionName=${self._name}`); + } + return { transform: self._transform, - ...args[1], + ...newOptions, }; } }, diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 5a8c754406..c9f496f1e6 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -1,3 +1,5 @@ +import { normalizeProjection } from "./mongo_utils"; + /** * Provide a synchronous Collection API using fibers, backed by * MongoDB. This is only for use on the server, and mostly identical @@ -422,15 +424,15 @@ MongoConnection.prototype._remove = function (collection_name, selector, try { var collection = self.rawCollection(collection_name); - const result = {}; + let result = {}; try { const {deletedCount} = collection.deleteMany(replaceTypes(selector, replaceMeteorAtomWithMongo), {safe: true}).await(); - result.numberAffected = deletedCount; + result.modifiedCount = deletedCount; }catch(err){ callback(err) } - callback(null, result) + callback(null, transformResult({result}).numberAffected) } catch (err) { write.committed(); throw err; @@ -642,6 +644,8 @@ MongoConnection.prototype._update = function (collection_name, selector, mod, }; var transformResult = function (driverResult) { + console.log(`driverResult`, driverResult); + var meteorResult = { numberAffected: 0 }; if (driverResult) { var mongoResult = driverResult.result; @@ -655,7 +659,9 @@ var transformResult = function (driverResult) { meteorResult.insertedId = mongoResult.upsertedId; } } else { - meteorResult.numberAffected = mongoResult.n; + // n was used before Mongo 5.0, in Mongo 5.0 we are not receiving this n + // field and so we are using modifiedCount instead + meteorResult.numberAffected = mongoResult.n || mongoResult.modifiedCount; } } @@ -875,12 +881,6 @@ CursorDescription = function (collectionName, selector, options) { self.collectionName = collectionName; self.selector = Mongo.Collection._rewriteSelector(selector); self.options = options || {}; - // transform fields key in projection - const { fields, projection, ...otherOptions } = self.options; - // TODO: enable this comment when deprecating the fields option - // Log.debug(`fields option has been deprecated, please use the new 'projection' instead`) - - self.options = { ...otherOptions, ...(projection || fields ? {projection: fields || projection} : {}) }; }; Cursor = function (mongo, cursorDescription) { diff --git a/packages/mongo/mongo_livedata_tests.js b/packages/mongo/mongo_livedata_tests.js index 4ad47bb2fa..2d490e90de 100644 --- a/packages/mongo/mongo_livedata_tests.js +++ b/packages/mongo/mongo_livedata_tests.js @@ -357,6 +357,8 @@ Tinytest.addAsync("mongo-livedata - basics, " + idGeneration, function (test, on expectObserve('', function () { var count = coll.update({run: run, x: -1}, {$inc: {x: 2}}, {multi: true}); + console.log(`count`, count); + test.equal(count, 0); }); @@ -563,7 +565,7 @@ if (Meteor.isServer) { function error() { throw new Meteor.Error('unsafe object mutation'); } - + const denyModifications = { get(target, key) { const type = Object.prototype.toString.call(target[key]); @@ -577,7 +579,7 @@ if (Meteor.isServer) { deleteProperty: error, defineProperty: error, }; - + // Object.freeze only throws in silent mode // So we make our own version that always throws. function freeze(obj) { @@ -589,7 +591,7 @@ if (Meteor.isServer) { // Make sure that if anything touches the original object, this will throw return origApplyCallback.call(this, callback, freeze(args)); } - + const run = test.runId(); const coll = new Mongo.Collection(`livedata_test_scribble_collection_${run}`, collectionOptions); const expectMutatable = (o) => { @@ -623,7 +625,7 @@ if (Meteor.isServer) { coll.insert({run, a: [ {c: 1} ]}); coll.update({run}, { $set: { 'a.0.c': 2 } }); }); - + handle.stop(); handle2.stop(); @@ -720,6 +722,8 @@ if (Meteor.isServer) { var output = []; var callbacks = { changed: function (newDoc) { + console.log(`newDoc`, newDoc); + output.push({changed: newDoc._id}); } }; @@ -764,11 +768,16 @@ if (Meteor.isServer) { test.isTrue(observeMultiplexer); test.isTrue(observeMultiplexer === o2.handle._multiplexer); + console.log('runInFence filipe') + console.log(`o1.output`, o1.output); + // Update. Both observes fire. runInFence(function () { coll.update(docId1, {$set: {x: 'y'}}); }); - test.length(o1.output, 1); + console.log('runInFence filipe 2') + console.log(`o1.output `, o1.output); + test.length(o1.output, 1, 'filipe'); test.length(o2.output, 1); test.equal(o1.output.shift(), {changed: docId1}); test.equal(o2.output.shift(), {changed: docId1}); @@ -2231,7 +2240,7 @@ _.each(Meteor.isServer ? [true, false] : [true], function (minimongo) { }); // end idGeneration parametrization Tinytest.add('mongo-livedata - rewrite selector', function (test) { - + test.equal(Mongo.Collection._rewriteSelector('foo'), {_id: 'foo'}); diff --git a/packages/mongo/mongo_utils.js b/packages/mongo/mongo_utils.js new file mode 100644 index 0000000000..e97e722fd3 --- /dev/null +++ b/packages/mongo/mongo_utils.js @@ -0,0 +1,11 @@ +export const normalizeProjection = options => { + // transform fields key in projection + const { fields, projection, ...otherOptions } = options || {}; + // TODO: enable this comment when deprecating the fields option + // Log.debug(`fields option has been deprecated, please use the new 'projection' instead`) + + return { + ...otherOptions, + ...(projection || fields ? { projection: fields || projection } : {}), + }; +}; diff --git a/packages/mongo/observe_changes_tests.js b/packages/mongo/observe_changes_tests.js index 0d920deac7..56ab79db7c 100644 --- a/packages/mongo/observe_changes_tests.js +++ b/packages/mongo/observe_changes_tests.js @@ -48,17 +48,16 @@ _.each ([{added: 'added', forceOrdered: true}, handle.stop(); - var badCursor = c.find({}, {fields: {noodles: 1, _id: false}}); test.throws(function () { - badCursor.observeChanges(logger); - }); + c.find({}, {fields: {noodles: 1, _id: false}}) + }, undefined, 'bad cursor excluding _id from projection'); onComplete(); }); }); }); -Tinytest.addAsync("observeChanges - callback isolation", function (test, onComplete) { +Tinytest.onlyAsync("observeChanges - callback isolation", function (test, onComplete) { var c = makeCollection(); withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, function (logger) { var handles = []; @@ -420,5 +419,5 @@ if (Meteor.isServer) { }); c.insert({ type: { name: 'foobar' } }); } - ); + ); } diff --git a/packages/tinytest/tinytest.js b/packages/tinytest/tinytest.js index fadb83d3eb..045548c6de 100644 --- a/packages/tinytest/tinytest.js +++ b/packages/tinytest/tinytest.js @@ -203,7 +203,7 @@ export class TestCaseResults { // The upshot is, if you want to test whether an error is of a // particular class, use a predicate function. // - throws(f, expected) { + throws(f, expected, message) { var actual, predicate; if (expected === undefined) { @@ -236,9 +236,9 @@ export class TestCaseResults { else this.fail({ type: "throws", - message: actual ? + message: (actual ? "wrong error thrown: " + actual.message : - "did not throw an error as expected" + "did not throw an error as expected") + (message ? ": " + message : ""), }); } From f0508aa0658fc78f415242f62bd8d970f4fbef81 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Tue, 4 Jan 2022 16:24:56 -0300 Subject: [PATCH 056/393] Fix test searching for mongo v4 - replace with v5 --- tools/tests/mongo.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/tests/mongo.js b/tools/tests/mongo.js index 14bd25eee9..a82e98b9f1 100644 --- a/tools/tests/mongo.js +++ b/tools/tests/mongo.js @@ -40,7 +40,7 @@ function testMeteorMongo(appDir) { // Make sure we match the DB version that's printed as part of the // non-quiet shell startup text, so that we don't confuse it with the // output of the db.version() command below. - mongoRun.match(/MongoDB server version: 4\.\d+\.\d+/); + mongoRun.match(/MongoDB server version: 5\.\d+\.\d+/); // Make sure the shell does not display the banner about Mongo's free // monitoring service. @@ -48,7 +48,7 @@ function testMeteorMongo(appDir) { // Note: when mongo shell's input is not a tty, there is no prompt. mongoRun.write('db.version()\n'); - mongoRun.match(/4\.\d+\.\d+/); + mongoRun.match(/5\.\d+\.\d+/); mongoRun.stop(); run.stop(); From a5890b99931de266562f63cf5476c1a5a3c2ce4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Tue, 4 Jan 2022 15:42:30 -0400 Subject: [PATCH 057/393] Cleaning up tests --- packages/mongo/mongo_driver.js | 2 -- packages/mongo/mongo_livedata_tests.js | 9 +-------- packages/mongo/observe_changes_tests.js | 2 +- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index c9f496f1e6..3b4ecec08e 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -644,8 +644,6 @@ MongoConnection.prototype._update = function (collection_name, selector, mod, }; var transformResult = function (driverResult) { - console.log(`driverResult`, driverResult); - var meteorResult = { numberAffected: 0 }; if (driverResult) { var mongoResult = driverResult.result; diff --git a/packages/mongo/mongo_livedata_tests.js b/packages/mongo/mongo_livedata_tests.js index 2d490e90de..ec241d2ee1 100644 --- a/packages/mongo/mongo_livedata_tests.js +++ b/packages/mongo/mongo_livedata_tests.js @@ -357,8 +357,6 @@ Tinytest.addAsync("mongo-livedata - basics, " + idGeneration, function (test, on expectObserve('', function () { var count = coll.update({run: run, x: -1}, {$inc: {x: 2}}, {multi: true}); - console.log(`count`, count); - test.equal(count, 0); }); @@ -768,16 +766,11 @@ if (Meteor.isServer) { test.isTrue(observeMultiplexer); test.isTrue(observeMultiplexer === o2.handle._multiplexer); - console.log('runInFence filipe') - console.log(`o1.output`, o1.output); - // Update. Both observes fire. runInFence(function () { coll.update(docId1, {$set: {x: 'y'}}); }); - console.log('runInFence filipe 2') - console.log(`o1.output `, o1.output); - test.length(o1.output, 1, 'filipe'); + test.length(o1.output, 1, 'test that is breaking'); test.length(o2.output, 1); test.equal(o1.output.shift(), {changed: docId1}); test.equal(o2.output.shift(), {changed: docId1}); diff --git a/packages/mongo/observe_changes_tests.js b/packages/mongo/observe_changes_tests.js index 56ab79db7c..5dab99445a 100644 --- a/packages/mongo/observe_changes_tests.js +++ b/packages/mongo/observe_changes_tests.js @@ -57,7 +57,7 @@ _.each ([{added: 'added', forceOrdered: true}, }); }); -Tinytest.onlyAsync("observeChanges - callback isolation", function (test, onComplete) { +Tinytest.addAsync("observeChanges - callback isolation", function (test, onComplete) { var c = makeCollection(); withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, function (logger) { var handles = []; From 0e0da07e0bada81ca5f4bdcbc33e43057adc32fa Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Tue, 4 Jan 2022 17:02:32 -0300 Subject: [PATCH 058/393] New dev-bundle for Mongo 5 --- meteor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meteor b/meteor index fb2f73f60d..d5bb41a990 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/usr/bin/env bash -BUNDLE_VERSION=14.18.2.4 +BUNDLE_VERSION=14.18.2.5 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. From cbf8775c17bc600194800f855a421fc94762114f Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Wed, 5 Jan 2022 12:26:48 -0300 Subject: [PATCH 059/393] Fix oplog parsing for mongodb 5 and result parsing for replace operations. Fix tests based on mongo version and dependent on mongo driver internals. --- packages/mongo/collection_tests.js | 8 +++++--- packages/mongo/mongo_driver.js | 20 +++++++++++--------- packages/mongo/mongo_livedata_tests.js | 16 +++++++++------- packages/mongo/oplog_observe_driver.js | 3 ++- 4 files changed, 27 insertions(+), 20 deletions(-) diff --git a/packages/mongo/collection_tests.js b/packages/mongo/collection_tests.js index ea17630507..e6f3228392 100644 --- a/packages/mongo/collection_tests.js +++ b/packages/mongo/collection_tests.js @@ -170,13 +170,15 @@ Tinytest.add('collection - calling find with a valid readPreference', defaultCursor.count(); customCursor.count(); + // defaultCursor._synchronousCursor._dbCursor.operation is not an option anymore + // defaultCursor._synchronousCursor._dbCursor.option is test.equal( - defaultCursor._synchronousCursor._dbCursor.operation.readPreference + defaultCursor._synchronousCursor._dbCursor.options.readPreference .mode, defaultReadPreference ); test.equal( - customCursor._synchronousCursor._dbCursor.operation.readPreference.mode, + customCursor._synchronousCursor._dbCursor.options.readPreference.mode, customReadPreference ); } @@ -199,4 +201,4 @@ Tinytest.add('collection - calling find with an invalid readPreference', }, `Invalid read preference mode ${invalidReadPreference}`); } } -); \ No newline at end of file +); diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 3b4ecec08e..e9d86c8be0 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -611,7 +611,7 @@ MongoConnection.prototype._update = function (collection_name, selector, mod, const strings = Object.keys(mongoMod).filter((key) => !key.startsWith("$")); let updateMethod = strings.length > 0 ? 'replaceOne' : 'updateMany'; updateMethod = updateMethod === 'updateMany' && !mongoOpts.multi ? 'updateOne' : updateMethod; - collection[updateMethod]( + collection[updateMethod].bind(collection)( mongoSelector, mongoMod, mongoOpts, bindEnvironmentForWrite(function (err, result) { if (! err) { @@ -651,9 +651,9 @@ var transformResult = function (driverResult) { // upserted values -- even with options.multi, when the upsert does insert, // it only inserts one element. if (mongoResult.upsertedCount) { - meteorResult.numberAffected += mongoResult.upsertedCount.length; + meteorResult.numberAffected = mongoResult.upsertedCount; - if (mongoResult.upsertedCount.length === 1) { + if (mongoResult.upsertedId) { meteorResult.insertedId = mongoResult.upsertedId; } } else { @@ -727,15 +727,17 @@ var simulateUpsertWithInsertedId = function (collection, selector, mod, } else { let method = collection.updateMany; if(!Object.keys(mod).some(key => key.startsWith("$"))){ - method = collection.replaceOne; + method = collection.replaceOne.bind(collection); + mod = replacementWithId; } method(selector, mod, {upsert:true,...mongoOptsForUpdate}, bindEnvironmentForWrite(function (err, result) { if (err) { callback(err); - } else if (result && result.result.n != 0) { + } else if (result && (result.modifiedCount || result.upsertedCount)) { callback(null, { - numberAffected: result.result.n + numberAffected: result.modifiedCount || result.upsertedCount, + insertedId: result.upsertedId }); } else { doConditionalInsert(); @@ -745,7 +747,7 @@ var simulateUpsertWithInsertedId = function (collection, selector, mod, }; var doConditionalInsert = function () { - collection.updateMany(selector, replacementWithId, mongoOptsForInsert, + collection.replaceOne(selector, replacementWithId, mongoOptsForInsert, bindEnvironmentForWrite(function (err, result) { if (err) { // figure out if this is a @@ -758,8 +760,8 @@ var simulateUpsertWithInsertedId = function (collection, selector, mod, } } else { callback(null, { - numberAffected: result.result.upserted.length, - insertedId: insertedId, + numberAffected: result.upsertedCount, + insertedId: result.upsertedId, }); } })); diff --git a/packages/mongo/mongo_livedata_tests.js b/packages/mongo/mongo_livedata_tests.js index ec241d2ee1..55c4f3ad6e 100644 --- a/packages/mongo/mongo_livedata_tests.js +++ b/packages/mongo/mongo_livedata_tests.js @@ -720,8 +720,6 @@ if (Meteor.isServer) { var output = []; var callbacks = { changed: function (newDoc) { - console.log(`newDoc`, newDoc); - output.push({changed: newDoc._id}); } }; @@ -1773,7 +1771,10 @@ _.each(Meteor.isServer ? [true, false] : [true], function (minimongo) { coll = coll._collection; var result1 = upsert(coll, useUpdate, {foo: 'bar'}, {foo: 'bar'}); + console.log(result1) test.equal(result1.numberAffected, 1); + console.log("result1"); + console.log(result1); if (! skipIds) test.isTrue(result1.insertedId); compareResults(test, skipIds, coll.find().fetch(), [{foo: 'bar', _id: result1.insertedId}]); @@ -1860,6 +1861,8 @@ _.each(Meteor.isServer ? [true, false] : [true], function (minimongo) { {name: 'Steve'}, {insertedId: 'steve'}); test.equal(result9.numberAffected, 1); + console.log("result9") + console.log(result9) if (! skipIds) test.equal(result9.insertedId, 'steve'); compareResults(test, skipIds, coll.find().fetch(), @@ -3306,12 +3309,11 @@ Meteor.isServer && Tinytest.add( } ); -Meteor.isServer && Tinytest.add("mongo-livedata - npm modules", function (test) { +Meteor.isServer && Tinytest.only("mongo-livedata - npm modules", function (test) { // Make sure the version number looks like a version number. - test.matches(MongoInternals.NpmModules.mongodb.version, /^3\.(\d+)\.(\d+)/); - test.equal(typeof(MongoInternals.NpmModules.mongodb.module), 'function'); - test.equal(typeof(MongoInternals.NpmModules.mongodb.module.connect), - 'function'); + test.matches(MongoInternals.NpmModules.mongodb.version, /^4\.(\d+)\.(\d+)/); + console.log(MongoInternals.NpmModules.mongodb) + test.equal(typeof(MongoInternals.NpmModules.mongodb.module), 'object'); test.equal(typeof(MongoInternals.NpmModules.mongodb.module.ObjectID), 'function'); diff --git a/packages/mongo/oplog_observe_driver.js b/packages/mongo/oplog_observe_driver.js index 8c372faf83..55fe120516 100644 --- a/packages/mongo/oplog_observe_driver.js +++ b/packages/mongo/oplog_observe_driver.js @@ -602,7 +602,8 @@ _.extend(OplogObserveDriver.prototype, { // database to figure out if the whole document matches the selector) or // a replacement (in which case we can just directly re-evaluate the // selector)? - var isReplace = !_.has(op.o, '$set') && !_.has(op.o, '$unset'); + // oplog format has changed on mongodb 5, we have to support both now + var isReplace = !_.has(op.o, '$set') && !_.has(op.o, 'diff') && !_.has(op.o, '$unset'); // If this modifier modifies something inside an EJSON custom type (ie, // anything with EJSON$), then we can't try to use // LocalCollection._modify, since that just mutates the EJSON encoding, From f434bcd5d57aece6b537b3947faeedf572229d27 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Wed, 5 Jan 2022 15:03:51 -0300 Subject: [PATCH 060/393] Use async functions for running insert/delete Fix "projection" field usage on observers --- packages/minimongo/common.js | 2 +- packages/minimongo/cursor.js | 2 +- packages/mongo/collection_tests.js | 13 +-- packages/mongo/mongo_driver.js | 120 +++++++++++++++---------- packages/mongo/mongo_livedata_tests.js | 4 +- packages/mongo/oplog_observe_driver.js | 4 +- 6 files changed, 89 insertions(+), 56 deletions(-) diff --git a/packages/minimongo/common.js b/packages/minimongo/common.js index 213396301f..82d966fb2b 100644 --- a/packages/minimongo/common.js +++ b/packages/minimongo/common.js @@ -857,7 +857,7 @@ export function isOperatorObject(valueSelector, inconsistentOK) { let theseAreOperators = undefined; Object.keys(valueSelector).forEach(selKey => { - const thisIsOperator = selKey.substr(0, 1) === '$'; + const thisIsOperator = selKey.substr(0, 1) === '$' || selKey === 'diff'; if (theseAreOperators === undefined) { theseAreOperators = thisIsOperator; diff --git a/packages/minimongo/cursor.js b/packages/minimongo/cursor.js index 8d5a938be2..a555fb74c0 100644 --- a/packages/minimongo/cursor.js +++ b/packages/minimongo/cursor.js @@ -25,7 +25,7 @@ export default class Cursor { this.skip = options.skip || 0; this.limit = options.limit; - this.fields = options.fields; + this.fields = options.projection || options.fields; this._projectionFn = LocalCollection._compileProjection(this.fields || {}); diff --git a/packages/mongo/collection_tests.js b/packages/mongo/collection_tests.js index e6f3228392..da34c62792 100644 --- a/packages/mongo/collection_tests.js +++ b/packages/mongo/collection_tests.js @@ -94,7 +94,7 @@ Tinytest.add('collection - call native find with sort function', return doc.a; }); }, - /Illegal sort clause/ + /Invalid sort format: undefined Sort must be a valid object/ ); } } @@ -159,7 +159,7 @@ Tinytest.add('collection - calling find with a valid readPreference', if (Meteor.isServer) { const defaultReadPreference = 'primary'; const customReadPreference = 'secondaryPreferred'; - const collection = new Mongo.Collection('readPreferenceTest1'); + const collection = new Mongo.Collection('readPreferenceTest'); const defaultCursor = collection.find(); const customCursor = collection.find( {}, @@ -171,14 +171,15 @@ Tinytest.add('collection - calling find with a valid readPreference', customCursor.count(); // defaultCursor._synchronousCursor._dbCursor.operation is not an option anymore - // defaultCursor._synchronousCursor._dbCursor.option is + // as the cursor options are now private + // You can check on abstract_cursor.ts the exposed public getters test.equal( - defaultCursor._synchronousCursor._dbCursor.options.readPreference + defaultCursor._synchronousCursor._dbCursor.readPreference .mode, defaultReadPreference ); test.equal( - customCursor._synchronousCursor._dbCursor.options.readPreference.mode, + customCursor._synchronousCursor._dbCursor.readPreference.mode, customReadPreference ); } @@ -198,7 +199,7 @@ Tinytest.add('collection - calling find with an invalid readPreference', test.throws(function() { // Trigger the creation of _synchronousCursor cursor.count(); - }, `Invalid read preference mode ${invalidReadPreference}`); + }, `Invalid read preference mode "${invalidReadPreference}"`); } } ); diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index e9d86c8be0..849c3396f3 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -376,8 +376,16 @@ MongoConnection.prototype._insert = function (collection_name, document, callback = bindEnvironmentForWrite(writeCallback(write, refresh, callback)); try { var collection = self.rawCollection(collection_name); - collection.insertOne(replaceTypes(document, replaceMeteorAtomWithMongo), - {safe: true}, callback); + collection.insertOne( + replaceTypes(document, replaceMeteorAtomWithMongo), + { + safe: true, + } + ).then(({insertedId}) => { + callback(null, transformResult({ result : {modifiedCount : insertedId ? 1 : 0} }).numberAffected); + }).catch((e) => { + callback(e) + }); } catch (err) { write.committed(); throw err; @@ -424,15 +432,15 @@ MongoConnection.prototype._remove = function (collection_name, selector, try { var collection = self.rawCollection(collection_name); - let result = {}; - try { - const {deletedCount} = collection.deleteMany(replaceTypes(selector, replaceMeteorAtomWithMongo), - {safe: true}).await(); - result.modifiedCount = deletedCount; - }catch(err){ - callback(err) - } - callback(null, transformResult({result}).numberAffected) + collection + .deleteMany(replaceTypes(selector, replaceMeteorAtomWithMongo), { + safe: true, + }) + .then(({ deletedCount }) => { + callback(null, transformResult({ result : {modifiedCount : deletedCount} }).numberAffected); + }).catch((err) => { + callback(err); + }); } catch (err) { write.committed(); throw err; @@ -610,7 +618,11 @@ MongoConnection.prototype._update = function (collection_name, selector, mod, const strings = Object.keys(mongoMod).filter((key) => !key.startsWith("$")); let updateMethod = strings.length > 0 ? 'replaceOne' : 'updateMany'; - updateMethod = updateMethod === 'updateMany' && !mongoOpts.multi ? 'updateOne' : updateMethod; + updateMethod = + updateMethod === 'updateMany' && !mongoOpts.multi + ? 'updateOne' + : updateMethod; + console.log(`Choose method ${updateMethod} for ${JSON.stringify(mongoMod)}`) collection[updateMethod].bind(collection)( mongoSelector, mongoMod, mongoOpts, bindEnvironmentForWrite(function (err, result) { @@ -727,44 +739,60 @@ var simulateUpsertWithInsertedId = function (collection, selector, mod, } else { let method = collection.updateMany; if(!Object.keys(mod).some(key => key.startsWith("$"))){ + console.log(`Choose method replace for ${JSON.stringify(mod)} on selector ${JSON.stringify(selector)}`) + method = collection.replaceOne.bind(collection); mod = replacementWithId; + console.trace("Aqui") } - method(selector, mod, {upsert:true,...mongoOptsForUpdate}, - bindEnvironmentForWrite(function (err, result) { - if (err) { - callback(err); - } else if (result && (result.modifiedCount || result.upsertedCount)) { - callback(null, { - numberAffected: result.modifiedCount || result.upsertedCount, - insertedId: result.upsertedId - }); - } else { - doConditionalInsert(); - } - })); + console.log("mongoOptsForUpdate"); + console.log(mongoOptsForUpdate); + console.log("method"); + console.log(method); + method( + selector, + mod, + { upsert: true, ...mongoOptsForUpdate }, + bindEnvironmentForWrite(function(err, result) { + console.log(err) + if (err) { + callback(err); + } else if (result && (result.modifiedCount || result.upsertedCount)) { + callback(null, { + numberAffected: result.modifiedCount || result.upsertedCount, + insertedId: result.upsertedId, + }); + } else { + doConditionalInsert(); + } + }) + ); } }; - var doConditionalInsert = function () { - collection.replaceOne(selector, replacementWithId, mongoOptsForInsert, - bindEnvironmentForWrite(function (err, result) { - if (err) { - // figure out if this is a - // "cannot change _id of document" error, and - // if so, try doUpdate() again, up to 3 times. - if (MongoConnection._isCannotChangeIdError(err)) { - doUpdate(); - } else { - callback(err); - } - } else { - callback(null, { - numberAffected: result.upsertedCount, - insertedId: result.upsertedId, - }); - } - })); + var doConditionalInsert = function() { + collection.replaceOne( + selector, + replacementWithId, + mongoOptsForInsert, + bindEnvironmentForWrite(function(err, result) { + if (err) { + // figure out if this is a + // "cannot change _id of document" error, and + // if so, try doUpdate() again, up to 3 times. + if (MongoConnection._isCannotChangeIdError(err)) { + doUpdate(); + } else { + callback(err); + } + } else { + callback(null, { + numberAffected: result.upsertedCount, + insertedId: result.upsertedId, + }); + } + }) + ); }; doUpdate(); @@ -977,8 +1005,8 @@ MongoConnection.prototype._createSynchronousCursor = function( sort: cursorOptions.sort, limit: cursorOptions.limit, skip: cursorOptions.skip, - projection: cursorOptions.fields, - readPreference: cursorOptions.readPreference + projection: cursorOptions.fields || cursorOptions.projection, + readPreference: cursorOptions.readPreference, }; // Do we want a tailable cursor (which only works on capped collections)? diff --git a/packages/mongo/mongo_livedata_tests.js b/packages/mongo/mongo_livedata_tests.js index 55c4f3ad6e..20aacf8f18 100644 --- a/packages/mongo/mongo_livedata_tests.js +++ b/packages/mongo/mongo_livedata_tests.js @@ -1779,7 +1779,9 @@ _.each(Meteor.isServer ? [true, false] : [true], function (minimongo) { test.isTrue(result1.insertedId); compareResults(test, skipIds, coll.find().fetch(), [{foo: 'bar', _id: result1.insertedId}]); + // !!!! var result2 = upsert(coll, useUpdate, {foo: 'bar'}, {foo: 'baz'}); + // !!!! test.equal(result2.numberAffected, 1); if (! skipIds) test.isFalse(result2.insertedId); @@ -3309,7 +3311,7 @@ Meteor.isServer && Tinytest.add( } ); -Meteor.isServer && Tinytest.only("mongo-livedata - npm modules", function (test) { +Meteor.isServer && Tinytest.add("mongo-livedata - npm modules", function (test) { // Make sure the version number looks like a version number. test.matches(MongoInternals.NpmModules.mongodb.version, /^4\.(\d+)\.(\d+)/); console.log(MongoInternals.NpmModules.mongodb) diff --git a/packages/mongo/oplog_observe_driver.js b/packages/mongo/oplog_observe_driver.js index 55fe120516..ec669c834c 100644 --- a/packages/mongo/oplog_observe_driver.js +++ b/packages/mongo/oplog_observe_driver.js @@ -86,7 +86,9 @@ OplogObserveDriver = function (options) { self._registerPhaseChange(PHASE.QUERYING); self._matcher = options.matcher; - var projection = self._cursorDescription.options.fields || {}; + // we are now using projection, not fields in the cursor description even if you pass {fields} + // in the cursor construction + var projection = self._cursorDescription.options.fields || self._cursorDescription.options.projection || {}; self._projectionFn = LocalCollection._compileProjection(projection); // Projection function, result of combining important fields for selector and // existing fields projection From f6654d982a3060e1179453a8e509e021d8b8d460 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Wed, 5 Jan 2022 18:34:42 -0300 Subject: [PATCH 061/393] Remove logs Fix undefined returned from the node-driver while we were expecting null in the err field --- packages/mongo/mongo_driver.js | 37 ++++++++++---------------- packages/mongo/mongo_livedata_tests.js | 5 ---- 2 files changed, 14 insertions(+), 28 deletions(-) diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 849c3396f3..478d7fd55b 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -376,16 +376,16 @@ MongoConnection.prototype._insert = function (collection_name, document, callback = bindEnvironmentForWrite(writeCallback(write, refresh, callback)); try { var collection = self.rawCollection(collection_name); - collection.insertOne( - replaceTypes(document, replaceMeteorAtomWithMongo), - { - safe: true, - } - ).then(({insertedId}) => { - callback(null, transformResult({ result : {modifiedCount : insertedId ? 1 : 0} }).numberAffected); - }).catch((e) => { - callback(e) - }); + collection.insertOne( + replaceTypes(document, replaceMeteorAtomWithMongo), + { + safe: true, + } + ).then(({insertedId}) => { + callback(null, transformResult({ result : {modifiedCount : insertedId ? 1 : 0} }).numberAffected); + }).catch((e) => { + callback(e, null) + }); } catch (err) { write.committed(); throw err; @@ -622,10 +622,10 @@ MongoConnection.prototype._update = function (collection_name, selector, mod, updateMethod === 'updateMany' && !mongoOpts.multi ? 'updateOne' : updateMethod; - console.log(`Choose method ${updateMethod} for ${JSON.stringify(mongoMod)}`) collection[updateMethod].bind(collection)( mongoSelector, mongoMod, mongoOpts, - bindEnvironmentForWrite(function (err, result) { + // mongo driver now returns undefined for err in the callback + bindEnvironmentForWrite(function (err = null, result) { if (! err) { var meteorResult = transformResult({result}); if (meteorResult && options._returnObject) { @@ -671,7 +671,7 @@ var transformResult = function (driverResult) { } else { // n was used before Mongo 5.0, in Mongo 5.0 we are not receiving this n // field and so we are using modifiedCount instead - meteorResult.numberAffected = mongoResult.n || mongoResult.modifiedCount; + meteorResult.numberAffected = mongoResult.n || mongoResult.matchedCount || mongoResult.modifiedCount; } } @@ -739,28 +739,19 @@ var simulateUpsertWithInsertedId = function (collection, selector, mod, } else { let method = collection.updateMany; if(!Object.keys(mod).some(key => key.startsWith("$"))){ - console.log(`Choose method replace for ${JSON.stringify(mod)} on selector ${JSON.stringify(selector)}`) - method = collection.replaceOne.bind(collection); - mod = replacementWithId; - console.trace("Aqui") } - console.log("mongoOptsForUpdate"); - console.log(mongoOptsForUpdate); - console.log("method"); - console.log(method); method( selector, mod, { upsert: true, ...mongoOptsForUpdate }, bindEnvironmentForWrite(function(err, result) { - console.log(err) if (err) { callback(err); } else if (result && (result.modifiedCount || result.upsertedCount)) { callback(null, { numberAffected: result.modifiedCount || result.upsertedCount, - insertedId: result.upsertedId, + insertedId: result.upsertedId || undefined, }); } else { doConditionalInsert(); diff --git a/packages/mongo/mongo_livedata_tests.js b/packages/mongo/mongo_livedata_tests.js index 20aacf8f18..60a87a4d3c 100644 --- a/packages/mongo/mongo_livedata_tests.js +++ b/packages/mongo/mongo_livedata_tests.js @@ -1771,10 +1771,7 @@ _.each(Meteor.isServer ? [true, false] : [true], function (minimongo) { coll = coll._collection; var result1 = upsert(coll, useUpdate, {foo: 'bar'}, {foo: 'bar'}); - console.log(result1) test.equal(result1.numberAffected, 1); - console.log("result1"); - console.log(result1); if (! skipIds) test.isTrue(result1.insertedId); compareResults(test, skipIds, coll.find().fetch(), [{foo: 'bar', _id: result1.insertedId}]); @@ -1863,8 +1860,6 @@ _.each(Meteor.isServer ? [true, false] : [true], function (minimongo) { {name: 'Steve'}, {insertedId: 'steve'}); test.equal(result9.numberAffected, 1); - console.log("result9") - console.log(result9) if (! skipIds) test.equal(result9.insertedId, 'steve'); compareResults(test, skipIds, coll.find().fetch(), From d5c47237849faf43396df80357f8ea8a5874a27e Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Thu, 6 Jan 2022 09:28:02 -0300 Subject: [PATCH 062/393] Fix wrong upsert param passed to mongo method --- packages/mongo/mongo_driver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 478d7fd55b..5c9f9145bb 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -744,7 +744,7 @@ var simulateUpsertWithInsertedId = function (collection, selector, mod, method( selector, mod, - { upsert: true, ...mongoOptsForUpdate }, + mongoOptsForUpdate, bindEnvironmentForWrite(function(err, result) { if (err) { callback(err); From 155ae639ee590bae66237fc1c29295072ec92aef Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Thu, 6 Jan 2022 18:48:27 -0300 Subject: [PATCH 063/393] Create oplog v2 converter for watching changes in the newest mongodb 5 format --- packages/mongo/mongo_driver.js | 7 +- packages/mongo/mongo_livedata_tests.js | 1 - packages/mongo/oplog_observe_driver.js | 5 + packages/mongo/oplog_v2_converter.js | 113 +++++++++++++++++++++ packages/mongo/oplog_v2_converter_tests.js | 77 ++++++++++++++ packages/mongo/package.js | 3 +- 6 files changed, 201 insertions(+), 5 deletions(-) create mode 100644 packages/mongo/oplog_v2_converter.js create mode 100644 packages/mongo/oplog_v2_converter_tests.js diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 5c9f9145bb..0f2f5932ef 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -1293,9 +1293,10 @@ MongoConnection.prototype._observeChanges = function ( // You may not filter out _id when observing changes, because the id is a core // part of the observeChanges API. - if (cursorDescription.options.fields && - (cursorDescription.options.fields._id === 0 || - cursorDescription.options.fields._id === false)) { + const fieldsOptions = cursorDescription.options.projection || cursorDescription.options.fields; + if (fieldsOptions && + (fieldsOptions._id === 0 || + fieldsOptions._id === false)) { throw Error("You may not observe a cursor with {fields: {_id: 0}}"); } diff --git a/packages/mongo/mongo_livedata_tests.js b/packages/mongo/mongo_livedata_tests.js index 60a87a4d3c..02b62d3f05 100644 --- a/packages/mongo/mongo_livedata_tests.js +++ b/packages/mongo/mongo_livedata_tests.js @@ -3309,7 +3309,6 @@ Meteor.isServer && Tinytest.add( Meteor.isServer && Tinytest.add("mongo-livedata - npm modules", function (test) { // Make sure the version number looks like a version number. test.matches(MongoInternals.NpmModules.mongodb.version, /^4\.(\d+)\.(\d+)/); - console.log(MongoInternals.NpmModules.mongodb) test.equal(typeof(MongoInternals.NpmModules.mongodb.module), 'object'); test.equal(typeof(MongoInternals.NpmModules.mongodb.module.ObjectID), 'function'); diff --git a/packages/mongo/oplog_observe_driver.js b/packages/mongo/oplog_observe_driver.js index ec669c834c..e068058af6 100644 --- a/packages/mongo/oplog_observe_driver.js +++ b/packages/mongo/oplog_observe_driver.js @@ -1,3 +1,5 @@ +import { oplogV2V1Converter } from "./oplog_v2_converter"; + var Future = Npm.require('fibers/future'); var PHASE = { @@ -600,6 +602,9 @@ _.extend(OplogObserveDriver.prototype, { if (self._matcher.documentMatches(op.o).result) self._addMatching(op.o); } else if (op.op === 'u') { + // we are mapping the new oplog format on mongo 5 + // to what we know better, $set + op.o = oplogV2V1Converter(op.o) // Is this a modifier ($set/$unset, which may require us to poll the // database to figure out if the whole document matches the selector) or // a replacement (in which case we can just directly re-evaluate the diff --git a/packages/mongo/oplog_v2_converter.js b/packages/mongo/oplog_v2_converter.js new file mode 100644 index 0000000000..d53ad1ff61 --- /dev/null +++ b/packages/mongo/oplog_v2_converter.js @@ -0,0 +1,113 @@ +// we are mapping the new oplog format on mongo 5 +// to what we know better, $set and $unset format +// new oplog format ex: +// { +// '$v': 2, +// diff: { u: { key1: 2022-01-06T18:23:16.131Z, key2: [ObjectID] } } +// } + +/* +the structure of an entry is: + + +-> entry: i, u, d + sFields. +-> sFields: i, u, d + sFields +-> sFields: arrayOperator -> { a: true, u0: 2 } +-> i,u,d: { key: value } +-> value: {key: value} + +i and u are both $set +d is $unset +on mongo 4 + */ + +const isArrayOperator = possibleArrayOperator => { + if (!Object.keys(possibleArrayOperator).length) return false; + + let isArrayOperator = true; + Object.keys(possibleArrayOperator).forEach(key => { + if (key !== 'a' && !key.match(/u\d+/)) { + isArrayOperator = false; + } + }); + return isArrayOperator; +}; +const nestedOplogEntryParsers = ( + { i = {}, u = {}, d = {}, ...sFields }, + prefixKey = '' +) => { + const sFieldsOperators = []; + Object.entries(sFields).forEach(([key, value]) => { + if (isArrayOperator(value || {})) { + const { a, ...uPosition } = value; + const [positionKey, newArrayIndexValue] = Object.entries(uPosition)[0]; + if (uPosition) + sFieldsOperators.push({ + [newArrayIndexValue === null ? '$unset' : '$set']: { + [`${prefixKey}${key.substring(1)}.${positionKey.substring(1)}`]: + newArrayIndexValue === null ? true : newArrayIndexValue, + }, + }); + } else { + sFieldsOperators.push( + nestedOplogEntryParsers(value, `${prefixKey}${key.substring(1)}.`) + ); + } + }); + const $unset = Object.keys(d).reduce((acc, key) => { + return { ...acc, [`${prefixKey}${key}`]: true }; + }, {}); + const setObjectSource = { ...i, ...u }; + const $set = Object.keys(setObjectSource).reduce((acc, key) => { + const prefixedKey = `${prefixKey}${key}`; + + return { + ...acc, + ...(typeof setObjectSource[key] === 'object' + ? flattenObject({ [prefixedKey]: setObjectSource[key] }) + : { + [prefixedKey]: setObjectSource[key], + }), + }; + }, {}); + + const c = [...sFieldsOperators, { $unset, $set }]; + const { $set: s, $unset: un } = c.reduce( + (acc, { $set: set = {}, $unset: unset = {} }) => { + return { + $set: { ...acc.$set, ...set }, + $unset: { ...acc.$unset, ...unset }, + }; + }, + {} + ); + return { + ...(Object.keys(s).length ? { $set: s } : {}), + ...(Object.keys(un).length ? { $unset: un } : {}), + }; +}; + +export const oplogV2V1Converter = v2OplogEntry => { + if (v2OplogEntry.$v !== 2) return v2OplogEntry; + return { $v: 2, ...nestedOplogEntryParsers(v2OplogEntry.diff || {}) }; +}; + +function flattenObject(ob) { + var toReturn = {}; + + for (const i in ob) { + if (!ob.hasOwnProperty(i)) continue; + + if (typeof ob[i] == 'object' && ob[i] !== null) { + var flatObject = flattenObject(ob[i]); + for (const x in flatObject) { + if (!flatObject.hasOwnProperty(x)) continue; + + toReturn[i + '.' + x] = flatObject[x]; + } + } else { + toReturn[i] = ob[i]; + } + } + return toReturn; +} diff --git a/packages/mongo/oplog_v2_converter_tests.js b/packages/mongo/oplog_v2_converter_tests.js new file mode 100644 index 0000000000..5620c786fb --- /dev/null +++ b/packages/mongo/oplog_v2_converter_tests.js @@ -0,0 +1,77 @@ +import { oplogV2V1Converter } from './oplog_v2_converter'; + +Tinytest.add('oplog - v2/v1 conversion', function(test) { + const entry1 = { + $v: 2, + diff: { scustom: { sEJSON$value: { u: { EJSONtail: 'd' } } } }, + }; + const entry2 = { + $v: 2, + diff: { u: { d: '2', oi: 'asdas' } }, + }; + //set inside an array + const entry3 = { $v: 2, diff: { sasd: { a: true, u0: 2 } } }; + //unset inside an array + const entry4 = { $v: 2, diff: { sasd: { a: true, u0: null } } }; + + //set a new nested field inside an object + const entry5 = { + $v: 2, + diff: { i: { a: { b: 2 } } }, + }; + + //set an existing nested field inside an object + const entry6 = { + $v: 2, + diff: { sa: { i: { b: 3, c: 1 } } }, + }; + + //unset an existing nested field inside an object + const entry7 = { + $v: 2, + diff: { sa: { d: { b: false } } }, + }; + const entry8 = { $v: 2, diff: { u: { c: 'bar' }, sb: { a: true, u0: 2 } } }; + + test.equal( + JSON.stringify(oplogV2V1Converter(entry1)), + JSON.stringify({ + $v: 2, + $set: { 'custom.EJSON$value.EJSONtail': 'd' }, + }) + ); + test.equal( + JSON.stringify(oplogV2V1Converter(entry2)), + JSON.stringify({ + $v: 2, + $set: { d: '2', oi: 'asdas' }, + }) + ); + test.equal( + JSON.stringify(oplogV2V1Converter(entry3)), + JSON.stringify({ $v: 2, $set: { 'asd.0': 2 } }) + ); + test.equal( + JSON.stringify(oplogV2V1Converter(entry4)), + JSON.stringify({ $v: 2, $unset: { 'asd.0': true } }) + ); + test.equal( + JSON.stringify(oplogV2V1Converter(entry5)), + JSON.stringify({ $v: 2, $set: { 'a.b': 2 } }) + ); + test.equal( + JSON.stringify(oplogV2V1Converter(entry6)), + JSON.stringify({ + $v: 2, + $set: { 'a.b': 3, 'a.c': 1 }, + }) + ); + test.equal( + JSON.stringify(oplogV2V1Converter(entry7)), + JSON.stringify({ $v: 2, $unset: { 'a.b': true } }) + ); + test.equal( + JSON.stringify(oplogV2V1Converter(entry8)), + JSON.stringify({ $v: 2, $set: { 'b.0': 2, c: 'bar' } }) + ); +}); diff --git a/packages/mongo/package.js b/packages/mongo/package.js index cb493ea58b..c0c030f892 100644 --- a/packages/mongo/package.js +++ b/packages/mongo/package.js @@ -76,7 +76,7 @@ Package.onUse(function (api) { api.addFiles(['mongo_driver.js', 'oplog_tailing.js', 'observe_multiplex.js', 'doc_fetcher.js', - 'polling_observe_driver.js','oplog_observe_driver.js'], + 'polling_observe_driver.js','oplog_observe_driver.js', 'oplog_v2_converter.js'], 'server'); api.addFiles('local_collection_driver.js', ['client', 'server']); api.addFiles('remote_collection_driver.js', 'server'); @@ -98,5 +98,6 @@ Package.onTest(function (api) { api.addFiles('collection_tests.js', ['client', 'server']); api.addFiles('observe_changes_tests.js', ['client', 'server']); api.addFiles('oplog_tests.js', 'server'); + api.addFiles('oplog_v2_converter_tests.js', 'server'); api.addFiles('doc_fetcher_tests.js', 'server'); }); From 20740c637f71137ee8c9754d400c4b2fcb5bcabe Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Thu, 6 Jan 2022 20:40:52 -0300 Subject: [PATCH 064/393] Remove minimongo asserts based on skipLimit on count cursors() --- packages/minimongo/cursor.js | 1 - packages/minimongo/minimongo_tests_client.js | 45 +++++++------------- 2 files changed, 16 insertions(+), 30 deletions(-) diff --git a/packages/minimongo/cursor.js b/packages/minimongo/cursor.js index a555fb74c0..09491e3cb1 100644 --- a/packages/minimongo/cursor.js +++ b/packages/minimongo/cursor.js @@ -53,7 +53,6 @@ export default class Cursor { return this._getRawObjects({ ordered: true, - applySkipLimit: true }).length; } diff --git a/packages/minimongo/minimongo_tests_client.js b/packages/minimongo/minimongo_tests_client.js index 1e369c8d06..0d99c9d91c 100644 --- a/packages/minimongo/minimongo_tests_client.js +++ b/packages/minimongo/minimongo_tests_client.js @@ -137,46 +137,46 @@ Tinytest.add('minimongo - basics', test => { test.equal(c.find('abc').count(), 0); test.equal(c.find(undefined).count(), 0); test.equal(c.find().count(), 3); - test.equal(c.find(1, {skip: 1}).count(false), 1); + test.equal(c.find(1, {skip: 1}).count(false), 0); test.equal(c.find(1, {skip: 1}).count(), 0); - test.equal(c.find({_id: 1}, {skip: 1}).count(false), 1); + test.equal(c.find({_id: 1}, {skip: 1}).count(false), 0); test.equal(c.find({_id: 1}, {skip: 1}).count(), 0); test.equal(c.find({_id: undefined}).count(), 0); test.equal(c.find({_id: false}).count(), 0); test.equal(c.find({_id: null}).count(), 0); test.equal(c.find({_id: ''}).count(), 0); test.equal(c.find({_id: 0}).count(), 0); - test.equal(c.find({}, {skip: 1}).count(false), 3); + test.equal(c.find({}, {skip: 1}).count(false), 2); test.equal(c.find({}, {skip: 1}).count(), 2); test.equal(c.find({}, {skip: 2}).count(), 1); - test.equal(c.find({}, {limit: 2}).count(false), 3); + test.equal(c.find({}, {limit: 2}).count(false), 2); test.equal(c.find({}, {limit: 2}).count(), 2); test.equal(c.find({}, {limit: 1}).count(), 1); - test.equal(c.find({}, {skip: 1, limit: 1}).count(false), 3); + test.equal(c.find({}, {skip: 1, limit: 1}).count(false), 1); test.equal(c.find({}, {skip: 1, limit: 1}).count(), 1); - test.equal(c.find({tags: 'fruit'}, {skip: 1}).count(false), 2); + test.equal(c.find({tags: 'fruit'}, {skip: 1}).count(false), 1); test.equal(c.find({tags: 'fruit'}, {skip: 1}).count(), 1); - test.equal(c.find({tags: 'fruit'}, {limit: 1}).count(false), 2); + test.equal(c.find({tags: 'fruit'}, {limit: 1}).count(false), 1); test.equal(c.find({tags: 'fruit'}, {limit: 1}).count(), 1); - test.equal(c.find({tags: 'fruit'}, {skip: 1, limit: 1}).count(false), 2); + test.equal(c.find({tags: 'fruit'}, {skip: 1, limit: 1}).count(false), 1); test.equal(c.find({tags: 'fruit'}, {skip: 1, limit: 1}).count(), 1); - test.equal(c.find(1, {sort: ['_id', 'desc'], skip: 1}).count(false), 1); + test.equal(c.find(1, {sort: ['_id', 'desc'], skip: 1}).count(false), 0); test.equal(c.find(1, {sort: ['_id', 'desc'], skip: 1}).count(), 0); - test.equal(c.find({_id: 1}, {sort: ['_id', 'desc'], skip: 1}).count(false), 1); + test.equal(c.find({_id: 1}, {sort: ['_id', 'desc'], skip: 1}).count(false), 0); test.equal(c.find({_id: 1}, {sort: ['_id', 'desc'], skip: 1}).count(), 0); - test.equal(c.find({}, {sort: ['_id', 'desc'], skip: 1}).count(false), 3); + test.equal(c.find({}, {sort: ['_id', 'desc'], skip: 1}).count(false), 2); test.equal(c.find({}, {sort: ['_id', 'desc'], skip: 1}).count(), 2); test.equal(c.find({}, {sort: ['_id', 'desc'], skip: 2}).count(), 1); - test.equal(c.find({}, {sort: ['_id', 'desc'], limit: 2}).count(false), 3); + test.equal(c.find({}, {sort: ['_id', 'desc'], limit: 2}).count(false), 2); test.equal(c.find({}, {sort: ['_id', 'desc'], limit: 2}).count(), 2); test.equal(c.find({}, {sort: ['_id', 'desc'], limit: 1}).count(), 1); - test.equal(c.find({}, {sort: ['_id', 'desc'], skip: 1, limit: 1}).count(false), 3); + test.equal(c.find({}, {sort: ['_id', 'desc'], skip: 1, limit: 1}).count(false), 1); test.equal(c.find({}, {sort: ['_id', 'desc'], skip: 1, limit: 1}).count(), 1); - test.equal(c.find({tags: 'fruit'}, {sort: ['_id', 'desc'], skip: 1}).count(false), 2); + test.equal(c.find({tags: 'fruit'}, {sort: ['_id', 'desc'], skip: 1}).count(false), 1); test.equal(c.find({tags: 'fruit'}, {sort: ['_id', 'desc'], skip: 1}).count(), 1); - test.equal(c.find({tags: 'fruit'}, {sort: ['_id', 'desc'], limit: 1}).count(false), 2); + test.equal(c.find({tags: 'fruit'}, {sort: ['_id', 'desc'], limit: 1}).count(false), 1); test.equal(c.find({tags: 'fruit'}, {sort: ['_id', 'desc'], limit: 1}).count(), 1); - test.equal(c.find({tags: 'fruit'}, {sort: ['_id', 'desc'], skip: 1, limit: 1}).count(false), 2); + test.equal(c.find({tags: 'fruit'}, {sort: ['_id', 'desc'], skip: 1, limit: 1}).count(false), 1); test.equal(c.find({tags: 'fruit'}, {sort: ['_id', 'desc'], skip: 1, limit: 1}).count(), 1); // Regression test for #455. @@ -3498,32 +3498,26 @@ Tinytest.add('minimongo - count on cursor with limit', test => { const c = Tracker.autorun(c => { const cursor = coll.find({_id: {$exists: true}}, {sort: {_id: 1}, limit: 3}); count = cursor.count(); - unlimitedCount = cursor.count(false); }); test.equal(count, 3); - test.equal(unlimitedCount, 4); coll.remove('A'); // still 3 in the collection Tracker.flush(); test.equal(count, 3); - test.equal(unlimitedCount, 3); coll.remove('B'); // expect count now 2 Tracker.flush(); test.equal(count, 2); - test.equal(unlimitedCount, 2); coll.insert({_id: 'A'}); // now 3 again Tracker.flush(); test.equal(count, 3); - test.equal(unlimitedCount, 3); coll.insert({_id: 'B'}); // now 4 entries, but count should be 3 still Tracker.flush(); test.equal(count, 3); - test.equal(unlimitedCount, 4); // unlimitedCount should be 4 now c.stop(); }); @@ -3802,36 +3796,29 @@ Tinytest.add('minimongo - fine-grained reactivity of query with fields projectio Tinytest.add('minimongo - reactive skip/limit count while updating', test => { const X = new LocalCollection; let count = -1; - let unlimitedCount = -1; const c = Tracker.autorun(() => { count = X.find({}, {skip: 1, limit: 1}).count(); - unlimitedCount = X.find({}, {skip: 1, limit: 1}).count(false); }); test.equal(count, 0); - test.equal(unlimitedCount, 0); X.insert({}); Tracker.flush({_throwFirstError: true}); test.equal(count, 0); - test.equal(unlimitedCount, 1); X.insert({}); Tracker.flush({_throwFirstError: true}); test.equal(count, 1); - test.equal(unlimitedCount, 2); X.update({}, {$set: {foo: 1}}); Tracker.flush({_throwFirstError: true}); test.equal(count, 1); - test.equal(unlimitedCount, 2); // Make sure a second update also works X.update({}, {$set: {foo: 2}}); Tracker.flush({_throwFirstError: true}); test.equal(count, 1); - test.equal(unlimitedCount, 2); c.stop(); }); From ef796f9126a6611b1d95e4d59c8fbd9e24516e3a Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Thu, 6 Jan 2022 20:55:35 -0300 Subject: [PATCH 065/393] Fix oplog converter case in which the value is an array but shouldnt be flattened --- packages/mongo/oplog_v2_converter.js | 2 +- packages/mongo/oplog_v2_converter_tests.js | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/mongo/oplog_v2_converter.js b/packages/mongo/oplog_v2_converter.js index d53ad1ff61..c0420b8bc1 100644 --- a/packages/mongo/oplog_v2_converter.js +++ b/packages/mongo/oplog_v2_converter.js @@ -63,7 +63,7 @@ const nestedOplogEntryParsers = ( return { ...acc, - ...(typeof setObjectSource[key] === 'object' + ...(!Array.isArray(setObjectSource[key]) && typeof setObjectSource[key] === 'object' ? flattenObject({ [prefixedKey]: setObjectSource[key] }) : { [prefixedKey]: setObjectSource[key], diff --git a/packages/mongo/oplog_v2_converter_tests.js b/packages/mongo/oplog_v2_converter_tests.js index 5620c786fb..51194d0298 100644 --- a/packages/mongo/oplog_v2_converter_tests.js +++ b/packages/mongo/oplog_v2_converter_tests.js @@ -1,6 +1,6 @@ import { oplogV2V1Converter } from './oplog_v2_converter'; -Tinytest.add('oplog - v2/v1 conversion', function(test) { +Tinytest.only('oplog - v2/v1 conversion', function(test) { const entry1 = { $v: 2, diff: { scustom: { sEJSON$value: { u: { EJSONtail: 'd' } } } }, @@ -33,6 +33,8 @@ Tinytest.add('oplog - v2/v1 conversion', function(test) { }; const entry8 = { $v: 2, diff: { u: { c: 'bar' }, sb: { a: true, u0: 2 } } }; + const entry9 = {"$v":2,"diff":{"sservices":{"sresume":{"u":{"loginTokens":[]}}}}}; + test.equal( JSON.stringify(oplogV2V1Converter(entry1)), JSON.stringify({ @@ -74,4 +76,8 @@ Tinytest.add('oplog - v2/v1 conversion', function(test) { JSON.stringify(oplogV2V1Converter(entry8)), JSON.stringify({ $v: 2, $set: { 'b.0': 2, c: 'bar' } }) ); + test.equal( + JSON.stringify(oplogV2V1Converter(entry9)), + JSON.stringify({ '$v': 2, '$set': { 'services.resume.loginTokens': [] } }) + ); }); From 3247b91b87a3129993c9f525a11c06d7644942a1 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Thu, 6 Jan 2022 21:02:29 -0300 Subject: [PATCH 066/393] Fix oplog converter case in which there is no need to convert --- packages/mongo/oplog_v2_converter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mongo/oplog_v2_converter.js b/packages/mongo/oplog_v2_converter.js index c0420b8bc1..ec1a08245e 100644 --- a/packages/mongo/oplog_v2_converter.js +++ b/packages/mongo/oplog_v2_converter.js @@ -88,7 +88,7 @@ const nestedOplogEntryParsers = ( }; export const oplogV2V1Converter = v2OplogEntry => { - if (v2OplogEntry.$v !== 2) return v2OplogEntry; + if (v2OplogEntry.$v !== 2 || !v2OplogEntry.diff) return v2OplogEntry; return { $v: 2, ...nestedOplogEntryParsers(v2OplogEntry.diff || {}) }; }; From c05c6fe64840ac4b4f868b30f7a0c2acaa6cdd0f Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Thu, 6 Jan 2022 21:03:56 -0300 Subject: [PATCH 067/393] Fix oplog converter case in which there is no need to convert - new test --- packages/mongo/oplog_v2_converter_tests.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/mongo/oplog_v2_converter_tests.js b/packages/mongo/oplog_v2_converter_tests.js index 51194d0298..b5f9505811 100644 --- a/packages/mongo/oplog_v2_converter_tests.js +++ b/packages/mongo/oplog_v2_converter_tests.js @@ -35,6 +35,8 @@ Tinytest.only('oplog - v2/v1 conversion', function(test) { const entry9 = {"$v":2,"diff":{"sservices":{"sresume":{"u":{"loginTokens":[]}}}}}; + const entry10 = {"$v":2,"$set":{"services.resume.loginTokens":[{"when":"2022-01-06T23:58:35.704Z","hashedToken":"RlalW6ZSvPPJLH6sW3B1b+vrUnPy+Ox5oMv3O3S7jwg="},{"when":"2022-01-06T23:58:35.704Z","hashedToken":"DWG0Qw/+nZ48wAIhKR2r9H41wLpth9BM+Br6aZsl2bU="}]}}; + test.equal( JSON.stringify(oplogV2V1Converter(entry1)), JSON.stringify({ @@ -80,4 +82,8 @@ Tinytest.only('oplog - v2/v1 conversion', function(test) { JSON.stringify(oplogV2V1Converter(entry9)), JSON.stringify({ '$v': 2, '$set': { 'services.resume.loginTokens': [] } }) ); + test.equal( + JSON.stringify(oplogV2V1Converter(entry10)), + JSON.stringify( {"$v":2,"$set":{"services.resume.loginTokens":[{"when":"2022-01-06T23:58:35.704Z","hashedToken":"RlalW6ZSvPPJLH6sW3B1b+vrUnPy+Ox5oMv3O3S7jwg="},{"when":"2022-01-06T23:58:35.704Z","hashedToken":"DWG0Qw/+nZ48wAIhKR2r9H41wLpth9BM+Br6aZsl2bU="}]}}) + ); }); From 01f94078a8019b3ebe4c0b7bdae8f8cda41807b0 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Fri, 7 Jan 2022 10:02:41 -0300 Subject: [PATCH 068/393] Document new changes on mongodb driver in our history.md - code style improvements --- History.md | 36 +++++-- packages/mongo/mongo_livedata_tests.js | 7 +- packages/mongo/oplog_observe_driver.js | 1 + packages/mongo/oplog_v2_converter.js | 19 ++-- packages/mongo/oplog_v2_converter_tests.js | 103 ++++++++++++++------- 5 files changed, 108 insertions(+), 58 deletions(-) diff --git a/History.md b/History.md index 6fe4e82bd2..262b97e16e 100644 --- a/History.md +++ b/History.md @@ -10,9 +10,37 @@ #### Independent Releases +## v2.6, UNRELEASED + +#### Highlights + +* Support for MongoDB 5+ +* Embedded Mongo now uses MongoDB 5.0.5 + +#### Breaking Changes + +* `mongo@1.14.0` + - useUnifiedTopology is not an option anymore, it defaults to true. + - native parser is not an option anymore, it defaults to false in the mongo connection. + - poolSize not an option anymore, we are using max/minPoolSize for the same behavior on mongo connection. + - fields option is deprecated, we are maintaining a translation layer to "projection" field (now prefered) until the next minor version, where we will start showing alerts. + - _ensureIndex is now showing a deprecation message + - applySkipLimit option for count() on find cursors is no longer supported. + - we are maintaining a translation layer for the new oplog format, so if you read or rely on any behavior of it please read our oplog_v2_converter.js code + - update/insert/remove behavior is maintained in the Meteor way, documented in our docs, but we are now using replaceOne/updateOne/updateMany internally. This is subject to changes in the API rewrite of MongoDB without Fibers AND if you are using rawCollection directly you have to review your methods otherwise you will see deprecation messages if you are still using the old mongodb style directly. + - internal result of operations inside nodejs mongodb driver have changed. If you are depending on rawCollection results(not only the effect inside the DB), please review the expected format as we have done [here](https://github.com/meteor/meteor/blob/155ae639ee590bae66237fc1c29295072ec92aef/packages/mongo/mongo_driver.js#L658) + - waitForStepDownOnNonCommandShutdown=false is not needed anymore when spawning the mongodb process + +#### Migration Steps + +#### Meteor Version Release + +#### Independent Releases + * `oauth@2.1.1` - Fixes end of redirect response for oauth inside iframes. [PR](https://github.com/meteor/meteor/pull/11825) and [Issue](https://github.com/meteor/meteor/issues/11817) + ## v2.5.3, 2022-01-04 #### Highlights @@ -44,8 +72,6 @@ #### Breaking Changes -- N/A - #### Migration Steps - N/A @@ -97,12 +123,6 @@ * `standard-minifier-js@2.7.3` - Using `minifier-js@2.7.3` -* `mongo@1.14.0` - - useUnifiedTopology is not an option anymore, it defaults to true. - - fields option is deprecated, we are maintaining a translation layer to "projection" field (now prefered) until the next minor version, where we will start showing alerts. - - _ensureIndex is now showing a deprecation message - - applySkipLimit option for count() on find cursors is no longer supported. - * `npm-mongo@4.2.1` - Update MongoDB driver version to 4.2.1 diff --git a/packages/mongo/mongo_livedata_tests.js b/packages/mongo/mongo_livedata_tests.js index 02b62d3f05..3224b6d3d6 100644 --- a/packages/mongo/mongo_livedata_tests.js +++ b/packages/mongo/mongo_livedata_tests.js @@ -310,8 +310,7 @@ Tinytest.addAsync("mongo-livedata - basics, " + idGeneration, function (test, on test.equal(coll.findOne({run: run}, {sort: {x: -1}, skip: 1}).x, 1); - // Regression test for https://github.com/meteor/meteor/issues/7436 - // - ensure applySkipLimit defaults to true for count() + // - applySkipLimit is no longer an option // Note that the current behavior is inconsistent on the client. // (https://github.com/meteor/meteor/issues/1201) if (Meteor.isServer) { @@ -768,7 +767,7 @@ if (Meteor.isServer) { runInFence(function () { coll.update(docId1, {$set: {x: 'y'}}); }); - test.length(o1.output, 1, 'test that is breaking'); + test.length(o1.output, 1); test.length(o2.output, 1); test.equal(o1.output.shift(), {changed: docId1}); test.equal(o2.output.shift(), {changed: docId1}); @@ -1776,9 +1775,7 @@ _.each(Meteor.isServer ? [true, false] : [true], function (minimongo) { test.isTrue(result1.insertedId); compareResults(test, skipIds, coll.find().fetch(), [{foo: 'bar', _id: result1.insertedId}]); - // !!!! var result2 = upsert(coll, useUpdate, {foo: 'bar'}, {foo: 'baz'}); - // !!!! test.equal(result2.numberAffected, 1); if (! skipIds) test.isFalse(result2.insertedId); diff --git a/packages/mongo/oplog_observe_driver.js b/packages/mongo/oplog_observe_driver.js index e068058af6..57a0448d65 100644 --- a/packages/mongo/oplog_observe_driver.js +++ b/packages/mongo/oplog_observe_driver.js @@ -610,6 +610,7 @@ _.extend(OplogObserveDriver.prototype, { // a replacement (in which case we can just directly re-evaluate the // selector)? // oplog format has changed on mongodb 5, we have to support both now + // diff is the format in Mongo 5+ (oplog v2) var isReplace = !_.has(op.o, '$set') && !_.has(op.o, 'diff') && !_.has(op.o, '$unset'); // If this modifier modifies something inside an EJSON custom type (ie, // anything with EJSON$), then we can't try to use diff --git a/packages/mongo/oplog_v2_converter.js b/packages/mongo/oplog_v2_converter.js index ec1a08245e..658e64ed0a 100644 --- a/packages/mongo/oplog_v2_converter.js +++ b/packages/mongo/oplog_v2_converter.js @@ -24,13 +24,7 @@ on mongo 4 const isArrayOperator = possibleArrayOperator => { if (!Object.keys(possibleArrayOperator).length) return false; - let isArrayOperator = true; - Object.keys(possibleArrayOperator).forEach(key => { - if (key !== 'a' && !key.match(/u\d+/)) { - isArrayOperator = false; - } - }); - return isArrayOperator; + return !Object.keys(possibleArrayOperator).find(key => key !== 'a' && !key.match(/u\d+/)); }; const nestedOplogEntryParsers = ( { i = {}, u = {}, d = {}, ...sFields }, @@ -41,13 +35,16 @@ const nestedOplogEntryParsers = ( if (isArrayOperator(value || {})) { const { a, ...uPosition } = value; const [positionKey, newArrayIndexValue] = Object.entries(uPosition)[0]; - if (uPosition) + if (uPosition) { sFieldsOperators.push({ [newArrayIndexValue === null ? '$unset' : '$set']: { [`${prefixKey}${key.substring(1)}.${positionKey.substring(1)}`]: - newArrayIndexValue === null ? true : newArrayIndexValue, + newArrayIndexValue === null ? true : newArrayIndexValue, }, }); + }else{ + throw new Error(`Unsupported oplog array entry, please review the input: ${JSON.stringify(value)}`) + } } else { sFieldsOperators.push( nestedOplogEntryParsers(value, `${prefixKey}${key.substring(1)}.`) @@ -93,13 +90,13 @@ export const oplogV2V1Converter = v2OplogEntry => { }; function flattenObject(ob) { - var toReturn = {}; + const toReturn = {}; for (const i in ob) { if (!ob.hasOwnProperty(i)) continue; if (typeof ob[i] == 'object' && ob[i] !== null) { - var flatObject = flattenObject(ob[i]); + const flatObject = flattenObject(ob[i]); for (const x in flatObject) { if (!flatObject.hasOwnProperty(x)) continue; diff --git a/packages/mongo/oplog_v2_converter_tests.js b/packages/mongo/oplog_v2_converter_tests.js index b5f9505811..28c6ee8825 100644 --- a/packages/mongo/oplog_v2_converter_tests.js +++ b/packages/mongo/oplog_v2_converter_tests.js @@ -5,38 +5,6 @@ Tinytest.only('oplog - v2/v1 conversion', function(test) { $v: 2, diff: { scustom: { sEJSON$value: { u: { EJSONtail: 'd' } } } }, }; - const entry2 = { - $v: 2, - diff: { u: { d: '2', oi: 'asdas' } }, - }; - //set inside an array - const entry3 = { $v: 2, diff: { sasd: { a: true, u0: 2 } } }; - //unset inside an array - const entry4 = { $v: 2, diff: { sasd: { a: true, u0: null } } }; - - //set a new nested field inside an object - const entry5 = { - $v: 2, - diff: { i: { a: { b: 2 } } }, - }; - - //set an existing nested field inside an object - const entry6 = { - $v: 2, - diff: { sa: { i: { b: 3, c: 1 } } }, - }; - - //unset an existing nested field inside an object - const entry7 = { - $v: 2, - diff: { sa: { d: { b: false } } }, - }; - const entry8 = { $v: 2, diff: { u: { c: 'bar' }, sb: { a: true, u0: 2 } } }; - - const entry9 = {"$v":2,"diff":{"sservices":{"sresume":{"u":{"loginTokens":[]}}}}}; - - const entry10 = {"$v":2,"$set":{"services.resume.loginTokens":[{"when":"2022-01-06T23:58:35.704Z","hashedToken":"RlalW6ZSvPPJLH6sW3B1b+vrUnPy+Ox5oMv3O3S7jwg="},{"when":"2022-01-06T23:58:35.704Z","hashedToken":"DWG0Qw/+nZ48wAIhKR2r9H41wLpth9BM+Br6aZsl2bU="}]}}; - test.equal( JSON.stringify(oplogV2V1Converter(entry1)), JSON.stringify({ @@ -44,6 +12,11 @@ Tinytest.only('oplog - v2/v1 conversion', function(test) { $set: { 'custom.EJSON$value.EJSONtail': 'd' }, }) ); + + const entry2 = { + $v: 2, + diff: { u: { d: '2', oi: 'asdas' } }, + }; test.equal( JSON.stringify(oplogV2V1Converter(entry2)), JSON.stringify({ @@ -51,18 +24,37 @@ Tinytest.only('oplog - v2/v1 conversion', function(test) { $set: { d: '2', oi: 'asdas' }, }) ); + + //set inside an array + const entry3 = { $v: 2, diff: { sasd: { a: true, u0: 2 } } }; + test.equal( JSON.stringify(oplogV2V1Converter(entry3)), JSON.stringify({ $v: 2, $set: { 'asd.0': 2 } }) ); + + //unset inside an array + const entry4 = { $v: 2, diff: { sasd: { a: true, u0: null } } }; test.equal( JSON.stringify(oplogV2V1Converter(entry4)), JSON.stringify({ $v: 2, $unset: { 'asd.0': true } }) ); + + //set a new nested field inside an object + const entry5 = { + $v: 2, + diff: { i: { a: { b: 2 } } }, + }; test.equal( JSON.stringify(oplogV2V1Converter(entry5)), JSON.stringify({ $v: 2, $set: { 'a.b': 2 } }) ); + + //set an existing nested field inside an object + const entry6 = { + $v: 2, + diff: { sa: { i: { b: 3, c: 1 } } }, + }; test.equal( JSON.stringify(oplogV2V1Converter(entry6)), JSON.stringify({ @@ -70,20 +62,63 @@ Tinytest.only('oplog - v2/v1 conversion', function(test) { $set: { 'a.b': 3, 'a.c': 1 }, }) ); + + //unset an existing nested field inside an object + const entry7 = { + $v: 2, + diff: { sa: { d: { b: false } } }, + }; test.equal( JSON.stringify(oplogV2V1Converter(entry7)), JSON.stringify({ $v: 2, $unset: { 'a.b': true } }) ); + + const entry8 = { $v: 2, diff: { u: { c: 'bar' }, sb: { a: true, u0: 2 } } }; test.equal( JSON.stringify(oplogV2V1Converter(entry8)), JSON.stringify({ $v: 2, $set: { 'b.0': 2, c: 'bar' } }) ); + + const entry9 = { + $v: 2, + diff: { sservices: { sresume: { u: { loginTokens: [] } } } }, + }; test.equal( JSON.stringify(oplogV2V1Converter(entry9)), - JSON.stringify({ '$v': 2, '$set': { 'services.resume.loginTokens': [] } }) + JSON.stringify({ $v: 2, $set: { 'services.resume.loginTokens': [] } }) ); + + const entry10 = { + $v: 2, + $set: { + 'services.resume.loginTokens': [ + { + when: '2022-01-06T23:58:35.704Z', + hashedToken: 'RlalW6ZSvPPJLH6sW3B1b+vrUnPy+Ox5oMv3O3S7jwg=', + }, + { + when: '2022-01-06T23:58:35.704Z', + hashedToken: 'DWG0Qw/+nZ48wAIhKR2r9H41wLpth9BM+Br6aZsl2bU=', + }, + ], + }, + }; test.equal( JSON.stringify(oplogV2V1Converter(entry10)), - JSON.stringify( {"$v":2,"$set":{"services.resume.loginTokens":[{"when":"2022-01-06T23:58:35.704Z","hashedToken":"RlalW6ZSvPPJLH6sW3B1b+vrUnPy+Ox5oMv3O3S7jwg="},{"when":"2022-01-06T23:58:35.704Z","hashedToken":"DWG0Qw/+nZ48wAIhKR2r9H41wLpth9BM+Br6aZsl2bU="}]}}) + JSON.stringify({ + $v: 2, + $set: { + 'services.resume.loginTokens': [ + { + when: '2022-01-06T23:58:35.704Z', + hashedToken: 'RlalW6ZSvPPJLH6sW3B1b+vrUnPy+Ox5oMv3O3S7jwg=', + }, + { + when: '2022-01-06T23:58:35.704Z', + hashedToken: 'DWG0Qw/+nZ48wAIhKR2r9H41wLpth9BM+Br6aZsl2bU=', + }, + ], + }, + }) ); }); From 76d51e3cac02ad0c4250afeda00dfb972853ab56 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Fri, 7 Jan 2022 10:05:15 -0300 Subject: [PATCH 069/393] Document new changes on mongodb driver in our history.md --- History.md | 1 + 1 file changed, 1 insertion(+) diff --git a/History.md b/History.md index 262b97e16e..a46b8a7c19 100644 --- a/History.md +++ b/History.md @@ -30,6 +30,7 @@ - update/insert/remove behavior is maintained in the Meteor way, documented in our docs, but we are now using replaceOne/updateOne/updateMany internally. This is subject to changes in the API rewrite of MongoDB without Fibers AND if you are using rawCollection directly you have to review your methods otherwise you will see deprecation messages if you are still using the old mongodb style directly. - internal result of operations inside nodejs mongodb driver have changed. If you are depending on rawCollection results(not only the effect inside the DB), please review the expected format as we have done [here](https://github.com/meteor/meteor/blob/155ae639ee590bae66237fc1c29295072ec92aef/packages/mongo/mongo_driver.js#L658) - waitForStepDownOnNonCommandShutdown=false is not needed anymore when spawning the mongodb process + - _synchronousCursor._dbCursor.operation is not an option anymore in the raw cursor from nodejs mongodb driver. If you want to access the options, use _synchronousCursor._dbCursor.(GETTERS) - for example, _synchronousCursor._dbCursor.readPreference. #### Migration Steps From 0922f3decaa423b1ba4eec41d501b9c4abb62b08 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Fri, 7 Jan 2022 10:44:13 -0300 Subject: [PATCH 070/393] Prepare Meteor 2.6 beta-0 release --- packages/allow-deny/package.js | 2 +- packages/meteor-tool/package.js | 2 +- packages/minimongo/package.js | 2 +- packages/mongo/package.js | 2 +- packages/tinytest/package.js | 2 +- scripts/admin/meteor-release-experimental.json | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/allow-deny/package.js b/packages/allow-deny/package.js index da6b451c9a..3548f3cb3b 100644 --- a/packages/allow-deny/package.js +++ b/packages/allow-deny/package.js @@ -1,6 +1,6 @@ Package.describe({ name: 'allow-deny', - version: '1.1.0', + version: '1.1.1-beta260.0', // 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. diff --git a/packages/meteor-tool/package.js b/packages/meteor-tool/package.js index 6fb03d8fd7..427cb050b6 100644 --- a/packages/meteor-tool/package.js +++ b/packages/meteor-tool/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: 'The Meteor command-line tool', - version: '2.5.3', + version: '2.6.0-beta.0', }); Package.includeTool(); diff --git a/packages/minimongo/package.js b/packages/minimongo/package.js index 2ffb5128ab..eef0592ed3 100644 --- a/packages/minimongo/package.js +++ b/packages/minimongo/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Meteor's client-side datastore: a port of MongoDB to Javascript", - version: '1.7.0' + version: '1.8.0-beta260.0' }); Package.onUse(api => { diff --git a/packages/mongo/package.js b/packages/mongo/package.js index c0c030f892..a4f237213a 100644 --- a/packages/mongo/package.js +++ b/packages/mongo/package.js @@ -9,7 +9,7 @@ Package.describe({ summary: "Adaptor for using MongoDB and Minimongo over DDP", - version: '1.13.0' + version: '1.14.0-beta260.0' }); Npm.depends({ diff --git a/packages/tinytest/package.js b/packages/tinytest/package.js index 6ee6b2faf9..b952c4658c 100644 --- a/packages/tinytest/package.js +++ b/packages/tinytest/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Tiny testing framework", - version: '1.2.0' + version: '1.2.1-beta260.0' }); Package.onUse(function (api) { diff --git a/scripts/admin/meteor-release-experimental.json b/scripts/admin/meteor-release-experimental.json index b482147b72..0b26d5a011 100644 --- a/scripts/admin/meteor-release-experimental.json +++ b/scripts/admin/meteor-release-experimental.json @@ -1,6 +1,6 @@ { "track": "METEOR", - "version": "2.5.2-beta.9", + "version": "2.6-beta.0", "recommended": false, "official": false, "description": "Meteor experimental release" From 52fcdcc04ac7986c8e3ee09163b05ff5153eec91 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Fri, 7 Jan 2022 10:45:24 -0300 Subject: [PATCH 071/393] Prepare Meteor 2.6 beta-0 release --- History.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/History.md b/History.md index a46b8a7c19..1a350f7dbd 100644 --- a/History.md +++ b/History.md @@ -10,7 +10,7 @@ #### Independent Releases -## v2.6, UNRELEASED +## v2.6-beta.0, UNRELEASED #### Highlights From 99b2973d3baa644704bb65308b0b42e3e8cb200f Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Fri, 7 Jan 2022 10:47:05 -0300 Subject: [PATCH 072/393] Prepare Meteor 2.6 beta-0 release --- packages/npm-mongo/package.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/npm-mongo/package.js b/packages/npm-mongo/package.js index ff62ade192..138c8d7175 100644 --- a/packages/npm-mongo/package.js +++ b/packages/npm-mongo/package.js @@ -3,7 +3,7 @@ Package.describe({ summary: "Wrapper around the mongo npm package", - version: "4.2.1", + version: "4.3.0-beta260.0", documentation: null }); From cef9637f71c3ff70470df1b45e91cadc417f2009 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Fri, 7 Jan 2022 10:53:48 -0300 Subject: [PATCH 073/393] Remove wrong "only" clause on test --- packages/mongo/oplog_v2_converter_tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mongo/oplog_v2_converter_tests.js b/packages/mongo/oplog_v2_converter_tests.js index 28c6ee8825..6c8e73063f 100644 --- a/packages/mongo/oplog_v2_converter_tests.js +++ b/packages/mongo/oplog_v2_converter_tests.js @@ -1,6 +1,6 @@ import { oplogV2V1Converter } from './oplog_v2_converter'; -Tinytest.only('oplog - v2/v1 conversion', function(test) { +Tinytest.add('oplog - v2/v1 conversion', function(test) { const entry1 = { $v: 2, diff: { scustom: { sEJSON$value: { u: { EJSONtail: 'd' } } } }, From 130a5d494fd4a9a5ea57e1254570adcf19aa2270 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Fri, 7 Jan 2022 12:53:33 -0300 Subject: [PATCH 074/393] Update mongodb driver to latest 4.3 version released 16 hours ago. Add fallback to hello command for mongo 4 servers Bump all packages to new beta --- packages/allow-deny/package.js | 2 +- packages/meteor-tool/package.js | 2 +- packages/minimongo/package.js | 2 +- packages/mongo/mongo_driver.js | 14 +++++-- packages/mongo/package.js | 2 +- .../.npm/package/npm-shrinkwrap.json | 39 +++++++++++++------ packages/npm-mongo/package.js | 4 +- packages/tinytest/package.js | 2 +- .../admin/meteor-release-experimental.json | 2 +- 9 files changed, 45 insertions(+), 24 deletions(-) diff --git a/packages/allow-deny/package.js b/packages/allow-deny/package.js index 3548f3cb3b..28c427dd3f 100644 --- a/packages/allow-deny/package.js +++ b/packages/allow-deny/package.js @@ -1,6 +1,6 @@ Package.describe({ name: 'allow-deny', - version: '1.1.1-beta260.0', + version: '1.1.1-beta260.1', // 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. diff --git a/packages/meteor-tool/package.js b/packages/meteor-tool/package.js index 427cb050b6..c27c9b18fa 100644 --- a/packages/meteor-tool/package.js +++ b/packages/meteor-tool/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: 'The Meteor command-line tool', - version: '2.6.0-beta.0', + version: '2.6.0-beta.1', }); Package.includeTool(); diff --git a/packages/minimongo/package.js b/packages/minimongo/package.js index eef0592ed3..d3a8e5006b 100644 --- a/packages/minimongo/package.js +++ b/packages/minimongo/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Meteor's client-side datastore: a port of MongoDB to Javascript", - version: '1.8.0-beta260.0' + version: '1.8.0-beta260.1' }); Package.onUse(api => { diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 0f2f5932ef..a065d5a816 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -192,10 +192,16 @@ MongoConnection = function (url, options) { } var db = client.db(); - const helloDocument = db.admin().command( { hello: 1 } ).await(); - // First, figure out what the current primary is, if any. - if (helloDocument.isWritablePrimary) { - self._primary = helloDocument.primary; + try { + const helloDocument = db.admin().command({hello: 1}).await(); + // First, figure out what the current primary is, if any. + if (helloDocument.isWritablePrimary) { + self._primary = helloDocument.primary; + } + }catch(_){ + if (db.serverConfig && db.serverConfig.isMasterDoc) { + self._primary = db.serverConfig.isMasterDoc.primary; + } } client.topology.on( diff --git a/packages/mongo/package.js b/packages/mongo/package.js index a4f237213a..b85d03b378 100644 --- a/packages/mongo/package.js +++ b/packages/mongo/package.js @@ -9,7 +9,7 @@ Package.describe({ summary: "Adaptor for using MongoDB and Minimongo over DDP", - version: '1.14.0-beta260.0' + version: '1.14.0-beta260.1' }); Npm.depends({ diff --git a/packages/npm-mongo/.npm/package/npm-shrinkwrap.json b/packages/npm-mongo/.npm/package/npm-shrinkwrap.json index fa086c41c6..9768941e87 100644 --- a/packages/npm-mongo/.npm/package/npm-shrinkwrap.json +++ b/packages/npm-mongo/.npm/package/npm-shrinkwrap.json @@ -2,9 +2,9 @@ "lockfileVersion": 1, "dependencies": { "@types/node": { - "version": "16.11.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", - "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==" + "version": "17.0.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz", + "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==" }, "@types/webidl-conversions": { "version": "6.1.1", @@ -22,9 +22,9 @@ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "bson": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.0.tgz", - "integrity": "sha512-8jw1NU1hglS+Da1jDOUYuNcBJ4cNHCFIqzlwoFNnsTOg2R/ox0aTYcTiBN4dzRa9q7Cvy6XErh3L8ReTEb9AQQ==" + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.1.tgz", + "integrity": "sha512-I1LQ7Hz5zgwR4QquilLNZwbhPw0Apx7i7X9kGMBTsqPdml/03Q9NBtD9nt/19ahjlphktQImrnderxqpzeVDjw==" }, "buffer": { "version": "5.7.1", @@ -41,20 +41,25 @@ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" + }, "memory-pager": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" }, "mongodb": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.2.1.tgz", - "integrity": "sha512-nDC+ulM/Ea3Q2VG5eemuGfB7T4ORwrtKegH2XW9OLlUBgQF6OTNrzFCS1Z3SJGVA+T0Sr1xBYV6DMnp0A7us0g==" + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.3.0.tgz", + "integrity": "sha512-ovq9ZD9wEvab+LsaQOiwtne1Sy2egaHW8K/H5M18Tv+V5PgTRi+qdmxDGlbm94TSL3h56m6amstptu115Nzgow==" }, "mongodb-connection-string-url": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.2.0.tgz", - "integrity": "sha512-U0cDxLUrQrl7DZA828CA+o69EuWPWEJTwdMPozyd7cy/dbtncUZczMw7wRHcwMD7oKOn0NM2tF9jdf5FFVW9CA==" + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.4.1.tgz", + "integrity": "sha512-d5Kd2bVsKcSA7YI/yo57fSTtMwRQdFkvc5IZwod1RRxJtECeWPPSo7zqcUGJELifRA//Igs4spVtYAmvFCatug==" }, "punycode": { "version": "2.1.1", @@ -66,6 +71,16 @@ "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==" }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" + }, + "socks": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz", + "integrity": "sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==" + }, "sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", diff --git a/packages/npm-mongo/package.js b/packages/npm-mongo/package.js index 138c8d7175..372fa8866c 100644 --- a/packages/npm-mongo/package.js +++ b/packages/npm-mongo/package.js @@ -3,12 +3,12 @@ Package.describe({ summary: "Wrapper around the mongo npm package", - version: "4.3.0-beta260.0", + version: "4.3.0-beta260.1", documentation: null }); Npm.depends({ - mongodb: "4.2.1" + mongodb: "4.3.0" }); Package.onUse(function (api) { diff --git a/packages/tinytest/package.js b/packages/tinytest/package.js index b952c4658c..c8fb88c09f 100644 --- a/packages/tinytest/package.js +++ b/packages/tinytest/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Tiny testing framework", - version: '1.2.1-beta260.0' + version: '1.2.1-beta260.1' }); Package.onUse(function (api) { diff --git a/scripts/admin/meteor-release-experimental.json b/scripts/admin/meteor-release-experimental.json index 0b26d5a011..36027f2923 100644 --- a/scripts/admin/meteor-release-experimental.json +++ b/scripts/admin/meteor-release-experimental.json @@ -1,6 +1,6 @@ { "track": "METEOR", - "version": "2.6-beta.0", + "version": "2.6-beta.1", "recommended": false, "official": false, "description": "Meteor experimental release" From 1fbecb0d0fc8be95d723b6c57eec90cd30ca8bc5 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Fri, 7 Jan 2022 13:40:27 -0300 Subject: [PATCH 075/393] Improve primary detection mechanism to be compatible with older mongodb instances Generate 2.6 beta 2 --- packages/allow-deny/package.js | 2 +- packages/meteor-tool/package.js | 2 +- packages/minimongo/package.js | 2 +- packages/mongo/mongo_driver.js | 9 ++++++--- packages/mongo/package.js | 2 +- packages/npm-mongo/package.js | 2 +- packages/tinytest/package.js | 2 +- scripts/admin/meteor-release-experimental.json | 2 +- 8 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/allow-deny/package.js b/packages/allow-deny/package.js index 28c427dd3f..a74a1f6ad6 100644 --- a/packages/allow-deny/package.js +++ b/packages/allow-deny/package.js @@ -1,6 +1,6 @@ Package.describe({ name: 'allow-deny', - version: '1.1.1-beta260.1', + version: '1.1.1-beta260.2', // 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. diff --git a/packages/meteor-tool/package.js b/packages/meteor-tool/package.js index c27c9b18fa..8be0089157 100644 --- a/packages/meteor-tool/package.js +++ b/packages/meteor-tool/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: 'The Meteor command-line tool', - version: '2.6.0-beta.1', + version: '2.6.0-beta.2', }); Package.includeTool(); diff --git a/packages/minimongo/package.js b/packages/minimongo/package.js index d3a8e5006b..2d17886a3b 100644 --- a/packages/minimongo/package.js +++ b/packages/minimongo/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Meteor's client-side datastore: a port of MongoDB to Javascript", - version: '1.8.0-beta260.1' + version: '1.8.0-beta260.2' }); Package.onUse(api => { diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index a065d5a816..e7507d5417 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -195,12 +195,15 @@ MongoConnection = function (url, options) { try { const helloDocument = db.admin().command({hello: 1}).await(); // First, figure out what the current primary is, if any. - if (helloDocument.isWritablePrimary) { + if (helloDocument.primary) { self._primary = helloDocument.primary; } }catch(_){ - if (db.serverConfig && db.serverConfig.isMasterDoc) { - self._primary = db.serverConfig.isMasterDoc.primary; + // ismaster command is supported on older mongodb versions + const isMasterDocument = db.admin().command({ismaster:1}).await(); + // First, figure out what the current primary is, if any. + if (isMasterDocument.primary) { + self._primary = isMasterDocument.primary; } } diff --git a/packages/mongo/package.js b/packages/mongo/package.js index b85d03b378..0a363814e1 100644 --- a/packages/mongo/package.js +++ b/packages/mongo/package.js @@ -9,7 +9,7 @@ Package.describe({ summary: "Adaptor for using MongoDB and Minimongo over DDP", - version: '1.14.0-beta260.1' + version: '1.14.0-beta260.2' }); Npm.depends({ diff --git a/packages/npm-mongo/package.js b/packages/npm-mongo/package.js index 372fa8866c..f5bbe3a876 100644 --- a/packages/npm-mongo/package.js +++ b/packages/npm-mongo/package.js @@ -3,7 +3,7 @@ Package.describe({ summary: "Wrapper around the mongo npm package", - version: "4.3.0-beta260.1", + version: "4.3.0-beta260.2", documentation: null }); diff --git a/packages/tinytest/package.js b/packages/tinytest/package.js index c8fb88c09f..94d5f4da7b 100644 --- a/packages/tinytest/package.js +++ b/packages/tinytest/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Tiny testing framework", - version: '1.2.1-beta260.1' + version: '1.2.1-beta260.2' }); Package.onUse(function (api) { diff --git a/scripts/admin/meteor-release-experimental.json b/scripts/admin/meteor-release-experimental.json index 36027f2923..7e1c554c9d 100644 --- a/scripts/admin/meteor-release-experimental.json +++ b/scripts/admin/meteor-release-experimental.json @@ -1,6 +1,6 @@ { "track": "METEOR", - "version": "2.6-beta.1", + "version": "2.6-beta.2", "recommended": false, "official": false, "description": "Meteor experimental release" From e8988953012195e596a57fdb0eeedda29832076c Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Wed, 12 Jan 2022 14:59:24 -0300 Subject: [PATCH 076/393] Fix Oplog Tailing high cpu usage with new Mongodb driver. As the old way of passing the "tailable" option to find cursors is now removed, the cursor was dying and making calls all the time. Fix it by using addCursorFlag --- packages/mongo/mongo_driver.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index e7507d5417..a77549d164 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -1009,16 +1009,23 @@ MongoConnection.prototype._createSynchronousCursor = function( readPreference: cursorOptions.readPreference, }; + // Do we want a tailable cursor (which only works on capped collections)? + if (cursorOptions.tailable) { + mongoOptions.numberOfRetries = -1; + } + + var dbCursor = collection.find( + replaceTypes(cursorDescription.selector, replaceMeteorAtomWithMongo), + mongoOptions); + // Do we want a tailable cursor (which only works on capped collections)? if (cursorOptions.tailable) { // We want a tailable cursor... - mongoOptions.tailable = true; + dbCursor.addCursorFlag("tailable", true) // ... and for the server to wait a bit if any getMore has no data (rather // than making us put the relevant sleeps in the client)... - mongoOptions.awaitdata = true; - // ... and to keep querying the server indefinitely rather than just 5 times - // if there's no more data. - mongoOptions.numberOfRetries = -1; + dbCursor.addCursorFlag("awaitData", true) + // And if this is on the oplog collection and the cursor specifies a 'ts', // then set the undocumented oplog replay flag, which does a special scan to // find the first document (instead of creating an index on ts). This is a @@ -1026,14 +1033,10 @@ MongoConnection.prototype._createSynchronousCursor = function( // only works with the ts field. if (cursorDescription.collectionName === OPLOG_COLLECTION && cursorDescription.selector.ts) { - mongoOptions.oplogReplay = true; + dbCursor.addCursorFlag("oplogReplay", true) } } - var dbCursor = collection.find( - replaceTypes(cursorDescription.selector, replaceMeteorAtomWithMongo), - mongoOptions); - if (typeof cursorOptions.maxTimeMs !== 'undefined') { dbCursor = dbCursor.maxTimeMS(cursorOptions.maxTimeMs); } From 68f4647dea13fcf4967b05a79abf1ba7780ce7e9 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Wed, 12 Jan 2022 15:18:40 -0300 Subject: [PATCH 077/393] New beta: 2.6-beta.3 --- packages/allow-deny/package.js | 2 +- packages/meteor-tool/package.js | 2 +- packages/minimongo/package.js | 2 +- packages/mongo/package.js | 2 +- packages/npm-mongo/package.js | 2 +- packages/tinytest/package.js | 2 +- scripts/admin/meteor-release-experimental.json | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/allow-deny/package.js b/packages/allow-deny/package.js index a74a1f6ad6..350e6479d9 100644 --- a/packages/allow-deny/package.js +++ b/packages/allow-deny/package.js @@ -1,6 +1,6 @@ Package.describe({ name: 'allow-deny', - version: '1.1.1-beta260.2', + version: '1.1.1-beta260.3', // 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. diff --git a/packages/meteor-tool/package.js b/packages/meteor-tool/package.js index 8be0089157..57350c4506 100644 --- a/packages/meteor-tool/package.js +++ b/packages/meteor-tool/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: 'The Meteor command-line tool', - version: '2.6.0-beta.2', + version: '2.6.0-beta.3', }); Package.includeTool(); diff --git a/packages/minimongo/package.js b/packages/minimongo/package.js index 2d17886a3b..5878a18625 100644 --- a/packages/minimongo/package.js +++ b/packages/minimongo/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Meteor's client-side datastore: a port of MongoDB to Javascript", - version: '1.8.0-beta260.2' + version: '1.8.0-beta260.3' }); Package.onUse(api => { diff --git a/packages/mongo/package.js b/packages/mongo/package.js index 0a363814e1..a1de4f33bc 100644 --- a/packages/mongo/package.js +++ b/packages/mongo/package.js @@ -9,7 +9,7 @@ Package.describe({ summary: "Adaptor for using MongoDB and Minimongo over DDP", - version: '1.14.0-beta260.2' + version: '1.14.0-beta260.3' }); Npm.depends({ diff --git a/packages/npm-mongo/package.js b/packages/npm-mongo/package.js index f5bbe3a876..86662ec935 100644 --- a/packages/npm-mongo/package.js +++ b/packages/npm-mongo/package.js @@ -3,7 +3,7 @@ Package.describe({ summary: "Wrapper around the mongo npm package", - version: "4.3.0-beta260.2", + version: "4.3.0-beta260.3", documentation: null }); diff --git a/packages/tinytest/package.js b/packages/tinytest/package.js index 94d5f4da7b..9a1d22eeee 100644 --- a/packages/tinytest/package.js +++ b/packages/tinytest/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Tiny testing framework", - version: '1.2.1-beta260.2' + version: '1.2.1-beta260.3' }); Package.onUse(function (api) { diff --git a/scripts/admin/meteor-release-experimental.json b/scripts/admin/meteor-release-experimental.json index 7e1c554c9d..a14b9ef51e 100644 --- a/scripts/admin/meteor-release-experimental.json +++ b/scripts/admin/meteor-release-experimental.json @@ -1,6 +1,6 @@ { "track": "METEOR", - "version": "2.6-beta.2", + "version": "2.6-beta.3", "recommended": false, "official": false, "description": "Meteor experimental release" From 1e18868deaef9037dca0995b9cedeb067030a46b Mon Sep 17 00:00:00 2001 From: denihs Date: Thu, 23 Dec 2021 12:10:19 -0400 Subject: [PATCH 078/393] Creating the new accounts-2fa package --- packages/accounts-2fa/.gitignore | 2 + packages/accounts-2fa/README.md | 96 ++++++++++++++++++++++++++++++++ packages/accounts-2fa/index.js | 4 ++ packages/accounts-2fa/package.js | 24 ++++++++ packages/accounts-2fa/utils.js | 12 ++++ 5 files changed, 138 insertions(+) create mode 100644 packages/accounts-2fa/.gitignore create mode 100644 packages/accounts-2fa/README.md create mode 100644 packages/accounts-2fa/index.js create mode 100644 packages/accounts-2fa/package.js create mode 100644 packages/accounts-2fa/utils.js diff --git a/packages/accounts-2fa/.gitignore b/packages/accounts-2fa/.gitignore new file mode 100644 index 0000000000..3ccf4f8cd6 --- /dev/null +++ b/packages/accounts-2fa/.gitignore @@ -0,0 +1,2 @@ +.build* +.versions diff --git a/packages/accounts-2fa/README.md b/packages/accounts-2fa/README.md new file mode 100644 index 0000000000..e25c581bc6 --- /dev/null +++ b/packages/accounts-2fa/README.md @@ -0,0 +1,96 @@ +#accounts-2fa + +--- + +Easy 2-Factor Integration For Meteor Apps + +This package uses [node-2fa](https://www.npmjs.com/package/node-2fa) which works on top of [notp](https://github.com/guyht/notp), which implements TOTP ([RFC 6238](https://www.ietf.org/rfc/rfc6238.txt)) (the Authenticator standard), which is based on HOTP ([RFC 4226](https://www.ietf.org/rfc/rfc4226.txt)) to provide codes that are exactly compatible with all other Authenticator apps and services that use them. + + +#Using it + +```shell +meteor add accounts-2fa +``` + +```js +/******** Server ********/ +import { generateSecret, verifyCode } from "meteor/accounts-2fa"; + +Meteor.methods({ + generateSecret(username) { + const newSecret = generateSecret({ username, appName: "Meteor" }); + /* + * { + * secret: 'AFBYWAQ5MQ1OA32FQXDHQMPHI3YFK358', + * svg: ... + * } + */ + return newSecret; + }, + verifyCode(code, secret) { + const result = verifyCode({ code, secret }); + /* + * => {delta: 0} //if success + * => null //if failed + */ + return result; + }, +}); + +/******** Client - React example ********/ + +const [secretData, setSecret] = useState(null); +const [qrCode, setQrCode] = useState(null); + +const handleGenerateSecret = () => { + Meteor.call("generateSecret", "John", (error, result) => { + if (error) { + console.error("Error generating secret", error); + return; + } + const { svg, secret } = result; + setSecret(secret); + /* + the svg can be converted to base64, then be used like: + + */ + setQrCode(Buffer.from(svg).toString("base64")); + }); +}; + +const handleVerifyCode = (code) => { + Meteor.call("verifyCode", code, secretData, (error, result) => { + if (error) { + console.error("Error verifying code", error); + return; + } + // do something with the result + }); +}; + +``` + +#API + +####generateSecret(options) + +Receive options is an object containing `appName` which is the name of your app that will show up when the user scans the QR code and `username` which can be the username and will also show up in the user's app. Both parameters are optional. + +> Avoid using spaces in the `appName` and `username`. Today, there's an open issue on `node-2fa` with token invalidation when there's a space on these variables: [node-2fa/issues/6](https://github.com/jeremyscalpello/node-2fa/issues/6). + +####verifyCode({ code, secret, window }) + +> This function operates exactly like on the package `node-2fa`. You can find the same description [here](https://github.com/jeremyscalpello/node-2fa#verifytokensecret-token-window). + +Checks if a time-based token matches a token from secret key within a +/- window (default: 4) minute window. + +Returns either `null` if the token does not match, or an object containing delta key, which is an integer of how far behind / forward the code time sync is in terms of how many new codes have been generated since entry. + +ex. +- {delta: -1} means that the client entered the key too late (a newer key was meant to be used). +- {delta: 1} means the client entered the key too early (an older key was meant to be used). +- {delta: 0} means the client was within the time frame of the current key. diff --git a/packages/accounts-2fa/index.js b/packages/accounts-2fa/index.js new file mode 100644 index 0000000000..9a27520837 --- /dev/null +++ b/packages/accounts-2fa/index.js @@ -0,0 +1,4 @@ +export { + generateSecret, + verifyCode, +} from './utils'; diff --git a/packages/accounts-2fa/package.js b/packages/accounts-2fa/package.js new file mode 100644 index 0000000000..3ab6366b5a --- /dev/null +++ b/packages/accounts-2fa/package.js @@ -0,0 +1,24 @@ +Package.describe({ + name: 'accounts-2fa', + version: '1.0.0', + summary: 'Package used to enable two factor authentication through OTP protocol', + git: '', + // By default, Meteor will default to using README.md for documentation. + // To avoid submitting documentation, set this field to null. + documentation: 'README.md' +}); + +Npm.depends({ + "node-2fa": "2.0.3", + "qrcode-svg": "1.1.0", +}); + +Package.onUse(function(api) { + api.use("ecmascript"); + + api.addFiles([ + "utils.js", + ], 'server'); + + api.mainModule('index.js', 'server'); +}); diff --git a/packages/accounts-2fa/utils.js b/packages/accounts-2fa/utils.js new file mode 100644 index 0000000000..dfd9c7d713 --- /dev/null +++ b/packages/accounts-2fa/utils.js @@ -0,0 +1,12 @@ +import twofactor from "node-2fa"; +import QRCode from "qrcode-svg"; + +export const generateSecret = ({ username, appName } = {}) => { + const { secret, uri } = twofactor.generateSecret({ name: appName, account: username }); + const svg = new QRCode(uri).svg(); + return { svg, secret }; +} + +export const verifyCode = ({ secret, code, window = 4 }) => { + return twofactor.verifyToken(secret, code, window); +} From 507c565d109f39bba79a59765fda318c893768c6 Mon Sep 17 00:00:00 2001 From: denihs Date: Fri, 7 Jan 2022 17:33:43 -0400 Subject: [PATCH 079/393] Changing version when the flag --container-size started to be available --- packages/accounts-2fa/2fa-client.js | 67 +++++++++++++++++++++++++++++ packages/accounts-2fa/2fa-server.js | 54 +++++++++++++++++++++++ packages/accounts-2fa/package.js | 14 +++--- packages/accounts-2fa/utils.js | 16 +++---- 4 files changed, 136 insertions(+), 15 deletions(-) create mode 100644 packages/accounts-2fa/2fa-client.js create mode 100644 packages/accounts-2fa/2fa-server.js diff --git a/packages/accounts-2fa/2fa-client.js b/packages/accounts-2fa/2fa-client.js new file mode 100644 index 0000000000..fb0274ace8 --- /dev/null +++ b/packages/accounts-2fa/2fa-client.js @@ -0,0 +1,67 @@ +import { verifyCode } from "./utils"; +import {Accounts} from "meteor/accounts-base"; + +// Used in the various functions below to handle errors consistently +const reportError = (error, callback) => { + if (callback) { + callback(error); + } else { + throw error; + } +}; +const originalLoginWithPassword = Meteor.loginWithPassword; + +Meteor.loginWithPassword = (selector, password, callback) => { + if (typeof selector === 'string') { + if (!selector.includes('@')) { + selector = {username: selector}; + } else { + selector = {email: selector}; + } + } + + Accounts.connection.call( + 'getUserTwoFactorAuthenticationData', + { selector }, + (err, twoFactorAuthenticationData) => { + const { type, secret } = twoFactorAuthenticationData || {}; + if (err) { + reportError(err, callback); + return; + } + if (type === "otp") { + callback(null, code => { + verifyCode({ secret, code}); + originalLoginWithPassword(selector, password, callback); + }); + return; + } + originalLoginWithPassword(selector, password, callback); + } + ); +}; + + +Accounts.generateSvgCode = (appName, callback) => { + let cb = callback; + if (typeof appName === "function") { + cb = appName; + } + Accounts.connection.call( + 'generateSvgCode', + appName, + cb, + ); +}; + + +Accounts.enableUser2fa = (code, callback) => { + if (!code) { + return reportError(new Meteor.Error(400, 'Must pass a code to validate'), callback); + } + Accounts.connection.call( + 'enableUser2fa', + code, + callback, + ); +}; diff --git a/packages/accounts-2fa/2fa-server.js b/packages/accounts-2fa/2fa-server.js new file mode 100644 index 0000000000..49af04cbf0 --- /dev/null +++ b/packages/accounts-2fa/2fa-server.js @@ -0,0 +1,54 @@ +import twofactor from "node-2fa"; +import QRCode from "qrcode-svg"; +import { verifyCode } from "./utils"; + +Meteor.methods({ + getUserTwoFactorAuthenticationData({ selector }) { + const user = Meteor.users.findOne(selector); + return user && user.twoFactorAuthentication; + }, + generateSvgCode(appName) { + const user = Meteor.user(); + + if (!user) { return null; } + + const { username } = user; + + const { secret, uri } = twofactor.generateSecret({ name: appName, account: username }); + const svg = new QRCode(uri).svg(); + + Meteor.users.update({ username }, { + $set: { + twoFactorAuthentication: { + secret, + } + } + }); + + return svg; + }, + enableUser2fa(code) { + const user = Meteor.user(); + + if (!user) { + throw new Meteor.Error(400, "No user logged in."); + } + + const { twoFactorAuthentication, username } = user; + + + if (!twoFactorAuthentication || !twoFactorAuthentication.secret) { + throw new Meteor.Error(400, "The user does not have a secret generated. You may have to call the function generateSvgCode first."); + } + + verifyCode({ code, secret: twoFactorAuthentication.secret }); + Meteor.users.update({ username }, { + $set: { + twoFactorAuthentication: { + ...twoFactorAuthentication, + type: "otp", + } + } + }); + }, +}); diff --git a/packages/accounts-2fa/package.js b/packages/accounts-2fa/package.js index 3ab6366b5a..919d0f8b26 100644 --- a/packages/accounts-2fa/package.js +++ b/packages/accounts-2fa/package.js @@ -14,11 +14,15 @@ Npm.depends({ }); Package.onUse(function(api) { + api.use(['accounts-base'], ['client', 'server']); + + // Export Accounts (etc) to packages using this one. + api.imply('accounts-base', ['client', 'server']); + + api.use("ecmascript"); - api.addFiles([ - "utils.js", - ], 'server'); - - api.mainModule('index.js', 'server'); + api.addFiles(["2fa-client.js"], 'client'); + api.addFiles(["2fa-server.js"], 'server'); + api.addFiles(["utils.js"], ['client', 'server']); }); diff --git a/packages/accounts-2fa/utils.js b/packages/accounts-2fa/utils.js index dfd9c7d713..63f35419d1 100644 --- a/packages/accounts-2fa/utils.js +++ b/packages/accounts-2fa/utils.js @@ -1,12 +1,8 @@ import twofactor from "node-2fa"; -import QRCode from "qrcode-svg"; -export const generateSecret = ({ username, appName } = {}) => { - const { secret, uri } = twofactor.generateSecret({ name: appName, account: username }); - const svg = new QRCode(uri).svg(); - return { svg, secret }; -} - -export const verifyCode = ({ secret, code, window = 4 }) => { - return twofactor.verifyToken(secret, code, window); -} +export const verifyCode = ({ secret, code }) => { + const { delta } = twofactor.verifyToken(secret, code) || {}; + if (!delta || delta < 0) { + throw new Meteor.Error(404, "Invalid token"); + } +}; From 6d64fdf11c8e95ef0656f752b8c834d321dd372c Mon Sep 17 00:00:00 2001 From: denihs Date: Thu, 13 Jan 2022 15:50:04 -0400 Subject: [PATCH 080/393] - creating disabled 2fa method - creating verify 2fa method - changing strategy on how to authenticate user with 2fa enabled --- packages/accounts-2fa/2fa-client.js | 48 ++++++---------- packages/accounts-2fa/2fa-server.js | 55 ++++++++++++++++--- packages/accounts-2fa/index.js | 4 -- packages/accounts-2fa/package.js | 1 - packages/accounts-2fa/utils.js | 8 --- packages/accounts-password/password_client.js | 8 ++- packages/accounts-password/password_server.js | 13 ++++- 7 files changed, 82 insertions(+), 55 deletions(-) delete mode 100644 packages/accounts-2fa/index.js delete mode 100644 packages/accounts-2fa/utils.js diff --git a/packages/accounts-2fa/2fa-client.js b/packages/accounts-2fa/2fa-client.js index fb0274ace8..1164f41b45 100644 --- a/packages/accounts-2fa/2fa-client.js +++ b/packages/accounts-2fa/2fa-client.js @@ -1,5 +1,4 @@ -import { verifyCode } from "./utils"; -import {Accounts} from "meteor/accounts-base"; +import { Accounts } from "meteor/accounts-base"; // Used in the various functions below to handle errors consistently const reportError = (error, callback) => { @@ -9,46 +8,23 @@ const reportError = (error, callback) => { throw error; } }; -const originalLoginWithPassword = Meteor.loginWithPassword; -Meteor.loginWithPassword = (selector, password, callback) => { - if (typeof selector === 'string') { - if (!selector.includes('@')) { - selector = {username: selector}; - } else { - selector = {email: selector}; - } - } +Accounts.has2FAEnabled = (selector, callback) => { Accounts.connection.call( - 'getUserTwoFactorAuthenticationData', - { selector }, - (err, twoFactorAuthenticationData) => { - const { type, secret } = twoFactorAuthenticationData || {}; - if (err) { - reportError(err, callback); - return; - } - if (type === "otp") { - callback(null, code => { - verifyCode({ secret, code}); - originalLoginWithPassword(selector, password, callback); - }); - return; - } - originalLoginWithPassword(selector, password, callback); - } + 'has2FAEnabled', + selector, + callback, ); }; - -Accounts.generateSvgCode = (appName, callback) => { +Accounts.generateSvgCodeAndSaveSecret = (appName, callback) => { let cb = callback; if (typeof appName === "function") { cb = appName; } Accounts.connection.call( - 'generateSvgCode', + 'generateSvgCodeAndSaveSecret', appName, cb, ); @@ -57,7 +33,7 @@ Accounts.generateSvgCode = (appName, callback) => { Accounts.enableUser2fa = (code, callback) => { if (!code) { - return reportError(new Meteor.Error(400, 'Must pass a code to validate'), callback); + return reportError(new Meteor.Error(400, 'Must provide a code to validate'), callback); } Accounts.connection.call( 'enableUser2fa', @@ -65,3 +41,11 @@ Accounts.enableUser2fa = (code, callback) => { callback, ); }; + + +Accounts.disableUser2fa = callback => { + Accounts.connection.call( + 'disableUser2fa', + callback, + ); +}; diff --git a/packages/accounts-2fa/2fa-server.js b/packages/accounts-2fa/2fa-server.js index 49af04cbf0..2db71b778a 100644 --- a/packages/accounts-2fa/2fa-server.js +++ b/packages/accounts-2fa/2fa-server.js @@ -1,13 +1,36 @@ +import { Accounts } from 'meteor/accounts-base'; import twofactor from "node-2fa"; import QRCode from "qrcode-svg"; -import { verifyCode } from "./utils"; +import { Meteor } from "meteor/meteor"; + +Accounts.checkUserHas2FAEnabled = selector => { + if (!Meteor.isServer) { + throw new Meteor.Error(400, "The function checkUserHas2FAEnabled can only be called on the server"); + } + + if (typeof selector === 'string') { + if (!selector.includes('@')) { + selector = {username: selector}; + } else { + selector = {email: selector}; + } + } + + const user = Meteor.users.findOne(selector) || {}; + const { twoFactorAuthentication } = user; + return twoFactorAuthentication && twoFactorAuthentication.secret && twoFactorAuthentication.type === "otp"; +}; + +Accounts.isTokenValid = (secret, token) => { + if (!Meteor.isServer) { + throw new Meteor.Error(400, "The function isTokenValid can only be called on the server"); + } + const { delta } = twofactor.verifyToken(secret, token, 10) || {}; + return delta != null && delta >= 0; +}; Meteor.methods({ - getUserTwoFactorAuthenticationData({ selector }) { - const user = Meteor.users.findOne(selector); - return user && user.twoFactorAuthentication; - }, - generateSvgCode(appName) { + generateSvgCodeAndSaveSecret(appName) { const user = Meteor.user(); if (!user) { return null; } @@ -40,8 +63,10 @@ Meteor.methods({ if (!twoFactorAuthentication || !twoFactorAuthentication.secret) { throw new Meteor.Error(400, "The user does not have a secret generated. You may have to call the function generateSvgCode first."); } + if (!Accounts.isTokenValid(twoFactorAuthentication.secret, code)) { + throw new Meteor.Error(400, "Invalid token."); + } - verifyCode({ code, secret: twoFactorAuthentication.secret }); Meteor.users.update({ username }, { $set: { twoFactorAuthentication: { @@ -51,4 +76,20 @@ Meteor.methods({ } }); }, + disableUser2fa() { + const user = Meteor.user(); + + if (!user) { + throw new Meteor.Error(400, "No user logged in."); + } + + Meteor.users.update({ username: user.username }, { + $set: { + twoFactorAuthentication: {} + } + }); + }, + has2FAEnabled(selector) { + return Accounts.checkUserHas2FAEnabled(selector); + } }); diff --git a/packages/accounts-2fa/index.js b/packages/accounts-2fa/index.js deleted file mode 100644 index 9a27520837..0000000000 --- a/packages/accounts-2fa/index.js +++ /dev/null @@ -1,4 +0,0 @@ -export { - generateSecret, - verifyCode, -} from './utils'; diff --git a/packages/accounts-2fa/package.js b/packages/accounts-2fa/package.js index 919d0f8b26..10c70f17cc 100644 --- a/packages/accounts-2fa/package.js +++ b/packages/accounts-2fa/package.js @@ -24,5 +24,4 @@ Package.onUse(function(api) { api.addFiles(["2fa-client.js"], 'client'); api.addFiles(["2fa-server.js"], 'server'); - api.addFiles(["utils.js"], ['client', 'server']); }); diff --git a/packages/accounts-2fa/utils.js b/packages/accounts-2fa/utils.js deleted file mode 100644 index 63f35419d1..0000000000 --- a/packages/accounts-2fa/utils.js +++ /dev/null @@ -1,8 +0,0 @@ -import twofactor from "node-2fa"; - -export const verifyCode = ({ secret, code }) => { - const { delta } = twofactor.verifyToken(secret, code) || {}; - if (!delta || delta < 0) { - throw new Meteor.Error(404, "Invalid token"); - } -}; diff --git a/packages/accounts-password/password_client.js b/packages/accounts-password/password_client.js index d4d53c04d3..15bf432dcc 100644 --- a/packages/accounts-password/password_client.js +++ b/packages/accounts-password/password_client.js @@ -25,12 +25,15 @@ const reportError = (error, callback) => { * single key: `email`, `username` or `id`. Username or email match in a case * insensitive manner. * @param {String} password The user's password. + * @param {String} token + * Optional if not using the package accounts-2fa. This will be the user's token + * when they're trying to log in. * @param {Function} [callback] Optional callback. * Called with no arguments on success, or with a single `Error` argument * on failure. * @importFromPackage meteor */ -Meteor.loginWithPassword = (selector, password, callback) => { +Meteor.loginWithPassword = (selector, password, token, callback) => { if (typeof selector === 'string') if (!selector.includes('@')) selector = {username: selector}; @@ -40,7 +43,8 @@ Meteor.loginWithPassword = (selector, password, callback) => { Accounts.callLoginMethod({ methodArguments: [{ user: selector, - password: Accounts._hashPassword(password) + password: Accounts._hashPassword(password), + token, }], userCallback: (error, result) => { if (error) { diff --git a/packages/accounts-password/password_server.js b/packages/accounts-password/password_server.js index f539d55436..c4cabfc21e 100644 --- a/packages/accounts-password/password_server.js +++ b/packages/accounts-password/password_server.js @@ -169,18 +169,29 @@ Accounts.registerLoginHandler("password", options => { check(options, { user: Accounts._userQueryValidator, - password: passwordValidator + password: passwordValidator, + token: Match.Optional(NonEmptyString), }); const user = Accounts._findUserByQuery(options.user, {fields: { services: 1, + twoFactorAuthentication: 1, ...Accounts._checkPasswordUserFields, }}); if (!user) { Accounts._handleError("User not found"); } + if(Accounts.checkUserHas2FAEnabled && Accounts.checkUserHas2FAEnabled(options.user)){ + if(!options.token){ + Accounts._handleError("Token must be informed."); + } + if(!Accounts.isTokenValid(user.twoFactorAuthentication.secret, options.token)){ + Accounts._handleError("Invalid token."); + } + } + if (!user.services || !user.services.password || !user.services.password.bcrypt) { Accounts._handleError("User has no password set"); From cdbdd0e501d55ba3e83afc23a2a37b9946fdac48 Mon Sep 17 00:00:00 2001 From: denihs Date: Mon, 17 Jan 2022 10:13:25 -0400 Subject: [PATCH 081/393] - Changing read.me --- packages/accounts-2fa/README.md | 204 ++++++++++++++++++++++---------- 1 file changed, 140 insertions(+), 64 deletions(-) diff --git a/packages/accounts-2fa/README.md b/packages/accounts-2fa/README.md index e25c581bc6..3835903c71 100644 --- a/packages/accounts-2fa/README.md +++ b/packages/accounts-2fa/README.md @@ -13,84 +13,160 @@ This package uses [node-2fa](https://www.npmjs.com/package/node-2fa) which works meteor add accounts-2fa ``` +When the package is added, the meteor `Meteor.loginWithPassword()` starts to accept 3 parameters: `Meteor.loginWithPassword(selector, password, token)`. The token is required when the user has 2FA enabled. Examples: + +**Generating a new QR code** ```js -/******** Server ********/ -import { generateSecret, verifyCode } from "meteor/accounts-2fa"; +import { Buffer } from "buffer"; +import { Accounts } from 'meteor/accounts-base'; -Meteor.methods({ - generateSecret(username) { - const newSecret = generateSecret({ username, appName: "Meteor" }); - /* - * { - * secret: 'AFBYWAQ5MQ1OA32FQXDHQMPHI3YFK358', - * svg: ... - * } - */ - return newSecret; - }, - verifyCode(code, secret) { - const result = verifyCode({ code, secret }); - /* - * => {delta: 0} //if success - * => null //if failed - */ - return result; - }, -}); - -/******** Client - React example ********/ - -const [secretData, setSecret] = useState(null); +-- + const [qrCode, setQrCode] = useState(null); -const handleGenerateSecret = () => { - Meteor.call("generateSecret", "John", (error, result) => { - if (error) { - console.error("Error generating secret", error); - return; - } - const { svg, secret } = result; - setSecret(secret); - /* - the svg can be converted to base64, then be used like: - - */ - setQrCode(Buffer.from(svg).toString("base64")); - }); -}; +-- + + +``` -const handleVerifyCode = (code) => { - Meteor.call("verifyCode", code, secretData, (error, result) => { - if (error) { - console.error("Error verifying code", error); - return; - } - // do something with the result - }); -}; +**Enabling 2FA** +```js +import { Accounts } from 'meteor/accounts-base'; +-- + +const [code, setCode] = useState(null); + +-- + +const handleValidateCodeFromQr = () => { + try { + Accounts.enableUser2fa(code); + console.log("2FA enabled"); + } catch (err) { + console.error('Error verifying code from qr', err); + } +} + +-- + +
+ qr code + setCode(value)}/> + +
+``` + +**Disabling 2FA** +```js +import { Accounts } from 'meteor/accounts-base'; + +--- + + +``` + +**Login** + +```js +// Verify with the user has 2FA enabled. If no, performe normal login. + + +// If 2FA is enabled, inform a token, with username and password. + ``` #API -####generateSecret(options) +####Accounts.generateSvgCodeAndSaveSecret({String} appName, {Function} [callback]) -Receive options is an object containing `appName` which is the name of your app that will show up when the user scans the QR code and `username` which can be the username and will also show up in the user's app. Both parameters are optional. +Receive an `appName` which is the name of your app that will show up when the user scans the QR code. Also, a callback that's called with no arguments on success, or with a single `Error` argument +on failure. Both parameters are optional. -> Avoid using spaces in the `appName` and `username`. Today, there's an open issue on `node-2fa` with token invalidation when there's a space on these variables: [node-2fa/issues/6](https://github.com/jeremyscalpello/node-2fa/issues/6). +On success, this function will add an object to the logged user containing the QR secret: -####verifyCode({ code, secret, window }) +```js +twoFactorAuthetication: { + secret: "***" +} +``` -> This function operates exactly like on the package `node-2fa`. You can find the same description [here](https://github.com/jeremyscalpello/node-2fa#verifytokensecret-token-window). +> Avoid using spaces in the `appName`. Today, there's an open issue on `node-2fa` with token invalidation when there's a space on these variables: [node-2fa/issues/6](https://github.com/jeremyscalpello/node-2fa/issues/6). -Checks if a time-based token matches a token from secret key within a +/- window (default: 4) minute window. +####Accounts.enableUser2fa({String} code) -Returns either `null` if the token does not match, or an object containing delta key, which is an integer of how far behind / forward the code time sync is in terms of how many new codes have been generated since entry. +Called with a code the user will receive from the authenticator app once they read the QR code. This function throws an error on failure. If the code provided is correct, a `type` will be added to the user's `twoFactorAuthentication` object: -ex. -- {delta: -1} means that the client entered the key too late (a newer key was meant to be used). -- {delta: 1} means the client entered the key too early (an older key was meant to be used). -- {delta: 0} means the client was within the time frame of the current key. +```js +twoFactorAuthetication: { + type: "otp", + secret: "***", +} +``` + +#### Accounts.disableUser2fa() + +Called with no arguments. Remove the object `twoFactorAuthentication` from the user. Throws an error on failure. + + +#### Accounts.has2FAEnabled({String} username, {Function} [callback]) + +Called with two arguments: Username, and a callback function. The `username` is the user you want to verify if the 2FA is enabled. The callback is called with a boolean on success indicating if the user have or not the 2FA enabled, or with a single `Error` argument on failure. From 50f793e462b305f70761df258056668ebc18361a Mon Sep 17 00:00:00 2001 From: denihs Date: Mon, 17 Jan 2022 10:49:12 -0400 Subject: [PATCH 082/393] - Creating doc --- docs/source/packages/accounts-2fa.md | 27 +++++++++++++++++++++++ packages/accounts-2fa/2fa-client.js | 33 +++++++++++++++++++++++++--- packages/accounts-2fa/README.md | 2 +- 3 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 docs/source/packages/accounts-2fa.md diff --git a/docs/source/packages/accounts-2fa.md b/docs/source/packages/accounts-2fa.md new file mode 100644 index 0000000000..d6ec973993 --- /dev/null +++ b/docs/source/packages/accounts-2fa.md @@ -0,0 +1,27 @@ +--- +title: accounts-2fa +description: Documentation of Meteor's `accounts-2fa` package. +--- + +The package `accounts-2fa` allows you to easily integrate 2FA with the OTP technology on your login flow. + +The first step to use 2FA is to generate a QR code so the user can read it on an authenticator app and start to receiving codes. +{% apibox "Accounts.generateSvgCodeAndSaveSecret" "module":"accounts-base" %} + + +{% apibox "Accounts.enableUser2fa" "module":"accounts-base" %} +Once the user has the codes, now the 2FA can enable by calling this function with a code. + +{% apibox "Accounts.disableUser2fa" "module":"accounts-base" %} +Use this function to give the users the option of disabling the 2FA. + +{% apibox "Accounts.has2FAEnabled" "module":"accounts-base" %} +Use this function to verify if the user has 2FA enabled. + +

Log in with code

+ +Once this package is added, the method `Meteor.loginWithPassword` starts to accept on additional parameter, which is the token: `Meteor.loginWithPassword(selector, password, token, [callback])`. + +If the user has 2FA enabled, they only will be able to log in if they provide a valid code. + +You can see examples on how to use this package on it's [read.me](https://github.com/meteor/meteor/tree/feature/accounts-2fa-package/packages/accounts-2fa). diff --git a/packages/accounts-2fa/2fa-client.js b/packages/accounts-2fa/2fa-client.js index 1164f41b45..9e85d71795 100644 --- a/packages/accounts-2fa/2fa-client.js +++ b/packages/accounts-2fa/2fa-client.js @@ -9,7 +9,13 @@ const reportError = (error, callback) => { } }; - +/** + * @summary Verify if the user has 2FA enabled + * @locus Client + * @param {Object|String} selector Username, email or custom selector to identify the user. + * @param {Function} [callback] Optional callback. Called with a boolean on success that indicates whether the user has + * or not 2FA enabled, or with a single `Error` argument on failure. + */ Accounts.has2FAEnabled = (selector, callback) => { Accounts.connection.call( 'has2FAEnabled', @@ -18,6 +24,14 @@ Accounts.has2FAEnabled = (selector, callback) => { ); }; +/** + * @summary Generates a svg QR code and save secret on user + * @locus Client + * @param {String} appName Optional. It's the name of your app that will show up when the user scans the QR code. + * @param {Function} [callback] Optional callback. + * Called with no arguments on success, or with a single `Error` argument + * on failure. + */ Accounts.generateSvgCodeAndSaveSecret = (appName, callback) => { let cb = callback; if (typeof appName === "function") { @@ -30,7 +44,14 @@ Accounts.generateSvgCodeAndSaveSecret = (appName, callback) => { ); }; - +/** + * @summary Generates a svg QR code and save secret on user + * @locus Client + * @param {String} code Code received from the authenticator app. + * @param {Function} [callback] Optional callback. + * Called with no arguments on success, or with a single `Error` argument + * on failure. + */ Accounts.enableUser2fa = (code, callback) => { if (!code) { return reportError(new Meteor.Error(400, 'Must provide a code to validate'), callback); @@ -42,7 +63,13 @@ Accounts.enableUser2fa = (code, callback) => { ); }; - +/** + * @summary Disable user 2FA + * @locus Client + * @param {Function} [callback] Optional callback. + * Called with no arguments on success, or with a single `Error` argument + * on failure. + */ Accounts.disableUser2fa = callback => { Accounts.connection.call( 'disableUser2fa', diff --git a/packages/accounts-2fa/README.md b/packages/accounts-2fa/README.md index 3835903c71..94fc26d6ff 100644 --- a/packages/accounts-2fa/README.md +++ b/packages/accounts-2fa/README.md @@ -169,4 +169,4 @@ Called with no arguments. Remove the object `twoFactorAuthentication` from the u #### Accounts.has2FAEnabled({String} username, {Function} [callback]) -Called with two arguments: Username, and a callback function. The `username` is the user you want to verify if the 2FA is enabled. The callback is called with a boolean on success indicating if the user have or not the 2FA enabled, or with a single `Error` argument on failure. +Called with two arguments: Username, and a callback function. The `username` is the user you want to verify if the 2FA is enabled. The callback is called with a boolean on success that indicates whether the user has or not the 2FA enabled, or with a single `Error` argument on failure. From 81ab454acdb7cc8dde9b456daba7b25ff05c0452 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Fri, 7 Jan 2022 14:13:02 -0300 Subject: [PATCH 083/393] Update tar library for faster extractions/compressions --- meteor | 2 +- scripts/dev-bundle-tool-package.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/meteor b/meteor index 3d8b5e6bd4..089cf80cfc 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/usr/bin/env bash -BUNDLE_VERSION=14.18.2.3 +BUNDLE_VERSION=14.18.2.6 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. diff --git a/scripts/dev-bundle-tool-package.js b/scripts/dev-bundle-tool-package.js index e0a22b6ca8..4fdb66293c 100644 --- a/scripts/dev-bundle-tool-package.js +++ b/scripts/dev-bundle-tool-package.js @@ -35,7 +35,7 @@ var packageJson = { uuid: "3.4.0", "graceful-fs": "4.2.6", fstream: "https://github.com/meteor/fstream/tarball/cf4ea6c175355cec7bee38311e170d08c4078a5d", - tar: "2.2.2", + tar: "6.1.11", // Fork of kexec@3.0.0 with my Node.js 12 compatibility PR // https://github.com/jprichardson/node-kexec/pull/37 applied. // TODO: We should replace this with: https://github.com/jprichardson/node-kexec/pull/38 From b73404493313af8b7f6035773b9983871c09eb92 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Mon, 10 Jan 2022 16:35:35 -0300 Subject: [PATCH 084/393] Update tar library for faster extractions/compressions - add node-fs for manipulating tar streams --- meteor | 2 +- scripts/dev-bundle-tool-package.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/meteor b/meteor index 089cf80cfc..d60643115a 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/usr/bin/env bash -BUNDLE_VERSION=14.18.2.6 +BUNDLE_VERSION=14.18.2.7 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. diff --git a/scripts/dev-bundle-tool-package.js b/scripts/dev-bundle-tool-package.js index 4fdb66293c..f2b7a29346 100644 --- a/scripts/dev-bundle-tool-package.js +++ b/scripts/dev-bundle-tool-package.js @@ -36,6 +36,7 @@ var packageJson = { "graceful-fs": "4.2.6", fstream: "https://github.com/meteor/fstream/tarball/cf4ea6c175355cec7bee38311e170d08c4078a5d", tar: "6.1.11", + 'tar-fs': "2.1.1", // Fork of kexec@3.0.0 with my Node.js 12 compatibility PR // https://github.com/jprichardson/node-kexec/pull/37 applied. // TODO: We should replace this with: https://github.com/jprichardson/node-kexec/pull/38 From eda0180a8e8fc518e377fedde4c9733c563de282 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Mon, 10 Jan 2022 16:56:26 -0300 Subject: [PATCH 085/393] Update tar library for faster extractions/compressions - use node-fs for manipulating tar streams --- tools/fs/files.ts | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/tools/fs/files.ts b/tools/fs/files.ts index 27283ba421..6604336785 100644 --- a/tools/fs/files.ts +++ b/tools/fs/files.ts @@ -917,22 +917,23 @@ function tryExtractWithNpmTar( ) { ensureDirectoryEmpty(tempDir); - const tar = require("tar"); + const tar = require("tar-fs"); const zlib = require("zlib"); return new Promise((resolve, reject) => { const gunzip = zlib.createGunzip().on('error', reject); - const extractor = new tar.Extract({ - path: convertToOSPath(tempDir) - }).on('entry', function (e: any) { - if (process.platform === "win32" || options.forceConvert) { - // On Windows, try to convert old packages that have colons in - // paths by blindly replacing all of the paths. Otherwise, we - // can't even extract the tarball - e.path = colonConverter.convert(e.path); + const extractor = tar.extract(convertToOSPath(tempDir), { + map: function(header: any) { + if (process.platform === "win32" || options.forceConvert) { + // On Windows, try to convert old packages that have colons in + // paths by blindly replacing all of the paths. Otherwise, we + // can't even extract the tarball + header.name = colonConverter.convert(header.name); + } + return header } }).on('error', reject) - .on('end', resolve); + .on('resolve', resolve); // write the buffer to the (gunzip|untar) pipeline; these calls // cause the tar to be extracted to disk. @@ -954,7 +955,7 @@ function addExecBitWhenReadBitPresent(fileMode: number) { // needed. The tar archive will contain a top-level directory named // after dirPath. export function createTarGzStream(dirPath: string) { - const tar = require("tar"); + const tar = require("tar-fs"); const fstream = require('fstream'); const zlib = require("zlib"); @@ -1016,9 +1017,7 @@ export function createTarGzStream(dirPath: string) { } }); - return fileStream.pipe(tar.Pack({ - noProprietary: true, - })).pipe(zlib.createGzip()); + return fileStream.pipe(tar.pack).pipe(zlib.createGzip()); } // Tar-gzips a directory into a tarball on disk, synchronously. From ff81b7fc9c049d2d15565f2ade879197460f7d38 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Mon, 10 Jan 2022 18:08:57 -0300 Subject: [PATCH 086/393] Update tar library for faster extractions/compressions - use node-fs for manipulating tar streams --- tools/fs/files.ts | 71 ++++------------------------------------------- 1 file changed, 6 insertions(+), 65 deletions(-) diff --git a/tools/fs/files.ts b/tools/fs/files.ts index 6604336785..e7feed2a08 100644 --- a/tools/fs/files.ts +++ b/tools/fs/files.ts @@ -943,81 +943,22 @@ function tryExtractWithNpmTar( }); } -// In the same fashion as node-pre-gyp does, add the executable -// bit but only if the read bit was present. Same as: -// https://github.com/mapbox/node-pre-gyp/blob/7a28f4b0f562ba4712722fefe4eeffb7b20fbf7a/lib/install.js#L71-L77 -// and others reported in: https://github.com/npm/node-tar/issues/7 -function addExecBitWhenReadBitPresent(fileMode: number) { - return fileMode |= (fileMode >>> 2) & 0o111; -} - // Tar-gzips a directory, returning a stream that can then be piped as // needed. The tar archive will contain a top-level directory named // after dirPath. export function createTarGzStream(dirPath: string) { const tar = require("tar-fs"); - const fstream = require('fstream'); const zlib = require("zlib"); + const basename = pathBasename(dirPath); - // Create a segment of the file path which we will look for to - // identify exactly what we think is a "bin" file (that is, something - // which should be expected to work within the context of an - // 'npm run-script'). - const binPathMatch = ["", "node_modules", ".bin", ""].join(path.sep); - - // Don't use `{ path: dirPath, type: 'Directory' }` as an argument to - // fstream.Reader. This triggers a collection of odd behaviors in fstream - // (which might be bugs or might just be weirdnesses). - // - // First, if we pass an object with `type: 'Directory'` as an argument, then - // the resulting tarball has no entry for the top-level directory, because - // the reader emits an entry (with just the path, no permissions or other - // properties) before the pipe to gzip is even set up, so that entry gets - // lost. Even if we pause the streams until all the pipes are set up, we'll - // get the entry in the tarball for the top-level directory without - // permissions or other properties, which is problematic. Just passing - // `dirPath` appears to cause `fstream` to stat the directory before emitting - // an entry for it, so the pipes are set up by the time the entry is emitted, - // and the entry has all the right permissions, etc. from statting it. - // - // The second weird behavior is that we need an entry for the top-level - // directory in the tarball to untar it with npm `tar`. (GNU tar, in - // contrast, appears to have no problems untarring tarballs without entries - // for the top-level directory inside them.) The problem is that, without an - // entry for the top-level directory, `fstream` will create the directory - // with the same permissions as the first file inside it. This manifests as - // an EACCESS when untarring if the first file inside the top-level directory - // is not writeable. - const fileStream = fstream.Reader({ - path: convertToOSPath(dirPath), - filter(entry: any) { - if (process.platform !== "win32") { - return true; - } - - // Refuse to create a directory that isn't listable. Tarballs - // created on Windows will have non-executable directories (since - // executable isn't a thing in Windows directory permissions), and - // so the resulting extracted directories will not be listable on - // Linux/Mac unless we explicitly make them executable. We think - // this should really be an option that you pass to node tar, but - // setting it in an 'entry' handler is the same strategy that npm - // does, so we do that here too. - if (entry.type === "Directory") { - entry.props.mode = addExecBitWhenReadBitPresent(entry.props.mode); - } - - // In a similar way as for directories, but only if is in a path - // location that is expected to be executable (npm "bin" links) - if (entry.type === "File" && entry.path.indexOf(binPathMatch) > -1) { - entry.props.mode = addExecBitWhenReadBitPresent(entry.props.mode); - } - - return true; + const tarStream = tar.pack(convertToOSPath(dirPath), { + map: (header: any) => { + header.name = `${basename}/${header.name}` + return header } }); - return fileStream.pipe(tar.pack).pipe(zlib.createGzip()); + return tarStream.pipe(zlib.createGzip()); } // Tar-gzips a directory into a tarball on disk, synchronously. From 8ee02968a69123fd14755c7f9ab802e9acc7eb77 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Mon, 10 Jan 2022 18:11:42 -0300 Subject: [PATCH 087/393] Update tar library for faster extractions/compressions - remove unused import --- tools/fs/files.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/fs/files.ts b/tools/fs/files.ts index e7feed2a08..972040ebcd 100644 --- a/tools/fs/files.ts +++ b/tools/fs/files.ts @@ -6,7 +6,6 @@ import assert from "assert"; import fs, { PathLike, Stats, Dirent } from "fs"; -import path from "path"; import os from "os"; import { spawn, execFile } from "child_process"; import { EventEmitter } from "events"; From 42ae637d9725228c9dcf767b8ad2b67d54f3ed25 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Tue, 11 Jan 2022 10:30:29 -0300 Subject: [PATCH 088/393] Update tar library for faster extractions/compressions - fix extract test --- tools/fs/files.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/fs/files.ts b/tools/fs/files.ts index 972040ebcd..6e3298f827 100644 --- a/tools/fs/files.ts +++ b/tools/fs/files.ts @@ -932,7 +932,7 @@ function tryExtractWithNpmTar( return header } }).on('error', reject) - .on('resolve', resolve); + .on('finish', resolve); // write the buffer to the (gunzip|untar) pipeline; these calls // cause the tar to be extracted to disk. From fddf4a446ecaef00dc6d8d596631814558c99963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Tue, 18 Jan 2022 14:17:52 -0400 Subject: [PATCH 089/393] Update Node.js to 14.18.3 --- meteor | 2 +- scripts/build-dev-bundle-common.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/meteor b/meteor index d60643115a..16c71b9e08 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/usr/bin/env bash -BUNDLE_VERSION=14.18.2.7 +BUNDLE_VERSION=14.18.3.0 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. diff --git a/scripts/build-dev-bundle-common.sh b/scripts/build-dev-bundle-common.sh index 386cb631e1..64dbeaa4f9 100644 --- a/scripts/build-dev-bundle-common.sh +++ b/scripts/build-dev-bundle-common.sh @@ -5,7 +5,7 @@ set -u UNAME=$(uname) ARCH=$(uname -m) -NODE_VERSION=14.18.2 +NODE_VERSION=14.18.3 MONGO_VERSION_64BIT=4.4.4 MONGO_VERSION_32BIT=3.2.22 NPM_VERSION=6.14.15 From 4359346770a583a1cee5f9a45f72ed64c8622356 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Wed, 12 Jan 2022 10:37:44 -0300 Subject: [PATCH 090/393] Bump to 2.5.4-beta.0 --- History.md | 24 +++++++++++++++++++ packages/meteor-tool/package.js | 2 +- .../admin/meteor-release-experimental.json | 2 +- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/History.md b/History.md index 1427e14970..40aa23491a 100644 --- a/History.md +++ b/History.md @@ -34,6 +34,30 @@ #### Independent Releases +## v2.5.4, UNRELEASED + +#### Highlights + +* Bump node version to 14.18.3 - security patch +* Change the tar implementation for streams, used on deploying and unpacking packages. Reduced "upload bundle" time when deploying is expected. + +#### Breaking Changes + +- N/A + +#### Migration Steps + +- N/A + +#### Meteor Version Release + +* `meteor-tool@2.5.4` + - Bump node version to 14.18.3 - security patch + - Change the tar implementation for streams, used on deploying and unpacking packages. Reduced "upload bundle" time when deploying is expected. + + +#### Independent Releases + ## v2.5.2, 2021-12-21 #### Highlights diff --git a/packages/meteor-tool/package.js b/packages/meteor-tool/package.js index 6fb03d8fd7..b98725310a 100644 --- a/packages/meteor-tool/package.js +++ b/packages/meteor-tool/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: 'The Meteor command-line tool', - version: '2.5.3', + version: '2.5.4-beta.0', }); Package.includeTool(); diff --git a/scripts/admin/meteor-release-experimental.json b/scripts/admin/meteor-release-experimental.json index b482147b72..ef1291d9d8 100644 --- a/scripts/admin/meteor-release-experimental.json +++ b/scripts/admin/meteor-release-experimental.json @@ -1,6 +1,6 @@ { "track": "METEOR", - "version": "2.5.2-beta.9", + "version": "2.5.4-beta.0", "recommended": false, "official": false, "description": "Meteor experimental release" From b40ab1da2a82be2e8f58ce5e304bf6191526522d Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Fri, 14 Jan 2022 11:34:18 -0300 Subject: [PATCH 091/393] New patch release: 2.5.4 :tada: --- History.md | 50 +++++++++++----------- packages/meteor-tool/package.js | 2 +- scripts/admin/meteor-release-official.json | 2 +- 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/History.md b/History.md index 40aa23491a..f443a6a492 100644 --- a/History.md +++ b/History.md @@ -13,6 +13,32 @@ * `oauth@2.1.1` - Fixes end of redirect response for oauth inside iframes. [PR](https://github.com/meteor/meteor/pull/11825) and [Issue](https://github.com/meteor/meteor/issues/11817) + +## v2.5.4, 2022-01-14 + +#### Highlights + +* Bump node version to 14.18.3 - security patch +* Change the tar implementation for streams, used on deploying and unpacking packages. Reduced "upload bundle" time when deploying is expected. + +#### Breaking Changes + +- N/A + +#### Migration Steps + +- N/A + +#### Meteor Version Release + +* `meteor-tool@2.5.4` + - Bump node version to 14.18.3 - security patch + - Change the tar implementation for streams, used on deploying and unpacking packages. Reduced "upload bundle" time when deploying is expected. + + +#### Independent Releases + + ## v2.5.3, 2022-01-04 #### Highlights @@ -34,30 +60,6 @@ #### Independent Releases -## v2.5.4, UNRELEASED - -#### Highlights - -* Bump node version to 14.18.3 - security patch -* Change the tar implementation for streams, used on deploying and unpacking packages. Reduced "upload bundle" time when deploying is expected. - -#### Breaking Changes - -- N/A - -#### Migration Steps - -- N/A - -#### Meteor Version Release - -* `meteor-tool@2.5.4` - - Bump node version to 14.18.3 - security patch - - Change the tar implementation for streams, used on deploying and unpacking packages. Reduced "upload bundle" time when deploying is expected. - - -#### Independent Releases - ## v2.5.2, 2021-12-21 #### Highlights diff --git a/packages/meteor-tool/package.js b/packages/meteor-tool/package.js index b98725310a..6faca80dfd 100644 --- a/packages/meteor-tool/package.js +++ b/packages/meteor-tool/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: 'The Meteor command-line tool', - version: '2.5.4-beta.0', + version: '2.5.4', }); Package.includeTool(); diff --git a/scripts/admin/meteor-release-official.json b/scripts/admin/meteor-release-official.json index 94d480daf1..4a3fac9f52 100644 --- a/scripts/admin/meteor-release-official.json +++ b/scripts/admin/meteor-release-official.json @@ -1,6 +1,6 @@ { "track": "METEOR", - "version": "2.5.3", + "version": "2.5.4", "recommended": false, "official": true, "description": "The Official Meteor Distribution" From d68eac38b4dd3c0939cbe4e56bfd0b15c27a665f Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Fri, 14 Jan 2022 11:46:18 -0300 Subject: [PATCH 092/393] New patch release: 2.5.4 :tada: --- npm-packages/meteor-installer/README.md | 1 + npm-packages/meteor-installer/config.js | 2 +- npm-packages/meteor-installer/package.json | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/npm-packages/meteor-installer/README.md b/npm-packages/meteor-installer/README.md index dde995f685..f8cd81ae5e 100644 --- a/npm-packages/meteor-installer/README.md +++ b/npm-packages/meteor-installer/README.md @@ -26,6 +26,7 @@ npm install -g meteor | 2.5.2 | 2.5.1 | | 2.5.3 | 2.5.2 | | 2.5.4 | 2.5.3 | +| 2.5.5 | 2.5.4 | ### Important note diff --git a/npm-packages/meteor-installer/config.js b/npm-packages/meteor-installer/config.js index 68c8f59cb2..d3968ab3af 100644 --- a/npm-packages/meteor-installer/config.js +++ b/npm-packages/meteor-installer/config.js @@ -1,7 +1,7 @@ const path = require('path'); const os = require('os'); -const METEOR_LATEST_VERSION = '2.5.3'; +const METEOR_LATEST_VERSION = '2.5.4'; const sudoUser = process.env.SUDO_USER || ''; function isRoot() { return process.getuid && process.getuid() === 0; diff --git a/npm-packages/meteor-installer/package.json b/npm-packages/meteor-installer/package.json index 9a348c36b8..dff0e79a4f 100644 --- a/npm-packages/meteor-installer/package.json +++ b/npm-packages/meteor-installer/package.json @@ -1,6 +1,6 @@ { "name": "meteor", - "version": "2.5.4", + "version": "2.5.5", "description": "Install Meteor", "main": "install.js", "scripts": { From 4a185145605ca5d12251ebcd3c470150c0b18875 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Fri, 14 Jan 2022 22:55:06 -0300 Subject: [PATCH 093/393] Standardize only one way of extracting tars, as native ones can be tricky with permissions. Fix issue while extracting archives created on windows, inside linux/mac with permissions --- tools/fs/files.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tools/fs/files.ts b/tools/fs/files.ts index 6e3298f827..01469baa7b 100644 --- a/tools/fs/files.ts +++ b/tools/fs/files.ts @@ -777,13 +777,8 @@ export function extractTarGz( const startTime = +new Date; - let promise = process.platform === "win32" - ? tryExtractWithNative7z(buffer, tempDir, options) - : tryExtractWithNativeTar(buffer, tempDir, options) - - promise = promise.catch( - () => tryExtractWithNpmTar(buffer, tempDir, options) - ); + // standardize only one way of extracting, as native ones can be tricky + const promise = tryExtractWithNpmTar(buffer, tempDir, options) promise.await(); @@ -922,6 +917,10 @@ function tryExtractWithNpmTar( return new Promise((resolve, reject) => { const gunzip = zlib.createGunzip().on('error', reject); const extractor = tar.extract(convertToOSPath(tempDir), { + /* the following lines guarantees that archives created on windows + are going to be readable and writable on unixes */ + readable: true, // all dirs and files should be readable + writable: true, // all dirs and files should be writable map: function(header: any) { if (process.platform === "win32" || options.forceConvert) { // On Windows, try to convert old packages that have colons in From 2732e977026af7af166b2e6221a2dbfd9e0a7084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Tue, 18 Jan 2022 14:21:42 -0400 Subject: [PATCH 094/393] Clean up unused functions and imports in tools/fs/files.ts --- tools/fs/files.ts | 99 +---------------------------------------------- 1 file changed, 1 insertion(+), 98 deletions(-) diff --git a/tools/fs/files.ts b/tools/fs/files.ts index 01469baa7b..e22feaa457 100644 --- a/tools/fs/files.ts +++ b/tools/fs/files.ts @@ -4,10 +4,9 @@ /// (such as testing whether an directory is a meteor app) /// -import assert from "assert"; import fs, { PathLike, Stats, Dirent } from "fs"; import os from "os"; -import { spawn, execFile } from "child_process"; +import { execFile } from "child_process"; import { EventEmitter } from "events"; import { Slot } from "@wry/context"; import { dep } from "optimism"; @@ -808,102 +807,6 @@ function ensureDirectoryEmpty(dir: string) { }); } -function tryExtractWithNativeTar( - buffer: Buffer, - tempDir: string, - options: TarOptions = {}, -) { - ensureDirectoryEmpty(tempDir); - - if (options.forceConvert) { - return Promise.reject(new Error( - "Native tar cannot convert colons in package names")); - } - - return new Promise((resolve, reject) => { - const flags = options.verbose ? "-xzvf" : "-xzf"; - const tarProc = spawn("tar", [flags, "-"], { - cwd: convertToOSPath(tempDir), - stdio: options.verbose ? [ - "pipe", // Always need to write to tarProc.stdin. - process.stdout, - process.stderr - ] : "pipe", - }); - - tarProc.on("error", reject); - tarProc.on("exit", resolve); - - if (tarProc.stdin) { - tarProc.stdin.write(buffer); - tarProc.stdin.end(); - } - }); -} - -function tryExtractWithNative7z( - buffer: Buffer, - tempDir: string, - options: TarOptions = {}, -) { - ensureDirectoryEmpty(tempDir); - - if (options.forceConvert) { - return Promise.reject(new Error( - "Native 7z.exe cannot convert colons in package names")); - } - - const exeOSPath = convertToOSPath(pathJoin(getCurrentNodeBinDir(), "7z.exe")); - const tarGzBasename = "out.tar.gz"; - const spawnOptions = { - cwd: convertToOSPath(tempDir), - stdio: (options.verbose ? "inherit" : "pipe") as ("inherit" | "pipe"), - }; - - writeFile(pathJoin(tempDir, tarGzBasename), buffer); - - return new Promise((resolve, reject) => { - spawn(exeOSPath, [ - "x", "-y", tarGzBasename - ], spawnOptions) - .on("error", reject) - .on("exit", resolve); - - }).then(code => { - assert.strictEqual(code, 0); - - let tarBasename: string; - const foundTar = readdir(tempDir).some(file => { - if (file !== tarGzBasename) { - tarBasename = file; - return true; - } - }); - - assert.ok(foundTar, "failed to find .tar file"); - - function cleanUp() { - unlink(pathJoin(tempDir, tarGzBasename)); - unlink(pathJoin(tempDir, tarBasename)); - } - - return new Promise((resolve, reject) => { - spawn(exeOSPath, [ - "x", "-y", tarBasename - ], spawnOptions) - .on("error", reject) - .on("exit", resolve); - - }).then(code => { - cleanUp(); - return code; - }, error => { - cleanUp(); - throw error; - }); - }); -} - function tryExtractWithNpmTar( buffer: Buffer, tempDir: string, From 8074913478441d46e286c3d8b345103e125ac260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Tue, 18 Jan 2022 14:28:54 -0400 Subject: [PATCH 095/393] Fixing name convention in class properties `_loginCallbacks_called` should be `_loginCallbacksCalled` --- packages/accounts-base/accounts_client.js | 30 +++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/accounts-base/accounts_client.js b/packages/accounts-base/accounts_client.js index c8aad1ef81..ddcda98cad 100644 --- a/packages/accounts-base/accounts_client.js +++ b/packages/accounts-base/accounts_client.js @@ -34,7 +34,7 @@ export class AccountsClient extends AccountsCommon { // This tracks whether callbacks registered with // Accounts.onLogin have been called - this._loginCallbacks_called = false; + this._loginCallbacksCalled = false; } /// @@ -123,7 +123,7 @@ export class AccountsClient extends AccountsCommon { wait: true }, (error, result) => { this._loggingOut.set(false); - this._loginCallbacks_called = false; + this._loginCallbacksCalled = false; if (error) { callback && callback(error); } else { @@ -207,7 +207,7 @@ export class AccountsClient extends AccountsCommon { // logged in, or with the error on error. // callLoginMethod(options) { - options = { + options = { methodName: 'login', methodArguments: [{}], _suppressLoggingIn: false, @@ -226,7 +226,7 @@ export class AccountsClient extends AccountsCommon { const loginCallbacks = ({ error, loginDetails }) => { if (!called) { called = true; - this._loginCallbacks_called = true; + this._loginCallbacksCalled = true; if (!error) { this._onLoginHook.each(callback => { callback(loginDetails); @@ -386,7 +386,7 @@ export class AccountsClient extends AccountsCommon { this.connection.setUserId(null); this._reconnectStopper && this._reconnectStopper.stop(); } - + makeClientLoggedIn(userId, token, tokenExpires) { this._storeLoginToken(userId, token, tokenExpires); this.connection.setUserId(userId); @@ -449,7 +449,7 @@ export class AccountsClient extends AccountsCommon { // before callbacks are registered see #10157 _startupCallback(callback) { // Are we already logged in? - if (this._loginCallbacks_called) { + if (this._loginCallbacksCalled) { // If already logged in before handler is registered, it's safe to // assume type is a 'resume', so we execute the callback at the end // of the queue so that Meteor.startup can complete before any @@ -641,14 +641,14 @@ export class AccountsClient extends AccountsCommon { _initUrlMatching() { // By default, allow the autologin process to happen. this._autoLoginEnabled = true; - + // We only support one callback per URL. this._accountsCallbacks = {}; - + // Try to match the saved value of window.location.hash. this._attemptToMatchHash(); }; - + // Separate out this functionality for testing _attemptToMatchHash() { attemptToMatchHash(this, this.savedHash, defaultSuccessHandler); @@ -737,8 +737,8 @@ export class AccountsClient extends AccountsCommon { }; /** - * @summary True if a login method (such as `Meteor.loginWithPassword`, - * `Meteor.loginWithFacebook`, or `Accounts.createUser`) is currently in + * @summary True if a login method (such as `Meteor.loginWithPassword`, + * `Meteor.loginWithFacebook`, or `Accounts.createUser`) is currently in * progress. A reactive data source. * @locus Client * @importFromPackage meteor @@ -746,7 +746,7 @@ export class AccountsClient extends AccountsCommon { Meteor.loggingIn = () => Accounts.loggingIn(); /** - * @summary True if a logout method (such as `Meteor.logout`) is currently in + * @summary True if a logout method (such as `Meteor.logout`) is currently in * progress. A reactive data source. * @locus Client * @importFromPackage meteor @@ -772,7 +772,7 @@ Meteor.logoutOtherClients = callback => Accounts.logoutOtherClients(callback); /** * @summary Login with a Meteor access token. * @locus Client - * @param {Object} [token] Local storage token for use with login across + * @param {Object} [token] Local storage token for use with login across * multiple tabs in the same browser. * @param {Function} [callback] Optional callback. Called with no arguments on * success. @@ -821,7 +821,7 @@ if (Package.blaze) { * @summary Calls [Meteor.loggingIn()](#meteor_loggingin) or [Meteor.loggingOut()](#meteor_loggingout). */ Template.registerHelper( - 'loggingInOrOut', + 'loggingInOrOut', () => Meteor.loggingIn() || Meteor.loggingOut() ); } @@ -878,6 +878,6 @@ const attemptToMatchHash = (accounts, hash, success) => { // Export for testing export const AccountsTest = { - attemptToMatchHash: (hash, success) => + attemptToMatchHash: (hash, success) => attemptToMatchHash(Accounts, hash, success), }; From ea12e3917f8952d150b0105bd9360b687b68c96f Mon Sep 17 00:00:00 2001 From: denihs Date: Tue, 18 Jan 2022 16:46:14 -0400 Subject: [PATCH 096/393] Meteor version to 2.5.5 :tada: --- History.md | 16 +++++++++++----- packages/accounts-base/package.js | 2 +- packages/meteor-tool/package.js | 2 +- scripts/admin/meteor-release-official.json | 2 +- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/History.md b/History.md index f443a6a492..5120c73e47 100644 --- a/History.md +++ b/History.md @@ -10,11 +10,8 @@ #### Independent Releases -* `oauth@2.1.1` - - Fixes end of redirect response for oauth inside iframes. [PR](https://github.com/meteor/meteor/pull/11825) and [Issue](https://github.com/meteor/meteor/issues/11817) - -## v2.5.4, 2022-01-14 +## v2.5.5, 2022-01-18 #### Highlights @@ -31,13 +28,22 @@ #### Meteor Version Release -* `meteor-tool@2.5.4` +* `meteor-tool@2.5.5` - Bump node version to 14.18.3 - security patch - Change the tar implementation for streams, used on deploying and unpacking packages. Reduced "upload bundle" time when deploying is expected. +* `accounts-base@2.2.1` + - Fixes onLogin firing twice. [PR](https://github.com/meteor/meteor/pull/11785) and [Issue](https://github.com/meteor/meteor/issues/10853) + #### Independent Releases +* `oauth@2.1.1` + - Fixes end of redirect response for oauth inside iframes. [PR](https://github.com/meteor/meteor/pull/11825) and [Issue](https://github.com/meteor/meteor/issues/11817) + +## v2.5.4, 2022-01-14 + +This version should be ignored. Proceed to 2.5.5 above. ## v2.5.3, 2022-01-04 diff --git a/packages/accounts-base/package.js b/packages/accounts-base/package.js index 29ddffc6cc..fb5a3befc8 100644 --- a/packages/accounts-base/package.js +++ b/packages/accounts-base/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: 'A user account system', - version: '2.2.0', + version: '2.2.1', }); Package.onUse(api => { diff --git a/packages/meteor-tool/package.js b/packages/meteor-tool/package.js index 6faca80dfd..87ce59240c 100644 --- a/packages/meteor-tool/package.js +++ b/packages/meteor-tool/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: 'The Meteor command-line tool', - version: '2.5.4', + version: '2.5.5', }); Package.includeTool(); diff --git a/scripts/admin/meteor-release-official.json b/scripts/admin/meteor-release-official.json index 4a3fac9f52..45de381549 100644 --- a/scripts/admin/meteor-release-official.json +++ b/scripts/admin/meteor-release-official.json @@ -1,6 +1,6 @@ { "track": "METEOR", - "version": "2.5.4", + "version": "2.5.5", "recommended": false, "official": true, "description": "The Official Meteor Distribution" From fb04fb51f08e661cdc7e8567e59513b1feb74a28 Mon Sep 17 00:00:00 2001 From: denihs Date: Wed, 19 Jan 2022 09:10:08 -0400 Subject: [PATCH 097/393] Meteor installer for version 2.5.5 --- npm-packages/meteor-installer/README.md | 1 + npm-packages/meteor-installer/config.js | 2 +- npm-packages/meteor-installer/package.json | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/npm-packages/meteor-installer/README.md b/npm-packages/meteor-installer/README.md index f8cd81ae5e..b46c682037 100644 --- a/npm-packages/meteor-installer/README.md +++ b/npm-packages/meteor-installer/README.md @@ -27,6 +27,7 @@ npm install -g meteor | 2.5.3 | 2.5.2 | | 2.5.4 | 2.5.3 | | 2.5.5 | 2.5.4 | +| 2.5.6 | 2.5.5 | ### Important note diff --git a/npm-packages/meteor-installer/config.js b/npm-packages/meteor-installer/config.js index d3968ab3af..49d2130ad2 100644 --- a/npm-packages/meteor-installer/config.js +++ b/npm-packages/meteor-installer/config.js @@ -1,7 +1,7 @@ const path = require('path'); const os = require('os'); -const METEOR_LATEST_VERSION = '2.5.4'; +const METEOR_LATEST_VERSION = '2.5.5'; const sudoUser = process.env.SUDO_USER || ''; function isRoot() { return process.getuid && process.getuid() === 0; diff --git a/npm-packages/meteor-installer/package.json b/npm-packages/meteor-installer/package.json index dff0e79a4f..4878e07e99 100644 --- a/npm-packages/meteor-installer/package.json +++ b/npm-packages/meteor-installer/package.json @@ -1,6 +1,6 @@ { "name": "meteor", - "version": "2.5.5", + "version": "2.5.6", "description": "Install Meteor", "main": "install.js", "scripts": { From f358c6d4a7fb8dabaa76f8de0563cebed79deca2 Mon Sep 17 00:00:00 2001 From: Ikko Ashimine Date: Wed, 19 Jan 2022 22:42:02 +0900 Subject: [PATCH 098/393] chore(docs): fix typo in email.md recieve -> receive --- docs/source/api/email.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/api/email.md b/docs/source/api/email.md index 86f577e307..e801e6a709 100644 --- a/docs/source/api/email.md +++ b/docs/source/api/email.md @@ -117,7 +117,7 @@ Email.customTransport = (data) => { // The rest of the options are from Email.send options const mailgun = Mailgun({ apiKey: data.packageSettings.mailgun.privateKey, domain: 'mg.mygreatapp.com' }) - // Since the data object that we recieve already includes the correct key names for sending + // Since the data object that we receive already includes the correct key names for sending // we can just pass it to the mailgun sending message. mailgun.messages().send(data, (error, body) => { if (error) Log.error(error) From df1304c2534fe9edef51aa292a7a1b4f238312a2 Mon Sep 17 00:00:00 2001 From: denihs Date: Wed, 19 Jan 2022 09:45:45 -0400 Subject: [PATCH 099/393] Changes requested on code review - change method name - add comment - don't changing loginWithPassword structure --- docs/source/packages/accounts-2fa.md | 2 +- packages/accounts-2fa/2fa-client.js | 10 +-- packages/accounts-2fa/2fa-server.js | 10 +-- packages/accounts-2fa/README.md | 4 +- packages/accounts-2fa/package.js | 5 -- packages/accounts-password/password_client.js | 80 +++++++++++++------ packages/accounts-password/password_server.js | 1 + 7 files changed, 68 insertions(+), 44 deletions(-) diff --git a/docs/source/packages/accounts-2fa.md b/docs/source/packages/accounts-2fa.md index d6ec973993..3f7736c0a8 100644 --- a/docs/source/packages/accounts-2fa.md +++ b/docs/source/packages/accounts-2fa.md @@ -6,7 +6,7 @@ description: Documentation of Meteor's `accounts-2fa` package. The package `accounts-2fa` allows you to easily integrate 2FA with the OTP technology on your login flow. The first step to use 2FA is to generate a QR code so the user can read it on an authenticator app and start to receiving codes. -{% apibox "Accounts.generateSvgCodeAndSaveSecret" "module":"accounts-base" %} +{% apibox "Accounts.generate2faActivationQrCode" "module":"accounts-base" %} {% apibox "Accounts.enableUser2fa" "module":"accounts-base" %} diff --git a/packages/accounts-2fa/2fa-client.js b/packages/accounts-2fa/2fa-client.js index 9e85d71795..d7cca62c5f 100644 --- a/packages/accounts-2fa/2fa-client.js +++ b/packages/accounts-2fa/2fa-client.js @@ -16,9 +16,9 @@ const reportError = (error, callback) => { * @param {Function} [callback] Optional callback. Called with a boolean on success that indicates whether the user has * or not 2FA enabled, or with a single `Error` argument on failure. */ -Accounts.has2FAEnabled = (selector, callback) => { +Accounts.has2faEnabled = (selector, callback) => { Accounts.connection.call( - 'has2FAEnabled', + 'has2faEnabled', selector, callback, ); @@ -32,20 +32,20 @@ Accounts.has2FAEnabled = (selector, callback) => { * Called with no arguments on success, or with a single `Error` argument * on failure. */ -Accounts.generateSvgCodeAndSaveSecret = (appName, callback) => { +Accounts.generate2faActivationQrCode = (appName, callback) => { let cb = callback; if (typeof appName === "function") { cb = appName; } Accounts.connection.call( - 'generateSvgCodeAndSaveSecret', + 'generate2faActivationQrCode', appName, cb, ); }; /** - * @summary Generates a svg QR code and save secret on user + * @summary Enable the user 2FA * @locus Client * @param {String} code Code received from the authenticator app. * @param {Function} [callback] Optional callback. diff --git a/packages/accounts-2fa/2fa-server.js b/packages/accounts-2fa/2fa-server.js index 2db71b778a..c0b758da15 100644 --- a/packages/accounts-2fa/2fa-server.js +++ b/packages/accounts-2fa/2fa-server.js @@ -3,9 +3,9 @@ import twofactor from "node-2fa"; import QRCode from "qrcode-svg"; import { Meteor } from "meteor/meteor"; -Accounts.checkUserHas2FAEnabled = selector => { +Accounts.checkUserHas2faEnabled = selector => { if (!Meteor.isServer) { - throw new Meteor.Error(400, "The function checkUserHas2FAEnabled can only be called on the server"); + throw new Meteor.Error(400, "The function checkUserHas2faEnabled can only be called on the server"); } if (typeof selector === 'string') { @@ -30,7 +30,7 @@ Accounts.isTokenValid = (secret, token) => { }; Meteor.methods({ - generateSvgCodeAndSaveSecret(appName) { + generate2faActivationQrCode(appName) { const user = Meteor.user(); if (!user) { return null; } @@ -61,7 +61,7 @@ Meteor.methods({ if (!twoFactorAuthentication || !twoFactorAuthentication.secret) { - throw new Meteor.Error(400, "The user does not have a secret generated. You may have to call the function generateSvgCode first."); + throw new Meteor.Error(500, "The user does not have a secret generated. You may have to call the function generateSvgCode first."); } if (!Accounts.isTokenValid(twoFactorAuthentication.secret, code)) { throw new Meteor.Error(400, "Invalid token."); @@ -89,7 +89,7 @@ Meteor.methods({ } }); }, - has2FAEnabled(selector) { + has2faEnabled(selector) { return Accounts.checkUserHas2FAEnabled(selector); } }); diff --git a/packages/accounts-2fa/README.md b/packages/accounts-2fa/README.md index 94fc26d6ff..d16662d9bd 100644 --- a/packages/accounts-2fa/README.md +++ b/packages/accounts-2fa/README.md @@ -28,7 +28,7 @@ const [qrCode, setQrCode] = useState(null); +``` + + +At this point, the 2FA won't be activated just yet. Now that the user has access to the codes generated by their authenticator app, you can call the function `Accounts.enableUser2fa`: {% apibox "Accounts.enableUser2fa" "module":"accounts-base" %} -Once the user has the codes, now the 2FA can enable by calling this function with a code. + +Called with a code the user will receive from the authenticator app once they read the QR code. This function throws an error on failure. If the code provided is correct, a `type` will be added to the user's `twoFactorAuthentication` object, and now the 2FA will be considered enabled: + +```js +twoFactorAuthetication: { + type: "otp", + secret: "***", +} +``` + +

Log in with 2FA

+ +Now that you have a way to allow your users to enable 2FA on their accounts, you can create a login flow based on that. + +To verify if a user has or not 2FA enabled you can call the function `Accounts.has2FAEnabled`: + +{% apibox "Accounts.has2faEnabled" "module":"accounts-base" %} + +With this function, you can verify if the has or not 2FA enabled, and based on this information, you can directly log the user in the 2FA is not enabled, or redirect the user to a place where they can provide a code, in case they do have 2FA enabled. + +A way of using it would be: + +```js + +``` + +If the user has 2FA enabled, and you try to use the function `Meteor.loginWithPassword`, the login will fail, as the user should provide a token to access the app. + +The function you will need to call now to allow the user to log in is `Meteor.loginWithPasswordAnd2faToken`: + +{% apibox "Meteor.loginWithPasswordAnd2faToken" %} + +Now you will be able to receive a code from the user and this function will verify if the code is valid. If it is, the user will be logged in. + +So the call of this function should look something like this: + +```js + +``` + +

Disabling 2FA

+ +To disable 2FA you call simply use this function: {% apibox "Accounts.disableUser2fa" "module":"accounts-base" %} -Use this function to give the users the option of disabling the 2FA. -{% apibox "Accounts.has2FAEnabled" "module":"accounts-base" %} -Use this function to verify if the user has 2FA enabled. - -

Log in with code

- -Once this package is added, the method `Meteor.loginWithPassword` starts to accept on additional parameter, which is the token: `Meteor.loginWithPassword(selector, password, token, [callback])`. - -If the user has 2FA enabled, they only will be able to log in if they provide a valid code. - -You can see examples on how to use this package on it's [read.me](https://github.com/meteor/meteor/tree/feature/accounts-2fa-package/packages/accounts-2fa). +To call this function the user must be already logged in. diff --git a/packages/accounts-2fa/2fa-client.js b/packages/accounts-2fa/2fa-client.js index d7cca62c5f..753f3102c8 100644 --- a/packages/accounts-2fa/2fa-client.js +++ b/packages/accounts-2fa/2fa-client.js @@ -1,4 +1,4 @@ -import { Accounts } from "meteor/accounts-base"; +import { Accounts } from 'meteor/accounts-base'; // Used in the various functions below to handle errors consistently const reportError = (error, callback) => { @@ -13,35 +13,35 @@ const reportError = (error, callback) => { * @summary Verify if the user has 2FA enabled * @locus Client * @param {Object|String} selector Username, email or custom selector to identify the user. - * @param {Function} [callback] Optional callback. Called with a boolean on success that indicates whether the user has + * @param {Function} [callback] Called with a boolean on success that indicates whether the user has * or not 2FA enabled, or with a single `Error` argument on failure. */ Accounts.has2faEnabled = (selector, callback) => { - Accounts.connection.call( - 'has2faEnabled', - selector, - callback, - ); + Accounts.connection.call('has2faEnabled', selector, callback); }; /** * @summary Generates a svg QR code and save secret on user * @locus Client * @param {String} appName Optional. It's the name of your app that will show up when the user scans the QR code. - * @param {Function} [callback] Optional callback. - * Called with no arguments on success, or with a single `Error` argument + * @param {Function} [callback] + * Called with a QR code in SVG format on success, or with a single `Error` argument * on failure. */ Accounts.generate2faActivationQrCode = (appName, callback) => { let cb = callback; - if (typeof appName === "function") { + if (typeof appName === 'function') { cb = appName; } - Accounts.connection.call( - 'generate2faActivationQrCode', - appName, - cb, - ); + + if (!cb) { + throw new Meteor.Error( + 500, + 'A callback is necessary when calling the function generate2faActivationQrCode so a QR code can be provided' + ); + } + + Accounts.connection.call('generate2faActivationQrCode', appName, cb); }; /** @@ -54,13 +54,12 @@ Accounts.generate2faActivationQrCode = (appName, callback) => { */ Accounts.enableUser2fa = (code, callback) => { if (!code) { - return reportError(new Meteor.Error(400, 'Must provide a code to validate'), callback); + return reportError( + new Meteor.Error(400, 'Must provide a code to validate'), + callback + ); } - Accounts.connection.call( - 'enableUser2fa', - code, - callback, - ); + Accounts.connection.call('enableUser2fa', code, callback); }; /** @@ -71,8 +70,5 @@ Accounts.enableUser2fa = (code, callback) => { * on failure. */ Accounts.disableUser2fa = callback => { - Accounts.connection.call( - 'disableUser2fa', - callback, - ); + Accounts.connection.call('disableUser2fa', callback); }; diff --git a/packages/accounts-2fa/README.md b/packages/accounts-2fa/README.md index d16662d9bd..f820667fa4 100644 --- a/packages/accounts-2fa/README.md +++ b/packages/accounts-2fa/README.md @@ -1,172 +1,9 @@ -#accounts-2fa +# accounts-2fa ---- +[Source code of released version](https://github.com/meteor/meteor/tree/master/packages/accounts-2fa) +| [Source code of development version](https://github.com/meteor/meteor/tree/devel/packages/accounts-2fa) +*** -Easy 2-Factor Integration For Meteor Apps - -This package uses [node-2fa](https://www.npmjs.com/package/node-2fa) which works on top of [notp](https://github.com/guyht/notp), which implements TOTP ([RFC 6238](https://www.ietf.org/rfc/rfc6238.txt)) (the Authenticator standard), which is based on HOTP ([RFC 4226](https://www.ietf.org/rfc/rfc4226.txt)) to provide codes that are exactly compatible with all other Authenticator apps and services that use them. - - -#Using it - -```shell -meteor add accounts-2fa -``` - -When the package is added, the meteor `Meteor.loginWithPassword()` starts to accept 3 parameters: `Meteor.loginWithPassword(selector, password, token)`. The token is required when the user has 2FA enabled. Examples: - -**Generating a new QR code** -```js -import { Buffer } from "buffer"; -import { Accounts } from 'meteor/accounts-base'; - --- - -const [qrCode, setQrCode] = useState(null); - --- - - -``` - -**Enabling 2FA** -```js -import { Accounts } from 'meteor/accounts-base'; - --- - -const [code, setCode] = useState(null); - --- - -const handleValidateCodeFromQr = () => { - try { - Accounts.enableUser2fa(code); - console.log("2FA enabled"); - } catch (err) { - console.error('Error verifying code from qr', err); - } -} - --- - -
- qr code - setCode(value)}/> - -
-``` - -**Disabling 2FA** -```js -import { Accounts } from 'meteor/accounts-base'; - ---- - - -``` - -**Login** - -```js -// Verify with the user has 2FA enabled. If no, performe normal login. - - -// If 2FA is enabled, inform a token, with username and password. - -``` - -#API - -####Accounts.generate2faActivationQrCode({String} appName, {Function} [callback]) - -Receive an `appName` which is the name of your app that will show up when the user scans the QR code. Also, a callback that's called with no arguments on success, or with a single `Error` argument -on failure. Both parameters are optional. - -On success, this function will add an object to the logged user containing the QR secret: - -```js -twoFactorAuthetication: { - secret: "***" -} -``` - -> Avoid using spaces in the `appName`. Today, there's an open issue on `node-2fa` with token invalidation when there's a space on these variables: [node-2fa/issues/6](https://github.com/jeremyscalpello/node-2fa/issues/6). - -####Accounts.enableUser2fa({String} code) - -Called with a code the user will receive from the authenticator app once they read the QR code. This function throws an error on failure. If the code provided is correct, a `type` will be added to the user's `twoFactorAuthentication` object: - -```js -twoFactorAuthetication: { - type: "otp", - secret: "***", -} -``` - -#### Accounts.disableUser2fa() - -Called with no arguments. Remove the object `twoFactorAuthentication` from the user. Throws an error on failure. - - -#### Accounts.has2FAEnabled({String} username, {Function} [callback]) - -Called with two arguments: Username, and a callback function. The `username` is the user you want to verify if the 2FA is enabled. The callback is called with a boolean on success that indicates whether the user has or not the 2FA enabled, or with a single `Error` argument on failure. +A package to implement 2FA in login flows that use `Meteor.loginWithPassword`. See +the [project page](https://docs.meteor.com/packages/accounts-2fa.html) on Meteor Accounts for more +details. From 7a6818ef2a95eba56e3406455eaef041b6f606fe Mon Sep 17 00:00:00 2001 From: denihs Date: Thu, 20 Jan 2022 11:05:48 -0400 Subject: [PATCH 105/393] Changes requested on code review - typo --- docs/source/packages/accounts-2fa.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/packages/accounts-2fa.md b/docs/source/packages/accounts-2fa.md index ac9d4aa9d9..991845dc96 100644 --- a/docs/source/packages/accounts-2fa.md +++ b/docs/source/packages/accounts-2fa.md @@ -13,7 +13,7 @@ The first step to use 2FA is to generate a QR code so the user can read it on an {% apibox "Accounts.generate2faActivationQrCode" "module":"accounts-base" %} -Receive an `appName` which is the name of your app that will show up when the user scans the QR code. Also, a callback called with a QR code in SVG format on success or a single `Error` argument +Receives an `appName` which is the name of your app that will show up when the user scans the QR code. Also, a callback called with a QR code in SVG format on success or a single `Error` argument on failure. Both parameters are optional. On success, this function will also add an object to the logged user containing the QR secret: From 691729d56a5a757d96122c7efcaf4c71bbf55176 Mon Sep 17 00:00:00 2001 From: denihs Date: Thu, 20 Jan 2022 13:45:09 -0400 Subject: [PATCH 106/393] Changes requested on code review - bumping accounts-password version --- packages/accounts-password/package.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/accounts-password/package.js b/packages/accounts-password/package.js index 7509672c0f..5e739186c2 100644 --- a/packages/accounts-password/package.js +++ b/packages/accounts-password/package.js @@ -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: '2.2.0', + version: '2.2.1', }); Npm.depends({ From d79d8e8f2ad1b08e0064905d81450a197ca87b22 Mon Sep 17 00:00:00 2001 From: denihs Date: Thu, 20 Jan 2022 13:45:47 -0400 Subject: [PATCH 107/393] Changes requested on code review - bumping accounts-password version (minor) --- packages/accounts-password/package.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/accounts-password/package.js b/packages/accounts-password/package.js index 5e739186c2..8ed4267272 100644 --- a/packages/accounts-password/package.js +++ b/packages/accounts-password/package.js @@ -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: '2.2.1', + version: '2.3.0', }); Npm.depends({ From 426872ebb4fe00c14914a66f5f4705d3d88823be Mon Sep 17 00:00:00 2001 From: denihs Date: Thu, 20 Jan 2022 13:48:45 -0400 Subject: [PATCH 108/393] Changes requested on code review - Running prettier on new lines --- packages/accounts-2fa/2fa-server.js | 92 ++++++++++++------- packages/accounts-2fa/package.js | 14 +-- packages/accounts-password/password_client.js | 22 ++--- packages/accounts-password/password_server.js | 15 ++- 4 files changed, 87 insertions(+), 56 deletions(-) diff --git a/packages/accounts-2fa/2fa-server.js b/packages/accounts-2fa/2fa-server.js index 154748e35b..33479be425 100644 --- a/packages/accounts-2fa/2fa-server.js +++ b/packages/accounts-2fa/2fa-server.js @@ -1,29 +1,39 @@ import { Accounts } from 'meteor/accounts-base'; -import twofactor from "node-2fa"; -import QRCode from "qrcode-svg"; -import { Meteor } from "meteor/meteor"; +import twofactor from 'node-2fa'; +import QRCode from 'qrcode-svg'; +import { Meteor } from 'meteor/meteor'; Accounts.is2faEnabledForUser = selector => { if (!Meteor.isServer) { - throw new Meteor.Error(400, "The function is2faEnabledForUser can only be called on the server"); + throw new Meteor.Error( + 400, + 'The function is2faEnabledForUser can only be called on the server' + ); } if (typeof selector === 'string') { if (!selector.includes('@')) { - selector = {username: selector}; + selector = { username: selector }; } else { - selector = {email: selector}; + selector = { email: selector }; } } const user = Meteor.users.findOne(selector) || {}; const { twoFactorAuthentication } = user; - return twoFactorAuthentication && twoFactorAuthentication.secret && twoFactorAuthentication.type === "otp"; + return ( + twoFactorAuthentication && + twoFactorAuthentication.secret && + twoFactorAuthentication.type === 'otp' + ); }; Accounts.isTokenValid = (secret, token) => { if (!Meteor.isServer) { - throw new Meteor.Error(400, "The function isTokenValid can only be called on the server"); + throw new Meteor.Error( + 400, + 'The function isTokenValid can only be called on the server' + ); } const { delta } = twofactor.verifyToken(secret, token, 10) || {}; return delta != null && delta >= 0; @@ -33,20 +43,28 @@ Meteor.methods({ generate2faActivationQrCode(appName) { const user = Meteor.user(); - if (!user) { return null; } + if (!user) { + return null; + } const { username } = user; - const { secret, uri } = twofactor.generateSecret({ name: appName, account: username }); + const { secret, uri } = twofactor.generateSecret({ + name: appName, + account: username, + }); const svg = new QRCode(uri).svg(); - Meteor.users.update({ username }, { - $set: { - twoFactorAuthentication: { - secret, - } + Meteor.users.update( + { username }, + { + $set: { + twoFactorAuthentication: { + secret, + }, + }, } - }); + ); return svg; }, @@ -54,42 +72,50 @@ Meteor.methods({ const user = Meteor.user(); if (!user) { - throw new Meteor.Error(400, "No user logged in."); + throw new Meteor.Error(400, 'No user logged in.'); } const { twoFactorAuthentication, username } = user; - if (!twoFactorAuthentication || !twoFactorAuthentication.secret) { - throw new Meteor.Error(500, "The user does not have a secret generated. You may have to call the function generateSvgCode first."); + throw new Meteor.Error( + 500, + 'The user does not have a secret generated. You may have to call the function generateSvgCode first.' + ); } if (!Accounts.isTokenValid(twoFactorAuthentication.secret, code)) { - throw new Meteor.Error(400, "Invalid token."); + throw new Meteor.Error(400, 'Invalid token.'); } - Meteor.users.update({ username }, { - $set: { - twoFactorAuthentication: { - ...twoFactorAuthentication, - type: "otp", - } + Meteor.users.update( + { username }, + { + $set: { + twoFactorAuthentication: { + ...twoFactorAuthentication, + type: 'otp', + }, + }, } - }); + ); }, disableUser2fa() { const user = Meteor.user(); if (!user) { - throw new Meteor.Error(400, "No user logged in."); + throw new Meteor.Error(400, 'No user logged in.'); } - Meteor.users.update({ username: user.username }, { - $unset: { - twoFactorAuthentication: 1 + Meteor.users.update( + { username: user.username }, + { + $unset: { + twoFactorAuthentication: 1, + }, } - }); + ); }, has2faEnabled(selector) { return Accounts.is2faEnabledForUser(selector); - } + }, }); diff --git a/packages/accounts-2fa/package.js b/packages/accounts-2fa/package.js index bc757f1cb6..dc49dcfa6c 100644 --- a/packages/accounts-2fa/package.js +++ b/packages/accounts-2fa/package.js @@ -1,11 +1,12 @@ Package.describe({ version: '1.0.0', - summary: 'Package used to enable two factor authentication through OTP protocol', + summary: + 'Package used to enable two factor authentication through OTP protocol', }); Npm.depends({ - "node-2fa": "2.0.3", - "qrcode-svg": "1.1.0", + 'node-2fa': '2.0.3', + 'qrcode-svg': '1.1.0', }); Package.onUse(function(api) { @@ -14,9 +15,8 @@ Package.onUse(function(api) { // Export Accounts (etc) to packages using this one. api.imply('accounts-base', ['client', 'server']); + api.use('ecmascript'); - api.use("ecmascript"); - - api.addFiles(["2fa-client.js"], 'client'); - api.addFiles(["2fa-server.js"], 'server'); + api.addFiles(['2fa-client.js'], 'client'); + api.addFiles(['2fa-server.js'], 'server'); }); diff --git a/packages/accounts-password/password_client.js b/packages/accounts-password/password_client.js index 524dcb367f..c77208947f 100644 --- a/packages/accounts-password/password_client.js +++ b/packages/accounts-password/password_client.js @@ -10,27 +10,27 @@ const reportError = (error, callback) => { const internalLoginWithPassword = ({ selector, password, token, callback }) => { if (typeof selector === 'string') - if (!selector.includes('@')) - selector = {username: selector}; - else - selector = {email: selector}; + if (!selector.includes('@')) selector = { username: selector }; + else selector = { email: selector }; Accounts.callLoginMethod({ - methodArguments: [{ - user: selector, - password: Accounts._hashPassword(password), - token, - }], + methodArguments: [ + { + user: selector, + password: Accounts._hashPassword(password), + token, + }, + ], userCallback: (error, result) => { if (error) { reportError(error, callback); } else { callback && callback(); } - } + }, }); return selector; -} +}; // Attempt to log in with a password. // diff --git a/packages/accounts-password/password_server.js b/packages/accounts-password/password_server.js index 7a21e06144..cfc63581a0 100644 --- a/packages/accounts-password/password_server.js +++ b/packages/accounts-password/password_server.js @@ -184,12 +184,17 @@ Accounts.registerLoginHandler("password", options => { } // This method is added by the package accounts-2fa - if(Accounts.is2faEnabledForUser && Accounts.is2faEnabledForUser(options.user)){ - if(!options.token){ - Accounts._handleError("Token must be informed."); + if ( + Accounts.is2faEnabledForUser && + Accounts.is2faEnabledForUser(options.user) + ) { + if (!options.token) { + Accounts._handleError('Token must be informed.'); } - if(!Accounts.isTokenValid(user.twoFactorAuthentication.secret, options.token)){ - Accounts._handleError("Invalid token."); + if ( + !Accounts.isTokenValid(user.twoFactorAuthentication.secret, options.token) + ) { + Accounts._handleError('Invalid token.'); } } From 64701fe9465da2a3906a9179be175eb7c7b7ecc8 Mon Sep 17 00:00:00 2001 From: denihs Date: Thu, 20 Jan 2022 16:15:12 -0400 Subject: [PATCH 109/393] Changes requested on code review - adding test --- packages/accounts-2fa/2fa-server.js | 4 +- .../accounts-base/accounts_client_tests.js | 123 +++++++++++++++++- .../accounts-base/accounts_tests_setup.js | 31 +++++ packages/accounts-base/package.js | 1 + packages/accounts-password/password_client.js | 2 +- 5 files changed, 158 insertions(+), 3 deletions(-) diff --git a/packages/accounts-2fa/2fa-server.js b/packages/accounts-2fa/2fa-server.js index 33479be425..2f9fcecf5d 100644 --- a/packages/accounts-2fa/2fa-server.js +++ b/packages/accounts-2fa/2fa-server.js @@ -28,6 +28,8 @@ Accounts.is2faEnabledForUser = selector => { ); }; +Accounts.generate2faToken = secret => twofactor.generateToken(secret); + Accounts.isTokenValid = (secret, token) => { if (!Meteor.isServer) { throw new Meteor.Error( @@ -50,7 +52,7 @@ Meteor.methods({ const { username } = user; const { secret, uri } = twofactor.generateSecret({ - name: appName, + name: typeof appName === 'string' ? appName : undefined, account: username, }); const svg = new QRCode(uri).svg(); diff --git a/packages/accounts-base/accounts_client_tests.js b/packages/accounts-base/accounts_client_tests.js index 207870285d..47f2857b0c 100644 --- a/packages/accounts-base/accounts_client_tests.js +++ b/packages/accounts-base/accounts_client_tests.js @@ -1,8 +1,11 @@ +import {Accounts} from "meteor/accounts-base"; + const username = 'jsmith'; const password = 'password'; const excludeField = 'excludeField'; const defaultExcludeField = 'defaultExcludeField'; const excludeValue = 'foo'; +const secret2fa = 'shhhh'; const profile = { name: username, [excludeField]: excludeValue, @@ -22,12 +25,49 @@ const logoutAndCreateUser = (test, done, nextTests) => { }); }; -const removeTestUser = (done) => { +const createUserAndLogout = (test, done, nextTests) => { + // Setup a new test user + Accounts.createUser( + { + username, + password, + profile: { + name: username, + }, + }, + () => { + Meteor.logout(() => { + // Make sure we're logged out + test.isFalse(Meteor.user()); + // Handle next tests + nextTests(test, done); + }); + } + ); +}; + +const removeTestUser = done => { Meteor.call('removeAccountsTestUser', username, () => { done(); }); }; +const forceEnableUser2fa = done => { + Meteor.call('forceEnableUser2fa', username, secret2fa, (err, token) => { + done(token); + }); +}; + +const getTokenFromSecret = done => { + Meteor.call( + 'getTokenFromSecret', + { username }, + (err, token) => { + done(token); + } + ); +}; + Tinytest.addAsync( 'accounts - Meteor.loggingIn() is true right after a login call', (test, done) => { @@ -137,3 +177,84 @@ Tinytest.addAsync( }); } ); + + +Tinytest.addAsync( + 'accounts-2fa - Meteor.loginWithPasswordAnd2faToken() fails when token is not provided', + (test, done) => { + createUserAndLogout(test, done, () => { + try { + Meteor.loginWithPasswordAnd2faToken(username, password); + } catch (e) { + test.equal( + e.reason, + 'token is required to use loginWithPasswordAnd2faToken and must be a string' + ); + } finally { + test.isFalse(Meteor.user()); + removeTestUser(done); + } + }); + } +); + + +Tinytest.addAsync( + 'accounts-2fa - Meteor.loginWithPasswordAnd2faToken() fails with invalid token', + (test, done) => { + createUserAndLogout(test, done, () => { + forceEnableUser2fa(() => { + Meteor.loginWithPasswordAnd2faToken(username, password, 'ABC', e => { + test.isFalse(Meteor.user()); + test.equal(e.reason, 'Invalid token.'); + removeTestUser(done); + }); + }); + }); + } +); + +Tinytest.addAsync( + 'accounts-2fa - Meteor.loginWithPasswordAnd2faToken() succeeds when token is correct', + (test, done) => { + createUserAndLogout(test, done, () => { + forceEnableUser2fa((token) => { + Meteor.loginWithPasswordAnd2faToken(username, password, token, e => { + test.equal(e, undefined); + test.isTrue(Meteor.user()); + removeTestUser(done); + }); + }); + }); + } +); + +Tinytest.addAsync( + 'accounts-2fa - Generates secret, enable 2fa, verifies if 2fa is enabled, disable 2fa, verifies if 2fa is disabled', + (test, done) => { + logoutAndCreateUser(test, done, () => { + // Generates secret + Accounts.generate2faActivationQrCode((err, svg) => { + test.isTrue(svg != null); + getTokenFromSecret(token => { + // enable 2fa + Accounts.enableUser2fa(token, () => { + // verifies if 2fa is enabled + Accounts.has2faEnabled(username, (err, isEnabled) => { + test.isTrue(isEnabled); + // disable 2fa + Accounts.disableUser2fa(() => { + // verifies if 2fa is disabled + Accounts.has2faEnabled(username, (err, isEnabled) => { + test.isFalse(!!isEnabled); + removeTestUser(done); + }); + }); + }); + }); + }); + }); + }); + } +); + diff --git a/packages/accounts-base/accounts_tests_setup.js b/packages/accounts-base/accounts_tests_setup.js index 10c17f72ac..f2ab728f59 100644 --- a/packages/accounts-base/accounts_tests_setup.js +++ b/packages/accounts-base/accounts_tests_setup.js @@ -1,5 +1,36 @@ +const getTokenFromSecret = ({ username, secret: secretParam }) => { + let secret = secretParam; + + if (!secret) { + const { twoFactorAuthentication } = + Meteor.users.findOne({ username }) || {}; + if (!twoFactorAuthentication) { + throw new Meteor.Error(500, 'twoFactorAuthentication not set.'); + } + secret = twoFactorAuthentication.secret; + } + const { token } = Accounts.generate2faToken(secret); + + return token; +}; + Meteor.methods({ removeAccountsTestUser(username) { Meteor.users.remove({ username }); }, + forceEnableUser2fa(username, secret) { + Meteor.users.update( + { username }, + { + $set: { + twoFactorAuthentication: { + secret, + type: 'otp', + }, + }, + } + ); + return getTokenFromSecret({ username, secret }); + }, + getTokenFromSecret, }); diff --git a/packages/accounts-base/package.js b/packages/accounts-base/package.js index fb5a3befc8..75f2057c20 100644 --- a/packages/accounts-base/package.js +++ b/packages/accounts-base/package.js @@ -60,6 +60,7 @@ Package.onTest(api => { 'oauth-encryption', 'ddp', 'accounts-password', + 'accounts-2fa', ]); api.addFiles('accounts_tests_setup.js', 'server'); diff --git a/packages/accounts-password/password_client.js b/packages/accounts-password/password_client.js index c77208947f..dbee3f8355 100644 --- a/packages/accounts-password/password_client.js +++ b/packages/accounts-password/password_client.js @@ -78,7 +78,7 @@ Meteor.loginWithPasswordAnd2faToken = (selector, password, token, callback) => { if (token == null || typeof token !== 'string' || !token) { throw new Meteor.Error( 400, - 'token is required to use loginWithPasswordAnd2faToken and must be a string', + 'token is required to use loginWithPasswordAnd2faToken and must be a string' ); } return internalLoginWithPassword({ selector, password, token, callback }); From b31cee533d0b7b34cf4447d9b9bc51efb91a0214 Mon Sep 17 00:00:00 2001 From: denihs Date: Thu, 20 Jan 2022 16:56:50 -0400 Subject: [PATCH 110/393] Changes requested on code review - fixing typo --- docs/source/packages/accounts-2fa.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/packages/accounts-2fa.md b/docs/source/packages/accounts-2fa.md index 991845dc96..45e68a21e4 100644 --- a/docs/source/packages/accounts-2fa.md +++ b/docs/source/packages/accounts-2fa.md @@ -133,7 +133,7 @@ So the call of this function should look something like this:

Disabling 2FA

-To disable 2FA you call simply use this function: +To disable 2FA you can simply use this function: {% apibox "Accounts.disableUser2fa" "module":"accounts-base" %} From d63a93875f5fc1add6e2979a02629f3b7d2d2697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=BCster?= Date: Fri, 21 Jan 2022 17:12:41 +0100 Subject: [PATCH 111/393] fix(ejson): fix equals check with Arrays --- packages/ejson/ejson.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/ejson/ejson.js b/packages/ejson/ejson.js index a4670297d2..25efbc761d 100644 --- a/packages/ejson/ejson.js +++ b/packages/ejson/ejson.js @@ -486,10 +486,16 @@ EJSON.equals = (a, b, options) => { return b.equals(a, options); } - if (a instanceof Array) { - if (!(b instanceof Array)) { - return false; - } + // Array.isArray works across iframes while instanceof won't + const aIsArray = Array.isArray(a); + const bIsArray = Array.isArray(b); + + // if not both or none are array they are not equal + if (aIsArray !== bIsArray) { + return false; + } + + if (aIsArray && bIsArray) { if (a.length !== b.length) { return false; } From 9c76ea63c3f3f8b9f6432dd5bdb607e4282edcd2 Mon Sep 17 00:00:00 2001 From: filipenevola Date: Wed, 3 Nov 2021 18:40:49 -0400 Subject: [PATCH 112/393] MongoDB 5.0 Support - Supporting new error name for BulkWrite --- packages/allow-deny/allow-deny.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/allow-deny/allow-deny.js b/packages/allow-deny/allow-deny.js index be5483feed..e1bff9f9ab 100644 --- a/packages/allow-deny/allow-deny.js +++ b/packages/allow-deny/allow-deny.js @@ -194,7 +194,10 @@ CollectionPrototype._defineMutationMethods = function(options) { } catch (e) { if ( e.name === 'MongoError' || + // for old versions of MongoDB (probably not necessary but it's here just in case) e.name === 'BulkWriteError' || + // for newer versions of MongoDB (https://docs.mongodb.com/drivers/node/current/whats-new/#bulkwriteerror---mongobulkwriteerror) + e.name === 'MongoBulkWriteError' || e.name === 'MinimongoError' ) { throw new Meteor.Error(409, e.toString()); From d0ea1f54d5d4bc43d544cc1af68ef1ea9725f69c Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Tue, 7 Dec 2021 16:36:09 -0300 Subject: [PATCH 113/393] useUnifiedTopology is not an option anymore on Mongo 4.2.1 - update history --- History.md | 6 ++++++ packages/mongo/mongo_driver.js | 15 --------------- packages/npm-mongo/package.js | 4 ++-- 3 files changed, 8 insertions(+), 17 deletions(-) diff --git a/History.md b/History.md index 5120c73e47..2b4d7241a7 100644 --- a/History.md +++ b/History.md @@ -128,6 +128,12 @@ This version should be ignored. Proceed to 2.5.5 above. * `standard-minifier-js@2.7.3` - Using `minifier-js@2.7.3` + +* `mongo@1.14.0` + - useUnifiedTopology is not an option anymore, it defaults to true. + +* `npm-mongo@4.2.1` + - Update MongoDB driver version to 4.2.1 ## v2.5.1, 2021-11-17 diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 7997ddec73..72d9108d55 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -146,23 +146,8 @@ MongoConnection = function (url, options) { var mongoOptions = Object.assign({ ignoreUndefined: true, - // (node:59240) [MONGODB DRIVER] Warning: Current Server Discovery and - // Monitoring engine is deprecated, and will be removed in a future version. - // To use the new Server Discover and Monitoring engine, pass option - // { useUnifiedTopology: true } to the MongoClient constructor. - useUnifiedTopology: true, }, userOptions); - // The autoReconnect and reconnectTries options are incompatible with - // useUnifiedTopology: https://github.com/meteor/meteor/pull/10861#commitcomment-37525845 - if (!mongoOptions.useUnifiedTopology) { - // Reconnect on error. This defaults to true, but it never hurts to be - // explicit about it. - mongoOptions.autoReconnect = true; - // Try to reconnect forever, instead of stopping after 30 tries (the - // default), with each attempt separated by 1000ms. - mongoOptions.reconnectTries = Infinity; - } // Disable the native parser by default, unless specifically enabled // in the mongo URL. diff --git a/packages/npm-mongo/package.js b/packages/npm-mongo/package.js index 600d277450..ff62ade192 100644 --- a/packages/npm-mongo/package.js +++ b/packages/npm-mongo/package.js @@ -3,12 +3,12 @@ Package.describe({ summary: "Wrapper around the mongo npm package", - version: "3.9.1", + version: "4.2.1", documentation: null }); Npm.depends({ - mongodb: "3.6.10" + mongodb: "4.2.1" }); Package.onUse(function (api) { From 06ba9fa4b059ea80aace23aeea0322b6168a52c6 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Tue, 7 Dec 2021 17:01:07 -0300 Subject: [PATCH 114/393] fields option is deprecated on mongo queries, projection is the default now --- History.md | 2 ++ packages/mongo/mongo_driver.js | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/History.md b/History.md index 2b4d7241a7..ac686a77a4 100644 --- a/History.md +++ b/History.md @@ -131,6 +131,8 @@ This version should be ignored. Proceed to 2.5.5 above. * `mongo@1.14.0` - useUnifiedTopology is not an option anymore, it defaults to true. + - fields option is deprecated, we are maintaining a translation layer to "projection" field (now prefered) until the next minor version, where we will start showing alerts. + * `npm-mongo@4.2.1` - Update MongoDB driver version to 4.2.1 diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 72d9108d55..715164a9de 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -877,6 +877,12 @@ CursorDescription = function (collectionName, selector, options) { self.collectionName = collectionName; self.selector = Mongo.Collection._rewriteSelector(selector); self.options = options || {}; + // transform fields key in projection + const { fields, projection, ...otherOptions } = self.options; + // TODO: enable this comment when deprecating the fields option + // Log.debug(`fields option has been deprecated, please use the new 'projection' instead`) + + self.options = { ...otherOptions, ...(projection || fields ? {projection: fields || projection} : {}) }; }; Cursor = function (mongo, cursorDescription) { From 158b9668dff2797dfc5289d9005260f2103c2eb6 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Tue, 7 Dec 2021 17:03:55 -0300 Subject: [PATCH 115/393] Show deprecation warning for _ensureIndex --- History.md | 2 ++ packages/mongo/collection.js | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/History.md b/History.md index ac686a77a4..accaf7ace4 100644 --- a/History.md +++ b/History.md @@ -132,6 +132,8 @@ This version should be ignored. Proceed to 2.5.5 above. * `mongo@1.14.0` - useUnifiedTopology is not an option anymore, it defaults to true. - fields option is deprecated, we are maintaining a translation layer to "projection" field (now prefered) until the next minor version, where we will start showing alerts. + - _ensureIndex is now showing a deprecation message + * `npm-mongo@4.2.1` diff --git a/packages/mongo/collection.js b/packages/mongo/collection.js index ad5ab93c6f..eca28116c5 100644 --- a/packages/mongo/collection.js +++ b/packages/mongo/collection.js @@ -726,9 +726,8 @@ Object.assign(Mongo.Collection.prototype, { var self = this; if (!self._collection._ensureIndex || !self._collection.createIndex) throw new Error('Can only call createIndex on server collections'); - // TODO enable this message a release before we will remove this function - // import { Log } from 'meteor/logging'; - // Log.debug(`_ensureIndex has been deprecated, please use the new 'createIndex' instead${options?.name ? `, index name: ${options.name}` : `, index: ${JSON.stringify(index)}`}`) + import { Log } from 'meteor/logging'; + Log.debug(`_ensureIndex has been deprecated, please use the new 'createIndex' instead${options?.name ? `, index name: ${options.name}` : `, index: ${JSON.stringify(index)}`}`) if (self._collection.createIndex) { self._collection.createIndex(index, options); } else { From af05771a41524d3f4f4494aedf875fd2cd0ea089 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Tue, 7 Dec 2021 21:07:58 -0300 Subject: [PATCH 116/393] Update connect method on mongo 5 --- packages/mongo/mongo_driver.js | 5 +++-- packages/mongo/mongo_livedata_tests.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 715164a9de..2dcd4d7d95 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -190,9 +190,10 @@ MongoConnection = function (url, options) { var connectFuture = new Future; - MongoDB.connect( + new MongoDB.MongoClient( url, - mongoOptions, + mongoOptions + ).connect( Meteor.bindEnvironment( function (err, client) { if (err) { diff --git a/packages/mongo/mongo_livedata_tests.js b/packages/mongo/mongo_livedata_tests.js index 8f77d1c822..a19edd3a72 100644 --- a/packages/mongo/mongo_livedata_tests.js +++ b/packages/mongo/mongo_livedata_tests.js @@ -3318,7 +3318,7 @@ Meteor.isServer && Tinytest.add("mongo-livedata - npm modules", function (test) var c = new Mongo.Collection(Random.id()); var rawCollection = c.rawCollection(); test.isTrue(rawCollection); - test.isTrue(rawCollection.findAndModify); + test.isTrue(rawCollection.findOneAndUpdate); var rawDb = c.rawDatabase(); test.isTrue(rawDb); test.isTrue(rawDb.admin); From a05c9b9dff1f98c17cc0bafd594bc2a8b4aa9e5f Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Tue, 7 Dec 2021 21:11:44 -0300 Subject: [PATCH 117/393] Update mongo version to 5.0.5 on dev-bundle --- meteor | 2 +- scripts/build-dev-bundle-common.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/meteor b/meteor index 16c71b9e08..44a3e4a67c 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/usr/bin/env bash -BUNDLE_VERSION=14.18.3.0 +BUNDLE_VERSION=14.18.3.1 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. diff --git a/scripts/build-dev-bundle-common.sh b/scripts/build-dev-bundle-common.sh index 64dbeaa4f9..14ba7789df 100644 --- a/scripts/build-dev-bundle-common.sh +++ b/scripts/build-dev-bundle-common.sh @@ -6,7 +6,7 @@ set -u UNAME=$(uname) ARCH=$(uname -m) NODE_VERSION=14.18.3 -MONGO_VERSION_64BIT=4.4.4 +MONGO_VERSION_64BIT=5.0.5 MONGO_VERSION_32BIT=3.2.22 NPM_VERSION=6.14.15 From b87f326329446a2053682d5e7c7354d52f4b5767 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Tue, 7 Dec 2021 22:12:43 -0300 Subject: [PATCH 118/393] waitForStepDownOnNonCommandShutdown is not needed anymore --- tools/runners/run-mongo.js | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/tools/runners/run-mongo.js b/tools/runners/run-mongo.js index 0d2fae2ade..1ad2b0693f 100644 --- a/tools/runners/run-mongo.js +++ b/tools/runners/run-mongo.js @@ -71,19 +71,7 @@ function spawnMongod(mongodPath, port, dbPath, replSetName) { '8', '--replSet', replSetName, - '--noauth', - - // Starting with version 4.0.8/4.1.10, MongoDB performs a step down - // procedure if the primary receives a SIGTERM signal - // (https://jira.mongodb.org/browse/SERVER-38994). During this procedure, - // the process doesn't shut down for up to ten seconds until a secondary - // becomes the new primary. Since Meteor starts a single-node replica set, - // this is unnecessary because there are no secondaries. The following - // parameter disables the step down. This will be the default for single- - // node replica sets in MongoDB 4.3 (relevant commit: https://git.io/JeNkT), - // so the parameter can be removed in the future. - '--setParameter', - 'waitForStepDownOnNonCommandShutdown=false', + '--noauth' ]; // Use mmapv1 on 32bit platforms, as our binary doesn't support WT @@ -104,6 +92,7 @@ function spawnMongod(mongodPath, port, dbPath, replSetName) { args = ['-x86_64', mongodPath, ...args]; mongodPath = 'arch'; } + console.log(args.join(' ')) return child_process.spawn(mongodPath, args, { // Apparently in some contexts, Mongo crashes if your locale isn't set up // right. I wasn't able to reproduce it, but many people on #4019 From f67c70519fc61ee521f37d7179faf5c4cd942fbe Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Wed, 8 Dec 2021 08:08:21 -0300 Subject: [PATCH 119/393] native_parser is no longer an option, and we cant connect anymore with new Server() --- packages/mongo/mongo_driver.js | 11 ----------- tools/runners/run-mongo.js | 4 ++-- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 2dcd4d7d95..127df81f15 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -149,17 +149,6 @@ MongoConnection = function (url, options) { }, userOptions); - // Disable the native parser by default, unless specifically enabled - // in the mongo URL. - // - The native driver can cause errors which normally would be - // thrown, caught, and handled into segfaults that take down the - // whole app. - // - Binary modules don't yet work when you bundle and move the bundle - // to a different platform (aka deploy) - // We should revisit this after binary npm module support lands. - if (!(/[\?&]native_?[pP]arser=/.test(url))) { - mongoOptions.native_parser = false; - } // Internally the oplog connections specify their own poolSize // which we don't want to overwrite with any user defined value diff --git a/tools/runners/run-mongo.js b/tools/runners/run-mongo.js index 1ad2b0693f..d5f7260800 100644 --- a/tools/runners/run-mongo.js +++ b/tools/runners/run-mongo.js @@ -688,12 +688,12 @@ var launchMongo = function(options) { // Connect to the intended primary and start a replset. const client = new MongoClient( - new Server('127.0.0.1', options.port, { + `mongodb://127.0.0.1:${options.port}`, { poolSize: 1, socketOptions: { connectTimeoutMS: 60000, }, - }) + } ); yieldingMethod(client, 'connect'); From ae7d5c107bdff3914f856726e8f11abdeca04db4 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Wed, 8 Dec 2021 10:50:38 -0300 Subject: [PATCH 120/393] poolSize is no longer an option db.serverConfig is no longer available, topology events are emmited from MongoClient --- packages/mongo/mongo_driver.js | 8 ++++---- tools/runners/run-mongo.js | 26 ++++++++++++++------------ 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 127df81f15..fc869d1b2c 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -190,13 +190,13 @@ MongoConnection = function (url, options) { } var db = client.db(); - + const helloDocument = db.admin().command( { hello: 1 } ).await(); // First, figure out what the current primary is, if any. - if (db.serverConfig.isMasterDoc) { - self._primary = db.serverConfig.isMasterDoc.primary; + if (helloDocument.isWritablePrimary) { + self._primary = helloDocument.primary; } - db.serverConfig.on( + client.topology.on( 'joined', Meteor.bindEnvironment(function (kind, doc) { if (kind === 'primary') { if (doc.primary !== self._primary) { diff --git a/tools/runners/run-mongo.js b/tools/runners/run-mongo.js index d5f7260800..443a8dcc6e 100644 --- a/tools/runners/run-mongo.js +++ b/tools/runners/run-mongo.js @@ -679,21 +679,21 @@ var launchMongo = function(options) { stopOrReadyPromise.await(); }; - var initiateReplSetAndWaitForReady = function() { + var initiateReplSetAndWaitForReady = function () { try { // Load mongo so we'll be able to talk to it. - const { MongoClient, Server } = loadIsopackage( - 'npm-mongo' + const {MongoClient} = loadIsopackage( + 'npm-mongo' ).NpmModuleMongodb; // Connect to the intended primary and start a replset. const client = new MongoClient( - `mongodb://127.0.0.1:${options.port}`, { - poolSize: 1, - socketOptions: { - connectTimeoutMS: 60000, - }, - } + `mongodb://127.0.0.1:${options.port}`, { + minPoolSize: 1, + maxPoolSize: 1, + socketTimeoutMS: 60000, + directConnection: true + } ); yieldingMethod(client, 'connect'); @@ -707,7 +707,7 @@ var launchMongo = function(options) { _id: replSetName, version: 1, protocolVersion: 1, - members: [{ _id: 0, host: '127.0.0.1:' + options.port, priority: 100 }], + members: [{_id: 0, host: '127.0.0.1:' + options.port, priority: 100}], }; try { @@ -762,7 +762,7 @@ var launchMongo = function(options) { // Wait until the primary is writable. If it isn't writable after one // minute, throw an error and report the replica set status. while (!stopped) { - const { ismaster } = yieldingMethod(db.admin(), 'command', { + const {ismaster} = yieldingMethod(db.admin(), 'command', { isMaster: 1, }); @@ -774,7 +774,7 @@ var launchMongo = function(options) { }); throw new Error( - 'Primary not writable after one minute. Last replica set status: ' + + 'Primary not writable after one minute. Last replica set status: ' + JSON.stringify(status) ); } @@ -791,6 +791,7 @@ var launchMongo = function(options) { } } }; + console.log("DONE! - Enabling rplset") try { if (options.multiple) { @@ -812,6 +813,7 @@ var launchMongo = function(options) { var portFile = !noOplog && files.pathJoin(dbPath, 'METEOR-PORT'); launchOneMongoAndWaitForReadyForInitiate(dbPath, options.port, portFile); if (!stopped && !noOplog) { + console.log("got here"); initiateReplSetAndWaitForReady(); if (!stopped) { // Write down that we configured the database properly. From 2013ba400cec7a9198f618f85b64b5f3896df616 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Wed, 8 Dec 2021 10:51:19 -0300 Subject: [PATCH 121/393] poolSize is no longer an option, use maxPoolSize instead --- packages/mongo/mongo_driver.js | 6 +++--- packages/mongo/oplog_tailing.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index fc869d1b2c..82cd70e22c 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -150,12 +150,12 @@ MongoConnection = function (url, options) { - // Internally the oplog connections specify their own poolSize + // Internally the oplog connections specify their own maxPoolSize // which we don't want to overwrite with any user defined value - if (_.has(options, 'poolSize')) { + if (_.has(options, 'maxPoolSize')) { // If we just set this for "server", replSet will override it. If we just // set it for replSet, it will be ignored if we're not using a replSet. - mongoOptions.poolSize = options.poolSize; + mongoOptions.maxPoolSize = options.maxPoolSize; } // Transform options like "tlsCAFileAsset": "filename.pem" into diff --git a/packages/mongo/oplog_tailing.js b/packages/mongo/oplog_tailing.js index fabbd1a3c1..fc702318db 100644 --- a/packages/mongo/oplog_tailing.js +++ b/packages/mongo/oplog_tailing.js @@ -206,12 +206,12 @@ Object.assign(OplogHandle.prototype, { // The tail connection will only ever be running a single tail command, so // it only needs to make one underlying TCP connection. self._oplogTailConnection = new MongoConnection( - self._oplogUrl, {poolSize: 1}); + self._oplogUrl, {maxPoolSize: 1}); // XXX better docs, but: it's to get monotonic results // XXX is it safe to say "if there's an in flight query, just use its // results"? I don't think so but should consider that self._oplogLastEntryConnection = new MongoConnection( - self._oplogUrl, {poolSize: 1}); + self._oplogUrl, {maxPoolSize: 1}); // Now, make sure that there actually is a repl set here. If not, oplog // tailing won't ever find anything! From f41f99b9d94e5074f89c4d0852c78688045de1a0 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Wed, 8 Dec 2021 10:53:27 -0300 Subject: [PATCH 122/393] db.collection is no longer async, and the callback option is no longer available --- packages/mongo/mongo_driver.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 82cd70e22c..463572ea24 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -258,9 +258,7 @@ MongoConnection.prototype.rawCollection = function (collectionName) { if (! self.db) throw Error("rawCollection called before Connection created?"); - var future = new Future; - self.db.collection(collectionName, future.resolver()); - return future.wait(); + return self.db.collection(collectionName); }; MongoConnection.prototype._createCappedCollection = function ( From c62ca287eae6de1ac93f37ea24947f6b44ada8da Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Wed, 8 Dec 2021 11:02:29 -0300 Subject: [PATCH 123/393] Find count() no longers accept applySkipLimit, instead it accepts an option object --- packages/mongo/mongo_driver.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 463572ea24..041a17e307 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -1148,9 +1148,9 @@ _.extend(SynchronousCursor.prototype, { return self.map(_.identity); }, - count: function (applySkipLimit = false) { + count: function (options = {}) { var self = this; - return self._synchronousCount(applySkipLimit).wait(); + return self._synchronousCount(options).wait(); }, // This method is NOT wrapped in Cursor. From d4bff7199acfcd0ee5694dd8395db91a7f51e6b0 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Wed, 8 Dec 2021 11:16:42 -0300 Subject: [PATCH 124/393] Find count() no longers accept applySkipLimit, instead it takes no options anymore Use insertOne/insertMany --- History.md | 2 +- packages/minimongo/cursor.js | 9 +- packages/mongo/mongo_driver.js | 6 +- packages/mongo/mongo_livedata_tests.js | 6 +- packages/mongo/oplog_tests.js | 2 +- .../.npm/package/npm-shrinkwrap.json | 128 ++++++++---------- tools/runners/run-mongo.js | 3 - 7 files changed, 66 insertions(+), 90 deletions(-) diff --git a/History.md b/History.md index accaf7ace4..be4ddb04e3 100644 --- a/History.md +++ b/History.md @@ -133,7 +133,7 @@ This version should be ignored. Proceed to 2.5.5 above. - useUnifiedTopology is not an option anymore, it defaults to true. - fields option is deprecated, we are maintaining a translation layer to "projection" field (now prefered) until the next minor version, where we will start showing alerts. - _ensureIndex is now showing a deprecation message - + - applySkipLimit option for count() on find cursors is no longer supported. * `npm-mongo@4.2.1` diff --git a/packages/minimongo/cursor.js b/packages/minimongo/cursor.js index 7c000af170..8d5a938be2 100644 --- a/packages/minimongo/cursor.js +++ b/packages/minimongo/cursor.js @@ -41,16 +41,11 @@ export default class Cursor { * @summary Returns the number of documents that match a query. * @memberOf Mongo.Cursor * @method count - * @param {boolean} [applySkipLimit=true] If set to `false`, the value - * returned will reflect the total - * number of matching documents, - * ignoring any value supplied for - * limit * @instance * @locus Anywhere * @returns {Number} */ - count(applySkipLimit = true) { + count() { if (this.reactive) { // allow the observe to be unordered this._depend({added: true, removed: true}, true); @@ -58,7 +53,7 @@ export default class Cursor { return this._getRawObjects({ ordered: true, - applySkipLimit + applySkipLimit: true }).length; } diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 041a17e307..b858220a2e 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -374,7 +374,7 @@ MongoConnection.prototype._insert = function (collection_name, document, callback = bindEnvironmentForWrite(writeCallback(write, refresh, callback)); try { var collection = self.rawCollection(collection_name); - collection.insert(replaceTypes(document, replaceMeteorAtomWithMongo), + collection.insertOne(replaceTypes(document, replaceMeteorAtomWithMongo), {safe: true}, callback); } catch (err) { write.committed(); @@ -1148,9 +1148,9 @@ _.extend(SynchronousCursor.prototype, { return self.map(_.identity); }, - count: function (options = {}) { + count: function () { var self = this; - return self._synchronousCount(options).wait(); + return self._synchronousCount().wait(); }, // This method is NOT wrapped in Cursor. diff --git a/packages/mongo/mongo_livedata_tests.js b/packages/mongo/mongo_livedata_tests.js index a19edd3a72..470bc6e8c7 100644 --- a/packages/mongo/mongo_livedata_tests.js +++ b/packages/mongo/mongo_livedata_tests.js @@ -311,13 +311,11 @@ Tinytest.addAsync("mongo-livedata - basics, " + idGeneration, function (test, on // Regression test for https://github.com/meteor/meteor/issues/7436 - // - ensure applySkipLimit defaults to false for count() + // - ensure applySkipLimit defaults to true for count() // Note that the current behavior is inconsistent on the client. // (https://github.com/meteor/meteor/issues/1201) if (Meteor.isServer) { - test.equal(coll.find({run: run}, {limit: 1}).count(), 2); - test.equal(coll.find({run: run}, {limit: 1}).count(true), 1); - test.equal(coll.find({run: run}, {limit: 1}).count(false), 2); + test.equal(coll.find({run: run}, {limit: 1}).count(), 1); } var cur = coll.find({run: run}, {sort: ["x"]}); diff --git a/packages/mongo/oplog_tests.js b/packages/mongo/oplog_tests.js index 7789f54a9f..e327c2321e 100644 --- a/packages/mongo/oplog_tests.js +++ b/packages/mongo/oplog_tests.js @@ -91,7 +91,7 @@ process.env.MONGO_OPLOG_URL && testAsyncMulti( } // XXX implement bulk insert #1255 var rawCollection = self.collection.rawCollection(); - rawCollection.insert(docs, Meteor.bindEnvironment(expect(function (err) { + rawCollection.insertMany(docs, Meteor.bindEnvironment(expect(function (err) { test.isFalse(err); }))); }, diff --git a/packages/npm-mongo/.npm/package/npm-shrinkwrap.json b/packages/npm-mongo/.npm/package/npm-shrinkwrap.json index 3f30a25eaa..fa086c41c6 100644 --- a/packages/npm-mongo/.npm/package/npm-shrinkwrap.json +++ b/packages/npm-mongo/.npm/package/npm-shrinkwrap.json @@ -1,35 +1,45 @@ { "lockfileVersion": 1, "dependencies": { - "bl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", - "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==" + "@types/node": { + "version": "16.11.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", + "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==" + }, + "@types/webidl-conversions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz", + "integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q==" + }, + "@types/whatwg-url": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz", + "integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==" + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "bson": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", - "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==" + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.0.tgz", + "integrity": "sha512-8jw1NU1hglS+Da1jDOUYuNcBJ4cNHCFIqzlwoFNnsTOg2R/ox0aTYcTiBN4dzRa9q7Cvy6XErh3L8ReTEb9AQQ==" }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==" }, "denque": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz", - "integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz", + "integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==" }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "memory-pager": { "version": "1.5.0", @@ -37,41 +47,19 @@ "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" }, "mongodb": { - "version": "3.6.10", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.10.tgz", - "integrity": "sha512-fvIBQBF7KwCJnDZUnFFy4WqEFP8ibdXeFANnylW19+vOwdjOAvqIzPdsNCEMT6VKTHnYu4K64AWRih0mkFms6Q==" + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.2.1.tgz", + "integrity": "sha512-nDC+ulM/Ea3Q2VG5eemuGfB7T4ORwrtKegH2XW9OLlUBgQF6OTNrzFCS1Z3SJGVA+T0Sr1xBYV6DMnp0A7us0g==" }, - "optional-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.1.tgz", - "integrity": "sha512-EnUe33GTAltyZlIsQ2l93KzBC9zi8BsxLvKP3wxALOsz/YIakVojyuZsv5PFFk8y8e6r+SbaPIsNmyPoSK0OHw==" + "mongodb-connection-string-url": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.2.0.tgz", + "integrity": "sha512-U0cDxLUrQrl7DZA828CA+o69EuWPWEJTwdMPozyd7cy/dbtncUZczMw7wRHcwMD7oKOn0NM2tF9jdf5FFVW9CA==" }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "require-at": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz", - "integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==" - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "saslprep": { "version": "1.0.3", @@ -83,22 +71,20 @@ "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=" }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } + "tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==" }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" + }, + "whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==" } } } diff --git a/tools/runners/run-mongo.js b/tools/runners/run-mongo.js index 443a8dcc6e..f61f1bffcd 100644 --- a/tools/runners/run-mongo.js +++ b/tools/runners/run-mongo.js @@ -92,7 +92,6 @@ function spawnMongod(mongodPath, port, dbPath, replSetName) { args = ['-x86_64', mongodPath, ...args]; mongodPath = 'arch'; } - console.log(args.join(' ')) return child_process.spawn(mongodPath, args, { // Apparently in some contexts, Mongo crashes if your locale isn't set up // right. I wasn't able to reproduce it, but many people on #4019 @@ -791,7 +790,6 @@ var launchMongo = function(options) { } } }; - console.log("DONE! - Enabling rplset") try { if (options.multiple) { @@ -813,7 +811,6 @@ var launchMongo = function(options) { var portFile = !noOplog && files.pathJoin(dbPath, 'METEOR-PORT'); launchOneMongoAndWaitForReadyForInitiate(dbPath, options.port, portFile); if (!stopped && !noOplog) { - console.log("got here"); initiateReplSetAndWaitForReady(); if (!stopped) { // Write down that we configured the database properly. From 9e9c9c9e6eba02b8d0e37d507fea9dcfdbd578e4 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Tue, 4 Jan 2022 14:17:48 -0300 Subject: [PATCH 125/393] Use updateMany/deleteMany/replaceOne - we are sticking with Meteor default "update" for deciding which internal mongodb method to call based in the existence of "$" operators --- packages/mongo/mongo_driver.js | 38 ++++++++++++++++---------- packages/mongo/mongo_livedata_tests.js | 2 +- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index b858220a2e..5a8c754406 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -422,11 +422,15 @@ MongoConnection.prototype._remove = function (collection_name, selector, try { var collection = self.rawCollection(collection_name); - var wrappedCallback = function(err, driverResult) { - callback(err, transformResult(driverResult).numberAffected); - }; - collection.remove(replaceTypes(selector, replaceMeteorAtomWithMongo), - {safe: true}, wrappedCallback); + const result = {}; + try { + const {deletedCount} = collection.deleteMany(replaceTypes(selector, replaceMeteorAtomWithMongo), + {safe: true}).await(); + result.numberAffected = deletedCount; + }catch(err){ + callback(err) + } + callback(null, result) } catch (err) { write.committed(); throw err; @@ -602,11 +606,14 @@ MongoConnection.prototype._update = function (collection_name, selector, mod, Object.assign(mongoMod.$setOnInsert, replaceTypes({_id: options.insertedId}, replaceMeteorAtomWithMongo)); } - collection.update( + const strings = Object.keys(mongoMod).filter((key) => !key.startsWith("$")); + let updateMethod = strings.length > 0 ? 'replaceOne' : 'updateMany'; + updateMethod = updateMethod === 'updateMany' && !mongoOpts.multi ? 'updateOne' : updateMethod; + collection[updateMethod]( mongoSelector, mongoMod, mongoOpts, bindEnvironmentForWrite(function (err, result) { if (! err) { - var meteorResult = transformResult(result); + var meteorResult = transformResult({result}); if (meteorResult && options._returnObject) { // If this was an upsert() call, and we ended up // inserting a new doc and we know its id, then @@ -638,15 +645,14 @@ var transformResult = function (driverResult) { var meteorResult = { numberAffected: 0 }; if (driverResult) { var mongoResult = driverResult.result; - // On updates with upsert:true, the inserted values come as a list of // upserted values -- even with options.multi, when the upsert does insert, // it only inserts one element. - if (mongoResult.upserted) { - meteorResult.numberAffected += mongoResult.upserted.length; + if (mongoResult.upsertedCount) { + meteorResult.numberAffected += mongoResult.upsertedCount.length; - if (mongoResult.upserted.length == 1) { - meteorResult.insertedId = mongoResult.upserted[0]._id; + if (mongoResult.upsertedCount.length === 1) { + meteorResult.insertedId = mongoResult.upsertedId; } } else { meteorResult.numberAffected = mongoResult.n; @@ -715,7 +721,11 @@ var simulateUpsertWithInsertedId = function (collection, selector, mod, if (! tries) { callback(new Error("Upsert failed after " + NUM_OPTIMISTIC_TRIES + " tries.")); } else { - collection.update(selector, mod, mongoOptsForUpdate, + let method = collection.updateMany; + if(!Object.keys(mod).some(key => key.startsWith("$"))){ + method = collection.replaceOne; + } + method(selector, mod, {upsert:true,...mongoOptsForUpdate}, bindEnvironmentForWrite(function (err, result) { if (err) { callback(err); @@ -731,7 +741,7 @@ var simulateUpsertWithInsertedId = function (collection, selector, mod, }; var doConditionalInsert = function () { - collection.update(selector, replacementWithId, mongoOptsForInsert, + collection.updateMany(selector, replacementWithId, mongoOptsForInsert, bindEnvironmentForWrite(function (err, result) { if (err) { // figure out if this is a diff --git a/packages/mongo/mongo_livedata_tests.js b/packages/mongo/mongo_livedata_tests.js index 470bc6e8c7..4ad47bb2fa 100644 --- a/packages/mongo/mongo_livedata_tests.js +++ b/packages/mongo/mongo_livedata_tests.js @@ -3546,7 +3546,7 @@ if (Meteor.isServer) { session.withTransaction(session => { let promise = Promise.resolve(); ["a", "b"].forEach((id, index) => { - promise = promise.then(() => rawCollection.update( + promise = promise.then(() => rawCollection.updateMany( { _id: id }, { $set: { field: `updated${index + 1}` } }, { session } From a031b774714d5096ef8cfbeb38affad084b1dccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Tue, 4 Jan 2022 15:26:47 -0400 Subject: [PATCH 126/393] Fixing tests in mongo livedata and observe changes --- packages/mongo/collection.js | 19 ++++++++++++++++--- packages/mongo/mongo_driver.js | 20 ++++++++++---------- packages/mongo/mongo_livedata_tests.js | 21 +++++++++++++++------ packages/mongo/mongo_utils.js | 11 +++++++++++ packages/mongo/observe_changes_tests.js | 9 ++++----- packages/tinytest/tinytest.js | 6 +++--- 6 files changed, 59 insertions(+), 27 deletions(-) create mode 100644 packages/mongo/mongo_utils.js diff --git a/packages/mongo/collection.js b/packages/mongo/collection.js index eca28116c5..44c1fdfcad 100644 --- a/packages/mongo/collection.js +++ b/packages/mongo/collection.js @@ -1,6 +1,8 @@ // options.connection, if given, is a LivedataClient or LivedataServer // XXX presently there is no way to destroy/clean up a Collection +import { normalizeProjection } from "./mongo_utils"; + /** * @summary Namespace for MongoDB-related items * @namespace @@ -320,15 +322,18 @@ Object.assign(Mongo.Collection.prototype, { }, _getFindOptions(args) { + const [, options] = args || []; + const newOptions = normalizeProjection(options); + var self = this; if (args.length < 2) { return { transform: self._transform }; } else { check( - args[1], + newOptions, Match.Optional( Match.ObjectIncluding({ - fields: Match.Optional(Match.OneOf(Object, undefined)), + projection: Match.Optional(Match.OneOf(Object, undefined)), sort: Match.Optional( Match.OneOf(Object, Array, Function, undefined) ), @@ -338,9 +343,17 @@ Object.assign(Mongo.Collection.prototype, { ) ); + const { projection } = newOptions; + + // this error: "Cannot do exclusion on field _id in inclusion projection" + // happens on MongoDB CLI but doesn't happen in the Node.js Driver for MongoDB 5.0+ + if (projection && projection._id != null && !projection._id) { + throw new Error(`Cannot do exclusion on field _id in inclusion projection, collectionName=${self._name}`); + } + return { transform: self._transform, - ...args[1], + ...newOptions, }; } }, diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 5a8c754406..c9f496f1e6 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -1,3 +1,5 @@ +import { normalizeProjection } from "./mongo_utils"; + /** * Provide a synchronous Collection API using fibers, backed by * MongoDB. This is only for use on the server, and mostly identical @@ -422,15 +424,15 @@ MongoConnection.prototype._remove = function (collection_name, selector, try { var collection = self.rawCollection(collection_name); - const result = {}; + let result = {}; try { const {deletedCount} = collection.deleteMany(replaceTypes(selector, replaceMeteorAtomWithMongo), {safe: true}).await(); - result.numberAffected = deletedCount; + result.modifiedCount = deletedCount; }catch(err){ callback(err) } - callback(null, result) + callback(null, transformResult({result}).numberAffected) } catch (err) { write.committed(); throw err; @@ -642,6 +644,8 @@ MongoConnection.prototype._update = function (collection_name, selector, mod, }; var transformResult = function (driverResult) { + console.log(`driverResult`, driverResult); + var meteorResult = { numberAffected: 0 }; if (driverResult) { var mongoResult = driverResult.result; @@ -655,7 +659,9 @@ var transformResult = function (driverResult) { meteorResult.insertedId = mongoResult.upsertedId; } } else { - meteorResult.numberAffected = mongoResult.n; + // n was used before Mongo 5.0, in Mongo 5.0 we are not receiving this n + // field and so we are using modifiedCount instead + meteorResult.numberAffected = mongoResult.n || mongoResult.modifiedCount; } } @@ -875,12 +881,6 @@ CursorDescription = function (collectionName, selector, options) { self.collectionName = collectionName; self.selector = Mongo.Collection._rewriteSelector(selector); self.options = options || {}; - // transform fields key in projection - const { fields, projection, ...otherOptions } = self.options; - // TODO: enable this comment when deprecating the fields option - // Log.debug(`fields option has been deprecated, please use the new 'projection' instead`) - - self.options = { ...otherOptions, ...(projection || fields ? {projection: fields || projection} : {}) }; }; Cursor = function (mongo, cursorDescription) { diff --git a/packages/mongo/mongo_livedata_tests.js b/packages/mongo/mongo_livedata_tests.js index 4ad47bb2fa..2d490e90de 100644 --- a/packages/mongo/mongo_livedata_tests.js +++ b/packages/mongo/mongo_livedata_tests.js @@ -357,6 +357,8 @@ Tinytest.addAsync("mongo-livedata - basics, " + idGeneration, function (test, on expectObserve('', function () { var count = coll.update({run: run, x: -1}, {$inc: {x: 2}}, {multi: true}); + console.log(`count`, count); + test.equal(count, 0); }); @@ -563,7 +565,7 @@ if (Meteor.isServer) { function error() { throw new Meteor.Error('unsafe object mutation'); } - + const denyModifications = { get(target, key) { const type = Object.prototype.toString.call(target[key]); @@ -577,7 +579,7 @@ if (Meteor.isServer) { deleteProperty: error, defineProperty: error, }; - + // Object.freeze only throws in silent mode // So we make our own version that always throws. function freeze(obj) { @@ -589,7 +591,7 @@ if (Meteor.isServer) { // Make sure that if anything touches the original object, this will throw return origApplyCallback.call(this, callback, freeze(args)); } - + const run = test.runId(); const coll = new Mongo.Collection(`livedata_test_scribble_collection_${run}`, collectionOptions); const expectMutatable = (o) => { @@ -623,7 +625,7 @@ if (Meteor.isServer) { coll.insert({run, a: [ {c: 1} ]}); coll.update({run}, { $set: { 'a.0.c': 2 } }); }); - + handle.stop(); handle2.stop(); @@ -720,6 +722,8 @@ if (Meteor.isServer) { var output = []; var callbacks = { changed: function (newDoc) { + console.log(`newDoc`, newDoc); + output.push({changed: newDoc._id}); } }; @@ -764,11 +768,16 @@ if (Meteor.isServer) { test.isTrue(observeMultiplexer); test.isTrue(observeMultiplexer === o2.handle._multiplexer); + console.log('runInFence filipe') + console.log(`o1.output`, o1.output); + // Update. Both observes fire. runInFence(function () { coll.update(docId1, {$set: {x: 'y'}}); }); - test.length(o1.output, 1); + console.log('runInFence filipe 2') + console.log(`o1.output `, o1.output); + test.length(o1.output, 1, 'filipe'); test.length(o2.output, 1); test.equal(o1.output.shift(), {changed: docId1}); test.equal(o2.output.shift(), {changed: docId1}); @@ -2231,7 +2240,7 @@ _.each(Meteor.isServer ? [true, false] : [true], function (minimongo) { }); // end idGeneration parametrization Tinytest.add('mongo-livedata - rewrite selector', function (test) { - + test.equal(Mongo.Collection._rewriteSelector('foo'), {_id: 'foo'}); diff --git a/packages/mongo/mongo_utils.js b/packages/mongo/mongo_utils.js new file mode 100644 index 0000000000..e97e722fd3 --- /dev/null +++ b/packages/mongo/mongo_utils.js @@ -0,0 +1,11 @@ +export const normalizeProjection = options => { + // transform fields key in projection + const { fields, projection, ...otherOptions } = options || {}; + // TODO: enable this comment when deprecating the fields option + // Log.debug(`fields option has been deprecated, please use the new 'projection' instead`) + + return { + ...otherOptions, + ...(projection || fields ? { projection: fields || projection } : {}), + }; +}; diff --git a/packages/mongo/observe_changes_tests.js b/packages/mongo/observe_changes_tests.js index 0d920deac7..56ab79db7c 100644 --- a/packages/mongo/observe_changes_tests.js +++ b/packages/mongo/observe_changes_tests.js @@ -48,17 +48,16 @@ _.each ([{added: 'added', forceOrdered: true}, handle.stop(); - var badCursor = c.find({}, {fields: {noodles: 1, _id: false}}); test.throws(function () { - badCursor.observeChanges(logger); - }); + c.find({}, {fields: {noodles: 1, _id: false}}) + }, undefined, 'bad cursor excluding _id from projection'); onComplete(); }); }); }); -Tinytest.addAsync("observeChanges - callback isolation", function (test, onComplete) { +Tinytest.onlyAsync("observeChanges - callback isolation", function (test, onComplete) { var c = makeCollection(); withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, function (logger) { var handles = []; @@ -420,5 +419,5 @@ if (Meteor.isServer) { }); c.insert({ type: { name: 'foobar' } }); } - ); + ); } diff --git a/packages/tinytest/tinytest.js b/packages/tinytest/tinytest.js index fadb83d3eb..045548c6de 100644 --- a/packages/tinytest/tinytest.js +++ b/packages/tinytest/tinytest.js @@ -203,7 +203,7 @@ export class TestCaseResults { // The upshot is, if you want to test whether an error is of a // particular class, use a predicate function. // - throws(f, expected) { + throws(f, expected, message) { var actual, predicate; if (expected === undefined) { @@ -236,9 +236,9 @@ export class TestCaseResults { else this.fail({ type: "throws", - message: actual ? + message: (actual ? "wrong error thrown: " + actual.message : - "did not throw an error as expected" + "did not throw an error as expected") + (message ? ": " + message : ""), }); } From 8c3a6cdd6c6f1f10ee5176c5a657559cc7d55a32 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Tue, 4 Jan 2022 16:24:56 -0300 Subject: [PATCH 127/393] Fix test searching for mongo v4 - replace with v5 --- tools/tests/mongo.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/tests/mongo.js b/tools/tests/mongo.js index 14bd25eee9..a82e98b9f1 100644 --- a/tools/tests/mongo.js +++ b/tools/tests/mongo.js @@ -40,7 +40,7 @@ function testMeteorMongo(appDir) { // Make sure we match the DB version that's printed as part of the // non-quiet shell startup text, so that we don't confuse it with the // output of the db.version() command below. - mongoRun.match(/MongoDB server version: 4\.\d+\.\d+/); + mongoRun.match(/MongoDB server version: 5\.\d+\.\d+/); // Make sure the shell does not display the banner about Mongo's free // monitoring service. @@ -48,7 +48,7 @@ function testMeteorMongo(appDir) { // Note: when mongo shell's input is not a tty, there is no prompt. mongoRun.write('db.version()\n'); - mongoRun.match(/4\.\d+\.\d+/); + mongoRun.match(/5\.\d+\.\d+/); mongoRun.stop(); run.stop(); From ff7262a49daa4b143c4c6592ffbf04d35a2fa9e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Tue, 4 Jan 2022 15:42:30 -0400 Subject: [PATCH 128/393] Cleaning up tests --- packages/mongo/mongo_driver.js | 2 -- packages/mongo/mongo_livedata_tests.js | 9 +-------- packages/mongo/observe_changes_tests.js | 2 +- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index c9f496f1e6..3b4ecec08e 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -644,8 +644,6 @@ MongoConnection.prototype._update = function (collection_name, selector, mod, }; var transformResult = function (driverResult) { - console.log(`driverResult`, driverResult); - var meteorResult = { numberAffected: 0 }; if (driverResult) { var mongoResult = driverResult.result; diff --git a/packages/mongo/mongo_livedata_tests.js b/packages/mongo/mongo_livedata_tests.js index 2d490e90de..ec241d2ee1 100644 --- a/packages/mongo/mongo_livedata_tests.js +++ b/packages/mongo/mongo_livedata_tests.js @@ -357,8 +357,6 @@ Tinytest.addAsync("mongo-livedata - basics, " + idGeneration, function (test, on expectObserve('', function () { var count = coll.update({run: run, x: -1}, {$inc: {x: 2}}, {multi: true}); - console.log(`count`, count); - test.equal(count, 0); }); @@ -768,16 +766,11 @@ if (Meteor.isServer) { test.isTrue(observeMultiplexer); test.isTrue(observeMultiplexer === o2.handle._multiplexer); - console.log('runInFence filipe') - console.log(`o1.output`, o1.output); - // Update. Both observes fire. runInFence(function () { coll.update(docId1, {$set: {x: 'y'}}); }); - console.log('runInFence filipe 2') - console.log(`o1.output `, o1.output); - test.length(o1.output, 1, 'filipe'); + test.length(o1.output, 1, 'test that is breaking'); test.length(o2.output, 1); test.equal(o1.output.shift(), {changed: docId1}); test.equal(o2.output.shift(), {changed: docId1}); diff --git a/packages/mongo/observe_changes_tests.js b/packages/mongo/observe_changes_tests.js index 56ab79db7c..5dab99445a 100644 --- a/packages/mongo/observe_changes_tests.js +++ b/packages/mongo/observe_changes_tests.js @@ -57,7 +57,7 @@ _.each ([{added: 'added', forceOrdered: true}, }); }); -Tinytest.onlyAsync("observeChanges - callback isolation", function (test, onComplete) { +Tinytest.addAsync("observeChanges - callback isolation", function (test, onComplete) { var c = makeCollection(); withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, function (logger) { var handles = []; From 95dc4f29ca11420e55b41458cc77bde74728a8c4 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Tue, 4 Jan 2022 17:02:32 -0300 Subject: [PATCH 129/393] New dev-bundle for Mongo 5 --- meteor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meteor b/meteor index 44a3e4a67c..7edc696474 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/usr/bin/env bash -BUNDLE_VERSION=14.18.3.1 +BUNDLE_VERSION=14.18.3.2 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. From 86024fc7d9cbf3b2917fd89fa7520c58d0cbca00 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Wed, 5 Jan 2022 12:26:48 -0300 Subject: [PATCH 130/393] Fix oplog parsing for mongodb 5 and result parsing for replace operations. Fix tests based on mongo version and dependent on mongo driver internals. --- packages/mongo/collection_tests.js | 8 +++++--- packages/mongo/mongo_driver.js | 20 +++++++++++--------- packages/mongo/mongo_livedata_tests.js | 16 +++++++++------- packages/mongo/oplog_observe_driver.js | 3 ++- 4 files changed, 27 insertions(+), 20 deletions(-) diff --git a/packages/mongo/collection_tests.js b/packages/mongo/collection_tests.js index ea17630507..e6f3228392 100644 --- a/packages/mongo/collection_tests.js +++ b/packages/mongo/collection_tests.js @@ -170,13 +170,15 @@ Tinytest.add('collection - calling find with a valid readPreference', defaultCursor.count(); customCursor.count(); + // defaultCursor._synchronousCursor._dbCursor.operation is not an option anymore + // defaultCursor._synchronousCursor._dbCursor.option is test.equal( - defaultCursor._synchronousCursor._dbCursor.operation.readPreference + defaultCursor._synchronousCursor._dbCursor.options.readPreference .mode, defaultReadPreference ); test.equal( - customCursor._synchronousCursor._dbCursor.operation.readPreference.mode, + customCursor._synchronousCursor._dbCursor.options.readPreference.mode, customReadPreference ); } @@ -199,4 +201,4 @@ Tinytest.add('collection - calling find with an invalid readPreference', }, `Invalid read preference mode ${invalidReadPreference}`); } } -); \ No newline at end of file +); diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 3b4ecec08e..e9d86c8be0 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -611,7 +611,7 @@ MongoConnection.prototype._update = function (collection_name, selector, mod, const strings = Object.keys(mongoMod).filter((key) => !key.startsWith("$")); let updateMethod = strings.length > 0 ? 'replaceOne' : 'updateMany'; updateMethod = updateMethod === 'updateMany' && !mongoOpts.multi ? 'updateOne' : updateMethod; - collection[updateMethod]( + collection[updateMethod].bind(collection)( mongoSelector, mongoMod, mongoOpts, bindEnvironmentForWrite(function (err, result) { if (! err) { @@ -651,9 +651,9 @@ var transformResult = function (driverResult) { // upserted values -- even with options.multi, when the upsert does insert, // it only inserts one element. if (mongoResult.upsertedCount) { - meteorResult.numberAffected += mongoResult.upsertedCount.length; + meteorResult.numberAffected = mongoResult.upsertedCount; - if (mongoResult.upsertedCount.length === 1) { + if (mongoResult.upsertedId) { meteorResult.insertedId = mongoResult.upsertedId; } } else { @@ -727,15 +727,17 @@ var simulateUpsertWithInsertedId = function (collection, selector, mod, } else { let method = collection.updateMany; if(!Object.keys(mod).some(key => key.startsWith("$"))){ - method = collection.replaceOne; + method = collection.replaceOne.bind(collection); + mod = replacementWithId; } method(selector, mod, {upsert:true,...mongoOptsForUpdate}, bindEnvironmentForWrite(function (err, result) { if (err) { callback(err); - } else if (result && result.result.n != 0) { + } else if (result && (result.modifiedCount || result.upsertedCount)) { callback(null, { - numberAffected: result.result.n + numberAffected: result.modifiedCount || result.upsertedCount, + insertedId: result.upsertedId }); } else { doConditionalInsert(); @@ -745,7 +747,7 @@ var simulateUpsertWithInsertedId = function (collection, selector, mod, }; var doConditionalInsert = function () { - collection.updateMany(selector, replacementWithId, mongoOptsForInsert, + collection.replaceOne(selector, replacementWithId, mongoOptsForInsert, bindEnvironmentForWrite(function (err, result) { if (err) { // figure out if this is a @@ -758,8 +760,8 @@ var simulateUpsertWithInsertedId = function (collection, selector, mod, } } else { callback(null, { - numberAffected: result.result.upserted.length, - insertedId: insertedId, + numberAffected: result.upsertedCount, + insertedId: result.upsertedId, }); } })); diff --git a/packages/mongo/mongo_livedata_tests.js b/packages/mongo/mongo_livedata_tests.js index ec241d2ee1..55c4f3ad6e 100644 --- a/packages/mongo/mongo_livedata_tests.js +++ b/packages/mongo/mongo_livedata_tests.js @@ -720,8 +720,6 @@ if (Meteor.isServer) { var output = []; var callbacks = { changed: function (newDoc) { - console.log(`newDoc`, newDoc); - output.push({changed: newDoc._id}); } }; @@ -1773,7 +1771,10 @@ _.each(Meteor.isServer ? [true, false] : [true], function (minimongo) { coll = coll._collection; var result1 = upsert(coll, useUpdate, {foo: 'bar'}, {foo: 'bar'}); + console.log(result1) test.equal(result1.numberAffected, 1); + console.log("result1"); + console.log(result1); if (! skipIds) test.isTrue(result1.insertedId); compareResults(test, skipIds, coll.find().fetch(), [{foo: 'bar', _id: result1.insertedId}]); @@ -1860,6 +1861,8 @@ _.each(Meteor.isServer ? [true, false] : [true], function (minimongo) { {name: 'Steve'}, {insertedId: 'steve'}); test.equal(result9.numberAffected, 1); + console.log("result9") + console.log(result9) if (! skipIds) test.equal(result9.insertedId, 'steve'); compareResults(test, skipIds, coll.find().fetch(), @@ -3306,12 +3309,11 @@ Meteor.isServer && Tinytest.add( } ); -Meteor.isServer && Tinytest.add("mongo-livedata - npm modules", function (test) { +Meteor.isServer && Tinytest.only("mongo-livedata - npm modules", function (test) { // Make sure the version number looks like a version number. - test.matches(MongoInternals.NpmModules.mongodb.version, /^3\.(\d+)\.(\d+)/); - test.equal(typeof(MongoInternals.NpmModules.mongodb.module), 'function'); - test.equal(typeof(MongoInternals.NpmModules.mongodb.module.connect), - 'function'); + test.matches(MongoInternals.NpmModules.mongodb.version, /^4\.(\d+)\.(\d+)/); + console.log(MongoInternals.NpmModules.mongodb) + test.equal(typeof(MongoInternals.NpmModules.mongodb.module), 'object'); test.equal(typeof(MongoInternals.NpmModules.mongodb.module.ObjectID), 'function'); diff --git a/packages/mongo/oplog_observe_driver.js b/packages/mongo/oplog_observe_driver.js index 8c372faf83..55fe120516 100644 --- a/packages/mongo/oplog_observe_driver.js +++ b/packages/mongo/oplog_observe_driver.js @@ -602,7 +602,8 @@ _.extend(OplogObserveDriver.prototype, { // database to figure out if the whole document matches the selector) or // a replacement (in which case we can just directly re-evaluate the // selector)? - var isReplace = !_.has(op.o, '$set') && !_.has(op.o, '$unset'); + // oplog format has changed on mongodb 5, we have to support both now + var isReplace = !_.has(op.o, '$set') && !_.has(op.o, 'diff') && !_.has(op.o, '$unset'); // If this modifier modifies something inside an EJSON custom type (ie, // anything with EJSON$), then we can't try to use // LocalCollection._modify, since that just mutates the EJSON encoding, From ebf91b0dfed27f3d30dbcabab0fffd53e567c4cd Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Wed, 5 Jan 2022 15:03:51 -0300 Subject: [PATCH 131/393] Use async functions for running insert/delete Fix "projection" field usage on observers --- packages/minimongo/common.js | 2 +- packages/minimongo/cursor.js | 2 +- packages/mongo/collection_tests.js | 13 +-- packages/mongo/mongo_driver.js | 120 +++++++++++++++---------- packages/mongo/mongo_livedata_tests.js | 4 +- packages/mongo/oplog_observe_driver.js | 4 +- 6 files changed, 89 insertions(+), 56 deletions(-) diff --git a/packages/minimongo/common.js b/packages/minimongo/common.js index 213396301f..82d966fb2b 100644 --- a/packages/minimongo/common.js +++ b/packages/minimongo/common.js @@ -857,7 +857,7 @@ export function isOperatorObject(valueSelector, inconsistentOK) { let theseAreOperators = undefined; Object.keys(valueSelector).forEach(selKey => { - const thisIsOperator = selKey.substr(0, 1) === '$'; + const thisIsOperator = selKey.substr(0, 1) === '$' || selKey === 'diff'; if (theseAreOperators === undefined) { theseAreOperators = thisIsOperator; diff --git a/packages/minimongo/cursor.js b/packages/minimongo/cursor.js index 8d5a938be2..a555fb74c0 100644 --- a/packages/minimongo/cursor.js +++ b/packages/minimongo/cursor.js @@ -25,7 +25,7 @@ export default class Cursor { this.skip = options.skip || 0; this.limit = options.limit; - this.fields = options.fields; + this.fields = options.projection || options.fields; this._projectionFn = LocalCollection._compileProjection(this.fields || {}); diff --git a/packages/mongo/collection_tests.js b/packages/mongo/collection_tests.js index e6f3228392..da34c62792 100644 --- a/packages/mongo/collection_tests.js +++ b/packages/mongo/collection_tests.js @@ -94,7 +94,7 @@ Tinytest.add('collection - call native find with sort function', return doc.a; }); }, - /Illegal sort clause/ + /Invalid sort format: undefined Sort must be a valid object/ ); } } @@ -159,7 +159,7 @@ Tinytest.add('collection - calling find with a valid readPreference', if (Meteor.isServer) { const defaultReadPreference = 'primary'; const customReadPreference = 'secondaryPreferred'; - const collection = new Mongo.Collection('readPreferenceTest1'); + const collection = new Mongo.Collection('readPreferenceTest'); const defaultCursor = collection.find(); const customCursor = collection.find( {}, @@ -171,14 +171,15 @@ Tinytest.add('collection - calling find with a valid readPreference', customCursor.count(); // defaultCursor._synchronousCursor._dbCursor.operation is not an option anymore - // defaultCursor._synchronousCursor._dbCursor.option is + // as the cursor options are now private + // You can check on abstract_cursor.ts the exposed public getters test.equal( - defaultCursor._synchronousCursor._dbCursor.options.readPreference + defaultCursor._synchronousCursor._dbCursor.readPreference .mode, defaultReadPreference ); test.equal( - customCursor._synchronousCursor._dbCursor.options.readPreference.mode, + customCursor._synchronousCursor._dbCursor.readPreference.mode, customReadPreference ); } @@ -198,7 +199,7 @@ Tinytest.add('collection - calling find with an invalid readPreference', test.throws(function() { // Trigger the creation of _synchronousCursor cursor.count(); - }, `Invalid read preference mode ${invalidReadPreference}`); + }, `Invalid read preference mode "${invalidReadPreference}"`); } } ); diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index e9d86c8be0..849c3396f3 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -376,8 +376,16 @@ MongoConnection.prototype._insert = function (collection_name, document, callback = bindEnvironmentForWrite(writeCallback(write, refresh, callback)); try { var collection = self.rawCollection(collection_name); - collection.insertOne(replaceTypes(document, replaceMeteorAtomWithMongo), - {safe: true}, callback); + collection.insertOne( + replaceTypes(document, replaceMeteorAtomWithMongo), + { + safe: true, + } + ).then(({insertedId}) => { + callback(null, transformResult({ result : {modifiedCount : insertedId ? 1 : 0} }).numberAffected); + }).catch((e) => { + callback(e) + }); } catch (err) { write.committed(); throw err; @@ -424,15 +432,15 @@ MongoConnection.prototype._remove = function (collection_name, selector, try { var collection = self.rawCollection(collection_name); - let result = {}; - try { - const {deletedCount} = collection.deleteMany(replaceTypes(selector, replaceMeteorAtomWithMongo), - {safe: true}).await(); - result.modifiedCount = deletedCount; - }catch(err){ - callback(err) - } - callback(null, transformResult({result}).numberAffected) + collection + .deleteMany(replaceTypes(selector, replaceMeteorAtomWithMongo), { + safe: true, + }) + .then(({ deletedCount }) => { + callback(null, transformResult({ result : {modifiedCount : deletedCount} }).numberAffected); + }).catch((err) => { + callback(err); + }); } catch (err) { write.committed(); throw err; @@ -610,7 +618,11 @@ MongoConnection.prototype._update = function (collection_name, selector, mod, const strings = Object.keys(mongoMod).filter((key) => !key.startsWith("$")); let updateMethod = strings.length > 0 ? 'replaceOne' : 'updateMany'; - updateMethod = updateMethod === 'updateMany' && !mongoOpts.multi ? 'updateOne' : updateMethod; + updateMethod = + updateMethod === 'updateMany' && !mongoOpts.multi + ? 'updateOne' + : updateMethod; + console.log(`Choose method ${updateMethod} for ${JSON.stringify(mongoMod)}`) collection[updateMethod].bind(collection)( mongoSelector, mongoMod, mongoOpts, bindEnvironmentForWrite(function (err, result) { @@ -727,44 +739,60 @@ var simulateUpsertWithInsertedId = function (collection, selector, mod, } else { let method = collection.updateMany; if(!Object.keys(mod).some(key => key.startsWith("$"))){ + console.log(`Choose method replace for ${JSON.stringify(mod)} on selector ${JSON.stringify(selector)}`) + method = collection.replaceOne.bind(collection); mod = replacementWithId; + console.trace("Aqui") } - method(selector, mod, {upsert:true,...mongoOptsForUpdate}, - bindEnvironmentForWrite(function (err, result) { - if (err) { - callback(err); - } else if (result && (result.modifiedCount || result.upsertedCount)) { - callback(null, { - numberAffected: result.modifiedCount || result.upsertedCount, - insertedId: result.upsertedId - }); - } else { - doConditionalInsert(); - } - })); + console.log("mongoOptsForUpdate"); + console.log(mongoOptsForUpdate); + console.log("method"); + console.log(method); + method( + selector, + mod, + { upsert: true, ...mongoOptsForUpdate }, + bindEnvironmentForWrite(function(err, result) { + console.log(err) + if (err) { + callback(err); + } else if (result && (result.modifiedCount || result.upsertedCount)) { + callback(null, { + numberAffected: result.modifiedCount || result.upsertedCount, + insertedId: result.upsertedId, + }); + } else { + doConditionalInsert(); + } + }) + ); } }; - var doConditionalInsert = function () { - collection.replaceOne(selector, replacementWithId, mongoOptsForInsert, - bindEnvironmentForWrite(function (err, result) { - if (err) { - // figure out if this is a - // "cannot change _id of document" error, and - // if so, try doUpdate() again, up to 3 times. - if (MongoConnection._isCannotChangeIdError(err)) { - doUpdate(); - } else { - callback(err); - } - } else { - callback(null, { - numberAffected: result.upsertedCount, - insertedId: result.upsertedId, - }); - } - })); + var doConditionalInsert = function() { + collection.replaceOne( + selector, + replacementWithId, + mongoOptsForInsert, + bindEnvironmentForWrite(function(err, result) { + if (err) { + // figure out if this is a + // "cannot change _id of document" error, and + // if so, try doUpdate() again, up to 3 times. + if (MongoConnection._isCannotChangeIdError(err)) { + doUpdate(); + } else { + callback(err); + } + } else { + callback(null, { + numberAffected: result.upsertedCount, + insertedId: result.upsertedId, + }); + } + }) + ); }; doUpdate(); @@ -977,8 +1005,8 @@ MongoConnection.prototype._createSynchronousCursor = function( sort: cursorOptions.sort, limit: cursorOptions.limit, skip: cursorOptions.skip, - projection: cursorOptions.fields, - readPreference: cursorOptions.readPreference + projection: cursorOptions.fields || cursorOptions.projection, + readPreference: cursorOptions.readPreference, }; // Do we want a tailable cursor (which only works on capped collections)? diff --git a/packages/mongo/mongo_livedata_tests.js b/packages/mongo/mongo_livedata_tests.js index 55c4f3ad6e..20aacf8f18 100644 --- a/packages/mongo/mongo_livedata_tests.js +++ b/packages/mongo/mongo_livedata_tests.js @@ -1779,7 +1779,9 @@ _.each(Meteor.isServer ? [true, false] : [true], function (minimongo) { test.isTrue(result1.insertedId); compareResults(test, skipIds, coll.find().fetch(), [{foo: 'bar', _id: result1.insertedId}]); + // !!!! var result2 = upsert(coll, useUpdate, {foo: 'bar'}, {foo: 'baz'}); + // !!!! test.equal(result2.numberAffected, 1); if (! skipIds) test.isFalse(result2.insertedId); @@ -3309,7 +3311,7 @@ Meteor.isServer && Tinytest.add( } ); -Meteor.isServer && Tinytest.only("mongo-livedata - npm modules", function (test) { +Meteor.isServer && Tinytest.add("mongo-livedata - npm modules", function (test) { // Make sure the version number looks like a version number. test.matches(MongoInternals.NpmModules.mongodb.version, /^4\.(\d+)\.(\d+)/); console.log(MongoInternals.NpmModules.mongodb) diff --git a/packages/mongo/oplog_observe_driver.js b/packages/mongo/oplog_observe_driver.js index 55fe120516..ec669c834c 100644 --- a/packages/mongo/oplog_observe_driver.js +++ b/packages/mongo/oplog_observe_driver.js @@ -86,7 +86,9 @@ OplogObserveDriver = function (options) { self._registerPhaseChange(PHASE.QUERYING); self._matcher = options.matcher; - var projection = self._cursorDescription.options.fields || {}; + // we are now using projection, not fields in the cursor description even if you pass {fields} + // in the cursor construction + var projection = self._cursorDescription.options.fields || self._cursorDescription.options.projection || {}; self._projectionFn = LocalCollection._compileProjection(projection); // Projection function, result of combining important fields for selector and // existing fields projection From 3e777770824a90f43f7eb6a641d56c6b72f73c2f Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Wed, 5 Jan 2022 18:34:42 -0300 Subject: [PATCH 132/393] Remove logs Fix undefined returned from the node-driver while we were expecting null in the err field --- packages/mongo/mongo_driver.js | 37 ++++++++++---------------- packages/mongo/mongo_livedata_tests.js | 5 ---- 2 files changed, 14 insertions(+), 28 deletions(-) diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 849c3396f3..478d7fd55b 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -376,16 +376,16 @@ MongoConnection.prototype._insert = function (collection_name, document, callback = bindEnvironmentForWrite(writeCallback(write, refresh, callback)); try { var collection = self.rawCollection(collection_name); - collection.insertOne( - replaceTypes(document, replaceMeteorAtomWithMongo), - { - safe: true, - } - ).then(({insertedId}) => { - callback(null, transformResult({ result : {modifiedCount : insertedId ? 1 : 0} }).numberAffected); - }).catch((e) => { - callback(e) - }); + collection.insertOne( + replaceTypes(document, replaceMeteorAtomWithMongo), + { + safe: true, + } + ).then(({insertedId}) => { + callback(null, transformResult({ result : {modifiedCount : insertedId ? 1 : 0} }).numberAffected); + }).catch((e) => { + callback(e, null) + }); } catch (err) { write.committed(); throw err; @@ -622,10 +622,10 @@ MongoConnection.prototype._update = function (collection_name, selector, mod, updateMethod === 'updateMany' && !mongoOpts.multi ? 'updateOne' : updateMethod; - console.log(`Choose method ${updateMethod} for ${JSON.stringify(mongoMod)}`) collection[updateMethod].bind(collection)( mongoSelector, mongoMod, mongoOpts, - bindEnvironmentForWrite(function (err, result) { + // mongo driver now returns undefined for err in the callback + bindEnvironmentForWrite(function (err = null, result) { if (! err) { var meteorResult = transformResult({result}); if (meteorResult && options._returnObject) { @@ -671,7 +671,7 @@ var transformResult = function (driverResult) { } else { // n was used before Mongo 5.0, in Mongo 5.0 we are not receiving this n // field and so we are using modifiedCount instead - meteorResult.numberAffected = mongoResult.n || mongoResult.modifiedCount; + meteorResult.numberAffected = mongoResult.n || mongoResult.matchedCount || mongoResult.modifiedCount; } } @@ -739,28 +739,19 @@ var simulateUpsertWithInsertedId = function (collection, selector, mod, } else { let method = collection.updateMany; if(!Object.keys(mod).some(key => key.startsWith("$"))){ - console.log(`Choose method replace for ${JSON.stringify(mod)} on selector ${JSON.stringify(selector)}`) - method = collection.replaceOne.bind(collection); - mod = replacementWithId; - console.trace("Aqui") } - console.log("mongoOptsForUpdate"); - console.log(mongoOptsForUpdate); - console.log("method"); - console.log(method); method( selector, mod, { upsert: true, ...mongoOptsForUpdate }, bindEnvironmentForWrite(function(err, result) { - console.log(err) if (err) { callback(err); } else if (result && (result.modifiedCount || result.upsertedCount)) { callback(null, { numberAffected: result.modifiedCount || result.upsertedCount, - insertedId: result.upsertedId, + insertedId: result.upsertedId || undefined, }); } else { doConditionalInsert(); diff --git a/packages/mongo/mongo_livedata_tests.js b/packages/mongo/mongo_livedata_tests.js index 20aacf8f18..60a87a4d3c 100644 --- a/packages/mongo/mongo_livedata_tests.js +++ b/packages/mongo/mongo_livedata_tests.js @@ -1771,10 +1771,7 @@ _.each(Meteor.isServer ? [true, false] : [true], function (minimongo) { coll = coll._collection; var result1 = upsert(coll, useUpdate, {foo: 'bar'}, {foo: 'bar'}); - console.log(result1) test.equal(result1.numberAffected, 1); - console.log("result1"); - console.log(result1); if (! skipIds) test.isTrue(result1.insertedId); compareResults(test, skipIds, coll.find().fetch(), [{foo: 'bar', _id: result1.insertedId}]); @@ -1863,8 +1860,6 @@ _.each(Meteor.isServer ? [true, false] : [true], function (minimongo) { {name: 'Steve'}, {insertedId: 'steve'}); test.equal(result9.numberAffected, 1); - console.log("result9") - console.log(result9) if (! skipIds) test.equal(result9.insertedId, 'steve'); compareResults(test, skipIds, coll.find().fetch(), From 16b74d74fc1a36a7db12c59ef7359431bc61f321 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Thu, 6 Jan 2022 09:28:02 -0300 Subject: [PATCH 133/393] Fix wrong upsert param passed to mongo method --- packages/mongo/mongo_driver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 478d7fd55b..5c9f9145bb 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -744,7 +744,7 @@ var simulateUpsertWithInsertedId = function (collection, selector, mod, method( selector, mod, - { upsert: true, ...mongoOptsForUpdate }, + mongoOptsForUpdate, bindEnvironmentForWrite(function(err, result) { if (err) { callback(err); From 9c9232f84ad3da66d3d4210f01434f24c8394db8 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Thu, 6 Jan 2022 18:48:27 -0300 Subject: [PATCH 134/393] Create oplog v2 converter for watching changes in the newest mongodb 5 format --- packages/mongo/mongo_driver.js | 7 +- packages/mongo/mongo_livedata_tests.js | 1 - packages/mongo/oplog_observe_driver.js | 5 + packages/mongo/oplog_v2_converter.js | 113 +++++++++++++++++++++ packages/mongo/oplog_v2_converter_tests.js | 77 ++++++++++++++ packages/mongo/package.js | 3 +- 6 files changed, 201 insertions(+), 5 deletions(-) create mode 100644 packages/mongo/oplog_v2_converter.js create mode 100644 packages/mongo/oplog_v2_converter_tests.js diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 5c9f9145bb..0f2f5932ef 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -1293,9 +1293,10 @@ MongoConnection.prototype._observeChanges = function ( // You may not filter out _id when observing changes, because the id is a core // part of the observeChanges API. - if (cursorDescription.options.fields && - (cursorDescription.options.fields._id === 0 || - cursorDescription.options.fields._id === false)) { + const fieldsOptions = cursorDescription.options.projection || cursorDescription.options.fields; + if (fieldsOptions && + (fieldsOptions._id === 0 || + fieldsOptions._id === false)) { throw Error("You may not observe a cursor with {fields: {_id: 0}}"); } diff --git a/packages/mongo/mongo_livedata_tests.js b/packages/mongo/mongo_livedata_tests.js index 60a87a4d3c..02b62d3f05 100644 --- a/packages/mongo/mongo_livedata_tests.js +++ b/packages/mongo/mongo_livedata_tests.js @@ -3309,7 +3309,6 @@ Meteor.isServer && Tinytest.add( Meteor.isServer && Tinytest.add("mongo-livedata - npm modules", function (test) { // Make sure the version number looks like a version number. test.matches(MongoInternals.NpmModules.mongodb.version, /^4\.(\d+)\.(\d+)/); - console.log(MongoInternals.NpmModules.mongodb) test.equal(typeof(MongoInternals.NpmModules.mongodb.module), 'object'); test.equal(typeof(MongoInternals.NpmModules.mongodb.module.ObjectID), 'function'); diff --git a/packages/mongo/oplog_observe_driver.js b/packages/mongo/oplog_observe_driver.js index ec669c834c..e068058af6 100644 --- a/packages/mongo/oplog_observe_driver.js +++ b/packages/mongo/oplog_observe_driver.js @@ -1,3 +1,5 @@ +import { oplogV2V1Converter } from "./oplog_v2_converter"; + var Future = Npm.require('fibers/future'); var PHASE = { @@ -600,6 +602,9 @@ _.extend(OplogObserveDriver.prototype, { if (self._matcher.documentMatches(op.o).result) self._addMatching(op.o); } else if (op.op === 'u') { + // we are mapping the new oplog format on mongo 5 + // to what we know better, $set + op.o = oplogV2V1Converter(op.o) // Is this a modifier ($set/$unset, which may require us to poll the // database to figure out if the whole document matches the selector) or // a replacement (in which case we can just directly re-evaluate the diff --git a/packages/mongo/oplog_v2_converter.js b/packages/mongo/oplog_v2_converter.js new file mode 100644 index 0000000000..d53ad1ff61 --- /dev/null +++ b/packages/mongo/oplog_v2_converter.js @@ -0,0 +1,113 @@ +// we are mapping the new oplog format on mongo 5 +// to what we know better, $set and $unset format +// new oplog format ex: +// { +// '$v': 2, +// diff: { u: { key1: 2022-01-06T18:23:16.131Z, key2: [ObjectID] } } +// } + +/* +the structure of an entry is: + + +-> entry: i, u, d + sFields. +-> sFields: i, u, d + sFields +-> sFields: arrayOperator -> { a: true, u0: 2 } +-> i,u,d: { key: value } +-> value: {key: value} + +i and u are both $set +d is $unset +on mongo 4 + */ + +const isArrayOperator = possibleArrayOperator => { + if (!Object.keys(possibleArrayOperator).length) return false; + + let isArrayOperator = true; + Object.keys(possibleArrayOperator).forEach(key => { + if (key !== 'a' && !key.match(/u\d+/)) { + isArrayOperator = false; + } + }); + return isArrayOperator; +}; +const nestedOplogEntryParsers = ( + { i = {}, u = {}, d = {}, ...sFields }, + prefixKey = '' +) => { + const sFieldsOperators = []; + Object.entries(sFields).forEach(([key, value]) => { + if (isArrayOperator(value || {})) { + const { a, ...uPosition } = value; + const [positionKey, newArrayIndexValue] = Object.entries(uPosition)[0]; + if (uPosition) + sFieldsOperators.push({ + [newArrayIndexValue === null ? '$unset' : '$set']: { + [`${prefixKey}${key.substring(1)}.${positionKey.substring(1)}`]: + newArrayIndexValue === null ? true : newArrayIndexValue, + }, + }); + } else { + sFieldsOperators.push( + nestedOplogEntryParsers(value, `${prefixKey}${key.substring(1)}.`) + ); + } + }); + const $unset = Object.keys(d).reduce((acc, key) => { + return { ...acc, [`${prefixKey}${key}`]: true }; + }, {}); + const setObjectSource = { ...i, ...u }; + const $set = Object.keys(setObjectSource).reduce((acc, key) => { + const prefixedKey = `${prefixKey}${key}`; + + return { + ...acc, + ...(typeof setObjectSource[key] === 'object' + ? flattenObject({ [prefixedKey]: setObjectSource[key] }) + : { + [prefixedKey]: setObjectSource[key], + }), + }; + }, {}); + + const c = [...sFieldsOperators, { $unset, $set }]; + const { $set: s, $unset: un } = c.reduce( + (acc, { $set: set = {}, $unset: unset = {} }) => { + return { + $set: { ...acc.$set, ...set }, + $unset: { ...acc.$unset, ...unset }, + }; + }, + {} + ); + return { + ...(Object.keys(s).length ? { $set: s } : {}), + ...(Object.keys(un).length ? { $unset: un } : {}), + }; +}; + +export const oplogV2V1Converter = v2OplogEntry => { + if (v2OplogEntry.$v !== 2) return v2OplogEntry; + return { $v: 2, ...nestedOplogEntryParsers(v2OplogEntry.diff || {}) }; +}; + +function flattenObject(ob) { + var toReturn = {}; + + for (const i in ob) { + if (!ob.hasOwnProperty(i)) continue; + + if (typeof ob[i] == 'object' && ob[i] !== null) { + var flatObject = flattenObject(ob[i]); + for (const x in flatObject) { + if (!flatObject.hasOwnProperty(x)) continue; + + toReturn[i + '.' + x] = flatObject[x]; + } + } else { + toReturn[i] = ob[i]; + } + } + return toReturn; +} diff --git a/packages/mongo/oplog_v2_converter_tests.js b/packages/mongo/oplog_v2_converter_tests.js new file mode 100644 index 0000000000..5620c786fb --- /dev/null +++ b/packages/mongo/oplog_v2_converter_tests.js @@ -0,0 +1,77 @@ +import { oplogV2V1Converter } from './oplog_v2_converter'; + +Tinytest.add('oplog - v2/v1 conversion', function(test) { + const entry1 = { + $v: 2, + diff: { scustom: { sEJSON$value: { u: { EJSONtail: 'd' } } } }, + }; + const entry2 = { + $v: 2, + diff: { u: { d: '2', oi: 'asdas' } }, + }; + //set inside an array + const entry3 = { $v: 2, diff: { sasd: { a: true, u0: 2 } } }; + //unset inside an array + const entry4 = { $v: 2, diff: { sasd: { a: true, u0: null } } }; + + //set a new nested field inside an object + const entry5 = { + $v: 2, + diff: { i: { a: { b: 2 } } }, + }; + + //set an existing nested field inside an object + const entry6 = { + $v: 2, + diff: { sa: { i: { b: 3, c: 1 } } }, + }; + + //unset an existing nested field inside an object + const entry7 = { + $v: 2, + diff: { sa: { d: { b: false } } }, + }; + const entry8 = { $v: 2, diff: { u: { c: 'bar' }, sb: { a: true, u0: 2 } } }; + + test.equal( + JSON.stringify(oplogV2V1Converter(entry1)), + JSON.stringify({ + $v: 2, + $set: { 'custom.EJSON$value.EJSONtail': 'd' }, + }) + ); + test.equal( + JSON.stringify(oplogV2V1Converter(entry2)), + JSON.stringify({ + $v: 2, + $set: { d: '2', oi: 'asdas' }, + }) + ); + test.equal( + JSON.stringify(oplogV2V1Converter(entry3)), + JSON.stringify({ $v: 2, $set: { 'asd.0': 2 } }) + ); + test.equal( + JSON.stringify(oplogV2V1Converter(entry4)), + JSON.stringify({ $v: 2, $unset: { 'asd.0': true } }) + ); + test.equal( + JSON.stringify(oplogV2V1Converter(entry5)), + JSON.stringify({ $v: 2, $set: { 'a.b': 2 } }) + ); + test.equal( + JSON.stringify(oplogV2V1Converter(entry6)), + JSON.stringify({ + $v: 2, + $set: { 'a.b': 3, 'a.c': 1 }, + }) + ); + test.equal( + JSON.stringify(oplogV2V1Converter(entry7)), + JSON.stringify({ $v: 2, $unset: { 'a.b': true } }) + ); + test.equal( + JSON.stringify(oplogV2V1Converter(entry8)), + JSON.stringify({ $v: 2, $set: { 'b.0': 2, c: 'bar' } }) + ); +}); diff --git a/packages/mongo/package.js b/packages/mongo/package.js index cb493ea58b..c0c030f892 100644 --- a/packages/mongo/package.js +++ b/packages/mongo/package.js @@ -76,7 +76,7 @@ Package.onUse(function (api) { api.addFiles(['mongo_driver.js', 'oplog_tailing.js', 'observe_multiplex.js', 'doc_fetcher.js', - 'polling_observe_driver.js','oplog_observe_driver.js'], + 'polling_observe_driver.js','oplog_observe_driver.js', 'oplog_v2_converter.js'], 'server'); api.addFiles('local_collection_driver.js', ['client', 'server']); api.addFiles('remote_collection_driver.js', 'server'); @@ -98,5 +98,6 @@ Package.onTest(function (api) { api.addFiles('collection_tests.js', ['client', 'server']); api.addFiles('observe_changes_tests.js', ['client', 'server']); api.addFiles('oplog_tests.js', 'server'); + api.addFiles('oplog_v2_converter_tests.js', 'server'); api.addFiles('doc_fetcher_tests.js', 'server'); }); From eb82e917a7c0ce1f37aec90783dd35f40026dd75 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Thu, 6 Jan 2022 20:40:52 -0300 Subject: [PATCH 135/393] Remove minimongo asserts based on skipLimit on count cursors() --- packages/minimongo/cursor.js | 1 - packages/minimongo/minimongo_tests_client.js | 45 +++++++------------- 2 files changed, 16 insertions(+), 30 deletions(-) diff --git a/packages/minimongo/cursor.js b/packages/minimongo/cursor.js index a555fb74c0..09491e3cb1 100644 --- a/packages/minimongo/cursor.js +++ b/packages/minimongo/cursor.js @@ -53,7 +53,6 @@ export default class Cursor { return this._getRawObjects({ ordered: true, - applySkipLimit: true }).length; } diff --git a/packages/minimongo/minimongo_tests_client.js b/packages/minimongo/minimongo_tests_client.js index 1e369c8d06..0d99c9d91c 100644 --- a/packages/minimongo/minimongo_tests_client.js +++ b/packages/minimongo/minimongo_tests_client.js @@ -137,46 +137,46 @@ Tinytest.add('minimongo - basics', test => { test.equal(c.find('abc').count(), 0); test.equal(c.find(undefined).count(), 0); test.equal(c.find().count(), 3); - test.equal(c.find(1, {skip: 1}).count(false), 1); + test.equal(c.find(1, {skip: 1}).count(false), 0); test.equal(c.find(1, {skip: 1}).count(), 0); - test.equal(c.find({_id: 1}, {skip: 1}).count(false), 1); + test.equal(c.find({_id: 1}, {skip: 1}).count(false), 0); test.equal(c.find({_id: 1}, {skip: 1}).count(), 0); test.equal(c.find({_id: undefined}).count(), 0); test.equal(c.find({_id: false}).count(), 0); test.equal(c.find({_id: null}).count(), 0); test.equal(c.find({_id: ''}).count(), 0); test.equal(c.find({_id: 0}).count(), 0); - test.equal(c.find({}, {skip: 1}).count(false), 3); + test.equal(c.find({}, {skip: 1}).count(false), 2); test.equal(c.find({}, {skip: 1}).count(), 2); test.equal(c.find({}, {skip: 2}).count(), 1); - test.equal(c.find({}, {limit: 2}).count(false), 3); + test.equal(c.find({}, {limit: 2}).count(false), 2); test.equal(c.find({}, {limit: 2}).count(), 2); test.equal(c.find({}, {limit: 1}).count(), 1); - test.equal(c.find({}, {skip: 1, limit: 1}).count(false), 3); + test.equal(c.find({}, {skip: 1, limit: 1}).count(false), 1); test.equal(c.find({}, {skip: 1, limit: 1}).count(), 1); - test.equal(c.find({tags: 'fruit'}, {skip: 1}).count(false), 2); + test.equal(c.find({tags: 'fruit'}, {skip: 1}).count(false), 1); test.equal(c.find({tags: 'fruit'}, {skip: 1}).count(), 1); - test.equal(c.find({tags: 'fruit'}, {limit: 1}).count(false), 2); + test.equal(c.find({tags: 'fruit'}, {limit: 1}).count(false), 1); test.equal(c.find({tags: 'fruit'}, {limit: 1}).count(), 1); - test.equal(c.find({tags: 'fruit'}, {skip: 1, limit: 1}).count(false), 2); + test.equal(c.find({tags: 'fruit'}, {skip: 1, limit: 1}).count(false), 1); test.equal(c.find({tags: 'fruit'}, {skip: 1, limit: 1}).count(), 1); - test.equal(c.find(1, {sort: ['_id', 'desc'], skip: 1}).count(false), 1); + test.equal(c.find(1, {sort: ['_id', 'desc'], skip: 1}).count(false), 0); test.equal(c.find(1, {sort: ['_id', 'desc'], skip: 1}).count(), 0); - test.equal(c.find({_id: 1}, {sort: ['_id', 'desc'], skip: 1}).count(false), 1); + test.equal(c.find({_id: 1}, {sort: ['_id', 'desc'], skip: 1}).count(false), 0); test.equal(c.find({_id: 1}, {sort: ['_id', 'desc'], skip: 1}).count(), 0); - test.equal(c.find({}, {sort: ['_id', 'desc'], skip: 1}).count(false), 3); + test.equal(c.find({}, {sort: ['_id', 'desc'], skip: 1}).count(false), 2); test.equal(c.find({}, {sort: ['_id', 'desc'], skip: 1}).count(), 2); test.equal(c.find({}, {sort: ['_id', 'desc'], skip: 2}).count(), 1); - test.equal(c.find({}, {sort: ['_id', 'desc'], limit: 2}).count(false), 3); + test.equal(c.find({}, {sort: ['_id', 'desc'], limit: 2}).count(false), 2); test.equal(c.find({}, {sort: ['_id', 'desc'], limit: 2}).count(), 2); test.equal(c.find({}, {sort: ['_id', 'desc'], limit: 1}).count(), 1); - test.equal(c.find({}, {sort: ['_id', 'desc'], skip: 1, limit: 1}).count(false), 3); + test.equal(c.find({}, {sort: ['_id', 'desc'], skip: 1, limit: 1}).count(false), 1); test.equal(c.find({}, {sort: ['_id', 'desc'], skip: 1, limit: 1}).count(), 1); - test.equal(c.find({tags: 'fruit'}, {sort: ['_id', 'desc'], skip: 1}).count(false), 2); + test.equal(c.find({tags: 'fruit'}, {sort: ['_id', 'desc'], skip: 1}).count(false), 1); test.equal(c.find({tags: 'fruit'}, {sort: ['_id', 'desc'], skip: 1}).count(), 1); - test.equal(c.find({tags: 'fruit'}, {sort: ['_id', 'desc'], limit: 1}).count(false), 2); + test.equal(c.find({tags: 'fruit'}, {sort: ['_id', 'desc'], limit: 1}).count(false), 1); test.equal(c.find({tags: 'fruit'}, {sort: ['_id', 'desc'], limit: 1}).count(), 1); - test.equal(c.find({tags: 'fruit'}, {sort: ['_id', 'desc'], skip: 1, limit: 1}).count(false), 2); + test.equal(c.find({tags: 'fruit'}, {sort: ['_id', 'desc'], skip: 1, limit: 1}).count(false), 1); test.equal(c.find({tags: 'fruit'}, {sort: ['_id', 'desc'], skip: 1, limit: 1}).count(), 1); // Regression test for #455. @@ -3498,32 +3498,26 @@ Tinytest.add('minimongo - count on cursor with limit', test => { const c = Tracker.autorun(c => { const cursor = coll.find({_id: {$exists: true}}, {sort: {_id: 1}, limit: 3}); count = cursor.count(); - unlimitedCount = cursor.count(false); }); test.equal(count, 3); - test.equal(unlimitedCount, 4); coll.remove('A'); // still 3 in the collection Tracker.flush(); test.equal(count, 3); - test.equal(unlimitedCount, 3); coll.remove('B'); // expect count now 2 Tracker.flush(); test.equal(count, 2); - test.equal(unlimitedCount, 2); coll.insert({_id: 'A'}); // now 3 again Tracker.flush(); test.equal(count, 3); - test.equal(unlimitedCount, 3); coll.insert({_id: 'B'}); // now 4 entries, but count should be 3 still Tracker.flush(); test.equal(count, 3); - test.equal(unlimitedCount, 4); // unlimitedCount should be 4 now c.stop(); }); @@ -3802,36 +3796,29 @@ Tinytest.add('minimongo - fine-grained reactivity of query with fields projectio Tinytest.add('minimongo - reactive skip/limit count while updating', test => { const X = new LocalCollection; let count = -1; - let unlimitedCount = -1; const c = Tracker.autorun(() => { count = X.find({}, {skip: 1, limit: 1}).count(); - unlimitedCount = X.find({}, {skip: 1, limit: 1}).count(false); }); test.equal(count, 0); - test.equal(unlimitedCount, 0); X.insert({}); Tracker.flush({_throwFirstError: true}); test.equal(count, 0); - test.equal(unlimitedCount, 1); X.insert({}); Tracker.flush({_throwFirstError: true}); test.equal(count, 1); - test.equal(unlimitedCount, 2); X.update({}, {$set: {foo: 1}}); Tracker.flush({_throwFirstError: true}); test.equal(count, 1); - test.equal(unlimitedCount, 2); // Make sure a second update also works X.update({}, {$set: {foo: 2}}); Tracker.flush({_throwFirstError: true}); test.equal(count, 1); - test.equal(unlimitedCount, 2); c.stop(); }); From d8fd0fb5cefa211aaf99d4d257515737bdb60ede Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Thu, 6 Jan 2022 20:55:35 -0300 Subject: [PATCH 136/393] Fix oplog converter case in which the value is an array but shouldnt be flattened --- packages/mongo/oplog_v2_converter.js | 2 +- packages/mongo/oplog_v2_converter_tests.js | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/mongo/oplog_v2_converter.js b/packages/mongo/oplog_v2_converter.js index d53ad1ff61..c0420b8bc1 100644 --- a/packages/mongo/oplog_v2_converter.js +++ b/packages/mongo/oplog_v2_converter.js @@ -63,7 +63,7 @@ const nestedOplogEntryParsers = ( return { ...acc, - ...(typeof setObjectSource[key] === 'object' + ...(!Array.isArray(setObjectSource[key]) && typeof setObjectSource[key] === 'object' ? flattenObject({ [prefixedKey]: setObjectSource[key] }) : { [prefixedKey]: setObjectSource[key], diff --git a/packages/mongo/oplog_v2_converter_tests.js b/packages/mongo/oplog_v2_converter_tests.js index 5620c786fb..51194d0298 100644 --- a/packages/mongo/oplog_v2_converter_tests.js +++ b/packages/mongo/oplog_v2_converter_tests.js @@ -1,6 +1,6 @@ import { oplogV2V1Converter } from './oplog_v2_converter'; -Tinytest.add('oplog - v2/v1 conversion', function(test) { +Tinytest.only('oplog - v2/v1 conversion', function(test) { const entry1 = { $v: 2, diff: { scustom: { sEJSON$value: { u: { EJSONtail: 'd' } } } }, @@ -33,6 +33,8 @@ Tinytest.add('oplog - v2/v1 conversion', function(test) { }; const entry8 = { $v: 2, diff: { u: { c: 'bar' }, sb: { a: true, u0: 2 } } }; + const entry9 = {"$v":2,"diff":{"sservices":{"sresume":{"u":{"loginTokens":[]}}}}}; + test.equal( JSON.stringify(oplogV2V1Converter(entry1)), JSON.stringify({ @@ -74,4 +76,8 @@ Tinytest.add('oplog - v2/v1 conversion', function(test) { JSON.stringify(oplogV2V1Converter(entry8)), JSON.stringify({ $v: 2, $set: { 'b.0': 2, c: 'bar' } }) ); + test.equal( + JSON.stringify(oplogV2V1Converter(entry9)), + JSON.stringify({ '$v': 2, '$set': { 'services.resume.loginTokens': [] } }) + ); }); From 316f5d4512580422770d87028dbd4224265153c5 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Thu, 6 Jan 2022 21:02:29 -0300 Subject: [PATCH 137/393] Fix oplog converter case in which there is no need to convert --- packages/mongo/oplog_v2_converter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mongo/oplog_v2_converter.js b/packages/mongo/oplog_v2_converter.js index c0420b8bc1..ec1a08245e 100644 --- a/packages/mongo/oplog_v2_converter.js +++ b/packages/mongo/oplog_v2_converter.js @@ -88,7 +88,7 @@ const nestedOplogEntryParsers = ( }; export const oplogV2V1Converter = v2OplogEntry => { - if (v2OplogEntry.$v !== 2) return v2OplogEntry; + if (v2OplogEntry.$v !== 2 || !v2OplogEntry.diff) return v2OplogEntry; return { $v: 2, ...nestedOplogEntryParsers(v2OplogEntry.diff || {}) }; }; From 34d25f181dc0e4e9a9efa94c3f45bdbbbf5cc05a Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Thu, 6 Jan 2022 21:03:56 -0300 Subject: [PATCH 138/393] Fix oplog converter case in which there is no need to convert - new test --- packages/mongo/oplog_v2_converter_tests.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/mongo/oplog_v2_converter_tests.js b/packages/mongo/oplog_v2_converter_tests.js index 51194d0298..b5f9505811 100644 --- a/packages/mongo/oplog_v2_converter_tests.js +++ b/packages/mongo/oplog_v2_converter_tests.js @@ -35,6 +35,8 @@ Tinytest.only('oplog - v2/v1 conversion', function(test) { const entry9 = {"$v":2,"diff":{"sservices":{"sresume":{"u":{"loginTokens":[]}}}}}; + const entry10 = {"$v":2,"$set":{"services.resume.loginTokens":[{"when":"2022-01-06T23:58:35.704Z","hashedToken":"RlalW6ZSvPPJLH6sW3B1b+vrUnPy+Ox5oMv3O3S7jwg="},{"when":"2022-01-06T23:58:35.704Z","hashedToken":"DWG0Qw/+nZ48wAIhKR2r9H41wLpth9BM+Br6aZsl2bU="}]}}; + test.equal( JSON.stringify(oplogV2V1Converter(entry1)), JSON.stringify({ @@ -80,4 +82,8 @@ Tinytest.only('oplog - v2/v1 conversion', function(test) { JSON.stringify(oplogV2V1Converter(entry9)), JSON.stringify({ '$v': 2, '$set': { 'services.resume.loginTokens': [] } }) ); + test.equal( + JSON.stringify(oplogV2V1Converter(entry10)), + JSON.stringify( {"$v":2,"$set":{"services.resume.loginTokens":[{"when":"2022-01-06T23:58:35.704Z","hashedToken":"RlalW6ZSvPPJLH6sW3B1b+vrUnPy+Ox5oMv3O3S7jwg="},{"when":"2022-01-06T23:58:35.704Z","hashedToken":"DWG0Qw/+nZ48wAIhKR2r9H41wLpth9BM+Br6aZsl2bU="}]}}) + ); }); From 05869e86f78d2ce7964e05eeb9290f47ed9d207c Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Fri, 7 Jan 2022 10:02:41 -0300 Subject: [PATCH 139/393] Document new changes on mongodb driver in our history.md - code style improvements --- History.md | 35 +++++-- packages/mongo/mongo_livedata_tests.js | 7 +- packages/mongo/oplog_observe_driver.js | 1 + packages/mongo/oplog_v2_converter.js | 19 ++-- packages/mongo/oplog_v2_converter_tests.js | 103 ++++++++++++++------- 5 files changed, 106 insertions(+), 59 deletions(-) diff --git a/History.md b/History.md index be4ddb04e3..391041bb1c 100644 --- a/History.md +++ b/History.md @@ -10,6 +10,32 @@ #### Independent Releases +## v2.6, UNRELEASED + +#### Highlights + +* Support for MongoDB 5+ +* Embedded Mongo now uses MongoDB 5.0.5 + +#### Breaking Changes + +* `mongo@1.14.0` + - useUnifiedTopology is not an option anymore, it defaults to true. + - native parser is not an option anymore, it defaults to false in the mongo connection. + - poolSize not an option anymore, we are using max/minPoolSize for the same behavior on mongo connection. + - fields option is deprecated, we are maintaining a translation layer to "projection" field (now prefered) until the next minor version, where we will start showing alerts. + - _ensureIndex is now showing a deprecation message + - applySkipLimit option for count() on find cursors is no longer supported. + - we are maintaining a translation layer for the new oplog format, so if you read or rely on any behavior of it please read our oplog_v2_converter.js code + - update/insert/remove behavior is maintained in the Meteor way, documented in our docs, but we are now using replaceOne/updateOne/updateMany internally. This is subject to changes in the API rewrite of MongoDB without Fibers AND if you are using rawCollection directly you have to review your methods otherwise you will see deprecation messages if you are still using the old mongodb style directly. + - internal result of operations inside nodejs mongodb driver have changed. If you are depending on rawCollection results(not only the effect inside the DB), please review the expected format as we have done [here](https://github.com/meteor/meteor/blob/155ae639ee590bae66237fc1c29295072ec92aef/packages/mongo/mongo_driver.js#L658) + - waitForStepDownOnNonCommandShutdown=false is not needed anymore when spawning the mongodb process + +#### Migration Steps + +#### Meteor Version Release + +#### Independent Releases ## v2.5.5, 2022-01-18 @@ -35,7 +61,6 @@ * `accounts-base@2.2.1` - Fixes onLogin firing twice. [PR](https://github.com/meteor/meteor/pull/11785) and [Issue](https://github.com/meteor/meteor/issues/10853) - #### Independent Releases * `oauth@2.1.1` @@ -76,8 +101,6 @@ This version should be ignored. Proceed to 2.5.5 above. #### Breaking Changes -- N/A - #### Migration Steps - N/A @@ -129,12 +152,6 @@ This version should be ignored. Proceed to 2.5.5 above. * `standard-minifier-js@2.7.3` - Using `minifier-js@2.7.3` -* `mongo@1.14.0` - - useUnifiedTopology is not an option anymore, it defaults to true. - - fields option is deprecated, we are maintaining a translation layer to "projection" field (now prefered) until the next minor version, where we will start showing alerts. - - _ensureIndex is now showing a deprecation message - - applySkipLimit option for count() on find cursors is no longer supported. - * `npm-mongo@4.2.1` - Update MongoDB driver version to 4.2.1 diff --git a/packages/mongo/mongo_livedata_tests.js b/packages/mongo/mongo_livedata_tests.js index 02b62d3f05..3224b6d3d6 100644 --- a/packages/mongo/mongo_livedata_tests.js +++ b/packages/mongo/mongo_livedata_tests.js @@ -310,8 +310,7 @@ Tinytest.addAsync("mongo-livedata - basics, " + idGeneration, function (test, on test.equal(coll.findOne({run: run}, {sort: {x: -1}, skip: 1}).x, 1); - // Regression test for https://github.com/meteor/meteor/issues/7436 - // - ensure applySkipLimit defaults to true for count() + // - applySkipLimit is no longer an option // Note that the current behavior is inconsistent on the client. // (https://github.com/meteor/meteor/issues/1201) if (Meteor.isServer) { @@ -768,7 +767,7 @@ if (Meteor.isServer) { runInFence(function () { coll.update(docId1, {$set: {x: 'y'}}); }); - test.length(o1.output, 1, 'test that is breaking'); + test.length(o1.output, 1); test.length(o2.output, 1); test.equal(o1.output.shift(), {changed: docId1}); test.equal(o2.output.shift(), {changed: docId1}); @@ -1776,9 +1775,7 @@ _.each(Meteor.isServer ? [true, false] : [true], function (minimongo) { test.isTrue(result1.insertedId); compareResults(test, skipIds, coll.find().fetch(), [{foo: 'bar', _id: result1.insertedId}]); - // !!!! var result2 = upsert(coll, useUpdate, {foo: 'bar'}, {foo: 'baz'}); - // !!!! test.equal(result2.numberAffected, 1); if (! skipIds) test.isFalse(result2.insertedId); diff --git a/packages/mongo/oplog_observe_driver.js b/packages/mongo/oplog_observe_driver.js index e068058af6..57a0448d65 100644 --- a/packages/mongo/oplog_observe_driver.js +++ b/packages/mongo/oplog_observe_driver.js @@ -610,6 +610,7 @@ _.extend(OplogObserveDriver.prototype, { // a replacement (in which case we can just directly re-evaluate the // selector)? // oplog format has changed on mongodb 5, we have to support both now + // diff is the format in Mongo 5+ (oplog v2) var isReplace = !_.has(op.o, '$set') && !_.has(op.o, 'diff') && !_.has(op.o, '$unset'); // If this modifier modifies something inside an EJSON custom type (ie, // anything with EJSON$), then we can't try to use diff --git a/packages/mongo/oplog_v2_converter.js b/packages/mongo/oplog_v2_converter.js index ec1a08245e..658e64ed0a 100644 --- a/packages/mongo/oplog_v2_converter.js +++ b/packages/mongo/oplog_v2_converter.js @@ -24,13 +24,7 @@ on mongo 4 const isArrayOperator = possibleArrayOperator => { if (!Object.keys(possibleArrayOperator).length) return false; - let isArrayOperator = true; - Object.keys(possibleArrayOperator).forEach(key => { - if (key !== 'a' && !key.match(/u\d+/)) { - isArrayOperator = false; - } - }); - return isArrayOperator; + return !Object.keys(possibleArrayOperator).find(key => key !== 'a' && !key.match(/u\d+/)); }; const nestedOplogEntryParsers = ( { i = {}, u = {}, d = {}, ...sFields }, @@ -41,13 +35,16 @@ const nestedOplogEntryParsers = ( if (isArrayOperator(value || {})) { const { a, ...uPosition } = value; const [positionKey, newArrayIndexValue] = Object.entries(uPosition)[0]; - if (uPosition) + if (uPosition) { sFieldsOperators.push({ [newArrayIndexValue === null ? '$unset' : '$set']: { [`${prefixKey}${key.substring(1)}.${positionKey.substring(1)}`]: - newArrayIndexValue === null ? true : newArrayIndexValue, + newArrayIndexValue === null ? true : newArrayIndexValue, }, }); + }else{ + throw new Error(`Unsupported oplog array entry, please review the input: ${JSON.stringify(value)}`) + } } else { sFieldsOperators.push( nestedOplogEntryParsers(value, `${prefixKey}${key.substring(1)}.`) @@ -93,13 +90,13 @@ export const oplogV2V1Converter = v2OplogEntry => { }; function flattenObject(ob) { - var toReturn = {}; + const toReturn = {}; for (const i in ob) { if (!ob.hasOwnProperty(i)) continue; if (typeof ob[i] == 'object' && ob[i] !== null) { - var flatObject = flattenObject(ob[i]); + const flatObject = flattenObject(ob[i]); for (const x in flatObject) { if (!flatObject.hasOwnProperty(x)) continue; diff --git a/packages/mongo/oplog_v2_converter_tests.js b/packages/mongo/oplog_v2_converter_tests.js index b5f9505811..28c6ee8825 100644 --- a/packages/mongo/oplog_v2_converter_tests.js +++ b/packages/mongo/oplog_v2_converter_tests.js @@ -5,38 +5,6 @@ Tinytest.only('oplog - v2/v1 conversion', function(test) { $v: 2, diff: { scustom: { sEJSON$value: { u: { EJSONtail: 'd' } } } }, }; - const entry2 = { - $v: 2, - diff: { u: { d: '2', oi: 'asdas' } }, - }; - //set inside an array - const entry3 = { $v: 2, diff: { sasd: { a: true, u0: 2 } } }; - //unset inside an array - const entry4 = { $v: 2, diff: { sasd: { a: true, u0: null } } }; - - //set a new nested field inside an object - const entry5 = { - $v: 2, - diff: { i: { a: { b: 2 } } }, - }; - - //set an existing nested field inside an object - const entry6 = { - $v: 2, - diff: { sa: { i: { b: 3, c: 1 } } }, - }; - - //unset an existing nested field inside an object - const entry7 = { - $v: 2, - diff: { sa: { d: { b: false } } }, - }; - const entry8 = { $v: 2, diff: { u: { c: 'bar' }, sb: { a: true, u0: 2 } } }; - - const entry9 = {"$v":2,"diff":{"sservices":{"sresume":{"u":{"loginTokens":[]}}}}}; - - const entry10 = {"$v":2,"$set":{"services.resume.loginTokens":[{"when":"2022-01-06T23:58:35.704Z","hashedToken":"RlalW6ZSvPPJLH6sW3B1b+vrUnPy+Ox5oMv3O3S7jwg="},{"when":"2022-01-06T23:58:35.704Z","hashedToken":"DWG0Qw/+nZ48wAIhKR2r9H41wLpth9BM+Br6aZsl2bU="}]}}; - test.equal( JSON.stringify(oplogV2V1Converter(entry1)), JSON.stringify({ @@ -44,6 +12,11 @@ Tinytest.only('oplog - v2/v1 conversion', function(test) { $set: { 'custom.EJSON$value.EJSONtail': 'd' }, }) ); + + const entry2 = { + $v: 2, + diff: { u: { d: '2', oi: 'asdas' } }, + }; test.equal( JSON.stringify(oplogV2V1Converter(entry2)), JSON.stringify({ @@ -51,18 +24,37 @@ Tinytest.only('oplog - v2/v1 conversion', function(test) { $set: { d: '2', oi: 'asdas' }, }) ); + + //set inside an array + const entry3 = { $v: 2, diff: { sasd: { a: true, u0: 2 } } }; + test.equal( JSON.stringify(oplogV2V1Converter(entry3)), JSON.stringify({ $v: 2, $set: { 'asd.0': 2 } }) ); + + //unset inside an array + const entry4 = { $v: 2, diff: { sasd: { a: true, u0: null } } }; test.equal( JSON.stringify(oplogV2V1Converter(entry4)), JSON.stringify({ $v: 2, $unset: { 'asd.0': true } }) ); + + //set a new nested field inside an object + const entry5 = { + $v: 2, + diff: { i: { a: { b: 2 } } }, + }; test.equal( JSON.stringify(oplogV2V1Converter(entry5)), JSON.stringify({ $v: 2, $set: { 'a.b': 2 } }) ); + + //set an existing nested field inside an object + const entry6 = { + $v: 2, + diff: { sa: { i: { b: 3, c: 1 } } }, + }; test.equal( JSON.stringify(oplogV2V1Converter(entry6)), JSON.stringify({ @@ -70,20 +62,63 @@ Tinytest.only('oplog - v2/v1 conversion', function(test) { $set: { 'a.b': 3, 'a.c': 1 }, }) ); + + //unset an existing nested field inside an object + const entry7 = { + $v: 2, + diff: { sa: { d: { b: false } } }, + }; test.equal( JSON.stringify(oplogV2V1Converter(entry7)), JSON.stringify({ $v: 2, $unset: { 'a.b': true } }) ); + + const entry8 = { $v: 2, diff: { u: { c: 'bar' }, sb: { a: true, u0: 2 } } }; test.equal( JSON.stringify(oplogV2V1Converter(entry8)), JSON.stringify({ $v: 2, $set: { 'b.0': 2, c: 'bar' } }) ); + + const entry9 = { + $v: 2, + diff: { sservices: { sresume: { u: { loginTokens: [] } } } }, + }; test.equal( JSON.stringify(oplogV2V1Converter(entry9)), - JSON.stringify({ '$v': 2, '$set': { 'services.resume.loginTokens': [] } }) + JSON.stringify({ $v: 2, $set: { 'services.resume.loginTokens': [] } }) ); + + const entry10 = { + $v: 2, + $set: { + 'services.resume.loginTokens': [ + { + when: '2022-01-06T23:58:35.704Z', + hashedToken: 'RlalW6ZSvPPJLH6sW3B1b+vrUnPy+Ox5oMv3O3S7jwg=', + }, + { + when: '2022-01-06T23:58:35.704Z', + hashedToken: 'DWG0Qw/+nZ48wAIhKR2r9H41wLpth9BM+Br6aZsl2bU=', + }, + ], + }, + }; test.equal( JSON.stringify(oplogV2V1Converter(entry10)), - JSON.stringify( {"$v":2,"$set":{"services.resume.loginTokens":[{"when":"2022-01-06T23:58:35.704Z","hashedToken":"RlalW6ZSvPPJLH6sW3B1b+vrUnPy+Ox5oMv3O3S7jwg="},{"when":"2022-01-06T23:58:35.704Z","hashedToken":"DWG0Qw/+nZ48wAIhKR2r9H41wLpth9BM+Br6aZsl2bU="}]}}) + JSON.stringify({ + $v: 2, + $set: { + 'services.resume.loginTokens': [ + { + when: '2022-01-06T23:58:35.704Z', + hashedToken: 'RlalW6ZSvPPJLH6sW3B1b+vrUnPy+Ox5oMv3O3S7jwg=', + }, + { + when: '2022-01-06T23:58:35.704Z', + hashedToken: 'DWG0Qw/+nZ48wAIhKR2r9H41wLpth9BM+Br6aZsl2bU=', + }, + ], + }, + }) ); }); From 4152c205a99418390ae5c047d1cdb9880abe3448 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Fri, 7 Jan 2022 10:05:15 -0300 Subject: [PATCH 140/393] Document new changes on mongodb driver in our history.md --- History.md | 1 + 1 file changed, 1 insertion(+) diff --git a/History.md b/History.md index 391041bb1c..8b4f8a5ed0 100644 --- a/History.md +++ b/History.md @@ -30,6 +30,7 @@ - update/insert/remove behavior is maintained in the Meteor way, documented in our docs, but we are now using replaceOne/updateOne/updateMany internally. This is subject to changes in the API rewrite of MongoDB without Fibers AND if you are using rawCollection directly you have to review your methods otherwise you will see deprecation messages if you are still using the old mongodb style directly. - internal result of operations inside nodejs mongodb driver have changed. If you are depending on rawCollection results(not only the effect inside the DB), please review the expected format as we have done [here](https://github.com/meteor/meteor/blob/155ae639ee590bae66237fc1c29295072ec92aef/packages/mongo/mongo_driver.js#L658) - waitForStepDownOnNonCommandShutdown=false is not needed anymore when spawning the mongodb process + - _synchronousCursor._dbCursor.operation is not an option anymore in the raw cursor from nodejs mongodb driver. If you want to access the options, use _synchronousCursor._dbCursor.(GETTERS) - for example, _synchronousCursor._dbCursor.readPreference. #### Migration Steps From 1329238201cff9cf3166fdcf36775d630c5f882d Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Fri, 7 Jan 2022 10:44:13 -0300 Subject: [PATCH 141/393] Prepare Meteor 2.6 beta-0 release --- packages/allow-deny/package.js | 2 +- packages/meteor-tool/package.js | 2 +- packages/minimongo/package.js | 2 +- packages/mongo/package.js | 2 +- packages/tinytest/package.js | 2 +- scripts/admin/meteor-release-experimental.json | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/allow-deny/package.js b/packages/allow-deny/package.js index da6b451c9a..3548f3cb3b 100644 --- a/packages/allow-deny/package.js +++ b/packages/allow-deny/package.js @@ -1,6 +1,6 @@ Package.describe({ name: 'allow-deny', - version: '1.1.0', + version: '1.1.1-beta260.0', // 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. diff --git a/packages/meteor-tool/package.js b/packages/meteor-tool/package.js index 87ce59240c..427cb050b6 100644 --- a/packages/meteor-tool/package.js +++ b/packages/meteor-tool/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: 'The Meteor command-line tool', - version: '2.5.5', + version: '2.6.0-beta.0', }); Package.includeTool(); diff --git a/packages/minimongo/package.js b/packages/minimongo/package.js index 2ffb5128ab..eef0592ed3 100644 --- a/packages/minimongo/package.js +++ b/packages/minimongo/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Meteor's client-side datastore: a port of MongoDB to Javascript", - version: '1.7.0' + version: '1.8.0-beta260.0' }); Package.onUse(api => { diff --git a/packages/mongo/package.js b/packages/mongo/package.js index c0c030f892..a4f237213a 100644 --- a/packages/mongo/package.js +++ b/packages/mongo/package.js @@ -9,7 +9,7 @@ Package.describe({ summary: "Adaptor for using MongoDB and Minimongo over DDP", - version: '1.13.0' + version: '1.14.0-beta260.0' }); Npm.depends({ diff --git a/packages/tinytest/package.js b/packages/tinytest/package.js index 6ee6b2faf9..b952c4658c 100644 --- a/packages/tinytest/package.js +++ b/packages/tinytest/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Tiny testing framework", - version: '1.2.0' + version: '1.2.1-beta260.0' }); Package.onUse(function (api) { diff --git a/scripts/admin/meteor-release-experimental.json b/scripts/admin/meteor-release-experimental.json index ef1291d9d8..0b26d5a011 100644 --- a/scripts/admin/meteor-release-experimental.json +++ b/scripts/admin/meteor-release-experimental.json @@ -1,6 +1,6 @@ { "track": "METEOR", - "version": "2.5.4-beta.0", + "version": "2.6-beta.0", "recommended": false, "official": false, "description": "Meteor experimental release" From f700b5035304a7cc484c15441d456be1283f61a4 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Fri, 7 Jan 2022 10:45:24 -0300 Subject: [PATCH 142/393] Prepare Meteor 2.6 beta-0 release --- History.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/History.md b/History.md index 8b4f8a5ed0..bd19cafe1d 100644 --- a/History.md +++ b/History.md @@ -10,7 +10,7 @@ #### Independent Releases -## v2.6, UNRELEASED +## v2.6-beta.0, UNRELEASED #### Highlights From c0f290a6e7d4856f6243b8eae6f45c361de04c21 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Fri, 7 Jan 2022 10:47:05 -0300 Subject: [PATCH 143/393] Prepare Meteor 2.6 beta-0 release --- packages/npm-mongo/package.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/npm-mongo/package.js b/packages/npm-mongo/package.js index ff62ade192..138c8d7175 100644 --- a/packages/npm-mongo/package.js +++ b/packages/npm-mongo/package.js @@ -3,7 +3,7 @@ Package.describe({ summary: "Wrapper around the mongo npm package", - version: "4.2.1", + version: "4.3.0-beta260.0", documentation: null }); From cb0533c35d3d7c66ff3baed70b6633e84c3c495f Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Fri, 7 Jan 2022 10:53:48 -0300 Subject: [PATCH 144/393] Remove wrong "only" clause on test --- packages/mongo/oplog_v2_converter_tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mongo/oplog_v2_converter_tests.js b/packages/mongo/oplog_v2_converter_tests.js index 28c6ee8825..6c8e73063f 100644 --- a/packages/mongo/oplog_v2_converter_tests.js +++ b/packages/mongo/oplog_v2_converter_tests.js @@ -1,6 +1,6 @@ import { oplogV2V1Converter } from './oplog_v2_converter'; -Tinytest.only('oplog - v2/v1 conversion', function(test) { +Tinytest.add('oplog - v2/v1 conversion', function(test) { const entry1 = { $v: 2, diff: { scustom: { sEJSON$value: { u: { EJSONtail: 'd' } } } }, From c8eb08611697ebb182f08acc4f7bf1f7417e5bb9 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Fri, 7 Jan 2022 12:53:33 -0300 Subject: [PATCH 145/393] Update mongodb driver to latest 4.3 version released 16 hours ago. Add fallback to hello command for mongo 4 servers Bump all packages to new beta --- packages/allow-deny/package.js | 2 +- packages/meteor-tool/package.js | 2 +- packages/minimongo/package.js | 2 +- packages/mongo/mongo_driver.js | 14 +++++-- packages/mongo/package.js | 2 +- .../.npm/package/npm-shrinkwrap.json | 39 +++++++++++++------ packages/npm-mongo/package.js | 4 +- packages/tinytest/package.js | 2 +- .../admin/meteor-release-experimental.json | 2 +- 9 files changed, 45 insertions(+), 24 deletions(-) diff --git a/packages/allow-deny/package.js b/packages/allow-deny/package.js index 3548f3cb3b..28c427dd3f 100644 --- a/packages/allow-deny/package.js +++ b/packages/allow-deny/package.js @@ -1,6 +1,6 @@ Package.describe({ name: 'allow-deny', - version: '1.1.1-beta260.0', + version: '1.1.1-beta260.1', // 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. diff --git a/packages/meteor-tool/package.js b/packages/meteor-tool/package.js index 427cb050b6..c27c9b18fa 100644 --- a/packages/meteor-tool/package.js +++ b/packages/meteor-tool/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: 'The Meteor command-line tool', - version: '2.6.0-beta.0', + version: '2.6.0-beta.1', }); Package.includeTool(); diff --git a/packages/minimongo/package.js b/packages/minimongo/package.js index eef0592ed3..d3a8e5006b 100644 --- a/packages/minimongo/package.js +++ b/packages/minimongo/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Meteor's client-side datastore: a port of MongoDB to Javascript", - version: '1.8.0-beta260.0' + version: '1.8.0-beta260.1' }); Package.onUse(api => { diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 0f2f5932ef..a065d5a816 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -192,10 +192,16 @@ MongoConnection = function (url, options) { } var db = client.db(); - const helloDocument = db.admin().command( { hello: 1 } ).await(); - // First, figure out what the current primary is, if any. - if (helloDocument.isWritablePrimary) { - self._primary = helloDocument.primary; + try { + const helloDocument = db.admin().command({hello: 1}).await(); + // First, figure out what the current primary is, if any. + if (helloDocument.isWritablePrimary) { + self._primary = helloDocument.primary; + } + }catch(_){ + if (db.serverConfig && db.serverConfig.isMasterDoc) { + self._primary = db.serverConfig.isMasterDoc.primary; + } } client.topology.on( diff --git a/packages/mongo/package.js b/packages/mongo/package.js index a4f237213a..b85d03b378 100644 --- a/packages/mongo/package.js +++ b/packages/mongo/package.js @@ -9,7 +9,7 @@ Package.describe({ summary: "Adaptor for using MongoDB and Minimongo over DDP", - version: '1.14.0-beta260.0' + version: '1.14.0-beta260.1' }); Npm.depends({ diff --git a/packages/npm-mongo/.npm/package/npm-shrinkwrap.json b/packages/npm-mongo/.npm/package/npm-shrinkwrap.json index fa086c41c6..9768941e87 100644 --- a/packages/npm-mongo/.npm/package/npm-shrinkwrap.json +++ b/packages/npm-mongo/.npm/package/npm-shrinkwrap.json @@ -2,9 +2,9 @@ "lockfileVersion": 1, "dependencies": { "@types/node": { - "version": "16.11.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", - "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==" + "version": "17.0.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz", + "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==" }, "@types/webidl-conversions": { "version": "6.1.1", @@ -22,9 +22,9 @@ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "bson": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.0.tgz", - "integrity": "sha512-8jw1NU1hglS+Da1jDOUYuNcBJ4cNHCFIqzlwoFNnsTOg2R/ox0aTYcTiBN4dzRa9q7Cvy6XErh3L8ReTEb9AQQ==" + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.1.tgz", + "integrity": "sha512-I1LQ7Hz5zgwR4QquilLNZwbhPw0Apx7i7X9kGMBTsqPdml/03Q9NBtD9nt/19ahjlphktQImrnderxqpzeVDjw==" }, "buffer": { "version": "5.7.1", @@ -41,20 +41,25 @@ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" + }, "memory-pager": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" }, "mongodb": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.2.1.tgz", - "integrity": "sha512-nDC+ulM/Ea3Q2VG5eemuGfB7T4ORwrtKegH2XW9OLlUBgQF6OTNrzFCS1Z3SJGVA+T0Sr1xBYV6DMnp0A7us0g==" + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.3.0.tgz", + "integrity": "sha512-ovq9ZD9wEvab+LsaQOiwtne1Sy2egaHW8K/H5M18Tv+V5PgTRi+qdmxDGlbm94TSL3h56m6amstptu115Nzgow==" }, "mongodb-connection-string-url": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.2.0.tgz", - "integrity": "sha512-U0cDxLUrQrl7DZA828CA+o69EuWPWEJTwdMPozyd7cy/dbtncUZczMw7wRHcwMD7oKOn0NM2tF9jdf5FFVW9CA==" + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.4.1.tgz", + "integrity": "sha512-d5Kd2bVsKcSA7YI/yo57fSTtMwRQdFkvc5IZwod1RRxJtECeWPPSo7zqcUGJELifRA//Igs4spVtYAmvFCatug==" }, "punycode": { "version": "2.1.1", @@ -66,6 +71,16 @@ "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==" }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" + }, + "socks": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz", + "integrity": "sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==" + }, "sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", diff --git a/packages/npm-mongo/package.js b/packages/npm-mongo/package.js index 138c8d7175..372fa8866c 100644 --- a/packages/npm-mongo/package.js +++ b/packages/npm-mongo/package.js @@ -3,12 +3,12 @@ Package.describe({ summary: "Wrapper around the mongo npm package", - version: "4.3.0-beta260.0", + version: "4.3.0-beta260.1", documentation: null }); Npm.depends({ - mongodb: "4.2.1" + mongodb: "4.3.0" }); Package.onUse(function (api) { diff --git a/packages/tinytest/package.js b/packages/tinytest/package.js index b952c4658c..c8fb88c09f 100644 --- a/packages/tinytest/package.js +++ b/packages/tinytest/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Tiny testing framework", - version: '1.2.1-beta260.0' + version: '1.2.1-beta260.1' }); Package.onUse(function (api) { diff --git a/scripts/admin/meteor-release-experimental.json b/scripts/admin/meteor-release-experimental.json index 0b26d5a011..36027f2923 100644 --- a/scripts/admin/meteor-release-experimental.json +++ b/scripts/admin/meteor-release-experimental.json @@ -1,6 +1,6 @@ { "track": "METEOR", - "version": "2.6-beta.0", + "version": "2.6-beta.1", "recommended": false, "official": false, "description": "Meteor experimental release" From c4d4ea2a09b0c2697ae4e817a2b160e2bd547e5e Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Fri, 7 Jan 2022 13:40:27 -0300 Subject: [PATCH 146/393] Improve primary detection mechanism to be compatible with older mongodb instances Generate 2.6 beta 2 --- packages/allow-deny/package.js | 2 +- packages/meteor-tool/package.js | 2 +- packages/minimongo/package.js | 2 +- packages/mongo/mongo_driver.js | 9 ++++++--- packages/mongo/package.js | 2 +- packages/npm-mongo/package.js | 2 +- packages/tinytest/package.js | 2 +- scripts/admin/meteor-release-experimental.json | 2 +- 8 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/allow-deny/package.js b/packages/allow-deny/package.js index 28c427dd3f..a74a1f6ad6 100644 --- a/packages/allow-deny/package.js +++ b/packages/allow-deny/package.js @@ -1,6 +1,6 @@ Package.describe({ name: 'allow-deny', - version: '1.1.1-beta260.1', + version: '1.1.1-beta260.2', // 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. diff --git a/packages/meteor-tool/package.js b/packages/meteor-tool/package.js index c27c9b18fa..8be0089157 100644 --- a/packages/meteor-tool/package.js +++ b/packages/meteor-tool/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: 'The Meteor command-line tool', - version: '2.6.0-beta.1', + version: '2.6.0-beta.2', }); Package.includeTool(); diff --git a/packages/minimongo/package.js b/packages/minimongo/package.js index d3a8e5006b..2d17886a3b 100644 --- a/packages/minimongo/package.js +++ b/packages/minimongo/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Meteor's client-side datastore: a port of MongoDB to Javascript", - version: '1.8.0-beta260.1' + version: '1.8.0-beta260.2' }); Package.onUse(api => { diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index a065d5a816..e7507d5417 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -195,12 +195,15 @@ MongoConnection = function (url, options) { try { const helloDocument = db.admin().command({hello: 1}).await(); // First, figure out what the current primary is, if any. - if (helloDocument.isWritablePrimary) { + if (helloDocument.primary) { self._primary = helloDocument.primary; } }catch(_){ - if (db.serverConfig && db.serverConfig.isMasterDoc) { - self._primary = db.serverConfig.isMasterDoc.primary; + // ismaster command is supported on older mongodb versions + const isMasterDocument = db.admin().command({ismaster:1}).await(); + // First, figure out what the current primary is, if any. + if (isMasterDocument.primary) { + self._primary = isMasterDocument.primary; } } diff --git a/packages/mongo/package.js b/packages/mongo/package.js index b85d03b378..0a363814e1 100644 --- a/packages/mongo/package.js +++ b/packages/mongo/package.js @@ -9,7 +9,7 @@ Package.describe({ summary: "Adaptor for using MongoDB and Minimongo over DDP", - version: '1.14.0-beta260.1' + version: '1.14.0-beta260.2' }); Npm.depends({ diff --git a/packages/npm-mongo/package.js b/packages/npm-mongo/package.js index 372fa8866c..f5bbe3a876 100644 --- a/packages/npm-mongo/package.js +++ b/packages/npm-mongo/package.js @@ -3,7 +3,7 @@ Package.describe({ summary: "Wrapper around the mongo npm package", - version: "4.3.0-beta260.1", + version: "4.3.0-beta260.2", documentation: null }); diff --git a/packages/tinytest/package.js b/packages/tinytest/package.js index c8fb88c09f..94d5f4da7b 100644 --- a/packages/tinytest/package.js +++ b/packages/tinytest/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Tiny testing framework", - version: '1.2.1-beta260.1' + version: '1.2.1-beta260.2' }); Package.onUse(function (api) { diff --git a/scripts/admin/meteor-release-experimental.json b/scripts/admin/meteor-release-experimental.json index 36027f2923..7e1c554c9d 100644 --- a/scripts/admin/meteor-release-experimental.json +++ b/scripts/admin/meteor-release-experimental.json @@ -1,6 +1,6 @@ { "track": "METEOR", - "version": "2.6-beta.1", + "version": "2.6-beta.2", "recommended": false, "official": false, "description": "Meteor experimental release" From 1630a794b4346974afcdc38dbcdc3e632849c82b Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Wed, 12 Jan 2022 14:59:24 -0300 Subject: [PATCH 147/393] Fix Oplog Tailing high cpu usage with new Mongodb driver. As the old way of passing the "tailable" option to find cursors is now removed, the cursor was dying and making calls all the time. Fix it by using addCursorFlag --- packages/mongo/mongo_driver.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index e7507d5417..a77549d164 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -1009,16 +1009,23 @@ MongoConnection.prototype._createSynchronousCursor = function( readPreference: cursorOptions.readPreference, }; + // Do we want a tailable cursor (which only works on capped collections)? + if (cursorOptions.tailable) { + mongoOptions.numberOfRetries = -1; + } + + var dbCursor = collection.find( + replaceTypes(cursorDescription.selector, replaceMeteorAtomWithMongo), + mongoOptions); + // Do we want a tailable cursor (which only works on capped collections)? if (cursorOptions.tailable) { // We want a tailable cursor... - mongoOptions.tailable = true; + dbCursor.addCursorFlag("tailable", true) // ... and for the server to wait a bit if any getMore has no data (rather // than making us put the relevant sleeps in the client)... - mongoOptions.awaitdata = true; - // ... and to keep querying the server indefinitely rather than just 5 times - // if there's no more data. - mongoOptions.numberOfRetries = -1; + dbCursor.addCursorFlag("awaitData", true) + // And if this is on the oplog collection and the cursor specifies a 'ts', // then set the undocumented oplog replay flag, which does a special scan to // find the first document (instead of creating an index on ts). This is a @@ -1026,14 +1033,10 @@ MongoConnection.prototype._createSynchronousCursor = function( // only works with the ts field. if (cursorDescription.collectionName === OPLOG_COLLECTION && cursorDescription.selector.ts) { - mongoOptions.oplogReplay = true; + dbCursor.addCursorFlag("oplogReplay", true) } } - var dbCursor = collection.find( - replaceTypes(cursorDescription.selector, replaceMeteorAtomWithMongo), - mongoOptions); - if (typeof cursorOptions.maxTimeMs !== 'undefined') { dbCursor = dbCursor.maxTimeMS(cursorOptions.maxTimeMs); } From f3c38b8cb0c4e1a69a787667913a06cb0862f8ec Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Wed, 12 Jan 2022 15:18:40 -0300 Subject: [PATCH 148/393] New beta: 2.6-beta.3 --- packages/allow-deny/package.js | 2 +- packages/meteor-tool/package.js | 2 +- packages/minimongo/package.js | 2 +- packages/mongo/package.js | 2 +- packages/npm-mongo/package.js | 2 +- packages/tinytest/package.js | 2 +- scripts/admin/meteor-release-experimental.json | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/allow-deny/package.js b/packages/allow-deny/package.js index a74a1f6ad6..350e6479d9 100644 --- a/packages/allow-deny/package.js +++ b/packages/allow-deny/package.js @@ -1,6 +1,6 @@ Package.describe({ name: 'allow-deny', - version: '1.1.1-beta260.2', + version: '1.1.1-beta260.3', // 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. diff --git a/packages/meteor-tool/package.js b/packages/meteor-tool/package.js index 8be0089157..57350c4506 100644 --- a/packages/meteor-tool/package.js +++ b/packages/meteor-tool/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: 'The Meteor command-line tool', - version: '2.6.0-beta.2', + version: '2.6.0-beta.3', }); Package.includeTool(); diff --git a/packages/minimongo/package.js b/packages/minimongo/package.js index 2d17886a3b..5878a18625 100644 --- a/packages/minimongo/package.js +++ b/packages/minimongo/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Meteor's client-side datastore: a port of MongoDB to Javascript", - version: '1.8.0-beta260.2' + version: '1.8.0-beta260.3' }); Package.onUse(api => { diff --git a/packages/mongo/package.js b/packages/mongo/package.js index 0a363814e1..a1de4f33bc 100644 --- a/packages/mongo/package.js +++ b/packages/mongo/package.js @@ -9,7 +9,7 @@ Package.describe({ summary: "Adaptor for using MongoDB and Minimongo over DDP", - version: '1.14.0-beta260.2' + version: '1.14.0-beta260.3' }); Npm.depends({ diff --git a/packages/npm-mongo/package.js b/packages/npm-mongo/package.js index f5bbe3a876..86662ec935 100644 --- a/packages/npm-mongo/package.js +++ b/packages/npm-mongo/package.js @@ -3,7 +3,7 @@ Package.describe({ summary: "Wrapper around the mongo npm package", - version: "4.3.0-beta260.2", + version: "4.3.0-beta260.3", documentation: null }); diff --git a/packages/tinytest/package.js b/packages/tinytest/package.js index 94d5f4da7b..9a1d22eeee 100644 --- a/packages/tinytest/package.js +++ b/packages/tinytest/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Tiny testing framework", - version: '1.2.1-beta260.2' + version: '1.2.1-beta260.3' }); Package.onUse(function (api) { diff --git a/scripts/admin/meteor-release-experimental.json b/scripts/admin/meteor-release-experimental.json index 7e1c554c9d..a14b9ef51e 100644 --- a/scripts/admin/meteor-release-experimental.json +++ b/scripts/admin/meteor-release-experimental.json @@ -1,6 +1,6 @@ { "track": "METEOR", - "version": "2.6-beta.2", + "version": "2.6-beta.3", "recommended": false, "official": false, "description": "Meteor experimental release" From d68b30f384c3946f16bb0558b7fb0241d7be43b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Fri, 21 Jan 2022 13:30:37 -0400 Subject: [PATCH 149/393] dev-bundle-14.18.3.3 --- meteor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meteor b/meteor index 7edc696474..3e01811a2e 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/usr/bin/env bash -BUNDLE_VERSION=14.18.3.2 +BUNDLE_VERSION=14.18.3.3 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. From 9203c3181a321004ee220f24dfe1e8594e9d01ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Fri, 21 Jan 2022 13:42:18 -0400 Subject: [PATCH 150/393] Bump Meteor version to 2.6-beta.4 --- packages/allow-deny/package.js | 2 +- packages/meteor-tool/package.js | 2 +- packages/minimongo/package.js | 2 +- packages/mongo/package.js | 2 +- packages/npm-mongo/package.js | 2 +- packages/tinytest/package.js | 2 +- scripts/admin/meteor-release-experimental.json | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/allow-deny/package.js b/packages/allow-deny/package.js index 350e6479d9..fb1dff8242 100644 --- a/packages/allow-deny/package.js +++ b/packages/allow-deny/package.js @@ -1,6 +1,6 @@ Package.describe({ name: 'allow-deny', - version: '1.1.1-beta260.3', + version: '1.1.1-beta260.4', // 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. diff --git a/packages/meteor-tool/package.js b/packages/meteor-tool/package.js index 57350c4506..9e943397c6 100644 --- a/packages/meteor-tool/package.js +++ b/packages/meteor-tool/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: 'The Meteor command-line tool', - version: '2.6.0-beta.3', + version: '2.6.0-beta.4', }); Package.includeTool(); diff --git a/packages/minimongo/package.js b/packages/minimongo/package.js index 5878a18625..2706de45bd 100644 --- a/packages/minimongo/package.js +++ b/packages/minimongo/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Meteor's client-side datastore: a port of MongoDB to Javascript", - version: '1.8.0-beta260.3' + version: '1.8.0-beta260.4' }); Package.onUse(api => { diff --git a/packages/mongo/package.js b/packages/mongo/package.js index a1de4f33bc..6161fd3f41 100644 --- a/packages/mongo/package.js +++ b/packages/mongo/package.js @@ -9,7 +9,7 @@ Package.describe({ summary: "Adaptor for using MongoDB and Minimongo over DDP", - version: '1.14.0-beta260.3' + version: '1.14.0-beta260.4' }); Npm.depends({ diff --git a/packages/npm-mongo/package.js b/packages/npm-mongo/package.js index 86662ec935..7f87c854f2 100644 --- a/packages/npm-mongo/package.js +++ b/packages/npm-mongo/package.js @@ -3,7 +3,7 @@ Package.describe({ summary: "Wrapper around the mongo npm package", - version: "4.3.0-beta260.3", + version: "4.3.0-beta260.4", documentation: null }); diff --git a/packages/tinytest/package.js b/packages/tinytest/package.js index 9a1d22eeee..706633b600 100644 --- a/packages/tinytest/package.js +++ b/packages/tinytest/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Tiny testing framework", - version: '1.2.1-beta260.3' + version: '1.2.1-beta260.4' }); Package.onUse(function (api) { diff --git a/scripts/admin/meteor-release-experimental.json b/scripts/admin/meteor-release-experimental.json index a14b9ef51e..71e67a1080 100644 --- a/scripts/admin/meteor-release-experimental.json +++ b/scripts/admin/meteor-release-experimental.json @@ -1,6 +1,6 @@ { "track": "METEOR", - "version": "2.6-beta.3", + "version": "2.6-beta.4", "recommended": false, "official": false, "description": "Meteor experimental release" From 0585b738ca90d2c3dad54b3c5aa89e0ed6f0355e Mon Sep 17 00:00:00 2001 From: Denilson Date: Fri, 21 Jan 2022 14:11:25 -0400 Subject: [PATCH 151/393] Update docs/source/packages/accounts-2fa.md Co-authored-by: Frederico Maia --- docs/source/packages/accounts-2fa.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/packages/accounts-2fa.md b/docs/source/packages/accounts-2fa.md index 45e68a21e4..f73d9f6259 100644 --- a/docs/source/packages/accounts-2fa.md +++ b/docs/source/packages/accounts-2fa.md @@ -3,7 +3,7 @@ title: accounts-2fa description: Documentation of Meteor's `accounts-2fa` package. --- -The package allows you to easily integrate 2FA with the OTP technology on your login flow. It uses [node-2fa](https://www.npmjs.com/package/node-2fa) which works on top of [notp](https://github.com/guyht/notp), which implements TOTP ([RFC 6238](https://www.ietf.org/rfc/rfc6238.txt)) (the Authenticator standard), which is based on HOTP ([RFC 4226](https://www.ietf.org/rfc/rfc4226.txt)) to provide codes that are exactly compatible with all other Authenticator apps and services that use them. +The package allows you to easily integrate 2FA with the OTP technology on your login flow. It uses [node-2fa](https://www.npmjs.com/package/node-2fa) which works on top of [notp](https://github.com/guyht/notp), **that** implements TOTP ([RFC 6238](https://www.ietf.org/rfc/rfc6238.txt)) (the Authenticator standard), which is based on HOTP ([RFC 4226](https://www.ietf.org/rfc/rfc4226.txt)) to provide codes that are exactly compatible with all other Authenticator apps and services that use them. > This package is meant to be used with `accounts-password`, so if you still didn't add `account-password` to your project, you'll need to add it too. In the future we want to enable the use of this app with other login methods, like `accounts-passwordless` or our oauth methods (Google, GitHub, etc...). From 6dea1aced9cd78a6b4529501b6cc95d8f5ad10f0 Mon Sep 17 00:00:00 2001 From: Denilson Date: Fri, 21 Jan 2022 14:11:47 -0400 Subject: [PATCH 152/393] Update docs/source/packages/accounts-2fa.md Co-authored-by: Frederico Maia --- docs/source/packages/accounts-2fa.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/packages/accounts-2fa.md b/docs/source/packages/accounts-2fa.md index f73d9f6259..86a08a7cad 100644 --- a/docs/source/packages/accounts-2fa.md +++ b/docs/source/packages/accounts-2fa.md @@ -5,7 +5,7 @@ description: Documentation of Meteor's `accounts-2fa` package. The package allows you to easily integrate 2FA with the OTP technology on your login flow. It uses [node-2fa](https://www.npmjs.com/package/node-2fa) which works on top of [notp](https://github.com/guyht/notp), **that** implements TOTP ([RFC 6238](https://www.ietf.org/rfc/rfc6238.txt)) (the Authenticator standard), which is based on HOTP ([RFC 4226](https://www.ietf.org/rfc/rfc4226.txt)) to provide codes that are exactly compatible with all other Authenticator apps and services that use them. -> This package is meant to be used with `accounts-password`, so if you still didn't add `account-password` to your project, you'll need to add it too. In the future we want to enable the use of this app with other login methods, like `accounts-passwordless` or our oauth methods (Google, GitHub, etc...). +> This package is meant to be used with `accounts-password`, so if you don't have `account-password` in your project, you'll need to add it. In the future, we want to enable the use of this app with other login methods, like `accounts-passwordless` or our oauth methods (Google, GitHub, etc...).

Activating 2FA

From 276d5b5b507512e4f2da85133ed5c41a609dd164 Mon Sep 17 00:00:00 2001 From: Denilson Date: Fri, 21 Jan 2022 14:12:05 -0400 Subject: [PATCH 153/393] Update docs/source/packages/accounts-2fa.md Co-authored-by: Frederico Maia --- docs/source/packages/accounts-2fa.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/packages/accounts-2fa.md b/docs/source/packages/accounts-2fa.md index 86a08a7cad..3d5e771590 100644 --- a/docs/source/packages/accounts-2fa.md +++ b/docs/source/packages/accounts-2fa.md @@ -9,7 +9,7 @@ The package allows you to easily integrate 2FA with the OTP technology on your l

Activating 2FA

-The first step to use 2FA is to generate a QR code so the user can read it on an authenticator app and start to receiving codes. +The first step to using 2FA is to generate a QR code so that the user can scan it in an authenticator app and start receiving codes. {% apibox "Accounts.generate2faActivationQrCode" "module":"accounts-base" %} From ab0ddcda3d20f5a522d3a57e87dd36b57f421845 Mon Sep 17 00:00:00 2001 From: Denilson Date: Fri, 21 Jan 2022 14:12:15 -0400 Subject: [PATCH 154/393] Update docs/source/packages/accounts-2fa.md Co-authored-by: Frederico Maia --- docs/source/packages/accounts-2fa.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/packages/accounts-2fa.md b/docs/source/packages/accounts-2fa.md index 3d5e771590..0a48a60bc5 100644 --- a/docs/source/packages/accounts-2fa.md +++ b/docs/source/packages/accounts-2fa.md @@ -60,7 +60,7 @@ At this point, the 2FA won't be activated just yet. Now that the user has access {% apibox "Accounts.enableUser2fa" "module":"accounts-base" %} -Called with a code the user will receive from the authenticator app once they read the QR code. This function throws an error on failure. If the code provided is correct, a `type` will be added to the user's `twoFactorAuthentication` object, and now the 2FA will be considered enabled: +It should be called with a code that the users will receive from the authenticator app once they read the QR code. This function throws an error on failure. If the code provided is correct, a `type` will be added to the user's `twoFactorAuthentication` object and now 2FA will be enabled: ```js twoFactorAuthetication: { From 30222b3849063389d838e13a08beccf50b75418c Mon Sep 17 00:00:00 2001 From: Denilson Date: Fri, 21 Jan 2022 14:12:41 -0400 Subject: [PATCH 155/393] Update docs/source/packages/accounts-2fa.md Co-authored-by: Frederico Maia --- docs/source/packages/accounts-2fa.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/packages/accounts-2fa.md b/docs/source/packages/accounts-2fa.md index 0a48a60bc5..21ff88fb5a 100644 --- a/docs/source/packages/accounts-2fa.md +++ b/docs/source/packages/accounts-2fa.md @@ -73,7 +73,7 @@ twoFactorAuthetication: { Now that you have a way to allow your users to enable 2FA on their accounts, you can create a login flow based on that. -To verify if a user has or not 2FA enabled you can call the function `Accounts.has2FAEnabled`: +To verify whether or not a user has 2FA enabled, you can call the function `Accounts.has2FAEnabled`: {% apibox "Accounts.has2faEnabled" "module":"accounts-base" %} From 295f6f42dcb729b598b15037a938696b246db88a Mon Sep 17 00:00:00 2001 From: Denilson Date: Fri, 21 Jan 2022 14:13:00 -0400 Subject: [PATCH 156/393] Update docs/source/packages/accounts-2fa.md Co-authored-by: Frederico Maia --- docs/source/packages/accounts-2fa.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/packages/accounts-2fa.md b/docs/source/packages/accounts-2fa.md index 21ff88fb5a..eef2d551e1 100644 --- a/docs/source/packages/accounts-2fa.md +++ b/docs/source/packages/accounts-2fa.md @@ -111,7 +111,7 @@ A way of using it would be: If the user has 2FA enabled, and you try to use the function `Meteor.loginWithPassword`, the login will fail, as the user should provide a token to access the app. -The function you will need to call now to allow the user to log in is `Meteor.loginWithPasswordAnd2faToken`: +The function you will need to call now to allow the user to login is `Meteor.loginWithPasswordAnd2faToken`: {% apibox "Meteor.loginWithPasswordAnd2faToken" %} From 5a3e4876875f0d01bf585c2bf26de2d689a1a1cc Mon Sep 17 00:00:00 2001 From: Denilson Date: Fri, 21 Jan 2022 14:33:44 -0400 Subject: [PATCH 157/393] Update docs/source/packages/accounts-2fa.md Co-authored-by: Frederico Maia --- docs/source/packages/accounts-2fa.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/packages/accounts-2fa.md b/docs/source/packages/accounts-2fa.md index eef2d551e1..e67c5b3125 100644 --- a/docs/source/packages/accounts-2fa.md +++ b/docs/source/packages/accounts-2fa.md @@ -77,7 +77,7 @@ To verify whether or not a user has 2FA enabled, you can call the function `Acco {% apibox "Accounts.has2faEnabled" "module":"accounts-base" %} -With this function, you can verify if the has or not 2FA enabled, and based on this information, you can directly log the user in the 2FA is not enabled, or redirect the user to a place where they can provide a code, in case they do have 2FA enabled. +With this function, you can check whether or not the user has 2FA enabled, and based on this information, you can directly log the user in the 2FA is not enabled, or redirect the user to a place where they can provide a code, in case they do have 2FA enabled. A way of using it would be: From f7e62862044ce718673ea7b192ea51aa56e718b5 Mon Sep 17 00:00:00 2001 From: denihs Date: Fri, 21 Jan 2022 14:35:36 -0400 Subject: [PATCH 158/393] Changes requested on code review - fixing typo --- docs/source/packages/accounts-2fa.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/packages/accounts-2fa.md b/docs/source/packages/accounts-2fa.md index e67c5b3125..8f981962fc 100644 --- a/docs/source/packages/accounts-2fa.md +++ b/docs/source/packages/accounts-2fa.md @@ -77,7 +77,7 @@ To verify whether or not a user has 2FA enabled, you can call the function `Acco {% apibox "Accounts.has2faEnabled" "module":"accounts-base" %} -With this function, you can check whether or not the user has 2FA enabled, and based on this information, you can directly log the user in the 2FA is not enabled, or redirect the user to a place where they can provide a code, in case they do have 2FA enabled. +With this function, you can check whether or not the user has 2FA enabled, and based on this information, you can directly call `Meteor.loginWithPassword` if the 2FA is not enabled, or redirect the user to a place where they can provide a code, in case they do have 2FA enabled. A way of using it would be: From 88bee7bd1a22af09f461ea12f6dea0821385727f Mon Sep 17 00:00:00 2001 From: denihs Date: Fri, 21 Jan 2022 14:42:36 -0400 Subject: [PATCH 159/393] Changes requested on code review - wrong text --- docs/source/packages/accounts-2fa.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/packages/accounts-2fa.md b/docs/source/packages/accounts-2fa.md index 8f981962fc..961f5dac5c 100644 --- a/docs/source/packages/accounts-2fa.md +++ b/docs/source/packages/accounts-2fa.md @@ -14,7 +14,7 @@ The first step to using 2FA is to generate a QR code so that the user can scan i {% apibox "Accounts.generate2faActivationQrCode" "module":"accounts-base" %} Receives an `appName` which is the name of your app that will show up when the user scans the QR code. Also, a callback called with a QR code in SVG format on success or a single `Error` argument -on failure. Both parameters are optional. +on failure. On success, this function will also add an object to the logged user containing the QR secret: From 0b70db76246730e9a9d3631564d3bb648bec1a8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Fri, 21 Jan 2022 14:54:21 -0400 Subject: [PATCH 160/393] Updating History and Guide with more info about Meteor 2.6 --- History.md | 28 +++++++++-- guide/_config.yml | 2 +- guide/source/2.5-migration.md | 39 ++++++++++++++++ guide/source/2.6-migration.md | 87 +++++++++++++++++++++++++++++++++++ 4 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 guide/source/2.5-migration.md create mode 100644 guide/source/2.6-migration.md diff --git a/History.md b/History.md index bd19cafe1d..996fca7a10 100644 --- a/History.md +++ b/History.md @@ -14,12 +14,22 @@ #### Highlights -* Support for MongoDB 5+ +* MongoDB Node.js driver Upgrade to support MongoDB 5.x * Embedded Mongo now uses MongoDB 5.0.5 #### Breaking Changes * `mongo@1.14.0` + - This is not a breaking change in Meteor itself but internal result of operations inside Node.js MongoDB driver have changed. If you are depending on rawCollection results (not only the effect inside the DB), please review the expected format as we have done [here](https://github.com/meteor/meteor/blob/155ae639ee590bae66237fc1c29295072ec92aef/packages/mongo/mongo_driver.js#L658) + +#### Migration Steps + +Read our [Migration Guide](https://guide.meteor.com/2.6-migration.html) for this version. + +#### Meteor Version Release + +* `mongo@1.14.0` + - internal result of operations inside Node.js MongoDB driver have changed. If you are depending on rawCollection results (not only the effect inside the DB), please review the expected format as we have done [here](https://github.com/meteor/meteor/blob/155ae639ee590bae66237fc1c29295072ec92aef/packages/mongo/mongo_driver.js#L658) - useUnifiedTopology is not an option anymore, it defaults to true. - native parser is not an option anymore, it defaults to false in the mongo connection. - poolSize not an option anymore, we are using max/minPoolSize for the same behavior on mongo connection. @@ -28,14 +38,24 @@ - applySkipLimit option for count() on find cursors is no longer supported. - we are maintaining a translation layer for the new oplog format, so if you read or rely on any behavior of it please read our oplog_v2_converter.js code - update/insert/remove behavior is maintained in the Meteor way, documented in our docs, but we are now using replaceOne/updateOne/updateMany internally. This is subject to changes in the API rewrite of MongoDB without Fibers AND if you are using rawCollection directly you have to review your methods otherwise you will see deprecation messages if you are still using the old mongodb style directly. - - internal result of operations inside nodejs mongodb driver have changed. If you are depending on rawCollection results(not only the effect inside the DB), please review the expected format as we have done [here](https://github.com/meteor/meteor/blob/155ae639ee590bae66237fc1c29295072ec92aef/packages/mongo/mongo_driver.js#L658) - waitForStepDownOnNonCommandShutdown=false is not needed anymore when spawning the mongodb process - _synchronousCursor._dbCursor.operation is not an option anymore in the raw cursor from nodejs mongodb driver. If you want to access the options, use _synchronousCursor._dbCursor.(GETTERS) - for example, _synchronousCursor._dbCursor.readPreference. -#### Migration Steps +* `allow-deny@1.1.1` + - TODO -#### Meteor Version Release +* `meteor-tool@2.6.0` + - TODO +* `minimongo@1.8.0` + - TODO + +* `npm-mongo@4.3.0` + - Upgraded MongoDB Node.js driver to 4.3.0 + +* `tinytest@1.2.1` + - TODO + #### Independent Releases ## v2.5.5, 2022-01-18 diff --git a/guide/_config.yml b/guide/_config.yml index 056ae2e027..85f352bc87 100644 --- a/guide/_config.yml +++ b/guide/_config.yml @@ -34,7 +34,7 @@ sidebar_categories: - index - code-style - structure - - 2.4-migration + - 2.6-migration Data: - collections - data-loading diff --git a/guide/source/2.5-migration.md b/guide/source/2.5-migration.md new file mode 100644 index 0000000000..15a263b76d --- /dev/null +++ b/guide/source/2.5-migration.md @@ -0,0 +1,39 @@ +--- +title: Migrating to Meteor 2.5 +description: How to migrate your application to Meteor 2.5. +--- + +Most of the new features in Meteor 2.5 are either applied directly behind the scenes (in a backwards compatible manner) or are opt-in. For a complete breakdown of the changes, please refer to the [changelog](http://docs.meteor.com/changelog.html). + +The above being said, there are a few items that you should implement to have easier time in the future. + +

Cordova Android 10

+ +Cordova Android v10 now enables AndroidX. If you use any cordova-plugin that depends or uses any old support library, you need to include the cordova-plugin-androidx-adapter cordova-plugin, otherwise you will get build errors. + +

Login with token

+ +`Meteor.loginWithToken` from the new package `accounts-passwordless` was conflicting with another method with the same name on `accounts-base` so we had to rename the method of `accounts-passwordless` package to `Meteor.passwordlessLoginWithToken`, this change was released in Meteor 2.5.1. + +

Migrating from a version older than 2.4?

+ +If you're migrating from a version of Meteor older than Meteor 2.4, there may be important considerations not listed in this guide (which specifically covers 2.3 to 2.4). Please review the older migration guides for details: + +* [Migrating to Meteor 2.4](2.4-migration.html) (from 2.3) +* [Migrating to Meteor 2.3](2.3-migration.html) (from 2.2) +* [Migrating to Meteor 2.2](2.2-migration.html) (from 2.0) +* [Migrating to Meteor 2.0](2.0-migration.html) (from 1.12) +* [Migrating to Meteor 1.12](1.12-migration.html) (from 1.11) +* [Migrating to Meteor 1.11](1.11-migration.html) (from 1.10.2) +* [Migrating to Meteor 1.10.2](1.10.2-migration.html) (from 1.10) +* [Migrating to Meteor 1.10](1.10-migration.html) (from 1.9.3) +* [Migrating to Meteor 1.9.3](1.9.3-migration.html) (from 1.9) +* [Migrating to Meteor 1.9](1.9-migration.html) (from 1.8.3) +* [Migrating to Meteor 1.8.3](1.8.3-migration.html) (from 1.8.2) +* [Migrating to Meteor 1.8.2](1.8.2-migration.html) (from 1.8) +* [Migrating to Meteor 1.8](1.8-migration.html) (from 1.7) +* [Migrating to Meteor 1.7](1.7-migration.html) (from 1.6) +* [Migrating to Meteor 1.6](1.6-migration.html) (from 1.5) +* [Migrating to Meteor 1.5](1.5-migration.html) (from 1.4) +* [Migrating to Meteor 1.4](1.4-migration.html) (from 1.3) +* [Migrating to Meteor 1.3](1.3-migration.html) (from 1.2) diff --git a/guide/source/2.6-migration.md b/guide/source/2.6-migration.md new file mode 100644 index 0000000000..7b5ddb1ab1 --- /dev/null +++ b/guide/source/2.6-migration.md @@ -0,0 +1,87 @@ +--- +title: Migrating to Meteor 2.6 +description: How to migrate your application to Meteor 2.6. +--- + +Most of the new features in Meteor 2.6 are either applied directly behind the scenes (in a backwards compatible manner) or are opt-in. For a complete breakdown of the changes, please refer to the [changelog](http://docs.meteor.com/changelog.html). + +The above being said, there are a few items that you should implement to have easier time in the future. + +

MongoDB 5.0

+ +#### Introduction +Meteor before 2.6 was supporting MongoDB 4.x, starting from this version we've upgraded to MongoDB Node.js driver version 4.3.0 which supports MongoDB 5.0. + +This change was necessary at the time of writing this guide (January 2022) as MongoDB Atlas is going to migrate automatically all the clusters in the plans Atlas M0 (Free Cluster), M2, and M5 to MongoDB 5.0 in February 2022. + +If you are running on MongoDB Atlas and in one of these plans you have to run Meteor 2.6 in order to connect and interact with your MongoDB properly as your MongoDB is going to be upgraded to 5.0 in February. If you are not running at these plans, you can continue to use your MongoDB at previous versions and you can use previous versions of Meteor still without any problems. + +That said, we encourage everybody to run the latest version of Meteor as soon as possible as you can benefit from a new MongoDB driver and also other features that we are always adding to Meteor. + +This version of Meteor is also compatible with previous version of MongoDB server, so you can continue using the latest Meteor without any issues even if you are not running MongoDB 5.x yet. You can check [here](https://docs.mongodb.com/drivers/node/current/compatibility/) which versions of MongoDB server the Node.js driver in the version 4.3.0 supports and as a consequence these are the versions of MongoDB server supported by Meteor 2.6 as well. In short, Meteor 2.6 supports these versions of MongoDB server: 5.1, 5.0, 4.4, 4.2, 4.0, 3.6. + +#### Changes + +Here is a list of the changes that we have made to Meteor core packages in order to make it compatible with MongoDB Node.js Driver 4.3.x, most of them are not going to affect you but we recommend that you test your application well before upgrading to the latest version of Meteor as we have made many changes on how Meteor interact with MongoDB. + - internal result of operations inside Node.js MongoDB driver have changed. If you are depending on rawCollection results (not only the effect inside the DB), please review the expected format as we have done [here](https://github.com/meteor/meteor/blob/155ae639ee590bae66237fc1c29295072ec92aef/packages/mongo/mongo_driver.js#L658) + - useUnifiedTopology is not an option anymore, it defaults to true. + - native parser is not an option anymore, it defaults to false in the mongo connection. + - poolSize not an option anymore, we are using max/minPoolSize for the same behavior on mongo connection. + - fields option is deprecated, we are maintaining a translation layer to "projection" field (now prefered) until the next minor version, where we will start showing alerts. + - _ensureIndex is now showing a deprecation message + - applySkipLimit option for count() on find cursors is no longer supported. + - we are maintaining a translation layer for the new oplog format, so if you read or rely on any behavior of it please read our oplog_v2_converter.js code + - update/insert/remove behavior is maintained in the Meteor way, documented in our docs, but we are now using replaceOne/updateOne/updateMany internally. This is subject to changes in the API rewrite of MongoDB without Fibers AND if you are using rawCollection directly you have to review your methods otherwise you will see deprecation messages if you are still using the old mongodb style directly. + - waitForStepDownOnNonCommandShutdown=false is not needed anymore when spawning the mongodb process + - _synchronousCursor._dbCursor.operation is not an option anymore in the raw cursor from nodejs mongodb driver. If you want to access the options, use _synchronousCursor._dbCursor.(GETTERS) - for example, _synchronousCursor._dbCursor.readPreference. + +Below we describe a few common cases in this migrations: + +#### 1) Same version of MongoDB server + +If you are not changing your MongoDB server version you don't need to change anything in your code, even if you are using `rawCollection` results, but as we did many changes on how Meteor interact with MongoDB in order to be compatible with the new driver we recommend that you test your application carefully before releasing to production with this Meteor version. + +We've made many tests in real applications and also in our automatic tests suite, we believe we've fixed all the issues that we found along the way but Meteor interaction with MongoDB is so broad and open that maybe you have different use cases that could lead to different issues. + +Again, we are not aware of any issues that were introduced by these changes, but it's important that you check your app behavior, especially if you have places where you believe you are not using MongoDB in a traditional way. + +#### 2) Migrating from MongoDB 4.x to MongoDB 5.x + +As we have made many changes to Meteor core packages in this version we recommend the following steps to migrate: +- Upgrade your app to use Meteor 2.6 (meteor update --release 2.6) in a branch; +- Create a staging environment with MongoDB 5.x and your app environment using the branch created in the previous step; + - If you are using MongoDB Atlas, in MongoDB Atlas we were not able to migrate to a free MongoDB 5.x instance, so we had to migrate to a paid cluster in order to test the app properly, maybe this will change after February 2022; +- Set up your staging database with MongoDB 5.x and restore your production data there, or populate this database in a way that you can reproduce the same cases as if you were in production; +- Run your app pointing your MONGO_URL to this new database, that is running MongoDB 5.x; +- Run your end-to-end tests in this environment. If you don't have a robust end-to-end test we recommend that you test your app manually to make sure everything is working properly. +- Once you have a stable end-to-end test (or manual test), you can consider that you are ready to run using MongoDB 5.x, so you can consider it as any other database version migration. + +We are not aware of any issues that were introduced by the necessary changes to support MongoDB, but it's important that you check your app behavior, especially if you have places where you believe you are not using MongoDB in a traditional way. + +For example, the MongoDB Oplog suffered a lot of changes, and we had to create a conversor to keep our oplog tail understanding the changes coming from the oplog, so if you have complex features and queries that depend on oplog (Meteor real-time diff system), you should review if your app is working properly especially in this part. + +As MongoDB also removed a few deprecated methods that were used by Meteor we recommend testing all important operations of your application. + +

Migrating from a version older than 2.5?

+ +If you're migrating from a version of Meteor older than Meteor 2.5, there may be important considerations not listed in this guide (which specifically covers 2.4 to 2.5). Please review the older migration guides for details: + +* [Migrating to Meteor 2.5](2.5-migration.html) (from 2.4) +* [Migrating to Meteor 2.4](2.4-migration.html) (from 2.3) +* [Migrating to Meteor 2.3](2.3-migration.html) (from 2.2) +* [Migrating to Meteor 2.2](2.2-migration.html) (from 2.0) +* [Migrating to Meteor 2.0](2.0-migration.html) (from 1.12) +* [Migrating to Meteor 1.12](1.12-migration.html) (from 1.11) +* [Migrating to Meteor 1.11](1.11-migration.html) (from 1.10.2) +* [Migrating to Meteor 1.10.2](1.10.2-migration.html) (from 1.10) +* [Migrating to Meteor 1.10](1.10-migration.html) (from 1.9.3) +* [Migrating to Meteor 1.9.3](1.9.3-migration.html) (from 1.9) +* [Migrating to Meteor 1.9](1.9-migration.html) (from 1.8.3) +* [Migrating to Meteor 1.8.3](1.8.3-migration.html) (from 1.8.2) +* [Migrating to Meteor 1.8.2](1.8.2-migration.html) (from 1.8) +* [Migrating to Meteor 1.8](1.8-migration.html) (from 1.7) +* [Migrating to Meteor 1.7](1.7-migration.html) (from 1.6) +* [Migrating to Meteor 1.6](1.6-migration.html) (from 1.5) +* [Migrating to Meteor 1.5](1.5-migration.html) (from 1.4) +* [Migrating to Meteor 1.4](1.4-migration.html) (from 1.3) +* [Migrating to Meteor 1.3](1.3-migration.html) (from 1.2) From 1a82283296814cf21e9c0be5e35dbfb491cf836d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Fri, 21 Jan 2022 15:24:40 -0400 Subject: [PATCH 161/393] Fixes typo in the migration guide --- guide/source/2.6-migration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/source/2.6-migration.md b/guide/source/2.6-migration.md index 7b5ddb1ab1..00a40f597a 100644 --- a/guide/source/2.6-migration.md +++ b/guide/source/2.6-migration.md @@ -35,7 +35,7 @@ Here is a list of the changes that we have made to Meteor core packages in order - waitForStepDownOnNonCommandShutdown=false is not needed anymore when spawning the mongodb process - _synchronousCursor._dbCursor.operation is not an option anymore in the raw cursor from nodejs mongodb driver. If you want to access the options, use _synchronousCursor._dbCursor.(GETTERS) - for example, _synchronousCursor._dbCursor.readPreference. -Below we describe a few common cases in this migrations: +Below we describe a few common cases in this migration: #### 1) Same version of MongoDB server From 65af9faa3d83d2a2bff7c1473caee82c38f1f2cb Mon Sep 17 00:00:00 2001 From: Denilson Date: Fri, 21 Jan 2022 16:32:21 -0400 Subject: [PATCH 162/393] Update docs/source/packages/accounts-2fa.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Filipe Névola --- docs/source/packages/accounts-2fa.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/packages/accounts-2fa.md b/docs/source/packages/accounts-2fa.md index 961f5dac5c..72e9e34748 100644 --- a/docs/source/packages/accounts-2fa.md +++ b/docs/source/packages/accounts-2fa.md @@ -133,7 +133,7 @@ So the call of this function should look something like this:

Disabling 2FA

-To disable 2FA you can simply use this function: +To disable 2FA for an user use this method: {% apibox "Accounts.disableUser2fa" "module":"accounts-base" %} From febb8290e11e2632372afaa56420d458aaa1780c Mon Sep 17 00:00:00 2001 From: denihs Date: Fri, 21 Jan 2022 16:55:57 -0400 Subject: [PATCH 163/393] Changes requested on code review --- docs/source/packages/accounts-2fa.md | 10 +++++----- packages/accounts-2fa/2fa-server.js | 6 +++--- packages/accounts-base/accounts_client_tests.js | 14 +++++++------- packages/accounts-password/password_client.js | 12 ++++++------ packages/accounts-password/password_server.js | 4 ++-- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/source/packages/accounts-2fa.md b/docs/source/packages/accounts-2fa.md index 961f5dac5c..25a53df6e0 100644 --- a/docs/source/packages/accounts-2fa.md +++ b/docs/source/packages/accounts-2fa.md @@ -60,7 +60,7 @@ At this point, the 2FA won't be activated just yet. Now that the user has access {% apibox "Accounts.enableUser2fa" "module":"accounts-base" %} -It should be called with a code that the users will receive from the authenticator app once they read the QR code. This function throws an error on failure. If the code provided is correct, a `type` will be added to the user's `twoFactorAuthentication` object and now 2FA will be enabled: +It should be called with a code that the users will receive from the authenticator app once they read the QR code. The callback is called with a single `Error` argument on failure. If the code provided is correct, a `type` will be added to the user's `twoFactorAuthentication` object and now 2FA will be enabled: ```js twoFactorAuthetication: { @@ -109,11 +109,11 @@ A way of using it would be: ``` -If the user has 2FA enabled, and you try to use the function `Meteor.loginWithPassword`, the login will fail, as the user should provide a token to access the app. +If the user has 2FA enabled, and you try to use the function `Meteor.loginWithPassword`, the login will fail, as the user should provide a code to access the app. -The function you will need to call now to allow the user to login is `Meteor.loginWithPasswordAnd2faToken`: +The function you will need to call now to allow the user to login is `Meteor.loginWithPasswordAnd2faCode`: -{% apibox "Meteor.loginWithPasswordAnd2faToken" %} +{% apibox "Meteor.loginWithPasswordAnd2faCode" %} Now you will be able to receive a code from the user and this function will verify if the code is valid. If it is, the user will be logged in. @@ -121,7 +121,7 @@ So the call of this function should look something like this: ```js ``` +

Working with accounts-passwordless

+ +Following the same strategy from the previous package, you can use the function `Accounts.has2faEnabled` to verify whether or not the user has 2FA enabled. If yes, you send them their token and on next step you receive their token and their 2FA code, otherwise, you still send them their token but on the next step you don't ask them for a 2FA code. + +Here it's an example: + +```js +Accounts.has2faEnabled(username, (err, isEnabled) => { + if (err) {console.error("...", err);return;} + + Accounts.requestLoginTokenForUser({selector: "email@example.com"}, e => { + if (e) {console.error("...", e);return;} + + if (isEnabled) { + setShouldAskTokenAndCode(true); + return; + } + + setShouldAskToken(true); + }); +}); +``` + +Now you can either call the standard method [`Meteor.passwordlessLoginWithToken`](https://docs.meteor.com/packages/accounts-passwordless.html#Meteor-passwordlessLoginWithToken) if they don't have 2FA enabled, or in case they do, you call the method `Meteor.passwordlessLoginWithTokenAnd2faCode` that will allow you to provide a selector, token, and 2FA code: + +{% apibox "Meteor.passwordlessLoginWithTokenAnd2faCode" %} + +So, using this strategy your code should look something like this: + +```js +; +``` +

Disabling 2FA

To disable 2FA for a user use this method: @@ -146,3 +208,34 @@ To disable 2FA for a user use this method: {% apibox "Accounts.disableUser2fa" "module":"accounts-base" %} To call this function the user must be already logged in. + +

How to integrate an Authentication Package with accounts-2fa

+ +To integrate this package with any other existing Login method, it's necessary following two steps: + +1 - For the client, create a new method from your current login method. So for example, from the method `Meteor.loginWithPassword` we created a new one called `Meteor.loginWithPasswordAnd2faCode`, and the only difference between them is that the latest one receives one additional parameter, the 2FA code, but we call the same function on the server side. + +2 - For the server, inside the function that will log the user in, you verify if the function `Accounts._is2faEnabledForUser` exists, and if yes, you call it providing the user you want to check if the 2FA is enabled, and if either of these statements are false, you proceed with the login flow. This function exists only when the package `accounts-2fa` is added to the project. + +If both statements are true, now you verify if a code was provided, if not throw an error, if it was provided, verify if the code is valid by calling the function `Accounts._isTokenValid`, if not, throw an error. + +Here it's an example: + +```js +if ( + Accounts._is2faEnabledForUser && + Accounts._is2faEnabledForUser(user) + ) { + if (!code) { + Accounts._handleError('2FA code must be informed.'); + } + if ( + !Accounts._isTokenValid(user.services.twoFactorAuthentication.secret, code) + ) { + Accounts._handleError('Invalid 2FA code.'); + } + } + + // continue the login flow +``` + From 71b0514c746610ec2b413cf1fbedb90e4df82251 Mon Sep 17 00:00:00 2001 From: denihs Date: Tue, 25 Jan 2022 16:20:08 -0400 Subject: [PATCH 180/393] Bump accounts-passwordless --- packages/accounts-passwordless/package.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/accounts-passwordless/package.js b/packages/accounts-passwordless/package.js index 86cfa7293f..d9c87f7c97 100644 --- a/packages/accounts-passwordless/package.js +++ b/packages/accounts-passwordless/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: 'No-password login/sign-up support for accounts', - version: '2.0.0', + version: '2.1.0', }); Package.onUse(api => { From f51a5e86fdfa2fa2aba5046f54f4358ea98f2b5a Mon Sep 17 00:00:00 2001 From: denihs Date: Tue, 25 Jan 2022 16:49:45 -0400 Subject: [PATCH 181/393] Fixing tests --- packages/accounts-base/accounts_client_tests.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/accounts-base/accounts_client_tests.js b/packages/accounts-base/accounts_client_tests.js index 7342471b8f..5f72c5dd02 100644 --- a/packages/accounts-base/accounts_client_tests.js +++ b/packages/accounts-base/accounts_client_tests.js @@ -200,13 +200,13 @@ Tinytest.addAsync( Tinytest.addAsync( - 'accounts-2fa - Meteor.loginWithPasswordAnd2faCode() fails with invalid token', + 'accounts-2fa - Meteor.loginWithPasswordAnd2faCode() fails with invalid code', (test, done) => { createUserAndLogout(test, done, () => { forceEnableUser2fa(() => { Meteor.loginWithPasswordAnd2faCode(username, password, 'ABC', e => { test.isFalse(Meteor.user()); - test.equal(e.reason, 'Invalid token.'); + test.equal(e.reason, 'Invalid 2FA code.'); removeTestUser(done); }); }); From fd974c21338797ac5a97a7da0d94f5408fb7a478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Tue, 25 Jan 2022 17:19:01 -0400 Subject: [PATCH 182/393] Updating readmes --- CONTRIBUTING.md | 6 ++-- History.md | 2 +- docs/_config.yml | 1 + docs/source/roadmap.md | 74 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 docs/source/roadmap.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 031e3746f5..c339df7798 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -64,7 +64,7 @@ Current Core Committers: ### Tracking project work -Right now, the best place to track the work being done on Meteor is to take a look at the latest release milestone [here](https://github.com/meteor/meteor/milestones). Also, the [Meteor Roadmap](Roadmap.md) contains high-level information on the current priorities of the project. +Right now, the best place to track the work being done on Meteor is to take a look at the latest release milestone [here](https://github.com/meteor/meteor/milestones). Also, the [Meteor Roadmap](https://docs.meteor.com/roadmap.html) contains high-level information on the current priorities of the project. ## Reporting a bug in Meteor
@@ -126,7 +126,7 @@ for more details on proposing changes to core code. Feature requests are tracked in the [Discussions](https://github.com/meteor/meteor/discussions). Meteor is a big project with [many sub-projects](https://github.com/meteor/meteor/tree/devel/packages). -Community is welcome to help in all the sub-projects. We use our [roadmap](Roadmap.md) to communicate the high-level features we're currently prioritizing. +Community is welcome to help in all the sub-projects. We use our [roadmap](https://docs.meteor.com/roadmap.html) to communicate the high-level features we're currently prioritizing. Every additional feature adds a maintenance cost in addition to its value. This cost starts with the work of writing the feature or reviewing a community pull @@ -199,7 +199,7 @@ For more information about how to work with Meteor core, take a look at the [Dev ### Proposing your change -You'll have the best chance of getting a change into core if you can build consensus in the community for it or if it is listed in the [roadmap](https://github.com/meteor/meteor/blob/devel/Roadmap.md). Start by creating a well specified Discussion [here](https://github.com/meteor/meteor/discussions). +You'll have the best chance of getting a change into core if you can build consensus in the community for it or if it is listed in the [roadmap](https://docs.meteor.com/roadmap.html). Start by creating a well specified Discussion [here](https://github.com/meteor/meteor/discussions). Help drive discussion and advocate for your feature on the Github ticket (and perhaps the forums). The higher the demand for the feature and the greater the clarity of it's specification will determine the likelihood of a core contributor prioritizing your feature by flagging it with the `ready` label. diff --git a/History.md b/History.md index 996fca7a10..7739f03427 100644 --- a/History.md +++ b/History.md @@ -1015,7 +1015,7 @@ This version should be ignored. Proceed to 2.5.5 above. Simple run `meteor update` in your app. -Great new features and no breaking changes (except one package deprecation). You can always check our [Roadmap](./Roadmap.md) to understand what is next. +Great new features and no breaking changes (except one package deprecation). You can always check our [Roadmap](https://docs.meteor.com/roadmap.html) to understand what is next. ## v1.12.2, 2021-10-12 diff --git a/docs/_config.yml b/docs/_config.yml index 704e93e54a..4f7b834e31 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -29,6 +29,7 @@ sidebar_categories: - index - changelog - install + - roadmap API: - api/core - api/pubsub diff --git a/docs/source/roadmap.md b/docs/source/roadmap.md new file mode 100644 index 0000000000..e893dffca4 --- /dev/null +++ b/docs/source/roadmap.md @@ -0,0 +1,74 @@ +--- +title: Meteor Roadmap +description: Describes the high-level features and actions for the Meteor project in the near-to-medium term future. +--- +# Meteor Roadmap + +**Up to date as of Jan 26, 2022** + +This document describes the high-level features and actions for the Meteor project in the near-to-medium term future. The description of many items include sentences and ideas from Meteor community members. + +As with any roadmap, this is a living document that will evolve as priorities and dependencies shift. + +Contributors are encouraged to focus their efforts on work that aligns with the roadmap then we can work together in these areas. + +If you have new feature requests or ideas you should open a new [discussion](https://github.com/meteor/meteor/discussions/new). + +# Core team + +The items in this section are the core team's priorities. + +## Next minor releases + +- New Core Packages + - Accounts 2FA package; [PR](https://github.com/meteor/meteor/pull/11818) + - Better file upload support; +- TailwindCSS 3.0 JIT Support; [Discussion](https://github.com/meteor/meteor/discussions/11804) +- Tree-shaking; [PR](https://github.com/meteor/meteor/pull/11164) +- Guide review; +- Provide new async APIs where Fibers is required; + - Mongo package with Async API; [PR](https://github.com/meteor/meteor/pull/11605) +- Finish Blaze 2.6; +- ES Modules Support; +- node: Protocol Import Support; + +## Next major releases + +- Support Top-level await; +- Remove Fibers dependency from Meteor Public APIs; +- Make Fibers optional on runtime, or remove it entirely; +- Improve Dev Tools + - Better way to define Meteor public API types; + - Better support for mainstream IDEs; +- Launch new sections for [Meteor University](https://university.meteor.com/); +- ARM Support; +- HTTP/3 Support; +- Change Streams Support; + +# Community + +The items in this section are not the priorities of the core team but the core team can support community members working on it. + +- Finish Vue3 integration; [Basic usage](https://github.com/meteor-vue/meteor-vue3/tree/main/packages/vue3#vuejsvue3) +- Svelte real app examples; +- SolidJS real app examples; +- React Native real app examples; + +# Previous releases +- Support to MongoDB 5.0; [Migration Guide](https://guide.meteor.com/2.6-migration.html) +- Add missing binaries to Fibers fork; [Issue](https://github.com/meteor/meteor/issues/11791) +- [Meteor University launch](https://university.meteor.com/) +- [2FA OTP support in Meteor Accounts](https://forums.meteor.com/t/2fa-otp-support-in-meteor-accounts-meteor-cloud/57248) +- [Meteor + SolidJS demo](https://github.com/edemaine/solid-meteor-demo) +- TypeScript update to v4.4.1 +- Mac M1 Support +- HMR now works on all architectures and legacy browsers +- New core package: accounts-passwordless +- New Meteor NPM installer +- Apollo skeleton upgraded to Apollo server v3 +- [Node.js update](https://docs.meteor.com/changelog.html#v2320210624) to v14 from 12.22.1 +- Cordova update to version 10 +- New Skeleton for Svelte +- Repository with [Meteor Examples](https://github.com/meteor/examples) + +For more completed items, refer to the [project history](https://github.com/meteor/meteor/blob/devel/History.md). From 6b39b0db61bdf6c4ffeffcc51e9c94726de41c53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Tue, 25 Jan 2022 17:19:29 -0400 Subject: [PATCH 183/393] Updating readmes --- Roadmap.md | 171 ----------------------------------------------------- 1 file changed, 171 deletions(-) delete mode 100644 Roadmap.md diff --git a/Roadmap.md b/Roadmap.md deleted file mode 100644 index 05a30e3ce7..0000000000 --- a/Roadmap.md +++ /dev/null @@ -1,171 +0,0 @@ -# Meteor Roadmap - -**Up to date as of Jan 20, 2021** - -This document describes the high-level features and actions for the Meteor project in the near- to medium-term future. This roadmap was built based on community feedback and to improve areas where Meteor is already strong. The description of many items include sentences and ideas from Meteor community members. - -As with any roadmap, this is a living document that will evolve as priorities and dependencies shift; we aim to update the roadmap with any changes or status updates every quarter. - -Contributors are encouraged to focus their efforts on work that aligns with the roadmap then we can work together in these areas. - -PRs to the roadmap are welcome. If you are willing to contribute please open a PR explaining your ideas and what you would be able to do yourself. - -## Priorities for V2 - -Updated at: 2021/01/20 - -V2 initial release (2.0) was delivered today (2021/01/20) with Hot Module Replacement (HMR), React Fast Refresh, and Free deploy including MongoDB on [Cloud](https://www.meteor.com/cloud) and some other features. See all the changes [here](./History.md). - -We expect to have HMR also working for Blaze in Meteor 2.1 in the following weeks (it's currently on [beta](https://github.com/meteor/blaze/pull/313)). - -Other important updates that you should expect to see in Meteor 2.2, 2.3 and so on: -- Node.js 14; [PR](https://github.com/meteor/meteor/pull/11197) -- Cordova 10; [PR](https://github.com/meteor/meteor/pull/11208) -- Remove deprecated code pre v1; [PR](https://github.com/meteor/meteor/pull/11226) -- Tree-shaking; [PR](https://github.com/meteor/meteor/pull/11164) -- Blaze HMR; [PR](https://github.com/meteor/blaze/pull/313) -- Tutorials migration (help needed, understand [here](https://forums.meteor.com/t/new-meteor-react-tutorial-and-new-format-for-tutorials/54074)); - -Do you want to get involved in the items above? Talk to [Filipe Névola](https://twitter.com/filipenevola) in the [community Slack](https://join.slack.com/t/meteor-community/shared_invite/enQtODA0NTU2Nzk5MTA3LWY5NGMxMWRjZDgzYWMyMTEyYTQ3MTcwZmU2YjM5MTY3MjJkZjQ0NWRjOGZlYmIxZjFlYTA5Mjg4OTk3ODRiOTc) - -V2 minor releases are not limited by the items above, these are the ones already in progress, some of them are going to be ready in the next weeks. - -## Priorities for V3 - -Updated at: 2021/01/20 - -Meteor is accelerating! We are going release more often this year. - -We expect to have new features focusing in ease app development in V3 or probably in V2 as well. - -See feature requests and other discussions [here](https://github.com/meteor/meteor/discussions) - -## Recently completed - -### MongoDB shared hosting on Cloud -- Leaders: Meteor Software -- Status: shipped in January 2021 - -You can host your app on Galaxy and use our shared MongoDB for non-commercial/non-production apps. We don't recommend a shared MongoDB instance for production apps. - -### Free deploy on Cloud -- Leaders: Meteor Software -- Status: shipped in January 2021 - -Meteor free deploy is back. - -### Hot Module Replacement -- Leaders: [zodern](https://github.com/zodern) -- Status: shipped in January 2021 -- PRs: https://github.com/meteor/meteor/pull/11117 - -HMR is available since Meteor 2.0 - -### Vue.js -- Leaders: [Brian Mulhall](https://github.com/BrianMulhall) -- Status: shipped in August 2020 -- PRs: https://github.com/meteor/simple-todos-vue - -Tutorial is ready and create command meteor create --vue - -### Apollo -- Leaders: [Jan Dvorak](https://github.com/StorytellerCZ) -- Status: shipped in August 2020 -- PRs: https://github.com/meteor/meteor/pull/11119 - -Apollo skeleton, meteor create --apollo - -### Performance improvements on Windows -- Leaders: [zodern](https://github.com/zodern) -- Status: shipped in August 2020 -- PRs: https://github.com/meteor/meteor/pull/10838 https://github.com/meteor/meteor/pull/11114 https://github.com/meteor/meteor/pull/11115 https://github.com/meteor/meteor/pull/11102 - -Explore ideas to improve performance on Windows such as build in place. - -### Update React Tutorial -- Leaders: [Leonardo Venturini](https://github.com/leonardoventurini) / [Brian Mulhall](https://github.com/BrianMulhall) -- Status: shipped in July 2020 -- PRs: https://github.com/meteor/simple-todos-react - -### React Native -- Leaders: [Nathaniel Dsouza](https://github.com/TheRealNate) -- Status: shipped in June 2020 -- PRs: https://github.com/meteor/guide/pull/1041 https://github.com/meteor/guide/pull/1039 https://github.com/meteor/guide/pull/1035 - -Guide is ready ([check here](https://guide.meteor.com/react-native.html)). - -### Update Blaze Tutorial -- Leaders: [Jan Küster](https://github.com/jankapunkt), [Harry Adel](https://github.com/harryadelb), [Brian Mulhall](https://github.com/BrianMulhall) -- Status: shipped in April 2020 -- PRs: https://github.com/meteor/tutorials/pull/200 https://github.com/meteor/tutorials/pull/199 - -Blaze tutorial should reflect latest best practices. - -### Update MongoDB driver -- Leaders: [Christian Klaussner](https://github.com/klaussner) -- Status: shipped in Meteor 1.10.1 -- PRs: https://github.com/meteor/meteor/pull/10861 / https://github.com/meteor/meteor/pull/10723 - -Update to Mongodb driver from 3.2.7 to 3.5.4, this version is compatible with MongoDB 4.2. - -### Update Cordova to 9 -- Leaders: [Filipe Névola](https://github.com/filipenevola) / [Renan Castro](https://github.com/renanccastro) -- Status: shipped in Meteor 1.10.1 -- PRs: https://github.com/meteor/meteor/pull/10861 / https://github.com/meteor/meteor/pull/10810 / https://github.com/meteor/meteor/pull/10861 - -Update Cordoba lib and its dependencies to latest (version 9) - -### Update to Node.js 12 -- Leaders: [Ben Newman](https://github.com/benjamn) -- Status: shipped in Meteor 1.9. -- PRs: https://github.com/meteor/meteor/pull/10527 - -Since Node.js 12 is scheduled to become the LTS version on October 1st, 2019, Meteor 1.9 will update the Node.js version used by Meteor from 8.16.1 (in Meteor 1.8.2) to 12.10.0 (the most recent current version). - -### Different JS bundles for modern versus legacy browsers - -- Status: shipped in Meteor 1.6.2. -- PRs: https://github.com/meteor/meteor/pull/9439 - -### Eliminate the need for an `imports` directory - -- Status: shipped in Meteor 1.6.2. -- PRs: https://github.com/meteor/meteor/pull/9690, https://github.com/meteor/meteor/pull/9714, https://github.com/meteor/meteor/pull/9715 - -### Make Mongo more optional - -- Status: shipped in Meteor 1.6.2. -- PRs: https://github.com/meteor/meteor/pull/8999 - -### Upgrade to Node 8 - -- Status: shipped in Meteor 1.6. -- PRs: https://github.com/meteor/meteor/pull/8728 - -### Upgrade to npm 5 - -- Status: shipped in Meteor 1.6 - -### Dynamic `import(...)` - -- Status: shipped in Meteor 1.5 - -### Rebuild performance improvements - -- Status: shipped in Meteor 1.4.2 - -### MongoDB updates - -- Status: shipped in Meteor 1.4 - -### Support for Node 4 and beyond - -- Status: shipped in Meteor 1.4 - -### View Layer - -- Status: Blaze split into new repository and can be published independently as of 1.4.2 - -### Other - -For more completed items, refer to the project history here: https://github.com/meteor/meteor/blob/devel/History.md From 77f4da17fdb5948cb27fe2ce4ffd24f7d4304d46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Tue, 25 Jan 2022 17:21:42 -0400 Subject: [PATCH 184/393] Updating readmes --- docs/source/roadmap.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/source/roadmap.md b/docs/source/roadmap.md index e893dffca4..8eb74f1e47 100644 --- a/docs/source/roadmap.md +++ b/docs/source/roadmap.md @@ -1,8 +1,9 @@ --- -title: Meteor Roadmap +title: Roadmap description: Describes the high-level features and actions for the Meteor project in the near-to-medium term future. --- -# Meteor Roadmap + +## Introduction **Up to date as of Jan 26, 2022** @@ -14,11 +15,11 @@ Contributors are encouraged to focus their efforts on work that aligns with the If you have new feature requests or ideas you should open a new [discussion](https://github.com/meteor/meteor/discussions/new). -# Core team +## Core team The items in this section are the core team's priorities. -## Next minor releases +### Next minor releases - New Core Packages - Accounts 2FA package; [PR](https://github.com/meteor/meteor/pull/11818) @@ -32,7 +33,7 @@ The items in this section are the core team's priorities. - ES Modules Support; - node: Protocol Import Support; -## Next major releases +### Next major releases - Support Top-level await; - Remove Fibers dependency from Meteor Public APIs; @@ -45,7 +46,7 @@ The items in this section are the core team's priorities. - HTTP/3 Support; - Change Streams Support; -# Community +## Community The items in this section are not the priorities of the core team but the core team can support community members working on it. @@ -54,7 +55,7 @@ The items in this section are not the priorities of the core team but the core t - SolidJS real app examples; - React Native real app examples; -# Previous releases +## Previous releases - Support to MongoDB 5.0; [Migration Guide](https://guide.meteor.com/2.6-migration.html) - Add missing binaries to Fibers fork; [Issue](https://github.com/meteor/meteor/issues/11791) - [Meteor University launch](https://university.meteor.com/) From b1ac191fc4f4906dc927cd22b1940c217fdad35e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Tue, 25 Jan 2022 17:22:26 -0400 Subject: [PATCH 185/393] Updating readmes --- docs/source/changelog.md | 2 +- docs/source/index.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/changelog.md b/docs/source/changelog.md index 7fa86ac6de..62f564f8fa 100644 --- a/docs/source/changelog.md +++ b/docs/source/changelog.md @@ -1,5 +1,5 @@ --- -title: Meteor Changelog +title: Changelog --- {%- changelog '../History.md' %} diff --git a/docs/source/index.md b/docs/source/index.md index ad257ef281..e3d016c0e9 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -1,5 +1,5 @@ --- -title: Meteor API Docs +title: Docs --- From 68ad8c10651b3f9130ff6692e159865513be4760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Tue, 25 Jan 2022 17:24:48 -0400 Subject: [PATCH 186/393] Updating readmes --- docs/source/roadmap.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/roadmap.md b/docs/source/roadmap.md index 8eb74f1e47..76c5c72ee4 100644 --- a/docs/source/roadmap.md +++ b/docs/source/roadmap.md @@ -72,4 +72,4 @@ The items in this section are not the priorities of the core team but the core t - New Skeleton for Svelte - Repository with [Meteor Examples](https://github.com/meteor/examples) -For more completed items, refer to the [project history](https://github.com/meteor/meteor/blob/devel/History.md). +For more completed items, refer to our [changelog](https://docs.meteor.com/changelog.html). From da35e0ba58f8e3da6190c71f36de98e68fb4ba46 Mon Sep 17 00:00:00 2001 From: Renan Castro Date: Tue, 25 Jan 2022 18:34:53 -0300 Subject: [PATCH 187/393] Update 2.6-migration.md --- guide/source/2.6-migration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/source/2.6-migration.md b/guide/source/2.6-migration.md index 3565ed8e29..fc5cb8cf88 100644 --- a/guide/source/2.6-migration.md +++ b/guide/source/2.6-migration.md @@ -64,7 +64,7 @@ For example, the MongoDB Oplog suffered a lot of changes, and we had to create a As MongoDB also removed a few deprecated methods that were used by Meteor we recommend testing all important operations of your application. -

Note for Package Maintainers

+#### Note for Package Maintainers If you depend on any method inside the rawCollection() object, you have to review every call with the new driver API [here](https://mongodb.github.io/node-mongodb-native/4.3/). From 82745790ea59be8766e8d96ae5774e9ace373cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Wed, 26 Jan 2022 19:23:16 -0400 Subject: [PATCH 188/393] Moving warning about _ensureIndex only _ensureIndex is really used internally --- packages/mongo/collection.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mongo/collection.js b/packages/mongo/collection.js index 44c1fdfcad..0ec2698281 100644 --- a/packages/mongo/collection.js +++ b/packages/mongo/collection.js @@ -739,11 +739,11 @@ Object.assign(Mongo.Collection.prototype, { var self = this; if (!self._collection._ensureIndex || !self._collection.createIndex) throw new Error('Can only call createIndex on server collections'); - import { Log } from 'meteor/logging'; - Log.debug(`_ensureIndex has been deprecated, please use the new 'createIndex' instead${options?.name ? `, index name: ${options.name}` : `, index: ${JSON.stringify(index)}`}`) if (self._collection.createIndex) { self._collection.createIndex(index, options); } else { + import { Log } from 'meteor/logging'; + Log.debug(`_ensureIndex has been deprecated, please use the new 'createIndex' instead${options?.name ? `, index name: ${options.name}` : `, index: ${JSON.stringify(index)}`}`) self._collection._ensureIndex(index, options); } }, From 9db1dfa31a46d34f4920ae550e0854e6ab8162dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Thu, 27 Jan 2022 09:24:58 -0400 Subject: [PATCH 189/393] Improves history and migration guide for 2.6 --- History.md | 42 +++++++++++++---------------------- guide/source/2.6-migration.md | 40 +++++++++++++++++++++++---------- packages/npm-mongo/package.js | 4 ++-- 3 files changed, 45 insertions(+), 41 deletions(-) diff --git a/History.md b/History.md index e1caaaa7e3..2e2bcb518f 100644 --- a/History.md +++ b/History.md @@ -1,32 +1,19 @@ -## vNEXT, UNRELEASED - -#### Highlights -* You are now able to use dark theme specific splash screens for both iOS and Android by passing an object `{src: 'light-image-src-here.png', srcDarkMode: 'dark-mode-src-here.png'}` to the corresponding key in `App.launchScreens` -#### Breaking Changes - -* Legacy launch screens keys for iOS on `App.launchScreens` are now deprecated in favor of new storyboard compliant keys [PR #11797](https://github.com/meteor/meteor/pull/11797). - - This will drop the following keys we have: `['iphone5','iphone6','iphone6p_portrait','iphone6p_landscape','iphoneX_portrait','iphoneX_landscape','ipad_portrait_2x','ipad_landscape_2x','iphone','iphone_2x','ipad_portrait','ipad_landscape']` - -#### Migration Steps -* Replace the deprecated keys `['iphone5','iphone6','iphone6p_portrait','iphone6p_landscape','iphoneX_portrait','iphoneX_landscape','ipad_portrait_2x','ipad_landscape_2x','iphone','iphone_2x','ipad_portrait','ipad_landscape']` with the -corresponding new key: `['ios_universal','ios_universal_3x','Default@2x~universal~comany','Default@2x~universal~comcom','Default@3x~universal~anycom','Default@3x~universal~comany','Default@2x~iphone~anyany','Default@2x~iphone~comany','Default@2x~iphone~comcom','Default@3x~iphone~anyany','Default@3x~iphone~anycom','Default@3x~iphone~comany','Default@2x~ipad~anyany','Default@2x~ipad~comany']` -and adapt necessary splash images to the new dimensions asked by Apple. You can get more info [here](https://docs.meteor.com/api/mobile-config.html#App-launchScreens). - -#### Meteor Version Release - -#### Independent Releases - -## v2.6-beta.0, UNRELEASED +## v2.6, UNRELEASED #### Highlights -* MongoDB Node.js driver Upgrade to support MongoDB 5.x +* MongoDB Node.js driver Upgrade from 3.6.10 to 4.3.1 +* MongoDB Server 5.x Support * Embedded Mongo now uses MongoDB 5.0.5 +* You are now able to use dark theme specific splash screens for both iOS and Android by passing an object `{src: 'light-image-src-here.png', srcDarkMode: 'dark-mode-src-here.png'}` to the corresponding key in `App.launchScreens` #### Breaking Changes * `mongo@1.14.0` - - This is not a breaking change in Meteor itself but internal result of operations inside Node.js MongoDB driver have changed. If you are depending on rawCollection results (not only the effect inside the DB), please review the expected format as we have done [here](https://github.com/meteor/meteor/blob/155ae639ee590bae66237fc1c29295072ec92aef/packages/mongo/mongo_driver.js#L658) + - This is not a breaking change in Meteor itself but internal result of operations inside Node.js MongoDB driver have changed. If you are depending on rawCollection, read the [Migration Guide](https://guide.meteor.com/2.6-migration.html) for more details. + +* `meteor-tool@2.6` + - Legacy launch screens keys for iOS on `App.launchScreens` are now deprecated in favor of new storyboard compliant keys [PR #11797](https://github.com/meteor/meteor/pull/11797). This will drop the following keys we have: `['iphone5','iphone6','iphone6p_portrait','iphone6p_landscape','iphoneX_portrait','iphoneX_landscape','ipad_portrait_2x','ipad_landscape_2x','iphone','iphone_2x','ipad_portrait','ipad_landscape']`. Read the [Migration Guide](https://guide.meteor.com/2.6-migration.html) for more details. #### Migration Steps @@ -48,19 +35,20 @@ Read our [Migration Guide](https://guide.meteor.com/2.6-migration.html) for this - _synchronousCursor._dbCursor.operation is not an option anymore in the raw cursor from nodejs mongodb driver. If you want to access the options, use _synchronousCursor._dbCursor.(GETTERS) - for example, _synchronousCursor._dbCursor.readPreference. * `allow-deny@1.1.1` - - TODO + - Handle `MongoBulkWriteError` as `BulkWriteError` was already handled. * `meteor-tool@2.6.0` - - TODO + - Cordova changes to support new Launch Screens. + - Mongo changes to support new embedded version, 5.0.5. * `minimongo@1.8.0` - - TODO + - Changes to keep everything compatible with MongoDB Server 5.x and MongoDB Node.js driver 4.x. -* `npm-mongo@4.3.0` - - Upgraded MongoDB Node.js driver to 4.3.0 +* `npm-mongo@4.3.1` + - Upgraded MongoDB Node.js driver to 4.3.1 * `tinytest@1.2.1` - - TODO + - Custom message support for `throws` #### Independent Releases diff --git a/guide/source/2.6-migration.md b/guide/source/2.6-migration.md index fc5cb8cf88..556961ee39 100644 --- a/guide/source/2.6-migration.md +++ b/guide/source/2.6-migration.md @@ -7,19 +7,44 @@ Most of the new features in Meteor 2.6 are either applied directly behind the sc The above being said, there are a few items that you should implement to have easier time in the future. +

Cordova - Launch Screens for iOS

+ +You are now able to use dark theme specific splash screens for both iOS and Android by passing an object `{src: 'light-image-src-here.png', srcDarkMode: 'dark-mode-src-here.png'}` to the corresponding key in `App.launchScreens` + +Legacy launch screens keys for iOS on `App.launchScreens` are now deprecated in favor of new storyboard compliant keys [PR #11797](https://github.com/meteor/meteor/pull/11797). This will drop the following keys we have: `['iphone5','iphone6','iphone6p_portrait','iphone6p_landscape','iphoneX_portrait','iphoneX_landscape','ipad_portrait_2x','ipad_landscape_2x','iphone','iphone_2x','ipad_portrait','ipad_landscape']` + +To migrate replace the deprecated keys `['iphone5','iphone6','iphone6p_portrait','iphone6p_landscape','iphoneX_portrait','iphoneX_landscape','ipad_portrait_2x','ipad_landscape_2x','iphone','iphone_2x','ipad_portrait','ipad_landscape']` with the + corresponding new key: `['ios_universal','ios_universal_3x','Default@2x~universal~comany','Default@2x~universal~comcom','Default@3x~universal~anycom','Default@3x~universal~comany','Default@2x~iphone~anyany','Default@2x~iphone~comany','Default@2x~iphone~comcom','Default@3x~iphone~anyany','Default@3x~iphone~anycom','Default@3x~iphone~comany','Default@2x~ipad~anyany','Default@2x~ipad~comany']` + and adapt necessary splash images to the new dimensions asked by Apple. You can get more info [here](https://docs.meteor.com/api/mobile-config.html#App-launchScreens). +

MongoDB 5.0

#### Introduction -Meteor before 2.6 was supporting MongoDB 4.x, starting from this version we've upgraded to MongoDB Node.js driver from version 3.6 to 4.3.0 which supports MongoDB 5.0. +Meteor before 2.6 was supporting MongoDB Server 4.x, starting from this version we've upgraded to MongoDB Node.js driver from version 3.6 to 4.3.1 which supports MongoDB Server 5.x. -This change was necessary at the time of writing this guide (January 2022) as MongoDB Atlas is going to migrate automatically all the clusters in the plans Atlas M0 (Free Cluster), M2, and M5 to MongoDB 5.0 in February 2022. +This change was necessary at the time of writing this guide (January 2022) as MongoDB Atlas is going to migrate automatically all the clusters in the plans Atlas M0 (Free Cluster), M2, and M5 to MongoDB 5.0 in February 2022, but this change would be necessary anyway as this is now the latest version of MongoDB Server. The migration in the M0, M2 and M5 is just a sign from MongoDB that they believe MongoDB 5.0 should be the version used by everybody as soon as possible. -If you are running on MongoDB Atlas and in one of these plans you have to run Meteor 2.6 in order to connect and interact with your MongoDB properly as your MongoDB is going to be upgraded to 5.0 in February. If you are not running at these plans, you can continue to use your MongoDB at previous versions and you can use previous versions of Meteor still without any problems. +If you are running on MongoDB Atlas and in one of these plans you have to run Meteor 2.6 in order to connect and interact with your MongoDB properly as your MongoDB is going to be upgraded to 5.0 in February. If you are not running at these plans, you can continue to use your MongoDB at previous versions and you can use previous versions of Meteor still without any problems. + +An important note is that we have migrated everything supported by Meteor to be compatible with MongoDB 5.x and also MongoDB Node.js Driver 4.x but this doesn't include, as you should expect, what you do in your code or package using `rawCollection`. `rawCollection` is a way for Meteor to provide you the freedom to interact with MongoDB driver but that also comes with the responsibility to keep your code up-to-date with the version of the driver used by Meteor. That said, we encourage everybody to run the latest version of Meteor as soon as possible as you can benefit from a new MongoDB driver and also other features that we are always adding to Meteor. This version of Meteor is also compatible with previous version of MongoDB server, so you can continue using the latest Meteor without any issues even if you are not running MongoDB 5.x yet. You can check [here](https://docs.mongodb.com/drivers/node/current/compatibility/) which versions of MongoDB server the Node.js driver in the version 4.3.0 supports and as a consequence these are the versions of MongoDB server supported by Meteor 2.6 as well. In short, Meteor 2.6 supports these versions of MongoDB server: 5.1, 5.0, 4.4, 4.2, 4.0, 3.6. +#### Embedded MongoDB + +If you are using Embedded MongoDB in your local environment you should run `meteor reset` in order to have your database working properly after this upgrade. `meteor reset` is going to remove all the data in your local database. + +#### rawCollection users + +If you depend on any method inside the rawCollection() object, you have to review every call with the new driver API [here](https://mongodb.github.io/node-mongodb-native/4.3/). Also make sure you check all the changes made to the driver [here](https://docs.mongodb.com/drivers/node/current/whats-new/) and [here](https://github.com/mongodb/node-mongodb-native/blob/4.0/docs/CHANGES_4.0.0.md). + +You can check an example applied to the "aggregate" function, which had changes to its API. The collection.rawCollection().aggregate() function doesn't expect a callback any more like in older versions, and aggregate().toArray() now returns a promise. +For this specific case, we have written the fix in this [PR](https://github.com/sakulstra/meteor-aggregate/pull/8). + +If you are a user looking for errors that are showing in a custom package, please open an issue in the package owner repository so the maintainer can make the due changes. + #### Changes Here is a list of the changes that we have made to Meteor core packages in order to make it compatible with MongoDB Node.js Driver 4.3.x, most of them are not going to affect you but we recommend that you test your application well before upgrading to the latest version of Meteor as we have made many changes on how Meteor interact with MongoDB. @@ -64,15 +89,6 @@ For example, the MongoDB Oplog suffered a lot of changes, and we had to create a As MongoDB also removed a few deprecated methods that were used by Meteor we recommend testing all important operations of your application. -#### Note for Package Maintainers - -If you depend on any method inside the rawCollection() object, you have to review every call with the new driver API [here](https://mongodb.github.io/node-mongodb-native/4.3/). - -You can check an example applied to the "aggregate" function, which had changes to its API. The collection.rawCollection().aggregate() function doesn't expect a callback any more like in older versions, and aggregate().toArray() now returns a promise. -For this specific case, we have written in this [PR](https://github.com/sakulstra/meteor-aggregate/pull/8). - -If you are a user looking for errors that are showing in a custom package, please open an issue in the package owner repository so the maintainer can make the due changes. -

Migrating from a version older than 2.5?

If you're migrating from a version of Meteor older than Meteor 2.5, there may be important considerations not listed in this guide (which specifically covers 2.4 to 2.5). Please review the older migration guides for details: diff --git a/packages/npm-mongo/package.js b/packages/npm-mongo/package.js index 7f87c854f2..efe0ab0d5b 100644 --- a/packages/npm-mongo/package.js +++ b/packages/npm-mongo/package.js @@ -3,12 +3,12 @@ Package.describe({ summary: "Wrapper around the mongo npm package", - version: "4.3.0-beta260.4", + version: "4.3.1-beta260.4", documentation: null }); Npm.depends({ - mongodb: "4.3.0" + mongodb: "4.3.1" }); Package.onUse(function (api) { From b500d3803e0467b514dd0ed990eeb6bfcfbf8df5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Thu, 27 Jan 2022 09:33:40 -0400 Subject: [PATCH 190/393] Improves history and migration guide for 2.6 --- History.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/History.md b/History.md index 2e2bcb518f..cf56db7475 100644 --- a/History.md +++ b/History.md @@ -10,7 +10,7 @@ #### Breaking Changes * `mongo@1.14.0` - - This is not a breaking change in Meteor itself but internal result of operations inside Node.js MongoDB driver have changed. If you are depending on rawCollection, read the [Migration Guide](https://guide.meteor.com/2.6-migration.html) for more details. + - This is not a breaking change in Meteor itself but if you are depending on rawCollection, read the [Migration Guide](https://guide.meteor.com/2.6-migration.html) for more details. * `meteor-tool@2.6` - Legacy launch screens keys for iOS on `App.launchScreens` are now deprecated in favor of new storyboard compliant keys [PR #11797](https://github.com/meteor/meteor/pull/11797). This will drop the following keys we have: `['iphone5','iphone6','iphone6p_portrait','iphone6p_landscape','iphoneX_portrait','iphoneX_landscape','ipad_portrait_2x','ipad_landscape_2x','iphone','iphone_2x','ipad_portrait','ipad_landscape']`. Read the [Migration Guide](https://guide.meteor.com/2.6-migration.html) for more details. From db6f653fb26b986996b615bbbd041dfce5674f5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Thu, 27 Jan 2022 09:35:42 -0400 Subject: [PATCH 191/393] Improves history and migration guide for 2.6 --- History.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/History.md b/History.md index cf56db7475..8d84e9c907 100644 --- a/History.md +++ b/History.md @@ -10,7 +10,7 @@ #### Breaking Changes * `mongo@1.14.0` - - This is not a breaking change in Meteor itself but if you are depending on rawCollection, read the [Migration Guide](https://guide.meteor.com/2.6-migration.html) for more details. + - This is not a breaking change in Meteor itself but as this is a major upgrade in the MongoDB Node.js driver you should read the [Migration Guide](https://guide.meteor.com/2.6-migration.html), especially if you are using rawCollection. * `meteor-tool@2.6` - Legacy launch screens keys for iOS on `App.launchScreens` are now deprecated in favor of new storyboard compliant keys [PR #11797](https://github.com/meteor/meteor/pull/11797). This will drop the following keys we have: `['iphone5','iphone6','iphone6p_portrait','iphone6p_landscape','iphoneX_portrait','iphoneX_landscape','ipad_portrait_2x','ipad_landscape_2x','iphone','iphone_2x','ipad_portrait','ipad_landscape']`. Read the [Migration Guide](https://guide.meteor.com/2.6-migration.html) for more details. From 8843535527d03f72a457204f37dbe21cf12f5932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Thu, 27 Jan 2022 10:42:06 -0400 Subject: [PATCH 192/393] Bump Meteor version to 2.6-rc.0 --- packages/allow-deny/package.js | 2 +- packages/meteor-tool/package.js | 2 +- packages/minimongo/package.js | 2 +- packages/mongo/package.js | 2 +- packages/npm-mongo/package.js | 2 +- packages/tinytest/package.js | 2 +- scripts/admin/meteor-release-experimental.json | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/allow-deny/package.js b/packages/allow-deny/package.js index fb1dff8242..3245a986e6 100644 --- a/packages/allow-deny/package.js +++ b/packages/allow-deny/package.js @@ -1,6 +1,6 @@ Package.describe({ name: 'allow-deny', - version: '1.1.1-beta260.4', + version: '1.1.1-rc260.0', // 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. diff --git a/packages/meteor-tool/package.js b/packages/meteor-tool/package.js index 9e943397c6..7641d59413 100644 --- a/packages/meteor-tool/package.js +++ b/packages/meteor-tool/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: 'The Meteor command-line tool', - version: '2.6.0-beta.4', + version: '2.6.0-rc.0', }); Package.includeTool(); diff --git a/packages/minimongo/package.js b/packages/minimongo/package.js index 2706de45bd..7c2cabbdf1 100644 --- a/packages/minimongo/package.js +++ b/packages/minimongo/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Meteor's client-side datastore: a port of MongoDB to Javascript", - version: '1.8.0-beta260.4' + version: '1.8.0-rc260.0' }); Package.onUse(api => { diff --git a/packages/mongo/package.js b/packages/mongo/package.js index 6161fd3f41..0912b1a5b0 100644 --- a/packages/mongo/package.js +++ b/packages/mongo/package.js @@ -9,7 +9,7 @@ Package.describe({ summary: "Adaptor for using MongoDB and Minimongo over DDP", - version: '1.14.0-beta260.4' + version: '1.14.0-rc260.0' }); Npm.depends({ diff --git a/packages/npm-mongo/package.js b/packages/npm-mongo/package.js index efe0ab0d5b..510b495a01 100644 --- a/packages/npm-mongo/package.js +++ b/packages/npm-mongo/package.js @@ -3,7 +3,7 @@ Package.describe({ summary: "Wrapper around the mongo npm package", - version: "4.3.1-beta260.4", + version: "4.3.1-rc260.0", documentation: null }); diff --git a/packages/tinytest/package.js b/packages/tinytest/package.js index 706633b600..b73da5b4db 100644 --- a/packages/tinytest/package.js +++ b/packages/tinytest/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Tiny testing framework", - version: '1.2.1-beta260.4' + version: '1.2.1-rc260.0' }); Package.onUse(function (api) { diff --git a/scripts/admin/meteor-release-experimental.json b/scripts/admin/meteor-release-experimental.json index 71e67a1080..056e8359b9 100644 --- a/scripts/admin/meteor-release-experimental.json +++ b/scripts/admin/meteor-release-experimental.json @@ -1,6 +1,6 @@ { "track": "METEOR", - "version": "2.6-beta.4", + "version": "2.6-rc.0", "recommended": false, "official": false, "description": "Meteor experimental release" From a5a82619b82df03dfa74a92ac27f6d511ead2b3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Thu, 27 Jan 2022 10:54:15 -0400 Subject: [PATCH 193/393] Update npm-mongo npm-shrinkwrap.json --- packages/npm-mongo/.npm/package/npm-shrinkwrap.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/npm-mongo/.npm/package/npm-shrinkwrap.json b/packages/npm-mongo/.npm/package/npm-shrinkwrap.json index 9768941e87..a5207a4edf 100644 --- a/packages/npm-mongo/.npm/package/npm-shrinkwrap.json +++ b/packages/npm-mongo/.npm/package/npm-shrinkwrap.json @@ -2,9 +2,9 @@ "lockfileVersion": 1, "dependencies": { "@types/node": { - "version": "17.0.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz", - "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==" + "version": "17.0.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.12.tgz", + "integrity": "sha512-4YpbAsnJXWYK/fpTVFlMIcUIho2AYCi4wg5aNPrG1ng7fn/1/RZfCIpRCiBX+12RVa34RluilnvCqD+g3KiSiA==" }, "@types/webidl-conversions": { "version": "6.1.1", @@ -52,9 +52,9 @@ "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" }, "mongodb": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.3.0.tgz", - "integrity": "sha512-ovq9ZD9wEvab+LsaQOiwtne1Sy2egaHW8K/H5M18Tv+V5PgTRi+qdmxDGlbm94TSL3h56m6amstptu115Nzgow==" + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.3.1.tgz", + "integrity": "sha512-sNa8APSIk+r4x31ZwctKjuPSaeKuvUeNb/fu/3B6dRM02HpEgig7hTHM8A/PJQTlxuC/KFWlDlQjhsk/S43tBg==" }, "mongodb-connection-string-url": { "version": "2.4.1", From 08135fbbe117f4a9bc2a06022d55d19f90a4b016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Thu, 27 Jan 2022 11:42:55 -0400 Subject: [PATCH 194/393] Trigger docs update --- docs/source/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/index.md b/docs/source/index.md index ad257ef281..b8c660f282 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -34,4 +34,4 @@ Meteor is a full-stack JavaScript platform for developing modern web and mobile {% oldRedirects %} - + From eeb3da2e5be80decefd91ed5f668f4658b3ee8dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Thu, 27 Jan 2022 11:49:13 -0400 Subject: [PATCH 195/393] Update Installer --- npm-packages/meteor-installer/README.md | 1 + npm-packages/meteor-installer/config.js | 2 +- npm-packages/meteor-installer/package.json | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/npm-packages/meteor-installer/README.md b/npm-packages/meteor-installer/README.md index b46c682037..f27165a204 100644 --- a/npm-packages/meteor-installer/README.md +++ b/npm-packages/meteor-installer/README.md @@ -28,6 +28,7 @@ npm install -g meteor | 2.5.4 | 2.5.3 | | 2.5.5 | 2.5.4 | | 2.5.6 | 2.5.5 | +| 2.5.7 | 2.5.6 | ### Important note diff --git a/npm-packages/meteor-installer/config.js b/npm-packages/meteor-installer/config.js index 49d2130ad2..d34e9237bd 100644 --- a/npm-packages/meteor-installer/config.js +++ b/npm-packages/meteor-installer/config.js @@ -1,7 +1,7 @@ const path = require('path'); const os = require('os'); -const METEOR_LATEST_VERSION = '2.5.5'; +const METEOR_LATEST_VERSION = '2.5.6'; const sudoUser = process.env.SUDO_USER || ''; function isRoot() { return process.getuid && process.getuid() === 0; diff --git a/npm-packages/meteor-installer/package.json b/npm-packages/meteor-installer/package.json index 4878e07e99..b091152b27 100644 --- a/npm-packages/meteor-installer/package.json +++ b/npm-packages/meteor-installer/package.json @@ -1,6 +1,6 @@ { "name": "meteor", - "version": "2.5.6", + "version": "2.5.7", "description": "Install Meteor", "main": "install.js", "scripts": { From 2035192f18da259a5db298f6759e2e87c63287b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Thu, 27 Jan 2022 13:47:18 -0400 Subject: [PATCH 196/393] Improves history and migration guide for 2.6 --- docs/source/roadmap.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/source/roadmap.md b/docs/source/roadmap.md index 76c5c72ee4..fd19ba5e07 100644 --- a/docs/source/roadmap.md +++ b/docs/source/roadmap.md @@ -32,6 +32,7 @@ The items in this section are the core team's priorities. - Finish Blaze 2.6; - ES Modules Support; - node: Protocol Import Support; +- Explore new ways to expose Meteor type definitions; ### Next major releases @@ -44,7 +45,9 @@ The items in this section are the core team's priorities. - Launch new sections for [Meteor University](https://university.meteor.com/); - ARM Support; - HTTP/3 Support; -- Change Streams Support; +- MongoDB Change Streams Support; +- Explore bringing Redis-oplog to core; +- Explore Flutter as a front-end for Meteor apps; ## Community From 1f156fd7119874ce63b956b3d9f38e4c913f0034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20N=C3=A9vola?= Date: Thu, 27 Jan 2022 13:53:22 -0400 Subject: [PATCH 197/393] Improves history and migration guide for 2.6 --- History.md | 8990 +----------------------------- docs/source/changelog.md | 8990 +++++++++++++++++++++++++++++- guide/source/cordova.md | 2 +- packages/modern-browsers/TODO.md | 52 - 4 files changed, 8993 insertions(+), 9041 deletions(-) delete mode 100644 packages/modern-browsers/TODO.md diff --git a/History.md b/History.md index 8d84e9c907..7ee09cfb3d 100644 --- a/History.md +++ b/History.md @@ -1,8989 +1,5 @@ -## v2.6, UNRELEASED +# History -#### Highlights +This content was moved to [Changelog](https://docs.meteor.com/changelog.html). -* MongoDB Node.js driver Upgrade from 3.6.10 to 4.3.1 -* MongoDB Server 5.x Support -* Embedded Mongo now uses MongoDB 5.0.5 -* You are now able to use dark theme specific splash screens for both iOS and Android by passing an object `{src: 'light-image-src-here.png', srcDarkMode: 'dark-mode-src-here.png'}` to the corresponding key in `App.launchScreens` - -#### Breaking Changes - -* `mongo@1.14.0` - - This is not a breaking change in Meteor itself but as this is a major upgrade in the MongoDB Node.js driver you should read the [Migration Guide](https://guide.meteor.com/2.6-migration.html), especially if you are using rawCollection. - -* `meteor-tool@2.6` - - Legacy launch screens keys for iOS on `App.launchScreens` are now deprecated in favor of new storyboard compliant keys [PR #11797](https://github.com/meteor/meteor/pull/11797). This will drop the following keys we have: `['iphone5','iphone6','iphone6p_portrait','iphone6p_landscape','iphoneX_portrait','iphoneX_landscape','ipad_portrait_2x','ipad_landscape_2x','iphone','iphone_2x','ipad_portrait','ipad_landscape']`. Read the [Migration Guide](https://guide.meteor.com/2.6-migration.html) for more details. - -#### Migration Steps - -Read our [Migration Guide](https://guide.meteor.com/2.6-migration.html) for this version. - -#### Meteor Version Release - -* `mongo@1.14.0` - - internal result of operations inside Node.js MongoDB driver have changed. If you are depending on rawCollection results (not only the effect inside the DB), please review the expected format as we have done [here](https://github.com/meteor/meteor/blob/155ae639ee590bae66237fc1c29295072ec92aef/packages/mongo/mongo_driver.js#L658) - - useUnifiedTopology is not an option anymore, it defaults to true. - - native parser is not an option anymore, it defaults to false in the mongo connection. - - poolSize not an option anymore, we are using max/minPoolSize for the same behavior on mongo connection. - - fields option is deprecated, we are maintaining a translation layer to "projection" field (now prefered) until the next minor version, where we will start showing alerts. - - _ensureIndex is now showing a deprecation message - - applySkipLimit option for count() on find cursors is no longer supported. - - we are maintaining a translation layer for the new oplog format, so if you read or rely on any behavior of it please read our oplog_v2_converter.js code - - update/insert/remove behavior is maintained in the Meteor way, documented in our docs, but we are now using replaceOne/updateOne/updateMany internally. This is subject to changes in the API rewrite of MongoDB without Fibers AND if you are using rawCollection directly you have to review your methods otherwise you will see deprecation messages if you are still using the old mongodb style directly. - - waitForStepDownOnNonCommandShutdown=false is not needed anymore when spawning the mongodb process - - _synchronousCursor._dbCursor.operation is not an option anymore in the raw cursor from nodejs mongodb driver. If you want to access the options, use _synchronousCursor._dbCursor.(GETTERS) - for example, _synchronousCursor._dbCursor.readPreference. - -* `allow-deny@1.1.1` - - Handle `MongoBulkWriteError` as `BulkWriteError` was already handled. - -* `meteor-tool@2.6.0` - - Cordova changes to support new Launch Screens. - - Mongo changes to support new embedded version, 5.0.5. - -* `minimongo@1.8.0` - - Changes to keep everything compatible with MongoDB Server 5.x and MongoDB Node.js driver 4.x. - -* `npm-mongo@4.3.1` - - Upgraded MongoDB Node.js driver to 4.3.1 - -* `tinytest@1.2.1` - - Custom message support for `throws` - -#### Independent Releases - -## v2.5.6, 2022-01-25 - -#### Highlights - -* Go back to using node-fibers mainline dependency instead of a fork. Also ships fibers binaries. - -#### Breaking Changes - -- N/A - -#### Migration Steps - -- N/A - -#### Meteor Version Release - -* `meteor-tool@2.5.6` - - Go back to using node-fibers mainline dependency instead of a fork. Also ships fibers binaries. - -## v2.5.5, 2022-01-18 - -#### Highlights - -* Bump node version to 14.18.3 - security patch -* Change the tar implementation for streams, used on deploying and unpacking packages. Reduced "upload bundle" time when deploying is expected. - -#### Breaking Changes - -- N/A - -#### Migration Steps - -- N/A - -#### Meteor Version Release - -* `meteor-tool@2.5.5` - - Bump node version to 14.18.3 - security patch - - Change the tar implementation for streams, used on deploying and unpacking packages. Reduced "upload bundle" time when deploying is expected. - -* `accounts-base@2.2.1` - - Fixes onLogin firing twice. [PR](https://github.com/meteor/meteor/pull/11785) and [Issue](https://github.com/meteor/meteor/issues/10853) - -#### Independent Releases - -* `oauth@2.1.1` - - Fixes end of redirect response for oauth inside iframes. [PR](https://github.com/meteor/meteor/pull/11825) and [Issue](https://github.com/meteor/meteor/issues/11817) - -## v2.5.4, 2022-01-14 - -This version should be ignored. Proceed to 2.5.5 above. - -## v2.5.3, 2022-01-04 - -#### Highlights - -* Fixes invalid package.json error with `resolve` - -#### Breaking Changes - -- N/A - -#### Migration Steps - -- N/A - -#### Meteor Version Release - -* `meteor-tool@2.5.3` - - Fixes invalid package.json files breaking Meteor run. [PR](https://github.com/meteor/meteor/pull/11832) and [Issue](https://github.com/meteor/meteor/issues/11830) - -#### Independent Releases - -## v2.5.2, 2021-12-21 - -#### Highlights - -* Reify performance improvements -* Node.js update to 14.18.2 -* HMR Fixes - -#### Breaking Changes - -#### Migration Steps - -- N/A - -#### Meteor Version Release - -* `meteor-tool@2.5.2` - - Changes @meteorjs/babel and @meteorjs/reify to improve Reify performance. - - Upgrades Node.js to 14.18.2 - - Fixes isopacket [load failure](https://github.com/meteor/meteor/issues/10930) on Windows. [PR](https://github.com/meteor/meteor/pull/11740) - -* `hot-module-replacement@0.5.0` - - Prevents hot.accept from overriding hot.decline. [PR](https://github.com/meteor/meteor/pull/11801) - - Fixes falling back to hot code push on web archs. [PR](https://github.com/meteor/meteor/pull/11795) - -* `@meteorjs/babel@7.15.0` - - Updates @meteorjs/reify to improve Reify performance. - -* `@meteorjs/reify@0.23.0` - - Uses `@meteorjs/reify` instead of `reify` - - Check scope when wrapping to fix slowness in MUI v5. [PR](https://github.com/meteor/reify/pull/1) and [Issue](https://github.com/benjamn/reify/issues/277). - -* `standard-minifier-js@2.8.0` - - Bump to apply improvements from Reify - -* `typescript@4.4.1` - - Bump to apply improvements from Reify - -* `babel-compiler@7.8.0` - - Bump to apply improvements from Reify - -* `ecmascript@0.16.1` - - Bump to apply improvements from Reify - -* `modules@0.18.0` - - Bump to apply improvements from Reify - -#### Independent Releases - -* `react-fast-refresh@0.2.2` - - [Fixes](https://github.com/meteor/meteor/issues/11744) bugs. [PR](https://github.com/meteor/meteor/pull/11794/) - -* `accounts-ui@1.4.2` - - Update usage of `accounts-passwordless` to be compatible with 2.0.0. - -* `minifier-js@2.7.3` - - Revert `evaluate` option that was set to false in 2.7.2. - -* `standard-minifier-js@2.7.3` - - Using `minifier-js@2.7.3` - - -* `npm-mongo@4.2.1` - - Update MongoDB driver version to 4.2.1 - -## v2.5.1, 2021-11-17 - -#### Highlights -- Mac M1 Support - darwin arm64. [Read more](https://blog.meteor.com/). - -#### Breaking Changes -- `Meteor.loginWithToken` from the new package `accounts-passwordless` was conflicting with another method with the same name on `accounts-base` so we had to rename the method of `accounts-passwordless` package to `Meteor.passwordlessLoginWithToken`. - -#### Meteor Version Release - -* `meteor-tool@2.5.1` - - Meteor supports now Mac M1 chips (darwin arm64) - -* `accounts-passwordless@2.0.0` - - `Meteor.loginWithToken` from the new package `accounts-passwordless` was conflicting with another method with the same name on `accounts-base` so we had to rename the method of `accounts-passwordless` package to `Meteor.passwordlessLoginWithToken`. - -#### Independent Releases -* `minifier-js@2.7.2` - - Stopped using `evaluate` option in the compression to fix a [bug](https://github.com/meteor/meteor/issues/11756). - - Updated `terser` to [v5.9.0](https://github.com/terser/terser/blob/master/CHANGELOG.md#v590) to fix various bugs - -* `standard-minifier-js@2.7.2` - - Using `minifier-js@2.7.2` - -* `github-oauth@1.3.2` - - Migrate from `http` to `fetch` - - Fix GitHub login params to adhere to changes in GitHub API - -## v2.5, 2021-10-21 - -#### Highlights - -* New package: `accounts-passwordless` -* Cordova Android v10 -* HMR now works on all architectures and legacy browsers -* `Accounts.config()` and third-party login services can now be configured from Meteor settings - -#### Breaking Changes - -* Cordova Android v10 now enables AndroidX. If you use any cordova-plugin that depends or uses any old support library, you need to include the cordova-plugin-androidx-adapter cordova-plugin, otherwise you will get build errors. - -#### Meteor Version Release - -* CircleCI testing image was updated to include Android 30 and Node 14 - -* `meteor-tool@2.5` - - Cordova Android upgraded to v10 - - HMR improvements related to `hot-module-replacement@0.4.0` - - Fix finding local packages on Windows located on drives other than C - - Fix infinite loop in import scanner when file is on a different drive than source root - - Fix Meteor sometimes not detecting changes to a file after the first time it is modified - - Fixes Meteor sometimes hanging on Windows. Reverts the temporary fix in Meteor 2.4 of disabling native file watchers for some commands - - Uses recursive file watchers on Windows and macOS. In most situations removes the up to 5 seconds delay before detecting the first change to a file, and is more efficient. - - Node updated to [v14.18.1](https://nodejs.org/en/blog/release/v14.18.1/), following [October 12th 2021 security release](https://nodejs.org/en/blog/vulnerability/oct-2021-security-releases/) - - Skeletons had their dependencies updated - -* `accounts-passwordless@1.0.0` - - New accounts package to provide passwordless authentication. - -* `accounts-password@2.2.0` - - Changes to reuse code between passwordless and password packages. - -* `accounts-base@2.2.0` - - You can now apply all the settings for `Accounts.config` in `Meteor.settings.packages.accounts-base`. They will be applied automatically at the start of your app. Given the limitations of `json` format you can only apply configuration that can be applied via types supported by `json` (ie. booleans, strings, numbers, arrays). If you need a function in any of the config options the current approach will still work. The options should have the same name as in `Accounts.config`, [check them out in docs.](https://docs.meteor.com/api/accounts-multi.html#AccountsCommon-config). - - Changes to reuse code between passwordless and password packages. - -* `accounts-ui-unstyled@1.6.0` - - Add support for `accounts-passwordless`. - -* `service-configuration@1.3.0` - - You can now define services configuration via `Meteor.settings.packages.service-configuration` by adding keys as service names and their objects being the service settings. You will need to refer to the specific service for the settings that are expected, most commonly those will be `secret` and `appId`. - -* `autoupdate@1.8.0` - - Enable HMR for all web arch's - -* `ecmascript@0.16.0` - - Enable HMR for all web arch's - -* `hot-module-replacement@0.4.0` - - Provides polyfills needed by Meteor.absoluteUrl in legacy browsers - - Improvements for HMR to work in all architectures and legacy browsers - -* `module-runtime@0.14.0` - - Improvements for legacy browsers - -* `react-fast-refrest@0.2.0` - - Enable HMR for all web arch's - -* `typescript@4.4.0` - - Enable HMR for all web arch's - -* `webapp@1.13.0` - - Update `cordova-plugin-meteor-webapp` to v2 - - Removed dependency on `cordova-plugin-whitelist` as it is now included in core - - Cordova Meteor plugin is now using AndroidX - - Added new settings option `Meteor.settings.packages.webapp.alwaysReturnContent` that will always return content on requests like `POST`, essentially enabling behavior prior to Meteor 2.3.1. - -#### Independent Releases - -* `modern-browsers@0.1.6` - - Added `mobileSafariUI` as an alias for Mobile Safari - -* `minifier-js@2.7.1` - - Updated `terser` to [v5.8.0](https://github.com/terser/terser/blob/master/CHANGELOG.md#v580) to fix various bugs - -* `standard-minifier-js@2.7.1` - - Updated `@babel/runtime` to [v7.15.4](https://github.com/babel/babel/releases/tag/v7.15.4) - -* `accounts-ui@1.4.1` - - Update compatibility range with `less` from 3.0.2 to 4.0.0 - -* `accounts-ui-unstyled@1.5.1` - - Update compatibility range with `less` from 3.0.2 to 4.0.0 - -* `google-config-ui@1.0.3` - - Deliver siteUrl in the same way as other config-ui packages - -* `ecmascript-runtime-client@0.12.1` - - Revert `core-js` to v3.15.2 due to issues in legacy build with arrays, [see issue for more details](https://github.com/meteor/meteor/issues/11662) - -* `modern-browsers@0.1.7` - - Added `firefoxMobile` as an alias for `firefox` - -* `dynamic-import@0.7.2` - - Fixes 404 in dynamic-import/fetch when ROOT_URL is set with a custom path. [see issue](https://github.com/meteor/meteor/issues/11701) - -## v2.4.1, 2021-10-12 - -#### Meteor Version Release - -* `meteor-tool@2.4.1` - - Patch to make 2.4.1 compatible with Push to Deploy feature in Galaxy (Meteor Cloud) - -## v2.4, 2021-09-15 - -#### Highlights - -* Typescript updated to [v4.3.5](https://github.com/Microsoft/TypeScript/releases/tag/v4.3.5) -* Email package now allows setting `Email.customTransport` to override sending method. -* Use `createIndex` instead of `_ensureIndex` to align with new MongoDB naming. -* Apollo skeleton has been upgraded for [Apollo server v3](https://github.com/apollographql/apollo-server/blob/main/CHANGELOG.md#v300) -* `reify` has been updated to v0.22.2 which reduces the overhead of `import` statements and some uses of `export ... from`, especially when a module is imported a large number of times or re-exports a large number of exports from other modules. PRs [1](https://github.com/benjamn/reify/pull/246), [2](https://github.com/benjamn/reify/pull/291) -* Meteor NPM installer is [now available for all platforms](https://github.com/meteor/meteor/pull/11590). -* DDP server now allows you to set publication strategies for your publications to control mergebox behavior -* On Windows Meteor should no longer be hanging on commands - -#### Migration steps - -1. Replace all usage of `collection._ensureIndex` with `collection.createIndex`. You only need to rename the method as the functionality is the same. -2. If you are using a [well known service](https://nodemailer.com/smtp/well-known/) for the email package switch to using `Meteor.settings.packages.email` settings instead of `MAIL_URL` env variable. Alternatively you can utilize the new `Email.customTransport` function to override the default package behavior and use your own. [Read the email docs](https://docs.meteor.com/api/email.html) for implementation details. - -#### Meteor Version Release - -* Skeletons dependencies updated - -* `meteor-tool@2.4` - - `meteor show` now reports if a package is deprecated - - `reify` update to v0.22.2 which bring optimizations for imports. PRs [1](https://github.com/benjamn/reify/pull/246), [2](https://github.com/benjamn/reify/pull/291) - - Apollo skeleton now uses [Apollo server v3](https://github.com/apollographql/apollo-server/blob/main/CHANGELOG.md#v300) - [migration guide](https://www.apollographql.com/docs/apollo-server/migration/) - - Upgraded `chalk` to v4.1.1 - - Typescript updated to [v4.3.5](https://github.com/Microsoft/TypeScript/releases/tag/v4.3.5) - - `METEOR_SETTINGS` is now accepted an all modes - - Native file watchers are now disabled on Windows for many file-intensive actions (like, `create`, `update`, `build` etc.), this solves an issue with hanging Meteor commands on Windows - -* `webapp@1.12` - - npm dependencies have been updated - - Added hook to change runtime config delivered to the client app, [read more](https://github.com/meteor/meteor/pull/11506) - - Added hook to get notified when the app is updated, [read more](https://github.com/meteor/meteor/pull/11607) - - `@vlasky/whomst@0.1.7` - - Added `addUpdateNotifyHook` that gets called when runtime configuration is updated - -* `logging@1.3.0` - - Switch from `cli-color` to `chalk` to have the same dependency as meteor-tool - - Fix detecting eval - - Copy over code from `Meteor._debug` to `Log.debug` which will be deprecated in the future - -* `email@2.2` - - Modernized package code - - Add alternative API function that you can hook into to utilize your own sending method: `Email.customTransport`. [Read the docs](https://docs.meteor.com/api/email.html#Email-customTransport) - - Use `Meteor.settings` for easy setup to sending email via [known providers](https://nodemailer.com/smtp/well-known/). [Read the docs](https://docs.meteor.com/api/email.html) - -* `ddp-server@2.5.0` - - One of three different publication strategies can be selected for any Meteor publication - SERVER_MERGE, NO_MERGE and NO_MERGE_NO_HISTORY. These control the behaviour of the Meteor mergebox, providing a compromise between client-server bandwidth usage and server side memory usage. [See PR](https://github.com/meteor/meteor/pull/11368) or [the documentation](https://docs.meteor.com/api/pubsub.html#Publication-strategies) for more details. - -* `mongo@1.13.0` - - Add `createIndex` as a collection function (in MongoDB since MongoDB v3). This is a new name for `_ensureIndex` which MongoDB has deprecated and removed in MongoDB 5.0. Use of `_ensureIndex` will show a deprecation warning on development. - -* `accounts-base@2.1.0` - - Migrated usage of `_ensureIndex` to `createIndex` - -* `accounts-oauth@1.4.0` - - Migrated usage of `_ensureIndex` to `createIndex` - -* `accounts-password@2.1.0` - - Migrated usage of `_ensureIndex` to `createIndex` - -* `oauth@2.1.0` - - Migrated usage of `_ensureIndex` to `createIndex` - -* `oauth1@1.5.0` - - Migrated usage of `_ensureIndex` to `createIndex` - -* `facebook-oauth@1.10.0` - - Added login handler hook, like in the Google package for easier management in React Native and similar apps. [PR](https://github.com/meteor/meteor/pull/11603) - -* `service-configuration@1.5.0` - - Migrated usage of `_ensureIndex` to `createIndex` - -* `ecmascript-runtime-client@0.12.0` - - `core-js@3.16.0` - -* `ecmascript-runtime-server@0.11.0` - - `core-js@3.16.0` - -* `ecmascript-runtime@0.8.0` - - Version bump to ensure changes from server & client runtime get propagated. - -* `tinytest@1.2.0` - - Add option to temporarily replace `Tinytest.add` or `Tinytest.addAsync` by `Tinytest.only` or `Tinytest.onlyAsync` so only the tests added using `only*` are going to be executed. - -* `test-helpers@1.3.0` - - Support for `Tinytest.only` and `Tinytest.onlyAsync` - -* `modules@0.17.0` - - Update `reify` to `0.22.2` - -* `standard-minifier-js@2.7.0` - - `@babel/runtime@7.15.3` - - Code modernization - - Improved error handling - -* `minifier-js@2.7.0` - - Added tests - - Code modernization - -* `standard-minifier-css@1.7.4` - - `@babel/runtime@7.15.3` - -* `minifier-css@1.6.0` - - Updated dependencies - - `postcss@8.3.5` - - `cssnano@4.1.11` - -* `callback-hook@1.4.0` - - Added `forEach` iterator to be more in-line with the ES use for iterations. `each` is now deprecated, but will remain supported. - -## v2.3.7, 2021-10-12 - -#### Meteor Version Release - -* `meteor-tool@2.3.7` - - Patch to make 2.3.7 compatible with Push to Deploy feature in Galaxy (Meteor Cloud) - -## v2.3.6, 2021-09-02 - -#### Highlights - -* Updated Node.js per [August 31st security release](https://nodejs.org/en/blog/vulnerability/aug-2021-security-releases2/) - -#### Meteor Version Release - -* `meteor-tool@2.3.6` - - Node.js updated to [v14.17.6](https://nodejs.org/en/blog/release/v14.17.6/) - -#### Independent Releases - -* `minifier-js@2.6.1` - - Terser updated to [4.8.0](https://github.com/terser/terser/blob/master/CHANGELOG.md#v480) - -* `routepolicy@1.1.1` - - Removed `underscore` dependency since it was not used in the package - -* `email@2.1.1` - - Updated `nodemailer` to v6.6.3 - -* `callback-hook@1.3.1` - - Modernized the code - - Fixed a variable assignment bug in `dontBindEnvironment` function - -* `less@4.0.0` - - Updated `less` to v4.1.1 - - Fixed tests - -* `npm-mongo@3.9.1` - - `mongodb@3.6.10` - -* `accounts-base@2.0.1` - - Create index on `services.password.enroll.when` - - Blaze weak dependency updated to v2.5.0 - -* `facebook-oauth@1.9.1` - - Allow usage of `http` package both v1 and v2 for backward compatibility - -* `github-oauth@1.3.1` - - Allow usage of `http` package both v1 and v2 for backward compatibility - -* `google-oauth@1.3.1` - - Allow usage of `http` package both v1 and v2 for backward compatibility - -* `meetup-oauth@1.1.1` - - Allow usage of `http` package both v1 and v2 for backward compatibility - -* `meteor-developer-oauth@1.3.1` - - Allow usage of `http` package both v1 and v2 for backward compatibility - -* `weibo-oauth@1.3.1` - - Allow usage of `http` package both v1 and v2 for backward compatibility - -* `oauth1@1.4.1` - - Allow usage of `http` package both v1 and v2 for backward compatibility - - Blaze weak dependency updated to v2.5.0 - -* `ddp-server@2.4.1` - - Fix a bug where `testMessageOnConnect` has always been sent - -* `accounts-password@2.0.1` - - Fix use of `isEnroll` in reset password - -* `mdg:geolocation@1.3.1` - - Fixed API to work with Meteor 2.3+ - -* `mdg:reload-on-resume@1.0.5` - - Fixed API to work with Meteor 2.3+ - -## v2.3.5, 2021-08-12 - -#### Highlights - -* Updated Node.js per the [August security release](https://nodejs.org/en/blog/vulnerability/aug-2021-security-releases/) -* Includes same improvements as in Meteor v2.2.3 - - Typescript updated to [v4.3.5](https://github.com/Microsoft/TypeScript/releases/tag/v4.3.5) - - `@meteorjs/babel@7.12.0` - -#### Meteor Version Release - -* `meteor-tool@2.3.5` - - Node.js updated to [v14.17.5](https://nodejs.org/en/blog/release/v14.17.5/) - - Typescript updated to [v4.3.5](https://github.com/Microsoft/TypeScript/releases/tag/v4.3.5) - - `@meteorjs/babel@7.12.0` - - Fix broken source maps in VSCode - [PR](https://github.com/meteor/meteor/pull/11584) - -## v2.3.4, 2021-08-03 - -* Fix an issue in `bare` and `vue` skeletons - -## v2.3.3, 2021-08-02 - -* Security patch of Node.js to [14.17.4](https://nodejs.org/en/blog/release/v14.17.4/) -* App skeletons had the following dependencies updated: - - `meteor-node-stubs@1.1.0` - - `@babel/runtime@7.14.8` -* `babel/parser@7.14.9` for server dev bundle - -## v2.3.2, 2021-07-13 - -#### Meteor Version Release - -* `meteor-tool@2.3.2` - - fixes a bug that makes `meteor run android` run with the new aab package flag - -## v2.3.1, 2021-07-08 - -#### Highlights - -* Fix windows issue when running webapp package. -* Node.js updated to 14.17.3, following [security release](https://nodejs.org/en/blog/vulnerability/july-2021-security-releases/) - -#### Breaking Changes - -* Meteor will now generate ".aab" (bundle files) by default when building for Android. This is the [new default format](https://android-developers.googleblog.com/2021/06/the-future-of-android-app-bundles-is.html) for Android apps. Use the new build flag `--packageType=apk` if you still need to generate APK. - -#### Meteor Version Release - -* Updated travis CI environment to use Node.js 14.17.3 - -* `meteor-tool@2.3.1` - - Node.js updated to [14.17.2](https://nodejs.org/en/blog/release/v14.17.2/) and [14.17.3](https://nodejs.org/en/blog/release/v14.17.3/) - - `@babel/runtime` dependency updated to v7.14.6 across the tool and testing apps - - Skeletons dependencies updated - - Apollo skeleton removed `apollo-boost` dependency which is no longer needed - - New build flag `--packageType` to choose between apk/bundle for android builds (defaults to bundle). - -#### Independent Releases - -* `webapp@1.11.1` - - Remove `posix` from npm shrinkwrap, to fix a bug it causes on Windows. - -* `less@3.0.2` - - Updated `@babel/runtime` to v7.14.6 - - Updated `less` to v3.11.3 - -* `standard-minifiers-css@1.7.3` - - Updated `@babel/runtime` to v7.14.6 - -* `standard-minifiers-js@2.6.1` - - Updated `@babel/runtime` to v7.14.6 - -* `dynamic-import@0.7.1` - - Fix [Safari 14 bug](https://bugs.webkit.org/show_bug.cgi?id=226547) with indexedDB - -## v2.3, 2021-06-24 - -#### Highlights - -* Node.js update to 14.17.1 from 12.22.1 🎉 - -* Typescript update to [4.3.2](https://devblogs.microsoft.com/typescript/announcing-typescript-4-3/) - -* Packages had their backward compatibility to before Meteor 1.0 removed. See below for more details. - -* Improved tracking of which files are used by build plugins to know when it should do a full rebuild, a faster client-only rebuild, or can completely skip rebuilding after a file is modified. This should work with any type of file in any directory, and for both files in the app and files in packages. The most noticeable improvement is when modifying a file only used on the client Meteor will only rebuild the client, even if the file is not inside `imports` or a `client` folder. - -### Summary of breaking changes - -- As Node.js version was upgraded to a new major version we recommend that you review if your npm dependencies are compatible with Node.js 14. - - If we receive reports from breaking changes we are going to list them here but so far we are not aware of any. - - We recommend that you read Node.js [release notes](https://nodejs.org/en/blog/release/v14.0.0/) though. - -- Accounts have undergone some major changes including major version bump. See below for more details. - -- All official packages that have been deprecated have now the deprecated flag and will inform you about that if you install or update them. - -- If you are working with enrollments in user accounts, do note that the enrollment token handling is now separate from reset password token. The token is now under `services.password.enroll`, so adjust your code accordingly if you use it. - -### Migration steps - -- As Node.js version was upgraded we recommend that you remove your `node_modules` folder (`rm -rf node_modules`) and run `meteor npm i` to be sure you compile all the binary dependencies again using the new Node.js version. - - Maybe you also want to recreate your lock file. - - If you get an error try `meteor reset` which will clear caches, beware that this will also remove your local DB for your app. - -- If you are maintaining a package that depends on one of the accounts packages which had a major version bump you will either need to set the new version manually or set `api.versionsFrom('2.3')`. - You can also have it reference its current version and 2.3 like this: `api.versionsFrom(['1.12', '2.3'])`, for specific package it can be like this: `api.use('accounts-base@1.0.1 || 2.0.0')`. - -- Old API for packages definitions has been removed. The old underscore method names (e.g. `api.add_files()`) will no longer work, please use the camel case method names (e.g. `api.addFiles()`). - -### Breaking changes -* Removed deprecated `mobile-port` flag - -* Removed deprecated `raw` name from `isobuild` - -* Removed deprecated package API method names `Package.on_use`, `Package.on_test`, `Package._transitional_registerBuildPlugin` and `api.add_files`, if you haven't till now, please use the current camel case versions. - -* `accounts-base@2.0.0` - - Deprecated backward compatibility function `logoutOtherClients` has been removed. - -* `accounts-password@2.0.0` - - Deprecated backward compatibility functionality for `SRP` passwords from pre-Meteor 1.0 days has been removed. - - Enroll account workflow has been separated from reset password workflow (the enrollment token records are now stored in a separate db field `services.password.enroll`). - -* `ddp-client@2.5.0` - - Removed deprecated backward compatibility method names for Meteor before 1.0 - -* `ddp-server@2.4.0` - - Removed deprecated backward compatibility method names for Meteor before 1.0 - -* `meteor-base@1.5.0` - - Removed `livedata` dependency which was there for packages build for 0.9.0 - -* `minimongo@1.7.0` - - Removed the `rewind` method that was noop for compatibility with Meteor 0.8.1 - -* `mongo@1.12.0` - - Removed the `rewind` method that was noop for compatibility with Meteor 0.8.1 - -* `oauth@2.0.0` - - Removed deprecated `OAuth.initiateLogin` and other functionality like the addition of `?close` in return URI for deprecated OAuth flow pre Meteor 1.0 - -* `markdown@2.0.0` - - Use lazy imports to prevent it from being added to the initial bundle - - This package is now deprecated - -* `http@2.0.0` - - Internally http has been replaced by [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API), should still work as previous version, but edge cases might be different. This is to aid you in transition to fetch. Note that this means that the `npmRequestOptions` parameter to `HTTP.call` has been removed, as `request` is no longer used internally. - -* `socket-stream-client@0.4.0` - - Remove IE8 checks - -#### Meteor Version Release - -* `meteor-tool@2.3` - - Node.js update to 14.17.1 from 12.22.1 🎉 - - This is a major upgrade in Node.js. See the [release notes](https://nodejs.org/en/blog/release/v14.0.0/) for more details. - - `npm` update to 6.14.13. - - `fibers` has been updated to v5.0.0. - - `promise` has been updated to v8.1.0. - - `node-gyp` has been updated to v8.0.0. - - `node-pre-gyp` has been updated to v0.15.0. - - `@babel/runtime` has been updated to v7.14.0. - - `request` has been updated to v2.88.2. - - `uuid` has been updated to v3.4.0. - - `graceful-fs` has been updated to v4.2.6. - - `tar` has been updated to v2.2.2. - - `sqlite3` has been updated to v5.0.2. - - `http-proxy` has been updated to v1.18.1. - - `wordwrap` has been updated to v1.0.0. - - `moment` has been updated to v2.29.1. - - `glob` has been updated to v7.1.6. - - `split2` has been updated to v3.2.2. - - `lru-cache` has been updated to v4.1.5. - - `anser` has been updated to v2.0.1. - - `xmlbuilder2` has been updated to v1.8.1. - - `ws` has been updated to v7.4.5. - - `underscore` has been updated to v1.13.1 - - `optimism` has been updated to v0.16.1 - - `@wry/context` has been update to v0.6.0 - - Reduced time spent by server (re)start in development by adding a cache for Reify. This optimization is on by default in development. Set the new `METEOR_TOOL_ENABLE_REIFY_RUNTIME_CACHE` and `METEOR_REIFY_CACHE_DIR` environment variables to adjust it or turn it on for production [read more in the PR](https://github.com/meteor/meteor/pull/11400). - - New flag `--platforms` has been added to the `build` command to specify the platform you want to build for. `meteor build . --platforms=android`. This is useful for example when you are not using a MacOS and you want to build your app only for Android. Also to save time on CI not building all the platforms all the time. See [PR](https://github.com/meteor/meteor/pull/11437) for details. - - The undocumented environment variable `DDP_DEFAULT_CONNECTION_URL` behavior has changed. Setting `DDP_DEFAULT_CONNECTION_URL` when running the server (development: `meteor run` or production: `node main.js`) sets the default DDP server value for meteor. But this did not work for `cordova` apps. Now you can define the `cordova` app default DDP server value by setting `DDP_DEFAULT_CONNECTION_URL` when building (`meteor build`). - - Skeletons dependencies updated to latest version - - Svelte skeleton now has HMR - - New deploy option: `--build-only`. Helpful if you want to build first and after some validations proceeding with the upload and deploy. [Read more](https://cloud-guide.meteor.com/deploy-guide.html#cache-only) - - Improved watched system to properly rebuild `client` even when a file is outside of `client` or `imports` folders. See [PR](https://github.com/meteor/meteor/pull/11474) for details. - - Fix an issue when `App.appendToConfig` crashed Cordova build. - - Reify compiler now uses cache in runtime. [Read more](https://github.com/meteor/meteor/pull/11400) - -* `launch-screen@1.3.0` - - Removes LaunchScreen from web clients. - -* `meteor-babel@7.11.0 (@meteorjs/babel)` - - Fixes for Samsung Internet v6.2+ to be considered modern browser and addition of [logical assignment operators](https://github.com/tc39/proposal-logical-assignment) via `babel-presets-meteor`. - - This package was renamed to `@meteorjs/babel`. - -* `hot-module-replacement@0.3.0` - - Fixes various HMR bugs and edge cases see [PR for more](https://github.com/meteor/meteor/pull/11405). - -* `email@2.1.0` - - Updates `nodemailer` to `6.6.0` and it now adds `charset=utf-8` to `text/plain` messages by default. - -* `server-render@0.4.0` - - Updated npm dependencies - -* `accounts-base@2.0.0` - - New hook `setAdditionalFindUserOnExternalLogin` has been added which allows you to customize user selection on external logins if you want to, for example, login a user who has the same e-mail as the external account. - -* `ddp-server@2.4.0` - - Added support for `this.unblock()` in `Meteor.publish()` context. See [PR](https://github.com/meteor/meteor/pull/11392) for more details. - - Add support in `Meteor.publish()` for async functions - -* `webapp@1.11.0` - - Webapp will respond appropriately to unsupported requests instead of sending content, including handling for new HTTP verbs. See [PR](https://github.com/meteor/meteor/pull/11224) for more details. - -#### Independent Releases - -* `ddp-server@2.3.3` - - Updates dependencies which removes Node's HTTP deprecation warning. - -* `socket-stream-client@0.3.2` - - Updates dependencies which removes Node's HTTP deprecation warning. - -* `ddp-client@2.4.1` - - Re-ordering fields in DDP message for better client readability. - -* `mongo@1.11.1` - - Fixes a `Timestamp.ONE is undefined` bug. - -* `mongo-id@1.0.8` - - Removes unused dependency `id-map`. - -* `accounts-server@1.7.1` - - To better test password format & limit password to 256 characters, you can change this limit by setting `Meteor.settings.packages.accounts.passwordMaxLength`. - -* `static-html@1.3.1` - - Removes `underscore` dependency. - -* `dev-error-overlay@0.1.1` - - Fixes sometimes page content being on top of error overlay. - -* `id-map@1.1.1` - - Removes unused dependencies and modernizing the code. - -* `http@1.4.4` - - Used the new deprecation package flag instead of loud console warning. - -* `logic-solver@2.0.8` - - Fixed `package.js` to use current `api` method calls. - -* `socket-stream-client@0.3.3` - - Update `faye-websocket` dependency to v0.11.4. - -* `jshint@1.1.8` - - The package has been deprecated. - -* `npm-bcrypt@0.9.4` - - The package has been deprecated. - -* `ecmascript-runtime-client@0.11.1` - - Updated `core-js` to v3.14.0 - -* `ecmascript-runtime-server@0.11.1` - - Updated `core-js` to v3.14.0 - -* `url@1.3.2` - - Updated `core-js` to v3.14.0 - -* `hot-module-replacement@0.2.1` - - Add missing dependency. - -* `observe-sequence@1.0.17` - - Updated dependencies - -* `observe-sequence@1.0.18` - - When `#each` argument is unsupported it will be shown - - Moving package under Blaze repository - -* `react-fast-refresh@0.1.1` - - Fixed the package to work in IE11 - -## v2.2.4, 2021-10-12 - -#### Meteor Version Release - -* `meteor-tool@2.2.4` - - Patch to make 2.2.4 compatible with Push to Deploy feature in Galaxy (Meteor Cloud) - -## v2.2.3, 2021-08-12 - -#### Highlights - -* Security update to Node.js [12.22.5](https://nodejs.org/en/blog/release/v12.22.5/) -* Typescript updated to [v4.3.5](https://github.com/Microsoft/TypeScript/releases/tag/v4.3.5) - -#### Meteor Version Release - -* `meteor-tool@2.3.3` - - Updated Node.js to 12.22.5 per [Node security update](https://nodejs.org/en/blog/vulnerability/aug-2021-security-releases/) - - Typescript updated to [v4.3.5](https://github.com/Microsoft/TypeScript/releases/tag/v4.3.5) - - `@meteorjs/babel@7.12.0` - -* `@meteorjs/babel@7.12.0` && `@meteorjs/babel@7.13.0` - - Dependencies updated to their latest versions - -* `babel-compile@7.7.0` - - `@meteorjs/babel@7.12.0` - -* `ecmascript@0.15.3` - - Typescript and Babel version bump - -* `typescript@4.3.5` - - [`typescript@4.3.5`](https://github.com/Microsoft/TypeScript/releases/tag/v4.3.5) - -## v2.2.2, 2021-08-02 - -#### Highlights - -- Security update to Node.js [12.22.4](https://nodejs.org/en/blog/release/v12.22.4/) - -## v2.2.1, 2021-06-02 - -#### Highlights - -- Node.js updated to [12.22.2](https://nodejs.org/en/blog/release/v12.22.2/) -- npm updated to 6.14.13 - -#### Meteor Version Release - -* `meteor-tool@2.2.1` - - Updated Node.js to 12.22.2 per [Node security update](https://nodejs.org/en/blog/vulnerability/july-2021-security-releases/) - -## v2.2, 2021-04-15 - -#### Highlights - -- MongoDB Update to 4.4.4 -- Cordova Update to 10 -- Typescript Update to 4.2.2 -- New skeleton: `meteor create myapp --svelte` - -### Breaking changes - -* N/A - -### Migration steps - -* `meteor-tool` maybe you need to install the new Visual C++ Redistributable for Visual Studio 2019 to run MongoDB 4.4.4 on Windows. [read more](https://docs.meteor.com/windows.html) - -* `mongo` package is now using useUnifiedTopology as `true` by default otherwise the new driver was producing a warning (see details below). It's important to test your app with this change. - -* `cordova` plugins and main libraries were updated from 9 to 10. It's important to test your app with these changes. - -* `typescript` was updated to 4.2.2, make sure your read the [breaking changes](https://devblogs.microsoft.com/typescript/announcing-typescript-4-2/#breaking-changes). - -#### Meteor Version Release - -* `meteor-tool@2.2` - - Update embedded MongoDB version to 4.4.4 [#11341](https://github.com/meteor/meteor/pull/11341) - - Maybe you need to install the new Visual C++ Redistributable for Visual Studio 2019 to run on Windows. [read more](https://docs.meteor.com/windows.html) - - Fix WindowsLikeFilesystem true when release string includes case insensitive word microsoft. [#11321](https://github.com/meteor/meteor/pull/11321) - - Fix absoluteFilePath on Windows. [#11346](https://github.com/meteor/meteor/pull/11346) - - New skeleton: `meteor create myapp --svelte` - - Update Blaze skeleton to use HMR - -* `npm-mongo@3.9.0` - - Update MongoDB driver version to 3.6.6 - -* `mongo@1.11.0` - - Using useUnifiedTopology as `true` by default to avoid the warning: `(node:59240) [MONGODB DRIVER] Warning: Current Server Discovery and Monitoring engine is deprecated, and will be removed in a future version. To use the new Server Discover and Monitoring engine, pass option { useUnifiedTopology: true } to the MongoClient constructor. You can still use it as false with `Mongo._connectionOptions` or `Meteor.settings?.packages?.mongo?.options`. - -* `cordova@10` - - Update Cordova to 10.0.0 [#11208](https://github.com/meteor/meteor/pull/11208) - -* `typescript@4.2.2` - - Update Typescript to 4.2.2, make sure your read the [breaking changes](https://devblogs.microsoft.com/typescript/announcing-typescript-4-2/#breaking-changes) [#11329](https://github.com/meteor/meteor/pull/11329) - -* `accounts-base@1.9.0` - - Allow to set token expiration to be set in milliseconds. [#11366](https://github.com/meteor/meteor/pull/11366) - -* `facebook-oauth@1.9.0` - - Upgrade default Facebook API to v10 & allow overriding this value. [#11362](https://github.com/meteor/meteor/pull/11362) - -* `minimongo@1.6.2` - - Add [$mul](https://docs.mongodb.com/manual/reference/operator/update/mul/#up._S_mul) to minimongo. [#11364](https://github.com/meteor/meteor/pull/11364) - -* `webapp@1.10.1` - - Fix for UNIX sockets with node cluster. [#11369](https://github.com/meteor/meteor/pull/11369) - - -## v2.1.2, 2021-10-12 - -#### Meteor Version Release - -* `meteor-tool@2.1.2` - - Patch to make 2.1.2 compatible with Push to Deploy feature in Galaxy (Meteor Cloud) - -## v2.1.1, 2021-04-06 - -### Changes - -#### Highlights - -- Node.js security [update](https://nodejs.org/en/blog/vulnerability/april-2021-security-releases/) to 12.22.1 - -#### Meteor Version Release - -* `meteor-tool@2.1.1` - - Node.js security [update](https://nodejs.org/en/blog/vulnerability/april-2021-security-releases/) to 12.22.1 - - npm update to 6.14.12 - -### Breaking changes - -* N/A - -### Migration steps - -* N/A - -## v2.1, 2021-02-24 - -### Changes - -#### Highlights - -- Node.js security [update](https://nodejs.org/en/blog/vulnerability/february-2021-security-releases/) to 12.21.0 - -#### Meteor Version Release - -* `meteor-tool@2.1` - - Node.js security [update](https://nodejs.org/en/blog/vulnerability/february-2021-security-releases/) to 12.21.0 - - `meteor create my-app --plan professional` new flag `plan` to enable you to choose a plan from the deploy command. - -### Breaking changes - -* N/A - -### Migration steps - -* N/A - -## v2.0.1, 2021-10-12 - -#### Meteor Version Release - -* `meteor-tool@2.0.1` - - Patch to make 2.0.1 compatible with Push to Deploy feature in Galaxy (Meteor Cloud) - -## v2.0, 2021-01-20 - -### Changes - -#### Highlights - -- Free deploy on [Cloud](https://www.meteor.com/cloud): Deploy for free to Cloud with one command: `meteor deploy myapp.meteorapp.com --free`. ([docs](https://docs.meteor.com/commandline.html#meteordeploy)) - - -- Deploy including MongoDB on [Cloud](https://www.meteor.com/cloud): Deploy including MongoDB in a shared instance for free to Cloud with one command: `meteor deploy myapp.meteorapp.com --free --mongo`. ([docs](https://docs.meteor.com/commandline.html#meteordeploy)) - - -- Hot Module Replacement (HMR): Updates the javascript modules in a running app that were modified during a rebuild. Reduces the feedback cycle while developing so you can view and test changes quicker (it even updates the app before the build has finished). Enabled by adding the `hot-module-replacement` package to an app. React components are automatically updated by default using React Fast Refresh. Integrations with other libraries and view layers can be provided by third party packages. Support for Blaze is coming soon. This first version supports app code in the modern web architecture. ([docs](https://guide.meteor.com/build-tool.html#hot-module-replacement)) [#11117](https://github.com/meteor/meteor/pull/11117) - -#### Meteor Version Release - -* `meteor-tool@2.0` - - `meteor create my-app` now creates by default a project using React. If you want to create a new project using Blaze you should use the new option `--blaze`. - - `meteor create --react my-app` is still going to create a React project. - - `meteor create --free` deploy for free to Cloud with one command: `meteor deploy myapp.meteorapp.com --free`. ([docs](https://docs.meteor.com/commandline.html#meteordeploy)). - - `meteor create --free --mongo` deploy including MongoDB in a shared instance for free to Cloud with one command: `meteor deploy myapp.meteorapp.com --free --mongo`. ([docs](https://docs.meteor.com/commandline.html#meteordeploy)) - - `isobuild` fixes a regression on recompiling node modules in different architectures. [#11290](https://github.com/meteor/meteor/pull/11290) - - `isobuild` converts npm-discards.js to TypeScript. [#10663](https://github.com/meteor/meteor/pull/10663) - - `cordova` ensures the pathname of the rootUrl is used in the mobile URL. [#11053](hhttps://github.com/meteor/meteor/pull/11053) - - Add `file.hmrAvailable()` for compiler plugins to check if a file meets the minimum requirements to be updated with HMR [#11117](https://github.com/meteor/meteor/pull/11117) - - -* `hot-module-replacement@1.0.0` - - New package that enables Hot Module Replacement for the Meteor app and provides an API to configure how updates are applied. HMR reduces the feedback cycle while developing by updating modified javascript modules within the running application. ([docs](https://docs.meteor.com/packages/hot-module-replacement.html)) [#11117](https://github.com/meteor/meteor/pull/11117) - - These packages have been updated to support HMR: `autoupdate@1.7.0`, `babel-compiler@7.6.0`, `ddp-client@2.4.0`, `dynamic-import@0.6.0`, `ecmascript@0.15.0`, `modules@0.16.0`, `modules-runtime-hot@0.13.0`, `standard-minifier-css@1.7.2`, `webapp@1.10.0`, `webapp-hashing@1.1.0` - - -* `react-fast-refresh@0.1.0` - - New package that updates React components using HMR. This is enabled by default in apps that have HMR enabled and use a supported React version. ([docs](https://atmospherejs.com/meteor/react-fast-refresh)) [#11117](https://github.com/meteor/meteor/pull/11117) - - -* `dev-error-overlay@0.1.0` - - New package that allows you to see build errors and server crashes in your browser during development. Requires the app to have HMR enabled. [#11117](https://github.com/meteor/meteor/pull/11117) - - -* `accounts-base@1.8.0` and `accounts-password@1.7.0` - - Extra parameters can now be added to reset password, verify e-mail and enroll account links that are generated for account e-mails. By default, these are added as search parameters to the generated url. You can pass them as an object in the appropriate functions. E.g. `Accounts.sendEnrollmentEmail(userId, email, null, extraParams);`. [#11288](https://github.com/meteor/meteor/pull/11288) - - -* `logging@1.2.0` - - Updates dependencies and make debug available for use in non production environments. [#11068](https://github.com/meteor/meteor/pull/11068) - -#### Independent Releases -* `react-meteor-data@2.2.0` - - Fix issue with useTracker and Subscriptions when using deps. [#306](https://github.com/meteor/react-packages/pull/306) - - Remove version constraint on core TypeScript package [#308](https://github.com/meteor/react-packages/pull/308) - - -* `http` - - It has been deprecated. [#11068](https://github.com/meteor/meteor/pull/11068) - -### Breaking changes - -* `http` package has been deprecated. Please start on migrating towards the [fetch](https://atmospherejs.com/meteor/fetch) package instead. - -### Migration steps - -Simple run `meteor update` in your app. - -Great new features and no breaking changes (except one package deprecation). You can always check our [Roadmap](https://docs.meteor.com/roadmap.html) to understand what is next. - -## v1.12.2, 2021-10-12 - -#### Meteor Version Release - -* `meteor-tool@1.12.2` - - Patch to make 1.12.2 compatible with Push to Deploy feature in Galaxy (Meteor Cloud) - -## v1.12.1, 2021-01-06 - -### Breaking changes - -N/A - -### Migration steps - -N/A - -### Changes - -#### Highlights - -- Node.js 12.20.1 [release notes](https://nodejs.org/en/blog/vulnerability/january-2021-security-releases/) -- Fixes problem on IE because of modern syntax on `dynamic-import` package. - -#### Meteor Version Release - -* `dynamic-import@0.5.5` - - Fixes problem on IE because of modern syntax (arrow function). - -* `meteor-babel@7.10.6` - - Allows to disable sourceMap generation [#36](https://github.com/meteor/babel/pull/36) - -* `babel-compiler@7.5.5` - - Allows to disable sourceMap generation [#36](https://github.com/meteor/babel/pull/36) - -## v1.12, 2020-12-04 - -### Breaking changes - -- When importing types, you might need to use the "type" qualifier, like so: -```js -import { Point } from 'react-easy-crop/types'; -``` -to -```ts -import type { Point } from 'react-easy-crop/types'; -``` -Because now emitDecoratorsMetadata is enabled. - -- Refer to typescript breaking changes before migrating your existing project, from 3.7.6 to 4.1.2: https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes - -### Migration steps - -N/A - -### Changes - -#### Highlights -- TypeScript update from 3.7.6 to 4.1.2. - - enables decorators and metadata reflection. Important: these are stage 2 features so be aware that breaking changes could be introduced before they reach stage 3. - -#### Meteor Version Release -* `meteor-tool@1.12` - - updates TypeScript to 4.1.2. [#11225](https://github.com/meteor/meteor/pull/11225) and [#11255](https://github.com/meteor/meteor/pull/11255) - - adds new options for `meteor list` command (TODO pending link to updated doc). [#11165](https://github.com/meteor/meteor/pull/11165) - - supports Cordova add plugin command working again with plugin id or plugin name in the git URL as it was before Meteor 1.11. [#11202](https://github.com/meteor/meteor/pull/11202) - - avoids MiTM by downloading through https. [#11188](https://github.com/meteor/meteor/pull/11188) - -* `meteor-babel@7.10.5` - - updates TypeScript to 4.1.2 and enables decorators and metadata reflection. [#11225](https://github.com/meteor/meteor/pull/11225) and [#11255](https://github.com/meteor/meteor/pull/11255) - -* `minimongo@1.6.1` - - fixes a null reference exception, if an array contains null values while compiling a fields projection. [#10499](https://github.com/meteor/meteor/pull/10499). - -* `accounts-password@1.6.3` - - adds a new function `createUserVerifyingEmail` (TODO pending link to updated doc). [#11080](https://github.com/meteor/meteor/pull/11080) - - fixes a typo. [#11182](https://github.com/meteor/meteor/pull/11182) - -* `browser-content-policy@1.1.1` - - adds support to nonce - ```js - BrowserPolicy.content.allowScriptOrigin(`nonce-${nonce}`); - ``` - -* `accounts-ui@1.3.2` - - follow accounts-ui-unstyled release - -* `accounts-ui-unstyled@1.4.3` - - fixes the login form would send the server two login requests - - fixes the "forgot password" form would not only send the email but also refresh the page - -* `dynamic-import@0.5.4` - - fixes prefetching errors. [#11209](https://github.com/meteor/meteor/pull/11209) - - adds the option for dynamic-imports to fetch from the current origin instead of the absolute URL. [#11105](https://github.com/meteor/meteor/pull/11105) - -* `mongo-decimal@0.1.2` - - updates npm dependency `decimal.js` to v10.2.1 - -* `accounts-base@1.7.1` - - adds the ability to define default user fields published on login. [#11118](https://github.com/meteor/meteor/pull/11118) - -* `standard-minifier-css@1.7.0` - - modernize and update dependencies. [#11196](https://github.com/meteor/meteor/pull/11196) - - -#### Independent Releases -* `facebook-oauth@1.7.3` - - is now using Facebook GraphAPI v8. [#11160](https://github.com/meteor/meteor/pull/11160) - -## v1.11.1, 2020-09-16 - -### Breaking changes - -N/A - -### Migration steps - -N/A - -### Changes - -* `--apollo` skeleton was missing client cache setup [more](https://github.com/meteor/meteor/pull/11146) - -* `--vue` skeleton was updated to use proper folder structure [more](https://github.com/meteor/meteor/pull/11174) - -* All skeletons got their `npm` dependencies updated. [more](https://github.com/meteor/meteor/pull/11172) - -* Node.js has been updated to version [12.18.4](https://nodejs.org/en/blog/release/v12.18.4/), this is a [security release](https://nodejs.org/en/blog/vulnerability/september-2020-security-releases/) - -* Updated npm to version 6.14.8 [more](https://blog.npmjs.org/post/626732790304686080/release-6148) - -* `npm-mongo` version 3.8.1 was published, updating `mongodb` to [3.6.2](https://github.com/mongodb/node-mongodb-native/releases/tag/v3.6.2) [more](https://github.com/advisories/GHSA-pp7h-53gx-mx7r) - -* Updated PostCSS from 7.0.31 to 7.0.32 [more](https://github.com/meteor/meteor/issues/10682) - -* Allow android-webview-video-poster [more](https://github.com/meteor/meteor/pull/11159) - -## v1.11, 2020-08-18 - -### 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. - -* (Added later) Cordova add plugin is not working with plugin name in the git URL when the plugin id was different than the name in the config.xml. Fixed on [#11202](https://github.com/meteor/meteor/pull/11202) - -### Migration steps - -N/A - -### Changes - -* `meteor create --apollo` is now available thanks to [@StorytellerCZ](https://github.com/StorytellerCZ). PR [#11119](https://github.com/meteor/meteor/pull/11119) - -* `meteor create --vue` is now available thanks to [@chris-visser](https://github.com/chris-visser). PR [#11086](https://github.com/meteor/meteor/pull/11086) - -* `--cache-build` option is now available on `meteor deploy` command and you can use it safely all the time if you are using a Git repository to run your deploy. This is helpful if your upload is failing then you can retry just the upload and also if you deploy the same bundle to multiple environments. [Read more](https://cloud-guide.meteor.com/deploy-guide.html#cache-build). - -* Multiple optimizations in build performance, many of them for Windows thanks to [@zodern](https://github.com/zodern). PRs [#10838](https://github.com/meteor/meteor/pull/10838), [#11114](https://github.com/meteor/meteor/pull/11114), [#11115](https://github.com/meteor/meteor/pull/11115), [#11102](https://github.com/meteor/meteor/pull/11102), [#10839](https://github.com/meteor/meteor/pull/10839) - -* 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. - -* Node.js has been updated to version - [12.18.3](https://nodejs.org/en/blog/release/v12.18.3/) - -* Updated npm to version 6.14.5 - -* `mongodb` driver npm dependency has been updated to 3.6.0 - -* The version of MongoDB used by Meteor in development has been updated - from 4.2.5 to 4.2.8 - -## v1.10.2, 2020-04-21 - -### Breaking changes - -* The `babel-compiler` package, used by both `ecmascript` and - `typescript`, no longer supports stripping [Flow](https://flow.org/) - type annotations by default, which may be a breaking change if your - application (or Meteor package) relied on Flow syntax. - -### Migration steps - -* If you still need Babel's Flow plugins, you can install them with npm - and then enable them with a custom `.babelrc` file in your application's - (or package's) root directory: - ```json - { - "plugins": [ - "@babel/plugin-syntax-flow", - "@babel/plugin-transform-flow-strip-types" - ] - } - ``` - -### Changes - -* Adds support to override MongoDB options via Meteor settings. Code PR -[#10976](https://github.com/meteor/meteor/pull/10976), Docs PR -[#662](https://github.com/meteor/docs/pull/662) - -* The `meteor-babel` npm package has been updated to version 7.9.0. - -* The `typescript` npm package has been updated to version 3.8.3. - -* To pass Node command line flags to the server node instance, - now it is recommended to use `SERVER_NODE_OPTIONS` instead of `NODE_OPTIONS`. - Since Meteor 0.5.3, Meteor allowed to pass node command line flags via the `NODE_OPTIONS` - environment variable. - However, since Node version 8 / Meteor 1.6 this has become a default node - envar with the same behavior. The side effect is that this now also affects - Meteor tool. The command line parameters could already be set separately - via the `TOOL_NODE_FLAGS` envar. This is now also possible (again) for the server. - -* The version of MongoDB used by Meteor in development has been updated from - 4.2.1 to 4.2.5. - [PR #11020](https://github.com/meteor/meteor/pull/11020) - -* The `url` package now provides an isomorphic implementation of the [WHATWG `url()` - API](https://url.spec.whatwg.org/). - While remaining backwards compatible, you can now also import `URL` and `URLSearchParams` from `meteor/url`. - These will work for both modern and legacy browsers as well as node. - - -## v1.10.1, 2020-03-12 - -### Breaking changes - -* Cordova has been updated from version 7 to 9. We recommend that you test - your features that are taking advantage of Cordova plugins to be sure - they are still working as expected. - - * WKWebViewOnly is set by default now as true so if you are relying on - UIWebView or plugins that are using UIWebView APIs you probably want to - set it as false, you can do this by calling - `App.setPreference('WKWebViewOnly', false);` in your mobile-config.js. But we - don't recommend turning this into false because - [Apple have said](https://developer.apple.com/news/?id=12232019b) they are - going to reject apps using UIWebView. - -* Because MongoDB since 3.4 no longer supports 32-bit Windows, Meteor 1.10 has - also dropped support for 32-bit Windows. In other words, Meteor 1.10 supports - 64-bit Mac, Windows 64-bit, and Linux 64-bit. - -### Migration Steps -* If you get `Unexpected mongo exit code 62. Restarting.` when starting your local - MongoDB, you can either reset your project (`meteor reset`) - (if you don't care about your local data) - or you will need to update the feature compatibility version of your local MongoDB: - - 1. Downgrade your app to earlier version of Meteor `meteor update --release 1.9.2` - 2. Start your application - 3. While your application is running open a new terminal window, navigate to the - app directory and open `mongo` shell: `meteor mongo` - 4. Use: `db.adminCommand({ getParameter: 1, featureCompatibilityVersion: 1 })` to - check the current feature compatibility. - 5. If the returned version is less than 4.0 update like this: - `db.adminCommand({ setFeatureCompatibilityVersion: "4.2" })` - 6. You can now stop your app and update to Meteor 1.10. - - For more information about this, check out [MongoDB documentation](https://docs.mongodb.com/manual/release-notes/4.2-upgrade-standalone/). - -### Changes - -* The version of MongoDB used by Meteor in development has been updated - from 4.0.6 to 4.2.1, and the `mongodb` driver package has been updated - from 3.2.7 to 3.5.4, thanks to [@klaussner](https://github.com/klaussner). - [Feature #361](https://github.com/meteor/meteor-feature-requests/issues/361) - [PR #10723](https://github.com/meteor/meteor/pull/10723) - -* The `npm` command-line tool used by the `meteor npm` command (and by - Meteor internally) has been updated to version 6.14.0, and our - [fork](https://github.com/meteor/pacote/tree/v9.5.12-meteor) of its - `pacote` dependency has been updated to version 9.5.12. - -* Cordova was updated from version 7 to 9 - * cordova-lib from 7.1.0 to 9.0.1 [release notes](https://github.com/apache/cordova-lib/blob/master/RELEASENOTES.md) - * cordova-common from 2.1.1 to 3.2.1 [release notes](https://github.com/apache/cordova-common/blob/master/RELEASENOTES.md) - * cordova-android from 7.1.4 to 8.1.0 [release notes](https://github.com/apache/cordova-android/blob/master/RELEASENOTES.md) - * cordova-ios from 4.5.5 to 5.1.1 [release notes](https://github.com/apache/cordova-ios/blob/master/RELEASENOTES.md) - * cordova-plugin-wkwebview-engine from 1.1.4 to 1.2.1 [release notes](https://github.com/apache/cordova-plugin-wkwebview-engine/blob/master/RELEASENOTES.md#121-jul-20-2019) - * cordova-plugin-whitelist from 1.3.3 to 1.3.4 [release notes](https://github.com/apache/cordova-plugin-whitelist/blob/master/RELEASENOTES.md#134-jun-19-2019) - * cordova-plugin-splashscreen (included by mobile-experience > launch-screen) - from 4.1.0 to 5.0.3 [release notes](https://github.com/apache/cordova-plugin-splashscreen/blob/master/RELEASENOTES.md#503-may-09-2019) - * cordova-plugin-statusbar (included by mobile-experience > mobile-status-bar) - from 2.3.0 to 2.4.3 [release notes](https://github.com/apache/cordova-plugin-statusbar/blob/master/RELEASENOTES.md#243-jun-19-2019) - * On iOS WKWebViewOnly is set by default now as true. - * On iOS the Swift version is now set by default to `5` this change can make - your app to produce some warnings if your plugins are using old Swift code. - You can override the Swift version using - `App.setPreference('SwiftVersion', 4.2);` but we don't recommend that. - -* New command to ensure that Cordova dependencies are installed. Usage: - `meteor ensure-cordova-dependencies`. Meteor handles this automatically but in - some cases, like running in a CI, is useful to install them in advance. - -* You can now pass an `--exclude-archs` option to the `meteor run` and - `meteor test` commands to temporarily disable building certain web - architectures. For example, `meteor run --exclude-archs web.browser.legacy`. - Multiple architectures should be separated by commas. This option can be - used to improve (re)build times if you're not actively testing the - excluded architectures during development. - [Feature #333](https://github.com/meteor/meteor-feature-requests/issues/333), - [PR #10824](https://github.com/meteor/meteor/pull/10824) - -* `meteor create --react app` and `--typescript` now use `useTracker` hook instead of - `withTracker` HOC, it also uses `function` components instead of `classes`. - -## v1.9.3, 2020-03-09 - -### Breaking changes -* The MongoDB `retryWrites` option now defaults to `true` (it previously defaulted to false). Users of database services that don't support retryWrites will experience a fatal error due to this. - -### Migration Steps -* If you get the error `MongoError: This MongoDB deployment does not support retryable writes. Please add retryWrites=false to your connection string.`, append `retryWrites=false` to your MongoDB connection string. - -### Changes -* `mongodb` driver package has been updated - from 3.2.7 to 3.5.4 [#10961](https://github.com/meteor/meteor/pull/10961) - -## v1.9.2, 2020-02-20 - -### Breaking changes -N/A - -### Migration Steps -N/A - -### Changes - -* Node.js has been updated to version - [12.16.1](https://nodejs.org/en/blog/release/v12.16.1/), fixing several unintended - [regressions](https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V12.md#12.16.1) - introduced in 12.16.0. - -* The `meteor-babel` npm package has been updated to version 7.8.2. - -* The `typescript` npm package has been updated to version 3.7.5. - -## v1.9.1, 2020-02-18 - -### Breaking changes - -N/A - -### Migration Steps -N/A - -### Changes - -* Node.js has been updated to version - 12.16.0 from 12.14.0, which includes - security updates and small changes: - * [12.16.0](https://nodejs.org/en/blog/release/v12.16.0/) - * Updated V8 to [release v7.8](https://v8.dev/blog/v8-release-78) which includes improvements in performance, for example, object destructuring now is as fast as the equivalent variable assignment. - * [12.15.0](https://nodejs.org/en/blog/release/v12.15.0/) - -* `cursor.observeChanges` now accepts a second options argument. - If your observer functions do not mutate the passed arguments, you can specify - `{ nonMutatingCallbacks: true }`, which improves performance by reducing - the amount of data copies. - -## v1.9, 2020-01-09 - -### Breaking changes - -* Because Node.js 12 no longer supports 32-bit Linux, Meteor 1.9 has also - dropped support for 32-bit Linux. In other words, Meteor 1.9 supports - 64-bit Mac, Windows, and Linux, as well as 32-bit Windows. - -### Migration Steps -N/A - -### Changes - -* Node.js has been updated to version - [12.14.0](https://nodejs.org/en/blog/release/v12.14.0/), which includes - several major Node.js versions since 8.17.0 (used by Meteor 1.8.3): - * [12.0.0](https://nodejs.org/en/blog/release/v12.0.0/) - * [11.0.0](https://nodejs.org/en/blog/release/v10.0.0/) - * [10.0.0](https://nodejs.org/en/blog/release/v10.0.0/) - * [9.0.0](https://nodejs.org/en/blog/release/v9.0.0/) - -* The `fibers` npm package has been updated to version 4.0.3, which - includes [changes](https://github.com/laverdet/node-fibers/pull/429) - that may drastically reduce garbage collection pressure resulting from - heavy `Fiber` usage. - -* The `pathwatcher` npm package has been updated to use a fork of version - 8.0.2, with [PR #128](https://github.com/atom/node-pathwatcher/pull/128) - applied. - -* The `sqlite3` npm package has been updated to version 4.1.0. - -* The `node-gyp` npm package has been updated to version 6.0.1, and - `node-pre-gyp` has been updated to version 0.14.0. - -* The feature that restarts the application up to two times if it crashes - on startup has been removed. - [Feature #335](https://github.com/meteor/meteor-feature-requests/issues/335) - [PR #10345](https://github.com/meteor/meteor/pull/10345) - -* Facebook OAuth has been updated to call v5 API endpoints. [PR #10738](https://github.com/meteor/meteor/pull/10738) - -* `Meteor.user()`, `Meteor.findUserByEmail()` and `Meteor.findUserByUserName()` can take a new - `options` parameter which can be used to limit the returned fields. Useful for minimizing - DB bandwidth on the server and avoiding unnecessary reactive UI updates on the client. - [Issue #10469](https://github.com/meteor/meteor/issues/10469) - -* `Accounts.config()` has a new option `defaultFieldSelector` which will apply to all - `Meteor.user()` and `Meteor.findUserBy...()` functions without explicit field selectors, and - also to all `onLogin`, `onLogout` and `onLoginFailure` callbacks. This is useful if you store - large data on the user document (e.g. a growing list of transactions) which do no need to be - retrieved from the DB whenever you or a package author call `Meteor.user()` without limiting the - fields. [Issue #10469](https://github.com/meteor/meteor/issues/10469) - -* Lots of internal calls to `Meteor.user()` without field specifiers in `accounts-base` and - `accounts-password` packages have been optimized with explicit field selectors to only - the fields needed by the functions they are in. - [Issue #10469](https://github.com/meteor/meteor/issues/10469) - -## v1.8.3, 2019-12-19 - -### Migration Steps - -* If your application uses `blaze-html-templates`, the Meteor `jquery` - package will be automatically installed in your `.meteor/packages` file - when you update to Meteor 1.8.3. However, this new version of the Meteor - `jquery` package no longer bundles its own copy of the `jquery` npm - implementation, so you may need to install `jquery` from npm by running - ```sh - meteor npm i jquery - ``` - in your application directory. Symptoms of not installing jquery include - a blank browser window, with helpful error messages in the console. - -### Changes - -* Node has been updated to version - [8.17.0](https://nodejs.org/en/blog/release/v8.17.0/). - -* The `npm` npm package has been updated to version 6.13.4, and our - [fork](https://github.com/meteor/pacote/tree/v9.5.11-meteor) of its - `pacote` dependency has been updated to version 9.5.11, an important - [security release](https://nodejs.org/en/blog/vulnerability/december-2019-security-releases/). - -* Prior to Meteor 1.8.3, installing the `jquery` package from npm along - with the Meteor `jquery` package could result in bundling jQuery twice. - Thanks to [PR #10498](https://github.com/meteor/meteor/pull/10498), the - Meteor `jquery` package will no longer provide its own copy of jQuery, - but will simply display a warning in the console if the `jquery` npm - package cannot be found in your `node_modules` directory. If you are - using `blaze` in your application, updating to Meteor 1.8.3 will - automatically add this new version of the Meteor `jquery` package to - your application if you were not already using it (thanks to - [PR #10801](https://github.com/meteor/meteor/pull/10801)), but you might - need to run `meteor npm i jquery` manually, so that `blaze` can import - `jquery` from your `node_modules` directory. - -* The `meteor-babel` npm package has been updated to version 7.7.5. - -* The `typescript` npm package has been updated to version 3.7.3. - -## v1.8.2, 2019-11-14 - -### Breaking changes - -* Module-level variable declarations named `require` or `exports` are no - longer automatically renamed, so they may collide with module function - parameters of the same name, leading to errors like - `Uncaught SyntaxError: Identifier 'exports' has already been declared`. - See [this comment](https://github.com/meteor/meteor/pull/10522#issuecomment-535535056) - by [@SimonSimCity](https://github.com/SimonSimCity). - -* `Plugin.fs` methods are now always sync and no longer accept a callback. - -### Migration Steps - -* Be sure to update the `@babel/runtime` npm package to its latest version - (currently 7.7.2): - ```sh - meteor npm install @babel/runtime@latest - ``` - -* New Meteor applications now depend on `meteor-node-stubs@1.0.0`, so it - may be a good idea to update to the same major version: - ```sh - meteor npm install meteor-node-stubs@next - ``` - -* If you are the author of any Meteor packages, and you encounter errors - when using those packages in a Meteor 1.8.2 application (for example, - `module.watch` being undefined), we recommend that you bump the minor - version of your package and republish it using Meteor 1.8.2, so - Meteor 1.8.2 applications will automatically use the new version of the - package, as compiled by Meteor 1.8.2: - ```sh - cd path/to/your/package - # Add api.versionsFrom("1.8.2") to Package.onUse in package.js... - meteor --release 1.8.2 publish - ``` - This may not be necessary for all packages, especially those that have - been recently republished using Meteor 1.8.1, or local packages in the - `packages/` directory (which are always recompiled from source). - However, republishing packages is a general solution to a wide variety - of package versioning and compilation problems, and package authors can - make their users' lives easier by handling these issues proactively. - -### Changes - -* Node has been updated to version - [8.16.2](https://nodejs.org/en/blog/release/v8.16.2/). - -* The `npm` npm package has been updated to version 6.13.0, and our - [fork](https://github.com/meteor/pacote/tree/v9.5.9-meteor) of its - `pacote` dependency has been updated to version 9.5.9. - -* New Meteor applications now include an official `typescript` package, - supporting TypeScript compilation of `.ts` and `.tsx` modules, which can - be added to existing apps by running `meteor add typescript`. - -* New TypeScript-based Meteor applications can be created by running - ```sh - meteor create --typescript new-typescript-app - ``` - This app skeleton contains a recommended tsconfig.json file, and should - serve as a reference for how to make TypeScript and Meteor work together - (to the best of our current knowledge). - [PR #10695](https://github.com/meteor/meteor/pull/10695) - -* When bundling modern client code, the Meteor module system now prefers - the `"module"` field in `package.json` (if defined) over the `"main"` - field, which should unlock various `import`/`export`-based optimizations - such as tree shaking in future versions of Meteor. As before, server - code uses only the `"main"` field, like Node.js, and legacy client code - prefers `"browser"`, `"main"`, and then `"module"`. - [PR #10541](https://github.com/meteor/meteor/pull/10541), - [PR #10765](https://github.com/meteor/meteor/pull/10765). - -* ECMAScript module syntax (`import`, `export`, and dynamic `import()`) is - now supported by default everywhere, including in modules imported from - `node_modules`, thanks to the [Reify](https://github.com/benjamn/reify) - compiler. - -* If you need to import code from `node_modules` that uses modern syntax - beyond module syntax, it is now possible to enable recompilation for - specific npm packages using the `meteor.nodeModules.recompile` option in - your application's `package.json` file. - See [PR #10603](https://github.com/meteor/meteor/pull/10603) for further - explanation. - -* The Meteor build process is now able to detect whether files changed in - development were actually used by the server bundle, so that a full - server restart can be avoided when no files used by the server bundle - have changed. Client-only refreshes are typically much faster than - server restarts. Run `meteor add autoupdate` to enable client refreshes, - if you are not already using the `autoupdate` package. - [Issue #10449](https://github.com/meteor/meteor/issues/10449) - [PR #10686](https://github.com/meteor/meteor/pull/10686) - -* The `mongodb` npm package used by the `npm-mongo` Meteor package has - been updated to version 3.2.7. - -* The `meteor-babel` npm package has been updated to version 7.7.0, - enabling compilation of the `meteor/tools` codebase with TypeScript - (specifically, version 3.7.2 of the `typescript` npm package). - -* The `reify` npm package has been updated to version 0.20.12. - -* The `core-js` npm package used by `ecmascript-runtime-client` and - `ecmascript-runtime-server` has been updated to version 3.2.1. - -* The `terser` npm package used by `minifier-js` (and indirectly by - `standard-minifier-js`) has been updated to version 4.3.1. - -* The `node-gyp` npm package has been updated to version 5.0.1, and - `node-pre-gyp` has been updated to 0.13.0. - -* The `optimism` npm package has been updated to version 0.11.3, which - enables caching of thrown exceptions as well as ordinary results, in - addition to performance improvements. - -* The `pathwatcher` npm package has been updated to version 8.1.0. - -* The `underscore` npm package installed in the Meteor dev bundle (for use - by the `meteor/tools` codebase) has been updated from version 1.5.2 to - version 1.9.1, and `@types/underscore` has been installed for better - TypeScript support. - -* In addition to the `.js` and `.jsx` file extensions, the `ecmascript` - compiler plugin now automatically handles JavaScript modules with the - `.mjs` file extension. - -* Add `--cordova-server-port` option to override local port where Cordova will - serve static resources, which is useful when multiple Cordova apps are built - from the same application source code, since by default the port is generated - using the ID from the application's `.meteor/.id` file. - -* The `--test-app-path ` option for `meteor test-packages` and - `meteor test` now accepts relative paths as well as absolute paths. - -## v1.8.1, 2019-04-03 - -### Breaking changes - -* Although we are not aware of any specific backwards incompatibilities, - the major upgrade of `cordova-android` from 6.4.0 to 7.1.4 likely - deserves extra attention, if you use Cordova to build Android apps. - -### Migration Steps -N/A - -### Changes - -* Node has been updated from version 8.11.4 to version - [8.15.1](https://nodejs.org/en/blog/release/v8.15.1/), an important - [security release](https://nodejs.org/en/blog/vulnerability/february-2019-security-releases/), - which includes the changes from four other minor releases: - * [8.15.0](https://nodejs.org/en/blog/release/v8.15.0/) - * [8.14.0](https://nodejs.org/en/blog/release/v8.14.0/), an important - [security release](https://nodejs.org/en/blog/vulnerability/november-2018-security-releases/) - * [8.12.0](https://nodejs.org/en/blog/release/v8.12.0/) - * [8.13.0](https://nodejs.org/en/blog/release/v8.13.0/) - - > Note: While Node 8.12.0 included changes that may improve the - performance of Meteor apps, there have been reports of CPU usage spikes - in production due to excessive garbage collection, so this version of - Meteor should be considered experimental until those problems have been - fixed. [Issue #10216](https://github.com/meteor/meteor/issues/10216) - -* The `npm` tool has been upgraded to version - [6.9.0](https://github.com/npm/cli/releases/tag/v6.9.0), and our - [fork](https://github.com/meteor/pacote/tree/v9.5.0-meteor) of its - `pacote` dependency has been updated to version 9.5.0. - -* Mongo has been upgraded to version 4.0.6 for 64-bit systems (was 4.0.2), - and 3.2.22 for 32-bit systems (was 3.2.19). The `mongodb` npm package - used by `npm-mongo` has been updated to version 3.1.13 (was 3.1.6). - -* The `fibers` npm package has been updated to version 3.1.1, a major - update from version 2.0.0. Building this version of `fibers` requires a - C++11 compiler, unlike previous versions. If you deploy your Meteor app - manually (without using Galaxy), you may need to update the version of - `g++` used when running `npm install` in the `bundle/programs/server` - directory. - -* The `meteor-babel` npm package has been updated to version 7.3.4. - -* Cordova Hot Code Push mechanism is now switching versions explicitly with - call to `WebAppLocalServer.switchToPendingVersion` instead of trying to - switch every time a browser reload is detected. If you use any third - party package or have your own HCP routines implemented be sure to call - it before forcing a browser reload. If you use the automatic reload from - the `Reload` meteor package you do not need to do anything. - [cordova-plugin-meteor-webapp PR #62](https://github.com/meteor/cordova-plugin-meteor-webapp/pull/62) - -* Multiple Cordova-related bugs have been fixed, including Xcode 10 build - incompatibilities and hot code push errors due to duplicated - images/assets. [PR #10339](https://github.com/meteor/meteor/pull/10339) - -* The `cordova-android` and `cordova-ios` npm dependencies have been - updated to 7.1.4 (from 6.4.0) and 4.5.5 (from 4.5.4), respectively. - -* Build performance has improved (especially on Windows) thanks to - additional caching implemented by [@zodern](https://github.com/zodern) - in PRs [#10399](https://github.com/meteor/meteor/pull/10399), - [#10452](https://github.com/meteor/meteor/pull/10452), - [#10453](https://github.com/meteor/meteor/pull/10453), and - [#10454](https://github.com/meteor/meteor/pull/10454). - -* The `meteor mongo` command no longer uses the `--quiet` option, so the - normal startup text will be displayed, albeit without the banner about - Mongo's free monitoring service. See this - [MongoDB Jira issue](https://jira.mongodb.org/browse/SERVER-38862) - for more details. - -* In Meteor packages, `client/` and `server/` directories no longer have - any special meaning. In application code, `client/` directories are - ignored during the server build, and `server/` directories are ignored - during the client build, as before. This special behavior previously - applied to packages as well, but has now been removed. - [Issue #10393](https://github.com/meteor/meteor/issues/10393) - [PR #10414](https://github.com/meteor/meteor/pull/10414) - -* If your application is using Git for version control, the current Git - commit hash will now be exposed via the `Meteor.gitCommitHash` property - while the app is running (in both server and client code), and also via - the `"gitCommitHash"` property in the `star.json` file located in the - root directory of builds produced by `meteor build`, for consumption by - deployment tools. If you are not using Git, neither property will be - defined. [PR #10442](https://github.com/meteor/meteor/pull/10442) - -* The Meteor Tool now uses a more reliable method (the MongoDB - [`isMaster` command](https://docs.mongodb.com/manual/reference/command/isMaster/)) - to detect when the local development database has started and is ready to - accept read and write operations. - [PR #10500](https://github.com/meteor/meteor/pull/10500) - -* Setting the `x-no-compression` request header will prevent the `webapp` - package from compressing responses with `gzip`, which may be useful if - your Meteor app is behind a proxy that compresses resources with another - compression algorithm, such as [brotli](https://github.com/google/brotli). - [PR #10378](https://github.com/meteor/meteor/pull/10378) - -## v1.8.0.2, 2019-01-07 - -### Breaking changes -N/A - -### Migration steps -N/A - -### Changes - -* The [React tutorial](https://www.meteor.com/tutorials/react/creating-an-app) - has been updated to address a number of inaccuracies due to changes in - recent Meteor releases that were not fully incorporated back into the - tutorial. As a reminder, Meteor now supports a `meteor create --react` - command that can be used to create a new React-based app quickly. - -* Fixed a bug where modules named with `*.app-tests.js` (or `*.tests.js`) - file extensions sometimes could not be imported by the - `meteor.testModule` entry point when running the `meteor test` command - (or `meteor test --full-app`). - [PR #10402](https://github.com/meteor/meteor/pull/10402) - -* The `meteor-promise` package has been updated to version 0.8.7, which - includes a [commit](https://github.com/meteor/promise/commit/bbe4f0d20b70417950381aea112993c4cc8c1168) - that should prevent memory leaks when excess fibers are discarded from - the `Fiber` pool. - -* The `meteor-babel` npm package has been updated to version 7.2.0, - improving source maps for applications with custom `.babelrc` files. - -## v1.8.0.1, 2018-11-23 - -### Breaking changes -N/A - -### Migration steps -N/A - -### Changes - -* The `useragent` npm package used by `webapp` and (indirectly) by the - `modern-browsers` package has been updated from 2.2.1 to 2.3.0. The - `chromium` browser name has been aliased to use the same minimum modern - version as `chrome`, and browser names are now processed - case-insensitively by the `modern-browsers` package. - [PR #10334](https://github.com/meteor/meteor/pull/10334) - -* Fixed a module caching bug that allowed `findImportedModuleIdentifiers` - to return the same identifiers for the modern and legacy versions of a - given module, even if the set of imported modules is different (for - example, because Babel injects fewer `@babel/runtime/...` imports into - modern code). Now the caching is always based on the SHA-1 hash of the - _generated_ code, rather than trusting the hash provided by compiler - plugins. [PR #10330](https://github.com/meteor/meteor/pull/10330) - -## v1.8, 2018-10-08 - -### Breaking changes -N/A - -### Migration Steps - -* Update the `@babel/runtime` npm package to version 7.0.0 or later: - ```sh - meteor npm install @babel/runtime@latest - ``` - -### Changes - -* Although Node 8.12.0 has been released, Meteor 1.8 still uses Node - 8.11.4, due to concerns about excessive garbage collection and CPU usage - in production. To enable Galaxy customers to use Node 8.12.0, we are - planning a quick follow-up Meteor 1.8.1 release, which can be obtained - by running the command - ```bash - meteor update --release 1.8.1-beta.n - ``` - where `-beta.n` is the latest beta release according to the - [releases](https://github.com/meteor/meteor/releases) page (currently - `-beta.6`). - [Issue #10216](https://github.com/meteor/meteor/issues/10216) - [PR #10248](https://github.com/meteor/meteor/pull/10248) - -* Meteor 1.7 introduced a new client bundle called `web.browser.legacy` in - addition to the `web.browser` (modern) and `web.cordova` bundles. - Naturally, this extra bundle increased client (re)build times. Since - developers spend most of their time testing the modern bundle in - development, and the legacy bundle mostly provides a safe fallback in - production, Meteor 1.8 cleverly postpones building the legacy bundle - until just after the development server restarts, so that development - can continue as soon as the modern bundle has finished building. Since - the legacy build happens during a time when the build process would - otherwise be completely idle, the impact of the legacy build on server - performance is minimal. Nevertheless, the legacy bundle still gets - rebuilt regularly, so any legacy build errors will be surfaced in a - timely fashion, and legacy clients can test the new legacy bundle by - waiting a bit longer than modern clients. Applications using the - `autoupdate` or `hot-code-push` packages will reload modern and legacy - clients independently, once each new bundle becomes available. - [Issue #9948](https://github.com/meteor/meteor/issues/9948) - [PR #10055](https://github.com/meteor/meteor/pull/10055) - -* Compiler plugins that call `inputFile.addJavaScript` or - `inputFile.addStylesheet` may now delay expensive compilation work by - passing partial options (`{ path, hash }`) as the first argument, - followed by a callback function as the second argument, which will be - called by the build system once it knows the module will actually be - included in the bundle. For example, here's the old implementation of - `BabelCompiler#processFilesForTarget`: - ```js - processFilesForTarget(inputFiles) { - inputFiles.forEach(inputFile => { - var toBeAdded = this.processOneFileForTarget(inputFile); - if (toBeAdded) { - inputFile.addJavaScript(toBeAdded); - } - }); - } - ``` - and here's the new version: - ```js - processFilesForTarget(inputFiles) { - inputFiles.forEach(inputFile => { - if (inputFile.supportsLazyCompilation) { - inputFile.addJavaScript({ - path: inputFile.getPathInPackage(), - hash: inputFile.getSourceHash(), - }, function () { - return this.processOneFileForTarget(inputFile); - }); - } else { - var toBeAdded = this.processOneFileForTarget(inputFile); - if (toBeAdded) { - inputFile.addJavaScript(toBeAdded); - } - } - }); - } - ``` - If you are an author of a compiler plugin, we strongly recommend using - this new API, since unnecessary compilation of files that are not - included in the bundle can be a major source of performance problems for - compiler plugins. Although this new API is only available in Meteor 1.8, - you can use `inputFile.supportsLazyCompilation` to determine dynamically - whether the new API is available, so you can support older versions of - Meteor without having to publish multiple versions of your package. [PR - #9983](https://github.com/meteor/meteor/pull/9983) - -* New [React](https://reactjs.org/)-based Meteor applications can now be - created using the command - ```bash - meteor create --react new-react-app - ``` - Though relatively simple, this application template reflects the ideas - of many contributors, especially [@dmihal](https://github.com/dmihal) - and [@alexsicart](https://github.com/alexsicart), and it will no doubt - continue to evolve in future Meteor releases. - [Feature #182](https://github.com/meteor/meteor-feature-requests/issues/182) - [PR #10149](https://github.com/meteor/meteor/pull/10149) - -* The `.meteor/packages` file supports a new syntax for overriding - problematic version constraints from packages you do not control. - - If a package version constraint in `.meteor/packages` ends with a `!` - character, any other (non-`!`) constraints on that package elsewhere in - the application will be _weakened_ to allow any version greater than or - equal to the constraint, even if the major/minor versions do not match. - - For example, using both CoffeeScript 2 and `practicalmeteor:mocha` used - to be impossible (or at least very difficult) because of this - [`api.versionsFrom("1.3")`](https://github.com/practicalmeteor/meteor-mocha/blob/3a2658070a920f8846df48bb8d8c7b678b8c6870/package.js#L28) - statement, which unfortunately constrained the `coffeescript` package to - version 1.x. In Meteor 1.8, if you want to update `coffeescript` to - 2.x, you can relax the `practicalmeteor:mocha` constraint by putting - ``` - coffeescript@2.2.1_1! # note the ! - ``` - in your `.meteor/packages` file. The `coffeescript` version still needs - to be at least 1.x, so that `practicalmeteor:mocha` can count on that - minimum. However, `practicalmeteor:mocha` will no longer constrain the - major version of `coffeescript`, so `coffeescript@2.2.1_1` will work. - - [Feature #208](https://github.com/meteor/meteor-feature-requests/issues/208) - [Commit 4a70b12e](https://github.com/meteor/meteor/commit/4a70b12eddef00b6700f129e90018a6076cb1681) - [Commit 9872a3a7](https://github.com/meteor/meteor/commit/9872a3a71df033e4cf6290b75fea28f44427c0c2) - -* The `npm` package has been upgraded to version 6.4.1, and our - [fork](https://github.com/meteor/pacote/tree/v8.1.6-meteor) of its - `pacote` dependency has been rebased against version 8.1.6. - -* The `node-gyp` npm package has been updated to version 3.7.0, and the - `node-pre-gyp` npm package has been updated to version 0.10.3. - -* Scripts run via `meteor npm ...` can now use the `meteor` command more - safely, since the `PATH` environment variable will now be set so that - `meteor` always refers to the same `meteor` used to run `meteor npm`. - [PR #9941](https://github.com/meteor/meteor/pull/9941) - -* Minimongo's behavior for sorting fields containing an array - is now compatible with the behavior of [Mongo 3.6+](https://docs.mongodb.com/manual/release-notes/3.6-compatibility/#array-sort-behavior). - Note that this means it is now incompatible with the behavior of earlier MongoDB versions. - [PR #10214](https://github.com/meteor/meteor/pull/10214) - -* Meteor's `self-test` has been updated to use "headless" Chrome rather - than PhantomJS for browser tests. PhantomJS can still be forced by - passing the `--phantom` flag to the `meteor self-test` command. - [PR #9814](https://github.com/meteor/meteor/pull/9814) - -* Importing a directory containing an `index.*` file now works for - non-`.js` file extensions. As before, the list of possible extensions is - defined by which compiler plugins you have enabled. - [PR #10027](https://github.com/meteor/meteor/pull/10027) - -* Any client (modern or legacy) may now request any static JS or CSS - `web.browser` or `web.browser.legacy` resource, even if it was built for - a different architecture, which greatly simplifies CDN setup if your CDN - does not forward the `User-Agent` header to the origin. - [Issue #9953](https://github.com/meteor/meteor/issues/9953) - [PR #9965](https://github.com/meteor/meteor/pull/9965) - -* Cross-origin dynamic `import()` requests will now succeed in more cases. - [PR #9954](https://github.com/meteor/meteor/pull/9954) - -* Dynamic CSS modules (which are compiled to JS and handled like any other - JS module) will now be properly minified in production and source mapped - in development. [PR #9998](https://github.com/meteor/meteor/pull/9998) - -* While CSS is only minified in production, CSS files must be merged - together into a single stylesheet in both development and production. - This merging is [cached by `standard-minifier-css`](https://github.com/meteor/meteor/blob/183d5ff9500d908d537f58d35ce6cd6d780ab270/packages/standard-minifier-css/plugin/minify-css.js#L58-L62) - so that it does not happen on every rebuild in development, but not all - CSS minifier packages use the same caching techniques. Thanks to - [1ed095c36d](https://github.com/meteor/meteor/pull/9942/commits/1ed095c36d7b2915872eb0c943dae0c4f870d7e4), - this caching is now performed within the Meteor build tool, so it works - the same way for all CSS minifier packages, which may eliminate a few - seconds of rebuild time for projects with lots of CSS. - -* The `meteor-babel` npm package used by `babel-compiler` has been updated - to version 7.1.0. **Note:** This change _requires_ also updating the - `@babel/runtime` npm package to version 7.0.0-beta.56 or later: - ```sh - meteor npm install @babel/runtime@latest - ``` - [`meteor-babel` issue #22](https://github.com/meteor/babel/issues/22) - -* The `@babel/preset-env` and `@babel/preset-react` presets will be - ignored by Meteor if included in a `.babelrc` file, since Meteor already - provides equivalent/superior functionality without them. However, you - should feel free to leave these plugins in your `.babelrc` file if they - are needed by external tools. - -* The `install` npm package used by `modules-runtime` has been updated to - version 0.12.0. - -* The `reify` npm package has been updated to version 0.17.3, which - introduces the `module.link(id, {...})` runtime method as a replacement - for `module.watch(require(id), {...})`. Note: in future versions of - `reify` and Meteor, the `module.watch` runtime API will be removed, but - for now it still exists (and is used to implement `module.link`), so - that existing code will continue to work without recompilation. - -* The `uglify-es` npm package used by `minifier-js` has been replaced with - [`terser@3.9.2`](https://www.npmjs.com/package/terser), a fork of - `uglify-es` that appears to be (more actively) maintained. - [Issue #10042](https://github.com/meteor/meteor/issues/10042) - -* Mongo has been updated to version 4.0.2 and the `mongodb` npm package - used by `npm-mongo` has been updated to version 3.1.6. - [PR #10058](https://github.com/meteor/meteor/pull/10058) - [Feature Request #269](https://github.com/meteor/meteor-feature-requests/issues/269) - -* When a Meteor application uses a compiler plugin to process files with a - particular file extension (other than `.js` or `.json`), those file - extensions should be automatically appended to imports that do not - resolve as written. However, this behavior was not previously enabled - for modules inside `node_modules`. Thanks to - [8b04c25390](https://github.com/meteor/meteor/pull/9942/commits/8b04c253900e4ca2a194d2fcaf6fc2ce9a9085e7), - the same file extensions that are applied to modules outside the - `node_modules` directory will now be applied to those within it, though - `.js` and `.json` will always be tried first. - -* As foreshadowed in this [talk](https://youtu.be/vpCotlPieIY?t=29m18s) - about Meteor 1.7's modern/legacy bundling system - ([slides](https://slides.com/benjamn/meteor-night-may-2018#/46)), Meteor - now provides an isomorphic implementation of the [WHATWG `fetch()` - API](https://fetch.spec.whatwg.org/), which can be installed by running - ```sh - meteor add fetch - ``` - This package is a great demonstration of the modern/legacy bundling - system, since it has very different implementations in modern - browsers, legacy browsers, and Node. - [PR #10029](https://github.com/meteor/meteor/pull/10029) - -* The [`bundle-visualizer` - package](https://github.com/meteor/meteor/tree/release-1.7.1/packages/non-core/bundle-visualizer) - has received a number of UI improvements thanks to work by - [@jamesmillerburgess](https://github.com/jamesmillerburgess) in - [PR #10025](https://github.com/meteor/meteor/pull/10025). - [Feature #310](https://github.com/meteor/meteor-feature-requests/issues/310) - -* Sub-resource integrity hashes (sha512) can now be enabled for static CSS - and JS assets by calling `WebAppInternals.enableSubresourceIntegrity()`. - [PR #9933](https://github.com/meteor/meteor/pull/9933) - [PR #10050](https://github.com/meteor/meteor/pull/10050) - -* The environment variable `METEOR_PROFILE=milliseconds` now works for the - build portion of the `meteor build` and `meteor deploy` commands. - [Feature #239](https://github.com/meteor/meteor-feature-requests/issues/239) - -* Babel compiler plugins will now receive a `caller` option of the - following form: - ```js - { name: "meteor", arch } - ``` - where `arch` is the target architecture, e.g. `os.*`, `web.browser`, - `web.cordova`, or `web.browser.legacy`. - [PR #10211](https://github.com/meteor/meteor/pull/10211) - -## v1.7.0.5, 2018-08-16 - -### Breaking changes -N/A - -### Migration Steps -N/A - -### Changes - -* Node has been updated to version - [8.11.4](https://nodejs.org/en/blog/release/v8.11.4/), an important - [security release](https://nodejs.org/en/blog/vulnerability/august-2018-security-releases/). - -## v1.7.0.4, 2018-08-07 - -### Breaking changes -N/A - -### Migration Steps -N/A - -### Changes - -* The npm package `@babel/runtime`, which is depended on by most Meteor - apps, introduced a breaking change in version `7.0.0-beta.56` with the - removal of the `@babel/runtime/helpers/builtin` directory. While this - change has clear benefits in the long term, in the short term it has - been disruptive for Meteor 1.7.0.x applications that accidentally - updated to the latest version of `@babel/runtime`. Meteor 1.7.0.4 is a - patch release that provides better warnings about this problem, and - ensures newly created Meteor applications do not use `7.0.0-beta.56`. - [PR #10134](https://github.com/meteor/meteor/pull/10134) - -* The `npm` package has been upgraded to version 6.3.0, and our - [fork](https://github.com/meteor/pacote/tree/v8.1.6-meteor) of its - `pacote` dependency has been rebased against version 8.1.6. - [Issue #9940](https://github.com/meteor/meteor/issues/9940) - -* The `reify` npm package has been updated to version 0.16.4. - -## v1.7.0.3, 2018-06-13 - -### Breaking changes -N/A - -### Migration Steps -N/A - -### Changes - -* Fixed [Issue #9991](https://github.com/meteor/meteor/issues/9991), - introduced in - [Meteor 1.7.0.2](https://github.com/meteor/meteor/pull/9990) - by [PR #9977](https://github.com/meteor/meteor/pull/9977). - -## v1.7.0.2, 2018-06-13 - -### Breaking changes -N/A - -### Migration Steps -N/A - -### Changes - -* Node has been updated to version - [8.11.3](https://nodejs.org/en/blog/release/v8.11.3/), an important - [security release](https://nodejs.org/en/blog/vulnerability/june-2018-security-releases/). - -* The `meteor-babel` npm package has been updated to version - [7.0.0-beta.51](https://github.com/babel/babel/releases/tag/v7.0.0-beta.51). - -* Meteor apps created with `meteor create` or `meteor create --minimal` - will now have a directory called `tests/` rather than `test/`, so that - test code will not be eagerly loaded if you decide to remove the - `meteor.mainModule` configuration from `package.json`, thanks to - [PR #9977](https://github.com/meteor/meteor/pull/9977) by - [@robfallows](https://github.com/robfallows). - [Issue #9961](https://github.com/meteor/meteor/issues/9961) - -## v1.7.0.1, 2018-05-29 - -### Breaking changes - -* The `aggregate` method of raw Mongo collections now returns an - `AggregationCursor` rather than returning the aggregation result - directly. To obtain an array of aggregation results, you will need to - call the `.toArray()` method of the cursor: - ```js - // With MongoDB 2.x, callback style: - rawCollection.aggregate( - pipeline, - (error, results) => {...} - ); - - // With MongoDB 2.x, wrapAsync style: - const results = Meteor.wrapAsync( - rawCollection.aggregate, - rawCollection - )(pipeline); - - // With MongoDB 3.x, callback style: - rawCollection.aggregate( - pipeline, - (error, aggregationCursor) => { - ... - const results = aggregationCursor.toArray(); - ... - } - ); - - // With MongoDB 3.x, wrapAsync style: - const results = Meteor.wrapAsync( - rawCollection.aggregate, - rawCollection - )(pipeline).toArray(); - ``` - [Issue #9936](https://github.com/meteor/meteor/issues/9936) - -### Migration Steps - -* Update `@babel/runtime` (as well as other Babel-related packages) and - `meteor-node-stubs` to their latest versions: - ```sh - meteor npm install @babel/runtime@latest meteor-node-stubs@latest - ``` - -### Changes - -* Reverted an [optimization](https://github.com/meteor/meteor/pull/9825) - introduced in Meteor 1.7 to stop scanning `node_modules` for files that - might be of interest to compiler plugins, since the intended workarounds - (creating symlinks) did not satisfy all existing use cases. We will - revisit this optimization in Meteor 1.8. - [mozfet/meteor-autoform-materialize#43](https://github.com/mozfet/meteor-autoform-materialize/issues/43) - -* After updating to Meteor 1.7 or 1.7.0.1, you should update the - `@babel/runtime` npm package (as well as other Babel-related packages) - to their latest versions, along with the `meteor-node-stubs` package, - by running the following command: - ```sh - meteor npm install @babel/runtime@latest meteor-node-stubs@latest - ``` - -## v1.7, 2018-05-28 - -### Breaking changes -N/A - -### Migration Steps -N/A - -### Changes - -* More than 80% of internet users worldwide have access to a web browser - that natively supports the latest ECMAScript features and keeps itself - updated automatically, which means new features become available almost - as soon as they ship. In other words, the future we envisioned when we - first began [compiling code with - Babel](https://blog.meteor.com/how-much-does-ecmascript-2015-cost-2ded41d70914) - is finally here, yet most web frameworks and applications still compile - a single client-side JavaScript bundle that must function simultaneously - in the oldest and the newest browsers the application developer wishes - to support. - - That choice is understandable, because the alternative is daunting: not - only must you build multiple JavaScript and CSS bundles for different - browsers, with different dependency graphs and compilation rules and - webpack configurations, but your server must also be able to detect the - capabilities of each visiting client, so that it can deliver the - appropriate assets at runtime. Testing a matrix of different browsers - and application versions gets cumbersome quickly, so it's no surprise - that responsible web developers would rather ship a single, well-tested - bundle, and forget about taking advantage of modern features until - legacy browsers have disappeared completely. - - With Meteor 1.7, this awkward balancing act is no longer necessary, - because Meteor now automatically builds two sets of client-side assets, - one tailored to the capabilities of modern browsers, and the other - designed to work in all supported browsers, thus keeping legacy browsers - working exactly as they did before. Best of all, the entire Meteor - community relies on the same system, so any bugs or differences in - behavior can be identified and fixed quickly. - - In this system, a "modern" browser can be loosely defined as one with - full native support for `async` functions and `await` expressions, which - includes more than 80% of the world market, and 85% of the US market - ([source](https://caniuse.com/#feat=async-functions)). This standard may - seem extremely strict, since `async`/`await` was [just finalized in - ECMAScript 2017](http://2ality.com/2016/10/async-function-tips.html), - but the statistics clearly justify it. As another example, any modern - browser can handle native `class` syntax, though newer syntax like class - fields may still need to be compiled for now, whereas a legacy browser - will need compilation for both advanced and basic `class` syntax. And of - course you can safely assume that any modern browser has a native - `Promise` implementation, because `async` functions must return - `Promise`s. The list goes on and on. - - This boundary between modern and legacy browsers is designed to be tuned - over time, not only by the Meteor framework itself but also by each - individual Meteor application. For example, here's how the minimum - versions for native ECMAScript `class` support might be expressed: - - ```js - import { setMinimumBrowserVersions } from "meteor/modern-browsers"; - - setMinimumBrowserVersions({ - chrome: 49, - firefox: 45, - edge: 12, - ie: Infinity, // Sorry, IE11. - mobile_safari: [9, 2], // 9.2.0+ - opera: 36, - safari: 9, - electron: 1, - }, "classes"); - ``` - - The minimum modern version for each browser is simply the maximum of all - versions passed to `setMinimumBrowserVersions` for that browser. The - Meteor development server decides which assets to deliver to each client - based on the `User-Agent` string of the HTTP request. In production, - different bundles are named with unique hashes, which prevents cache - collisions, though Meteor also sets the `Vary: User-Agent` HTTP response - header to let well-behaved clients know they should cache modern and - legacy resources separately. - - For the most part, the modern/legacy system will transparently determine - how your code is compiled, bundled, and delivered—and yes, it - works with every existing part of Meteor, including dynamic `import()` - and even [the old `appcache` - package](https://github.com/meteor/meteor/pull/9776). However, if you're - writing dynamic code that depends on modern features, you can use the - boolean `Meteor.isModern` flag to detect the status of the current - environment (Node 8 is modern, too, of course). If you're writing a - Meteor package, you can call `api.addFiles(files, "legacy")` in your - `package.js` configuration file to add extra files to the legacy bundle, - or `api.addFiles(files, "client")` to add files to all client bundles, - or `api.addFiles(files, "web.browser")` to add files only to the modern - bundle, and the same rules apply to `api.mainModule`. Just be sure to - call `setMinimumBrowserVersions` (in server startup code) to enforce - your assumptions about ECMAScript feature support. - - We think this modern/legacy system is one of the most powerful features - we've added since we first introduced the `ecmascript` package in Meteor - 1.2, and we look forward to other frameworks attempting to catch up. - - [PR #9439](https://github.com/meteor/meteor/pull/9439) - -* Although Meteor does not recompile packages installed in `node_modules` - by default, compilation of specific npm packages (for example, to - support older browsers that the package author neglected) can now be - enabled in one of two ways: - - * Clone the package repository into your application's `imports` - directory, make any modifications necessary, then use `npm install` to - link `the-package` into `node_modules`: - ```sh - meteor npm install imports/the-package - ``` - Meteor will compile the contents of the package exposed via - `imports/the-package`, and this compiled code will be used when you - import `the-package` in any of the usual ways: - ```js - import stuff from "the-package" - require("the-package") === require("/imports/the-package") - import("the-package").then(...) - ``` - This reuse of compiled code is the critical new feature that was added - in Meteor 1.7. - - * Install the package normally with `meteor npm install the-package`, - then create a symbolic link *to* the installed package elsewhere in - your application, outside of `node_modules`: - ```sh - meteor npm install the-package - cd imports - ln -s ../node_modules/the-package . - ``` - Again, Meteor will compile the contents of the package because they - are exposed outside of `node_modules`, and the compiled code will be - used whenever `the-package` is imported from `node_modules`. - - > Note: this technique also works if you create symbolic links to - individual files, rather than linking the entire package directory. - - In both cases, Meteor will compile the exposed code as if it was part of - your application, using whatever compiler plugins you have installed. - You can influence this compilation using `.babelrc` files or any other - techniques you would normally use to configure compilation of - application code. [PR #9771](https://github.com/meteor/meteor/pull/9771) - [Feature #6](https://github.com/meteor/meteor-feature-requests/issues/6) - - > ~Note: since compilation of npm packages can now be enabled using the - techniques described above, Meteor will no longer automatically scan - `node_modules` directories for modules that can be compiled by - compiler plugins. If you have been using that functionality to import - compiled-to-JS modules from `node_modules`, you should start using the - symlinking strategy instead.~ **Follow-up note: this optimization was - reverted in Meteor 1.7.0.1 (see [above](#v1701-2018-05-29)).** - -* Node has been updated to version - [8.11.2](https://nodejs.org/en/blog/release/v8.11.2/), officially fixing - a [cause](https://github.com/nodejs/node/issues/19274) of frequent - segmentation faults in Meteor applications that was introduced in Node - 8.10.0. Meteor 1.6.1.1 shipped with a custom build of Node that patched - this problem, but that approach was never intended to be permanent. - -* The `npm` package has been upgraded to version 5.10.0, and our - [fork](https://github.com/meteor/pacote/tree/v7.6.1-meteor) of its - `pacote` dependency has been rebased against version 7.6.1. - -* Applications may now specify client and server entry point modules in a - newly-supported `"meteor"` section of `package.json`: - ```js - "meteor": { - "mainModule": { - "client": "client/main.js", - "server": "server/main.js" - } - } - ``` - When specified, these entry points override Meteor's default module - loading semantics, rendering `imports` directories unnecessary. If - `mainModule` is left unspecified for either client or server, the - default rules will apply for that architecture, as before. To disable - eager loading of modules on a given architecture, simply provide a - `mainModule` value of `false`: - ```js - "meteor": { - "mainModule": { - "client": false, - "server": "server/main.js" - } - } - ``` - [Feature #135](https://github.com/meteor/meteor-feature-requests/issues/135) - [PR #9690](https://github.com/meteor/meteor/pull/9690) - -* In addition to `meteor.mainModule`, the `"meteor"` section of - `package.json` may also specify `meteor.testModule` to control which - test modules are loaded by `meteor test` or `meteor test --full-app`: - ```js - "meteor": { - "mainModule": {...}, - "testModule": "tests.js" - } - ``` - If your client and server test files are different, you can expand the - `testModule` configuration using the same syntax as `mainModule`: - ```js - "meteor": { - "testModule": { - "client": "client/tests.js", - "server": "server/tests.js" - } - } - ``` - The same test module will be loaded whether or not you use the - `--full-app` option. Any tests that need to detect `--full-app` should - check `Meteor.isAppTest`. The module(s) specified by `meteor.testModule` - can import other test modules at runtime, so you can still distribute - test files across your codebase; just make sure you import the ones you - want to run. [PR #9714](https://github.com/meteor/meteor/pull/9714) - -* The `meteor create` command now supports a `--minimal` option, which - creates an app with as few Meteor packages as possible, in order to - minimize client bundle size while still demonstrating advanced features - such as server-side rendering. This starter application is a solid - foundation for any application that doesn't need Mongo or DDP. - -* The `meteor-babel` npm package has been updated to version - 7.0.0-beta.49-1. Note: while Babel has recently implemented support for - a new kind of `babel.config.js` configuration file (see [this - PR](https://github.com/babel/babel/pull/7358)), and future versions of - Meteor will no doubt embrace this functionality, Meteor 1.7 supports - only `.babelrc` files as a means of customizing the default Babel - configuration provided by Meteor. In other words, if your project - contains a `babel.config.js` file, it will be ignored by Meteor 1.7. - -* The `reify` npm package has been updated to version 0.16.2. - -* The `meteor-node-stubs` package, which provides stub implementations for - any Node built-in modules used by the client (such as `path` and - `http`), has a new minor version (0.4.1) that may help with Windows - installation problems. To install the new version, run - ```sh - meteor npm install meteor-node-stubs@latest - ``` - -* The `optimism` npm package has been updated to version 0.6.3. - -* The `minifier-js` package has been updated to use `uglify-es` 3.3.9. - -* Individual Meteor `self-test`'s can now be skipped by adjusting their - `define` call to be prefixed by `skip`. For example, - `selftest.skip.define('some test', ...` will skip running "some test". - [PR #9579](https://github.com/meteor/meteor/pull/9579) - -* Mongo has been upgraded to version 3.6.4 for 64-bit systems, and 3.2.19 - for 32-bit systems. [PR #9632](https://github.com/meteor/meteor/pull/9632) - - **NOTE:** After upgrading an application to use Mongo 3.6.4, it has been - observed ([#9591](https://github.com/meteor/meteor/issues/9591)) - that attempting to run that application with an older version of - Meteor (via `meteor --release X`), that uses an older version of Mongo, can - prevent the application from starting. This can be fixed by either - running `meteor reset`, or by repairing the Mongo database. To repair the - database, find the `mongod` binary on your system that lines up with the - Meteor release you're jumping back to, and run - `mongodb --dbpath your-apps-db --repair`. For example: - ```sh - ~/.meteor/packages/meteor-tool/1.6.0_1/mt-os.osx.x86_64/dev_bundle/mongodb/bin/mongod --dbpath /my-app/.meteor/local/db --repair - ``` - [PR #9632](https://github.com/meteor/meteor/pull/9632) - -* The `mongodb` driver package has been updated from version 2.2.34 to - version 3.0.7. [PR #9790](https://github.com/meteor/meteor/pull/9790) - [PR #9831](https://github.com/meteor/meteor/pull/9831) - [Feature #268](https://github.com/meteor/meteor-feature-requests/issues/268) - -* The `cordova-plugin-meteor-webapp` package depended on by the Meteor - `webapp` package has been updated to version 1.6.0. - [PR #9761](https://github.com/meteor/meteor/pull/9761) - -* Any settings read from a JSON file passed with the `--settings` option - during Cordova run/build/deploy will be exposed in `mobile-config.js` - via the `App.settings` property, similar to `Meteor.settings`. - [PR #9873](https://github.com/meteor/meteor/pull/9873) - -* The `@babel/plugin-proposal-class-properties` plugin provided by - `meteor-babel` now runs with the `loose:true` option, as required by - other (optional) plugins like `@babel/plugin-proposal-decorators`. - [Issue #9628](https://github.com/meteor/meteor/issues/9628) - -* The `underscore` package has been removed as a dependency from `meteor-base`. - This opens up the possibility of removing 14.4 kb from production bundles. - Since this would be a breaking change for any apps that may have been - using `_` without having any packages that depend on `underscore` - besides `meteor-base`, we have added an upgrader that will automatically - add `underscore` to the `.meteor/packages` file of any project which - lists `meteor-base`, but not `underscore`. Apps which do not require this - package can safely remove it using `meteor remove underscore`. - [PR #9596](https://github.com/meteor/meteor/pull/9596) - -* Meteor's `promise` package has been updated to support - [`Promise.prototype.finally`](https://github.com/tc39/proposal-promise-finally). - [Issue 9639](https://github.com/meteor/meteor/issues/9639) - [PR #9663](https://github.com/meteor/meteor/pull/9663) - -* Assets made available via symlinks in the `public` and `private` directories - of an application are now copied into Meteor application bundles when - using `meteor build`. This means npm package assets that need to be made - available publicly can now be symlinked from their `node_modules` location, - in the `public` directory, and remain available in production bundles. - [Issue #7013](https://github.com/meteor/meteor/issues/7013) - [PR #9666](https://github.com/meteor/meteor/pull/9666) - -* The `facts` package has been split into `facts-base` and `facts-ui`. The - original `facts` package has been deprecated. - [PR #9629](https://github.com/meteor/meteor/pull/9629) - -* If the new pseudo tag `` is used anywhere in the - `` of an app, it will be replaced by the `link` to Meteor's bundled - CSS. If the new tag isn't used, the bundle will be placed at the top of - the `` section as before (for backwards compatibility). - [Feature #24](https://github.com/meteor/meteor-feature-requests/issues/24) - [PR #9657](https://github.com/meteor/meteor/pull/9657) - -## v1.6.1.4, 2018-08-16 - -### Breaking changes -N/A - -### Migration Steps -N/A - -### Changes - -* Node has been updated to version - [8.11.4](https://nodejs.org/en/blog/release/v8.11.4/), an important - [security release](https://nodejs.org/en/blog/vulnerability/august-2018-security-releases/). - -## v1.6.1.3, 2018-06-16 - -### Breaking changes -N/A - -### Migration Steps -N/A - -### Changes - -* Node has been updated to version - [8.11.3](https://nodejs.org/en/blog/release/v8.11.3/), an important - [security release](https://nodejs.org/en/blog/vulnerability/june-2018-security-releases/). - -## v1.6.1.2, 2018-05-28 - -### Breaking changes -N/A - -### Migration Steps -N/A - -### Changes - -* Meteor 1.6.1.2 is a very small release intended to fix - [#9863](https://github.com/meteor/meteor/issues/9863) by making - [#9887](https://github.com/meteor/meteor/pull/9887) available to Windows - users without forcing them to update to Meteor 1.7 (yet). Thanks very - much to [@zodern](https://github.com/zodern) for identifying a solution - to this problem. [PR #9910](https://github.com/meteor/meteor/pull/9910) - -## v1.6.1.1, 2018-04-02 - -### Breaking changes -N/A - -### Migration Steps -* Update `@babel/runtime` npm package and any custom Babel plugin enabled in -`.babelrc` - ```sh - meteor npm install @babel/runtime@latest - ``` - -### Changes - -* Node has been updated to version - [8.11.1](https://nodejs.org/en/blog/release/v8.11.1/), an important - [security release](https://nodejs.org/en/blog/vulnerability/march-2018-security-releases/), - with a critical [patch](https://github.com/nodejs/node/pull/19477) - [applied](https://github.com/meteor/node/commits/v8.11.1-meteor) to - solve a segmentation fault - [problem](https://github.com/nodejs/node/issues/19274) that was - introduced in Node 8.10.0. - -* The `meteor-babel` npm package has been updated to version - 7.0.0-beta.42, which may require updating any custom Babel plugins - you've enabled in a `.babelrc` file, and/or running the following - command to update `@babel/runtime`: - ```sh - meteor npm install @babel/runtime@latest - ``` - -## v1.6.1, 2018-01-19 - -### Breaking changes - -* Meteor's Node Mongo driver is now configured with the - [`ignoreUndefined`](http://mongodb.github.io/node-mongodb-native/2.2/api/MongoClient.html#connect) - connection option set to `true`, to make sure fields with `undefined` - values are not first converted to `null`, when inserted/updated. `undefined` - values are now removed from all Mongo queries and insert/update documents. - - This is a potentially breaking change if you are upgrading an existing app - from an earlier version of Meteor. - - For example: - ```js - // return data pertaining to the current user - db.privateUserData.find({ - userId: currentUser._id // undefined - }); - ``` - Assuming there are no documents in the `privateUserData` collection with - `userId: null`, in Meteor versions prior to 1.6.1 this query will return - zero documents. From Meteor 1.6.1 onwards, this query will now return - _every_ document in the collection. It is highly recommend you review all - your existing queries to ensure that any potential usage of `undefined` in - query objects won't lead to problems. - -### Migration Steps -N/A - -### Changes - -* Node has been updated to version - [8.9.4](https://nodejs.org/en/blog/release/v8.9.4/). - -* The `meteor-babel` npm package (along with its Babel-related - dependencies) has been updated to version 7.0.0-beta.38, a major - update from Babel 6. Thanks to the strong abstraction of the - `meteor-babel` package, the most noticeable consequence of the Babel 7 - upgrade is that the `babel-runtime` npm package has been replaced by - `@babel/runtime`, which can be installed by running - ```js - meteor npm install @babel/runtime - ``` - in your application directory. There's a good chance that the old - `babel-runtime` package can be removed from your `package.json` - dependencies, though there's no harm in leaving it there. Please see - [this blog post](https://babeljs.io/blog/2017/09/12/planning-for-7.0) - for general information about updating to Babel 7 (note especially any - changes to plugins you've been using in any `.babelrc` files). - [PR #9440](https://github.com/meteor/meteor/pull/9440) - -* Because `babel-compiler@7.0.0` is a major version bump for a core - package, any package that explicitly depends on `babel-compiler` with - `api.use` or `api.imply` will need to be updated and republished in - order to remain compatible with Meteor 1.6.1. One notable example is the - `practicalmeteor:mocha` package. If you have been using this test-driver - package, we strongly recommend switching to `meteortesting:mocha` - instead. If you are the author of a package that depends on - `babel-compiler`, we recommend publishing your updated version using a - new major or minor version, so that you can continue releasing patch - updates compatible with older versions of Meteor, if necessary. - -* Meteor's Node Mongo driver is now configured with the - [`ignoreUndefined`](http://mongodb.github.io/node-mongodb-native/2.2/api/MongoClient.html#connect) - connection option set to `true`, to make sure fields with `undefined` - values are not first converted to `null`, when inserted/updated. `undefined` - values are now removed from all Mongo queries and insert/update documents. - [Issue #6051](https://github.com/meteor/meteor/issues/6051) - [PR #9444](https://github.com/meteor/meteor/pull/9444) - -* The `server-render` package now supports passing a `Stream` object to - `ServerSink` methods that previously expected a string, which enables - [streaming server-side rendering with React - 16](https://hackernoon.com/whats-new-with-server-side-rendering-in-react-16-9b0d78585d67): - ```js - import React from "react"; - import { renderToNodeStream } from "react-dom/server"; - import { onPageLoad } from "meteor/server-render"; - import App from "/imports/Server.js"; - - onPageLoad(sink => { - sink.renderIntoElementById("app", renderToNodeStream( - - )); - }); - ``` - [PR #9343](https://github.com/meteor/meteor/pull/9343) - -* The [`cordova-lib`](https://github.com/apache/cordova-cli) package has - been updated to version 7.1.0, - [`cordova-android`](https://github.com/apache/cordova-android/) has been - updated to version 6.4.0 (plus one additional - [commit](https://github.com/meteor/cordova-android/commit/317db7df0f7a054444197bc6d28453cf4ab23280)), - and [`cordova-ios`](https://github.com/apache/cordova-ios/) has been - updated to version 4.5.4. The cordova plugins `cordova-plugin-console`, - `cordova-plugin-device-motion`, and `cordova-plugin-device-orientation` - have been [deprecated](https://cordova.apache.org/news/2017/09/22/plugins-release.html) - and will likely be removed in a future Meteor release. - [Feature Request #196](https://github.com/meteor/meteor-feature-requests/issues/196) - [PR #9213](https://github.com/meteor/meteor/pull/9213) - [Issue #9447](https://github.com/meteor/meteor/issues/9447) - [PR #9448](https://github.com/meteor/meteor/pull/9448) - -* The previously-served `/manifest.json` application metadata file is now - served from `/__browser/manifest.json` for web browsers, to avoid - confusion with other kinds of `manifest.json` files. Cordova clients - will continue to load the manifest file from `/__cordova/manifest.json`, - as before. [Issue #6674](https://github.com/meteor/meteor/issues/6674) - [PR #9424](https://github.com/meteor/meteor/pull/9424) - -* The bundled version of MongoDB used by `meteor run` in development - on 64-bit architectures has been updated to 3.4.10. 32-bit architectures - will continue to use MongoDB 3.2.x versions since MongoDB is no longer - producing 32-bit versions of MongoDB for newer release tracks. - [PR #9396](https://github.com/meteor/meteor/pull/9396) - -* Meteor's internal `minifier-css` package has been updated to use `postcss` - for CSS parsing and minifying, instead of the abandoned `css-parse` and - `css-stringify` packages. Changes made to the `CssTools` API exposed by the - `minifier-css` package are mostly backwards compatible (the - `standard-minifier-css` package that uses it didn't have to change for - example), but now that we're using `postcss` the AST accepted and returned - from certain functions is different. This could impact developers who are - tying into Meteor's internal `minifier-css` package directly. The AST based - function changes are: - - * `CssTools.parseCss` now returns a PostCSS - [`Root`](http://api.postcss.org/Root.html) object. - * `CssTools.stringifyCss` expects a PostCSS `Root` object as its first - parameter. - * `CssTools.mergeCssAsts` expects an array of PostCSS `Root` objects as its - first parameter. - * `CssTools.rewriteCssUrls` expects a PostCSS `Root` object as its first - parameter. - - [PR #9263](https://github.com/meteor/meteor/pull/9263) - -* The `_` variable will once again remain bound to `underscore` (if - installed) in `meteor shell`, fixing a regression introduced by Node 8. - [PR #9406](https://github.com/meteor/meteor/pull/9406) - -* Dynamically `import()`ed modules will now be fetched from the - application server using an HTTP POST request, rather than a WebSocket - message. This strategy has all the benefits of the previous strategy, - except that it does not require establishing a WebSocket connection - before fetching dynamic modules, in exchange for slightly higher latency - per request. [PR #9384](https://github.com/meteor/meteor/pull/9384) - -* To reduce the total number of HTTP requests for dynamic modules, rapid - sequences of `import()` calls within the same tick of the event loop - will now be automatically batched into a single HTTP request. In other - words, the following code will result in only one HTTP request: - ```js - const [ - React, - ReactDOM - ] = await Promise.all([ - import("react"), - import("react-dom") - ]); - ``` - -* Thanks to a feature request and pull request from - [@CaptainN](https://github.com/CaptainN), all available dynamic modules - will be automatically prefetched after page load and permanently cached - in IndexedDB when the `appcache` package is in use, ensuring that - dynamic `import()` will work for offline apps. Although the HTML5 - Application Cache was deliberately *not* used for this prefetching, the - new behavior matches the spirit/intention of the `appcache` package. - [Feature Request #236](https://github.com/meteor/meteor-feature-requests/issues/236) - [PR #9482](https://github.com/meteor/meteor/pull/9482) - [PR #9434](https://github.com/meteor/meteor/pull/9434) - -* The `es5-shim` library is no longer included in the initial JavaScript - bundle, but is instead injected using a `

@C`XJ54?aUf^_)P^J+oSmJ4*&04^ zf>pLn8kJZ4{Db1;$3$=Nb=+49zgW;+aW(%$iaCw8FH(Nbm^3-Y^@cObWNDJKn%MG7$Tbw^9V2K5CAw7o8@&d({#MO93q5`=O=t(C2ywywi|_F7DKC7+Y)?)7<$A$V>ShIYpjf!Go#5y%GX_(;Op@0!GifB@~C(QK@&3?6-1zRN_40D!~P9))trH=lpRu*W74)B%}B zWA0lT%AWw4Sz`SOXSFZLWZ=jt)3M;@CLkXj_@ zBx#Dj_VQ7tbj+Pc(#;>vnRLL^%$gd;<{k;&bCqP(^8E}BQuX)e255c4p(GK2lI(Vn zXecP~h`kKH9qS6wT%s1JR=1CKmisr^ng%IFwC&04nbwP*XQL8g1zF$+w{v zd>Znh!x}%d4^js8W;tzRWKfnrSoykb2i4aZYR=%gEKrPhH){cu++pn@84lb>2$J)3 zY-&)D(@pf=2OqSriAWgJLry?(%a=DdH^FCdz_CQbcR&>#IBn&a^41J9l=_jLD>k|2 zv5^7Zl}b-R;UJzQ=9R?!|IRx9oqqp~1puJGldVLh_?r=bpG^I4aYVOO29rQT-1_AF Q0G810TPpHjWR0Hu4_KP90{{R3 diff --git a/tools/cordova/assets/launchscreens/1536x2048.png b/tools/cordova/assets/launchscreens/1536x2048.png deleted file mode 100644 index b528adf375eb77071d4bfc39565328eb30325144..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31521 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 diff --git a/tools/cordova/assets/launchscreens/2048x1536.png b/tools/cordova/assets/launchscreens/2048x1536.png deleted file mode 100644 index 356fa2dc6e401de1bce8da19e51fe8eb89e7328b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31120 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 diff --git a/tools/cordova/assets/launchscreens/320x470.png b/tools/cordova/assets/launchscreens/320x470.png deleted file mode 100644 index 73cd8714a734d54c236a99c1454d95a09b4a01f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7493 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 diff --git a/tools/cordova/assets/launchscreens/640x1136.png b/tools/cordova/assets/launchscreens/640x1136.png deleted file mode 100644 index 4b2ec00970bf619f56358558904e134ab1a3750a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17156 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 W_(3CMv*0g)0r?jy&&#CD-uw?cj$)z! diff --git a/tools/cordova/assets/launchscreens/640x960.png b/tools/cordova/assets/launchscreens/640x960.png deleted file mode 100644 index 903b9e6b47cab907f803773e82c34568f52618d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16415 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 CIK=A! diff --git a/tools/cordova/assets/launchscreens/720x960.png b/tools/cordova/assets/launchscreens/720x960.png deleted file mode 100644 index fdf414263489fc1f93e27e1c98db25ae32f66c57..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13333 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 diff --git a/tools/cordova/assets/launchscreens/750x1334.png b/tools/cordova/assets/launchscreens/750x1334.png deleted file mode 100644 index 481a76b526ad3c75edfb586d3ac21d4a8d72dc18..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20952 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 bFL7!*J~)e?1YgmJfgvd>Bl213wcGy%`k8PD diff --git a/tools/cordova/assets/launchscreens/768x1024.png b/tools/cordova/assets/launchscreens/768x1024.png deleted file mode 100644 index 159cb037bfd2c33d61fe62dfd6fcd0cc685e342f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12346 zcmdT~1zeQdw*O`rDM>*PkT3|Pq@@P~1SFJ}1}Q0#9%4q3l2Vb7krHVEVF+pIE|D&2 z0f!!X;C*<{``vTSJ?A{{yZ62Kd-Ega+p+fA|MlN%ueJ9Vtgfm+PRd9M006n-ts8d% zfB^go1Bjsj0Li`nx(0kAd3a0D830bw9REUqxHo6OLI#(cx-J@ymM-q5k1PPW`;KN7 zY>M`#Ru*?HOz(R-wOB|20DqI>4LMDZ;qRj=VNf|L!tQu!KM2eO0DlOGKLLd5C;-L= zeDnio*#LqwgGv$$w*wyESCcNsR}m~gRQdb*WCLw1F+(vGU1Cr;3V=Xi#8hmM<3Gnw zr~a`BdhtJ7!J)sjIxfNsB+Wh|`KFoLp! zW}+q5Fns)-JDO#)KLc-Ts1Rn%poO6GvU)XdQnSv`YFaeF#kjlc?Z9+;$XTpm0)84D zuiI%(1+!skFk17FN^|n^ot6EfC6^o-zJsltawu8qzOhSdbKNi2%&A@t9Y5-B7DKDS z(|E_Yg}rLXPE-V%+WAsc5?e*1yXIMH@U}{c*VONuoK5-JU^9~N&awn1xAiQSXa1`2 z8$wF68^i9T;|wBoo)KnmgDr&_!yYl;EaCGa5KmIx*W~F59gb*Nv0`$ZIuJVj1J_@} z_BXuYDA?cdhQeH4ZK1;V6l*`~zFnQfWnZ%8a5p40q3dAS1ZOx;d zy;zs0mTSbS(uh7!{&DLlNetEv%}e7-JAIWdkL$A09gSVY8(rfbIlc6oNfrHig?)0R z;Vko=7gcaY+TUHnfgLc?0+4MXc0q!rn+WEcIDqhGzCVI+B{GH!KOG${Ose z?6eZEh#++F-5G{SvE3MvxD)Ej)=0*potsXNEcs*Fu1@F}8wf2XrF+sUV8pxI3;hmQ z{)G0w;rQ=S`@6BiX7zzB?4aliHquWY_{2`YAr7BtQO;;67mAPz7)3d20OW9r8BI83 z1fYGWCdMfoH`c(Z9CKxYl`3>L%1)-M!ti7wBD(qteZp?<51-K@)Ek$XLAaURs5dFEKGu;M^=)d)S2Z>>5^0O|4n%?>uIGCkyE0kwsho(PyUtk_9Z{MiKM>GEcIe%JzRuzkMMvl5`&)RykgQ zmzNCLQBY8LM?i)vaR?tLr^ykr?qz9N*@>|+SLD*><|_bIOpjt=WgWo{3=CY-hknb; z&CPw%)@4tU)cF{9csOpY3F`S3lq7a7FMSb@I<+Yg;NL4qmy+6vp(F;OJpMD11HjlxUIRpy*;b6eMs5e zPF^&z;#M6j1J1z6C<_$+CmT z1&i?k=MW#WN|G6xzQ|~f6@La~=3vr$WUtZ_w`Q#gdn)Tex22HR-xVON%&-%Z3e=kU!>50uNPP*X4)1^q$t`Fx#{BU+*^r^ z3RthRE%}hlZ$@&%x>@qsehjm<@+?8}jpY5fTKvf*f7WaKi=O4*zNX_mt52A$gufWRRSSfMi^&tgiBJTeAkk-E z)In~9H$Og00T7|ig8#v17s%C5ZZ=t#>6Qq@JuNY+}tWi zQPnkIaP=e5I^OMn1X@4h+us)bdClJ&|Ad%dBIm#N`M(;os;<93njQj~^78if4wX-7 zxj&T(N9_=TyB{Sb<@B0|DB4qmimj>!+}Fy=hKGhqOH0=tu))3kcD-fdRU^W~YvZ_% zD#>2%FddL0k%-fT(+39!6RGXo(oiLz7ZoMBOg8ujg579%Y2`{7NC6( z{up?1Dy|?uKfk0Tru37i5veT~KQpv`Wo>Vd@zBe~Wpi!q#jdx9pj0Bp2V;E=)!fnD z&~VdIuyzC7b1O0LdaP-+Z|6eU;Ix(b9tZ@f5aDXR;5mv>}Sdz(`#!2KDyd0pV&DjCI^Pb$3^Sp z!D#M_sZ64kucwY)OS?Kl%(0jm$qKKX9Oi?QR0M^eD-$kr-)#Jt{!F=(BiCCn-DKLX$ZXmz$8qNyDnB2N$w~n8h-*j+%-Hevx#|4evwfsNp!v zWnQkAbXHe}0r@QJE4V@(u}Y2kMco`11cFgpxQEl3VjX^>I#ou+D6_qRDD?h_#0W;hrP&IEj(ha`W=CS+`W(4Ss;VhPrn06w@2|O9;xg zVf6cYlk!SGZizV=ETYg1 zr{L0#5bXNO-BYSI-2&2L!DX?R6Hdl?J(yfoci#>)w{YsWSls=Dnt>vWuH`KsSWt2% z9e*(L+6!coQ9WVHg8M@2^U?@lO+zyzre51NmF%WMr{T&eLy4&N}xuZP3>bMie_eEnd8?gvy|?6BoZ(I1*^4>t3AQ$F)@%TidI}c7X$>@6i{cLtY=G? z@n-2<7=DtgqAmQONA9j;DHU5lotv?P6ew-y7r=NdN1UC4Sn%^~s!v0FkFDdfx z`uvIHpBa;w>c3R|FUNczd;|k`uc$2DQvg|4PLAM{W@1+0kpTT8wm_n*GQgY(-^?in z)BERdaF|g@0hGWCYMD=N_KvvD_}9w|&iwkLLi79Q2N1z=;2>=2!ji z2@??!$-rPi^G?3Lz8)UmoZ%=B7EVr1D=RA*BCuEh-BGLFJdXm_q`-^@Oj5ilh^e~F zZEcnPsMr!Pa$wY-ot-U13JbDYo$Ele%KBPULF=7Gt$lnd44q)b%~OKLYNpb)ne!9$jZ#z(Z|-+14?Y1o;J5r$Mdputy8ll6v&wx zV5+N;-rfeg3bjECdwatQfxsGUd3pJo9~gCAE(vY0v;hZ<6!}&cNSS9`omGPESvtEVs0@G)a%^$kOZeaCOy^ttNon8z(e0GRn=#(Hgg;7zotU?}%%p zN{Wo6NxTBBzjf)FpSHI4WhPoEZEl{P8ki8B;lrGGT0I@n(V>!9JCl0zv*UP0X0=Rv zvwXT^vh6u7@B~5TekkgU+rx(s-QDSZ@9bu98zEg>gb{gGwzk|i9^~3vo4^V~YpvPa z8y1`7*7m@7X2?#vO3(#6R_-n)CU)GtIb7TK=#bg#&e6xpIXz#l;dHgc#S#<>P*K3g ze=U5PzD}HcRhv}keUI1RWNXhQ^obk9-7skxnSRGZrH#DS3s0wTB3(VTQIe99{)GP3 zHFaLYqXco`SMFpX?M=&=;+NHQjEt+0wDMZXwoZnw%W)wgCnH5dQE8UB8$NdxE=N!e zkB=|E;u51fX)N!#>$)@ly;=d4Hk7oNi2afjcj4(Zy{)aSyMCnG5mT$_6$cUtsVn>b zfJ%pf?1+tl24-gd@K8qOee3pz>EeQ@sHj?Ie+O{J`EELHv zkJg2cw61Su>wJymRJmDleO#v7(*D&PnO8(`+2<@hcmPec7!c z8h-LC_tOi6&9@^yn;(wqoYJ>BhkLZA(}9fVaqwedVX5tMBW?wW^MEg0Ba5Ej=qZ=? z;X&y`{6L;@T?4;yRcOcX;NZ?7-fOvawZy;L8Hq%$a+6fk94=55tQx0qZpy=ND@aAY zf0VCQarLn5fQ&oLVQQ}6D|V#Bzf`|W>(#!})NUAVUiSi=C*Z0f>2ye(>*qB9vni{< z^rec#Frhq*Px9Grwro=z$%oVCdi6q$wQ3$6PMz^3QV}c@Ja!Z^Jj5H*p9@N72(qg#I9y{jP@hb>_Axd z(s`rk*f>%Ty%W=1QF_9Nr|OrLOZBj`1EqNvJF10mp<+$^9 zfWNvkUiejjX0a_X6^)(x?9N-ElEqP`MQcZ0Ua=)`1>*O&Gv$6r2jfDY0a(yNhSUV- zKvHei+pV{YF8Hq(a;~h}7{9jd9vT{|tMhXly68z-TrsuapQ(_tvb6|%d(i(uJp;_> z4*Y_Sor9er6cM_CoI}fB#Y%{?ly(hyF4X~au+QOyqu!nxYv_8 z3Kd28%(u37*q(u`MTjB*=lv6=DOQ-R#G;r9gF(lMZ^Mb3QN+)wsd^cRq7t1;9P2|J z^s%zGj*G>~;YJgs1&;`L<>3b@VJuK%WR;rG=KD$pFY-nc+r8<6&Zt>2=JOymT!_6V zeO2}Yk)Tpmr@O5EBq-NYYKmmzx!W8iZlH8y3H=vt@7mMhtZap^FZqxn-&LNj>P_nW z`2Of)jyIYFVkS_p-#nYz?ktkwLr^YN!pNii1u3p})UW2140_17N`ape-@MUOjge=4 z>)O9Byz6rsA836CiB(%xf(7|IcF)dQOxn}o7w&q38nBx>IP~$E9BuDU7i>lO^@`EO zG_AHdfXE)k)zy&bcoszOUx?3H4aNn^!+HE;18VVOH5)#QjwUfj&0opCr!Dj1&OijH zZSbZJ45rAAN$@Bmnc8nUX;gJ!^iqgXC%7C!UWFR??e(Uv->~^0S1S8?h%`?w&E&Sm zvSRI86P^QB%fJtE1As0464;DqAQ>4uPB2 z>aqe@Q)P!X-41aXEFpQW7t2|RSq=wq9aLFB>)$=?eoEG1iMb~DNWbyeVXb@d6JkWJ zI$=cbp<xXXL`5Xa%x5z{WpB(hL8F}<98kd0&9fi3EsmjZDix3 zonz)Q5)zDd>ZY=lIzs3t`4ykNvt*eT?FJT&uk5@GwLHWdcMJxzg7Q>MWr|8l2FS)v zFOT9E)M6fO$DRar-bRY-;@80C?~X*Wt}JXaePO_DrYqygv+p>p+%!Fk*+9X9*rS#g zHUq`+gsuUX!J16Djpa%>O82g*jH{cQXl$&i;GTXXA!UYRmT^-kAV&kG1trn+>LCXw z1hqd_YYZg?1`bZlPF2w}o!j}Ji6B!XE?Y6&V#>-G=Zx`t zg`fc+4#n^rpLGlZAZaE)6`cKxLH++wEc=&<#Q)HkKLgkQulN~Njh%oNx^FAl8;}D5 zQT+BUkPM}U6N8&Z+r?fVfz<8Kftx|&nz=WkNVgwfR=MTK2VB0ak-FFEVI>?FRVd-L z#beaHLCey3E<~4RsbhC&qjzZ_Z|nr{>F6;8AZ7yq2n=9D0RW5&KmkyI3Jw5JVgL^O zQ6=oTKJd@>`99F1bn|0@c%%|KPAF%XYxBN ze}+l_DBz!@;;#h!yq4FsqG!RovVGvnSy?p>8g~c+w>^QBm6Cc&hS=#Ohfu*kTxZyv zKpy!V9*7?xyu6kIK)Mf-oCBXXy7u?I7+`ED6M7*oZz^9rZonA4J{zS0XD>Mkem3U2 z+}+&;iN7ukogkF`jK$jDp#?LnBe18p3~NNwG6@QJ693TC(-X|roSfDMjFGq)x&8fp z3%#-tphgYM2HfaPu7XFE>k7z`;xFCch}i3DyzNj(=dk+v$5SFeg)p*aW*G`$yxS02n0rUYsXRaz7lQ? zoXM7ddq{ihpzfqdAid2qaw<5Sb7i9X2){kC-Tc=j z>1m2!&5qa~6sm51PsL}UXIZ+_(xTXWNy83YXk{D??4d(BD1IE)=#cE165sp*fxC^3S% z6qB`BEUt``^BfrcQk)zn(RH7f-%^RM_~DC(Lf~~fFE12EMP?Wj1qOUFTY-6&N{wXq z43W}{6?!}d&N7KBSgdPH((21v+pDs&C56OP@KrLw44yU_QTZIpZmx;X(?twloJh9T z&C+JfPSTU`S{#r4m4SFno%ZGqZgKJQw8g;Rg>G4U((&K#wi8wM$n3khz8riEf5@&k z%ggio-x|`$z$5QAs{wv1^_V7>embGlvB(Ex?<1V=g9e@{42t@mLLb?35vFZl$YD;H zlUvQ=YUHJfZcz5h?Av9T6gkVv3gW!WKmgL=i`fJ4B&1A2PkKMfwmQ)^_CB^Y5`a?5#c=|Dyp)$v>qw@kJa-xNh^t38*SpI|q5=t{>wnabCcsP!47pq&0~UJKSr zT}hj$rIYseJcJjoEEG$G#H`+p5#kbW23M1me?zR2M;m|J`rI7HK$LBSM<-eiO~(>e zCcTm_8WQtREqc1kQh*BnX`?ym&a&bgl}K&9s_ry@g%k>OLqCOakx%A1GzBH4Bq0Mh z;CNqJ^m@3_W}F<_a#WsK9^G$*!Ak2L=3>s`NQv2!^2kWV3(jbeIPR9J(%I^kN2ZpD zc6@Ygd0#ZvznI!zWs7b&UPfBzd#zW4d))~|$V9^K4sZk_XZx~siYDw_)gI`H7wD0} zsIKT1*(8Og(39J`mu+i1o2Odzr`ZXk2lsLU9u_-DN6H<0EerJ+Bb!*l#VS3XgLoRg1OW~0>h0LBP{){@fx;iiAB%`# zzD=&7JSHaJ@T!&OzWRY>evWOfJ>4;PZ{h~)o?5Z<3*Y8-HKrc*KG?t3==^oy8Efr} z;{oPP1rqyGbr>5p%N*%Kc%ll)?)j{{Hn{~4AuKUxKp#sLP4wtx%(AE&ud4y|+1GLm zU5_VT$qAqvveGHPW3k|STOOG@Iy$}wpb2EJ`q3#Hy%Y-(iBVDu$ty=jHQSqoxCJxR z2A8JM+?4|$ejlWsM~-rLXeb+shBR2I*FHh#NRHT=1r$AddsSL`Y*Q`%IVp(n^V!F5 z3RBDVl~dcEecgeepXQyaR(XF4qi2T|$q7CtlpmSWAWw0TX4!k<@UEm^>c#JA<5Lkx zBHkq_?h^>ge3g+$ zM-fN_R#IgXh?AgWTMMM5r0fpg%rp$-5ZVn}cK63)MH03Npu`^H+D-RPA=uM2L9s=x zN!t8XeGa>YpF!WZdZTt9afddi@sYMr26jEBIWsk3D+%;@#Uu6isX#D4g7rIUReidh zi@M4q9cC{ylx=zy?S(S5>{2bB*G+;5U8j{u;d-X+$Cn);Z8ySw+ydRe5vl0mzqX;B zBbb2|&2J5)WaW7QfZn_$=WF6O2p|SvqNyB zdgvr;37WblGtLrxDU7nr?pdfdx?xI)%TywB*W?)=!RC@F&(GZntuPL$u;$tV+ z&J#DcwzpEipYsFXZtWk|mDY6C zo3YFKez?%$Zj{W0)7HT`67PB4s^<~g%3nDBUihVz%%Ru9=z0XgIhUg zu33Q_f*7k$pVV`jUhWrxSu!ewp4~#AO%?j$IMPJ5DYbHj%06+~f4=OC(VYp^uf1ad zb~Os`QxF$xl56+w@9!`piPTOJ&3?xu*eqcUMsZB&xvyW7CXIs`numtDeXP3g3toC= zwszjJYNLr9N^D}@>%Fs@?5Wjly^);NR-B7by}B?3-eSKBlNAKJ%ieB>clO_GS_{n5 z23=WkG5zWjd>1d^`@XHM&CkydJWCb!Rt-YW9qFI++}y&B?4*htSNpoogDg8R0k0g$ z88&xzK8iS0`Ru!G8t%-@bxDEyavYJ{n@d+rbivrlMZ)mx6Y2*Xn@S@KS>Pr6^z?L} zVm^4HGh{c%0WJk#P(1Au@VglJSK;Yj2DU#RyZ$;!``?VPe|9Fno#uZpaXSX_w{k%C z{B=tDi+cZ?T7t{$zoF&N&g6H}Av1KqpC{yhIqm;jmaLvVU=8B97dk-Prw)K0#ha=( Ja<7>@`43tfPk{gc diff --git a/tools/cordova/assets/launchscreens/960x720.png b/tools/cordova/assets/launchscreens/960x720.png deleted file mode 100644 index e8aab467a788522260ae104fd92ee12578dd75d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12918 zcmeHtcT|&Kmu@Hmf)ooPT|fm3J<_`hqEb{8>C!=jP(lq5iZp2!5a}RIr769m(o|YP zgeaj%PY4i7Ahf_e`1`(T-<`QLYwjO+tvi2Yz2)pv_TJBV_TJ}BjG?{`D>Dx>1Oj2z zy?*r$1VZ--0-;4Q(Lx|JMaNpaLBnD9>n2_h2+Q$<9}Ofe;}m$w4ZrpPei!Bd_qF!4 zhiKZtZ0t|#x>`Hh-?6v0^Y{2>uL6O{(CJ>)H1Zo>z@c5kr8qiQ3j04Wusor82s!x> zQUM`-lCe0=A_cL74294He0dRZ%h2>Hs-B88sQHR97#jWcND@n24{uV>=76w5X?UD+ z4x==V=Y%q({l|~Hl6aN=P&E-NuMjnYp|1ranLB1`lVozyHPXdpd>^Y#`Z4i<5=8Re zy01E>IAoauTlA{3U3{D;CKD@lBs4g0UFwLpMYj!~PNiXb?C?2}D5K4V`|c5&grTy` zarZaK6Sw;+9uv*w^=xZ5Ue4AVdK2o#Nv=4x>Q@GLc1w7r|ezHP%Qx)oOS%D~GotJX^b1=;EThC9NaK;YC3x_In&sUzn}9+>?5_ zU`ooRtM<}O>Y?GH^jy+}WOB$K>!SYC7Jq3qV+Pq+TGZA&eS}WzkRi=wbkrdfgw>Fd zRC36Waqtq_Zk5sEzKN~F$eODEM90f-=F!_*ovlY%O*O(9;vrBc8djdF5C-ua&G3_` zoI_A26z#$DK{I&PWI1?-;xk@4p2f^*6fw-(n?zcRjW)-5j+YT_u6f5KQ|eQ8&S8EI zo7v0+C7?V!qYKeWa}M)1Ir1;t93MC;d0!nFqz0<(KjOy?YpOqw{z(aXBy$U9VUMx5 zO0nDV@Z68_oF6P+y2n*WOMK8;q_Fxmpe|MHy&)?%O9{q)`$%GW)XtDcemQ$uAx%)A zYT!sfTA{ZVz=FV72Nv|dFi!iy*H=E~sx>6C7V@}_SxkfU z9C7IrgP4ZcC%MNEQZD2|4utm^?MAGqqR?E_Szg9amHtEMRDOA(a?x=z^a#p2LQH-##99Wk_Wem2%W}=jX zym(!+sO~6PFJ2J0SVLi4T3Twl8P*~*wNjU|j>Q@`si~@7?taynQ+}Gbv$He#<5Bg~ z3R|`F=SWFAYipIhq$9gUn){L!dz+h1V+x(`sKHjSBSU7@ZTv_M0b8L!F1psN|3%=HALRRoIVu9n8yb`CG=P@tXNQi6vdV zC3oMgT=TfXZMz-NEFnQCB{BtkYEGE4?R+~QN-7Rw=>;Nu)*!!>QMDPha>bWF34 z!MBDzJq^?q`yjg;OPu@tB_iX|3WB7d!0?=??+vHqR{P-mZew4H`%e}HWS5EHW$i64 z75Fk$2&zF*!0>q-&5z}XD6q%z4@a?UwUoN%2$vKCD?n^(sngWBt2)JFE z8jHcip7fD_{C6Vfe+{~Sq4?jf@IMRuzxAfJR<*X0u5gh1KU$XSc!nc%p>Hmfg><<R<)FSmv zOjxh4|Fp*`sR|?(N4?(>I>rS@j*XAwI>+l6YBQNS#h-h|=4I%9sZh=C5Bd?dM7sK6 z@tCg;#=4KKD6zl4e}>k)Y`m=RbuQq@808^u8CDjrtunOJiSK=TXTHQv7|J=;>Ww|f zZMCG_OJ0UOi0h`#E|>K>c=`CeV&$}^2K)Ja4YuwYH6pqx3vaf5%391dH0N9c99+Z;$G6k^n0NRT+uIFk4+Fmz@HnOI zOikJPD7|&oq=?!DczY+eRG3S&eKWuOdGp!L%P1%%Jw2UlZisQfw@a)&ehTLG zRz)ejS6^R0tsR5;oN$y+6WP3aILYsPW700yTJiB5RIPU;?19e)tr4%R`Cm6)7vQZptZT#B=^wh%Xuzd=t_i;LZeTn zPwFVgzz|%4=3Zsa&Rwca__@!IcZ6ALn~-_K(6Bup!f)Q}Th1;GO}S9kTGuvxjE|y^ z6fB?Uvd-gYYS)#!aIUDPm9+##UGy{(r0v(I$FbSh8k!XyYUcfgIje_pRX)Y9=Lp^; zA5zUJtb|QFxn0|yQn>AEIs){yH>i2#OY9;|Zl+v`2rEThF)zYU2!wEvqqme`NFRq~se?9-X()0XX z=Ksjlfqk-0xEvLg+nwdV_E{Yj;RUo@%1>!8q@8CwEFt^m>+s7 zL!IhM+zMpgS-ka%UZ?I*+PVhX+V-2% z(|J>lZodwnZ5_tp();I(jg74}Ea1OA>yHSweJm4#H&iw@5-%(lKe>h6-%~x)Nd6=R z5%f601U=!X6c|Xx-$viA{IvjPF3|b>Bz5vPmgV{A_;{=7H}(11*>ik;?(T+gAe|Ib z9kjRW-vi$pBCrdpJ)xV*_t`6zo)BH^a|pQQv5bq*Tq!m#|00@;y@pur>+iqfZ`B&i z|31_I&1*j_UuYz)wc+mcx0lPh!R~n-HXf9vQ4k_}r~)fpkJ&VHa&PgSh%#6W=k16g z?1~F}^jn>(Y@7v)j1HfC0w{ z^l68gLmTVr#7r5?vm3>}!<&~M%#^AQ<&mEKCK8G8-kX#R;06AZ7q7`O5WTqWZIG{3 z4;%ZE#+o;`iVU(75));0#=}{Xg)jvOeT|%7Y(nlp>3G~rogNFLvt~uuq8py)s)=ad zfLxh`rP!0QnmNA;Zy84<&MKI{YwZ#2lyf^SsN&qbC^<@2Xb`0EiE%i|nW}c1ClOx0 zeA$&Kh_!NX%XI5;GUl#4o63^BUe0O&*UCvv^PmJ`!$-utYI3FXGQ9@YZ;7T^wr1^` zcyc(^wACS6hzPScJ-;G3X1F9ROOT>L1D$wfGv)H5-K*z&FsC~fbLxVqF2 z&Bq?h=rQ}Y@M=uv6DePk_JnCd9r7YPocgYMiPWVMVT;M1F2D{|$YK;(!V6lj1M|SG z`FMEbfA(s-%gQ65v%+sabAIiU_S?>1EWv#Z)sQ`!nn-Y>m-njFv6T?!F>l1-aEP^8 zy`7z+qN2r*{#J#m)pVf_!dkyQH&mcu>1)s8`|6`F*b?E6BgYwi5?YSyF@`#H%Ve&- zPVm5G<9Ovf6BgqTlb`yy-3=Bsa#F)~cUrp^i;3ok`BZ9TmtyFY9Y6Ko-#spZdR!a8 zrzDKYqYSbN;Czk7L*sRiQA$svZt2Y0Dk+H~bx6g$Dss=AS;7JGx_|xp)fUBGoSVs; z`LY%CDM(yYNqKjKKF#cX)**d^6MTY#>WGb!mEO;n8?L$^?;pWuIQ6NYXM&oYqe={? zlD0FyH!9FPTo=*mA``ja3EwAME?$+8i;xlmA zx>od#P1-(CjaP2q^CIG2%=(o?0?;b$)Fi;7L`wHYcI5tB*mz56>!kpj&fh}!Ja@cq z7cMRVnUcC_Y;EZq>GNoct+KK*X}p!RyMvLp6YcCs))5gx8B!asN0>*t`9V(rxmZAWfAw86jwmf2`~#qUJbc?m0kr2@I*ch3^&G;`{&&2#wb z(>oqWp75F+JDEPu;pC*RTqPQ|Hoo@=yqk-wISQp^9Tjq;NUtrHGZei}Oh_mzFSl@| z=L!DU7xCGr5W}t|qcn@8q}=>;hK_%A`1Ly{*iR;Pj^ImA8maQ;mX-~1r!?DO^69;v zZc;JbK|;NmwK8EEbjvT#vjIZ?4`}%BQQ@D3{sZCv1Ho(LSkV4om`!_I2b?lsFP7QT z{N4;In!6gI9`g3WyinnV&qkU@n2k7JzrXX6=aW(L>kEdrHLV+NUlzJ>?xFkJ3-@z1 z?lmj+_)|uFkv*A>he?YP*@?o8 z>cC@RVWBsIFDN)Gd725zj>CtChokTj5fQmuV2dDVG;{nl^3YWK% zI8}~~s~|Rdia!~1>qNEQY^84C05kRW>g`2LthDZJ6W7<*bBZ-`tV5m+FJlSoMC@_s zcB1-TT~pH!Ck`ip59t1E^?gE7cITAGsk?JlegOg78;iKc#zsx!P>nW79vVN>ox;n@ zdxba1)6+9BaCfnGd^nE@-FpB2{U&CWtA!xP9B)Oclb;NLkqq29d(IXs_;G;H>Few3 z;^K1U?hc?Y_rV;xH6LH!=HFoVN;x_@8f2@*r<+73KC3xK zetMp#Ksir6a!yP%l}fGi*u~?;=1ve4ko;j;t*?3L)BI)O$8UI#-#9fro6T^%>}mWM z4%huF=hR#|a-T}c$l-O7jo^FLp3>0NG%`9$B@^!Mot2lDpIT8!a0S=3L{U{$Ro2wt z)oEQk!FN1}g0(`f%WZ@6CE&gZl7GN+t4$iMuFQg{qtLb4*8gnIufUpBF}i+hnx%DTbeK!Z?9w z9w--pcOV+JRiF_F#1^AKa(v&&!GaOt!3f08Qr7r6%qM4`Ks^=~7D2T@F=Fa@`j5MD zZYXQiV0D0^0gnNes46PCt8+AR;1%*AeXn^;_->ryu?(aIpR( zQ6N8#6o@_=Rl7ngC@APz%j~{FdXvFFHe+|aq17YUJnyK}Z12|A*5}WkQN2Th&f^^& z9X+~{s~QCr=w<^`SGX|VV=F2zg~Mr9&-|J(6(XgxUfHrMl3!f>K6b}|AHlVl>?K0T z>8elBR2I<8Nu}|4by_qfM%z(e#>n^{7bSLMz`!)KRlha1y*@>Ke;4E94t(uG9vr;) zbtVS^!w&V>%PFZ~LCRJfuV4PNxX-F+{MzJ5H_Df1Q)@_bt(Al8G`s24$b#qW>in?# z=H#Ss^L{~q0gh~N3Yi6xz1@fqV7sc=N_xfg(&G0w@lO|~bc-);gw4#%kYC*%g%V;57J|8bq1X5Ov$lSA#M)!^7mwrgQVeM60~wB#lROI;|FHR{e5T{$Hc z=CEj1lsjJ^(N(!*AOt+ zSXIIGvM&r!0@G@27YIF=cuS>SH5B14lw$6cXbi5rDks{u%7jHcG_4+a#dgJ!?*M#9+?>Fd~ z{ya4Bah?zHXOCXMAl+VG2EydBy7NNNz^s1iP>yj?sdzNVc#E|S+|9l`I^L~Kktlbw z;7rN-T6VeNB7VY(cT847LIR+Qs=w7x5;xBI?K&+llSm)0HFjq^bDx=!4DJ^A}{#Kz3R)3QF(X;^-lD zTtSTunE0hpRRyvIJ$)L1)6vr_%&gcI<`|n`EB`dEblM_*sUg*O(<*euujIf%=lZ5M zv}J#63>F)QE1ALh8gJV@J_{2^puA;CL;a`L*n??99d2}_j*NLxz|zxo)+uGpL{q8{ z9G>)({%4^2TQz55M5(%N-*rKmAj~$>eHqo)*QfQnrna_g%3~593r>obLmn|BHz%UV zXII`Yhb(_}xYPE5vbtJPjGibO{`u3%)q@xT&ck~xBGmUbZ8ux2q-lGPC)w^^?owzETObuqp+j@%EL9G>0LBt?|QpLDwAA+8di+3AqVu5mayRHLtTruOxAxPoF8?6)s;X*wc@ zBrTh+o8!Fip_>sOwz;PyTX!e1-;F0Ou@!2W=M5qDwAZ{kGDMWMM%s8a)^-SO&|}%w zv)RXKX?sN;!Phn$GR%Dj<$_!TGGkI%1=7y56<%AgAoUT3Y;zFuG4RpDXM@h%U8{lg z`knw$I>Pf@Z7urvbVYFup{1y67KF652SyZ{LY(|B^z~k}ul^&S zbvJ9vtJA?+kBLmi@MczmsFkMZ4EKiyHw0Nxobt(LS`FxC+z?I>_+ykxAf~>pf1`J2Uf2Gws|wF`Td%-8r&`a#h`BOzr}pbFbY16#=sjP=e0I z2XB*|C-rfPpIrssCSmZ;unwEBVz@S*e(XY`1-05JNOb-1-}D3^!4IK-1xxHz!83q1uEgU69kA zn7m$Joe#d+R1V*ze7_W}&wVY&|HpUE@1>3&M(jox!f_4J!lU>yZ!QbQ z-4rNK=`Gy_ivK{s#FsBx{?h^~ z^h6K_XP9RIKxRW~71KwyKaiE`+hyir*jJ+@?Vg5n8x$c-8w2NieTRt zqZc=bM85S>>ERJbP;_#*<=@u{BIy*j-@Q_Q*90U2CgfyFC*pLfWTb7h;d|-jlHNyA zpEvzf^i^V&KY>IrW{$$p%O%g-+|rWuU^YYHI~_gGxs;Y_7ySs#lnaN`YeUjvEv9Yp zi-aaUk3bcAFkUn!nYUM4`E~YrYfeTlH)rSbb84X%DIm|v_@1aihiW^)7@|RU8U>+c z1y?5Mzqo>zf1dwb|Ao-MFZ`Df6ZGE`K8>>czfn^Dt19L$&ewrSl@SV1Jdn55h z`&-BD#IrkVCcpgU+P+G(lHlI^vndqP_|6Fk)>hwQAZK%PHcv()YM*~4<4+!N1?r-$gYKr|JAz4#?B53 zyYX*!!g(|Racwf&o12?!bNztT)i}V28b;bt0ekCMD|^5pJg0|NR#s9Zt+cd})a~lN zo<{eMZ+4zuUQ=z10H%lxbmgy0yQl5oSZ~fq?^AbK0d6-eqlb}uTcW$a0Y`f~jB)>Y z8~ip-O;SPg~`HP-X(Qyn164$kn{89I$F^6 zhKbw92e13*Tyd{hP|Tt{ZR*_?*wjKs(8R2S__*s#K%*~m2l@Ng`?33PFw)s-92iZr zTl{2q+)_iCdB}YQbX{E@sNvm3_5x`@TYQ|Fn2J)vm-`DH{C^E}!{nFEM?Z%hmU1ms*F z&C1HUe+v4s1*`zo(-=RPMY;5wsCHw2MD0qeBlpn97K+k}tofzv^i1H8P}L8QKM#~wTDH8i zqarUzua#+4m&17Ko0@JZ@+)NOs|WA;8EzgT9N^eddwhJn*451I=2B-p_l!)#mzg11 zC23Sjqu~wpfrG7L)P3^Ow%q5vGVzb`xpSUtTPiA2`BrY=UuxeSHv_g*4nVHYO-sx3 z&{tgkSkl;wMh`db`nWRpdQWbP?dJ!;%r~uh7~Ne){OUnv1eC%0aUSm8!PF9>m zKt*Y*%bI0_<1L-C?&fMr09?DgWQcbNI4)$gdLV2j(%T3>teJ9iJaBhNAQ>%RvHIHj z!2w!*l=7_l6(xKtO0}>XDgq@p1&3DdYbOSuF!48uyBi67_S52`_=6=ooSn?!<}pn5 z#GARfxx~ao6A7+8G~3|V8p{BQT&j9uYSou2Eka^E*d3mxu+Lg%p0yI}bK;wO?@pU6 zRS_G&=e6mE=j;7lAJ~9?U-55eCTd(sOH0h7*oLIuE>(n4XD{o>=IecDIyyS#`?k3+UAukHZhXBLZ<>L-3@qqXwhJS`d$TW~wmv3mp0;Rt{1v61&iJ(H5&}ImVWzIqo)8ys>Nqye}w&dA7kg6z`Tarku^hY9;-z}@Lcj*jUjZl^rO zkf5_ZM}8e>R>ovNR=7HK)78ViPm*7^sBzSxAaa*?{)QARP2&o>M=iV}@(@Kp)<6-`_8<9qU3*)zv z2?!wupmK{m+pFLc2jkgZ8duA_=)Plh@ipc0j}^zg0WOaz;})GLM9*TJ*&G89NfqZZ z@yfIy!ag^NPLJ^%z9XF1Z+YZ9J~#cfNTL+aA4HO1^8`pQNPMSM&Mo%nRHUL%KX(1gYtsP{Wit~yJ@%2; zCvOQ(s8lI)-PzE3vY*H#_yaY58UK`eT>sS)iNu%*rw7Vxm0qeSgf+w~My0s8zyKlw zN+UFj~R zU4<$7Vqs+kQ;kZof1nd_7=9jWYAKDqI0QESCpHr+yHeRcVd^E@nYaXh6)|`bFv7#y z$CuDa3$f}^DQYFQ=EDh%jUJe#lT8;6f%(-RYJ-2h<>lp>|KiF<6Ff|;pRGmjn5N8d z5)ZmYfv!q(%L7r~?M-uMja!QBs*DQ`8o2lc1ZrGy-lNfmE`MU8>JB)Y>gtVVy1lnF z+W6Wk6{rH{>c)H6DtV5c2X6cdlSzH@mF2RE@DI6HPMIDj$1mn}m9loJ3p{SZkc~^| zz&`HLs)@Y8D<8R4dTj{Idraqnk)PoA=&tbiLd?5Crvn~*)$wMb?hGrHI?)!jz8~!G zuV{l?LB;8-*gu#E5}R7Oc?Ed&A$vyispt8su_fMp6A{I6xPhQ&8)`{^_~n8fIfZNQ z>4B@b#82AmoN+ebPrzw3F>P;JWy^dKw$JN8CGQ+f|Ij5qcd(`#uP!SJ>+Z-Z{n$d4 zGF|aC$FQzWw&Vvu^&)Z&aYVJ6tAF0*Sn}Dk=dFqOi=gU|JP!RWaRK2?2K2k>y06&9E!2 z`{*{MNC8LlZ}_9itp}YGr`DgY=3E;p|k#NL$2dk}a0s4wPG5r5IQbgKtZ zAZ{yA4c{9*Xc_t9Y+iHb!K&5ElR0NJb$Nn~_ffUEp~^~mQCV>+UsrtGDlY@7SY9)= zx}8wo_FDDSidE@sw;@n^Ypeb?FL3y#zifd0HO1g2?ViaMaD^hBTzFM3 znuq6L^u~40nawlt-|(B|uLi2G6e$#$_n9%!(6D}rJdwZ`c(ne!aB!UV%=tbM33>Pt zxr0$-WRzCTA5IIzZQhMM&z(uRJbQ6=&406F?>zu!$bQlC_EXR61Akn-jf71XMsI#9 zRS9+@&K#KpJPAqHJNNR}_Sw<3t#{kZ<);WS(PvUghH7WizGLz*CB%lA$n zQ1}@5G?muk7aSZ+#2yLA{S7{Bky`f!D&O*zZGsOZs%mQ6cOf4l=pIS}9l&|g%ou(v zP(Vq+Ud0>(b;Swr^Ox>Yz@b$cF<%x~3KAmycmn+8|DYhG<$)Y14}khtVR+E|KMnt5 jl0U}>{{V)Cm-`TP7dx)VBlMO4KoH$)`d7=e9)|rdT=&fM diff --git a/tools/cordova/assets/launchscreens/320x480.png b/tools/cordova/assets/launchscreens/android_mdpi_portrait.png similarity index 100% rename from tools/cordova/assets/launchscreens/320x480.png rename to tools/cordova/assets/launchscreens/android_mdpi_portrait.png diff --git a/tools/cordova/assets/launchscreens/ios_universal.png b/tools/cordova/assets/launchscreens/ios_universal.png new file mode 100644 index 0000000000000000000000000000000000000000..80fdd4c871ca68e69caa2e2f5f255d648a5590fa GIT binary patch literal 63225 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 xi_e^>!5j3ZYT|EA{H=+ { - const imageFile = files.pathJoin(launchScreensPath, `${size}.png`); + const setDefaultLaunchScreen = (key) => { + const imageFile = files.pathJoin(launchScreensPath, `${key}.png`); if (files.exists(imageFile)) { - this.imagePaths.splash[name] = imageFile; + this.imagePaths.splash[key] = imageFile; } }; _.each(iconsIosSizes, setDefaultIcon); _.each(iconsAndroidSizes, setDefaultIcon); - //TODO -> Fix default. - _.each(splashIosKeys, setDefaultLaunchScreen); - _.each(splashAndroidKeys, setDefaultLaunchScreen); + + setDefaultLaunchScreen('ios_universal'); + setDefaultLaunchScreen('android_mdpi_portrait'); this.pluginsConfiguration = {}; } From bf404dfd5c1ca1f461f83477006a8c2b44a0b85b Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Mon, 6 Dec 2021 09:15:57 -0300 Subject: [PATCH 022/393] Change how we process the splash images for cordova (fixes #11555): - Fixes iOS check. --- tools/cordova/builder.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tools/cordova/builder.js b/tools/cordova/builder.js index 8115823aa6..53e60e27fd 100644 --- a/tools/cordova/builder.js +++ b/tools/cordova/builder.js @@ -348,7 +348,7 @@ export class CordovaBuilder { this._configureAndCopyIcon(iconsIosSizes, platformElement.ios); this._configureAndCopyIcon(iconsAndroidSizes, platformElement.android); - this._configureAndCopySplashImages(splashIosKeys, platformElement.ios); + this._configureAndCopySplashImages(splashIosKeys, platformElement.ios, true); this._configureAndCopySplashImages(splashAndroidKeys, platformElement.android); } @@ -403,8 +403,7 @@ export class CordovaBuilder { }) } - _configureAndCopySplashImages(allowedValues, xmlElement) { - const isIos = xmlElement.name === 'ios'; + _configureAndCopySplashImages(allowedValues, xmlElement, isIos = false) { const appendDarkMode = (stringValue , { separator = '.', withChar = '~' } = {}) => { if (!stringValue) { throw new Error("No string was passed."); From 072e496e44e16fc036ad5ee557aa6ed7335066ca Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Tue, 7 Dec 2021 14:32:30 -0300 Subject: [PATCH 023/393] Change how we process the splash images for cordova (fixes #11555): - Use string template. --- tools/cordova/builder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/cordova/builder.js b/tools/cordova/builder.js index 53e60e27fd..40c39a44d4 100644 --- a/tools/cordova/builder.js +++ b/tools/cordova/builder.js @@ -416,7 +416,7 @@ export class CordovaBuilder { throw new Error("Invalid src value!"); } - return stringValue.substring(0, lastIndexOfSeparator) + withChar + darkModeIdentifier + stringValue.substring(lastIndexOfSeparator); + return `${stringValue.substring(0, lastIndexOfSeparator)}${withChar}${darkModeIdentifier}${stringValue.substring(lastIndexOfSeparator)}`; } Object.entries(allowedValues).forEach(([key, value]) => { From e9eb8e82d4d59b5495a4f10666e726ce9f932cac Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Wed, 8 Dec 2021 18:01:10 -0300 Subject: [PATCH 024/393] Change how we process the splash images for cordova (fixes #11555): - Updating docs --- docs/source/api/mobile-config.md | 6 ++--- tools/cordova/builder.js | 45 ++++++++++++++++---------------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/docs/source/api/mobile-config.md b/docs/source/api/mobile-config.md index af5538c680..849afde837 100644 --- a/docs/source/api/mobile-config.md +++ b/docs/source/api/mobile-config.md @@ -31,8 +31,8 @@ App.icons({ }); App.launchScreens({ - 'iphone_2x': 'splash/Default@2x~iphone.png', - 'iphone5': 'splash/Default~iphone5.png', + 'ios_universal': { src: 'splash/Default@2x.png', srcDarkMode: 'splash/Default@2x~dark.png' }, + 'ios_universal_3x': 'splash/Default~iphone5.png', // More screen sizes and platforms... }); @@ -84,4 +84,4 @@ App.accessRule('https://example.com', { type: 'navigation' }); {% apibox "App.appendToConfig" %} {% apibox "App.addResourceFile" %} -> Note: The resource file is copied in two steps : from the **src** of your meteor project to the root of the cordova project, then to the **target** \ No newline at end of file +> Note: The resource file is copied in two steps : from the **src** of your meteor project to the root of the cordova project, then to the **target** diff --git a/tools/cordova/builder.js b/tools/cordova/builder.js index 40c39a44d4..737fd59c57 100644 --- a/tools/cordova/builder.js +++ b/tools/cordova/builder.js @@ -760,29 +760,30 @@ configuration. The key may be deprecated.`); * be special "Nine-patch" image files that specify how they should be * stretched. See the [Android docs](https://developer.android.com/guide/topics/graphics/2d-graphics.html#nine-patch). * + * You have the option to pass only one source image string for each key + * or pass an object containing a dark mode image too ({src, srcDarkMode}). + * * Valid key values: - * - `iphone5` (640x1136) // iPhone 5, SE - * - `iphone6` (750x1334) // iPhone 6, 6s, 7, 8 - * - `iphone6p_portrait` (1242x2208) // iPhone 6 Plus, 6s Plus, 7 Plus, 8 Plus - * - `iphone6p_landscape` (2208x1242) // iPhone 6 Plus, 6s Plus, 7 Plus, 8 Plus - * - `iphoneX_portrait` (1125x2436) // iPhone X - * - `iphoneX_landscape` (2436x1125) // iPhone X - * - `ipad_portrait_2x` (1536x2048) // iPad, iPad mini - * - `ipad_landscape_2x` (2048x1536) // iPad, iPad mini - * - `iphone` (320x480) // Legacy - * - `iphone_2x` (640x960) // Legacy - * - `ipad_portrait` (768x1024) // Legacy - * - `ipad_landscape` (1024x768) // Legacy - * - `android_mdpi_portrait` (320x480) - * - `android_mdpi_landscape` (480x320) - * - `android_hdpi_portrait` (480x800) - * - `android_hdpi_landscape` (800x480) - * - `android_xhdpi_portrait` (720x1280) - * - `android_xhdpi_landscape` (1280x720) - * - `android_xxhdpi_portrait` (960x1600) - * - `android_xxhdpi_landscape` (1600x960) - * - `android_xxxhdpi_portrait` (1280x1920) - * - `android_xxxhdpi_landscape` (1920x1280) + * - 'ios_universal' (Default@2xuniversalanyany.png) + * - 'ios_universal_3x' (Default@3xuniversalanyany.png) + * - 'Default@2xiphoneanyany' + * - 'Default@2xiphonecomany' + * - 'Default@2xiphonecomcom' + * - 'Default@3xiphoneanyanyg' + * - 'Default@3xiphoneanycom' + * - 'Default@3xiphonecomany' + * - 'Default@2xipadanyany' + * - 'Default@2xipadcomany' + * - 'android_mdpi_portrait' (320x480) + * - 'android_mdpi_landscape' (480x320) + * - 'android_hdpi_portrait' (480x800) + * - 'android_hdpi_landscape' (800x480) + * - 'android_xhdpi_portrait' (720x1280) + * - 'android_xhdpi_landscape' (1280x720) + * - 'android_xxhdpi_portrait' (960x1600) + * - 'android_xxhdpi_landscape' (1600x960) + * - 'android_xxxhdpi_portrait' (1280x1920) + * - 'android_xxxhdpi_landscape' (1920x1280) * * @memberOf App */ From d99f42c8df4331161b6c9b56c20bc7767b783782 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Wed, 8 Dec 2021 18:05:22 -0300 Subject: [PATCH 025/393] Change how we process the splash images for cordova (fixes #11555): - Updating docs --- docs/source/api/mobile-config.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/api/mobile-config.md b/docs/source/api/mobile-config.md index 849afde837..8d69412956 100644 --- a/docs/source/api/mobile-config.md +++ b/docs/source/api/mobile-config.md @@ -32,7 +32,7 @@ App.icons({ App.launchScreens({ 'ios_universal': { src: 'splash/Default@2x.png', srcDarkMode: 'splash/Default@2x~dark.png' }, - 'ios_universal_3x': 'splash/Default~iphone5.png', + 'ios_universal_3x': 'splash/Default@3x.png', // More screen sizes and platforms... }); From 03b9c1be58398791bfb958a9c9e15f3f5c8b5c56 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Thu, 9 Dec 2021 10:41:40 -0300 Subject: [PATCH 026/393] Change how we process the splash images for cordova (fixes #11555): - Improving docs and adding breaking change on History.md. --- History.md | 2 ++ tools/cordova/builder.js | 22 ++++++++++++---------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/History.md b/History.md index a68e45bb4c..9ce556c798 100644 --- a/History.md +++ b/History.md @@ -4,6 +4,8 @@ #### Breaking Changes +* Legacy launch screens keys for iOS on `App.launchScreens` are now deprecated in favor of new storyboard compliant keys [PR #11797](https://github.com/meteor/meteor/pull/11797). + #### Meteor Version Release #### Independent Releases diff --git a/tools/cordova/builder.js b/tools/cordova/builder.js index 737fd59c57..d7cdd0e476 100644 --- a/tools/cordova/builder.js +++ b/tools/cordova/builder.js @@ -760,20 +760,22 @@ configuration. The key may be deprecated.`); * be special "Nine-patch" image files that specify how they should be * stretched. See the [Android docs](https://developer.android.com/guide/topics/graphics/2d-graphics.html#nine-patch). * + * For best practices when developing a splash image, see the [Apple Guidelines](https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/launch-screen/). + * * You have the option to pass only one source image string for each key * or pass an object containing a dark mode image too ({src, srcDarkMode}). * * Valid key values: - * - 'ios_universal' (Default@2xuniversalanyany.png) - * - 'ios_universal_3x' (Default@3xuniversalanyany.png) - * - 'Default@2xiphoneanyany' - * - 'Default@2xiphonecomany' - * - 'Default@2xiphonecomcom' - * - 'Default@3xiphoneanyanyg' - * - 'Default@3xiphoneanycom' - * - 'Default@3xiphonecomany' - * - 'Default@2xipadanyany' - * - 'Default@2xipadcomany' + * - 'ios_universal' (Default@2xuniversalanyany.png - 2732x2732) - All @2x devices, if device specific is not declared. + * - 'ios_universal_3x' (Default@3xuniversalanyany.png - 2208x2208) - All @3x devices, if device specific is not declared. + * - 'Default@2xiphoneanyany' (1334x1334) - iPhone SE/6s/7/8/XR + * - 'Default@2xiphonecomany' (750x1334) - iPhone SE/6s/7/8/XR + * - 'Default@2xiphonecomcom' (1334x750) - iPhone SE/6s/7/8/XR + * - 'Default@3xiphoneanyanyg' (2208x2208) - iPhone 6s Plus/7 Plus/8 Plus/X/XS/XS Max + * - 'Default@3xiphoneanycom' (2208x1242) - iPhone 6s Plus/7 Plus/8 Plus/X/XS/XS Max + * - 'Default@3xiphonecomany' (1242x2208) - iPhone 6s Plus/7 Plus/8 Plus/X/XS/XS Max + * - 'Default@2xipadanyany' (2732x2732) - iPad Pro 12.9"/11"/10.5"/9.7"/7.9" + * - 'Default@2xipadcomany' (1278x2732) - iPad Pro 12.9"/11"/10.5"/9.7"/7.9" * - 'android_mdpi_portrait' (320x480) * - 'android_mdpi_landscape' (480x320) * - 'android_hdpi_portrait' (480x800) From 64dbf0d78da40ab9b484ad43e81dd16399239018 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Thu, 9 Dec 2021 10:47:55 -0300 Subject: [PATCH 027/393] Change how we process the splash images for cordova (fixes #11555): - Improving docs --- tools/cordova/builder.js | 44 +++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/tools/cordova/builder.js b/tools/cordova/builder.js index d7cdd0e476..87e18f1ff1 100644 --- a/tools/cordova/builder.js +++ b/tools/cordova/builder.js @@ -754,7 +754,7 @@ configuration. The key may be deprecated.`); * @summary Set the launch screen images for your mobile app. * @param {Object} launchScreens A dictionary where keys are different * devices, screen sizes, and orientations, and the values are image paths - * relative to the project root directory. + * relative to the project root directory or an object containing a dark mode image path too ({src, srcDarkMode}). * * For Android, launch screen images should * be special "Nine-patch" image files that specify how they should be @@ -762,30 +762,28 @@ configuration. The key may be deprecated.`); * * For best practices when developing a splash image, see the [Apple Guidelines](https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/launch-screen/). * - * You have the option to pass only one source image string for each key - * or pass an object containing a dark mode image too ({src, srcDarkMode}). * * Valid key values: - * - 'ios_universal' (Default@2xuniversalanyany.png - 2732x2732) - All @2x devices, if device specific is not declared. - * - 'ios_universal_3x' (Default@3xuniversalanyany.png - 2208x2208) - All @3x devices, if device specific is not declared. - * - 'Default@2xiphoneanyany' (1334x1334) - iPhone SE/6s/7/8/XR - * - 'Default@2xiphonecomany' (750x1334) - iPhone SE/6s/7/8/XR - * - 'Default@2xiphonecomcom' (1334x750) - iPhone SE/6s/7/8/XR - * - 'Default@3xiphoneanyanyg' (2208x2208) - iPhone 6s Plus/7 Plus/8 Plus/X/XS/XS Max - * - 'Default@3xiphoneanycom' (2208x1242) - iPhone 6s Plus/7 Plus/8 Plus/X/XS/XS Max - * - 'Default@3xiphonecomany' (1242x2208) - iPhone 6s Plus/7 Plus/8 Plus/X/XS/XS Max - * - 'Default@2xipadanyany' (2732x2732) - iPad Pro 12.9"/11"/10.5"/9.7"/7.9" - * - 'Default@2xipadcomany' (1278x2732) - iPad Pro 12.9"/11"/10.5"/9.7"/7.9" - * - 'android_mdpi_portrait' (320x480) - * - 'android_mdpi_landscape' (480x320) - * - 'android_hdpi_portrait' (480x800) - * - 'android_hdpi_landscape' (800x480) - * - 'android_xhdpi_portrait' (720x1280) - * - 'android_xhdpi_landscape' (1280x720) - * - 'android_xxhdpi_portrait' (960x1600) - * - 'android_xxhdpi_landscape' (1600x960) - * - 'android_xxxhdpi_portrait' (1280x1920) - * - 'android_xxxhdpi_landscape' (1920x1280) + * - `ios_universal` (Default@2xuniversalanyany.png - 2732x2732) - All @2x devices, if device specific is not declared. + * - `ios_universal_3x` (Default@3xuniversalanyany.png - 2208x2208) - All @3x devices, if device specific is not declared. + * - `Default@2xiphoneanyany` (1334x1334) - iPhone SE/6s/7/8/XR + * - `Default@2xiphonecomany` (750x1334) - iPhone SE/6s/7/8/XR + * - `Default@2xiphonecomcom` (1334x750) - iPhone SE/6s/7/8/XR + * - `Default@3xiphoneanyanyg` (2208x2208) - iPhone 6s Plus/7 Plus/8 Plus/X/XS/XS Max + * - `Default@3xiphoneanycom` (2208x1242) - iPhone 6s Plus/7 Plus/8 Plus/X/XS/XS Max + * - `Default@3xiphonecomany` (1242x2208) - iPhone 6s Plus/7 Plus/8 Plus/X/XS/XS Max + * - `Default@2xipadanyany` (2732x2732) - iPad Pro 12.9"/11"/10.5"/9.7"/7.9" + * - `Default@2xipadcomany` (1278x2732) - iPad Pro 12.9"/11"/10.5"/9.7"/7.9" + * - `android_mdpi_portrait` (320x480) + * - `android_mdpi_landscape` (480x320) + * - `android_hdpi_portrait` (480x800) + * - `android_hdpi_landscape` (800x480) + * - `android_xhdpi_portrait` (720x1280) + * - `android_xhdpi_landscape` (1280x720) + * - `android_xxhdpi_portrait` (960x1600) + * - `android_xxhdpi_landscape` (1600x960) + * - `android_xxxhdpi_portrait` (1280x1920) + * - `android_xxxhdpi_landscape` (1920x1280) * * @memberOf App */ From a7380a7542b1f2f02a8743f54a0eecad1ccd6a2b Mon Sep 17 00:00:00 2001 From: Evan Broder Date: Sun, 21 Nov 2021 10:33:40 -0800 Subject: [PATCH 028/393] installer: Add a flag to skip modifying exec path For environments where modifying your dotfiles is undesirable, you can run the installer with `npm install -g meteor --no-meteor-setup-exec-path`. --- docs/source/install.md | 10 ++++++++++ npm-packages/meteor-installer/README.md | 10 ++++++++++ npm-packages/meteor-installer/config.js | 7 ++++++- npm-packages/meteor-installer/install.js | 11 ++++++++--- 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/docs/source/install.md b/docs/source/install.md index 058595c4e3..4da3db52bc 100644 --- a/docs/source/install.md +++ b/docs/source/install.md @@ -38,6 +38,16 @@ If you only use sudo because of a distribution default permission system, [check In some cases you can get this error `npm WARN checkPermissions Missing write access to /usr/local/lib/node_modules` because your Node.js installation was performed with wrong permissions. An easy way to fix this is to install Node.js using [nvm](https://github.com/nvm-sh/nvm) and forcing it to be used in your terminal. You can force it in the current session of your terminal by running `nvm use 14`. +