From dec0aefed5049abfcd9b3c0b06b1b09ba5ccc95d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 13:54:32 +0000 Subject: [PATCH 01/16] build(deps): bump actions/github-script from 6 to 8 Bumps [actions/github-script](https://github.com/actions/github-script) from 6 to 8. - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/v6...v8) --- updated-dependencies: - dependency-name: actions/github-script dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/inactive-issues.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/inactive-issues.yml b/.github/workflows/inactive-issues.yml index 2d8cba7a3f..620b484db6 100644 --- a/.github/workflows/inactive-issues.yml +++ b/.github/workflows/inactive-issues.yml @@ -17,7 +17,7 @@ jobs: uses: actions/checkout@v3 - name: Manage inactive issues - uses: actions/github-script@v6 + uses: actions/github-script@v8 with: script: | const script = require('./.github/scripts/inactive-issues.js') From 644952bff57b044566fb1304ec07f022e36be36c Mon Sep 17 00:00:00 2001 From: Harry Adel Date: Thu, 19 Feb 2026 02:40:26 +0200 Subject: [PATCH 02/16] Add absolute URLs to llms.txt --- v3-docs/docs/.vitepress/config.mts | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/v3-docs/docs/.vitepress/config.mts b/v3-docs/docs/.vitepress/config.mts index bef4db33ed..08b471f0d1 100644 --- a/v3-docs/docs/.vitepress/config.mts +++ b/v3-docs/docs/.vitepress/config.mts @@ -667,7 +667,27 @@ export default defineConfig({ vite: { plugins: [ llmstxt({ - title: "Meteor.js 3 Docs", + title: "Meteor.js 3 Documentation", + domain: "https://docs.meteor.com", + description: "Full-stack JavaScript platform for modern web and mobile applications.", + details: ` +Meteor is a full-stack JavaScript platform for developing web and mobile applications. + +Key capabilities: +- Real-time data synchronization with publications and subscriptions +- Built-in accounts and authentication system +- Frontend agnostic (React, Vue, Solid, Blaze, Svelte) +- Zero-config build system with modern tooling (SWC, Rspack) +- One-command deployment to Galaxy Cloud +- TypeScript support with full type inference + +Current version: Meteor 3.4 with Node.js 22.x support. + +## Structured API Data + +For complete API documentation in machine-readable format, see: +- [api-reference.json](/api-reference.json) - Full API reference with all functions, parameters, and types + `.trim(), }), ], }, From ded541a7bee5f25c1348d74d19ca951ce43c397c Mon Sep 17 00:00:00 2001 From: Harry Adel Date: Thu, 19 Feb 2026 02:40:44 +0200 Subject: [PATCH 03/16] Export API reference JSON --- v3-docs/docs/.gitignore | 5 +- .../generators/api-export/generateApiJson.js | 65 +++++++++++++++++++ v3-docs/docs/generators/codegen.js | 3 + 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 v3-docs/docs/generators/api-export/generateApiJson.js diff --git a/v3-docs/docs/.gitignore b/v3-docs/docs/.gitignore index bcd2cabb66..60c63498eb 100644 --- a/v3-docs/docs/.gitignore +++ b/v3-docs/docs/.gitignore @@ -3,4 +3,7 @@ /.vitepress/dist /data/data.js -/data/names.json \ No newline at end of file +/data/names.json + +# Generated API reference for LLMs +/public/api-reference.json \ No newline at end of file diff --git a/v3-docs/docs/generators/api-export/generateApiJson.js b/v3-docs/docs/generators/api-export/generateApiJson.js new file mode 100644 index 0000000000..1ab371ed9d --- /dev/null +++ b/v3-docs/docs/generators/api-export/generateApiJson.js @@ -0,0 +1,65 @@ +/** + * Generates a public JSON file from the JSDoc API data. + * This file is accessible to LLMs at /api-reference.json + */ + +const fs = require('fs'); +const path = require('path'); + +async function generateApiJson() { + console.log("πŸ“¦ Generating API reference JSON for LLMs..."); + + const dataPath = path.join(__dirname, '../../data/data.js'); + const publicDir = path.join(__dirname, '../../public'); + const outputPath = path.join(publicDir, 'api-reference.json'); + + // Check if data.js exists + if (!fs.existsSync(dataPath)) { + console.log("⚠️ data/data.js not found. Run 'npm run generate-jsdoc' first."); + return; + } + + // Read the data.js file + const dataContent = fs.readFileSync(dataPath, 'utf-8'); + + // Extract the JSON from the ES module export + // The file format is: // comment\nexport default{...}; + const jsonMatch = dataContent.match(/export default\s*(\{[\s\S]*\});?\s*$/); + + if (!jsonMatch) { + console.error("❌ Could not parse data/data.js"); + return; + } + + try { + // Parse the JSON + const apiData = JSON.parse(jsonMatch[1]); + + // Create public directory if it doesn't exist + if (!fs.existsSync(publicDir)) { + fs.mkdirSync(publicDir, { recursive: true }); + } + + // Add metadata + const output = { + _meta: { + generator: "Meteor Docs API Export", + generated: new Date().toISOString(), + description: "API reference for Meteor.js - for LLM consumption", + url: "https://docs.meteor.com/api-reference.json" + }, + apis: apiData + }; + + // Write the JSON file + fs.writeFileSync(outputPath, JSON.stringify(output, null, 2)); + + const apiCount = Object.keys(apiData).length; + console.log(`βœ… Generated api-reference.json with ${apiCount} APIs`); + + } catch (err) { + console.error("❌ Error generating API JSON:", err.message); + } +} + +module.exports = { generateApiJson }; diff --git a/v3-docs/docs/generators/codegen.js b/v3-docs/docs/generators/codegen.js index a6ed7ab00a..fae6b7a8af 100644 --- a/v3-docs/docs/generators/codegen.js +++ b/v3-docs/docs/generators/codegen.js @@ -1,11 +1,14 @@ const { generateChangelog } = require("./changelog/script.js"); const { listPackages } = require("./packages-listing/script.js"); const { generateMeteorVersions } = require("./meteor-versions/script.js"); +const { generateApiJson } = require("./api-export/generateApiJson.js"); + async function main() { console.log("πŸš‚ Started codegen πŸš‚"); await generateChangelog(); await listPackages(); await generateMeteorVersions(); + await generateApiJson(); console.log("πŸš€ Done codegen πŸš€"); } From d6242fe0da51bea7b50bff30ee8393916d241f89 Mon Sep 17 00:00:00 2001 From: Harry Adel Date: Sun, 1 Mar 2026 23:41:24 +0200 Subject: [PATCH 04/16] Get version dynamically --- v3-docs/docs/.vitepress/config.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v3-docs/docs/.vitepress/config.mts b/v3-docs/docs/.vitepress/config.mts index 08b471f0d1..d7644fd222 100644 --- a/v3-docs/docs/.vitepress/config.mts +++ b/v3-docs/docs/.vitepress/config.mts @@ -681,7 +681,7 @@ Key capabilities: - One-command deployment to Galaxy Cloud - TypeScript support with full type inference -Current version: Meteor 3.4 with Node.js 22.x support. +Current version: Meteor ${metadata.currentVersion}. ## Structured API Data From 78e1d397e6f5d23554338746297db489369d70a9 Mon Sep 17 00:00:00 2001 From: Harry Adel Date: Sun, 1 Mar 2026 23:41:50 +0200 Subject: [PATCH 05/16] Replace regex with dynamic import --- .../generators/api-export/generateApiJson.js | 33 +++++++------------ 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/v3-docs/docs/generators/api-export/generateApiJson.js b/v3-docs/docs/generators/api-export/generateApiJson.js index 1ab371ed9d..16e11fb3a3 100644 --- a/v3-docs/docs/generators/api-export/generateApiJson.js +++ b/v3-docs/docs/generators/api-export/generateApiJson.js @@ -5,41 +5,30 @@ const fs = require('fs'); const path = require('path'); +const { pathToFileURL } = require('url'); async function generateApiJson() { console.log("πŸ“¦ Generating API reference JSON for LLMs..."); - + const dataPath = path.join(__dirname, '../../data/data.js'); const publicDir = path.join(__dirname, '../../public'); const outputPath = path.join(publicDir, 'api-reference.json'); - + // Check if data.js exists if (!fs.existsSync(dataPath)) { console.log("⚠️ data/data.js not found. Run 'npm run generate-jsdoc' first."); return; } - - // Read the data.js file - const dataContent = fs.readFileSync(dataPath, 'utf-8'); - - // Extract the JSON from the ES module export - // The file format is: // comment\nexport default{...}; - const jsonMatch = dataContent.match(/export default\s*(\{[\s\S]*\});?\s*$/); - - if (!jsonMatch) { - console.error("❌ Could not parse data/data.js"); - return; - } - + try { - // Parse the JSON - const apiData = JSON.parse(jsonMatch[1]); - + // Dynamically import the ESM data module + const { default: apiData } = await import(pathToFileURL(dataPath).href); + // Create public directory if it doesn't exist if (!fs.existsSync(publicDir)) { fs.mkdirSync(publicDir, { recursive: true }); } - + // Add metadata const output = { _meta: { @@ -50,13 +39,13 @@ async function generateApiJson() { }, apis: apiData }; - + // Write the JSON file fs.writeFileSync(outputPath, JSON.stringify(output, null, 2)); - + const apiCount = Object.keys(apiData).length; console.log(`βœ… Generated api-reference.json with ${apiCount} APIs`); - + } catch (err) { console.error("❌ Error generating API JSON:", err.message); } From ea217a8eeb6aa78b2826342edab358c743248175 Mon Sep 17 00:00:00 2001 From: Harry Adel Date: Sun, 8 Mar 2026 12:56:30 +0200 Subject: [PATCH 06/16] Apply @italojs changes --- .../generators/api-export/generateApiJson.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/v3-docs/docs/generators/api-export/generateApiJson.js b/v3-docs/docs/generators/api-export/generateApiJson.js index 16e11fb3a3..d58c53e3a7 100644 --- a/v3-docs/docs/generators/api-export/generateApiJson.js +++ b/v3-docs/docs/generators/api-export/generateApiJson.js @@ -5,9 +5,15 @@ const fs = require('fs'); const path = require('path'); -const { pathToFileURL } = require('url'); -async function generateApiJson() { +function parseApiData(dataSource) { + const json = dataSource + .replace(/^(?:\/\/.*\n\s*)*export default\s*/, '') + .replace(/;\s*$/, ''); + return JSON.parse(json); +} + +exports.generateApiJson = async function generateApiJson() { console.log("πŸ“¦ Generating API reference JSON for LLMs..."); const dataPath = path.join(__dirname, '../../data/data.js'); @@ -21,8 +27,7 @@ async function generateApiJson() { } try { - // Dynamically import the ESM data module - const { default: apiData } = await import(pathToFileURL(dataPath).href); + const apiData = parseApiData(fs.readFileSync(dataPath, 'utf8')); // Create public directory if it doesn't exist if (!fs.existsSync(publicDir)) { @@ -49,6 +54,4 @@ async function generateApiJson() { } catch (err) { console.error("❌ Error generating API JSON:", err.message); } -} - -module.exports = { generateApiJson }; +}; From 59796fedb0a8cb8921c2a7f0dfae6fa8c3c336f0 Mon Sep 17 00:00:00 2001 From: Harry Adel Date: Thu, 12 Mar 2026 20:53:17 +0200 Subject: [PATCH 07/16] Fix puppeteer_runner.js using broken msg._text API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When puppeteer was bumped from 20.4.0 to 23.6.0 in d8c8c3db77, the ConsoleMessage class switched from underscore-prefixed _text property to ES2022 private #text field, accessible only via msg.text(). The runner code was never updated, so msg._text is always undefined and test output silently falls through to the else branch, printing only "Test number: N" with no actual test results. The text variable from msg.text() is already computed on line 15 for the Permissions policy filter β€” reuse it instead of the broken msg._text. --- packages/test-in-console/puppeteer_runner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/test-in-console/puppeteer_runner.js b/packages/test-in-console/puppeteer_runner.js index 991bbd4631..7065341278 100644 --- a/packages/test-in-console/puppeteer_runner.js +++ b/packages/test-in-console/puppeteer_runner.js @@ -16,7 +16,7 @@ async function runNextUrl(browser) { if (text.includes('Permissions policy violation')) { return; } - if (msg._text !== undefined) console.log(msg._text); + if (text) console.log(text); else { testNumber++; const currentClientTest = From cec38c47a242b9c09efa6680a4acff21908fffca Mon Sep 17 00:00:00 2001 From: Harry Adel Date: Thu, 12 Mar 2026 21:07:22 +0200 Subject: [PATCH 08/16] Docs: guide LLMs to use test-in-console/run.sh for package tests AGENTS.md listed `./meteor test-packages` but not the headless alternative. That command starts a web server and waits for a browser, producing no terminal output, which causes LLM agents to hang. Add `./packages/test-in-console/run.sh` alongside the existing test-packages command in AGENTS.md with a note explaining the difference. Also add package name examples and the PUPPETEER_DOWNLOAD_PATH hint to the testing skill. --- .github/skills/testing/SKILL.md | 5 ++++- AGENTS.md | 8 +++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/skills/testing/SKILL.md b/.github/skills/testing/SKILL.md index 160ba85c97..b4275b99a3 100644 --- a/.github/skills/testing/SKILL.md +++ b/.github/skills/testing/SKILL.md @@ -22,8 +22,11 @@ Test patterns, commands, and utilities for the Meteor codebase. ./meteor test-packages mongo # Test specific package TINYTEST_FILTER="collection" ./meteor test-packages # Filter specific tests -# Package tests in console (headless via Puppeteer) +# Package tests in console (headless via Puppeteer β€” prints results to terminal) +# Use this for automation or when you need terminal output without a browser. PUPPETEER_DOWNLOAD_PATH=~/.npm/chromium ./packages/test-in-console/run.sh +./packages/test-in-console/run.sh # Test all core packages +./packages/test-in-console/run.sh "mongo" # Test specific package # Modern E2E tests (Jest + Playwright) npm run install:modern # Install dependencies diff --git a/AGENTS.md b/AGENTS.md index 22f14547ae..2b774d8e66 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,11 +8,17 @@ Full-stack JavaScript platform for modern web and mobile applications. ./meteor run # Run from source ./meteor create my-app # Create app ./meteor self-test # CLI tests -./meteor test-packages ./packages/ # Package tests +./meteor test-packages ./packages/ # Package tests (browser UI at localhost:3000) +./packages/test-in-console/run.sh "" # Package tests (terminal output via Puppeteer) npm run test:unit # Unit tests (Jest) npm run test:e2e # E2E tests (Jest + Playwright) ``` +> **Note:** `./meteor test-packages` starts a web server and waits for a browser β€” +> it produces no terminal output. For automated/headless runs, use +> `./packages/test-in-console/run.sh ""` instead, which runs the same tests +> via Puppeteer and prints pass/fail results to stdout. + ## Structure ``` From f95cab0e54ef7535c5837720581a0a404eea1f3c Mon Sep 17 00:00:00 2001 From: dupontbertrand Date: Fri, 20 Mar 2026 00:57:42 +0100 Subject: [PATCH 09/16] docs: add CSS Modules section to rspack bundler integration guide The CSS Modules setup was missing from the rspack documentation, causing confusion for users trying to use .module.css files with TypeScript projects. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../rspack-bundler-integration.md | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/v3-docs/docs/about/modern-build-stack/rspack-bundler-integration.md b/v3-docs/docs/about/modern-build-stack/rspack-bundler-integration.md index 2ddbe1dc53..2c7bf79134 100644 --- a/v3-docs/docs/about/modern-build-stack/rspack-bundler-integration.md +++ b/v3-docs/docs/about/modern-build-stack/rspack-bundler-integration.md @@ -397,6 +397,46 @@ With the Meteor–Rspack integration, `zodern:melte` no longer works. Use the of Meteor-Rspack comes with built-in CSS support. You can import any CSS file into your code, and it will be processed and included in your HTML skeleton automatically. In addition, any CSS file placed in the same folder as your Meteor entry point will be processed and added as global styles without the need for explicit imports. +### CSS Modules + +[CSS Modules](https://rspack.rs/guide/tech/css#css-modules) are supported out of the box β€” any file named `*.module.css` is automatically scoped locally. + +By default, rspack uses **named exports**, so imports look like: + +``` js +import { app } from './App.module.css'; +``` + +If you prefer **default imports** (`import styles from './App.module.css'`), disable `namedExports` on both the `css/auto` and `css/module` parsers: + +``` js +module.exports = defineConfig(Meteor => ({ + module: { + parser: { + 'css/auto': { + namedExports: false, + }, + 'css/module': { + namedExports: false, + }, + }, + }, +})); +``` + +#### TypeScript + +When using CSS Modules with TypeScript, add a declaration file (e.g. `imports/css-modules.d.ts`) so the compiler recognizes `.module.css` imports: + +``` typescript +declare module '*.module.css' { + const classes: { readonly [key: string]: string }; + export default classes; +} +``` + +For more details, check [the official Rspack CSS Modules guide](https://rspack.rs/guide/tech/css#css-modules). + ### Less Less support is available in Meteor-Rspack. You need to replace the existing [Meteor `less` package](https://github.com/meteor/meteor/tree/master/packages/non-core/less) or similar with the Rspack configuration. From 9269deeba06c3c509523092b3b33b2d02cc533e9 Mon Sep 17 00:00:00 2001 From: felippeximenes Date: Tue, 27 Jan 2026 13:05:44 -0300 Subject: [PATCH 10/16] docs: add Windows PowerShell note about .\meteor and 7-Zip --- DEVELOPMENT.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 58eda23621..db40651623 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -33,6 +33,14 @@ can run Meteor directly from a Git checkout using these steps: $ ./meteor --help ``` + > **Note for Windows (PowerShell):** + > + > * In PowerShell, use `.\meteor` (not `./meteor`). + > * Meteor may need `7z.exe` available in your `PATH` to download/extract binaries (dev_bundle). + > * Verify: `where.exe 7z` + > * If missing, install 7-Zip and ensure it is on your PATH (for example via `choco install 7zip -y` or `scoop install 7zip`). + + 3. **Ready to Go!** Your local Meteor checkout is now ready to use! You can use this `./meteor` From ef6b2fe79536320d0c3e271bcff184920ddc8cb1 Mon Sep 17 00:00:00 2001 From: dupontbertrand Date: Mon, 23 Mar 2026 23:13:19 +0100 Subject: [PATCH 11/16] docs: add dupontbertrand:mail-preview to community packages Add documentation page for the mail-preview package, a zero-config dev-mode email preview UI that captures outgoing emails and displays them at /__meteor_mail__/. Ref: https://forums.meteor.com/t/built-in-mail-preview-ui-for-dev-mode/64489 --- v3-docs/docs/.vitepress/config.mts | 4 + v3-docs/docs/community-packages/index.md | 4 + .../docs/community-packages/mail-preview.md | 123 ++++++++++++++++++ 3 files changed, 131 insertions(+) create mode 100644 v3-docs/docs/community-packages/mail-preview.md diff --git a/v3-docs/docs/.vitepress/config.mts b/v3-docs/docs/.vitepress/config.mts index e25264bda4..fe19ac20d4 100644 --- a/v3-docs/docs/.vitepress/config.mts +++ b/v3-docs/docs/.vitepress/config.mts @@ -461,6 +461,10 @@ export default defineConfig({ text: "jam:offline", link: "/community-packages/offline", }, + { + text: "dupontbertrand:mail-preview", + link: "/community-packages/mail-preview", + }, ], collapsed: true, }, diff --git a/v3-docs/docs/community-packages/index.md b/v3-docs/docs/community-packages/index.md index 7de8ec0565..8e5411c860 100644 --- a/v3-docs/docs/community-packages/index.md +++ b/v3-docs/docs/community-packages/index.md @@ -40,6 +40,10 @@ Please bear in mind if you are adding a package to this list, try providing as m - [`jam:soft-delete`](./soft-delete.md), An easy way to add soft deletes to your Meteor app - [`jam:archive`](./archive.md), +#### Developer tools + +- [`dupontbertrand:mail-preview`](./mail-preview.md), Zero-config dev-mode mail preview UI β€” view captured emails at `/__meteor_mail__/` + #### Utilities - [`jam:offline`](./offline.md), An easy way to give your Meteor app offline capabilities and make it feel instant diff --git a/v3-docs/docs/community-packages/mail-preview.md b/v3-docs/docs/community-packages/mail-preview.md new file mode 100644 index 0000000000..9f654ccc93 --- /dev/null +++ b/v3-docs/docs/community-packages/mail-preview.md @@ -0,0 +1,123 @@ +# Mail Preview + +- `Who maintains the package` – [Bertrand Dupont](https://github.com/dupontbertrand) + +[[toc]] + +## What Is It? + +A zero-config, dev-mode mail preview UI for Meteor. Every email sent via `Email.sendAsync()` is captured and displayed in a browser UI at `/__meteor_mail__/`. + +Inspired by similar features in Rails (Action Mailer Preview), Phoenix (Swoosh), Laravel (Mailtrap), and Django (console backend). + +This is a `devOnly` package β€” it is **automatically excluded from production builds**. Zero overhead in production, no need to remove it before deploying. + +## How to Download It? + +```bash +meteor add dupontbertrand:mail-preview +``` + +That's it. No configuration needed. + +## How to Use It? + +1. Start your Meteor app in development mode (`meteor run`) +2. Trigger any email β€” password reset, verification, enrollment, or your own `Email.sendAsync()` calls +3. Open `http://localhost:3000/__meteor_mail__/` in your browser + +### What You Get + +- **Mail list** β€” live-updating list of all captured emails (polls every 2s, no page reload) +- **Mail detail** β€” view each email with tabs for **HTML render**, **Plain Text**, and **HTML Source** +- **Clickable links** β€” verification, password reset, and enrollment links work directly from the preview +- **JSON API** β€” programmatic access for testing and tooling +- **Clear all** β€” one-click button to clear captured mails + +### Example: Capturing an Accounts Email + +```js +// Server β€” nothing special needed, just use Meteor's built-in accounts +import { Accounts } from 'meteor/accounts-base'; + +// When a user registers, Meteor sends a verification email. +// mail-preview captures it automatically. +Accounts.sendVerificationEmail(userId); +``` + +Then open `/__meteor_mail__/` to see the captured email, click the verification link, and it works. + +### Example: Custom Emails with MJML + +```js +// Server +import { Email } from 'meteor/email'; +import mjml2html from 'mjml'; + +const { html } = mjml2html(` + + + + + Hello from Meteor! + + Visit Meteor + + + + + +`); + +await Email.sendAsync({ + from: 'noreply@example.com', + to: 'user@example.com', + subject: 'Welcome!', + html, +}); +``` + +The MJML-rendered email is captured and displayed with full HTML rendering in the preview UI. + +## JSON API + +For programmatic access (useful in tests or tooling): + +| Method | Endpoint | Description | +| -------- | ------------------------------ | ------------------------ | +| `GET` | `/__meteor_mail__/api/mails` | List all captured mails | +| `GET` | `/__meteor_mail__/api/mails/:id` | Get a single mail | +| `DELETE` | `/__meteor_mail__/api/mails` | Clear all captured mails | + +### Example: Using the API in Tests + +```js +// In a test, after triggering an email: +const res = await fetch('http://localhost:3000/__meteor_mail__/api/mails'); +const { mails } = await res.json(); + +assert.equal(mails[0].subject, 'Verify your email'); +assert.ok(mails[0].text.includes('verify-email')); +``` + +## How It Works + +The package uses `Email.hookSend()` to intercept outgoing emails and store them in memory (up to 50 β€” oldest are evicted). A middleware mounted via `WebApp.rawConnectHandlers` serves the preview UI. + +- **Dev mode only** β€” guarded by `Meteor.isDevelopment` and `devOnly: true` in `package.js` +- **No SMTP needed** β€” emails are captured before they reach any transport +- **No external dependencies** β€” uses only Meteor core packages (`email`, `webapp`, `ecmascript`) +- **Works alongside `MAIL_URL`** β€” if set, emails are still sent normally; the hook captures a copy + +## Compatibility + +- Meteor 3.4+ +- Works with `accounts-password` emails (verification, reset password, enrollment) +- Works with custom `Email.sendAsync()` / `Email.send()` calls +- Compatible with Rspack bundler + +## Sources + +- **Atmosphere:** [dupontbertrand:mail-preview](https://atmospherejs.com/dupontbertrand/mail-preview) +- **GitHub:** [dupontbertrand/meteor-mail-preview](https://github.com/dupontbertrand/meteor-mail-preview) +- **Forum Discussion:** [Built-in Mail Preview UI for Dev Mode](https://forums.meteor.com/t/built-in-mail-preview-ui-for-dev-mode/64489) From 494290612bd348489cbc69e3d9dca315f04aeb45 Mon Sep 17 00:00:00 2001 From: dupontbertrand Date: Mon, 23 Mar 2026 23:24:50 +0100 Subject: [PATCH 12/16] docs: add dupontbertrand:cluster to community packages - Add documentation page for dupontbertrand:cluster, a Meteor 3 compatible fork of meteorhacks:cluster - Add entry to community packages index under new Scaling / Clustering category - Add sidebar navigation entry --- v3-docs/docs/.vitepress/config.mts | 4 + v3-docs/docs/community-packages/cluster.md | 152 +++++++++++++++++++++ v3-docs/docs/community-packages/index.md | 4 + 3 files changed, 160 insertions(+) create mode 100644 v3-docs/docs/community-packages/cluster.md diff --git a/v3-docs/docs/.vitepress/config.mts b/v3-docs/docs/.vitepress/config.mts index 97af62202c..53eeaffe19 100644 --- a/v3-docs/docs/.vitepress/config.mts +++ b/v3-docs/docs/.vitepress/config.mts @@ -461,6 +461,10 @@ export default defineConfig({ text: "jam:offline", link: "/community-packages/offline", }, + { + text: "dupontbertrand:cluster", + link: "/community-packages/cluster", + }, ], collapsed: true, }, diff --git a/v3-docs/docs/community-packages/cluster.md b/v3-docs/docs/community-packages/cluster.md new file mode 100644 index 0000000000..04b8f6fadb --- /dev/null +++ b/v3-docs/docs/community-packages/cluster.md @@ -0,0 +1,152 @@ +# Cluster + +- `Who maintains the package` – [Bertrand Dupont](https://github.com/dupontbertrand) + +[[toc]] + +## What Is It? + +A Meteor 3 compatible fork of [`meteorhacks:cluster`](https://github.com/meteorhacks/cluster), providing multi-core support, load balancing, and service discovery for Meteor apps. + +The original package was abandoned around 2016. This fork ports it to Meteor 3 with async/await, modern dependencies, and updated MongoDB driver β€” while preserving the same public API. + +::: warning +This is a **compatibility / migration bridge**, not a new recommended scaling architecture. If you only need multi-core CPU utilization, an external process manager like [PM2](https://pm2.keymetrics.io/) is simpler. This package is for apps that relied on `meteorhacks:cluster` features like service discovery, DDP-aware proxying, or `Cluster.discoverConnection()`. +::: + +## How to Download It? + +```bash +meteor add dupontbertrand:cluster +``` + +## How to Use It? + +### Multi-Core (Simplest Use Case) + +Just set the `CLUSTER_WORKERS_COUNT` environment variable: + +```bash +# Use all available CPU cores +CLUSTER_WORKERS_COUNT=auto meteor run + +# Or specify a number +CLUSTER_WORKERS_COUNT=4 meteor run +``` + +The package forks child processes automatically. No code changes needed. + +### Service Discovery + Load Balancing + +For multi-instance deployments with automatic service registration and DDP-aware load balancing: + +```bash +export CLUSTER_DISCOVERY_URL="mongodb://your-mongo-host/cluster" +export CLUSTER_SERVICE="web" +export CLUSTER_WORKERS_COUNT=2 +``` + +Instances register themselves in MongoDB and discover each other automatically. No need to reconfigure a load balancer when adding or removing instances. + +### Microservices + +Connect to other services in the cluster by name: + +```js +// Server +const serviceConnection = Cluster.discoverConnection('payments'); + +// Call methods on the remote service +const result = await serviceConnection.callAsync('processPayment', data); +``` + +## API + +### `Cluster.connect(discoveryUrl, [options])` + +Connect to a discovery backend (currently MongoDB). + +```js +Cluster.connect('mongodb://host/db'); +``` + +Usually configured via the `CLUSTER_DISCOVERY_URL` environment variable instead of calling directly. + +### `Cluster.register(serviceName, [options])` + +Register this instance as a named service. + +```js +Cluster.register('web'); +``` + +Options: +- `endpoint` β€” the URL other instances use to reach this one (defaults to `ROOT_URL`) +- `balancer` β€” URL of the balancer (defaults to `CLUSTER_BALANCER_URL`) +- `uiService` β€” the service that serves the UI (defaults to the registered service name) + +Usually configured via the `CLUSTER_SERVICE` environment variable. + +### `Cluster.discoverConnection(serviceName, [ddpOptions])` + +Get a DDP connection to a named service. The connection automatically tracks healthy instances via the discovery backend. + +```js +const conn = Cluster.discoverConnection('analytics'); +const data = await conn.callAsync('getReport', params); +``` + +### `Cluster.allowPublicAccess(serviceList)` + +Allow public (non-authenticated) access to specific services. + +```js +Cluster.allowPublicAccess(['web', 'api']); +``` + +Usually configured via the `CLUSTER_PUBLIC_SERVICES` environment variable (comma-separated). + +## Environment Variables + +| Variable | Description | Default | +| -------- | ----------- | ------- | +| `CLUSTER_WORKERS_COUNT` | Number of worker processes (`auto` = all cores) | 1 (no clustering) | +| `CLUSTER_DISCOVERY_URL` | MongoDB URL for service discovery | β€” | +| `CLUSTER_SERVICE` | Name to register this instance as | β€” | +| `CLUSTER_ENDPOINT_URL` | URL for other instances to reach this one | `ROOT_URL` | +| `CLUSTER_BALANCER_URL` | URL of the load balancer | β€” | +| `CLUSTER_PUBLIC_SERVICES` | Comma-separated list of public services | β€” | +| `CLUSTER_UI_SERVICE` | Service that serves the UI | same as `CLUSTER_SERVICE` | + +## What Changed From meteorhacks:cluster + +This fork preserves the original API. Under the hood: + +- MongoDB discovery backend rewritten in **async/await** (was Fibers/wrapAsync) +- MongoDB driver updated from 1.4.x to **6.12.0** +- `underscore` replaced with native ES2015+ +- npm dependencies updated (`cookies`, `http-proxy`, `portscanner`) +- Fixed `Buffer()` deprecation and a pre-existing IPC listener bug +- Balancer made transport-aware for compatibility with pluggable transports ([#14231](https://github.com/meteor/meteor/pull/14231)) +- Test suite adapted for Meteor 3 + +Full changelog: [METEOR3-PORT.md](https://github.com/dupontbertrand/cluster/blob/master/METEOR3-PORT.md) + +## Known Limitations + +- The balancer uses `OverShadowServerEvent` to intercept HTTP/WS traffic at the `httpServer` level. This is invasive and could break if Meteor changes its listener initialization order. +- No Windows testing yet. +- The worker pool uses `child_process.fork` with per-worker ports (not `node:cluster`). + +## Compatibility + +- Meteor 3.4+ +- Node 22 +- Tested on Linux + +## Sources + +- **Atmosphere:** [dupontbertrand:cluster](https://atmospherejs.com/dupontbertrand/cluster) +- **GitHub:** [dupontbertrand/cluster](https://github.com/dupontbertrand/cluster) +- **Original package:** [meteorhacks/cluster](https://github.com/meteorhacks/cluster) +- **Forum Discussion:** [Memory usage and sessions quadruple after upgrading to Meteor 3](https://forums.meteor.com/t/memory-usage-and-sessions-quadruple-after-upgrading-to-meteor-3/64496) diff --git a/v3-docs/docs/community-packages/index.md b/v3-docs/docs/community-packages/index.md index ee31736a45..e49248e39f 100644 --- a/v3-docs/docs/community-packages/index.md +++ b/v3-docs/docs/community-packages/index.md @@ -36,6 +36,10 @@ Please bear in mind if you are adding a package to this list, try providing as m - [`jam:soft-delete`](./soft-delete.md), An easy way to add soft deletes to your Meteor app - [`jam:archive`](./archive.md), +#### Scaling / Clustering + +- [`dupontbertrand:cluster`](./cluster.md), Meteor 3 fork of meteorhacks:cluster β€” multi-core, load balancing, service discovery + #### Utilities - [`jam:offline`](./offline.md), An easy way to give your Meteor app offline capabilities and make it feel instant From d7638494e717667b1e5fa97cbf29b792b5cda8f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Thu, 26 Mar 2026 16:56:15 +0100 Subject: [PATCH 13/16] add .coderabbit.yml configuration for review automation --- .coderabbit.yalm | 95 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 .coderabbit.yalm diff --git a/.coderabbit.yalm b/.coderabbit.yalm new file mode 100644 index 0000000000..b4edd06cab --- /dev/null +++ b/.coderabbit.yalm @@ -0,0 +1,95 @@ +# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json + +language: "en-US" + +reviews: + profile: "chill" # community repo β€” keep it welcoming + request_changes_workflow: false + high_level_summary: true + poem: false # serious OSS platform + in_progress_fortune: false # noise + review_status: true + review_details: false + commit_status: true + collapse_walkthrough: true + changed_files_summary: true + sequence_diagrams: false # overkill for package-level PRs + estimate_code_review_effort: true + assess_linked_issues: true + related_issues: true + related_prs: true + suggested_labels: true + auto_apply_labels: false + suggested_reviewers: true + auto_assign_reviewers: false + + # Exclude generated, build, and Meteor-internal files + path_filters: + - "!**/node_modules/**" + - "!**/.meteor/**" + - "!**/bundle/**" + - "!**/programs/**" + - "!**/*.min.js" + - "!**/cordova-build/**" + - "!**/package-lock.json" + + path_instructions: + - path: "packages/**" + instructions: > + This is a core Meteor Atmosphere package. Focus on API backwards + compatibility, DDP/reactivity correctness, and client/server split. + Avoid nitpicking style β€” the codebase has legacy patterns. + - path: "tools/**" + instructions: > + This is the Meteor build tool (Isobuild). Be thorough about + correctness, edge cases, and performance in the CLI/build pipeline. + - path: "npm-packages/**" + instructions: > + These are npm packages published from the Meteor monorepo. + Check for correct exports, peer dependency handling, and Node.js compatibility. + - path: "v3-docs/**" + instructions: > + Documentation for Meteor v3. Check for accuracy, clarity, and + correct code examples. Grammar and spelling matter here. + - path: "scripts/**" + instructions: > + Build and CI scripts. Focus on correctness, portability, and + error handling. + + auto_review: + enabled: true + drafts: false + auto_incremental_review: true + auto_pause_after_reviewed_commits: 3 + ignore_title_keywords: + - "WIP" + - "DO NOT MERGE" + base_branches: [] + + finishing_touches: + docstrings: + enabled: false # legacy JS β€” too much noise across 100s of packages + unit_tests: + enabled: true + simplify: + enabled: false + + tools: + shellcheck: + enabled: true # βœ… they have .sh scripts in /scripts + markdownlint: + enabled: true # βœ… heavy docs contribution + languagetool: + enabled: true # βœ… useful for international doc contributors + level: "default" + disabled_categories: + - "TYPOGRAPHY" # too nitpicky for code comments + ruff: + enabled: false # ❌ not a Python project + biome: + enabled: false # ❌ they use ESLint already (.eslintignore exists) + ast-grep: + essential_rules: true + +chat: + auto_reply: true From 69f36ab27a64fc45704ab2c28ed62e3330abadfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Thu, 26 Mar 2026 17:03:22 +0100 Subject: [PATCH 14/16] update .coderabbit.yaml configuration --- .coderabbit.yalm => .coderabbit.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .coderabbit.yalm => .coderabbit.yaml (100%) diff --git a/.coderabbit.yalm b/.coderabbit.yaml similarity index 100% rename from .coderabbit.yalm rename to .coderabbit.yaml From a99f6e193789370a5738782e8e3ea08eb02cffd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Thu, 26 Mar 2026 17:07:22 +0100 Subject: [PATCH 15/16] re-run checks From d5b8f6c904bc102197b63c44eca77175dc2bfed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Thu, 26 Mar 2026 17:41:57 +0100 Subject: [PATCH 16/16] update .coderabbit.yaml to disable review_status --- .coderabbit.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.coderabbit.yaml b/.coderabbit.yaml index b4edd06cab..ba6de87c22 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -8,7 +8,7 @@ reviews: high_level_summary: true poem: false # serious OSS platform in_progress_fortune: false # noise - review_status: true + review_status: false review_details: false commit_status: true collapse_walkthrough: true