Merge branch 'devel' into ci/removing-travis

This commit is contained in:
Gabriel Grubba
2026-04-08 21:24:18 -03:00
committed by GitHub
22 changed files with 1753 additions and 4149 deletions

View File

@@ -1,93 +0,0 @@
android_bundle/
dev_bundle/
docs/
examples/
scripts/
!tools/*.js
!tools/isobuild/*.js
!tools/catalog/*.js
!tools/packaging/*.js
!tools/cli/*.js
!tools/runners/*.js
!tools/tool-env/*.js
!tools/fs/*.js
# Below, files that have yet to be converted to match the linter
tools/archinfo.js
tools/auth-client.js
tools/auth.js
tools/buildmessage.js
tools/cleanup.js
tools/colon-converter.js
tools/config.js
tools/console.js
tools/deploy.js
tools/fiber-helpers.js
tools/fs/files.js
tools/fs/mini-files.js
tools/http-helpers.js
tools/inspector.js
tools/index.js
tools/mongo-exit-codes.ts
tools/processes.ts
tools/progress.ts
tools/project-context.js
tools/runners/run-log.js
tools/fs/safe-pathwatcher.js
tools/selftest.js
tools/service-connection.js
tools/shell-client.ts
tools/stats.js
tools/test-utils.js
tools/upgraders.js
tools/utils/utils.js
tools/fs/watch.js
tools/catalog/catalog-local.js
tools/catalog/catalog-remote.js
tools/catalog/catalog.js
tools/catalog/catalog-utils.js
tools/cli/commands-cordova.js
tools/cli/commands-packages-query.js
tools/cli/commands-packages.js
tools/cli/commands.js
tools/cli/main.js
tools/tool-env/flush-buffers-on-exit-in-windows.js
tools/tool-env/install-babel.js
tools/tool-env/isopackets.js
tools/tool-env/profile-require.js
tools/tool-env/profile.js
tools/runners/run-all.js
tools/runners/run-app.js
tools/runners/run-mongo.js
tools/runners/run-proxy.js
tools/runners/run-selenium.js
tools/packaging/package-client.js
tools/packaging/package-map.js
tools/packaging/package-version-parser.js
tools/packaging/release.js
tools/packaging/tropohouse.js
tools/packaging/updater.js
tools/packaging/warehouse.js
tools/isobuild/build-plugin.js
tools/isobuild/builder.js
tools/isobuild/bundler.js
tools/isobuild/compiler-deprecated-compile-step.js
tools/isobuild/compiler-plugin.js
tools/isobuild/compiler.js
tools/isobuild/import-scanner.js
tools/isobuild/isopack-cache.js
tools/isobuild/isopack.js
tools/isobuild/js-analyze.js
tools/isobuild/linker.js
tools/isobuild/linter-plugin.js
tools/isobuild/meteor-npm.js
tools/isobuild/npm-discards.ts
tools/isobuild/package-api.js
tools/isobuild/package-source.js
tools/isobuild/source-arch.js

33
.fmtignore Normal file
View File

@@ -0,0 +1,33 @@
# Ignore everything by default
# oxfmt uses .prettierignore automatically
# Root and top-level directories
*.yml
*.md
*.json
tsconfig.json
.travis.yml
android_bundle/
dev_bundle/
docs/
examples/
guide/
scripts/
tools/
npm-packages/
v3-docs/
node_modules/
.meteor/
.circleci/
.github/
.coderabbit.yaml
# Ignore node_modules/npm inside packages
packages/**/node_modules/
packages/**/.npm/
# All packages ignored by default
# Only those listed below are formatted
packages/*
!packages/facts-base/

5
.git-blame-ignore-revs Normal file
View File

@@ -0,0 +1,5 @@
# This file lists commits that should be ignored by git blame.
# Configure with: git config blame.ignoreRevsFile .git-blame-ignore-revs
# auto-format and lint fixes 2026-03-27
1bfad0bcbb9c495172d1475f1c6d54df8d34ceae

View File

@@ -2,18 +2,22 @@ name: Check code-style
on:
push:
paths:
- 'packages/**'
- 'npm-packages/meteor-installer/**'
pull_request:
paths:
- 'packages/**'
- 'npm-packages/meteor-installer/**'
jobs:
check-code-style:
runs-on: ubuntu-latest
runs-on: oss-vm
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22.x
- run: npm ci
- name: Run ESLint@8
run: npx eslint@8 "./npm-packages/meteor-installer/**/*.js"
- name: Check formatting with oxfmt
run: npx oxfmt --ignore-path .fmtignore --check .
- name: Run lint with oxlint
run: npm run lint

View File

@@ -1,3 +0,0 @@
{
"esversion": 11
}

11
.oxfmtrc.json Normal file
View File

@@ -0,0 +1,11 @@
{
"semi": true,
"singleQuote": false,
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"trailingComma": "all",
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf"
}

30
.oxlintignore Normal file
View File

@@ -0,0 +1,30 @@
# Ignore everything by default
# Root and top-level directories
*.yml
*.md
*.json
android_bundle/
dev_bundle/
docs/
examples/
guide/
scripts/
tools/
npm-packages/
v3-docs/
node_modules/
.meteor/
.circleci/
.github/
.coderabbit.yaml
# Ignore node_modules/npm inside packages
packages/**/node_modules/
packages/**/.npm/
# All packages ignored by default
# Only those listed below are linted
packages/*
!packages/facts-base/

9
.oxlintrc.json Normal file
View File

@@ -0,0 +1,9 @@
{
"$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
"rules": {
"no-var": "error",
"no-unused-vars": "error",
"prefer-const": "error",
"prefer-template": "error"
}
}

6
lefthook.yml Normal file
View File

@@ -0,0 +1,6 @@
pre-push:
commands:
fmt-check:
run: npm run fmt:check
lint:
run: npm run lint

4839
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,28 +13,21 @@
"homepage": "https://www.meteor.com/",
"devDependencies": {
"@babel/core": "^7.21.3",
"@babel/eslint-parser": "^7.21.3",
"@babel/eslint-plugin": "^7.19.1",
"@babel/preset-react": "^7.18.6",
"@types/lodash.isempty": "^4.4.9",
"@types/node": "^18.16.18",
"@types/sockjs": "^0.3.36",
"@types/sockjs-client": "^1.5.4",
"@typescript-eslint/eslint-plugin": "^5.56.0",
"@typescript-eslint/parser": "^5.56.0",
"eslint": "^8.36.0",
"eslint-config-prettier": "^8.8.0",
"eslint-config-vazco": "^7.1.0",
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"prettier": "^2.8.8",
"lefthook": "^2.1.4",
"oxfmt": "^0.42.0",
"oxlint": "^1.57.0",
"typescript": "^5.4.5"
},
"scripts": {
"fmt": "oxfmt --ignore-path .fmtignore .",
"fmt:check": "oxfmt --ignore-path .fmtignore --check .",
"lint": "oxlint --ignore-path .oxlintignore .",
"lint:fix": "oxlint --fix --ignore-path .oxlintignore .",
"install:unit": "cd tools/unit-tests && npm install",
"test:unit": "cd tools/unit-tests && npm test",
"test:idle-bot": "node --test .github/scripts/__tests__/inactive-issues.test.js",

View File

@@ -1,12 +1,173 @@
Tinytest.add('facts-base - increments server facts', test => {
Facts.resetServerFacts()
function mockSub() {
const calls = { added: [], changed: [] };
return {
added(collection, id, fields) {
calls.added.push({ collection, id, fields });
},
changed(collection, id, fields) {
calls.changed.push({ collection, id, fields });
},
calls,
};
}
Facts.incrementServerFact('newPackage', 'skyIsBlue', 42);
test.equal(Facts._factsByPackage.newPackage, { skyIsBlue: 42 });
// -- resetServerFacts --
Facts.incrementServerFact('newPackage', 'skyIsBlue', 21);
test.equal(Facts._factsByPackage.newPackage, { skyIsBlue: 63 });
Tinytest.add("facts-base - resetServerFacts clears all facts", (test) => {
Facts.resetServerFacts();
Facts.incrementServerFact("pkg-a", "fact1", 10);
Facts.incrementServerFact("pkg-b", "fact2", 20);
Facts.incrementServerFact('newPackage', 'newFact', 7);
test.equal(Facts._factsByPackage.newPackage, { skyIsBlue: 63, newFact: 7 });
Facts.resetServerFacts();
test.equal(Facts._factsByPackage, {});
});
Tinytest.add("facts-base - resetServerFacts on already empty state is a no-op", (test) => {
Facts.resetServerFacts();
Facts.resetServerFacts();
test.equal(Facts._factsByPackage, {});
});
// -- incrementServerFact: new package --
Tinytest.add("facts-base - incrementServerFact creates entry for new package", (test) => {
Facts.resetServerFacts();
Facts.incrementServerFact("new-pkg", "connections", 5);
test.equal(Facts._factsByPackage["new-pkg"], { connections: 5 });
});
// -- incrementServerFact: existing package, new fact --
Tinytest.add("facts-base - incrementServerFact adds new fact to existing package", (test) => {
Facts.resetServerFacts();
Facts.incrementServerFact("my-pkg", "factA", 1);
Facts.incrementServerFact("my-pkg", "factB", 7);
test.equal(Facts._factsByPackage["my-pkg"], { factA: 1, factB: 7 });
});
// -- incrementServerFact: existing package, existing fact --
Tinytest.add("facts-base - incrementServerFact accumulates on existing fact", (test) => {
Facts.resetServerFacts();
Facts.incrementServerFact("pkg", "counter", 10);
Facts.incrementServerFact("pkg", "counter", 3);
test.equal(Facts._factsByPackage["pkg"].counter, 13);
});
// -- incrementServerFact: negative increment --
Tinytest.add("facts-base - incrementServerFact handles negative increment", (test) => {
Facts.resetServerFacts();
Facts.incrementServerFact("pkg", "counter", 10);
Facts.incrementServerFact("pkg", "counter", -4);
test.equal(Facts._factsByPackage["pkg"].counter, 6);
});
// -- incrementServerFact: multiple independent packages --
Tinytest.add("facts-base - incrementServerFact keeps packages independent", (test) => {
Facts.resetServerFacts();
Facts.incrementServerFact("alpha", "x", 1);
Facts.incrementServerFact("beta", "x", 100);
Facts.incrementServerFact("alpha", "x", 2);
test.equal(Facts._factsByPackage["alpha"].x, 3);
test.equal(Facts._factsByPackage["beta"].x, 100);
});
// -- subscription notifications: sub.added on new package --
Tinytest.add("facts-base - notifies subscriptions with added when package is new", (test) => {
Facts.resetServerFacts();
const sub = mockSub();
Facts._setActiveSubscriptions([sub]);
Facts.incrementServerFact("fresh-pkg", "sessions", 42);
test.equal(sub.calls.added.length, 1);
test.equal(sub.calls.added[0].collection, "meteor_Facts_server");
test.equal(sub.calls.added[0].id, "fresh-pkg");
test.equal(sub.calls.added[0].fields, { sessions: 42 });
test.equal(sub.calls.changed.length, 0);
Facts._setActiveSubscriptions([]);
});
// -- subscription notifications: sub.changed on existing package --
Tinytest.add("facts-base - notifies subscriptions with changed when fact is updated", (test) => {
Facts.resetServerFacts();
const sub = mockSub();
Facts.incrementServerFact("pkg", "rps", 10);
Facts._setActiveSubscriptions([sub]);
Facts.incrementServerFact("pkg", "rps", 5);
test.equal(sub.calls.changed.length, 1);
test.equal(sub.calls.changed[0].collection, "meteor_Facts_server");
test.equal(sub.calls.changed[0].id, "pkg");
test.equal(sub.calls.changed[0].fields, { rps: 15 });
test.equal(sub.calls.added.length, 0);
Facts._setActiveSubscriptions([]);
});
// -- subscription notifications: multiple subscriptions --
Tinytest.add("facts-base - notifies all active subscriptions on increment", (test) => {
Facts.resetServerFacts();
const sub1 = mockSub();
const sub2 = mockSub();
Facts._setActiveSubscriptions([sub1, sub2]);
Facts.incrementServerFact("pkg", "hits", 1);
test.equal(sub1.calls.added.length, 1);
test.equal(sub2.calls.added.length, 1);
Facts.incrementServerFact("pkg", "hits", 1);
test.equal(sub1.calls.changed.length, 1);
test.equal(sub2.calls.changed.length, 1);
Facts._setActiveSubscriptions([]);
});
// -- subscription notifications: no subscriptions --
Tinytest.add("facts-base - incrementServerFact works with no active subscriptions", (test) => {
Facts.resetServerFacts();
Facts._setActiveSubscriptions([]);
Facts.incrementServerFact("lonely-pkg", "value", 99);
test.equal(Facts._factsByPackage["lonely-pkg"], { value: 99 });
});
// -- setUserIdFilter --
Tinytest.add("facts-base - setUserIdFilter replaces the filter", (test) => {
let filterCalled = false;
Facts.setUserIdFilter(function (userId) {
filterCalled = true;
return userId === "admin";
});
test.isFalse(filterCalled);
// Restore default to not affect other tests
Facts.setUserIdFilter(function () {
return !!Package.autopublish;
});
});

View File

@@ -1,9 +1,5 @@
const Facts = {};
const FACTS_COLLECTION = 'meteor_Facts_server';
const FACTS_PUBLICATION = 'meteor_facts';
const FACTS_COLLECTION = "meteor_Facts_server";
const FACTS_PUBLICATION = "meteor_facts";
export {
Facts,
FACTS_COLLECTION,
FACTS_PUBLICATION,
};
export { Facts, FACTS_COLLECTION, FACTS_PUBLICATION };

View File

@@ -1,4 +1,4 @@
import { Facts, FACTS_COLLECTION, FACTS_PUBLICATION } from './facts_base_common';
import { Facts, FACTS_COLLECTION, FACTS_PUBLICATION } from "./facts_base_common";
const hasOwn = Object.prototype.hasOwnProperty;
@@ -6,7 +6,7 @@ const hasOwn = Object.prototype.hasOwnProperty;
// By default, we publish facts to no user if autopublish is off, and to all
// users if autopublish is on.
let userIdFilter = function (userId) {
let userIdFilter = function () {
return !!Package.autopublish;
};
@@ -20,8 +20,14 @@ Facts.setUserIdFilter = function (filter) {
const factsByPackage = {};
let activeSubscriptions = [];
// Make factsByPackage data available to the server environment
// Make internal state available to the server environment
Facts._factsByPackage = factsByPackage;
Facts._getActiveSubscriptions = function () {
return activeSubscriptions;
};
Facts._setActiveSubscriptions = function (subs) {
activeSubscriptions = subs;
};
Facts.incrementServerFact = function (pkg, fact, increment) {
if (!hasOwn.call(factsByPackage, pkg)) {
@@ -46,7 +52,7 @@ Facts.incrementServerFact = function (pkg, fact, increment) {
};
Facts.resetServerFacts = function () {
for (let pkg in factsByPackage) {
for (const pkg in factsByPackage) {
delete factsByPackage[pkg];
}
};
@@ -56,27 +62,26 @@ Facts.resetServerFacts = function () {
// called?
Meteor.defer(function () {
// XXX Also publish facts-by-package.
Meteor.publish(FACTS_PUBLICATION, function () {
const sub = this;
if (!userIdFilter(this.userId)) {
sub.ready();
return;
}
Meteor.publish(
FACTS_PUBLICATION,
function () {
const sub = this;
if (!userIdFilter(this.userId)) {
sub.ready();
return;
}
activeSubscriptions.push(sub);
Object.keys(factsByPackage).forEach(function (pkg) {
sub.added(FACTS_COLLECTION, pkg, factsByPackage[pkg]);
});
sub.onStop(function () {
activeSubscriptions =
activeSubscriptions.filter(activeSub => activeSub !== sub);
});
sub.ready();
}, {is_auto: true});
activeSubscriptions.push(sub);
Object.keys(factsByPackage).forEach(function (pkg) {
sub.added(FACTS_COLLECTION, pkg, factsByPackage[pkg]);
});
sub.onStop(function () {
activeSubscriptions = activeSubscriptions.filter((activeSub) => activeSub !== sub);
});
sub.ready();
},
{ is_auto: true },
);
});
export {
Facts,
FACTS_COLLECTION,
FACTS_PUBLICATION,
};
export { Facts, FACTS_COLLECTION, FACTS_PUBLICATION };

View File

@@ -1,25 +1,25 @@
Package.describe({
summary: "Publish internal app statistics",
version: '1.0.2',
version: "1.0.2",
});
Package.onUse(function (api) {
api.use('ecmascript', ['client', 'server']);
api.use("ecmascript", ["client", "server"]);
// Detect whether autopublish is used.
api.use('autopublish', 'server', {weak: true});
api.use("autopublish", "server", { weak: true });
// Unordered dependency on livedata, since livedata has a (weak) dependency on
// us.
api.use('ddp', 'server', {unordered: false});
api.use("ddp", "server", { unordered: false });
api.mainModule('facts_base_server.js', 'server');
api.mainModule('facts_base_common.js', 'client');
api.mainModule("facts_base_server.js", "server");
api.mainModule("facts_base_common.js", "client");
api.export('Facts');
api.export("Facts");
});
Package.onTest(function (api) {
api.use(['tinytest', 'ecmascript', 'facts-base']);
api.addFiles(['facts_base.tests.js'], 'server');
api.use(["tinytest", "ecmascript", "facts-base"]);
api.addFiles(["facts_base.tests.js"], "server");
});

View File

@@ -17,10 +17,13 @@ export default defineConfig({
text: "Guide",
items: [
{text: "Overview", link: "/"},
{text: "Migration Strategy", link: "/guide/migration-strategy"},
{text: "Frequently Asked Questions", link: "/frequently-asked-questions/"},
{text: "Breaking Changes", link: "/breaking-changes/"},
{text: "Meteor.call x Meteor.callAsync", link: "/breaking-changes/call-x-callAsync"},
{text: "Upgrading packages", link: "/breaking-changes/upgrading-packages"},
{text: "Package Replacements", link: "/guide/package-replacements"},
{text: "Removing Fibers Patterns", link: "/guide/removing-fibers"},
{text: "Publishing Packages", link: "/guide/publishing-packages"},
{text: "Common Errors", link: "/guide/common-errors"},
]

View File

@@ -135,6 +135,10 @@ Starting with Meteor 3.1, Express has been updated to version 5. If you're upgra
:::
::: info
You can start using the Express-based WebApp API while still on Meteor 2.x by installing [`harry97:webapp`](https://github.com/harryadel/harry97-webapp), a backport of Meteor 3's Express-based webapp for Meteor 2.17. This lets you migrate your middleware code before upgrading.
:::
The `webapp` package now exports these new properties:
```ts

View File

@@ -22,3 +22,133 @@ To resolve this issue, follow these steps:
By reducing the package footprint and updating dependencies, you should be able to complete the migration without memory-related errors.
This error was lastly reported [here](https://forums.meteor.com/t/meteor-update-fails/62171).
## Unhandled Promise Rejections in Async Callbacks
**Why this happens:**
When converting synchronous callbacks to async (e.g., in cron jobs, collection hooks, or event handlers), unhandled promise rejections can crash the server. This often manifests as seemingly unrelated errors like WebSocket connection failures or silent startup crashes.
A common case is `quave:synced-cron` where an async cron callback rejects without being caught, bringing down the entire process.
**How to solve it:**
Ensure all async callbacks properly handle errors:
```js
// Problem — unhandled rejection crashes the process
SyncedCron.add({
name: 'My Job',
schedule(parser) { return parser.text('every 1 hour'); },
async job() {
await SomeCollection.updateAsync(/* ... */); // [!code error] if this throws, process crashes
}
});
// Solution — wrap in try/catch
SyncedCron.add({
name: 'My Job',
schedule(parser) { return parser.text('every 1 hour'); },
async job() {
try { // [!code highlight]
await SomeCollection.updateAsync(/* ... */);
} catch (error) { // [!code highlight]
console.error('Cron job failed:', error); // [!code highlight]
} // [!code highlight]
}
});
```
If a package does not support async callbacks, consider lazy-loading it inside `Meteor.startup()` to ensure the environment is fully initialized first.
## SimpleSchema and Collection2 Changes
**Why this happens:**
`aldeed:collection2` v4 (the Meteor 3-compatible version) now bundles `aldeed:simple-schema` internally as an Atmosphere package. This replaces the previous setup where you installed the `simpl-schema` npm package separately. The npm `simpl-schema` v3+ actually dropped all Meteor support, so the Atmosphere package is a hard-fork that maintains Meteor-specific features like Tracker reactivity.
**Common issues and solutions:**
1. **Do not use the npm `simpl-schema` v3+ with Collection2 v4** — they conflict. Collection2 v4 bundles the compatible `aldeed:simple-schema` automatically:
```bash
# Remove the npm package if present
meteor npm remove simpl-schema # [!code highlight]
# Collection2 v4 bundles aldeed:simple-schema — no separate install needed
```
2. **Static vs. dynamic loading** — Collection2 v4 offers two import modes:
```js
// Static loading (default) — loaded immediately, increases initial bundle size
import 'meteor/aldeed:collection2'; // [!code highlight]
// Dynamic loading — deferred, reduces initial bundle size
import 'meteor/aldeed:collection2/dynamic'; // [!code highlight]
// You must explicitly call load() before using schemas
await Collection2.load(); // [!code highlight]
```
If your schemas aren't being applied or you get errors about missing schema methods, ensure you're using the right loading mode and calling `Collection2.load()` if using dynamic imports.
3. **Dot-notation fields need explicit parent objects** — schemas using nested fields like `'address.city'` may need the parent `address` object declared explicitly.
## Package Loading Order Issues
**Why this happens:**
Meteor 3's stricter module system changes the order in which packages and their side effects are loaded. Packages that relied on implicit loading order (e.g., a package that injects itself globally before another package reads it) may break.
**How to solve it:**
1. Use explicit `import` statements in your entry point files (`client/main.js`, `server/main.js`) to control load order
2. If a package needs to run before another, import it earlier in your entry point
3. For packages that register side effects (like template helpers or collection extensions), ensure they are imported before the code that depends on them
## `Meteor.bindEnvironment` Required for External Callbacks
**Why this happens:**
Code running outside of Meteor's async context (e.g., in Express middleware, third-party library callbacks, or raw Node.js event handlers) does not have access to Meteor's environment variables or DDP context. This was sometimes silently handled by Fibers but now requires explicit wrapping.
**How to solve it:**
Wrap external callbacks with `Meteor.bindEnvironment`:
```js
import { Meteor } from 'meteor/meteor';
import { WebApp } from 'meteor/webapp';
// Problem — no Meteor context in Express handler
WebApp.handlers.use('/webhook', (req, res) => {
const user = Meteor.user(); // [!code error] throws error — no Meteor context
});
// Solution — wrap with bindEnvironment
WebApp.handlers.use('/webhook', Meteor.bindEnvironment(async (req, res) => { // [!code highlight]
const user = await Meteor.userAsync(); // [!code highlight] works
res.send('OK');
})); // [!code highlight]
```
## Monkey-Patching Timing Issues
**Why this happens:**
Packages or application code that monkey-patches Meteor APIs (e.g., overriding `Meteor.publish`, wrapping collection methods) may fail if the target API isn't available yet when the patching code runs. Meteor 3's module loading changes can alter the timing of when code executes.
**How to solve it:**
Wrap monkey-patching code in `Meteor.startup()`:
```js
// Problem — patching may run before the target API is ready
Meteor.publish = patchedPublish(Meteor.publish); // [!code error]
// Solution — defer to startup
Meteor.startup(() => { // [!code highlight]
Meteor.publish = patchedPublish(Meteor.publish); // [!code highlight]
}); // [!code highlight]
```
For more complex cases, create a dedicated internal package that controls load order through `api.use()` dependencies in `package.js`.

View File

@@ -0,0 +1,156 @@
# Migration Strategy
This guide provides a recommended step-by-step order for migrating your Meteor 2.x application to Meteor 3.x. It is based on real-world migration experiences from projects like [WeKan](https://github.com/wekan/wekan/pull/6205), [Wework](https://github.com/nate-strauser/wework/pull/126), and several community migration reports.
::: tip
You don't have to complete every step before moving to the next. But following this general order will help you avoid common pitfalls and reduce the number of issues you face at each stage.
:::
## Step 1: Update to the Latest Meteor 2.x
Before jumping to Meteor 3, update your project to the latest Meteor 2.x release (2.16+). This gives you access to compatibility shims and warnings that make the transition smoother.
```bash
meteor update
```
Meteor 2.8+ introduced `*Async` methods alongside the existing sync ones, so you can start migrating your code incrementally while everything still works. See [Migrating to Async in v2](../migrating-to-async-in-v2/index.md) for details.
## Step 2: Audit and Replace Unmaintained Packages
This is often the most time-consuming step. Many Atmosphere packages are unmaintained and will not work with Meteor 3.
1. Review your `.meteor/packages` file
2. For each package, check if a Meteor 3-compatible version exists on [Packosphere](https://packosphere.com/) or the [Meteor Community Packages](https://github.com/Meteor-Community-Packages) GitHub org
3. Replace or remove packages that have no compatible version
See [Package Replacements](./package-replacements.md) for a table of common replacements.
::: tip
Reduce your package footprint as much as possible before upgrading. Fewer packages means fewer migration issues and faster build times.
:::
## Step 3: Identify Sync Code with `WARN_WHEN_USING_OLD_API`
Run your app with this environment variable to find all places where you're using the old synchronous API:
```bash
WARN_WHEN_USING_OLD_API=true meteor run
```
This will log warnings for every sync MongoDB method call on the server (e.g., `findOne`, `insert`, `update`), helping you identify what needs to change.
## Step 4: Restructure Entry Points
Meteor 3 works best with explicit entry points. If your project relies on Meteor's implicit file loading, now is the time to restructure.
1. Create explicit `client/main.js` and `server/main.js` files (if you don't already have them)
2. Configure `mainModule` in your `package.json`:
```json
{
"meteor": {
"mainModule": {
"client": "client/main.js",
"server": "server/main.js"
}
}
}
```
3. Replace ambient globals with explicit imports:
```js
// Before: relying on global availability
Template.hello.helpers({ /* ... */ });
// After: explicit import
import { Template } from 'meteor/templating';
Template.hello.helpers({ /* ... */ });
```
::: warning
This restructuring step can be substantial for large applications. WeKan's migration involved splitting collection definitions (shared models vs. server-only hooks/methods) and rewriting their entire boot sequence.
:::
### Prepare for Express Early with `harry97:webapp`
Meteor 3 replaces Connect with Express in the `webapp` package. If your app uses `WebApp.connectHandlers` or custom middleware, you can start writing Express-compatible code **while still on Meteor 2.x** by using the [`harry97:webapp`](https://github.com/harryadel/harry97-webapp) package — a backport of Meteor 3's Express-based webapp for Meteor 2.17.
```bash
meteor add harry97:webapp
```
This gives you access to the same Express API that Meteor 3 uses (`WebApp.handlers`, `WebApp.express`, etc.) with backward-compatible aliases for `WebApp.connectHandlers` and `WebApp.rawConnectHandlers`. When you eventually upgrade to Meteor 3, your middleware code will already be compatible — just remove `harry97:webapp` and the core `webapp` package takes over.
## Step 5: Convert Sync Code to Async
On the server, all MongoDB collection methods must use their `*Async` counterparts:
```js
// Before
const doc = MyCollection.findOne({ _id: id });
MyCollection.insert({ name: 'test' });
MyCollection.update({ _id: id }, { $set: { name: 'updated' } });
// After
const doc = await MyCollection.findOneAsync({ _id: id });
await MyCollection.insertAsync({ name: 'test' });
await MyCollection.updateAsync({ _id: id }, { $set: { name: 'updated' } });
```
Don't forget to also convert:
- `Meteor.user()``await Meteor.userAsync()`
- `Meteor.call()``await Meteor.callAsync()`
- `Email.send()``await Email.sendAsync()`
- `cursor.fetch()``await cursor.fetchAsync()`
- `cursor.count()``await cursor.countAsync()`
- `cursor.forEach()``await cursor.forEachAsync()`
- `cursor.map()``await cursor.mapAsync()`
- `createIndex()``await createIndexAsync()`
The [jscodeshift codemod](https://github.com/minhna/meteor-async-migration) can automate much of this work.
## Step 6: Replace Deprecated Patterns
Remove patterns that depend on Fibers or other removed APIs. See [Removing Fibers Patterns](./removing-fibers.md) for detailed before/after examples. Key replacements:
- `Meteor.wrapAsync()``util.promisify()` or manual Promises
- `Promise.await()``await` in an `async` function
- `HTTP.call()``await fetch()` (using the `meteor/fetch` core package)
- `Npm.require('fibers')` → remove entirely
## Step 7: Run `meteor update` to 3.x
Once your code is async-ready and your packages are compatible:
```bash
meteor update
```
Then clean up:
```bash
rm -rf node_modules package-lock.json
meteor npm install
```
If you encounter memory issues during the update, see [Common Errors](./common-errors.md#cannot-enlarge-memory-array).
## Step 8: Test and Iterate
After upgrading:
1. Run your app and check the server console for errors
2. Test each feature systematically — async issues often manifest as silent failures or unexpected `undefined` values
3. Pay special attention to:
- Meteor methods and publications
- Collection hooks and allow/deny callbacks
- Cron jobs and background tasks
- Third-party API integrations
::: tip
Migrate one module at a time when possible. This lets you catch and fix errors incrementally instead of facing them all at once.
:::
For real-world migration writeups, migration PRs, and more supporting links, see [Migration Reports and External Resources](../index.md#migration-reports-and-external-resources).

View File

@@ -0,0 +1,86 @@
# Package Replacements
One of the biggest challenges in migrating to Meteor 3 is dealing with unmaintained Atmosphere packages. This page lists common packages that need replacement and their recommended alternatives.
::: tip
Before replacing a package, check [Packosphere](https://packosphere.com/) — many packages have already been updated for Meteor 3 compatibility. Also check the [Meteor Community Packages](https://github.com/Meteor-Community-Packages) GitHub org and the [community tracking spreadsheet](https://docs.google.com/spreadsheets/d/1JbUZmJab3owZ9LV71Ubto32YX_QWQljRypJTOQupxL8/edit?usp=sharing).
:::
## Replacement Strategy
When a package doesn't have a Meteor 3-compatible version:
1. **Check Packosphere** for an updated version or fork
2. **Check the [Community Packages](https://github.com/Meteor-Community-Packages) org** — many packages have been transferred here for ongoing maintenance
3. **Look for an npm replacement** — Meteor 3 has better npm integration, so npm packages are often viable
4. **Inline the functionality** — if the package is small, consider copying the relevant code into your project
5. **Fork and patch** — as a last resort, fork the package and make the minimum changes for Meteor 3 compatibility
## Routing
| Old Package | Replacement | Notes |
|---|---|---|
| `kadira:flow-router` | `ostrio:flow-router-extra` | Drop-in replacement with additional features |
| `iron:router` | `ostrio:flow-router-extra` or `vlasky:galvanized-iron-router` | `galvanized-iron-router` is closest to iron:router's API |
## Data & Collections
| Old Package | Replacement | Notes |
|---|---|---|
| `aldeed:collection2` | `aldeed:collection2@4.0.0` | Updated for Meteor 3; now bundles `aldeed:simple-schema` internally — remove the `simpl-schema` npm package |
| `aldeed:schema-index` | `communitypackages:schema-index` | Transferred to Community Packages |
| `aldeed:schema-deny` | `communitypackages:schema-deny` | Transferred to Community Packages |
| `konecty:mongo-counter` | Inline MongoDB `$inc` operations | Simple enough to implement directly |
| `cottz:publish-relations` | `reywood:publish-composite` or [meteor-reactive-publish](https://github.com/nachocodoner/meteor-reactive-publish) | Both support reactive joins |
## Scheduling & Background Jobs
| Old Package | Replacement | Notes |
|---|---|---|
| `percolate:synced-cron` | `quave:synced-cron` | Community-maintained fork with async support |
## HTTP & APIs
| Old Package | Replacement | Notes |
|---|---|---|
| `http` (Meteor package) | `fetch` (`meteor/fetch`) | Core Meteor package; uses the standard `fetch` API |
| `simple:json-routes` | `WebApp.handlers` (Express) | Meteor 3 uses Express — see [Breaking Changes](../breaking-changes/index.md#webapp-switches-to-express-5) |
| Restivus | `WebApp.handlers` (Express) | Build REST endpoints directly with Express routes |
## Accounts & Auth
| Old Package | Replacement | Notes |
|---|---|---|
| `useraccounts:*` | `communitypackages:*` | Community-maintained alternatives |
## UI & Templates
| Old Package | Replacement | Notes |
|---|---|---|
| `mquandalle:jade` | Remove, convert to Spacebars/HTML | Jade template support was dropped |
| `peerlibrary:blaze-components` | Native Blaze templates | Convert to standard `Template` patterns |
| `meteorhacks:subs-manager` | Remove | Not needed with modern Meteor's subscription handling |
## Build Tools & CSS
| Old Package | Replacement | Notes |
|---|---|---|
| `fourseven:scss` | Remove (if using rspack on Meteor 3.4+) | rspack has native SCSS support |
::: info
For projects on Meteor 3.4+ with rspack, many build-tool-related Atmosphere packages (SCSS, Less, etc.) are no longer needed as rspack handles these natively. See the [Meteor-Rspack integration guide](https://docs.meteor.com/about/modern-build-stack/rspack-bundler-integration.html) for setup details and supported features.
:::
## Utilities
| Old Package | Replacement | Notes |
|---|---|---|
| `ongoworks:speakingurl` | `limax` (npm) | `npm install limax` |
| `underscore` | Native JavaScript | `Array.map`, `Object.keys`, `Array.filter`, spread syntax, etc. |
| `moment` | Native `Date`, `date-fns`, or `luxon` (npm) | `moment` is in maintenance mode |
## Community Resources
- [Community Package Migration Thread](https://forums.meteor.com/t/looking-for-help-migrating-packages-to-meteor-3-0/60985) — ongoing community discussion about package migration status
- [Package Compatibility Spreadsheet](https://docs.google.com/spreadsheets/d/1JbUZmJab3owZ9LV71Ubto32YX_QWQljRypJTOQupxL8/edit?usp=sharing) — collaborative tracking of package compatibility
- [Upgrading Packages Guide](../breaking-changes/upgrading-packages.md) — how to update your own packages for Meteor 3 compatibility

View File

@@ -0,0 +1,186 @@
# Removing Fibers Patterns
Meteor 3 removes the Fibers dependency entirely. This page shows before/after examples for the most common Fibers-dependent patterns you'll encounter during migration.
For background on why Fibers was removed, see the [FAQ](../frequently-asked-questions/index.md#what-is-fibers).
## `Meteor.wrapAsync` → Promises
`Meteor.wrapAsync` no longer exists. Replace it with `util.promisify` or manual Promise wrapping.
```js
// Before
import { Meteor } from 'meteor/meteor';
const syncFunction = Meteor.wrapAsync(someCallbackFunction); // [!code error]
const result = syncFunction(arg1, arg2); // [!code error]
// After — using util.promisify
import { promisify } from 'util';
const asyncFunction = promisify(someCallbackFunction);
const result = await asyncFunction(arg1, arg2); // [!code highlight]
```
If the callback doesn't follow the standard `(error, result)` pattern, wrap it manually:
```js
// After — manual Promise wrapping
function asyncFunction(arg1, arg2) {
return new Promise((resolve, reject) => {
someCallbackFunction(arg1, arg2, (error, result) => {
if (error) reject(error);
else resolve(result);
});
});
}
const result = await asyncFunction(arg1, arg2); // [!code highlight]
```
## `Promise.await` → `async/await`
`Promise.await` was a Fibers-based synchronous wait. Replace with standard `await` inside an `async` function.
```js
// Before
const result = Promise.await(someAsyncOperation()); // [!code error]
// After
const result = await someAsyncOperation(); // [!code highlight]
```
::: warning
The function containing `await` must be declared as `async`. This often means you need to make the calling function async too, which can cascade up the call chain.
:::
## `Npm.require('fibers')` → Remove
Any direct usage of the `fibers` npm module must be removed entirely.
```js
// Before
const Fiber = Npm.require('fibers'); // [!code error]
const Future = Npm.require('fibers/future'); // [!code error]
const future = new Future(); // [!code error]
someCallback((err, result) => { // [!code error]
if (err) future.throw(err); // [!code error]
else future.return(result); // [!code error]
}); // [!code error]
const result = future.wait(); // [!code error]
// After
const result = await new Promise((resolve, reject) => { // [!code highlight]
someCallback((err, result) => { // [!code highlight]
if (err) reject(err); // [!code highlight]
else resolve(result); // [!code highlight]
}); // [!code highlight]
}); // [!code highlight]
```
## `HTTP.call` → `fetch`
The old `HTTP` package is replaced by the `fetch` core Meteor package.
```js
// Before
import { HTTP } from 'meteor/http'; // [!code error]
const response = HTTP.call('GET', 'https://api.example.com/data', { // [!code error]
headers: { Authorization: `Bearer ${token}` } // [!code error]
}); // [!code error]
const data = response.data; // [!code error]
// After
import { fetch } from 'meteor/fetch'; // [!code highlight]
const response = await fetch('https://api.example.com/data', { // [!code highlight]
headers: { Authorization: `Bearer ${token}` } // [!code highlight]
}); // [!code highlight]
const data = await response.json(); // [!code highlight]
```
For POST requests:
```js
// Before
HTTP.call('POST', url, { // [!code error]
data: { key: 'value' } // [!code error]
}); // [!code error]
// After
await fetch(url, { // [!code highlight]
method: 'POST', // [!code highlight]
headers: { 'Content-Type': 'application/json' }, // [!code highlight]
body: JSON.stringify({ key: 'value' }) // [!code highlight]
}); // [!code highlight]
```
## `Email.send` → `Email.sendAsync`
```js
// Before
import { Email } from 'meteor/email';
Email.send({ // [!code error]
to: 'user@example.com',
from: 'noreply@example.com',
subject: 'Hello',
text: 'World'
}); // [!code error]
// After
await Email.sendAsync({ // [!code highlight]
to: 'user@example.com',
from: 'noreply@example.com',
subject: 'Hello',
text: 'World'
}); // [!code highlight]
```
## Synchronous `createIndex` → `createIndexAsync`
```js
// Before
MyCollection._ensureIndex({ email: 1 }); // [!code error]
// After
await MyCollection.createIndexAsync({ email: 1 }); // [!code highlight]
```
## Callback-Based Patterns → `async/await`
Many older Meteor patterns used callbacks or synchronous Fiber-based code. Convert these to `async/await`:
```js
// Before — callback in Meteor.startup
Meteor.startup(() => {
const settings = Settings.findOne({ key: 'app' }); // [!code error]
if (!settings) {
Settings.insert({ key: 'app', value: defaults }); // [!code error]
}
});
// After
Meteor.startup(async () => { // [!code highlight]
const settings = await Settings.findOneAsync({ key: 'app' }); // [!code highlight]
if (!settings) {
await Settings.insertAsync({ key: 'app', value: defaults }); // [!code highlight]
}
});
```
```js
// Before — synchronous publish
Meteor.publish('userPosts', function () {
const user = Meteor.users.findOne(this.userId); // [!code error]
return Posts.find({ authorId: user._id }); // [!code error]
});
// After
Meteor.publish('userPosts', async function () { // [!code highlight]
const user = await Meteor.users.findOneAsync(this.userId); // [!code highlight]
return Posts.find({ authorId: user._id });
});
```

View File

@@ -55,12 +55,15 @@ Which will install the necessary packages using the latest Node.js version from
This guide covers the necessary topics for migrating your application from Meteor 2.x to Meteor 3.0, including:
- [Migration Strategy](./guide/migration-strategy.md), a recommended step-by-step migration order based on real-world experience.
- [Frequently Asked Questions](./frequently-asked-questions/index.md), answers to common questions.
- [Breaking Changes](./breaking-changes/index.md), an overview of the changes that will affect your application.
- [Meteor.call x Meteor.callAsync](./breaking-changes/call-x-callAsync.md), why should you change your methods to use `Async` methods.
- [Upgrading packages](./breaking-changes/upgrading-packages.md), how to upgrade your packages to the be compatible with Meteor v3.
- [Package Replacements](./guide/package-replacements.md), common unmaintained packages and their Meteor 3-compatible alternatives.
- [Removing Fibers Patterns](./guide/removing-fibers.md), before/after examples for replacing Fibers-dependent code.
- [How async functions work and how to use them](./api/async-functions.md), a how-to guide in how to use async functions and helpers for Meteor.
- [Renamed Functions](./api/renamed-functions.md), a list of functions that were renamed in Meteor v3.
- [Removed Functions](./api/removed-functions.md), a list of functions that were removed in Meteor v3.
@@ -69,15 +72,23 @@ This guide covers the necessary topics for migrating your application from Meteo
- [Blaze in Meteor v3](./front-end/blaze.md), how to migrate your Blaze code to Meteor v3.
- [Migrating to Async in Meteor 2.x](migrating-to-async-in-v2/index.md), how can you migrate your application to Meteor v3 while in 2.x.
- [Common Errors](./guide/common-errors.md), documented errors and solutions you may encounter during migration.
## External Resources
We are aware of these articles and guides to assist with your migration:
## Migration Reports and External Resources
We are aware of these migration reports, articles, guides, and videos to assist with your migration:
- [Prepare your Meteor.js project for the big 3.0 release](https://dev.to/jankapunkt/prepare-your-meteorjs-project-for-the-big-30-release-14bf)
- [Gradually upgrading a Meteor.js project to 3.0](https://dev.to/meteor/gradually-upgrading-a-meteorjs-project-to-30-5aj0)
- [Meteor 3.0 Migration Guide, from Daniel](https://docs.google.com/document/d/1XxHE5MQaS0-85HQ-bkiXxmGlYi41ggkX3F-9Rjb9HhE/edit#heading=h.65xi3waq9bb)
- [Illustreets Migration Guide, large SaaS migrated to 3.0](https://forums.meteor.com/t/large-saas-migrated-to-3-0/61113) & their how-to [post](https://forums.meteor.com/t/meteor-3-0-beta-6-is-out/61277/12)
- [Atmosphere Migration from Meteor 2.x to Meteor 3.4 with Rspack](https://blog.galaxycloud.app/meteorjs-2-to-3-blaze-migration-rspack/)
- [The Meteor 3.0 Migration: A Space Exploration Mission](https://dev.to/meteor/the-meteor-30-migration-a-space-exploration-mission-3gb5) — Collection2, collection-hooks, SCSS, Cordova migration experience
- Dev Diary series by Harry Adel: [#24](https://harryadel.com/dev-diary-24/) (package audit & strategy), [#25](https://harryadel.com/dev-diary-25/) (auth packages & Fibers removal), [#26](https://harryadel.com/dev-diary-26/) (app restructuring & final migration)
- [WeKan Meteor 3 Migration PR](https://github.com/wekan/wekan/pull/6205) — large Blaze app migration with 23 model files
- [Wework Meteor 3.4 Migration PR](https://github.com/nate-strauser/wework/pull/126) — iron:router replacement, REST API migration
- [Community Package Migration Thread](https://forums.meteor.com/t/looking-for-help-migrating-packages-to-meteor-3-0/60985) — ongoing community discussion and tracking
- [Package Compatibility Spreadsheet](https://docs.google.com/spreadsheets/d/1JbUZmJab3owZ9LV71Ubto32YX_QWQljRypJTOQupxL8/edit?usp=sharing) — collaborative tracking of package compatibility
### Videos