diff --git a/.circleci/config.yml b/.circleci/config.yml index 8b714c9285..6736ff3860 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -79,7 +79,7 @@ run_save_node_bin: &run_save_node_bin build_machine_environment: &build_machine_environment # Specify that we want an actual machine (ala Circle 1.0), not a Docker image. docker: - - image: meteor/circleci:2024.09.11-android-34-node-20 + - image: meteor/circleci:2025.07.8-android-35-node-22 resource_class: large environment: # This multiplier scales the waitSecs for selftests. @@ -756,7 +756,7 @@ jobs: Docs: docker: # This Node version should match that in the meteor/docs CircleCI config. - - image: meteor/circleci:2024.09.11-android-34-node-20 + - image: meteor/circleci:2025.07.8-android-35-node-22 resource_class: large environment: CHECKOUT_METEOR_DOCS: /home/circleci/test_docs diff --git a/.travis.yml b/.travis.yml index 622ed6cadd..3e63726f4c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ dist: jammy sudo: required services: xvfb node_js: - - "22.16.0" + - "22.17.0" cache: directories: - ".meteor" diff --git a/docs/history.md b/docs/history.md index 243ea1da7d..2f932f7780 100644 --- a/docs/history.md +++ b/docs/history.md @@ -8,6 +8,123 @@ [//]: # (go to meteor/docs/generators/changelog/docs) +## v3.3.1, 05-08-2025 + +### Highlights + +- **MongoDB Driver Upgrades** + - Upgraded core MongoDB driver to `6.16.0` to address latest issues reported [#13710](https://github.com/meteor/meteor/pull/13710) + - Introduced `npm-mongo-legacy` to maintain compatibility with MongoDB 3.6 via `mongodb@6.9.0` [#13736](https://github.com/meteor/meteor/pull/13736) + - Mitigated a cursor leak issue by synchronizing `next()` and `close()` operations [#13786](https://github.com/meteor/meteor/pull/13786) + +- **Improved SWC integration** + - Fixed edge cases in config cache invalidation [#13809](https://github.com/meteor/meteor/pull/13809) + - Ensured `@swc/helpers` is consistently used for better bundle size and performance [#13820](https://github.com/meteor/meteor/pull/13820) + - Updated to SWC `1.12.14` [#13851](https://github.com/meteor/meteor/pull/13851) + +- **Tooling and Build System** + - Fixed regression affecting rebuild behavior [#13810](https://github.com/meteor/meteor/pull/13810) + - Addressed issues getting performance profiles in mounted volumes [#13827](https://github.com/meteor/meteor/pull/13827) + - Fallback to Babel parser when Acorn fails to parse source code [#13844](https://github.com/meteor/meteor/pull/13844) + +- **Mobile Support** + - Upgraded Cordova platform to version 14 [#13837](https://github.com/meteor/meteor/pull/13837) + +- **Developer Experience** + - Added TypeScript types for `isModern` and `getMinimumBrowserVersions` functions [#13704](https://github.com/meteor/meteor/pull/13704) + - Enhanced CLI help output and documented admin commands [#13826](https://github.com/meteor/meteor/pull/13826) + +- **Vite Tooling** + - Updated official Meteor + Vite skeletons [#13835](https://github.com/meteor/meteor/pull/13835) + +- **Runtime & Dependencies** + - Updated to Node.js `22.18.0` and NPM `10.9.3` [#13877](https://github.com/meteor/meteor/pull/13877) + - Bumped `meteor-node-stubs` to `1.2.21` [#13825](https://github.com/meteor/meteor/pull/13825) + +All Merged PRs@[GitHub PRs 3.3.1](https://github.com/meteor/meteor/pulls?q=is%3Apr+is%3Amerged+base%3Arelease-3.3.1) + +#### Breaking Changes + +##### MongoDB Driver Upgrades + +If you're using MongoDB 3.6 or earlier, install the new legacy package: + +```bash +meteor add npm-mongo-legacy +``` +This will pin the MongoDB driver to 6.9.0 for compatibility. + +If you’re on MongoDB 4+, the default [MongoDB driver 6.16.0](https://github.com/mongodb/node-mongodb-native/releases/tag/v6.16.0) is applied automatically. + +Please migrate your database as soon as possible to MongoDB 5 onward, as [MongoDB driver 6.17.0](https://github.com/mongodb/node-mongodb-native/releases/tag/v6.17.0) will drop MongoDB 4 support. We’ll keep offering `npm-mongo-legacy` so you can keep getting Meteor updates with your existing MongoDB legacy version. + +##### Cordova Upgrade + +The Cordova platform has been upgraded to version 14. Refer to the [Cordova Changelog](https://cordova.apache.org/announcements/2025/03/26/cordova-android-14.0.0.html) for more details on the changes and migration steps. + +#### Internal API changes + +N/A + +#### Migration Steps + +Please run the following command to update your project: + +```bash +meteor update --release 3.3.1 +``` + +--- + +While this is a patch release, Meteor 3.3, a recent minor update, introduced a modern build stack that’s now the default for new apps. Here’s how you can migrate to it. + +**Add this to your `package.json` to enable the new modern build stack:** + +```json +"meteor": { + "modern": true +} +``` + +Check the docs for help with the SWC migration, especially if your project uses many Babel plugins. + +[Modern Transpiler: SWC docs](https://docs.meteor.com/about/modern-build-stack/transpiler-swc.html) + +If you find any issues, please report them to the [Meteor issues tracker](https://github.com/meteor/meteor). + +#### Bumped Meteor Packages + +- babel-compiler@7.12.1 +- callback-hook@1.6.1 +- ecmascript@0.16.12 +- minifier-js@3.0.3 +- minimongo@2.0.3 +- modern-browsers@0.2.3 +- mongo@2.1.3 +- npm-mongo-legacy@6.9.0 +- npm-mongo@6.16.0 +- standard-minifier-js@3.1.1 +- tinytest@1.3.2 +- typescript@5.6.5 +- meteor-tool@3.3.1 + +#### Bumped NPM Packages + +- meteor-node-stubs@1.2.21 + +#### Special thanks to + +✨✨✨ + +- [@nachocodoner](https://github.com/nachocodoner) +- [@italojs](https://github.com/italojs) +- [@StorytellerCZ](https://github.com/StorytellerCZ) +- [@JorgenVatle](https://github.com/JorgenVatle) +- [@welkinwong](https://github.com/welkinwong) +- [@Saksham-Goel1107](https://github.com/Saksham-Goel1107) + +✨✨✨ + ## v3.3.0, 2025-06-11 ### Highlights diff --git a/meteor b/meteor index e452f7b273..65372bf258 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/usr/bin/env bash -BUNDLE_VERSION=22.16.0.1 +BUNDLE_VERSION=22.18.0.2 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. diff --git a/npm-packages/eslint-plugin-meteor/scripts/dev-bundle-tool-package.js b/npm-packages/eslint-plugin-meteor/scripts/dev-bundle-tool-package.js index 0ce217a8f2..aedf49c8c0 100644 --- a/npm-packages/eslint-plugin-meteor/scripts/dev-bundle-tool-package.js +++ b/npm-packages/eslint-plugin-meteor/scripts/dev-bundle-tool-package.js @@ -10,7 +10,7 @@ var packageJson = { dependencies: { // Explicit dependency because we are replacing it with a bundled version // and we want to make sure there are no dependencies on a higher version - npm: "10.9.2", + npm: "10.9.3", pacote: "https://github.com/meteor/pacote/tarball/a81b0324686e85d22c7688c47629d4009000e8b8", "node-gyp": "9.4.0", "@mapbox/node-pre-gyp": "1.0.11", diff --git a/npm-packages/meteor-installer/config.js b/npm-packages/meteor-installer/config.js index d820e33456..de877591e7 100644 --- a/npm-packages/meteor-installer/config.js +++ b/npm-packages/meteor-installer/config.js @@ -1,7 +1,7 @@ const os = require('os'); const path = require('path'); -const METEOR_LATEST_VERSION = '3.3'; +const METEOR_LATEST_VERSION = '3.3.1'; const sudoUser = process.env.SUDO_USER || ''; function isRoot() { return process.getuid && process.getuid() === 0; diff --git a/npm-packages/meteor-installer/package-lock.json b/npm-packages/meteor-installer/package-lock.json index a813defb08..4ac843a7c7 100644 --- a/npm-packages/meteor-installer/package-lock.json +++ b/npm-packages/meteor-installer/package-lock.json @@ -1,12 +1,12 @@ { "name": "meteor", - "version": "3.3.0", + "version": "3.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "meteor", - "version": "3.3.0", + "version": "3.3.1", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/npm-packages/meteor-installer/package.json b/npm-packages/meteor-installer/package.json index 5be1ef790a..3733b29f27 100644 --- a/npm-packages/meteor-installer/package.json +++ b/npm-packages/meteor-installer/package.json @@ -1,6 +1,6 @@ { "name": "meteor", - "version": "3.3.0", + "version": "3.3.1", "description": "Install Meteor", "main": "install.js", "scripts": { diff --git a/packages/babel-compiler/babel-compiler.js b/packages/babel-compiler/babel-compiler.js index 506f865117..10749cd588 100644 --- a/packages/babel-compiler/babel-compiler.js +++ b/packages/babel-compiler/babel-compiler.js @@ -25,11 +25,18 @@ var BCp = BabelCompiler.prototype; var excludedFileExtensionPattern = /\.(es5|min)\.js$/i; var hasOwn = Object.prototype.hasOwnProperty; +function getMeteorConfig() { + return Plugin?.getMeteorConfig() || {}; +} + // Check if verbose mode is enabled either in the provided config or in extraFeatures -BCp.isVerbose = function(config) { +BCp.isVerbose = function(config = getMeteorConfig()) { if (config?.modern?.transpiler?.verbose) { return true; } + if (config?.modern?.verbose) { + return true; + } if (config?.verbose) { return true; } @@ -80,47 +87,13 @@ function compileWithSwc(source, swcOptions = {}, { features }) { }; }); } -const DEFAULT_MODERN = { - transpiler: true, -}; -const normalizeModern = (r = false) => Object.fromEntries( - Object.entries(DEFAULT_MODERN).map(([k, def]) => [ - k, - r === true - ? def - : r === false || r?.[k] === false - ? false - : typeof r?.[k] === 'object' - ? { ...r[k] } - : def, - ]), -); - -let modernForced = JSON.parse(process.env.METEOR_MODERN || "false"); - -let lastModifiedMeteorConfig; -let lastModifiedMeteorConfigTime; BCp.initializeMeteorAppConfig = function () { - if (!lastModifiedMeteorConfig && !fs.existsSync(`${getMeteorAppDir()}/package.json`)) { - return; + const meteorConfig = getMeteorConfig(); + if (this.isVerbose()) { + logConfigBlock('Meteor Config', meteorConfig); } - const currentLastModifiedConfigTime = fs - .statSync(`${getMeteorAppDir()}/package.json`) - ?.mtime?.getTime(); - if (currentLastModifiedConfigTime !== lastModifiedMeteorConfigTime) { - lastModifiedMeteorConfigTime = currentLastModifiedConfigTime; - lastModifiedMeteorConfig = getMeteorAppPackageJson()?.meteor; - lastModifiedMeteorConfig = lastModifiedMeteorConfig != null ? { - ...lastModifiedMeteorConfig, - modern: normalizeModern(modernForced || lastModifiedMeteorConfig?.modern), - } : {}; - - if (this.isVerbose(lastModifiedMeteorConfig)) { - logConfigBlock('Meteor Config', lastModifiedMeteorConfig); - } - } - return lastModifiedMeteorConfig; + return meteorConfig; }; let lastModifiedSwcConfig; @@ -139,7 +112,9 @@ BCp.initializeMeteorAppSwcrc = function () { let currentLastModifiedConfigTime; if (hasSwcJs) { // For dynamic JS files, first get the resolved configuration - const resolvedConfig = lastModifiedSwcConfig || getMeteorAppSwcrc(swcFile); + const resolvedConfig = lastModifiedSwcConfigTime?.includes(`${fileModTime}`) + ? lastModifiedSwcConfig || getMeteorAppSwcrc(swcFile) + : getMeteorAppSwcrc(swcFile); // Calculate a hash of the resolved configuration to detect changes const contentHash = crypto .createHash('sha256') @@ -158,9 +133,11 @@ BCp.initializeMeteorAppSwcrc = function () { lastModifiedSwcConfigTime = currentLastModifiedConfigTime; lastModifiedSwcConfig = getMeteorAppSwcrc(swcFile); - if (this.isVerbose(lastModifiedMeteorConfig)) { - logConfigBlock('SWC Config', lastModifiedSwcConfig); + if (this.isVerbose()) { + logConfigBlock('SWC Custom Config', lastModifiedSwcConfig); } + + this._swcIncompatible = {}; } return lastModifiedSwcConfig; }; @@ -168,13 +145,49 @@ BCp.initializeMeteorAppSwcrc = function () { let lastModifiedSwcLegacyConfig; BCp.initializeMeteorAppLegacyConfig = function () { const swcLegacyConfig = convertBabelTargetsForSwc(Babel.getMinimumModernBrowserVersions()); - if (this.isVerbose(lastModifiedMeteorConfig) && !lastModifiedSwcLegacyConfig) { + if (this.isVerbose() && !lastModifiedSwcLegacyConfig) { logConfigBlock('SWC Legacy Config', swcLegacyConfig); } lastModifiedSwcLegacyConfig = swcLegacyConfig; return lastModifiedSwcConfig; }; +// Helper function to check if @swc/helpers is available +function hasSwcHelpers() { + return fs.existsSync(`${getMeteorAppDir()}/node_modules/@swc/helpers`); +} + +// Helper function to log friendly messages about SWC helpers +function logSwcHelpersStatus(isAvailable) { + const label = color('[SWC Helpers]', 36); + + if (isAvailable) { + // Green message for when helpers are available + console.log(`${label} ${color('✓ @swc/helpers is available in your project!', 32)}`); + console.log(` ${color('Benefits:', 32)}`); + console.log(` ${color('• Smaller bundle size: External helpers reduce code duplication', 32)}`); + console.log(` ${color('• Faster loads: less code to parse on first download', 32)}`); + console.log(` ${color('• Optional caching: separate vendor chunk can be cached by browsers', 32)}`); + } else { + // Yellow message for when helpers are not available + console.log(`${label} ${color('⚠ @swc/helpers is not available in your project', 33)}`); + console.log(` ${color('Suggestion:', 33)}`); + console.log(` ${color('• Add @swc/helpers to your project:', 33)}`); + console.log(` ${color('meteor npm install --save @swc/helpers', 33)}`); + console.log(` ${color('• This will reduce bundle size and improve performance', 33)}`); + } + console.log(); +} + +let hasSwcHelpersAvailable = false; +BCp.initializeMeteorAppSwcHelpersAvailable = function () { + hasSwcHelpersAvailable = hasSwcHelpers(); + if (this.isVerbose()) { + logSwcHelpersStatus(hasSwcHelpersAvailable); + } + return hasSwcHelpersAvailable; +}; + BCp.processFilesForTarget = function (inputFiles) { var compiler = this; @@ -184,6 +197,7 @@ BCp.processFilesForTarget = function (inputFiles) { this.initializeMeteorAppConfig(); this.initializeMeteorAppSwcrc(); this.initializeMeteorAppLegacyConfig(); + this.initializeMeteorAppSwcHelpersAvailable(); inputFiles.forEach(function (inputFile) { if (inputFile.supportsLazyCompilation) { @@ -314,6 +328,11 @@ BCp.processOneFileForTarget = function (inputFile, source) { jsx: hasJSXSupport, tsx: hasTSXSupport, }, + ...(hasSwcHelpersAvailable && + (packageName == null || + !['modules-runtime'].includes(packageName)) && { + externalHelpers: true, + }), }, module: { type: 'es6' }, minify: false, @@ -355,37 +374,38 @@ BCp.processOneFileForTarget = function (inputFile, source) { const isPackageCode = packageName != null; const isLegacyWebArch = arch.includes('legacy'); - const config = lastModifiedMeteorConfig?.modern?.transpiler; - const hasModernTranspiler = config != null && config !== false; + const transpConfig = getMeteorConfig()?.modern?.transpiler; + const hasModernTranspiler = transpConfig != null && transpConfig !== false; const shouldSkipSwc = !hasModernTranspiler || - (isAppCode && config?.excludeApp === true) || - (isNodeModulesCode && config?.excludeNodeModules === true) || - (isPackageCode && config?.excludePackages === true) || - (isLegacyWebArch && config?.excludeLegacy === true) || + (isAppCode && transpConfig?.excludeApp === true) || + (isNodeModulesCode && transpConfig?.excludeNodeModules === true) || + (isPackageCode && transpConfig?.excludePackages === true) || + (isLegacyWebArch && transpConfig?.excludeLegacy === true) || (isAppCode && - Array.isArray(config?.excludeApp) && - isExcludedConfig(inputFilePath, config?.excludeApp || [])) || + Array.isArray(transpConfig?.excludeApp) && + isExcludedConfig(inputFilePath, transpConfig?.excludeApp || [])) || (isNodeModulesCode && - Array.isArray(config?.excludeNodeModules) && - (isExcludedConfig(inputFilePath, config?.excludeNodeModules || []) || + Array.isArray(transpConfig?.excludeNodeModules) && + (isExcludedConfig(inputFilePath, transpConfig?.excludeNodeModules || []) || isExcludedConfig( inputFilePath.replace('node_modules/', ''), - config?.excludeNodeModules || [], + transpConfig?.excludeNodeModules || [], true, ))) || (isPackageCode && - Array.isArray(config?.excludePackages) && - (isExcludedConfig(packageName, config?.excludePackages || []) || + Array.isArray(transpConfig?.excludePackages) && + (isExcludedConfig(packageName, transpConfig?.excludePackages || []) || isExcludedConfig( `${packageName}/${inputFilePath}`, - config?.excludePackages || [], + transpConfig?.excludePackages || [], ))); const cacheKey = [ toBeAdded.hash, lastModifiedSwcConfigTime, isLegacyWebArch ? 'legacy' : '', + hasSwcHelpersAvailable, ] .filter(Boolean) .join('-'); @@ -402,7 +422,7 @@ BCp.processOneFileForTarget = function (inputFile, source) { compilation = this.readFromSwcCache({ cacheKey }); // Return cached result if found. if (compilation) { - if (this.isVerbose(config)) { + if (this.isVerbose()) { logTranspilation({ usedSwc: true, inputFilePath, @@ -432,7 +452,7 @@ BCp.processOneFileForTarget = function (inputFile, source) { usedSwc = false; } - if (this.isVerbose(config)) { + if (this.isVerbose()) { logTranspilation({ usedSwc, inputFilePath, @@ -448,7 +468,7 @@ BCp.processOneFileForTarget = function (inputFile, source) { babelOptions = setupBabelOptions(); compilation = compileWithBabel(source, babelOptions, cacheOptions); - if (this.isVerbose(config)) { + if (this.isVerbose()) { logTranspilation({ usedSwc: false, inputFilePath, diff --git a/packages/babel-compiler/package.js b/packages/babel-compiler/package.js index 4a45656d27..4fdba21272 100644 --- a/packages/babel-compiler/package.js +++ b/packages/babel-compiler/package.js @@ -1,14 +1,14 @@ Package.describe({ name: "babel-compiler", summary: "Parser/transpiler for ECMAScript 2015+ syntax", - version: '7.12.0', + version: '7.12.1', }); Npm.depends({ '@meteorjs/babel': '7.20.1', 'json5': '2.2.3', 'semver': '7.6.3', - "@meteorjs/swc-core": "1.1.3", + "@meteorjs/swc-core": "1.12.14", }); Package.onUse(function (api) { diff --git a/packages/callback-hook/hook.js b/packages/callback-hook/hook.js index ecc9c2ccfb..d1156f79fd 100644 --- a/packages/callback-hook/hook.js +++ b/packages/callback-hook/hook.js @@ -124,20 +124,6 @@ export class Hook { } } - async forEachAsync(iterator) { - const ids = Object.keys(this.callbacks); - for (let i = 0; i < ids.length; ++i) { - const id = ids[i]; - // check to see if the callback was removed during iteration - if (hasOwn.call(this.callbacks, id)) { - const callback = this.callbacks[id]; - if (!await iterator(callback)) { - break; - } - } - } - } - /** * For each registered callback, call the passed iterator function with the callback. * diff --git a/packages/callback-hook/package.js b/packages/callback-hook/package.js index 4b4f756266..6f9e9ac68a 100644 --- a/packages/callback-hook/package.js +++ b/packages/callback-hook/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Register callbacks on a hook", - version: '1.6.0', + version: '1.6.1', }); Package.onUse(function (api) { diff --git a/packages/ecmascript/package.js b/packages/ecmascript/package.js index a8fd89363d..3236405da8 100644 --- a/packages/ecmascript/package.js +++ b/packages/ecmascript/package.js @@ -1,6 +1,6 @@ Package.describe({ name: 'ecmascript', - version: '0.16.11', + version: '0.16.12', summary: 'Compiler plugin that supports ES2015+ in all .js files', documentation: 'README.md', }); diff --git a/packages/meteor-tool/package.js b/packages/meteor-tool/package.js index 88f7661c70..00beae7bc0 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: "3.3.0", + version: "3.3.1", }); Package.includeTool(); diff --git a/packages/minifier-js/package.js b/packages/minifier-js/package.js index 310391016c..22d44ba09e 100644 --- a/packages/minifier-js/package.js +++ b/packages/minifier-js/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "JavaScript minifier", - version: '3.0.2', + version: '3.0.3', }); Npm.depends({ diff --git a/packages/minimongo/common.js b/packages/minimongo/common.js index 7a08e569b1..2c97a941e5 100644 --- a/packages/minimongo/common.js +++ b/packages/minimongo/common.js @@ -2,6 +2,7 @@ import LocalCollection from './local_collection.js'; export const hasOwn = Object.prototype.hasOwnProperty; +export class MiniMongoQueryError extends Error {} // Each element selector contains: // - compileElementSelector, a function with args: // - operand - the "right hand side" of the operator @@ -24,7 +25,7 @@ export const ELEMENT_OPERATORS = { if (!(Array.isArray(operand) && operand.length === 2 && typeof operand[0] === 'number' && typeof operand[1] === 'number')) { - throw Error('argument to $mod must be an array of two numbers'); + throw new MiniMongoQueryError('argument to $mod must be an array of two numbers'); } // XXX could require to be ints or round or something @@ -38,7 +39,7 @@ export const ELEMENT_OPERATORS = { $in: { compileElementSelector(operand) { if (!Array.isArray(operand)) { - throw Error('$in needs an array'); + throw new MiniMongoQueryError('$in needs an array'); } const elementMatchers = operand.map(option => { @@ -47,7 +48,7 @@ export const ELEMENT_OPERATORS = { } if (isOperatorObject(option)) { - throw Error('cannot nest $ under $in'); + throw new MiniMongoQueryError('cannot nest $ under $in'); } return equalityElementMatcher(option); @@ -74,7 +75,7 @@ export const ELEMENT_OPERATORS = { // does. operand = 0; } else if (typeof operand !== 'number') { - throw Error('$size needs a number'); + throw new MiniMongoQueryError('$size needs a number'); } return value => Array.isArray(value) && value.length === operand; @@ -112,16 +113,16 @@ export const ELEMENT_OPERATORS = { 'maxKey': 127, }; if (!hasOwn.call(operandAliasMap, operand)) { - throw Error(`unknown string alias for $type: ${operand}`); + throw new MiniMongoQueryError(`unknown string alias for $type: ${operand}`); } operand = operandAliasMap[operand]; } else if (typeof operand === 'number') { if (operand === 0 || operand < -1 || (operand > 19 && operand !== 127)) { - throw Error(`Invalid numerical $type code: ${operand}`); + throw new MiniMongoQueryError(`Invalid numerical $type code: ${operand}`); } } else { - throw Error('argument to $type is not a number or a string'); + throw new MiniMongoQueryError('argument to $type is not a number or a string'); } return value => ( @@ -168,7 +169,7 @@ export const ELEMENT_OPERATORS = { $regex: { compileElementSelector(operand, valueSelector) { if (!(typeof operand === 'string' || operand instanceof RegExp)) { - throw Error('$regex has to be a string or RegExp'); + throw new MiniMongoQueryError('$regex has to be a string or RegExp'); } let regexp; @@ -180,7 +181,7 @@ export const ELEMENT_OPERATORS = { // ones (eg, Mongo supports x and s). Ideally we would implement x and s // by transforming the regexp, but not today... if (/[^gim]/.test(valueSelector.$options)) { - throw new Error('Only the i, m, and g regexp options are supported'); + throw new MiniMongoQueryError('Only the i, m, and g regexp options are supported'); } const source = operand instanceof RegExp ? operand.source : operand; @@ -198,7 +199,7 @@ export const ELEMENT_OPERATORS = { dontExpandLeafArrays: true, compileElementSelector(operand, valueSelector, matcher) { if (!LocalCollection._isPlainObject(operand)) { - throw Error('$elemMatch need an object'); + throw new MiniMongoQueryError('$elemMatch need an object'); } const isDocMatcher = !isOperatorObject( @@ -353,7 +354,7 @@ const VALUE_OPERATORS = { // $options just provides options for $regex; its logic is inside $regex $options(operand, valueSelector) { if (!hasOwn.call(valueSelector, '$regex')) { - throw Error('$options needs a $regex'); + throw new MiniMongoQueryError('$options needs a $regex'); } return everythingMatcher; @@ -361,14 +362,14 @@ const VALUE_OPERATORS = { // $maxDistance is basically an argument to $near $maxDistance(operand, valueSelector) { if (!valueSelector.$near) { - throw Error('$maxDistance needs a $near'); + throw new MiniMongoQueryError('$maxDistance needs a $near'); } return everythingMatcher; }, $all(operand, valueSelector, matcher) { if (!Array.isArray(operand)) { - throw Error('$all requires array'); + throw new MiniMongoQueryError('$all requires array'); } // Not sure why, but this seems to be what MongoDB does. @@ -379,7 +380,7 @@ const VALUE_OPERATORS = { const branchedMatchers = operand.map(criterion => { // XXX handle $all/$elemMatch combination if (isOperatorObject(criterion)) { - throw Error('no $ expressions in $all'); + throw new MiniMongoQueryError('no $ expressions in $all'); } // This is always a regexp or equality selector. @@ -392,7 +393,7 @@ const VALUE_OPERATORS = { }, $near(operand, valueSelector, matcher, isRoot) { if (!isRoot) { - throw Error('$near can\'t be inside another $ operator'); + throw new MiniMongoQueryError('$near can\'t be inside another $ operator'); } matcher._hasGeoQuery = true; @@ -433,7 +434,7 @@ const VALUE_OPERATORS = { maxDistance = valueSelector.$maxDistance; if (!isIndexable(operand)) { - throw Error('$near argument must be coordinate pair or GeoJSON'); + throw new MiniMongoQueryError('$near argument must be coordinate pair or GeoJSON'); } point = pointToArray(operand); @@ -549,12 +550,12 @@ const andBranchedMatchers = andSomeMatchers; function compileArrayOfDocumentSelectors(selectors, matcher, inElemMatch) { if (!Array.isArray(selectors) || selectors.length === 0) { - throw Error('$and/$or/$nor must be nonempty array'); + throw new MiniMongoQueryError('$and/$or/$nor must be nonempty array'); } return selectors.map(subSelector => { if (!LocalCollection._isPlainObject(subSelector)) { - throw Error('$or/$and/$nor entries need to be full objects'); + throw new MiniMongoQueryError('$or/$and/$nor entries need to be full objects'); } return compileDocumentSelector(subSelector, matcher, {inElemMatch}); @@ -576,7 +577,7 @@ export function compileDocumentSelector(docSelector, matcher, options = {}) { // Outer operators are either logical operators (they recurse back into // this function), or $where. if (!hasOwn.call(LOGICAL_OPERATORS, key)) { - throw new Error(`Unrecognized logical operator: ${key}`); + throw new MiniMongoQueryError(`Unrecognized logical operator: ${key}`); } matcher._isSimple = false; @@ -682,7 +683,7 @@ function distanceCoordinatePairs(a, b) { // for equality with that thing. export function equalityElementMatcher(elementSelector) { if (isOperatorObject(elementSelector)) { - throw Error('Can\'t create equalityValueSelector for operator object'); + throw new MiniMongoQueryError('Can\'t create equalityValueSelector for operator object'); } // Special-case: null and undefined are equal (if you got undefined in there @@ -759,7 +760,7 @@ function getOperandBitmask(operand, selector) { } // bad operand - throw Error( + throw new MiniMongoQueryError( `operand to ${selector} must be a numeric bitmask (representable as a ` + 'non-negative 32-bit signed integer), a bindata bitmask or an array with ' + 'bit positions (non-negative integers)' @@ -813,12 +814,11 @@ function insertIntoDocument(document, key, value) { (existingKey.length > key.length && existingKey.indexOf(`${key}.`) === 0) || (key.length > existingKey.length && key.indexOf(`${existingKey}.`) === 0) ) { - throw new Error( - `cannot infer query fields to set, both paths '${existingKey}' and ` + - `'${key}' are matched` + throw new MiniMongoQueryError( + `cannot infer query fields to set, both paths '${existingKey}' and '${key}' are matched` ); } else if (existingKey === key) { - throw new Error( + throw new MiniMongoQueryError( `cannot infer query fields to set, path '${key}' is matched twice` ); } @@ -863,7 +863,7 @@ export function isOperatorObject(valueSelector, inconsistentOK) { theseAreOperators = thisIsOperator; } else if (theseAreOperators !== thisIsOperator) { if (!inconsistentOK) { - throw new Error( + throw new MiniMongoQueryError( `Inconsistent operator: ${JSON.stringify(valueSelector)}` ); } @@ -1132,7 +1132,7 @@ function operatorBranchedMatcher(valueSelector, matcher, isRoot) { ); } - throw new Error(`Unrecognized operator: ${operator}`); + throw new MiniMongoQueryError(`Unrecognized operator: ${operator}`); }); return andBranchedMatchers(operatorMatchers); @@ -1232,7 +1232,7 @@ function populateDocumentWithObject(document, key, value) { // Literal (possibly empty) object ( or empty object ) // Don't allow mixing '$'-prefixed with non-'$'-prefixed fields if (keys.length !== unprefixedKeys.length) { - throw new Error(`unknown operator: ${unprefixedKeys[0]}`); + throw new MiniMongoQueryError(`unknown operator: ${unprefixedKeys[0]}`); } validateObject(value, key); diff --git a/packages/minimongo/package.js b/packages/minimongo/package.js index 280fea958b..d29b90c5ac 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: "2.0.2", + version: "2.0.3", }); Package.onUse((api) => { diff --git a/packages/modern-browsers/modern.d.ts b/packages/modern-browsers/modern.d.ts index 69f1268f74..efa33b82e8 100644 --- a/packages/modern-browsers/modern.d.ts +++ b/packages/modern-browsers/modern.d.ts @@ -1,4 +1,12 @@ +export declare function isModern( + browser: { name: string, major: number, minor?: number, patch?: number } +): boolean; + export declare function setMinimumBrowserVersions( versions: Record, - source: string + source?: string ): void; + +export declare function getMinimumBrowserVersions(): Record>; + +export declare function calculateHashOfMinimumVersions(): string; \ No newline at end of file diff --git a/packages/modern-browsers/package.js b/packages/modern-browsers/package.js index 5187b13a29..ca05f1f8a3 100644 --- a/packages/modern-browsers/package.js +++ b/packages/modern-browsers/package.js @@ -1,6 +1,6 @@ Package.describe({ name: 'modern-browsers', - version: '0.2.2', + version: '0.2.3', summary: 'API for defining the boundary between modern and legacy ' + 'JavaScript clients', diff --git a/packages/mongo/asynchronous_cursor.js b/packages/mongo/asynchronous_cursor.js index e40491f983..0c2668560a 100644 --- a/packages/mongo/asynchronous_cursor.js +++ b/packages/mongo/asynchronous_cursor.js @@ -8,6 +8,8 @@ import { replaceMongoAtomWithMeteor, replaceTypes } from './mongo_common'; * This is an internal implementation detail and is created lazily by the main Cursor class. */ export class AsynchronousCursor { + _closing = false; + _pendingNext = null; constructor(dbCursor, cursorDescription, options) { this._dbCursor = dbCursor; this._cursorDescription = cursorDescription; @@ -36,10 +38,19 @@ export class AsynchronousCursor { // Returns a Promise for the next object from the underlying cursor (before // the Mongo->Meteor type replacement). async _rawNextObjectPromise() { + if (this._closing) { + // Prevent next() after close is called + return null; + } try { - return this._dbCursor.next(); + this._pendingNext = this._dbCursor.next(); + const result = await this._pendingNext; + this._pendingNext = null; + return result; } catch (e) { console.error(e); + } finally { + this._pendingNext = null; } } @@ -74,24 +85,24 @@ export class AsynchronousCursor { // _nextObjectPromise) or rejected if the cursor doesn't return within // timeoutMS ms. _nextObjectPromiseWithTimeout(timeoutMS) { - if (!timeoutMS) { - return this._nextObjectPromise(); - } const nextObjectPromise = this._nextObjectPromise(); - const timeoutErr = new Error('Client-side timeout waiting for next object'); - const timeoutPromise = new Promise((resolve, reject) => { - setTimeout(() => { - reject(timeoutErr); + if (!timeoutMS) { + return nextObjectPromise; + } + + const timeoutPromise = new Promise(resolve => { + // On timeout, close the cursor. + const timeoutId = setTimeout(() => { + resolve(this.close()); }, timeoutMS); - }); - return Promise.race([nextObjectPromise, timeoutPromise]) - .catch((err) => { - if (err === timeoutErr) { - this.close(); - return; - } - throw err; + + // If the `_nextObjectPromise` returned first, cancel the timeout. + nextObjectPromise.finally(() => { + clearTimeout(timeoutId); }); + }); + + return Promise.race([nextObjectPromise, timeoutPromise]); } async forEach(callback, thisArg) { @@ -123,7 +134,16 @@ export class AsynchronousCursor { } // Mostly usable for tailable cursors. - close() { + async close() { + this._closing = true; + // If there's a pending next(), wait for it to finish or abort + if (this._pendingNext) { + try { + await this._pendingNext; + } catch (e) { + // ignore + } + } this._dbCursor.close(); } diff --git a/packages/mongo/collection/collection.js b/packages/mongo/collection/collection.js index ec2e3323b7..f41f97a68c 100644 --- a/packages/mongo/collection/collection.js +++ b/packages/mongo/collection/collection.js @@ -3,7 +3,8 @@ import { AsyncMethods } from './methods_async'; import { SyncMethods } from './methods_sync'; import { IndexMethods } from './methods_index'; import { - ID_GENERATORS, normalizeOptions, + ID_GENERATORS, + normalizeOptions, setupAutopublish, setupConnection, setupDriver, @@ -11,7 +12,6 @@ import { validateCollectionName } from './collection_utils'; import { ReplicationMethods } from './methods_replication'; -import { watchChangeStream } from './watch_change_stream'; /** * @summary Namespace for MongoDB-related items @@ -267,7 +267,3 @@ Meteor.Collection = Mongo.Collection; // Allow deny stuff is now in the allow-deny package Object.assign(Mongo.Collection.prototype, AllowDeny.CollectionPrototype); - -// Só agora que Mongo.Collection existe, adicionamos o método ao prototype -Object.assign(Mongo.Collection.prototype, { watchChangeStream }); - diff --git a/packages/mongo/collection/collection_utils.js b/packages/mongo/collection/collection_utils.js index a0f7442ded..e6c79646d5 100644 --- a/packages/mongo/collection/collection_utils.js +++ b/packages/mongo/collection/collection_utils.js @@ -88,12 +88,17 @@ export function normalizeOptions(options) { options.connection = options.manager; } + const cleanedOptions = Object.fromEntries( + Object.entries(options || {}).filter(([_, v]) => v !== undefined), + ); + + // 2) Spread defaults first, then only the defined overrides return { connection: undefined, idGeneration: 'STRING', transform: null, _driver: undefined, _preventAutopublish: false, - ...options, + ...cleanedOptions, }; } diff --git a/packages/mongo/collection/watch_change_stream.js b/packages/mongo/collection/watch_change_stream.js deleted file mode 100644 index a4e8ae7b95..0000000000 --- a/packages/mongo/collection/watch_change_stream.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @summary Watches the MongoDB collection using Change Streams. - * @locus Server - * @memberof Mongo.Collection - * @instance - * @param {Array} [pipeline] Optional aggregation pipeline to filter Change Stream events. - * @param {Object} [options] Optional settings for the Change Stream. - * @returns {ChangeStream} The MongoDB ChangeStream instance. - * @throws {Error} If called on a client/minimongo collection. - * - * @example - * const changeStream = MyCollection.watchChangeStream([ - * { $match: { 'operationType': 'insert' } } - * ]); - * changeStream.on('change', (change) => { - * console.log('Change detected:', change); - * }); - */ - -export function watchChangeStream(pipeline = [], options = {}) { - // Only available on server - if (typeof Package === 'undefined' || !this.rawCollection) { - throw new Error('watchChangeStream is only available on server collections'); - } - const raw = this.rawCollection(); - if (!raw.watch) { - throw new Error('Underlying collection does not support watch (Change Streams)'); - } - console.log('[watchChangeStream] Chamando raw.watch() com pipeline:', JSON.stringify(pipeline, null, 2), 'e options:', JSON.stringify(options, null, 2)); - return raw.watch(pipeline, options); -} diff --git a/packages/mongo/mongo_connection.js b/packages/mongo/mongo_connection.js index 114b8de9aa..b093bcfa00 100644 --- a/packages/mongo/mongo_connection.js +++ b/packages/mongo/mongo_connection.js @@ -1,5 +1,6 @@ import { Meteor } from 'meteor/meteor'; import { CLIENT_ONLY_METHODS, getAsyncMethodName } from 'meteor/minimongo/constants'; +import { MiniMongoQueryError } from 'meteor/minimongo/common'; import path from 'path'; import { AsynchronousCursor } from './asynchronous_cursor'; import { Cursor } from './cursor'; @@ -886,6 +887,9 @@ Object.assign(MongoConnection.prototype, { } catch (e) { // XXX make all compilation errors MinimongoError or something // so that this doesn't ignore unrelated exceptions + if (e instanceof MiniMongoQueryError) { + throw e; + } return false; } }, diff --git a/packages/mongo/package.js b/packages/mongo/package.js index cefaf18449..de5ab8e6f6 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: "2.1.2", + version: "2.1.3", }); Npm.depends({ diff --git a/packages/mongo/tests/collection_tests.js b/packages/mongo/tests/collection_tests.js index a0e9e7fecf..e8841c5892 100644 --- a/packages/mongo/tests/collection_tests.js +++ b/packages/mongo/tests/collection_tests.js @@ -485,3 +485,58 @@ Meteor.isServer && Tinytest.addAsync('collection - simple add', async function(t test.equal((await collection.findOneAsync(id)).a, 2); await collection.removeAsync({}); }); + +Tinytest.addAsync('collection - default idGeneration when not provided', async function(test) { + if (!Meteor.isServer) { + return; + } + // Create a collection without specifying idGeneration option + var collectionName = 'defaultIdGeneration' + test.id; + var collection = new Mongo.Collection(collectionName, { idGeneration: undefined }); + + // Insert a document + var id = await collection.insertAsync({a: 1}); + + // Verify that the _id is a string + test.isTrue(typeof id === 'string', 'Document _id should be a string when no idGeneration option is provided'); + test.isFalse(id instanceof Mongo.ObjectID, 'Document _id should not be a Mongo.ObjectID when no idGeneration option is provided'); + + // Clean up + await collection.removeAsync({}); +}); + +Tinytest.addAsync('collection - compare default idGeneration with explicit idGeneration', async function(test) { + if (!Meteor.isServer) { + return; + } + // Create a collection without specifying idGeneration option + var defaultCollectionName = 'defaultIdGeneration2' + test.id; + var defaultCollection = new Mongo.Collection(defaultCollectionName, { idGeneration: undefined }); + + // Create a collection with explicit STRING idGeneration + var stringCollectionName = 'stringIdGeneration' + test.id; + var stringCollection = new Mongo.Collection(stringCollectionName, { idGeneration: 'STRING' }); + + // Create a collection with MONGO idGeneration + var mongoCollectionName = 'mongoIdGeneration' + test.id; + var mongoCollection = new Mongo.Collection(mongoCollectionName, { idGeneration: 'MONGO' }); + + // Insert documents + var defaultId = await defaultCollection.insertAsync({a: 1}); + var stringId = await stringCollection.insertAsync({a: 1}); + var mongoId = await mongoCollection.insertAsync({a: 1}); + + // Verify default behaves like STRING + test.isTrue(typeof defaultId === 'string', 'Default idGeneration should produce string IDs'); + test.isTrue(typeof stringId === 'string', 'STRING idGeneration should produce string IDs'); + test.isFalse(defaultId instanceof Mongo.ObjectID, 'Default idGeneration should not produce Mongo.ObjectID'); + test.isFalse(stringId instanceof Mongo.ObjectID, 'STRING idGeneration should not produce Mongo.ObjectID'); + + // Verify MONGO produces ObjectIDs + test.isTrue(mongoId instanceof Mongo.ObjectID, 'MONGO idGeneration should produce Mongo.ObjectID'); + + // Clean up + await defaultCollection.removeAsync({}); + await stringCollection.removeAsync({}); + await mongoCollection.removeAsync({}); +}); diff --git a/packages/mongo/tests/mongo_livedata_tests.js b/packages/mongo/tests/mongo_livedata_tests.js index c17202dd21..0578665f80 100644 --- a/packages/mongo/tests/mongo_livedata_tests.js +++ b/packages/mongo/tests/mongo_livedata_tests.js @@ -4298,9 +4298,10 @@ Tinytest.addAsync( await Collection.updateAsync({ _id: 'a' }, { $set: { num: 1 } }); await Collection.updateAsync({ _id: 'b' }, { $set: { num: 2 } }); + if(Meteor.isClient) Meteor._sleepForMs(100); // wait for async operations to complete items = await Collection.find().fetchAsync(); itemIds = items.map(_item => _item.num); - + test.equal(itemIds, [1, 2]); await Collection.removeAsync({ _id: 'a' }); diff --git a/packages/mongo/tests/oplog_tests.js b/packages/mongo/tests/oplog_tests.js index eb6f5eb3df..2ffb0c32eb 100644 --- a/packages/mongo/tests/oplog_tests.js +++ b/packages/mongo/tests/oplog_tests.js @@ -1,3 +1,5 @@ +import { MiniMongoQueryError } from 'meteor/minimongo/common'; + var randomId = Random.id(); var OplogCollection = new Mongo.Collection("oplog-" + randomId); @@ -9,11 +11,16 @@ Tinytest.addAsync('mongo-livedata - oplog - cursorSupported', async function( var supported = async function(expected, selector, options) { var cursor = OplogCollection.find(selector, options); - var handle = await cursor.observeChanges({ added: function() {} }); - // If there's no oplog at all, we shouldn't ever use it. - if (!oplogEnabled) expected = false; - test.equal(!!handle._multiplexer._observeDriver._usesOplog, expected); - handle.stop(); + try { + var handle = await cursor.observeChanges({ added: function() {} }); + // If there's no oplog at all, we shouldn't ever use it. + if (!oplogEnabled) expected = false; + test.equal(!!handle._multiplexer._observeDriver._usesOplog, !!expected); + handle.stop(); + } catch(e){ + if (e instanceof MiniMongoQueryError) return test.isFalse(expected); + else test.fail(e.message); + } }; await supported(true, 'asdf'); diff --git a/packages/non-core/blaze b/packages/non-core/blaze index 0856ca8bf7..92a7d9ca78 160000 --- a/packages/non-core/blaze +++ b/packages/non-core/blaze @@ -1 +1 @@ -Subproject commit 0856ca8bf7730fbe1944142642a0c5be82fb9999 +Subproject commit 92a7d9ca7810655fb066274c54537d298fc1bca1 diff --git a/packages/npm-mongo-legacy/README.md b/packages/npm-mongo-legacy/README.md new file mode 100644 index 0000000000..39675814ec --- /dev/null +++ b/packages/npm-mongo-legacy/README.md @@ -0,0 +1,4 @@ +# npm-mongo-legacy +[Source code of released version](https://github.com/meteor/meteor/tree/devel/packages/npm-mongo-legacy) +*** + diff --git a/packages/npm-mongo-legacy/index.d.ts b/packages/npm-mongo-legacy/index.d.ts new file mode 100644 index 0000000000..d7ab3d7558 --- /dev/null +++ b/packages/npm-mongo-legacy/index.d.ts @@ -0,0 +1,3 @@ +import * as NpmModuleMongodb from 'mongodb'; +declare const NpmModuleMongodbVersion: string; +export { NpmModuleMongodb, NpmModuleMongodbVersion }; diff --git a/packages/npm-mongo-legacy/package.js b/packages/npm-mongo-legacy/package.js new file mode 100644 index 0000000000..50bf8dd77d --- /dev/null +++ b/packages/npm-mongo-legacy/package.js @@ -0,0 +1,18 @@ +// This has been moved out of the `mongo` package so it can be used by the tool +// via isopacket, without having to also load ddp-server. + +Package.describe({ + summary: "Wrapper around the mongo npm package (legacy)", + version: "6.9.0", + documentation: null, +}); + +Npm.depends({ + mongodb: "6.9.0", +}); + +Package.onUse(function (api) { + api.addFiles("wrapper.js", "server"); + api.export(["NpmModuleMongodb", "NpmModuleMongodbVersion"], "server"); + api.addAssets("index.d.ts", "server"); +}); diff --git a/packages/npm-mongo-legacy/wrapper.js b/packages/npm-mongo-legacy/wrapper.js new file mode 100644 index 0000000000..e341493895 --- /dev/null +++ b/packages/npm-mongo-legacy/wrapper.js @@ -0,0 +1,11 @@ +const oldNoDeprecationValue = process.noDeprecation; +try { + // Silence deprecation warnings introduced in a patch update to mongodb: + // https://github.com/meteor/meteor/pull/9942#discussion_r218564879 + process.noDeprecation = true; + NpmModuleMongodb = Npm.require('mongodb'); +} finally { + process.noDeprecation = oldNoDeprecationValue; +} + +NpmModuleMongodbVersion = Npm.require('mongodb/package.json').version; diff --git a/packages/npm-mongo/package.js b/packages/npm-mongo/package.js index 8b45db0032..be29b54a9a 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: "6.10.2", + version: "6.16.0", documentation: null, }); Npm.depends({ - mongodb: "6.9.0" + mongodb: "6.16.0" }); Package.onUse(function (api) { diff --git a/packages/npm-mongo/wrapper.js b/packages/npm-mongo/wrapper.js index e341493895..fd01b4fd0d 100644 --- a/packages/npm-mongo/wrapper.js +++ b/packages/npm-mongo/wrapper.js @@ -1,11 +1,49 @@ +const { MongoClient, MongoCompatibilityError } = Npm.require('mongodb'); + +function connect(client) { + return client.connect() + .catch(error => { + if (error.cause instanceof MongoCompatibilityError && error.message.includes('maximum wire version')) { + console.warn(`[DEPRECATION] Legacy MongoDB version detected, using mongo-legacy package: ${error.message} + Warning: MongoDB versions <= 3.6 are deprecated. Some Meteor features may not work properly with this version. + It is recommended to use MongoDB >= 4.`); + if (!Package['npm-mongo-legacy']) { + throw new Error('Please, install npm-mongo-legacy package to use this version of MongoDB running "meteor add npm-mongo-legacy", then move the listed package inside .meteor/packages to the top.'); + } + return false + } + }) +} + +if (process.env.MONGO_URL && (/^mongodb(\+srv)?:\/\//.test(process.env.MONGO_URL))) { + try { + connect(new MongoClient(process.env.MONGO_URL, { + tls: true, + tlsAllowInvalidCertificates: true, + })).then(client => { + if (client) client.close(); + }); + } catch (e) { + console.warn('Invalid MongoDB connection string in MONGO_URL:', process.env.MONGO_URL); + } +} + +const useLegacyMongo = !!Package['npm-mongo-legacy'] const oldNoDeprecationValue = process.noDeprecation; + +useLegacyMongo && console.log('WARN: npm-mongo-legacy package detected, using package for mongo <= 3.6'); + try { // Silence deprecation warnings introduced in a patch update to mongodb: // https://github.com/meteor/meteor/pull/9942#discussion_r218564879 process.noDeprecation = true; - NpmModuleMongodb = Npm.require('mongodb'); + NpmModuleMongodb = useLegacyMongo + ? Package['npm-mongo-legacy'].NpmModuleMongodb + : Npm.require('mongodb'); } finally { process.noDeprecation = oldNoDeprecationValue; } -NpmModuleMongodbVersion = Npm.require('mongodb/package.json').version; +NpmModuleMongodbVersion = useLegacyMongo + ? Package['npm-mongo-legacy'].NpmModuleMongodbVersion + : Npm.require('mongodb/package.json').version; diff --git a/packages/standard-minifier-js/package.js b/packages/standard-minifier-js/package.js index cbb25a00bb..6f23b6e939 100644 --- a/packages/standard-minifier-js/package.js +++ b/packages/standard-minifier-js/package.js @@ -1,6 +1,6 @@ Package.describe({ name: 'standard-minifier-js', - version: '3.1.0', + version: '3.1.1', summary: 'Standard javascript minifiers used with Meteor apps by default.', documentation: 'README.md', }); @@ -12,7 +12,7 @@ Package.registerBuildPlugin({ 'ecmascript' ], npmDependencies: { - '@meteorjs/swc-core': '1.1.3', + '@meteorjs/swc-core': '1.12.14', 'acorn': '8.10.0', "@babel/runtime": "7.18.9", '@babel/parser': '7.22.7', diff --git a/packages/standard-minifier-js/plugin/minify-js.js b/packages/standard-minifier-js/plugin/minify-js.js index e3fb61182d..0fe5ca613f 100644 --- a/packages/standard-minifier-js/plugin/minify-js.js +++ b/packages/standard-minifier-js/plugin/minify-js.js @@ -1,17 +1,4 @@ import { extractModuleSizesTree } from "./stats.js"; -import fs from 'fs'; - -export function getConfig() { - try{ - const meteorAppDir = process.cwd(); - const packageJson = fs.readFileSync(`${meteorAppDir}/package.json`, 'utf8'); - const meteorConfig = JSON.parse(packageJson).meteor; - return meteorConfig; - } catch (error) { - console.log(error); - return {}; - } -}; const statsEnabled = process.env.DISABLE_CLIENT_STATS !== 'true' @@ -26,9 +13,7 @@ const Meteor = typeof global.Meteor !== 'undefined' ? global.Meteor : { // Profile for test and production environments let Profile; -if (typeof global.Profile !== 'undefined') { - Profile = global.Profile; -} else if (typeof Plugin !== 'undefined' && Plugin.Profile) { +if (typeof Plugin !== 'undefined' && Plugin.Profile) { Profile = Plugin.Profile; } else { Profile = function (label, func) { @@ -41,6 +26,10 @@ if (typeof global.Profile !== 'undefined') { } } +function getMeteorConfig() { + return Plugin?.getMeteorConfig() || {}; +} + let swc; // Register the minifier only when Plugin is available (not in tests) @@ -54,11 +43,6 @@ if (typeof Plugin !== 'undefined') { } export class MeteorMinifier { - - constructor() { - this.config = getConfig(); - } - _minifyWithSWC(file) { return Profile('_minifyWithSWC', () => { swc = swc || require('@meteorjs/swc-core'); @@ -120,9 +104,14 @@ export class MeteorMinifier { minifyOneFile(file) { return Profile('minifyOneFile', () => { - const modern = this.config && (this.config.modern === true || (this.config.modern && this.config.modern.minifier === true)); + const meteorConfig = getMeteorConfig(); + const modern = + meteorConfig && + (meteorConfig?.modern === true || + (meteorConfig?.modern && + meteorConfig?.modern?.minifier === true)); // check if config is an empty object - if(this.config && Object.keys(this.config).length === 0 || !modern) { + if(meteorConfig && Object.keys(meteorConfig).length === 0 || !modern) { Meteor._debug(`Minifying using Terser | file: ${file.getPathInBundle()}`); return this._minifyWithTerser(file); } diff --git a/packages/tinytest/package.js b/packages/tinytest/package.js index e869b316ea..db464f3bf2 100644 --- a/packages/tinytest/package.js +++ b/packages/tinytest/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Tiny testing framework", - version: '1.3.1', + version: '1.3.2', }); Npm.depends({ diff --git a/packages/tinytest/tinytest.js b/packages/tinytest/tinytest.js index afa1391207..b66097aafb 100644 --- a/packages/tinytest/tinytest.js +++ b/packages/tinytest/tinytest.js @@ -74,7 +74,7 @@ export class TestCaseResults { var frame = stack[i]; // Heuristic: use the OUTERMOST line which is in a :tests.js // file (this is less likely to be a test helper function). - const fileName = frame.getFileName(); + const fileName = frame?.getFileName ? frame.getFileName() : null; if (fileName && fileName.match(/:tests\.js/)) { doc.filename = fileName; doc.line = frame.getLineNumber(); diff --git a/packages/typescript/package.js b/packages/typescript/package.js index b84968210d..2f4d514837 100644 --- a/packages/typescript/package.js +++ b/packages/typescript/package.js @@ -1,6 +1,6 @@ Package.describe({ name: 'typescript', - version: '5.6.4', + version: '5.6.5', summary: 'Compiler plugin that compiles TypeScript and ECMAScript in .ts and .tsx files', documentation: 'README.md', diff --git a/scripts/admin/meteor-release-experimental.json b/scripts/admin/meteor-release-experimental.json index 04dd922851..2432e8857d 100644 --- a/scripts/admin/meteor-release-experimental.json +++ b/scripts/admin/meteor-release-experimental.json @@ -1,6 +1,6 @@ { "track": "METEOR", - "version": "3.3-rc.0", + "version": "3.3.1-rc.2", "recommended": false, "official": false, "description": "Meteor experimental release" diff --git a/scripts/admin/meteor-release-official.json b/scripts/admin/meteor-release-official.json index 75cf5a5520..3b26432a73 100644 --- a/scripts/admin/meteor-release-official.json +++ b/scripts/admin/meteor-release-official.json @@ -1,6 +1,6 @@ { "track": "METEOR", - "version": "3.3", + "version": "3.3.1", "recommended": false, "official": true, "description": "The Official Meteor Distribution" diff --git a/scripts/build-dev-bundle-common.sh b/scripts/build-dev-bundle-common.sh index a00f7e4f2b..6f06d676a6 100644 --- a/scripts/build-dev-bundle-common.sh +++ b/scripts/build-dev-bundle-common.sh @@ -5,10 +5,10 @@ set -u UNAME=$(uname) ARCH=$(uname -m) -NODE_VERSION=22.16.0 +NODE_VERSION=22.18.0 MONGO_VERSION_64BIT=7.0.16 MONGO_VERSION_32BIT=3.2.22 -NPM_VERSION=10.9.2 +NPM_VERSION=10.9.3 if [ "$UNAME" == "Linux" ] ; then diff --git a/scripts/dev-bundle-tool-package.js b/scripts/dev-bundle-tool-package.js index 6996b8f674..b8043936a5 100644 --- a/scripts/dev-bundle-tool-package.js +++ b/scripts/dev-bundle-tool-package.js @@ -10,7 +10,7 @@ var packageJson = { dependencies: { // Explicit dependency because we are replacing it with a bundled version // and we want to make sure there are no dependencies on a higher version - npm: "10.9.2", + npm: "10.9.3", "node-gyp": "10.2.0", "@mapbox/node-pre-gyp": "1.0.11", typescript: "5.6.3", diff --git a/tools/cli/commands.js b/tools/cli/commands.js index 240dbdcd3a..d81c03ffb5 100644 --- a/tools/cli/commands.js +++ b/tools/cli/commands.js @@ -1,3 +1,5 @@ +import { getMeteorConfig } from "../tool-env/meteor-config"; + var main = require('./main.js'); var _ = require('underscore'); var files = require('../fs/files'); @@ -261,51 +263,6 @@ export function parseRunTargets(targets) { }); }; -const DEFAULT_MODERN = { - transpiler: true, - webArchOnly: true, - watcher: true, -}; - -const normalizeModern = (r = false) => Object.fromEntries( - Object.entries(DEFAULT_MODERN).map(([k, def]) => [ - k, - r === true - ? def - : r === false || r?.[k] === false - ? false - : typeof r?.[k] === 'object' - ? { ...r[k] } - : def, - ]), -); - - -let modernForced = JSON.parse(process.env.METEOR_MODERN || "false"); -let meteorConfig; - -function getMeteorConfig(appDir) { - if (meteorConfig) return meteorConfig; - const packageJsonPath = files.pathJoin(appDir, 'package.json'); - if (!files.exists(packageJsonPath)) { - return false; - } - const packageJsonFile = files.readFile(packageJsonPath, 'utf8'); - const packageJson = JSON.parse(packageJsonFile); - meteorConfig = packageJson?.meteor; - return meteorConfig; -} - -function isModernArchsOnlyEnabled(appDir) { - const meteorConfig = getMeteorConfig(appDir); - return normalizeModern(modernForced || meteorConfig?.modern).webArchOnly !== false; -} - -export function isModernWatcherEnabled(appDir) { - const meteorConfig = getMeteorConfig(appDir); - return normalizeModern(modernForced || meteorConfig?.modern).watcher !== false; -} - function filterWebArchs(webArchs, excludeArchsOption, appDir, options) { const platforms = (options.platforms || []); const isBuildMode = platforms?.length > 0; @@ -325,7 +282,7 @@ function filterWebArchs(webArchs, excludeArchsOption, appDir, options) { if (!isCordovaDev) { const excludeArchsOptions = excludeArchsOption ? excludeArchsOption.trim().split(/\s*,\s*/) : []; const hasExcludeArchsOptions = (excludeArchsOptions?.length || 0) > 0; - const hasModernArchsOnlyEnabled = appDir && isModernArchsOnlyEnabled(appDir); + const hasModernArchsOnlyEnabled = appDir && getMeteorConfig()?.modern?.webArchOnly !== false; if (hasExcludeArchsOptions && hasModernArchsOnlyEnabled) { console.warn('modern.webArchOnly and --exclude-archs are both active. If both are set, --exclude-archs takes priority.'); } @@ -1246,71 +1203,85 @@ main.registerCommand({ toIgnore.push(/(\.html|\.js|\.css)/); } - try { - // Prototype option should use local skeleton. - // Maybe we should use a different skeleton for prototype - if (options.prototype) throw new Error("Using prototype option"); - // if using the release option we should use the default skeleton - // using it as it was before 2.x - if (release.explicit) throw new Error("Using release option"); + const copyFromLocalSkeleton = async () => { + await files.cp_r( + skeletonPath, + appPath, + { + transformFilename: function (f) { + return transform(f); + }, + transformContents: function (contents, f) { + // check if this app is just for prototyping if it is then we need to add autopublish and insecure in the packages file + if (/packages/.test(f)) { + const prototypePackages = () => + "autopublish # Publish all data to the clients (for prototyping)\n" + + "insecure # Allow all DB writes from clients (for prototyping)"; - await setupExampleByURL(`https://github.com/meteor/skel-${skeleton}`); - } catch (e) { + // XXX: if there is the need to add more options maybe we should have a better abstraction for this if-else + if (options.prototype) { + return Buffer.from( + contents.toString().replace(/~prototype~/g, prototypePackages()) + ); + } else { + return Buffer.from(contents.toString().replace(/~prototype~/g, "")); + } + } + if (/(\.html|\.[jt]sx?|\.css)/.test(f)) { + return Buffer.from(transform(contents.toString())); + } else { + return contents; + } + }, + ignore: toIgnore, + preserveSymlinks: true, + } + ); + }; - if ( - e.message !== "Using prototype option" && - e.message !== "Using release option" - ) { - // something has happened while creating the app using git clone - Console.error( - `Something has happened while creating your app using git clone. + // Check if the local skeleton path exists + const skeletonPath = files.pathJoin( + __dirnameConverted, + "..", + "static-assets", + `skel-${skeleton}` + ); + + const useLocalSkeleton = files.exists(skeletonPath) || + options.prototype || + release.explicit; + if (useLocalSkeleton) { + // Use local skeleton + await copyFromLocalSkeleton(); + } else { + try { + // Prototype option should use local skeleton. + // Maybe we should use a different skeleton for prototype + if (options.prototype) throw new Error("Using prototype option"); + // if using the release option we should use the default skeleton + // using it as it was before 2.x + if (release.explicit) throw new Error("Using release option"); + + // If local skeleton doesn't exist, use setupExampleByURL + await setupExampleByURL(`https://github.com/meteor/skel-${skeleton}`); + } catch (e) { + if ( + e.message !== "Using prototype option" && + e.message !== "Using release option" + ) { + // something has happened while creating the app using git clone + Console.error( + `Something has happened while creating your app using git clone. Will use cached version of skeletons. Error message: `, - e.message - ); + e.message + ); + } + // For prototype or release options, use local skeleton + await copyFromLocalSkeleton(); } - - // TODO: decide if this should stay here or not. - await files.cp_r( - files.pathJoin( - __dirnameConverted, - "..", - "static-assets", - `skel-${skeleton}` - ), - appPath, - { - transformFilename: function (f) { - return transform(f); - }, - transformContents: function (contents, f) { - // check if this app is just for prototyping if it is then we need to add autopublish and insecure in the packages file - if (/packages/.test(f)) { - const prototypePackages = () => - "autopublish # Publish all data to the clients (for prototyping)\n" + - "insecure # Allow all DB writes from clients (for prototyping)"; - - // XXX: if there is the need to add more options maybe we should have a better abstraction for this if-else - if (options.prototype) { - return Buffer.from( - contents.toString().replace(/~prototype~/g, prototypePackages()) - ); - } else { - return Buffer.from(contents.toString().replace(/~prototype~/g, "")); - } - } - if (/(\.html|\.[jt]sx?|\.css)/.test(f)) { - return Buffer.from(transform(contents.toString())); - } else { - return contents; - } - }, - ignore: toIgnore, - preserveSymlinks: true, - } - ); - await setupMessages(); } + await setupMessages(); Console.info(""); }); @@ -3436,20 +3407,58 @@ const setupBenchmarkSuite = async (profilingPath) => { if (await files.exists(profilingPath)) { return; } + + // Check git availability and version const [okGitVersion, errGitVersion] = await bash`git --version`; if (errGitVersion) throw new Error("git is not installed"); - const parsedGitVersion = semver.coerce(okGitVersion.match(/\d+\.\d+\.\d+/)[0] || '')?.version; - const checkInvalidGitVersion = parsedGitVersion == null || semver.lt(parsedGitVersion, '2.25.0'); - if (checkInvalidGitVersion) { + const parsedGitVersion = semver.coerce(okGitVersion.match(/\d+\.\d+\.\d+/)?.[0] || '')?.version; + if (!parsedGitVersion || semver.lt(parsedGitVersion, '2.25.0')) { throw new Error("git version is too old. Please upgrade to at least 2.25"); } - // Set GIT_TERMINAL_PROMPT=0 to disable prompting + // Check tar availability + const [okTar, errTar] = await bash`tar --version`; + const hasTar = !errTar; + + // Disable interactive git prompts process.env.GIT_TERMINAL_PROMPT = 0; const repoUrl = "https://github.com/meteor/performance"; const branch = "v3.3.0"; + + let tarFailed = false; + + // If tar is available, prefer tar-based extraction + if (hasTar) { + const tempDir = "/tmp/meteor-performance-benchmark-suite"; + const tarCommand = [ + `rm -rf ${tempDir}`, + `git clone --no-checkout --depth 1 --filter=tree:0 --sparse --progress --branch ${branch} --single-branch ${repoUrl} ${tempDir}`, + `cd ${tempDir}`, + `git sparse-checkout init --cone`, + `git sparse-checkout set scripts`, + `git checkout ${branch}`, + `mkdir -p ${profilingPath}/scripts`, + `tar -czf /tmp/scripts.tar.gz -C ./scripts .`, + `tar -xzf /tmp/scripts.tar.gz -C ${profilingPath}/scripts`, + `rm -rf ${tempDir}`, + `rm -f /tmp/scripts.tar.gz` + ].join(" && "); + + const [okTarClone, errTarClone] = await bash`${tarCommand}`; + if (!errTarClone) { + Console.info("Meteor profiling suite cloned to: " + Console.path(profilingPath)); + return; + } else { + Console.warn("Tar-based cloning failed. Will attempt standard git clone..."); + tarFailed = errTarClone; + } + } else { + Console.warn("Tar not available. Will use standard git clone..."); + } + + // Fallback to plain git clone const gitCommand = [ `mkdir -p ${profilingPath}`, `git clone --no-checkout --depth 1 --filter=tree:0 --sparse --progress --branch ${branch} --single-branch ${repoUrl} ${profilingPath}`, @@ -3457,18 +3466,22 @@ const setupBenchmarkSuite = async (profilingPath) => { `git sparse-checkout init --cone`, `git sparse-checkout set scripts`, `git checkout ${branch}`, - `find ${profilingPath} -maxdepth 1 -type f -delete`, + `find ${profilingPath} -maxdepth 1 -type f -delete` ].join(" && "); - const [, errClone] = await bash`${gitCommand}`; - const errorMessage = errClone && typeof errClone === "string" ? errClone : errClone?.message; - if (errorMessage && errorMessage.includes("Cloning into")) { - throw new Error("error cloning benchmark"); + + const [okClone, errClone] = await bash`${gitCommand}`; + if (errClone) { + let combinedMessage = "Git clone failed."; + if (tarFailed) { + combinedMessage = `Tar-based cloning also failed:\n${tarFailed}\n\nGit fallback failed:\n${errClone}`; + } + throw new Error(combinedMessage); } - // remove .git folder from the example + + // Remove .git folder if present await files.rm_recursive_async(files.pathJoin(profilingPath, ".git")); - Console.info( - "Meteor profiling suite cloned to: " + Console.path(profilingPath), - ); + + Console.info("Meteor profiling suite cloned to: " + Console.path(profilingPath)); }; async function doBenchmarkCommand(options) { diff --git a/tools/cli/help.txt b/tools/cli/help.txt index d0c16928be..7f9e083861 100644 --- a/tools/cli/help.txt +++ b/tools/cli/help.txt @@ -492,7 +492,7 @@ Note that you must have mongosh installed to use this option. Options: --url, -U return a Mongo database URL --verbose, -v to show the errors that have occurred while connecting to the - database + database Currently, this feature can only be used when developing locally. The opened Mongo shell connects to the current project's local @@ -816,7 +816,16 @@ Usage: meteor admin [args] Rarely used commands for administering official Meteor services. Commands: -{{commands}} + + make-bootstrap-tarballs + recommend-release + change-homepage + set-unmigrated + set-banners + list-organizations + members + set-latest-readme + get-machine See 'meteor help admin ' for details on an admin command. @@ -875,12 +884,12 @@ for replacing the names, we offer $$PascalName$$, $$camelName$$, $$name$$. This is a MeteorJS project command. Options: - --help Show help. - --path The path to the folder where the files will be generated. Default is the current folder. - --templatePath Path to the template file check https://docs.meteor.com/commandline.html#meteorgenerate-templating for more info. - --replaceFn Replace function to replace the names in the template. Check https://docs.meteor.com/commandline.html#meteorgenerate-templating for more info. - --methods Generate methods. - --publications Generate publications. + --help Show help. + --path The path to the folder where the files will be generated. Default is the current folder. + --templatePath Path to the template file check https://docs.meteor.com/commandline.html#meteorgenerate-templating for more info. + --replaceFn Replace function to replace the names in the template. Check https://docs.meteor.com/commandline.html#meteorgenerate-templating for more info. + --methods Generate methods. + --publications Generate publications. >>> publish-release diff --git a/tools/cli/main.js b/tools/cli/main.js index 6888765ad9..b73e2b7868 100644 --- a/tools/cli/main.js +++ b/tools/cli/main.js @@ -287,12 +287,16 @@ main.captureAndExit = async function (header, title, f) { // NB: files required up to this point may not define commands -const { isModernWatcherEnabled } = require('./commands.js'); +const { initMeteorConfig } = require('../tool-env/meteor-config'); +require('./commands.js'); require('./commands-packages.js'); require('./commands-packages-query.js'); require('./commands-cordova.js'); require('./commands-aliases.js'); +// Initialize meteorConfig globally +initMeteorConfig(); + /////////////////////////////////////////////////////////////////////////////// // Record all the top-level commands as JSON /////////////////////////////////////////////////////////////////////////////// @@ -865,7 +869,9 @@ makeGlobalAsyncLocalStorage().run({}, async function () { var appDir = files.findAppDir(); if (appDir) { appDir = files.pathResolve(appDir); - global.modernWatcher = isModernWatcherEnabled(appDir); + + // Renitialize meteorConfig globally when having appDir context + initMeteorConfig(appDir); } await require('../tool-env/isopackets.js').ensureIsopacketsLoadable(); diff --git a/tools/cordova/index.js b/tools/cordova/index.js index 1230faba92..e1acabacb6 100644 --- a/tools/cordova/index.js +++ b/tools/cordova/index.js @@ -13,11 +13,11 @@ export const CORDOVA_ARCH = "web.cordova"; export const CORDOVA_PLATFORMS = ['ios', 'android']; -const CORDOVA_ANDROID_VERSION = "13.0.0"; +const CORDOVA_ANDROID_VERSION = "14.0.1"; export const CORDOVA_DEV_BUNDLE_VERSIONS = { - 'cordova-lib': '12.0.1', - 'cordova-common': '5.0.0', + 'cordova-lib': '12.0.2', + 'cordova-common': '5.0.1', 'cordova-create': '2.0.0', 'cordova-registry-mapper': '1.1.15', 'cordova-android': CORDOVA_ANDROID_VERSION, diff --git a/tools/fs/safe-watcher.ts b/tools/fs/safe-watcher.ts index 492bd03028..07fe605fb7 100644 --- a/tools/fs/safe-watcher.ts +++ b/tools/fs/safe-watcher.ts @@ -4,6 +4,7 @@ import { watch as watchLegacy, addWatchRoot as addWatchRootLegacy, closeAllWatch import { Profile } from "../tool-env/profile"; import { statOrNull, lstat, toPosixPath, convertToOSPath, pathRelative, watchFile, unwatchFile, pathResolve, pathDirname } from "./files"; +import { getMeteorConfig } from "../tool-env/meteor-config"; // Register process exit handlers to ensure subscriptions are properly cleaned up const registerExitHandlers = () => { @@ -380,7 +381,7 @@ function startNewEntry(absPath: string): Entry { */ export function watch (absPath: string, callback: ChangeCallback): SafeWatcher { // @ts-ignore - if (!global.modernWatcher) { + if (!getMeteorConfig()?.modern?.watcher) { // @ts-ignore return watchLegacy(absPath, callback); } @@ -444,7 +445,7 @@ const watchModern = */ export function addWatchRoot(absPath: string) { // @ts-ignore - if (!global.modernWatcher) { + if (!getMeteorConfig()?.modern?.watcher) { // @ts-ignore return addWatchRootLegacy(absPath); } @@ -477,7 +478,7 @@ async function safeUnsubscribeSub(root: string) { export async function closeAllWatchers() { // @ts-ignore - if (!global.modernWatcher) { + if (!getMeteorConfig()?.modern?.watcher) { // @ts-ignore return closeAllWatchersLegacy(); } diff --git a/tools/isobuild/import-scanner.ts b/tools/isobuild/import-scanner.ts index e05900b2f2..7aff1ee0e6 100644 --- a/tools/isobuild/import-scanner.ts +++ b/tools/isobuild/import-scanner.ts @@ -46,6 +46,7 @@ import { import { wrap } from "optimism"; const { compile: reifyCompile } = require("@meteorjs/reify/lib/compiler"); const { parse: reifyAcornParse } = require("@meteorjs/reify/lib/parsers/acorn"); +const { parse: reifyBabelParse } = require("@meteorjs/reify/lib/parsers/babel"); import Resolver, { Resolution } from "./resolver"; import LRUCache from 'lru-cache'; @@ -87,14 +88,32 @@ const reifyCompileWithCache = Profile("reifyCompileWithCache", wrap(function ( } const isLegacy = isLegacyArch(bundleArch); - let result = reifyCompile(stripHashBang(source), { - parse: reifyAcornParse, + const reifyOptions = { generateLetDeclarations: !isLegacy, avoidModernSyntax: isLegacy, enforceStrictMode: false, dynamicImport: true, ast: false, - }).code; + }; + + let result; + try { + // First attempt: use Acorn + result = reifyCompile(stripHashBang(source), { + ...reifyOptions, + parse: reifyAcornParse, + }).code; + } catch (acornError) { + // Fallback: use Babel parser + // acorn may throw SyntaxError due to the lack of support for + // some features, but babel should still be able to parse the file + // For example, acorn don’t support JSX, only with acorn-jsx, + // but it isn’t included in Reify. + result = reifyCompile(stripHashBang(source), { + ...reifyOptions, + parse: reifyBabelParse, + }).code; + } if (cacheFilePath) { Promise.resolve().then( diff --git a/tools/isobuild/isopack.js b/tools/isobuild/isopack.js index 99549c5d25..aa397e0a80 100644 --- a/tools/isobuild/isopack.js +++ b/tools/isobuild/isopack.js @@ -1,3 +1,5 @@ +import { getMeteorConfig } from "../tool-env/meteor-config"; + var compiler = require('./compiler.js'); var archinfo = require('../utils/archinfo'); var _ = require('underscore'); @@ -514,6 +516,9 @@ Object.assign(Isopack.prototype, { var Plugin = { name: pluginName, + // Share the meteorConfig object as part of plugin API + getMeteorConfig: getMeteorConfig, + // 'extension' is a file extension without the separation dot // (eg 'js', 'coffee', 'coffee.md') // diff --git a/tools/project-context.js b/tools/project-context.js index c14dce5cfa..f114f3a38a 100644 --- a/tools/project-context.js +++ b/tools/project-context.js @@ -1,3 +1,4 @@ +import { normalizeModernConfig, setMeteorConfig } from "./tool-env/meteor-config"; var assert = require("assert"); var _ = require('underscore'); @@ -492,6 +493,8 @@ Object.assign(ProjectContext.prototype, { self.meteorConfig = new MeteorConfig({ appDirectory: self.projectDir, }); + self.meteorConfig._ensureInitialized(); + if (buildmessage.jobHasMessages()) { return; } @@ -1217,7 +1220,16 @@ Object.assign(exports.ProjectConstraintsFile.prototype, { constraint: constraintToAdd, trailingSpaceAndComment: '' }; - self._constraintLines.push(lineRecord); + if (constraintToAdd.package === 'npm-mongo-legacy') { + const mongoIdx = self._constraintLines.findIndex(lr => lr.constraint && lr.constraint.package === 'mongo'); + if (mongoIdx > -1) { + self._constraintLines.splice(mongoIdx, 0, lineRecord); + } else { + self._constraintLines.push(lineRecord); + } + } else { + self._constraintLines.push(lineRecord); + } self._constraintMap[constraintToAdd.package] = lineRecord; self._modified = true; return; @@ -1811,6 +1823,13 @@ export class MeteorConfig { }, }), } : this._config; + const modernForced = JSON.parse(process.env.METEOR_MODERN || "false"); + // Reinitialize meteorConfig globally for project context + // Updates config when package.json changes trigger rebuilds + setMeteorConfig({ + ...(this._config || {}), + modern: normalizeModernConfig(modernForced || this._config?.modern || false), + }); return this._config; } @@ -1818,18 +1837,14 @@ export class MeteorConfig { // General utility for querying the "meteor" section of package.json. // TODO Implement an API for setting these values? get(...keys) { - let config = this._ensureInitialized(); - let filteredConfig = keys.length ? {} : config; - if (config) { - keys.every(key => { - if (config && _.has(config, key)) { - filteredConfig = config[key]; - return true; - } - return false; - }); - return filteredConfig; - } + const config = this._ensureInitialized(); + if (!config) return undefined; + + return keys.reduce((cur, key) => { + return (cur != null && _.has(cur, key)) + ? cur[key] + : undefined; + }, config); } getNodeModulesToRecompileByArch() { diff --git a/tools/static-assets/server/runtime.js b/tools/static-assets/server/runtime.js index 9a1c6b15ef..3c15a6f9d1 100644 --- a/tools/static-assets/server/runtime.js +++ b/tools/static-assets/server/runtime.js @@ -48,13 +48,15 @@ module.exports = function enable ({ cachePath, createLoader = true } = {}) { const reifyVersion = require("@meteorjs/reify/package.json").version; const reifyAcornParse = require("@meteorjs/reify/lib/parsers/acorn").parse; + const reifyBabelParse = require("@meteorjs/reify/lib/parsers/babel").parse; const reifyCompile = require("@meteorjs/reify/lib/compiler").compile; function compileContent (content) { let identical = true; + let result; try { - const result = reifyCompile(content, { + result = reifyCompile(content, { parse: reifyAcornParse, generateLetDeclarations: false, ast: false, @@ -63,9 +65,20 @@ module.exports = function enable ({ cachePath, createLoader = true } = {}) { identical = false; content = result.code; } - } finally { - return { content, identical }; + } catch (acornError) { + // Fallback: Babel + result = reifyCompile(content, { + parse: reifyBabelParse, + generateLetDeclarations: false, + ast: false, + }); + if (!result.identical) { + identical = false; + content = result.code; + } } + + return { content, identical }; } const _compile = Mp._compile; diff --git a/tools/static-assets/skel-apollo/package.json b/tools/static-assets/skel-apollo/package.json index 211e19310a..8af983a7cb 100644 --- a/tools/static-assets/skel-apollo/package.json +++ b/tools/static-assets/skel-apollo/package.json @@ -11,6 +11,7 @@ "@apollo/client": "^3.9.2", "@apollo/server": "^4.10.0", "@babel/runtime": "^7.23.9", + "@swc/helpers": "^0.5.17", "graphql": "^16.8.1", "meteor-node-stubs": "^1.2.12", "react": "^18.2.0", diff --git a/tools/static-assets/skel-blaze/package.json b/tools/static-assets/skel-blaze/package.json index 3845c0046d..e197964537 100644 --- a/tools/static-assets/skel-blaze/package.json +++ b/tools/static-assets/skel-blaze/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "@babel/runtime": "^7.23.5", + "@swc/helpers": "^0.5.17", "jquery": "^3.7.1", "meteor-node-stubs": "^1.2.12" }, diff --git a/tools/static-assets/skel-chakra-ui/package.json b/tools/static-assets/skel-chakra-ui/package.json index fdcb9c2717..99b3e13d76 100644 --- a/tools/static-assets/skel-chakra-ui/package.json +++ b/tools/static-assets/skel-chakra-ui/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "@babel/runtime": "^7.23.5", + "@swc/helpers": "^0.5.17", "@chakra-ui/icons": "^1.1.7", "@chakra-ui/react": "^1.8.8", "@emotion/react": "^11.9.3", diff --git a/tools/static-assets/skel-full/package.json b/tools/static-assets/skel-full/package.json index 953198e35f..1abdbdd683 100644 --- a/tools/static-assets/skel-full/package.json +++ b/tools/static-assets/skel-full/package.json @@ -7,10 +7,18 @@ }, "dependencies": { "@babel/runtime": "^7.23.5", + "@swc/helpers": "^0.5.17", "jquery": "^3.7.1", "meteor-node-stubs": "^1.2.12" }, "devDependencies": { "chai": "^4.2.0" + }, + "meteor": { + "mainModule": { + "client": "client/main.js", + "server": "server/main.js" + }, + "modern": true } } diff --git a/tools/static-assets/skel-minimal/package.json b/tools/static-assets/skel-minimal/package.json index 903c3e9bb7..74d5a9b271 100644 --- a/tools/static-assets/skel-minimal/package.json +++ b/tools/static-assets/skel-minimal/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "@babel/runtime": "^7.23.5", + "@swc/helpers": "^0.5.17", "meteor-node-stubs": "^1.2.12" }, "meteor": { diff --git a/tools/static-assets/skel-react/package.json b/tools/static-assets/skel-react/package.json index b49bf5903e..ee33ff775c 100644 --- a/tools/static-assets/skel-react/package.json +++ b/tools/static-assets/skel-react/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "@babel/runtime": "^7.23.5", + "@swc/helpers": "^0.5.17", "meteor-node-stubs": "^1.2.12", "react": "^18.2.0", "react-dom": "^18.2.0" diff --git a/tools/static-assets/skel-solid/client/entry-meteor.js b/tools/static-assets/skel-solid/client/entry-meteor.js index e69de29bb2..0ec3ddc121 100644 --- a/tools/static-assets/skel-solid/client/entry-meteor.js +++ b/tools/static-assets/skel-solid/client/entry-meteor.js @@ -0,0 +1,14 @@ +/** + * Entrypoint for the Meteor client + * + * Generally, this file can be left empty. Vite will add imports for + * lazy-loaded Meteor packages to this file to ensure they aren't omitted from + * the final production bundle. + * + * Use ./main.js as the primary entrypoint for your client code to take full + * advantage of Vite's plugin and build system. + * + * This can also be a good place to put code that you don't want Vite to + * process, for example, if you run into a compatibility issue or need to use + * nested imports which Vite doesn't support. + */ \ No newline at end of file diff --git a/tools/static-assets/skel-solid/client/main.js b/tools/static-assets/skel-solid/client/main.js new file mode 100644 index 0000000000..44bcc64d55 --- /dev/null +++ b/tools/static-assets/skel-solid/client/main.js @@ -0,0 +1 @@ +import '../imports/ui/main'; \ No newline at end of file diff --git a/tools/static-assets/skel-solid/client/main.jsx b/tools/static-assets/skel-solid/client/main.jsx deleted file mode 100644 index 97d382a9bd..0000000000 --- a/tools/static-assets/skel-solid/client/main.jsx +++ /dev/null @@ -1 +0,0 @@ -// main entry point is in imports/ui/main.jsx diff --git a/tools/static-assets/skel-solid/client/main.css b/tools/static-assets/skel-solid/imports/ui/main.css similarity index 100% rename from tools/static-assets/skel-solid/client/main.css rename to tools/static-assets/skel-solid/imports/ui/main.css diff --git a/tools/static-assets/skel-solid/imports/ui/main.jsx b/tools/static-assets/skel-solid/imports/ui/main.jsx index 99eb6c43d7..044f1aee69 100644 --- a/tools/static-assets/skel-solid/imports/ui/main.jsx +++ b/tools/static-assets/skel-solid/imports/ui/main.jsx @@ -2,6 +2,7 @@ import { render } from 'solid-js/web'; import { App } from './App'; import { Meteor } from "meteor/meteor"; +import './main.css'; Meteor.startup(() => { render(() => , document.getElementById('root')); diff --git a/tools/static-assets/skel-solid/package.json b/tools/static-assets/skel-solid/package.json index bb50ecbd9b..17401d9c1b 100644 --- a/tools/static-assets/skel-solid/package.json +++ b/tools/static-assets/skel-solid/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "@babel/runtime": "^7.23.9", + "@swc/helpers": "^0.5.17", "meteor-node-stubs": "^1.2.12", "picocolors": "^1.1.1", "solid-js": "^1.9.4" diff --git a/tools/static-assets/skel-solid/server/entry-meteor.js b/tools/static-assets/skel-solid/server/entry-meteor.js index e69de29bb2..8a066f8e94 100644 --- a/tools/static-assets/skel-solid/server/entry-meteor.js +++ b/tools/static-assets/skel-solid/server/entry-meteor.js @@ -0,0 +1,12 @@ +/** + * Entrypoint for the Meteor server + * Generally, this file can be left empty. Vite will add imports for your app's + * server bundle here during both development and production build. + * + * Use ./main.js as the primary entrypoint for your app to take full advantage + * of Vite's plugin and build system. + * + * This can also be a good place to put code that you don't want Vite to + * process, for example, if you run into a compatibility issue or need to use + * nested imports. + */ \ No newline at end of file diff --git a/tools/static-assets/skel-solid/vite.config.js b/tools/static-assets/skel-solid/vite.config.mjs similarity index 91% rename from tools/static-assets/skel-solid/vite.config.js rename to tools/static-assets/skel-solid/vite.config.mjs index 5752984348..e0215dbb10 100644 --- a/tools/static-assets/skel-solid/vite.config.js +++ b/tools/static-assets/skel-solid/vite.config.mjs @@ -8,7 +8,7 @@ export default defineConfig({ solidPlugin(), solidSvg({ defaultExport: 'component' }), meteor({ - clientEntry: 'imports/ui/main.jsx', + clientEntry: 'client/main.js', serverEntry: 'server/main.js', enableExperimentalFeatures: true, stubValidation: { diff --git a/tools/static-assets/skel-svelte/package.json b/tools/static-assets/skel-svelte/package.json index c1d703791d..0929124049 100644 --- a/tools/static-assets/skel-svelte/package.json +++ b/tools/static-assets/skel-svelte/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "@babel/runtime": "^7.23.5", + "@swc/helpers": "^0.5.17", "meteor-node-stubs": "^1.2.12", "svelte": "^3.59.2" }, diff --git a/tools/static-assets/skel-tailwind/package.json b/tools/static-assets/skel-tailwind/package.json index e4b556a821..73971202ba 100644 --- a/tools/static-assets/skel-tailwind/package.json +++ b/tools/static-assets/skel-tailwind/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "@babel/runtime": "^7.23.5", + "@swc/helpers": "^0.5.17", "autoprefixer": "^10.4.4", "meteor-node-stubs": "^1.2.12", "postcss": "^8.4.12", diff --git a/tools/static-assets/skel-typescript/package.json b/tools/static-assets/skel-typescript/package.json index 90c078b082..77ff7934bf 100644 --- a/tools/static-assets/skel-typescript/package.json +++ b/tools/static-assets/skel-typescript/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "@babel/runtime": "^7.23.5", + "@swc/helpers": "^0.5.17", "meteor-node-stubs": "^1.2.12", "react": "^18.2.0", "react-dom": "^18.2.0" diff --git a/tools/static-assets/skel-vue/client/entry-meteor.js b/tools/static-assets/skel-vue/client/entry-meteor.js index e69de29bb2..0ec3ddc121 100644 --- a/tools/static-assets/skel-vue/client/entry-meteor.js +++ b/tools/static-assets/skel-vue/client/entry-meteor.js @@ -0,0 +1,14 @@ +/** + * Entrypoint for the Meteor client + * + * Generally, this file can be left empty. Vite will add imports for + * lazy-loaded Meteor packages to this file to ensure they aren't omitted from + * the final production bundle. + * + * Use ./main.js as the primary entrypoint for your client code to take full + * advantage of Vite's plugin and build system. + * + * This can also be a good place to put code that you don't want Vite to + * process, for example, if you run into a compatibility issue or need to use + * nested imports which Vite doesn't support. + */ \ No newline at end of file diff --git a/tools/static-assets/skel-vue/client/main.css b/tools/static-assets/skel-vue/client/main.css deleted file mode 100644 index b5c61c9567..0000000000 --- a/tools/static-assets/skel-vue/client/main.css +++ /dev/null @@ -1,3 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; diff --git a/tools/static-assets/skel-vue/client/main.js b/tools/static-assets/skel-vue/client/main.js index 97d382a9bd..403d8a2d2a 100644 --- a/tools/static-assets/skel-vue/client/main.js +++ b/tools/static-assets/skel-vue/client/main.js @@ -1 +1 @@ -// main entry point is in imports/ui/main.jsx +import '../imports/ui/main' \ No newline at end of file diff --git a/tools/static-assets/skel-vue/imports/ui/App.vue b/tools/static-assets/skel-vue/imports/ui/App.vue index 7a775391cb..19a68a1ea1 100644 --- a/tools/static-assets/skel-vue/imports/ui/App.vue +++ b/tools/static-assets/skel-vue/imports/ui/App.vue @@ -1,5 +1,5 @@