diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 0000000000..ba6de87c22 --- /dev/null +++ b/.coderabbit.yaml @@ -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: false + 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 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/.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') 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 ``` 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` 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 = 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/.vitepress/config.mts b/v3-docs/docs/.vitepress/config.mts index def4899da6..4d1a1c7355 100644 --- a/v3-docs/docs/.vitepress/config.mts +++ b/v3-docs/docs/.vitepress/config.mts @@ -461,6 +461,14 @@ export default defineConfig({ text: "jam:offline", link: "/community-packages/offline", }, + { + text: "dupontbertrand:cluster", + link: "/community-packages/cluster", + }, + { + text: "dupontbertrand:mail-preview", + link: "/community-packages/mail-preview", + }, ], collapsed: true, }, @@ -681,7 +689,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 ${metadata.currentVersion}. + +## 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(), }), ], }, 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. 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 7de8ec0565..1d900c95de 100644 --- a/v3-docs/docs/community-packages/index.md +++ b/v3-docs/docs/community-packages/index.md @@ -40,6 +40,14 @@ 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 + +#### 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) 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..d58c53e3a7 --- /dev/null +++ b/v3-docs/docs/generators/api-export/generateApiJson.js @@ -0,0 +1,57 @@ +/** + * 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'); + +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'); + 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; + } + + try { + const apiData = parseApiData(fs.readFileSync(dataPath, 'utf8')); + + // 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); + } +}; 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 🚀"); }