diff --git a/.github/skills/e2e-coverage/SKILL.md b/.github/skills/e2e-coverage/SKILL.md
new file mode 100644
index 0000000000..c43b00ba2e
--- /dev/null
+++ b/.github/skills/e2e-coverage/SKILL.md
@@ -0,0 +1,50 @@
+---
+name: e2e-coverage
+description: Use when adding, modifying, or reviewing E2E test apps/skeletons to keep the test coverage report up to date.
+---
+
+# E2E Test Coverage Report
+
+Guidelines for maintaining `dev/modern-tools/rspack/E2E_COVERAGE.md` — a single-page report of what every E2E app and skeleton tests.
+
+## When to Update
+
+| Trigger | Action |
+|---------|--------|
+| New app added to `apps/` | Add a subsection under **Apps** with a coverage table |
+| New skeleton added to `skeleton.test.js` | Add a row to the **Skeletons** table |
+| New npm package imported for compatibility testing | Add an entry under **NPM Package Compatibility** with the package name, file, and reason |
+| New custom assertion added to a test file | Add a row to that app's coverage table |
+| New feature tested across multiple apps | Add a row to the **Feature Coverage Matrix** |
+| App or skeleton removed | Remove its entries from all sections |
+
+## Report Structure
+
+The report has five sections, in this order:
+
+1. **Test Lifecycle** — the phases every app/skeleton goes through (init, run, prod, test, test once, build) and what default assertions apply
+2. **Apps** — one subsection per `apps//` with a short description and a `| What is covered | Phase |` table
+3. **Skeletons** — single table with one row per skeleton (`| Skeleton | Port | Language | Extra coverage |`)
+4. **NPM Package Compatibility** — grouped by app, each entry has the package name, file path, and why it's included (ESM-only, native bindings, subpath exports, etc.)
+5. **Feature Coverage Matrix** — cross-reference table (`| Feature | Apps | Skeletons |`) showing where each capability is tested
+
+## How to Gather Information
+
+For each app or skeleton, check these sources:
+
+| Source | What to look for |
+|--------|-----------------|
+| `.test.js` | Test helper used, options (`env`, `configFile`, `buildDir`, `testFullApp`, `checkBundleFilePaths`), all `customAssertions` callbacks and what they assert |
+| `skeleton.test.js` | The `testMeteorSkeleton({ skeletonName: '' })` block for that skeleton |
+| `apps//server/main.js` | npm imports with comments explaining why (ESM-only, native bindings, etc.) |
+| `apps//imports/` | Shared code with special imports (`node:` protocol, JSX packages) |
+| `apps//rspack.config.*` | Custom config features (`compileWithRspack`, `compileWithMeteor`, `disablePlugins`, custom rules) |
+| `apps//package.json` | Dependencies that exist solely for compatibility testing |
+
+## Writing Guidelines
+
+- Keep descriptions short — one line per table row
+- Use the phase names from the lifecycle table: Init, Run, Prod, Test, Build, All
+- For npm packages, always state the **reason** (what module format issue it validates)
+- Don't duplicate info between the per-app table and the feature matrix — the app table has detail, the matrix has the cross-reference
+- When an env var is set in a test file, note it as `All (env prefix)` in the phase column
diff --git a/.github/skills/testing/SKILL.md b/.github/skills/testing/SKILL.md
index b4275b99a3..d54d674e95 100644
--- a/.github/skills/testing/SKILL.md
+++ b/.github/skills/testing/SKILL.md
@@ -28,15 +28,15 @@ 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
-npm run test:modern # Run all E2E tests
-npm run test:modern -- -t="React" # Run specific test
+# E2E tests (Jest + Playwright)
+npm run install:e2e # Install dependencies
+npm run test:e2e # Run all E2E tests
+npm run test:e2e -- -t="React" # Run specific test
```
-## Modern E2E Tests (`tools/modern-tests/`)
+## E2E Tests (`tools/e2e-tests/`)
-Jest + Playwright suite for verifying modern bundler integrations (rspack). Tests cover framework skeletons and build scenarios.
+Jest + Playwright suite for verifying bundler integrations (rspack). Tests cover framework skeletons and build scenarios.
**Test apps:** `apps/{react,vue,svelte,solid,blaze,typescript,babel,coffeescript,monorepo}`
diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml
index 72c3789bc8..ce29d07dfb 100644
--- a/.github/workflows/e2e-tests.yml
+++ b/.github/workflows/e2e-tests.yml
@@ -4,7 +4,7 @@ on:
pull_request:
paths:
- 'meteor'
- - 'tools/modern-tests/**'
+ - 'tools/e2e-tests/**'
- 'packages/rspack/**'
- 'packages/tools-core/**'
- 'packages/babel-compiler/**'
@@ -18,6 +18,7 @@ concurrency:
cancel-in-progress: true
env:
+ TOOL_NODE_FLAGS: "--max_old_space_size=12288"
NODE_OPTIONS: "--max_old_space_size=12288"
jobs:
@@ -32,8 +33,8 @@ jobs:
- Babel
- Blaze
- Coffeescript
- - Library
- Monorepo
+ - Other
- React
- R.Router
- Solid
@@ -51,7 +52,7 @@ jobs:
path: |
~/.npm
node_modules
- tools/modern-tests/node_modules
+ tools/e2e-tests/node_modules
packages/**/.npm
.meteor
dev_bundle
@@ -73,6 +74,14 @@ jobs:
- name: Install test deps
run: npm run install:e2e
+ - name: Set NPM_LINK_RSPACK=false for release branches
+ run: |
+ echo "Current branch: ${{ github.head_ref || github.ref_name }}"
+ if [[ "${{ github.head_ref || github.ref_name }}" == release-* ]]; then
+ echo "NPM_LINK_RSPACK=false" >> $GITHUB_ENV
+ echo "::warning::NPM_LINK_RSPACK=false on release branch. E2E tests will install @meteorjs/rspack from npm — make sure the latest version is published or tests may fail."
+ fi
+
- name: Prepare Meteor
run: ./meteor --get-ready
diff --git a/.github/workflows/inactive-issues.yml b/.github/workflows/inactive-issues.yml
index 2d8cba7a3f..1ea6d88be6 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@v7
with:
script: |
const script = require('./.github/scripts/inactive-issues.js')
diff --git a/.github/workflows/test-packages.yml b/.github/workflows/test-packages.yml
new file mode 100644
index 0000000000..29b8c48646
--- /dev/null
+++ b/.github/workflows/test-packages.yml
@@ -0,0 +1,52 @@
+name: Package tests
+
+on:
+ pull_request:
+
+jobs:
+ test-packages:
+ runs-on: ubuntu-22.04
+ concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+ timeout-minutes: 90
+ env:
+ CXX: g++-12
+ phantom: false
+ PUPPETEER_DOWNLOAD_PATH: /home/runner/.npm/chromium
+ TEST_PACKAGES_EXCLUDE: stylus
+ METEOR_MODERN: true
+ NODE_ENV: CI
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 22.17.0
+
+ - name: Restore caches
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/.npm
+ .meteor
+ .babel-cache
+ dev_bundle
+ /home/runner/.npm/chromium
+ key: ${{ runner.os }}-node-22.17-${{ hashFiles('meteor', '**/package-lock.json') }}
+ restore-keys: |
+ ${{ runner.os }}-node-22.17-
+
+ - name: Install system dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y g++-12 libnss3
+
+ - name: Install npm dependencies
+ run: npm install
+
+ - name: Run test-in-console suite
+ run: ./packages/test-in-console/run.sh
diff --git a/.github/workflows/windows-selftest.yml b/.github/workflows/windows-selftest.yml
index 04b3abc05d..97a22ad4cb 100644
--- a/.github/workflows/windows-selftest.yml
+++ b/.github/workflows/windows-selftest.yml
@@ -45,7 +45,7 @@ jobs:
uses: actions/checkout@v4
- name: Setup Node.js
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v4
with:
node-version: 22.x
diff --git a/.gitignore b/.gitignore
index 4742e13056..36de1287c6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,6 +14,7 @@ node_modules
\#*\#
.\#*
.idea
+!.idea/icon.svg
*.iml
*.sublime-project
*.sublime-workspace
diff --git a/.idea/icon.svg b/.idea/icon.svg
new file mode 100755
index 0000000000..16ecae9b10
--- /dev/null
+++ b/.idea/icon.svg
@@ -0,0 +1,21 @@
+
\ No newline at end of file
diff --git a/AGENTS.md b/AGENTS.md
index 2b774d8e66..6e04b3ad4b 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -48,6 +48,7 @@ Load these for detailed context on specific topics:
| [testing](.github/skills/testing/SKILL.md) | Writing tests, debugging failures, test infrastructure |
| [packages](.github/skills/packages/SKILL.md) | Finding packages by feature, understanding dependencies |
| [modern-tools](.github/skills/modern-tools/SKILL.md) | tools-core utilities, rspack, modern integrations |
+| [e2e-coverage](.github/skills/e2e-coverage/SKILL.md) | Updating the E2E test coverage report when apps/skeletons change |
| [ai-context](.github/skills/ai-context/SKILL.md) | Creating, updating, or maintaining AI documentation files |
## Package Domains
diff --git a/CLAUDE.md b/CLAUDE.md
index cfdb92933f..87a39b6cd2 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -11,4 +11,5 @@ Load these for detailed context on specific topics:
| [testing](.github/skills/testing/SKILL.md) | Writing tests, debugging failures, test infrastructure |
| [packages](.github/skills/packages/SKILL.md) | Finding packages by feature, understanding dependencies |
| [modern-tools](.github/skills/modern-tools/SKILL.md) | tools-core utilities, rspack, modern integrations |
+| [e2e-coverage](.github/skills/e2e-coverage/SKILL.md) | Updating the E2E test coverage report when apps/skeletons change |
| [ai-context](.github/skills/ai-context/SKILL.md) | Creating, updating, or maintaining AI documentation files |
diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md
index 58eda23621..d14f9c746b 100644
--- a/DEVELOPMENT.md
+++ b/DEVELOPMENT.md
@@ -148,7 +148,7 @@ Place test files next to the module they test using the `*.test.js` naming conve
### E2E tests (Jest + Playwright)
-End-to-end tests in `tools/modern-tests/` validate that Meteor skeletons and bundler integrations work correctly. They create real Meteor apps, start dev servers, and assert behavior in a headless Chromium browser.
+End-to-end tests in `tools/e2e-tests/` validate that Meteor skeletons and bundler integrations work correctly. They create real Meteor apps, start dev servers, and assert behavior in a headless Chromium browser.
```sh
# Install dependencies (first time)
@@ -161,7 +161,7 @@ npm run test:e2e
npm run test:e2e -- -t="React"
```
-Each test has a corresponding app fixture in `tools/modern-tests/apps/`. See that directory for examples when adding new E2E tests.
+Each test has a corresponding app fixture in `tools/e2e-tests/apps/`. See that directory for examples when adding new E2E tests.
### Self-tests (Meteor tool)
diff --git a/dev/modern-tools/rspack/E2E_COVERAGE.md b/dev/modern-tools/rspack/E2E_COVERAGE.md
new file mode 100644
index 0000000000..1199c73a50
--- /dev/null
+++ b/dev/modern-tools/rspack/E2E_COVERAGE.md
@@ -0,0 +1,290 @@
+# E2E Test Coverage
+
+> To update this report, follow the [e2e-coverage skill](/.github/skills/e2e-coverage/SKILL.md).
+
+End-to-end tests using Jest + Playwright that verify Meteor apps with the Rspack bundler across frameworks, build modes, and features.
+
+Test infrastructure lives in `tools/e2e-tests/`, with app fixtures in `tools/e2e-tests/apps/` and matching test files at `tools/e2e-tests/.test.js`.
+
+## Test Lifecycle
+
+Every app and skeleton goes through these phases (unless skipped):
+
+| Phase | What it does |
+|-------|-------------|
+| **Init** | Copies app, installs deps, adds rspack, generates config |
+| **Run (dev)** | `meteor run` — asserts build artifacts, app loads, client/server hot rebuild |
+| **Run (prod)** | `meteor run --production` — same checks in production mode |
+| **Test** | `meteor test` — runs mocha test driver, verifies test rebuild |
+| **Test once** | `meteor test --once` — runs tests to completion, checks exit code |
+| **Build** | `meteor build` — verifies bundle structure (main.js, programs/server, web.browser, web.browser.legacy) |
+| **Reset** | `meteor reset` — clears rspack build artifacts, caches, asset/chunk context dirs, and `.meteor/local` subdirectories |
+
+Default assertions on every run phase: build artifacts exist, page title matches, body styles render, `__rspack__` script tag is present.
+
+---
+
+## Apps
+
+Each app lives in `apps//` and has a matching `.test.js`.
+
+### react
+
+Core React integration with custom Meteor local directory.
+
+| What is covered | Phase |
+|----------------|-------|
+| Custom `METEOR_LOCAL_DIR` (`.meteor/local-custom`) | All (env prefix) |
+| Custom build dir (`_build-local-custom`) created | Run |
+| `.gitignore` updated with custom local dir | Run |
+| React + JSX environment detection | Run, Prod, Test, Build |
+| Image assets load (generated + public + background) | Run, Prod |
+| `Meteor.disablePlugins` suppresses rspack plugins | Run, Prod, Test, Build |
+| Custom rspack config (`rspack.config.cjs`) | All |
+| HMR works in dev, disabled in prod | Run, Prod |
+
+### react-router
+
+Full-featured React Router app with custom packages, Less, and advanced rspack config.
+
+| What is covered | Phase |
+|----------------|-------|
+| `METEOR_PACKAGE_DIRS` custom packages dir | All (env prefix) |
+| `babel-plugin-react-compiler` integration | Init, Prod, Build |
+| Compiler output cached in dev (babel.config.js) | Run |
+| 404 page routing (renders "Page Not Found") | Run, Prod |
+| Less stylesheet support (`white-space: break-spaces`) | Run, Prod |
+| Meteor modules config styles (`align-content: center`) | Run, Prod |
+| Custom HTML meta tags (`theme-color`) | Run, Prod |
+| Default + custom package loading | Run |
+| `resolve.extensions` loading (`.jsx`) | Run |
+| `rspack.config.override.js` custom plugin loading | Run, Test, Build |
+| React + TSX environment detection | Run, Prod, Test, Build |
+| Full-app test mode (`--full-app`) | Test |
+| Static assets in bundle (png, md) | Build |
+| HMR works in dev, disabled in prod | Run, Prod |
+
+### blaze
+
+Blaze templating engine integration.
+
+| What is covered | Phase |
+|----------------|-------|
+| Blaze environment detection (`isBlazeEnabled`) | Run, Prod, Test, Build |
+| HMR disabled (incompatible with Blaze) | Run, Prod |
+
+### full-blaze
+
+Full Blaze app (with `imports/` structure for tests).
+
+| What is covered | Phase |
+|----------------|-------|
+| Blaze environment detection | Run, Prod, Test, Build |
+| `imports/api/` test path structure | Test |
+| HMR disabled (incompatible with Blaze) | Run, Prod |
+
+### typescript
+
+TypeScript with SCSS, type checking, `.ts` rspack config, and `.ts` SWC config.
+
+| What is covered | Phase |
+|----------------|-------|
+| TypeScript rspack config (`rspack.config.ts`) | All |
+| TypeScript SWC config (`swc.config.ts`) with automatic JSX runtime | All |
+| `@swc/core` type-only import for SWC config typings | All |
+| Custom build dir (`build`) | All |
+| Custom asset/chunk context dirs (`assets`, `chunks`) | All |
+| SCSS styles support (`white-space: break-spaces`) | Run, Prod |
+| TypeScript + TSX environment detection | Run, Prod, Test, Build |
+| Portable build (Meteor.isDevelopment/isProduction not defined) | Run, Prod, Build |
+| `Meteor.extendSwcConfig` with path aliases (`@ui/*`, `@api/*`) | All |
+| `TsCheckerRspackPlugin` type checking (no errors) | Run |
+| `.meteor/local/types` directory generated | Run |
+| Separate client/server test files | Test |
+| CI: removes TsCheckerRspackPlugin (resource limits) | Init |
+| HMR works in dev, disabled in prod | Run, Prod |
+
+### babel
+
+Babel transpilation with custom module rules and `.mjs` rspack config.
+
+| What is covered | Phase |
+|----------------|-------|
+| Custom rspack config (`rspack.config.mjs`) | All |
+| Custom `NODE_ENV` compilation per phase | All (env prefix) |
+| Rspack mode assertion (development/production) | Run, Prod, Test, Build |
+| `Meteor.isDevelopment`/`Meteor.isProduction` defines | Run, Prod, Test, Build |
+| Module rules for `.js`/`.jsx` files | Run, Prod, Test, Build |
+| Module rules for `.tsx`/`.ts`/`.mts`/`.cts`/`.mjs`/`.cjs` | Run, Prod, Test, Build |
+| Module rules for `.graphql`/`.gql` files | Run, Prod, Test, Build |
+| Default rules negated (custom rules override) | Run, Prod, Test, Build |
+| HMR works in dev, disabled in prod | Run, Prod |
+
+### coffeescript
+
+CoffeeScript language support.
+
+| What is covered | Phase |
+|----------------|-------|
+| `.coffee` file compilation (client + server + test) | All |
+| CoffeeScript-specific conditional syntax | Run, Prod |
+| HMR works in dev, disabled in prod | Run, Prod |
+
+### vue
+
+Vue.js framework with Tailwind CSS.
+
+| What is covered | Phase |
+|----------------|-------|
+| Vue single-file components | All |
+| Tailwind CSS styles (`.p-8` padding) | Run, Prod |
+| HMR works in dev, disabled in prod | Run, Prod |
+
+### solid
+
+SolidJS framework integration.
+
+| What is covered | Phase |
+|----------------|-------|
+| SolidJS compilation and rendering | All |
+| HMR works in dev, disabled in prod | Run, Prod |
+
+### svelte
+
+Svelte framework integration.
+
+| What is covered | Phase |
+|----------------|-------|
+| Svelte compilation and rendering | All |
+| HMR works in dev, disabled in prod | Run, Prod |
+
+### monorepo
+
+Monorepo structure with app in subdirectory.
+
+| What is covered | Phase |
+|----------------|-------|
+| Monorepo layout (`app/` subdirectory) | All |
+| Custom rspack config (`rspack.config.cjs`) | All |
+| `rspack.config.override.cjs` custom plugin loading | Run, Test, Build |
+| Static assets in bundle (png, md) | Build |
+| HMR works in dev, disabled in prod | Run, Prod |
+
+### server-only
+
+Server-only app (no client entry point).
+
+| What is covered | Phase |
+|----------------|-------|
+| No client bundle (client skipped) | All |
+| No client tests (test client skipped) | Test |
+| Server entry loads (`server/main.js loaded`) | Run |
+
+---
+
+## Skeletons
+
+Tested via `skeleton.test.js` using `meteor create --`. Each skeleton verifies: app creation, dev run, production run, test once, build, and reset.
+
+| Skeleton | Port | Language | Extra coverage |
+|----------|------|----------|----------------|
+| angular | 3213 | TypeScript | |
+| apollo | 3201 | JSX | |
+| babel | 3212 | JSX | |
+| bare | 3219 | JS | No title/style checks, no client tests, skip build cache check |
+| blaze | 3202 | JS | |
+| chakra-ui | 3203 | JSX | No body style checks (custom UI library) |
+| coffeescript | 3211 | CoffeeScript | |
+| full | 3204 | JS | `imports/api/` test structure |
+| react | 3205 | JSX | Custom body styles (Inter font, padding) |
+| solid | 3206 | JS | |
+| svelte | 3207 | JS | |
+| tailwind | 3208 | TypeScript | Tailwind `bg-gray-100` styles (dev + prod color formats) |
+| typescript | 3209 | TypeScript | CI: removes TsCheckerRspackPlugin |
+| vue | 3210 | JS | |
+
+---
+
+## NPM Package Compatibility
+
+Several apps import specific npm packages to verify that Meteor + Rspack handles different module formats and edge cases without errors. The app boots successfully only if these imports resolve correctly.
+
+### react-router (`apps/react-router/server/main.js`)
+
+| Package | Reason |
+|---------|--------|
+| `s3mini` | ESM-only package (no CJS fallback) |
+| `@modelcontextprotocol/sdk/client/streamableHttp.js` | ESM subpath export (deep path into ESM package) |
+| `bcrypt` | Native Node.js bindings (compiled C++ addon) |
+| `puppeteer` | Large ESM-compatible package with complex dependency tree (`server/browser-tests/browser.app-test.js`) |
+
+### monorepo (`apps/monorepo/app/`)
+
+| Package | File | Reason |
+|---------|------|--------|
+| `pino` + `pino-pretty` | `server/main.js` | ESM-first logger; `pino-pretty` uses `thread-stream` which has worker file resolution issues — needs `Meteor.compileWithMeteor(["thread-stream"])` in rspack config |
+| `grubba-rpc` | `server/main.js` | Untranspiled npm dependency — needs `Meteor.compileWithRspack(["grubba-rpc"])` to compile it through rspack |
+| `node:buffer` | `imports/api/links.js` | Node.js built-in via `node:` protocol in shared client/server code — must be ignored on client without errors |
+| `@react-email/components` | `imports/emails/TestEmail.jsx` | JSX-heavy ESM package with many subpath exports |
+
+### babel (`apps/babel/server/apollo.js`)
+
+| Package | Reason |
+|---------|--------|
+| `@apollo/server` | ESM-first GraphQL server |
+| `@apollo/server/express4` | ESM subpath export (middleware from deep path) |
+| `graphql` | Peer dependency, dual CJS/ESM package |
+
+### typescript (`apps/typescript/rspack.config.ts`, `apps/typescript/swc.config.ts`)
+
+| Package | Reason |
+|---------|--------|
+| `node:module` (`createRequire`) | Node.js built-in in a `.ts` config file — tests CJS interop via `createRequire(import.meta.url)` in an ESM context |
+| `@swc/core` | Type-only import (`import type { Config }`) — provides typings for `swc.config.ts`, stripped at compile time |
+
+---
+
+## Feature Coverage Matrix
+
+Where each feature is tested across apps and skeletons.
+
+| Feature | Apps | Skeletons |
+|---------|------|-----------|
+| HMR (dev) | react, react-router, babel, coffeescript, vue, solid, svelte, monorepo, typescript | |
+| HMR disabled (prod) | all apps with HMR | |
+| HMR incompatible | blaze, full-blaze | |
+| Custom rspack config | react (.cjs), react-router, babel (.mjs), monorepo (.cjs), typescript (.ts) | |
+| Custom SWC config (.ts) | typescript | |
+| Config override file | react-router, monorepo | |
+| Custom build dir | react, typescript | |
+| Custom asset/chunk context dirs | typescript | |
+| Custom env vars | react (METEOR_LOCAL_DIR), react-router (METEOR_PACKAGE_DIRS) | |
+| Static asset bundling | react-router, monorepo | |
+| Less styles | react-router | |
+| SCSS styles | typescript | |
+| Tailwind CSS | vue | tailwind |
+| Image asset loading | react | |
+| 404 routing | react-router | |
+| Meta tags | react-router | |
+| Babel compiler plugin | react-router | |
+| TypeScript type checking | typescript | |
+| Meteor.disablePlugins | react | |
+| Custom package dirs | react-router | |
+| CoffeeScript compilation | coffeescript | coffeescript |
+| Server-only (no client) | server-only | |
+| Monorepo layout | monorepo | |
+| Full-app test mode | react-router | |
+| Module rules override | babel | |
+| Custom NODE_ENV compilation | babel | |
+| Portable build (no isDev/isProd defines) | typescript | |
+| `Meteor.extendSwcConfig` (path aliases) | typescript | |
+| `meteor reset` cleanup | all apps | all skeletons |
+| Skeleton creation | | all 14 skeletons |
+| Body style assertions | | react, tailwind (custom); most others (default) |
+| Custom .gitignore entries | react | |
+| ESM-only packages | react-router, monorepo, babel | |
+| ESM subpath exports | react-router, babel | |
+| Native bindings (C++ addon) | react-router | |
+| `node:` protocol imports | monorepo, typescript | |
+| Untranspiled npm deps (`compileWithRspack`) | monorepo | |
+| Worker resolution (`compileWithMeteor`) | monorepo | |
diff --git a/npm-packages/meteor-rspack/index.d.ts b/npm-packages/meteor-rspack/index.d.ts
index 896b9703df..20a17a1e7d 100644
--- a/npm-packages/meteor-rspack/index.d.ts
+++ b/npm-packages/meteor-rspack/index.d.ts
@@ -57,10 +57,21 @@ type MeteorEnv = Record & {
*/
splitVendorChunk: () => Record;
/**
- * Extend Rspack SWC loader config.
+ * Extend the SWC loader config by smart-merging custom options on top of
+ * Meteor's defaults. Only the properties you specify are overridden;
+ * everything else is preserved.
+ * @param swcConfig - SWC loader options to merge with defaults
* @returns A config object with SWC loader config
*/
extendSwcConfig: (swcConfig: SwcLoaderOptions) => Record;
+ /**
+ * Replace the SWC loader config entirely, discarding Meteor's defaults.
+ * Use this when you need full control over SWC options and don't want any
+ * automatic merging with Meteor's built-in configuration.
+ * @param swcConfig - Complete SWC loader options (replaces defaults)
+ * @returns A config object with SWC loader config
+ */
+ replaceSwcConfig: (swcConfig: SwcLoaderOptions) => Record;
/**
* Extend Rspack configs.
* @returns A config object with merged configs
@@ -75,6 +86,12 @@ type MeteorEnv = Record & {
disablePlugins: (
matchers: string | RegExp | ((plugin: any, index: number) => boolean) | Array boolean)>
) => Record;
+ /**
+ * Omit `Meteor.isDevelopment` and `Meteor.isProduction` from the DefinePlugin so
+ * the bundle is not tied to a specific Meteor environment (portable / isomorphic builds).
+ * @returns A config fragment with `meteor.enablePortableBuild: true`
+ */
+ enablePortableBuild: () => Record;
}
export type ConfigFactory = (
diff --git a/npm-packages/meteor-rspack/lib/ignore.js b/npm-packages/meteor-rspack/lib/ignore.js
index 17058cce19..7abc3ba959 100644
--- a/npm-packages/meteor-rspack/lib/ignore.js
+++ b/npm-packages/meteor-rspack/lib/ignore.js
@@ -117,7 +117,7 @@ function createIgnoreRegex(globPatterns) {
// For absolute paths, we don't want to force the pattern to match from the beginning
// but we still want to ensure it matches to the end of the path segment
- regexPattern = '(?:^|/)' + regexPattern + '$';
+ regexPattern = '(?:^|/)' + regexPattern;
return regexPattern;
}).filter(pattern => pattern !== null);
diff --git a/npm-packages/meteor-rspack/lib/meteorRspackConfigHelpers.js b/npm-packages/meteor-rspack/lib/meteorRspackConfigHelpers.js
new file mode 100644
index 0000000000..8cac1656c3
--- /dev/null
+++ b/npm-packages/meteor-rspack/lib/meteorRspackConfigHelpers.js
@@ -0,0 +1,121 @@
+const path = require('path');
+const fs = require('fs');
+const { cleanOmittedPaths } = require("./mergeRulesSplitOverlap.js");
+const { mergeMeteorRspackFragments } = require("./meteorRspackConfigFactory.js");
+
+// Helper function to load and process config files
+async function loadAndProcessConfig(configPath, configType, Meteor, argv, disableWarnings) {
+ try {
+ // Load the config file
+ let config;
+ if (path.extname(configPath) === '.mjs') {
+ // For ESM modules, we need to use dynamic import
+ const fileUrl = `file://${configPath}`;
+ const module = await import(fileUrl);
+ config = module.default || module;
+ } else {
+ // For CommonJS modules, we can use require
+ config = require(configPath)?.default || require(configPath);
+ }
+
+ // Process the config
+ const rawConfig = typeof config === 'function' ? config(Meteor, argv) : config;
+ const resolvedConfig = await Promise.resolve(rawConfig);
+ const userConfig = resolvedConfig && '0' in resolvedConfig ? resolvedConfig[0] : resolvedConfig;
+
+ // Define omitted paths and warning function
+ const omitPaths = [
+ "name",
+ "target",
+ "entry",
+ "output.path",
+ "output.filename",
+ ...(Meteor.isServer ? ["optimization.splitChunks", "optimization.runtimeChunk"] : []),
+ ].filter(Boolean);
+
+ const warningFn = path => {
+ if (disableWarnings) return;
+ console.warn(
+ `[${configType}] Ignored custom "${path}" — reserved for Meteor-Rspack integration.`,
+ );
+ };
+
+ // Clean omitted paths and merge Meteor Rspack fragments
+ let nextConfig = cleanOmittedPaths(userConfig, {
+ omitPaths,
+ warningFn,
+ });
+ nextConfig = mergeMeteorRspackFragments(nextConfig);
+
+ return nextConfig;
+ } catch (error) {
+ console.error(`Error loading ${configType} from ${configPath}:`, error);
+ if (configType === 'rspack.config.js') {
+ throw error; // Only rethrow for project config
+ }
+ return null;
+ }
+}
+
+/**
+ * Loads both the user's Rspack configuration and its potential override.
+ *
+ * @param {string|undefined} projectConfigPath
+ * @param {object} Meteor
+ * @param {object} argv
+ * @returns {Promise<{ nextUserConfig: object|null, nextOverrideConfig: object|null }>}
+ */
+async function loadUserAndOverrideConfig(projectConfigPath, Meteor, argv) {
+ let nextUserConfig = null;
+ let nextOverrideConfig = null;
+
+ const projectDir = process.cwd();
+ const isMeteorPackageConfig = projectDir.includes("/packages/rspack");
+
+ if (projectConfigPath) {
+ const configDir = path.dirname(projectConfigPath);
+ const configFileName = path.basename(projectConfigPath);
+ const configExt = path.extname(configFileName);
+ const configNameWithoutExt = configFileName.replace(configExt, '');
+ const configNameFull = `${configNameWithoutExt}.override${configExt}`;
+ const overrideConfigPath = path.join(configDir, configNameFull);
+
+ if (fs.existsSync(overrideConfigPath)) {
+ nextOverrideConfig = await loadAndProcessConfig(
+ overrideConfigPath,
+ configNameFull,
+ Meteor,
+ argv,
+ Meteor.isAngularEnabled
+ );
+ }
+
+ if (fs.existsSync(projectConfigPath) && !isMeteorPackageConfig) {
+ // Check if there's a .mjs or .cjs version of the config file
+ const mjsConfigPath = projectConfigPath.replace(/\.js$/, '.mjs');
+ const cjsConfigPath = projectConfigPath.replace(/\.js$/, '.cjs');
+
+ let projectConfigPathToUse = projectConfigPath;
+ if (fs.existsSync(mjsConfigPath)) {
+ projectConfigPathToUse = mjsConfigPath;
+ } else if (fs.existsSync(cjsConfigPath)) {
+ projectConfigPathToUse = cjsConfigPath;
+ }
+
+ nextUserConfig = await loadAndProcessConfig(
+ projectConfigPathToUse,
+ 'rspack.config.js',
+ Meteor,
+ argv,
+ Meteor.isAngularEnabled
+ );
+ }
+ }
+
+ return { nextUserConfig, nextOverrideConfig };
+}
+
+module.exports = {
+ loadAndProcessConfig,
+ loadUserAndOverrideConfig,
+};
diff --git a/npm-packages/meteor-rspack/lib/meteorRspackHelpers.js b/npm-packages/meteor-rspack/lib/meteorRspackHelpers.js
index 1b77f977cb..2fbbac3027 100644
--- a/npm-packages/meteor-rspack/lib/meteorRspackHelpers.js
+++ b/npm-packages/meteor-rspack/lib/meteorRspackHelpers.js
@@ -132,10 +132,14 @@ function splitVendorChunk() {
}
/**
- * Extend SWC loader config
- * Usage: extendSwcConfig()
+ * Extend SWC loader config by smart-merging custom options on top of Meteor's
+ * defaults (via `mergeSplitOverlap`). Only the properties you specify are
+ * overridden; everything else is preserved.
*
- * @returns {Record} `{ meteorRspackConfigX: { optimization: { ... } } }`
+ * Usage: Meteor.extendSwcConfig({ jsc: { parser: { decorators: true } } })
+ *
+ * @param {object} swcConfig - SWC loader options to merge with defaults
+ * @returns {Record} config fragment for spreading into rspack config
*/
function extendSwcConfig(swcConfig) {
return prepareMeteorRspackConfig({
@@ -152,6 +156,44 @@ function extendSwcConfig(swcConfig) {
});
}
+/**
+ * Replace the SWC loader config entirely, discarding Meteor's defaults.
+ * Use this when you need full control over SWC options and don't want any
+ * automatic merging with Meteor's built-in configuration.
+ *
+ * Usage: Meteor.replaceSwcConfig({ jsc: { parser: { syntax: 'typescript' }, target: 'es2020' } })
+ *
+ * @param {object} swcConfig - Complete SWC loader options (replaces defaults)
+ * @returns {Record} config fragment for spreading into rspack config
+ */
+function replaceSwcConfig(swcConfig) {
+ return prepareMeteorRspackConfig({
+ module: {
+ rules: [
+ {
+ test: /\.(?:[mc]?js|jsx|[mc]?ts|tsx)$/i,
+ exclude: /node_modules|\.meteor\/local/,
+ loader: 'builtin:swc-loader',
+ options: swcConfig,
+ },
+ ],
+ },
+ });
+}
+
+/**
+ * Signal that `Meteor.isDevelopment` and `Meteor.isProduction` should be omitted
+ * from DefinePlugin, making the bundle portable across Meteor environments.
+ * Usage: return Meteor.enablePortableBuild() in your rspack.config.js
+ *
+ * @returns {Record} config fragment with `meteor.enablePortableBuild: true`
+ */
+function enablePortableBuild() {
+ return prepareMeteorRspackConfig({
+ "meteor.enablePortableBuild": true,
+ });
+}
+
/**
* Remove plugins from a Rspack config by name, RegExp, predicate, or array of them.
* When using a function predicate, it receives both the plugin and its index in the plugins array.
@@ -202,12 +244,21 @@ function disablePlugins(config, matchers) {
return config;
}
+function outputMeteorRspack(data) {
+ const jsonString = JSON.stringify(data);
+ const output = `[Meteor-Rspack]${jsonString}[/Meteor-Rspack]`;
+ console.log(output);
+}
+
module.exports = {
compileWithMeteor,
compileWithRspack,
setCache,
splitVendorChunk,
extendSwcConfig,
+ replaceSwcConfig,
makeWebNodeBuiltinsAlias,
disablePlugins,
+ outputMeteorRspack,
+ enablePortableBuild,
};
diff --git a/npm-packages/meteor-rspack/lib/swc.js b/npm-packages/meteor-rspack/lib/swc.js
index 8b43703059..e113ad1626 100644
--- a/npm-packages/meteor-rspack/lib/swc.js
+++ b/npm-packages/meteor-rspack/lib/swc.js
@@ -9,11 +9,38 @@ const vm = require('vm');
function getMeteorAppSwcrc(file = '.swcrc') {
try {
const filePath = `${process.cwd()}/${file}`;
- if (file.endsWith('.js')) {
+ if (file.endsWith('.js') || file.endsWith('.ts')) {
let content = fs.readFileSync(filePath, 'utf-8');
- // Check if the content uses ES module syntax (export default)
+
+ if (file.endsWith('.ts')) {
+ try {
+ const swc = require('@swc/core');
+ const result = swc.transformSync(content, {
+ jsc: {
+ parser: {
+ syntax: 'typescript',
+ },
+ target: 'es2015',
+ },
+ });
+ content = result.code;
+ } catch (swcError) {
+ content = content
+ .replace(/import\s+type\s+.*?from\s+['"][^'"]+['"];?/g, '')
+ .replace(/import\s+.*?from\s+['"][^'"]+['"];?/g, '')
+ .replace(/import\s+['"][^'"]+['"];?/g, '')
+ .replace(/export\s+default\s+/, 'module.exports = ')
+ .replace(/export\s+/g, '')
+ .replace(/:\s*\w+(\[\])?(\s*=)/g, '$2')
+ .replace(/\(([^)]*?):\s*\w+(\[\])?\)/g, '($1)')
+ .replace(/\):\s*\w+(\[\])?\s*\{/g, ') {')
+ .replace(/interface\s+\w+\s*\{[^}]*\}/g, '')
+ .replace(/type\s+\w+\s*=\s*[^;]+;/g, '')
+ .replace(/as\s+\w+(\[\])?/g, '');
+ }
+ }
+
if (content.includes('export default')) {
- // Transform ES module syntax to CommonJS
content = content.replace(/export\s+default\s+/, 'module.exports = ');
}
const script = new vm.Script(`
@@ -27,7 +54,9 @@ function getMeteorAppSwcrc(file = '.swcrc') {
})()
`);
const context = vm.createContext({ process });
- return script.runInContext(context);
+ const result = script.runInContext(context);
+ // Handle CJS interop wrapper (e.g. { __esModule: true, default: config })
+ return result && result.__esModule && result.default ? result.default : result;
} else {
// For .swcrc and other JSON files, parse as JSON
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
@@ -45,12 +74,13 @@ function getMeteorAppSwcrc(file = '.swcrc') {
function getMeteorAppSwcConfig() {
const hasSwcRc = fs.existsSync(`${process.cwd()}/.swcrc`);
const hasSwcJs = !hasSwcRc && fs.existsSync(`${process.cwd()}/swc.config.js`);
+ const hasSwcTs = !hasSwcRc && !hasSwcJs && fs.existsSync(`${process.cwd()}/swc.config.ts`);
- if (!hasSwcRc && !hasSwcJs) {
+ if (!hasSwcRc && !hasSwcJs && !hasSwcTs) {
return undefined;
}
- const swcFile = hasSwcJs ? 'swc.config.js' : '.swcrc';
+ const swcFile = hasSwcTs ? 'swc.config.ts' : hasSwcJs ? 'swc.config.js' : '.swcrc';
const config = getMeteorAppSwcrc(swcFile);
// Set baseUrl to process.cwd() if it exists
diff --git a/npm-packages/meteor-rspack/lib/test.js b/npm-packages/meteor-rspack/lib/test.js
index 3c2644b337..1780d114bb 100644
--- a/npm-packages/meteor-rspack/lib/test.js
+++ b/npm-packages/meteor-rspack/lib/test.js
@@ -2,6 +2,10 @@ const fs = require('fs');
const path = require('path');
const { createIgnoreRegex, createIgnoreGlobConfig } = require("./ignore.js");
+// Normalize a path to always use forward slashes (POSIX style).
+// Module identifiers in bundled JS must use '/' regardless of OS.
+const toPosix = (p) => p.replace(/\\/g, '/');
+
/**
* Generates eager test files dynamically
* @param {Object} options - Options for generating the test file
@@ -9,17 +13,20 @@ const { createIgnoreRegex, createIgnoreGlobConfig } = require("./ignore.js");
* @param {string} options.projectDir - The project directory
* @param {string} options.buildContext - The build context
* @param {string[]} options.ignoreEntries - Array of ignore patterns
+ * @param {string[]} options.meteorIgnoreEntries - Array of meteor ignore patterns
* @param {string} options.extraEntry - Extra entry to load
* @returns {string} The path to the generated file
*/
const generateEagerTestFile = ({
- isAppTest,
- projectDir,
- buildContext,
- ignoreEntries: inIgnoreEntries = [],
- prefix: inPrefix = '',
- extraEntry,
- }) => {
+ isAppTest,
+ projectDir,
+ buildContext,
+ ignoreEntries: inIgnoreEntries = [],
+ meteorIgnoreEntries: inMeteorIgnoreEntries = [],
+ prefix: inPrefix = '',
+ extraEntry,
+ globalImportPath,
+}) => {
const distDir = path.resolve(projectDir, ".meteor/local/test");
if (!fs.existsSync(distDir)) {
fs.mkdirSync(distDir, { recursive: true });
@@ -39,6 +46,11 @@ const generateEagerTestFile = ({
const excludeFoldersRegex = createIgnoreRegex(
createIgnoreGlobConfig(ignoreEntries)
);
+ console.log("inMeteorIgnoreEntries", inMeteorIgnoreEntries);
+ // Create regex from meteor ignore entries
+ const excludeMeteorIgnoreRegex = inMeteorIgnoreEntries.length > 0
+ ? createIgnoreRegex(createIgnoreGlobConfig(inMeteorIgnoreEntries))
+ : null;
const prefix = (inPrefix && `${inPrefix}-`) || "";
const filename = isAppTest
@@ -49,25 +61,39 @@ const generateEagerTestFile = ({
? "/\\.app-(?:test|spec)s?\\.[^.]+$/"
: "/\\.(?:test|spec)s?\\.[^.]+$/";
- const content = `{
- const ctx = import.meta.webpackContext('/', {
+ const content = `${
+ globalImportPath ? `import '${toPosix(globalImportPath)}';\n\n` : ""
+ }${
+ excludeMeteorIgnoreRegex
+ ? `const MeteorIgnoreRegex = ${excludeMeteorIgnoreRegex.toString()};`
+ : ""
+ }
+{
+ const ctx = import.meta.webpackContext('${toPosix(projectDir)}', {
recursive: true,
regExp: ${regExp},
exclude: ${excludeFoldersRegex.toString()},
mode: 'eager',
});
- ctx.keys().forEach(ctx);
+ ctx.keys().filter((k) => {
+ ${
+ excludeMeteorIgnoreRegex
+ ? `// Only exclude based on *relative* path segments.
+ return !MeteorIgnoreRegex.test(k);`
+ : "return true;"
+ }
+ }).forEach(ctx);
${
extraEntry
- ? `const extra = import.meta.webpackContext('${path.dirname(
- extraEntry
- )}', {
+ ? `const extra = import.meta.webpackContext('${toPosix(path.dirname(
+ extraEntry
+ ))}', {
recursive: false,
regExp: ${new RegExp(`${path.basename(extraEntry)}$`).toString()},
mode: 'eager',
});
extra.keys().forEach(extra);`
- : ''
+ : ""
}
}`;
diff --git a/npm-packages/meteor-rspack/package-lock.json b/npm-packages/meteor-rspack/package-lock.json
index cbe90a99f1..7792758d69 100644
--- a/npm-packages/meteor-rspack/package-lock.json
+++ b/npm-packages/meteor-rspack/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@meteorjs/rspack",
- "version": "1.0.1",
+ "version": "1.1.0-beta.31",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@meteorjs/rspack",
- "version": "1.0.1",
+ "version": "1.1.0-beta.31",
"license": "ISC",
"dependencies": {
"fast-deep-equal": "^3.1.3",
@@ -14,9 +14,6 @@
"node-polyfill-webpack-plugin": "^4.1.0",
"webpack-merge": "^6.0.1"
},
- "devDependencies": {
- "semver": "^7.7.4"
- },
"peerDependencies": {
"@rspack/cli": ">=1.3.0",
"@rspack/core": ">=1.3.0"
@@ -4210,19 +4207,6 @@
"node": ">=10"
}
},
- "node_modules/semver": {
- "version": "7.7.4",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
- "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/send": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
diff --git a/npm-packages/meteor-rspack/package.json b/npm-packages/meteor-rspack/package.json
index 12f5e629c4..2bcbb679c2 100644
--- a/npm-packages/meteor-rspack/package.json
+++ b/npm-packages/meteor-rspack/package.json
@@ -1,6 +1,6 @@
{
"name": "@meteorjs/rspack",
- "version": "1.0.1",
+ "version": "1.1.0-beta.31",
"description": "Configuration logic for using Rspack in Meteor projects",
"main": "index.js",
"type": "commonjs",
@@ -12,15 +12,8 @@
"node-polyfill-webpack-plugin": "^4.1.0",
"webpack-merge": "^6.0.1"
},
- "scripts": {
- "bump": "node ./scripts/bump-version.js",
- "publish:beta": "bash ./scripts/publish-beta.sh"
- },
"peerDependencies": {
"@rspack/cli": ">=1.3.0",
"@rspack/core": ">=1.3.0"
- },
- "devDependencies": {
- "semver": "^7.7.4"
}
}
diff --git a/npm-packages/meteor-rspack/plugins/MeteorRspackOutputPlugin.js b/npm-packages/meteor-rspack/plugins/MeteorRspackOutputPlugin.js
new file mode 100644
index 0000000000..a4494ae49e
--- /dev/null
+++ b/npm-packages/meteor-rspack/plugins/MeteorRspackOutputPlugin.js
@@ -0,0 +1,36 @@
+// MeteorRspackOutputPlugin.js
+//
+// This plugin outputs a JSON stringified with a tag delimiter each time
+// a new Rspack compilation happens. The JSON content is configurable
+// via plugin instantiation options.
+
+const { outputMeteorRspack } = require('../lib/meteorRspackHelpers');
+
+class MeteorRspackOutputPlugin {
+ constructor(options = {}) {
+ this.pluginName = 'MeteorRspackOutputPlugin';
+ this.options = options;
+ this.compilationCount = 0;
+ // The data to be output as JSON, can be a static object or a function
+ this.getData =
+ typeof options.getData === 'function'
+ ? options.getData
+ : () => options.data || {};
+ }
+
+ apply(compiler) {
+ // Hook into the 'done' event which fires after each compilation completes
+ compiler.hooks.done.tap(this.pluginName, stats => {
+ this.compilationCount++;
+ const data = {
+ ...(this.getData(stats, {
+ compilationCount: this.compilationCount,
+ isRebuild: this.compilationCount > 1,
+ }) || {}),
+ };
+ outputMeteorRspack(data);
+ });
+ }
+}
+
+module.exports = { MeteorRspackOutputPlugin };
diff --git a/npm-packages/meteor-rspack/plugins/RequireExtenalsPlugin.js b/npm-packages/meteor-rspack/plugins/RequireExtenalsPlugin.js
index fb400a4382..51cf0958fd 100644
--- a/npm-packages/meteor-rspack/plugins/RequireExtenalsPlugin.js
+++ b/npm-packages/meteor-rspack/plugins/RequireExtenalsPlugin.js
@@ -10,6 +10,10 @@
const fs = require('fs');
const path = require('path');
+// Normalize a path to always use forward slashes (POSIX style).
+// Module identifiers in bundled JS must use '/' regardless of OS.
+const toPosix = (p) => p.replace(/\\/g, '/');
+
class RequireExternalsPlugin {
constructor({
filePath,
@@ -46,7 +50,7 @@ class RequireExternalsPlugin {
// Prepare paths
this.filePath = path.resolve(process.cwd(), filePath);
this.backRoot = '../'.repeat(
- filePath.replace(/^\.?\/+/, '').split('/').length - 1
+ filePath.replace(/^\.?[/\\]+/, '').split(/[/\\]/).length - 1
);
// Initialize funcCount based on existing helpers in the file
@@ -96,14 +100,16 @@ class RequireExternalsPlugin {
pkg &&
(path.isAbsolute(pkg) ||
pkg.startsWith('./') ||
+ pkg.startsWith('.\\') ||
pkg.startsWith('../') ||
+ pkg.startsWith('..\\') ||
!!depInfo.ext)
) {
const module = this.externalsMeta.get(pkg);
if (module) {
- return `${this.backRoot}${module.relativeRequest}`;
+ return `${this.backRoot}${toPosix(module.relativeRequest)}`;
}
- return `${this.backRoot}${name}`;
+ return `${this.backRoot}${toPosix(name)}`;
}
return pkg;
@@ -132,7 +138,7 @@ class RequireExternalsPlugin {
this.externalsMeta.set(externalRequest, {
originalRequest: request,
externalRequest,
- relativeRequest: path.join(relContext, request),
+ relativeRequest: toPosix(path.join(relContext, request)),
});
// tell Rspack "don't bundle this, import it at runtime"
diff --git a/npm-packages/meteor-rspack/rspack.config.js b/npm-packages/meteor-rspack/rspack.config.js
index 09138424a7..ea1fa3da79 100644
--- a/npm-packages/meteor-rspack/rspack.config.js
+++ b/npm-packages/meteor-rspack/rspack.config.js
@@ -10,18 +10,22 @@ const { getMeteorAppSwcConfig } = require('./lib/swc.js');
const HtmlRspackPlugin = require('./plugins/HtmlRspackPlugin.js');
const { RequireExternalsPlugin } = require('./plugins/RequireExtenalsPlugin.js');
const { AssetExternalsPlugin } = require('./plugins/AssetExternalsPlugin.js');
+const { MeteorRspackOutputPlugin } = require('./plugins/MeteorRspackOutputPlugin.js');
const { generateEagerTestFile } = require("./lib/test.js");
const { getMeteorIgnoreEntries, createIgnoreGlobConfig } = require("./lib/ignore");
-const { mergeMeteorRspackFragments } = require("./lib/meteorRspackConfigFactory.js");
const {
compileWithMeteor,
compileWithRspack,
setCache,
splitVendorChunk,
extendSwcConfig,
+ replaceSwcConfig,
makeWebNodeBuiltinsAlias,
disablePlugins,
+ outputMeteorRspack,
+ enablePortableBuild,
} = require('./lib/meteorRspackHelpers.js');
+const { loadUserAndOverrideConfig } = require('./lib/meteorRspackConfigHelpers.js');
const { prepareMeteorRspackConfig } = require("./lib/meteorRspackConfigFactory");
// Safe require that doesn't throw if the module isn't found
@@ -40,7 +44,11 @@ function safeRequire(moduleName) {
}
// Persistent filesystem cache strategy
-function createCacheStrategy(mode, side, { projectConfigPath, configPath } = {}) {
+function createCacheStrategy(
+ mode,
+ side,
+ { projectConfigPath, configPath, buildContext } = {},
+) {
// Check for configuration files
const tsconfigPath = path.join(process.cwd(), 'tsconfig.json');
const hasTsconfig = fs.existsSync(tsconfigPath);
@@ -52,6 +60,8 @@ function createCacheStrategy(mode, side, { projectConfigPath, configPath } = {})
const hasSwcrcConfig = fs.existsSync(swcrcPath);
const swcJsPath = path.join(process.cwd(), 'swc.config.js');
const hasSwcJsConfig = fs.existsSync(swcJsPath);
+ const swcTsPath = path.join(process.cwd(), 'swc.config.ts');
+ const hasSwcTsConfig = fs.existsSync(swcTsPath);
const postcssConfigPath = path.join(process.cwd(), 'postcss.config.js');
const hasPostcssConfig = fs.existsSync(postcssConfigPath);
const packageLockPath = path.join(process.cwd(), 'package-lock.json');
@@ -68,6 +78,7 @@ function createCacheStrategy(mode, side, { projectConfigPath, configPath } = {})
...(hasBabelJsConfig ? [babelJsConfig] : []),
...(hasSwcrcConfig ? [swcrcPath] : []),
...(hasSwcJsConfig ? [swcJsPath] : []),
+ ...(hasSwcTsConfig ? [swcTsPath] : []),
...(hasPostcssConfig ? [postcssConfigPath] : []),
...(hasPackageLock ? [packageLockPath] : []),
...(hasYarnLock ? [yarnLockPath] : []),
@@ -81,7 +92,9 @@ function createCacheStrategy(mode, side, { projectConfigPath, configPath } = {})
type: "persistent",
storage: {
type: "filesystem",
- directory: `node_modules/.cache/rspack${(side && `/${side}`) || ""}`,
+ directory: `node_modules/.cache/rspack/${
+ [buildContext, side].filter(Boolean).join('-') || 'default'
+ }`,
},
...(buildDependencies.length > 0 && {
buildDependencies: buildDependencies,
@@ -204,15 +217,45 @@ module.exports = async function (inMeteor = {}, argv = {}) {
const Meteor = { ...inMeteor };
// Convert string boolean values to actual booleans
for (const key in Meteor) {
- if (Meteor[key] === 'true' || Meteor[key] === true) {
+ if (Meteor[key] === "true" || Meteor[key] === true) {
Meteor[key] = true;
- } else if (Meteor[key] === 'false' || Meteor[key] === false) {
+ } else if (Meteor[key] === "false" || Meteor[key] === false) {
Meteor[key] = false;
}
}
- const isProd = !!Meteor.isProduction || argv.mode === 'production';
- const isDev = !!Meteor.isDevelopment || !isProd;
+ const isTestLike = !!Meteor.isTestLike;
+ const swcExternalHelpers = !!Meteor.swcExternalHelpers;
+ const isNative = !!Meteor.isNative;
+ const devServerPort = Meteor.devServerPort || 8080;
+
+ const projectDir = process.cwd();
+ const projectConfigPath =
+ Meteor.projectConfigPath || path.resolve(projectDir, "rspack.config.js");
+
+ // Determine context for bundles and assets
+ const meteorLocalDirName = process.env.METEOR_LOCAL_DIR
+ ? path.basename(process.env.METEOR_LOCAL_DIR.replace(/\\/g, "/"))
+ : "";
+ const buildContext =
+ Meteor.buildContext ||
+ process.env.RSPACK_BUILD_CONTEXT ||
+ `_build${(meteorLocalDirName && `-${meteorLocalDirName}`) || ""}`;
+ const assetsContext =
+ Meteor.assetsContext ||
+ process.env.RSPACK_ASSETS_CONTEXT ||
+ `build-assets${(meteorLocalDirName && `-${meteorLocalDirName}`) || ""}`;
+ const chunksContext =
+ Meteor.chunksContext ||
+ process.env.RSPACK_CHUNKS_CONTEXT ||
+ `build-chunks${(meteorLocalDirName && `-${meteorLocalDirName}`) || ""}`;
+
+ // Compute build paths before loading user config (needed by Meteor helpers below)
+ const outputPath = Meteor.outputPath;
+ const outputDir = path.dirname(Meteor.outputPath || "");
+ Meteor.buildOutputDir = path.resolve(projectDir, buildContext, outputDir);
+
+ // Meteor flags derived purely from input; independent of loaded user/override configs
const isTest = !!Meteor.isTest;
const isClient = !!Meteor.isClient;
const isServer = !!Meteor.isServer;
@@ -222,16 +265,10 @@ module.exports = async function (inMeteor = {}, argv = {}) {
const isTestModule = !!Meteor.isTestModule;
const isTestEager = !!Meteor.isTestEager;
const isTestFullApp = !!Meteor.isTestFullApp;
- const isTestLike = !!Meteor.isTestLike;
- const swcExternalHelpers = !!Meteor.swcExternalHelpers;
- const isNative = !!Meteor.isNative;
- const mode = isProd ? 'production' : 'development';
- const projectDir = process.cwd();
- const projectConfigPath = Meteor.projectConfigPath || path.resolve(projectDir, 'rspack.config.js');
+ const isProfile = !!Meteor.isProfile;
+ const isVerbose = !!Meteor.isVerbose;
const configPath = Meteor.configPath;
const testEntry = Meteor.testEntry;
- const testClientEntry = Meteor.testClientEntry;
- const testServerEntry = Meteor.testServerEntry;
const isTypescriptEnabled = Meteor.isTypescriptEnabled || false;
const isJsxEnabled =
@@ -240,58 +277,69 @@ module.exports = async function (inMeteor = {}, argv = {}) {
Meteor.isTsxEnabled || (isTypescriptEnabled && isReactEnabled) || false;
const isBundleVisualizerEnabled = Meteor.isBundleVisualizerEnabled || false;
const isAngularEnabled = Meteor.isAngularEnabled || false;
+ const enableSwcExternalHelpers = !isServer && swcExternalHelpers;
- // Determine entry points
- const entryPath = Meteor.entryPath;
+ // Defined here so it can be called both before and after the first config load;
+ // without loaded configs it falls through to argv/Meteor flags.
+ const getModeFromConfig = (userConfig, overrideConfig) => {
+ if (overrideConfig?.mode) return overrideConfig.mode;
+ if (userConfig?.mode) return userConfig.mode;
+ if (argv.mode) return argv.mode;
+ if (Meteor.isProduction) return "production";
+ if (Meteor.isDevelopment) return "development";
+ return null;
+ };
- // Determine output points
- const outputPath = Meteor.outputPath;
- const outputDir = path.dirname(Meteor.outputPath || '');
+ // Initial mode before user/override configs are loaded
+ const initialCurrentMode = getModeFromConfig();
+ const initialIsProd = initialCurrentMode
+ ? initialCurrentMode === "production"
+ : !!Meteor.isProduction;
+ const initialIsDev = initialCurrentMode
+ ? initialCurrentMode === "development"
+ : !!Meteor.isDevelopment || !initialIsProd;
+ const initialMode = initialIsProd ? "production" : "development";
- const outputFilename = Meteor.outputFilename;
-
- // Determine run point
- const runPath = Meteor.runPath;
-
- // Determine banner
- const bannerOutput = JSON.parse(Meteor.bannerOutput || process.env.RSPACK_BANNER || '""');
-
- // Determine output directories
- const clientOutputDir = path.resolve(projectDir, 'public');
- const serverOutputDir = path.resolve(projectDir, 'private');
-
- // Determine context for bundles and assets
- const buildContext = Meteor.buildContext || '_build';
- const assetsContext = Meteor.assetsContext || 'build-assets';
- const chunksContext = Meteor.chunksContext || 'build-chunks';
-
- // Determine build output and pass to Meteor
- const buildOutputDir = path.resolve(projectDir, buildContext, outputDir);
- Meteor.buildOutputDir = buildOutputDir;
-
- const cacheStrategy = createCacheStrategy(
- mode,
- (Meteor.isClient && 'client') || 'server',
- { projectConfigPath, configPath }
+ // Initialized with pre-load values so helpers work during the first config load;
+ // reassigned after load once mode is fully resolved.
+ let cacheStrategy = createCacheStrategy(
+ initialMode,
+ (Meteor.isClient && "client") || "server",
+ { projectConfigPath, configPath, buildContext }
);
+ let swcConfigRule = createSwcConfig({
+ isTypescriptEnabled,
+ isReactEnabled,
+ isJsxEnabled,
+ isTsxEnabled,
+ externalHelpers: enableSwcExternalHelpers,
+ isDevEnvironment: isRun && initialIsDev && !isTest && !isNative,
+ isClient,
+ isAngularEnabled,
+ });
+ Meteor.swcConfigOptions = swcConfigRule.options;
// Expose Meteor's helpers to expand Rspack configs
- Meteor.compileWithMeteor = deps => compileWithMeteor(deps);
+ Meteor.compileWithMeteor = (deps) => compileWithMeteor(deps);
Meteor.compileWithRspack = (deps, options = {}) =>
compileWithRspack(deps, {
options: mergeSplitOverlap(Meteor.swcConfigOptions, options),
});
- Meteor.setCache = enabled =>
- setCache(
- !!enabled,
- enabled === 'memory' ? undefined : cacheStrategy
- );
+ Meteor.setCache = (enabled) =>
+ setCache(!!enabled, enabled === "memory" ? undefined : cacheStrategy);
Meteor.splitVendorChunk = () => splitVendorChunk();
- Meteor.extendSwcConfig = (customSwcConfig) => extendSwcConfig(customSwcConfig);
+ Meteor.extendSwcConfig = (customSwcConfig) =>
+ extendSwcConfig(
+ mergeSplitOverlap(Meteor.swcConfigOptions, customSwcConfig)
+ );
+ Meteor.replaceSwcConfig = (customSwcConfig) =>
+ replaceSwcConfig(customSwcConfig);
Meteor.extendConfig = (...configs) => mergeSplitOverlap(...configs);
- Meteor.disablePlugins = matchers => prepareMeteorRspackConfig({
- disablePlugins: matchers,
- });
+ Meteor.disablePlugins = (matchers) =>
+ prepareMeteorRspackConfig({
+ disablePlugins: matchers,
+ });
+ Meteor.enablePortableBuild = () => enablePortableBuild();
// Add HtmlRspackPlugin function to Meteor
Meteor.HtmlRspackPlugin = (options = {}) => {
@@ -315,6 +363,51 @@ module.exports = async function (inMeteor = {}, argv = {}) {
});
};
+ // First pass: resolve user/override configs early so mode overrides (e.g. "production")
+ // are available before computing isProd/isDev and the rest of the build flags.
+ // Skipped for Angular since it manages its own mode via the second pass.
+ let { nextUserConfig, nextOverrideConfig } = isAngularEnabled
+ ? {}
+ : await loadUserAndOverrideConfig(projectConfigPath, Meteor, argv);
+
+ // Determine the final mode with loaded configs
+ const currentMode = getModeFromConfig(nextUserConfig, nextOverrideConfig);
+ const isProd = currentMode
+ ? currentMode === "production"
+ : !!Meteor.isProduction;
+ const isDev = currentMode
+ ? currentMode === "development"
+ : !!Meteor.isDevelopment || !isProd;
+ const mode = isProd ? "production" : "development";
+ const isPortableBuild = !!(
+ nextUserConfig?.["meteor.enablePortableBuild"] ||
+ nextOverrideConfig?.["meteor.enablePortableBuild"]
+ );
+
+ // Determine entry points
+ const entryPath = Meteor.entryPath || "";
+
+ // Determine output points
+ const outputFilename = Meteor.outputFilename;
+
+ cacheStrategy = createCacheStrategy(
+ mode,
+ (Meteor.isClient && "client") || "server",
+ { projectConfigPath, configPath }
+ );
+
+ // Determine run point
+ const runPath = Meteor.runPath || "";
+
+ // Determine banner
+ const bannerOutput = JSON.parse(
+ Meteor.bannerOutput || process.env.RSPACK_BANNER || '""'
+ );
+
+ // Determine output directories
+ const clientOutputDir = path.resolve(projectDir, "public");
+ const serverOutputDir = path.resolve(projectDir, "private");
+
// Get Meteor ignore entries
const meteorIgnoreEntries = getMeteorIgnoreEntries(projectDir);
@@ -330,21 +423,17 @@ module.exports = async function (inMeteor = {}, argv = {}) {
// Set default watch options
const watchOptions = {
ignored: [
- ...createIgnoreGlobConfig([
- ...meteorIgnoreEntries,
- ...additionalEntries,
- ]),
+ ...createIgnoreGlobConfig([...meteorIgnoreEntries, ...additionalEntries]),
],
};
if (Meteor.isDebug || Meteor.isVerbose) {
- console.log('[i] Rspack mode:', mode);
- console.log('[i] Meteor flags:', Meteor);
+ console.log("[i] Rspack mode:", mode);
+ console.log("[i] Meteor flags:", Meteor);
}
- const enableSwcExternalHelpers = !isServer && swcExternalHelpers;
const isDevEnvironment = isRun && isDev && !isTest && !isNative;
- const swcConfigRule = createSwcConfig({
+ swcConfigRule = createSwcConfig({
isTypescriptEnabled,
isReactEnabled,
isJsxEnabled,
@@ -354,7 +443,6 @@ module.exports = async function (inMeteor = {}, argv = {}) {
isClient,
isAngularEnabled,
});
- // Expose swc config to use in custom configs
Meteor.swcConfigOptions = swcConfigRule.options;
const externals = [
@@ -363,34 +451,34 @@ module.exports = async function (inMeteor = {}, argv = {}) {
...(isServer ? [/^bcrypt$/] : []),
];
const alias = {
- '/': path.resolve(process.cwd()),
+ "/": path.resolve(process.cwd()),
};
const fallback = {
...(isClient && makeWebNodeBuiltinsAlias()),
};
const extensions = [
- '.ts',
- '.tsx',
- '.mts',
- '.cts',
- '.js',
- '.jsx',
- '.mjs',
- '.cjs',
- '.json',
- '.wasm',
+ ".ts",
+ ".tsx",
+ ".mts",
+ ".cts",
+ ".js",
+ ".jsx",
+ ".mjs",
+ ".cjs",
+ ".json",
+ ".wasm",
];
const extraRules = [];
const reactRefreshModule = isReactEnabled
- ? safeRequire('@rspack/plugin-react-refresh')
+ ? safeRequire("@rspack/plugin-react-refresh")
: null;
const requireExternalsPlugin = new RequireExternalsPlugin({
filePath: path.join(buildContext, runPath),
...(Meteor.isBlazeEnabled && {
externals: /\.html$/,
- isEagerImport: module => module.endsWith('.html'),
+ isEagerImport: (module) => module.endsWith(".html"),
...(isProd && {
lastImports: [`./${outputFilename}`],
}),
@@ -400,25 +488,26 @@ module.exports = async function (inMeteor = {}, argv = {}) {
// Handle assets
const assetExternalsPlugin = new AssetExternalsPlugin();
- const assetModuleFilename = _fileInfo => {
+ const assetModuleFilename = (_fileInfo) => {
const filename = _fileInfo.filename;
- const isPublic = filename.startsWith('/') || filename.startsWith('public');
+ const isPublic = filename.startsWith("/") || filename.startsWith("public");
if (isPublic) return `[name][ext][query]`;
return `${assetsContext}/[hash][ext][query]`;
};
const rsdoctorModule = isBundleVisualizerEnabled
- ? safeRequire('@rsdoctor/rspack-plugin')
+ ? safeRequire("@rsdoctor/rspack-plugin")
: null;
- const doctorPluginConfig = isRun && isBundleVisualizerEnabled && rsdoctorModule?.RsdoctorRspackPlugin
- ? [
- new rsdoctorModule.RsdoctorRspackPlugin({
- port: isClient
- ? (parseInt(Meteor.rsdoctorClientPort || '8888', 10))
- : (parseInt(Meteor.rsdoctorServerPort || '8889', 10)),
- }),
- ]
- : [];
+ const doctorPluginConfig =
+ isRun && isBundleVisualizerEnabled && rsdoctorModule?.RsdoctorRspackPlugin
+ ? [
+ new rsdoctorModule.RsdoctorRspackPlugin({
+ port: isClient
+ ? parseInt(Meteor.rsdoctorClientPort || "8888", 10)
+ : parseInt(Meteor.rsdoctorServerPort || "8889", 10),
+ }),
+ ]
+ : [];
const bannerPluginConfig = !isBuild
? [
new BannerPlugin({
@@ -429,36 +518,42 @@ module.exports = async function (inMeteor = {}, argv = {}) {
: [];
// Not supported in Meteor yet (Rspack 1.7+ is enabled by default)
const lazyCompilationConfig = { lazyCompilation: false };
+ const shouldLogVerbose = isProfile || isVerbose;
+ const loggingConfig = shouldLogVerbose
+ ? {}
+ : { stats: "errors-warnings", infrastructureLogging: { level: "warn" } };
const clientEntry =
- isTest && isTestEager && isTestFullApp
+ isClient && isTest && isTestEager && isTestFullApp
? generateEagerTestFile({
isAppTest: true,
projectDir,
buildContext,
- ignoreEntries: [...meteorIgnoreEntries, "**/server/**"],
+ ignoreEntries: ["**/server/**"],
+ meteorIgnoreEntries,
prefix: "client",
extraEntry: path.resolve(process.cwd(), Meteor.mainClientEntry),
+ globalImportPath: path.resolve(projectDir, buildContext, entryPath),
})
- : isTest && isTestEager
+ : isClient && isTest && isTestEager
? generateEagerTestFile({
isAppTest: false,
isClient: true,
projectDir,
buildContext,
- ignoreEntries: [...meteorIgnoreEntries, "**/server/**"],
+ ignoreEntries: ["**/server/**"],
+ meteorIgnoreEntries,
prefix: "client",
+ globalImportPath: path.resolve(projectDir, buildContext, entryPath),
})
- : isTest && testEntry
+ : isClient && isTest && testEntry
? path.resolve(process.cwd(), testEntry)
- : isTest && testClientEntry
- ? path.resolve(process.cwd(), testClientEntry)
: path.resolve(process.cwd(), buildContext, entryPath);
- const clientNameConfig = `[${(isTest && 'test-') || ''}client-rspack]`;
+ const clientNameConfig = `[${(isTest && "test-") || ""}client-rspack]`;
// Base client config
let clientConfig = {
name: clientNameConfig,
- target: 'web',
+ target: "web",
mode,
entry: clientEntry,
output: {
@@ -467,7 +562,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
const chunkName = _module.chunk?.name;
const isMainChunk = !chunkName || chunkName === "main";
const chunkSuffix = `${chunksContext}/[id]${
- isProd ? '.[chunkhash]' : ''
+ isProd ? ".[chunkhash]" : ""
}.js`;
if (isDevEnvironment) {
if (isMainChunk) return outputFilename;
@@ -476,21 +571,21 @@ module.exports = async function (inMeteor = {}, argv = {}) {
if (isMainChunk) return `../${buildContext}/${outputPath}`;
return chunkSuffix;
},
- libraryTarget: 'commonjs2',
- publicPath: '/',
- chunkFilename: `${chunksContext}/[id]${isProd ? '.[chunkhash]' : ''}.js`,
+ libraryTarget: "commonjs2",
+ publicPath: "/",
+ chunkFilename: `${chunksContext}/[id]${isProd ? ".[chunkhash]" : ""}.js`,
assetModuleFilename,
cssFilename: `${chunksContext}/[name]${
- isProd ? '.[contenthash]' : ''
+ isProd ? ".[contenthash]" : ""
}.css`,
cssChunkFilename: `${chunksContext}/[id]${
- isProd ? '.[contenthash]' : ''
+ isProd ? ".[contenthash]" : ""
}.css`,
...(isProd && { clean: { keep: keepOutsideBuild() } }),
},
optimization: {
usedExports: true,
- splitChunks: { chunks: 'async' },
+ splitChunks: { chunks: "async" },
},
module: {
rules: [
@@ -499,7 +594,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
? [
{
test: /\.html$/i,
- loader: 'ignore-loader',
+ loader: "ignore-loader",
},
]
: []),
@@ -517,74 +612,90 @@ module.exports = async function (inMeteor = {}, argv = {}) {
assetExternalsPlugin,
].filter(Boolean),
new DefinePlugin({
- 'Meteor.isClient': JSON.stringify(true),
- 'Meteor.isServer': JSON.stringify(false),
- 'Meteor.isTest': JSON.stringify(isTestLike && !isTestFullApp),
- 'Meteor.isAppTest': JSON.stringify(isTestLike && isTestFullApp),
- 'Meteor.isDevelopment': JSON.stringify(isDev),
- 'Meteor.isProduction': JSON.stringify(isProd),
+ "Meteor.isClient": JSON.stringify(true),
+ "Meteor.isServer": JSON.stringify(false),
+ "Meteor.isTest": JSON.stringify(isTestLike && !isTestFullApp),
+ "Meteor.isAppTest": JSON.stringify(isTestLike && isTestFullApp),
+ ...(!isPortableBuild && {
+ "Meteor.isDevelopment": JSON.stringify(isDev),
+ "Meteor.isProduction": JSON.stringify(isProd),
+ }),
}),
...bannerPluginConfig,
Meteor.HtmlRspackPlugin(),
...doctorPluginConfig,
new NormalModuleReplacementPlugin(/^node:(.*)$/, (res) => {
- res.request = res.request.replace(/^node:/, '');
+ res.request = res.request.replace(/^node:/, "");
}),
],
watchOptions,
- devtool: isDevEnvironment || isNative || isTest ? 'source-map' : 'hidden-source-map',
+ devtool:
+ isDevEnvironment || isNative || isTest
+ ? "source-map"
+ : "hidden-source-map",
...(isDevEnvironment && {
devServer: {
...createRemoteDevServerConfig(),
- static: { directory: clientOutputDir, publicPath: '/__rspack__/' },
+ static: { directory: clientOutputDir, publicPath: "/__rspack__/" },
hot: true,
liveReload: true,
...(Meteor.isBlazeEnabled && { hot: false }),
- port: Meteor.devServerPort || 8080,
+ port: devServerPort,
devMiddleware: {
- writeToDisk: filePath =>
- /\.(html)$/.test(filePath) && !filePath.includes('.hot-update.'),
+ writeToDisk: (filePath) =>
+ /\.(html)$/.test(filePath) && !filePath.includes(".hot-update."),
+ },
+ onListening(devServer) {
+ if (!devServer) return;
+ const { host, port } = devServer.options;
+ const protocol =
+ devServer.options.server?.type === "https" ? "https" : "http";
+ const devServerUrl = `${protocol}://${host || "localhost"}:${port}`;
+ outputMeteorRspack({ devServerUrl });
},
},
}),
...merge(cacheStrategy, { experiments: { css: true } }),
...lazyCompilationConfig,
+ ...loggingConfig,
};
const serverEntry =
- isTest && isTestEager && isTestFullApp
+ isServer && isTest && isTestEager && isTestFullApp
? generateEagerTestFile({
isAppTest: true,
projectDir,
buildContext,
- ignoreEntries: [...meteorIgnoreEntries, "**/client/**"],
+ ignoreEntries: ["**/client/**"],
+ meteorIgnoreEntries,
prefix: "server",
+ globalImportPath: path.resolve(projectDir, buildContext, entryPath),
})
- : isTest && isTestEager
+ : isServer && isTest && isTestEager
? generateEagerTestFile({
isAppTest: false,
projectDir,
buildContext,
- ignoreEntries: [...meteorIgnoreEntries, "**/client/**"],
+ ignoreEntries: ["**/client/**"],
+ meteorIgnoreEntries,
prefix: "server",
+ globalImportPath: path.resolve(projectDir, buildContext, entryPath),
})
- : isTest && testEntry
+ : isServer && isTest && testEntry
? path.resolve(process.cwd(), testEntry)
- : isTest && testServerEntry
- ? path.resolve(process.cwd(), testServerEntry)
: path.resolve(projectDir, buildContext, entryPath);
- const serverNameConfig = `[${(isTest && 'test-') || ''}server-rspack]`;
+ const serverNameConfig = `[${(isTest && "test-") || ""}server-rspack]`;
// Base server config
let serverConfig = {
name: serverNameConfig,
- target: 'node',
+ target: "node",
mode,
entry: serverEntry,
output: {
path: serverOutputDir,
filename: () => `../${buildContext}/${outputPath}`,
- libraryTarget: 'commonjs2',
- chunkFilename: `${chunksContext}/[id]${isProd ? '.[chunkhash]' : ''}.js`,
+ libraryTarget: "commonjs2",
+ chunkFilename: `${chunksContext}/[id]${isProd ? ".[chunkhash]" : ""}.js`,
assetModuleFilename,
...(isProd && { clean: { keep: keepOutsideBuild() } }),
},
@@ -598,15 +709,15 @@ module.exports = async function (inMeteor = {}, argv = {}) {
parser: {
javascript: {
// Dynamic imports on the server are treated as bundled in the same chunk
- dynamicImportMode: 'eager',
+ dynamicImportMode: "eager",
},
},
},
resolve: {
extensions,
alias,
- modules: ['node_modules', path.resolve(projectDir)],
- conditionNames: ['import', 'require', 'node', 'default'],
+ modules: ["node_modules", path.resolve(projectDir)],
+ conditionNames: ["import", "require", "node", "default"],
},
externals,
externalsPresets: { node: true },
@@ -614,18 +725,22 @@ module.exports = async function (inMeteor = {}, argv = {}) {
new DefinePlugin(
isTest && (isTestModule || isTestEager)
? {
- 'Meteor.isTest': JSON.stringify(isTest && !isTestFullApp),
- 'Meteor.isAppTest': JSON.stringify(isTest && isTestFullApp),
- 'Meteor.isDevelopment': JSON.stringify(isDev),
+ "Meteor.isTest": JSON.stringify(isTest && !isTestFullApp),
+ "Meteor.isAppTest": JSON.stringify(isTest && isTestFullApp),
+ ...(!isPortableBuild && {
+ "Meteor.isDevelopment": JSON.stringify(isDev),
+ }),
}
: {
- 'Meteor.isClient': JSON.stringify(false),
- 'Meteor.isServer': JSON.stringify(true),
- 'Meteor.isTest': JSON.stringify(isTestLike && !isTestFullApp),
- 'Meteor.isAppTest': JSON.stringify(isTestLike && isTestFullApp),
- 'Meteor.isDevelopment': JSON.stringify(isDev),
- 'Meteor.isProduction': JSON.stringify(isProd),
- },
+ "Meteor.isClient": JSON.stringify(false),
+ "Meteor.isServer": JSON.stringify(true),
+ "Meteor.isTest": JSON.stringify(isTestLike && !isTestFullApp),
+ "Meteor.isAppTest": JSON.stringify(isTestLike && isTestFullApp),
+ ...(!isPortableBuild && {
+ "Meteor.isDevelopment": JSON.stringify(isDev),
+ "Meteor.isProduction": JSON.stringify(isProd),
+ }),
+ }
),
...bannerPluginConfig,
requireExternalsPlugin,
@@ -633,104 +748,21 @@ module.exports = async function (inMeteor = {}, argv = {}) {
...doctorPluginConfig,
],
watchOptions,
- devtool: isDevEnvironment || isNative || isTest ? 'source-map' : 'hidden-source-map',
+ devtool:
+ isDevEnvironment || isNative || isTest
+ ? "source-map"
+ : "hidden-source-map",
...((isDevEnvironment || (isTest && !isTestEager) || isNative) &&
cacheStrategy),
...lazyCompilationConfig,
+ ...loggingConfig,
};
- // Helper function to load and process config files
- async function loadAndProcessConfig(configPath, configType, Meteor, argv, isAngularEnabled) {
- try {
- // Load the config file
- let config;
- if (path.extname(configPath) === '.mjs') {
- // For ESM modules, we need to use dynamic import
- const fileUrl = `file://${configPath}`;
- const module = await import(fileUrl);
- config = module.default || module;
- } else {
- // For CommonJS modules, we can use require
- config = require(configPath)?.default || require(configPath);
- }
-
- // Process the config
- const rawConfig = typeof config === 'function' ? config(Meteor, argv) : config;
- const resolvedConfig = await Promise.resolve(rawConfig);
- const userConfig = resolvedConfig && '0' in resolvedConfig ? resolvedConfig[0] : resolvedConfig;
-
- // Define omitted paths and warning function
- const omitPaths = [
- "name",
- "target",
- "entry",
- "output.path",
- "output.filename",
- ...(Meteor.isServer ? ["optimization.splitChunks", "optimization.runtimeChunk"] : []),
- ].filter(Boolean);
-
- const warningFn = path => {
- if (isAngularEnabled) return;
- console.warn(
- `[${configType}] Ignored custom "${path}" — reserved for Meteor-Rspack integration.`,
- );
- };
-
- // Clean omitted paths and merge Meteor Rspack fragments
- let nextConfig = cleanOmittedPaths(userConfig, {
- omitPaths,
- warningFn,
- });
- nextConfig = mergeMeteorRspackFragments(nextConfig);
-
- return nextConfig;
- } catch (error) {
- console.error(`Error loading ${configType} from ${configPath}:`, error);
- if (configType === 'rspack.config.js') {
- throw error; // Only rethrow for project config
- }
- return null;
- }
- }
-
- // Load and apply project-level overrides for the selected build
- // Check if we're in a Meteor package directory by looking at the path
- const isMeteorPackageConfig = projectDir.includes('/packages/rspack');
- if (fs.existsSync(projectConfigPath) && !isMeteorPackageConfig) {
- // Check if there's a .mjs or .cjs version of the config file
- const mjsConfigPath = projectConfigPath.replace(/\.js$/, '.mjs');
- const cjsConfigPath = projectConfigPath.replace(/\.js$/, '.cjs');
-
- let projectConfigPathToUse = projectConfigPath;
- if (fs.existsSync(mjsConfigPath)) {
- projectConfigPathToUse = mjsConfigPath;
- } else if (fs.existsSync(cjsConfigPath)) {
- projectConfigPathToUse = cjsConfigPath;
- }
-
- const nextUserConfig = await loadAndProcessConfig(
- projectConfigPathToUse,
- 'rspack.config.js',
- Meteor,
- argv,
- isAngularEnabled
- );
-
- if (nextUserConfig) {
- if (Meteor.isClient) {
- clientConfig = mergeSplitOverlap(clientConfig, nextUserConfig);
- }
- if (Meteor.isServer) {
- serverConfig = mergeSplitOverlap(serverConfig, nextUserConfig);
- }
- }
- }
-
// Establish Angular overrides to ensure proper integration
const angularExpandConfig = isAngularEnabled
? {
mode: isProd ? "production" : "development",
- devServer: { port: Meteor.devServerPort },
+ devServer: { port: devServerPort },
stats: { preset: "normal" },
infrastructureLogging: { level: "info" },
...(isProd && isClient && { output: { module: false } }),
@@ -757,34 +789,31 @@ module.exports = async function (inMeteor = {}, argv = {}) {
}
: {};
- let config = mergeSplitOverlap(
- isClient ? clientConfig : serverConfig,
- angularExpandConfig
- );
+ // Second pass: re-run only when a mode override was detected, so the user config
+ // can depend on fully-computed Meteor flags and helpers (swcConfigOptions, buildOutputDir, etc.).
+ if (nextUserConfig?.mode || nextOverrideConfig?.mode || isAngularEnabled) {
+ ({ nextUserConfig, nextOverrideConfig } = await loadUserAndOverrideConfig(
+ projectConfigPath,
+ Meteor,
+ argv
+ ));
+ }
+ let statsOverrided = false;
+ let config = isClient ? clientConfig : serverConfig;
+ if (nextUserConfig) {
+ config = mergeSplitOverlap(config, nextUserConfig);
+ if (nextUserConfig.stats != null) {
+ statsOverrided = true;
+ }
+ }
+
+ config = mergeSplitOverlap(config, angularExpandConfig);
config = mergeSplitOverlap(config, testClientExpandConfig);
- // Check for override config file (extra file to override everything)
- if (projectConfigPath) {
- const configDir = path.dirname(projectConfigPath);
- const configFileName = path.basename(projectConfigPath);
- const configExt = path.extname(configFileName);
- const configNameWithoutExt = configFileName.replace(configExt, '');
- const configNameFull = `${configNameWithoutExt}.override${configExt}`;
- const overrideConfigPath = path.join(configDir, configNameFull);
-
- if (fs.existsSync(overrideConfigPath)) {
- const nextOverrideConfig = await loadAndProcessConfig(
- overrideConfigPath,
- configNameFull,
- Meteor,
- argv,
- isAngularEnabled
- );
-
- if (nextOverrideConfig) {
- // Apply override config as the last step
- config = mergeSplitOverlap(config, nextOverrideConfig);
- }
+ if (nextOverrideConfig) {
+ config = mergeSplitOverlap(config, nextOverrideConfig);
+ if (nextOverrideConfig.stats != null) {
+ statsOverrided = true;
}
}
@@ -794,18 +823,38 @@ module.exports = async function (inMeteor = {}, argv = {}) {
delete config.disablePlugins;
}
+ delete config["meteor.enablePortableBuild"];
+
if (Meteor.isDebug || Meteor.isVerbose) {
- console.log('Config:', inspect(config, { depth: null, colors: true }));
+ console.log("Config:", inspect(config, { depth: null, colors: true }));
}
// Check if lazyCompilation is enabled and warn the user
- if (config.lazyCompilation === true || typeof config.lazyCompilation === 'object') {
+ if (
+ config.lazyCompilation === true ||
+ typeof config.lazyCompilation === "object"
+ ) {
console.warn(
- '\n⚠️ Warning: lazyCompilation may not work correctly in the current Meteor-Rspack integration.\n' +
- ' This feature will be evaluated for support in future Meteor versions.\n' +
- ' If you encounter any issues, please disable it in your rspack config.\n',
+ "\n⚠️ Warning: lazyCompilation may not work correctly in the current Meteor-Rspack integration.\n" +
+ " This feature will be evaluated for support in future Meteor versions.\n" +
+ " If you encounter any issues, please disable it in your rspack config.\n"
);
}
+ // Add MeteorRspackOutputPlugin as the last plugin to output compilation info
+ const meteorRspackOutputPlugin = new MeteorRspackOutputPlugin({
+ getData: (stats, { isRebuild, compilationCount }) => ({
+ name: config.name,
+ mode: config.mode,
+ hasErrors: stats.hasErrors(),
+ hasWarnings: stats.hasWarnings(),
+ timestamp: Date.now(),
+ statsOverrided,
+ compilationCount,
+ isRebuild,
+ }),
+ });
+ config.plugins = [meteorRspackOutputPlugin, ...(config.plugins || [])];
+
return [config];
}
diff --git a/package.json b/package.json
index 9c096fcd0a..6508b0c3ef 100644
--- a/package.json
+++ b/package.json
@@ -37,9 +37,10 @@
"scripts": {
"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",
- "install:e2e": "cd tools/modern-tests && npm install && npx playwright install --with-deps chromium chromium-headless-shell",
- "test:e2e": "cd tools/modern-tests && npm test -- "
+ "install:e2e": "cd tools/e2e-tests && npm install && npx playwright install --with-deps chromium chromium-headless-shell",
+ "test:e2e": "cd tools/e2e-tests && npm test -- ",
+ "create-app:e2e": "cd tools/e2e-tests && node scripts/create-app.js",
+ "test:idle-bot": "node --test .github/scripts/__tests__/inactive-issues.test.js"
},
"jshintConfig": {
"esversion": 11
diff --git a/packages/accounts-2fa/2fa-server.js b/packages/accounts-2fa/2fa-server.js
index 801233d294..673ce77c15 100644
--- a/packages/accounts-2fa/2fa-server.js
+++ b/packages/accounts-2fa/2fa-server.js
@@ -123,7 +123,7 @@ Meteor.methods({
);
},
async has2faEnabled() {
- return await Accounts._is2faEnabledForUser();
+ return Accounts._is2faEnabledForUser();
},
});
diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js
index e8a884c87b..cd4e816460 100644
--- a/packages/accounts-base/accounts_server.js
+++ b/packages/accounts-base/accounts_server.js
@@ -560,7 +560,7 @@ export class AccountsServer extends AccountsCommon {
type,
fn
) {
- return await this._attemptLogin(
+ return this._attemptLogin(
methodInvocation,
methodName,
methodArgs,
@@ -707,7 +707,7 @@ export class AccountsServer extends AccountsCommon {
const result = await accounts._runLoginHandlers(this, options);
//console.log({result});
- return await accounts._attemptLogin(this, "login", arguments, result);
+ return accounts._attemptLogin(this, "login", arguments, result);
};
methods.logout = async function () {
@@ -760,7 +760,7 @@ export class AccountsServer extends AccountsCommon {
const newStampedToken = accounts._generateStampedLoginToken();
newStampedToken.when = currentStampedToken.when;
await accounts._insertLoginToken(this.userId, newStampedToken);
- return await accounts._loginUser(this, this.userId, newStampedToken);
+ return accounts._loginUser(this, this.userId, newStampedToken);
};
// Removes all tokens except the token associated with the current
diff --git a/packages/accounts-base/accounts_tests_setup.js b/packages/accounts-base/accounts_tests_setup.js
index 0e211828c0..7f8cf2a40d 100644
--- a/packages/accounts-base/accounts_tests_setup.js
+++ b/packages/accounts-base/accounts_tests_setup.js
@@ -32,7 +32,7 @@ Meteor.methods({
},
}
);
- return await getTokenFromSecret({ selector, secret });
+ return getTokenFromSecret({ selector, secret });
},
getTokenFromSecret,
});
diff --git a/packages/accounts-password/email_tests_setup.js b/packages/accounts-password/email_tests_setup.js
index fe393fb663..577f4555fb 100644
--- a/packages/accounts-password/email_tests_setup.js
+++ b/packages/accounts-password/email_tests_setup.js
@@ -55,7 +55,7 @@ Meteor.methods(
check(email, String);
const userId = await Accounts.createUser({ email });
await Accounts.sendEnrollmentEmail(userId);
- return await Meteor.users.findOneAsync(userId);
+ return Meteor.users.findOneAsync(userId);
}
}
);
diff --git a/packages/accounts-password/password_server.js b/packages/accounts-password/password_server.js
index cb7cb96112..711c163fe6 100644
--- a/packages/accounts-password/password_server.js
+++ b/packages/accounts-password/password_server.js
@@ -290,7 +290,7 @@ Accounts._checkPasswordAsync = checkPasswordAsync;
const passwordValidator = Match.OneOf(
- Match.Where(str => Match.test(str, String) && str.length <= Meteor.settings?.packages?.accounts?.passwordMaxLength || 256), {
+ Match.Where(str => Match.test(str, String) && str.length <= (Meteor.settings?.packages?.accounts?.passwordMaxLength || 256)), {
digest: Match.Where(str => Match.test(str, String) && str.length === 64),
algorithm: Match.OneOf('sha-256')
}
@@ -472,7 +472,7 @@ Meteor.methods(
Accounts.setPasswordAsync =
async (userId, newPlaintextPassword, options) => {
check(userId, String);
- check(newPlaintextPassword, Match.Where(str => Match.test(str, String) && str.length <= Meteor.settings?.packages?.accounts?.passwordMaxLength || 256));
+ check(newPlaintextPassword, Match.Where(str => Match.test(str, String) && str.length <= (Meteor.settings?.packages?.accounts?.passwordMaxLength || 256)));
check(options, Match.Maybe({ logout: Boolean }));
options = { logout: true, ...options };
@@ -507,6 +507,7 @@ Meteor.methods({forgotPassword: async options => {
const user = await Accounts.findUserByEmail(options.email, { fields: { emails: 1 } });
if (!user) {
+ if (Accounts._options.ambiguousErrorMessages) return;
Accounts._handleError("User not found");
}
diff --git a/packages/accounts-password/password_tests.js b/packages/accounts-password/password_tests.js
index 49f94544a0..f409516c59 100644
--- a/packages/accounts-password/password_tests.js
+++ b/packages/accounts-password/password_tests.js
@@ -1,4 +1,6 @@
Accounts._connectionCloseDelayMsForTests = 1000;
+Accounts._options.ambiguousErrorMessages = false;
+
const makeTestConnAsync =
(test) =>
new Promise((resolve, reject) => {
@@ -1415,9 +1417,8 @@ if (Meteor.isServer) (() => {
);
Accounts._options.ambiguousErrorMessages = true;
- await test.throwsAsync(
- async () => await Meteor.callAsync('forgotPassword', wrongOptions),
- 'Something went wrong. Please check your credentials'
+ await test.doesNotThrowsAsync(
+ async () => await Meteor.callAsync("forgotPassword", wrongOptions)
);
Accounts._options.ambiguousErrorMessages = false;
diff --git a/packages/accounts-passwordless/server_tests.js b/packages/accounts-passwordless/server_tests.js
index 768023b952..9855e1c020 100644
--- a/packages/accounts-passwordless/server_tests.js
+++ b/packages/accounts-passwordless/server_tests.js
@@ -28,6 +28,9 @@ const getData = ({ createdAt }) => {
};
Tinytest.add('passwordless - time expired', test => {
+ // The test suite for accounts-passwordless includes testing whether it gets the right error messages from the server.
+ // So, we need this disabled, otherwise those tests incorrectly fail when you run them.
+ Accounts._options.ambiguousErrorMessages = false;
const createdAt = new Date('July 17, 2022 13:00:00');
const currentDate = new Date('July 17, 2022 14:01:00');
diff --git a/packages/babel-compiler/babel-compiler.js b/packages/babel-compiler/babel-compiler.js
index 0de0637df2..2fdb836de0 100644
--- a/packages/babel-compiler/babel-compiler.js
+++ b/packages/babel-compiler/babel-compiler.js
@@ -101,17 +101,18 @@ let lastModifiedSwcConfigTime;
BCp.initializeMeteorAppSwcrc = function () {
const hasSwcRc = fs.existsSync(`${getMeteorAppDir()}/.swcrc`);
const hasSwcJs = !hasSwcRc && fs.existsSync(`${getMeteorAppDir()}/swc.config.js`);
- if (!lastModifiedSwcConfig && !hasSwcRc && !hasSwcJs) {
+ const hasSwcTs = !hasSwcRc && !hasSwcJs && fs.existsSync(`${getMeteorAppDir()}/swc.config.ts`);
+ if (!lastModifiedSwcConfig && !hasSwcRc && !hasSwcJs && !hasSwcTs) {
return;
}
- const swcFile = hasSwcJs ? 'swc.config.js' : '.swcrc';
+ const swcFile = hasSwcTs ? 'swc.config.ts' : hasSwcJs ? 'swc.config.js' : '.swcrc';
const filePath = `${getMeteorAppDir()}/${swcFile}`;
const fileStats = fs.statSync(filePath);
const fileModTime = fileStats?.mtime?.getTime();
let currentLastModifiedConfigTime;
- if (hasSwcJs) {
- // For dynamic JS files, first get the resolved configuration
+ if (hasSwcJs || hasSwcTs) {
+ // For dynamic JS/TS files, first get the resolved configuration
const resolvedConfig = lastModifiedSwcConfigTime?.includes(`${fileModTime}`)
? lastModifiedSwcConfig || getMeteorAppSwcrc(swcFile)
: getMeteorAppSwcrc(swcFile);
@@ -1073,8 +1074,37 @@ function getMeteorAppPackageJson() {
function getMeteorAppSwcrc(file = '.swcrc') {
try {
const filePath = `${getMeteorAppDir()}/${file}`;
- if (file.endsWith('.js')) {
+ if (file.endsWith('.js') || file.endsWith('.ts')) {
let content = fs.readFileSync(filePath, 'utf-8');
+
+ if (file.endsWith('.ts')) {
+ try {
+ const swc = require('@meteorjs/swc-core');
+ const result = swc.transformSync(content, {
+ jsc: {
+ parser: {
+ syntax: 'typescript',
+ },
+ target: 'es2015',
+ },
+ });
+ content = result.code;
+ } catch (swcError) {
+ content = content
+ .replace(/import\s+type\s+.*?from\s+['"][^'"]+['"];?/g, '')
+ .replace(/import\s+.*?from\s+['"][^'"]+['"];?/g, '')
+ .replace(/import\s+['"][^'"]+['"];?/g, '')
+ .replace(/export\s+default\s+/, 'module.exports = ')
+ .replace(/export\s+/g, '')
+ .replace(/:\s*\w+(\[\])?(\s*=)/g, '$2')
+ .replace(/\(([^)]*?):\s*\w+(\[\])?\)/g, '($1)')
+ .replace(/\):\s*\w+(\[\])?\s*\{/g, ') {')
+ .replace(/interface\s+\w+\s*\{[^}]*\}/g, '')
+ .replace(/type\s+\w+\s*=\s*[^;]+;/g, '')
+ .replace(/as\s+\w+(\[\])?/g, '');
+ }
+ }
+
// Check if the content uses ES module syntax (export default)
if (content.includes('export default')) {
// Transform ES module syntax to CommonJS
@@ -1091,7 +1121,9 @@ function getMeteorAppSwcrc(file = '.swcrc') {
})()
`);
const context = vm.createContext({ process });
- return script.runInContext(context);
+ const result = script.runInContext(context);
+ // Handle CJS interop wrapper (e.g. { __esModule: true, default: config })
+ return result && result.__esModule && result.default ? result.default : result;
} else {
// For .swcrc and other JSON files, parse as JSON
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
diff --git a/packages/ddp-client/common/livedata_connection.js b/packages/ddp-client/common/livedata_connection.js
index 9755a8012a..03385b7b68 100644
--- a/packages/ddp-client/common/livedata_connection.js
+++ b/packages/ddp-client/common/livedata_connection.js
@@ -1120,27 +1120,6 @@ export class Connection {
return Object.values(invokers).some((invoker) => !!invoker.sentMessage);
}
- async _processOneDataMessage(msg, updates) {
- const messageType = msg.msg;
-
- // msg is one of ['added', 'changed', 'removed', 'ready', 'updated']
- if (messageType === 'added') {
- await this._process_added(msg, updates);
- } else if (messageType === 'changed') {
- this._process_changed(msg, updates);
- } else if (messageType === 'removed') {
- this._process_removed(msg, updates);
- } else if (messageType === 'ready') {
- this._process_ready(msg, updates);
- } else if (messageType === 'updated') {
- this._process_updated(msg, updates);
- } else if (messageType === 'nosub') {
- // ignore this
- } else {
- Meteor._debug('discarding unknown livedata data message type', msg);
- }
- }
-
_prepareBuffersToFlush() {
const self = this;
if (self._bufferedWritesFlushHandle) {
diff --git a/packages/ddp-server/livedata_server_async_tests.js b/packages/ddp-server/livedata_server_async_tests.js
index 4ca4ca0864..3606b66044 100644
--- a/packages/ddp-server/livedata_server_async_tests.js
+++ b/packages/ddp-server/livedata_server_async_tests.js
@@ -168,9 +168,24 @@ Tinytest.addAsync('livedata server - async publish cursor', function(
connection: clientConn,
});
clientConn.subscribe('asyncPublishCursor', async () => {
- const actual = await remoteCollection.find().fetch();
- test.equal(actual[0].name, 'async');
- onComplete();
+ // Wait for data to arrive - the subscription is ready but data may still be in transit
+ // This can happen when a previous test run was interrupted (page reload) and the
+ // server is still processing the old session's grace period
+ let attempts = 0;
+ const maxAttempts = 50; // 5 seconds max wait
+ const checkData = async () => {
+ const actual = await remoteCollection.find().fetch();
+ if (actual.length > 0) {
+ test.equal(actual[0].name, 'async');
+ onComplete();
+ } else if (attempts++ < maxAttempts) {
+ setTimeout(checkData, 100);
+ } else {
+ test.fail('Timed out waiting for data in async publish cursor test');
+ onComplete();
+ }
+ };
+ await checkData();
});
});
});
diff --git a/packages/ecmascript-runtime-client/legacy.js b/packages/ecmascript-runtime-client/legacy.js
index 20a6d8fb3a..3d482c5438 100644
--- a/packages/ecmascript-runtime-client/legacy.js
+++ b/packages/ecmascript-runtime-client/legacy.js
@@ -1,8 +1,7 @@
try {
- Symbol = exports.Symbol = require("core-js/es/symbol");
- Map = exports.Map = require("core-js/es/map");
- Set = exports.Set = require("core-js/es/set");
-
+ Symbol = exports.Symbol = global.Symbol || require("core-js/es/symbol");
+ Map = exports.Map = global.Map || require("core-js/es/map");
+ Set = exports.Set = global.Set || require("core-js/es/set");
} catch (e) {
throw new Error([
"The core-js npm package could not be found in your node_modules ",
diff --git a/packages/facebook-config-ui/facebook_configure.html b/packages/facebook-config-ui/facebook_configure.html
index 6eca279ec5..678004abd0 100644
--- a/packages/facebook-config-ui/facebook_configure.html
+++ b/packages/facebook-config-ui/facebook_configure.html
@@ -7,46 +7,25 @@
Visit https://developers.facebook.com/apps
- Click "Add a New App".
+ Click "Create App" and fill out the required information.
- Add a "Display Name" for your app and click on "Create App ID".
+ In "Use cases" select "Authenticate and request data from users with Facebook Login".
- Answer the "Security Check" CAPTCHA and click on "Submit".
+ In the app dashboard, click "Add Product" and find "Facebook Login", then click "Set Up".
- When the new app dashboard loads, click on "Settings" in the left hand menu.
+ Select "Web" as your platform.
- From the top of the "Basic" settings page, note down your "App ID" and "App Secret" (you will be asked for them at the bottom of this popup).
+ In the "Facebook Login > Settings" from the left sidebar, set "Valid OAuth Redirect URIs" to {{siteUrl}}_oauth/facebook and click "Save Changes".
- Click on the "Add Platform" button, and select "Website".
+ Go to "Settings > Basic" in the left sidebar.
- In the "Website" section, set the "Site URL" to {{siteUrl}} and click on "Save Changes".
-
-
- Click on "Add Product" in the left hand menu.
-
-
- Hover over "Facebook Login", click on "Set Up".
-
-
- Click on "Facebook Login > Settings" from the left hand menu.
-
-
- Set "Valid OAuth redirect URIs" to {{siteUrl}}_oauth/facebook and click on "Save Changes".
-
-
- Select "App Review" from the left hand menu.
-
-
- Toggle the "Make app public" switch to "Yes".
-
-
- Select a "Category" in the "Make app public" popup and click on "Confirm".
+ Note down your "App ID" and "App Secret" (click "Show" to reveal the App Secret). You'll need these for configuration.
- "Create Project", if needed. Wait for Google to finish provisioning.
+ Create a new project or select an existing one.
- On the left sidebar, go to "Credentials" and, on the right, "OAuth consent screen". Make sure to enter an email address and a product name, and save.
+ In the left sidebar, go to "APIs & Services" > "OAuth consent screen".
- On the left sidebar, go to "Credentials". Click the "Create credentials" button, then select "OAuth client ID" as the type.
+ Configure the consent screen: select "External" user type, enter your app name, user support email, and developer contact email, then click "Save and Continue".
- Select "Web application" as your application type.
+ Skip the "Scopes" step (or add scopes if needed) and click "Save and Continue".
- Set Authorized Javascript Origins to: {{siteUrl}}
+ Add test users if needed, then click "Save and Continue".
- Set Authorized Redirect URI to: {{siteUrl}}_oauth/google?close
+ In the left sidebar, go to "Credentials" and click "Create Credentials" > "OAuth client ID".
- Finish by clicking "Create".
+ Select "Web application" as the application type.
+
+
+ Add your site URL to "Authorized JavaScript origins": {{siteUrl}}
+
+
+ Add to "Authorized redirect URIs": {{siteUrl}}_oauth/google
+
+
+ Click "Create" and note down your "Client ID" and "Client Secret" from the popup.
- Click on "Create New Consumer".
+ Click "Create new client".
- Set the Consumer name to the name of your application.
+ Set the "Client name" to the name of your application.
- Optionally set the Application Website to the URL of your
- website. You can leave this blank.
+ Set the "Application Website" to your site URL.
- Set the Redirect URI to: {{siteUrl}} (Do not append a path to this URL.)
+ Set the Redirect URI to: {{siteUrl}} (Do not append a path to this URL.)
+
+
+ Fill out all the other required fields.
+
+
+ Click "Create" and note down your "Key" (Client ID) and "Secret" (Client Secret).
diff --git a/packages/meteor-developer-config-ui/meteor_developer_configure.html b/packages/meteor-developer-config-ui/meteor_developer_configure.html
index 6d34d867e6..592a11b8be 100644
--- a/packages/meteor-developer-config-ui/meteor_developer_configure.html
+++ b/packages/meteor-developer-config-ui/meteor_developer_configure.html
@@ -4,15 +4,17 @@
Follow these steps:
-
- Click the orange "立即创建微连接" button
+ Click "创建应用" (Create Application) and select "网页应用" (Web Application).
- Select the third option, APP / 网页应用 (Web Applications)
+ Complete the registration form with your app details.
- Complete the registration process. An email confirmation will be send. No SMS confirmation required.
+ Complete the email verification process.
- Open 应用信息 (Application) -> 高级信息 (Advanced Information)
+ In your app dashboard, go to "应用信息" > "高级信息" (Application Info > Advanced Information).
- Set OAuth2.0 授权回调页 (authorized callback page) to: {{siteUrl}}_oauth/weibo
+ Set "OAuth2.0 授权回调页" (OAuth2.0 Redirect URI) to: {{siteUrl}}_oauth/weibo
+
+
+ In "应用信息" > "基本信息" (Application Info > Basic Information), note down your "App Key" (Client ID) and "App Secret" (Client Secret).
diff --git a/tools/cli/commands-packages.js b/tools/cli/commands-packages.js
index 02f896676f..395c26ba36 100644
--- a/tools/cli/commands-packages.js
+++ b/tools/cli/commands-packages.js
@@ -1832,7 +1832,7 @@ main.registerCommand({
}
// Compile the app to resolve NPM dependencies bump coming from plugins
- if (!files.inCheckout() && options["npm"]) {
+ if (options["npm"]) {
await compileMeteorApp(options);
return 0;
}
diff --git a/tools/cli/commands.js b/tools/cli/commands.js
index 76a6081cb2..a888036a88 100644
--- a/tools/cli/commands.js
+++ b/tools/cli/commands.js
@@ -1868,6 +1868,15 @@ main.registerCommand({
"MONGO_URL will NOT be reset.");
}
+ // Always clean the default .meteor/local directory to prevent regressions.
+ // When METEOR_LOCAL_DIR is set, also clean the custom local directory.
+ const defaultLocalRelative = files.pathJoin('.meteor', 'local');
+ const customLocalRelative = process.env.METEOR_LOCAL_DIR || null;
+ const localDirs = [defaultLocalRelative];
+ if (customLocalRelative && customLocalRelative !== defaultLocalRelative) {
+ localDirs.push(customLocalRelative);
+ }
+
const resetMeteorNpmCachePromise = options['skip-cache'] ? Promise.resolve() : files.rm_recursive_async(
files.pathJoin(options.appDir, "node_modules", ".cache", "meteor")
);
@@ -1882,19 +1891,23 @@ main.registerCommand({
// XXX detect the case where Meteor is running the app, but
// MONGO_URL was set, so we don't see a Mongo process
var findMongoPort = require('../runners/run-mongo.js').findMongoPort;
- var isRunning = !! await findMongoPort(files.pathJoin(options.appDir, ".meteor", "local", "db"));
- if (isRunning) {
- Console.error("reset: Meteor is running.");
- Console.error();
- Console.error(
- "This command does not work while Meteor is running your application.",
- "Exit the running Meteor development server.");
- return 1;
+ // Check all local dirs for a running Mongo instance
+ for (const localRelative of localDirs) {
+ const localDir = files.pathResolve(options.appDir, localRelative);
+ var isRunning = !! await findMongoPort(files.pathJoin(localDir, "db"));
+ if (isRunning) {
+ Console.error("reset: Meteor is running.");
+ Console.error();
+ Console.error(
+ "This command does not work while Meteor is running your application.",
+ "Exit the running Meteor development server.");
+ return 1;
+ }
}
await Promise.all([
- files.rm_recursive_async(
- files.pathJoin(options.appDir, ".meteor", "local")
+ ...localDirs.map((rel) =>
+ files.rm_recursive_async(files.pathResolve(options.appDir, rel))
),
resetMeteorNpmCachePromise,
...resetRspackPromises,
@@ -1904,11 +1917,19 @@ main.registerCommand({
return;
}
- var allExceptDb = files.getPathsInDir(files.pathJoin('.meteor', 'local'), {
- cwd: options.appDir,
- maxDepth: 1,
- }).filter(function (path) {
- return !path.includes('.meteor/local/db');
+ // Collect all paths inside each local dir except db
+ var allExceptDb = localDirs.flatMap((rel) => {
+ try {
+ return files.getPathsInDir(rel, {
+ cwd: options.appDir,
+ maxDepth: 1,
+ }).filter(function (p) {
+ return !p.includes('/db');
+ });
+ } catch (e) {
+ // Directory may not exist (e.g. default dir when only custom is used)
+ return [];
+ }
});
var allRemovePromises = [
diff --git a/tools/modern-tests/README.md b/tools/e2e-tests/README.md
similarity index 100%
rename from tools/modern-tests/README.md
rename to tools/e2e-tests/README.md
diff --git a/tools/modern-tests/apps/babel/.babelrc b/tools/e2e-tests/apps/babel/.babelrc
similarity index 100%
rename from tools/modern-tests/apps/babel/.babelrc
rename to tools/e2e-tests/apps/babel/.babelrc
diff --git a/tools/modern-tests/apps/babel/.gitignore b/tools/e2e-tests/apps/babel/.gitignore
similarity index 100%
rename from tools/modern-tests/apps/babel/.gitignore
rename to tools/e2e-tests/apps/babel/.gitignore
diff --git a/tools/modern-tests/apps/babel/.meteor/.gitignore b/tools/e2e-tests/apps/babel/.meteor/.gitignore
similarity index 100%
rename from tools/modern-tests/apps/babel/.meteor/.gitignore
rename to tools/e2e-tests/apps/babel/.meteor/.gitignore
diff --git a/tools/modern-tests/apps/babel/.meteor/.id b/tools/e2e-tests/apps/babel/.meteor/.id
similarity index 100%
rename from tools/modern-tests/apps/babel/.meteor/.id
rename to tools/e2e-tests/apps/babel/.meteor/.id
diff --git a/tools/modern-tests/apps/babel/.meteor/packages b/tools/e2e-tests/apps/babel/.meteor/packages
similarity index 100%
rename from tools/modern-tests/apps/babel/.meteor/packages
rename to tools/e2e-tests/apps/babel/.meteor/packages
diff --git a/tools/modern-tests/apps/babel/.meteor/platforms b/tools/e2e-tests/apps/babel/.meteor/platforms
similarity index 100%
rename from tools/modern-tests/apps/babel/.meteor/platforms
rename to tools/e2e-tests/apps/babel/.meteor/platforms
diff --git a/tools/modern-tests/apps/babel/.meteor/release b/tools/e2e-tests/apps/babel/.meteor/release
similarity index 100%
rename from tools/modern-tests/apps/babel/.meteor/release
rename to tools/e2e-tests/apps/babel/.meteor/release
diff --git a/tools/modern-tests/apps/babel/.meteor/versions b/tools/e2e-tests/apps/babel/.meteor/versions
similarity index 100%
rename from tools/modern-tests/apps/babel/.meteor/versions
rename to tools/e2e-tests/apps/babel/.meteor/versions
diff --git a/tools/modern-tests/apps/babel/.swcrc b/tools/e2e-tests/apps/babel/.swcrc
similarity index 100%
rename from tools/modern-tests/apps/babel/.swcrc
rename to tools/e2e-tests/apps/babel/.swcrc
diff --git a/tools/modern-tests/apps/babel/client/main.css b/tools/e2e-tests/apps/babel/client/main.css
similarity index 100%
rename from tools/modern-tests/apps/babel/client/main.css
rename to tools/e2e-tests/apps/babel/client/main.css
diff --git a/tools/modern-tests/apps/babel/client/main.html b/tools/e2e-tests/apps/babel/client/main.html
similarity index 100%
rename from tools/modern-tests/apps/babel/client/main.html
rename to tools/e2e-tests/apps/babel/client/main.html
diff --git a/tools/modern-tests/apps/babel/client/main.jsx b/tools/e2e-tests/apps/babel/client/main.jsx
similarity index 100%
rename from tools/modern-tests/apps/babel/client/main.jsx
rename to tools/e2e-tests/apps/babel/client/main.jsx
diff --git a/tools/modern-tests/apps/babel/imports/api/links.js b/tools/e2e-tests/apps/babel/imports/api/links.js
similarity index 100%
rename from tools/modern-tests/apps/babel/imports/api/links.js
rename to tools/e2e-tests/apps/babel/imports/api/links.js
diff --git a/tools/modern-tests/apps/babel/imports/apollo/schema.graphql b/tools/e2e-tests/apps/babel/imports/apollo/schema.graphql
similarity index 100%
rename from tools/modern-tests/apps/babel/imports/apollo/schema.graphql
rename to tools/e2e-tests/apps/babel/imports/apollo/schema.graphql
diff --git a/tools/modern-tests/apps/babel/imports/ui/App.jsx b/tools/e2e-tests/apps/babel/imports/ui/App.jsx
similarity index 100%
rename from tools/modern-tests/apps/babel/imports/ui/App.jsx
rename to tools/e2e-tests/apps/babel/imports/ui/App.jsx
diff --git a/tools/modern-tests/apps/babel/imports/ui/Hello.jsx b/tools/e2e-tests/apps/babel/imports/ui/Hello.jsx
similarity index 100%
rename from tools/modern-tests/apps/babel/imports/ui/Hello.jsx
rename to tools/e2e-tests/apps/babel/imports/ui/Hello.jsx
diff --git a/tools/modern-tests/apps/babel/imports/ui/Info.jsx b/tools/e2e-tests/apps/babel/imports/ui/Info.jsx
similarity index 100%
rename from tools/modern-tests/apps/babel/imports/ui/Info.jsx
rename to tools/e2e-tests/apps/babel/imports/ui/Info.jsx
diff --git a/tools/modern-tests/apps/babel/package.json b/tools/e2e-tests/apps/babel/package.json
similarity index 100%
rename from tools/modern-tests/apps/babel/package.json
rename to tools/e2e-tests/apps/babel/package.json
diff --git a/tools/modern-tests/apps/babel/rspack.config.mjs b/tools/e2e-tests/apps/babel/rspack.config.mjs
similarity index 100%
rename from tools/modern-tests/apps/babel/rspack.config.mjs
rename to tools/e2e-tests/apps/babel/rspack.config.mjs
diff --git a/tools/modern-tests/apps/babel/server/apollo.js b/tools/e2e-tests/apps/babel/server/apollo.js
similarity index 100%
rename from tools/modern-tests/apps/babel/server/apollo.js
rename to tools/e2e-tests/apps/babel/server/apollo.js
diff --git a/tools/modern-tests/apps/babel/server/main.js b/tools/e2e-tests/apps/babel/server/main.js
similarity index 100%
rename from tools/modern-tests/apps/babel/server/main.js
rename to tools/e2e-tests/apps/babel/server/main.js
diff --git a/tools/modern-tests/apps/babel/tests/main.js b/tools/e2e-tests/apps/babel/tests/main.js
similarity index 100%
rename from tools/modern-tests/apps/babel/tests/main.js
rename to tools/e2e-tests/apps/babel/tests/main.js
diff --git a/tools/modern-tests/apps/blaze/.gitignore b/tools/e2e-tests/apps/blaze/.gitignore
similarity index 100%
rename from tools/modern-tests/apps/blaze/.gitignore
rename to tools/e2e-tests/apps/blaze/.gitignore
diff --git a/tools/modern-tests/apps/blaze/.meteor/.gitignore b/tools/e2e-tests/apps/blaze/.meteor/.gitignore
similarity index 100%
rename from tools/modern-tests/apps/blaze/.meteor/.gitignore
rename to tools/e2e-tests/apps/blaze/.meteor/.gitignore
diff --git a/tools/modern-tests/apps/blaze/.meteor/.id b/tools/e2e-tests/apps/blaze/.meteor/.id
similarity index 100%
rename from tools/modern-tests/apps/blaze/.meteor/.id
rename to tools/e2e-tests/apps/blaze/.meteor/.id
diff --git a/tools/modern-tests/apps/blaze/.meteor/packages b/tools/e2e-tests/apps/blaze/.meteor/packages
similarity index 100%
rename from tools/modern-tests/apps/blaze/.meteor/packages
rename to tools/e2e-tests/apps/blaze/.meteor/packages
diff --git a/tools/modern-tests/apps/blaze/.meteor/platforms b/tools/e2e-tests/apps/blaze/.meteor/platforms
similarity index 100%
rename from tools/modern-tests/apps/blaze/.meteor/platforms
rename to tools/e2e-tests/apps/blaze/.meteor/platforms
diff --git a/tools/modern-tests/apps/blaze/.meteor/release b/tools/e2e-tests/apps/blaze/.meteor/release
similarity index 100%
rename from tools/modern-tests/apps/blaze/.meteor/release
rename to tools/e2e-tests/apps/blaze/.meteor/release
diff --git a/tools/modern-tests/apps/blaze/.meteor/versions b/tools/e2e-tests/apps/blaze/.meteor/versions
similarity index 100%
rename from tools/modern-tests/apps/blaze/.meteor/versions
rename to tools/e2e-tests/apps/blaze/.meteor/versions
diff --git a/tools/modern-tests/apps/blaze/client/main.css b/tools/e2e-tests/apps/blaze/client/main.css
similarity index 100%
rename from tools/modern-tests/apps/blaze/client/main.css
rename to tools/e2e-tests/apps/blaze/client/main.css
diff --git a/tools/modern-tests/apps/blaze/client/main.html b/tools/e2e-tests/apps/blaze/client/main.html
similarity index 100%
rename from tools/modern-tests/apps/blaze/client/main.html
rename to tools/e2e-tests/apps/blaze/client/main.html
diff --git a/tools/modern-tests/apps/blaze/client/main.js b/tools/e2e-tests/apps/blaze/client/main.js
similarity index 100%
rename from tools/modern-tests/apps/blaze/client/main.js
rename to tools/e2e-tests/apps/blaze/client/main.js
diff --git a/tools/modern-tests/apps/blaze/package.json b/tools/e2e-tests/apps/blaze/package.json
similarity index 100%
rename from tools/modern-tests/apps/blaze/package.json
rename to tools/e2e-tests/apps/blaze/package.json
diff --git a/tools/modern-tests/apps/blaze/server/main.js b/tools/e2e-tests/apps/blaze/server/main.js
similarity index 100%
rename from tools/modern-tests/apps/blaze/server/main.js
rename to tools/e2e-tests/apps/blaze/server/main.js
diff --git a/tools/modern-tests/apps/blaze/tests/main.js b/tools/e2e-tests/apps/blaze/tests/main.js
similarity index 100%
rename from tools/modern-tests/apps/blaze/tests/main.js
rename to tools/e2e-tests/apps/blaze/tests/main.js
diff --git a/tools/modern-tests/apps/coffeescript/.gitignore b/tools/e2e-tests/apps/coffeescript/.gitignore
similarity index 100%
rename from tools/modern-tests/apps/coffeescript/.gitignore
rename to tools/e2e-tests/apps/coffeescript/.gitignore
diff --git a/tools/modern-tests/apps/coffeescript/.meteor/.gitignore b/tools/e2e-tests/apps/coffeescript/.meteor/.gitignore
similarity index 100%
rename from tools/modern-tests/apps/coffeescript/.meteor/.gitignore
rename to tools/e2e-tests/apps/coffeescript/.meteor/.gitignore
diff --git a/tools/modern-tests/apps/coffeescript/.meteor/.id b/tools/e2e-tests/apps/coffeescript/.meteor/.id
similarity index 100%
rename from tools/modern-tests/apps/coffeescript/.meteor/.id
rename to tools/e2e-tests/apps/coffeescript/.meteor/.id
diff --git a/tools/modern-tests/apps/coffeescript/.meteor/packages b/tools/e2e-tests/apps/coffeescript/.meteor/packages
similarity index 100%
rename from tools/modern-tests/apps/coffeescript/.meteor/packages
rename to tools/e2e-tests/apps/coffeescript/.meteor/packages
diff --git a/tools/modern-tests/apps/coffeescript/.meteor/platforms b/tools/e2e-tests/apps/coffeescript/.meteor/platforms
similarity index 100%
rename from tools/modern-tests/apps/coffeescript/.meteor/platforms
rename to tools/e2e-tests/apps/coffeescript/.meteor/platforms
diff --git a/tools/modern-tests/apps/coffeescript/.meteor/release b/tools/e2e-tests/apps/coffeescript/.meteor/release
similarity index 100%
rename from tools/modern-tests/apps/coffeescript/.meteor/release
rename to tools/e2e-tests/apps/coffeescript/.meteor/release
diff --git a/tools/modern-tests/apps/coffeescript/.meteor/versions b/tools/e2e-tests/apps/coffeescript/.meteor/versions
similarity index 100%
rename from tools/modern-tests/apps/coffeescript/.meteor/versions
rename to tools/e2e-tests/apps/coffeescript/.meteor/versions
diff --git a/tools/modern-tests/apps/coffeescript/client/main.coffee b/tools/e2e-tests/apps/coffeescript/client/main.coffee
similarity index 100%
rename from tools/modern-tests/apps/coffeescript/client/main.coffee
rename to tools/e2e-tests/apps/coffeescript/client/main.coffee
diff --git a/tools/modern-tests/apps/coffeescript/client/main.css b/tools/e2e-tests/apps/coffeescript/client/main.css
similarity index 100%
rename from tools/modern-tests/apps/coffeescript/client/main.css
rename to tools/e2e-tests/apps/coffeescript/client/main.css
diff --git a/tools/modern-tests/apps/coffeescript/client/main.html b/tools/e2e-tests/apps/coffeescript/client/main.html
similarity index 100%
rename from tools/modern-tests/apps/coffeescript/client/main.html
rename to tools/e2e-tests/apps/coffeescript/client/main.html
diff --git a/tools/modern-tests/apps/coffeescript/imports/api/links.coffee b/tools/e2e-tests/apps/coffeescript/imports/api/links.coffee
similarity index 100%
rename from tools/modern-tests/apps/coffeescript/imports/api/links.coffee
rename to tools/e2e-tests/apps/coffeescript/imports/api/links.coffee
diff --git a/tools/modern-tests/apps/coffeescript/imports/ui/App.coffee b/tools/e2e-tests/apps/coffeescript/imports/ui/App.coffee
similarity index 100%
rename from tools/modern-tests/apps/coffeescript/imports/ui/App.coffee
rename to tools/e2e-tests/apps/coffeescript/imports/ui/App.coffee
diff --git a/tools/modern-tests/apps/coffeescript/imports/ui/Hello.coffee b/tools/e2e-tests/apps/coffeescript/imports/ui/Hello.coffee
similarity index 100%
rename from tools/modern-tests/apps/coffeescript/imports/ui/Hello.coffee
rename to tools/e2e-tests/apps/coffeescript/imports/ui/Hello.coffee
diff --git a/tools/modern-tests/apps/coffeescript/imports/ui/Info.coffee b/tools/e2e-tests/apps/coffeescript/imports/ui/Info.coffee
similarity index 100%
rename from tools/modern-tests/apps/coffeescript/imports/ui/Info.coffee
rename to tools/e2e-tests/apps/coffeescript/imports/ui/Info.coffee
diff --git a/tools/modern-tests/apps/coffeescript/package.json b/tools/e2e-tests/apps/coffeescript/package.json
similarity index 100%
rename from tools/modern-tests/apps/coffeescript/package.json
rename to tools/e2e-tests/apps/coffeescript/package.json
diff --git a/tools/modern-tests/apps/coffeescript/rspack.config.js b/tools/e2e-tests/apps/coffeescript/rspack.config.js
similarity index 100%
rename from tools/modern-tests/apps/coffeescript/rspack.config.js
rename to tools/e2e-tests/apps/coffeescript/rspack.config.js
diff --git a/tools/modern-tests/apps/coffeescript/server/main.coffee b/tools/e2e-tests/apps/coffeescript/server/main.coffee
similarity index 100%
rename from tools/modern-tests/apps/coffeescript/server/main.coffee
rename to tools/e2e-tests/apps/coffeescript/server/main.coffee
diff --git a/tools/modern-tests/apps/coffeescript/tests/main.coffee b/tools/e2e-tests/apps/coffeescript/tests/main.coffee
similarity index 100%
rename from tools/modern-tests/apps/coffeescript/tests/main.coffee
rename to tools/e2e-tests/apps/coffeescript/tests/main.coffee
diff --git a/tools/modern-tests/apps/full-blaze/.gitignore b/tools/e2e-tests/apps/full-blaze/.gitignore
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/.gitignore
rename to tools/e2e-tests/apps/full-blaze/.gitignore
diff --git a/tools/modern-tests/apps/full-blaze/.meteor/.gitignore b/tools/e2e-tests/apps/full-blaze/.meteor/.gitignore
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/.meteor/.gitignore
rename to tools/e2e-tests/apps/full-blaze/.meteor/.gitignore
diff --git a/tools/modern-tests/apps/full-blaze/.meteor/.id b/tools/e2e-tests/apps/full-blaze/.meteor/.id
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/.meteor/.id
rename to tools/e2e-tests/apps/full-blaze/.meteor/.id
diff --git a/tools/modern-tests/apps/full-blaze/.meteor/packages b/tools/e2e-tests/apps/full-blaze/.meteor/packages
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/.meteor/packages
rename to tools/e2e-tests/apps/full-blaze/.meteor/packages
diff --git a/tools/modern-tests/apps/full-blaze/.meteor/platforms b/tools/e2e-tests/apps/full-blaze/.meteor/platforms
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/.meteor/platforms
rename to tools/e2e-tests/apps/full-blaze/.meteor/platforms
diff --git a/tools/modern-tests/apps/full-blaze/.meteor/release b/tools/e2e-tests/apps/full-blaze/.meteor/release
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/.meteor/release
rename to tools/e2e-tests/apps/full-blaze/.meteor/release
diff --git a/tools/modern-tests/apps/full-blaze/.meteor/versions b/tools/e2e-tests/apps/full-blaze/.meteor/versions
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/.meteor/versions
rename to tools/e2e-tests/apps/full-blaze/.meteor/versions
diff --git a/tools/modern-tests/apps/full-blaze/client/head.html b/tools/e2e-tests/apps/full-blaze/client/head.html
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/client/head.html
rename to tools/e2e-tests/apps/full-blaze/client/head.html
diff --git a/tools/modern-tests/apps/full-blaze/client/main.js b/tools/e2e-tests/apps/full-blaze/client/main.js
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/client/main.js
rename to tools/e2e-tests/apps/full-blaze/client/main.js
diff --git a/tools/modern-tests/apps/full-blaze/client/main.less b/tools/e2e-tests/apps/full-blaze/client/main.less
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/client/main.less
rename to tools/e2e-tests/apps/full-blaze/client/main.less
diff --git a/tools/modern-tests/apps/full-blaze/imports/api/links/links.js b/tools/e2e-tests/apps/full-blaze/imports/api/links/links.js
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/imports/api/links/links.js
rename to tools/e2e-tests/apps/full-blaze/imports/api/links/links.js
diff --git a/tools/modern-tests/apps/full-blaze/imports/api/links/links.tests.js b/tools/e2e-tests/apps/full-blaze/imports/api/links/links.tests.js
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/imports/api/links/links.tests.js
rename to tools/e2e-tests/apps/full-blaze/imports/api/links/links.tests.js
diff --git a/tools/modern-tests/apps/full-blaze/imports/api/links/methods.js b/tools/e2e-tests/apps/full-blaze/imports/api/links/methods.js
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/imports/api/links/methods.js
rename to tools/e2e-tests/apps/full-blaze/imports/api/links/methods.js
diff --git a/tools/modern-tests/apps/full-blaze/imports/api/links/methods.tests.js b/tools/e2e-tests/apps/full-blaze/imports/api/links/methods.tests.js
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/imports/api/links/methods.tests.js
rename to tools/e2e-tests/apps/full-blaze/imports/api/links/methods.tests.js
diff --git a/tools/modern-tests/apps/full-blaze/imports/api/links/server/publications.js b/tools/e2e-tests/apps/full-blaze/imports/api/links/server/publications.js
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/imports/api/links/server/publications.js
rename to tools/e2e-tests/apps/full-blaze/imports/api/links/server/publications.js
diff --git a/tools/modern-tests/apps/full-blaze/imports/startup/both/index.js b/tools/e2e-tests/apps/full-blaze/imports/startup/both/index.js
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/imports/startup/both/index.js
rename to tools/e2e-tests/apps/full-blaze/imports/startup/both/index.js
diff --git a/tools/modern-tests/apps/full-blaze/imports/startup/client/index.js b/tools/e2e-tests/apps/full-blaze/imports/startup/client/index.js
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/imports/startup/client/index.js
rename to tools/e2e-tests/apps/full-blaze/imports/startup/client/index.js
diff --git a/tools/modern-tests/apps/full-blaze/imports/startup/client/routes.js b/tools/e2e-tests/apps/full-blaze/imports/startup/client/routes.js
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/imports/startup/client/routes.js
rename to tools/e2e-tests/apps/full-blaze/imports/startup/client/routes.js
diff --git a/tools/modern-tests/apps/full-blaze/imports/startup/server/fixtures.js b/tools/e2e-tests/apps/full-blaze/imports/startup/server/fixtures.js
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/imports/startup/server/fixtures.js
rename to tools/e2e-tests/apps/full-blaze/imports/startup/server/fixtures.js
diff --git a/tools/modern-tests/apps/full-blaze/imports/startup/server/index.js b/tools/e2e-tests/apps/full-blaze/imports/startup/server/index.js
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/imports/startup/server/index.js
rename to tools/e2e-tests/apps/full-blaze/imports/startup/server/index.js
diff --git a/tools/modern-tests/apps/full-blaze/imports/startup/server/register-api.js b/tools/e2e-tests/apps/full-blaze/imports/startup/server/register-api.js
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/imports/startup/server/register-api.js
rename to tools/e2e-tests/apps/full-blaze/imports/startup/server/register-api.js
diff --git a/tools/modern-tests/apps/full-blaze/imports/ui/components/hello/hello.html b/tools/e2e-tests/apps/full-blaze/imports/ui/components/hello/hello.html
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/imports/ui/components/hello/hello.html
rename to tools/e2e-tests/apps/full-blaze/imports/ui/components/hello/hello.html
diff --git a/tools/modern-tests/apps/full-blaze/imports/ui/components/hello/hello.js b/tools/e2e-tests/apps/full-blaze/imports/ui/components/hello/hello.js
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/imports/ui/components/hello/hello.js
rename to tools/e2e-tests/apps/full-blaze/imports/ui/components/hello/hello.js
diff --git a/tools/modern-tests/apps/full-blaze/imports/ui/components/info/info.html b/tools/e2e-tests/apps/full-blaze/imports/ui/components/info/info.html
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/imports/ui/components/info/info.html
rename to tools/e2e-tests/apps/full-blaze/imports/ui/components/info/info.html
diff --git a/tools/modern-tests/apps/full-blaze/imports/ui/components/info/info.js b/tools/e2e-tests/apps/full-blaze/imports/ui/components/info/info.js
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/imports/ui/components/info/info.js
rename to tools/e2e-tests/apps/full-blaze/imports/ui/components/info/info.js
diff --git a/tools/modern-tests/apps/full-blaze/imports/ui/layouts/body/body.html b/tools/e2e-tests/apps/full-blaze/imports/ui/layouts/body/body.html
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/imports/ui/layouts/body/body.html
rename to tools/e2e-tests/apps/full-blaze/imports/ui/layouts/body/body.html
diff --git a/tools/modern-tests/apps/full-blaze/imports/ui/layouts/body/body.js b/tools/e2e-tests/apps/full-blaze/imports/ui/layouts/body/body.js
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/imports/ui/layouts/body/body.js
rename to tools/e2e-tests/apps/full-blaze/imports/ui/layouts/body/body.js
diff --git a/tools/modern-tests/apps/full-blaze/imports/ui/pages/home/home.html b/tools/e2e-tests/apps/full-blaze/imports/ui/pages/home/home.html
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/imports/ui/pages/home/home.html
rename to tools/e2e-tests/apps/full-blaze/imports/ui/pages/home/home.html
diff --git a/tools/modern-tests/apps/full-blaze/imports/ui/pages/home/home.js b/tools/e2e-tests/apps/full-blaze/imports/ui/pages/home/home.js
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/imports/ui/pages/home/home.js
rename to tools/e2e-tests/apps/full-blaze/imports/ui/pages/home/home.js
diff --git a/tools/modern-tests/apps/full-blaze/imports/ui/pages/not-found/not-found.html b/tools/e2e-tests/apps/full-blaze/imports/ui/pages/not-found/not-found.html
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/imports/ui/pages/not-found/not-found.html
rename to tools/e2e-tests/apps/full-blaze/imports/ui/pages/not-found/not-found.html
diff --git a/tools/modern-tests/apps/full-blaze/imports/ui/pages/not-found/not-found.js b/tools/e2e-tests/apps/full-blaze/imports/ui/pages/not-found/not-found.js
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/imports/ui/pages/not-found/not-found.js
rename to tools/e2e-tests/apps/full-blaze/imports/ui/pages/not-found/not-found.js
diff --git a/tools/modern-tests/apps/full-blaze/imports/ui/stylesheets/not-found.less b/tools/e2e-tests/apps/full-blaze/imports/ui/stylesheets/not-found.less
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/imports/ui/stylesheets/not-found.less
rename to tools/e2e-tests/apps/full-blaze/imports/ui/stylesheets/not-found.less
diff --git a/tools/modern-tests/apps/full-blaze/package.json b/tools/e2e-tests/apps/full-blaze/package.json
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/package.json
rename to tools/e2e-tests/apps/full-blaze/package.json
diff --git a/tools/modern-tests/apps/full-blaze/private/README.md b/tools/e2e-tests/apps/full-blaze/private/README.md
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/private/README.md
rename to tools/e2e-tests/apps/full-blaze/private/README.md
diff --git a/tools/modern-tests/apps/full-blaze/public/img/404.svg b/tools/e2e-tests/apps/full-blaze/public/img/404.svg
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/public/img/404.svg
rename to tools/e2e-tests/apps/full-blaze/public/img/404.svg
diff --git a/tools/modern-tests/apps/full-blaze/public/img/bg-footer.svg b/tools/e2e-tests/apps/full-blaze/public/img/bg-footer.svg
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/public/img/bg-footer.svg
rename to tools/e2e-tests/apps/full-blaze/public/img/bg-footer.svg
diff --git a/tools/modern-tests/apps/full-blaze/rspack.config.js b/tools/e2e-tests/apps/full-blaze/rspack.config.js
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/rspack.config.js
rename to tools/e2e-tests/apps/full-blaze/rspack.config.js
diff --git a/tools/modern-tests/apps/full-blaze/server/main.js b/tools/e2e-tests/apps/full-blaze/server/main.js
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/server/main.js
rename to tools/e2e-tests/apps/full-blaze/server/main.js
diff --git a/tools/modern-tests/apps/full-blaze/swc.config.js b/tools/e2e-tests/apps/full-blaze/swc.config.js
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/swc.config.js
rename to tools/e2e-tests/apps/full-blaze/swc.config.js
diff --git a/tools/modern-tests/apps/full-blaze/tests/main.js b/tools/e2e-tests/apps/full-blaze/tests/main.js
similarity index 100%
rename from tools/modern-tests/apps/full-blaze/tests/main.js
rename to tools/e2e-tests/apps/full-blaze/tests/main.js
diff --git a/tools/modern-tests/apps/monorepo/.npmrc b/tools/e2e-tests/apps/monorepo/.npmrc
similarity index 100%
rename from tools/modern-tests/apps/monorepo/.npmrc
rename to tools/e2e-tests/apps/monorepo/.npmrc
diff --git a/tools/modern-tests/apps/monorepo/app/.meteor/packages b/tools/e2e-tests/apps/monorepo/app/.meteor/packages
similarity index 100%
rename from tools/modern-tests/apps/monorepo/app/.meteor/packages
rename to tools/e2e-tests/apps/monorepo/app/.meteor/packages
diff --git a/tools/modern-tests/apps/monorepo/app/.meteor/platforms b/tools/e2e-tests/apps/monorepo/app/.meteor/platforms
similarity index 100%
rename from tools/modern-tests/apps/monorepo/app/.meteor/platforms
rename to tools/e2e-tests/apps/monorepo/app/.meteor/platforms
diff --git a/tools/modern-tests/apps/monorepo/app/.meteor/release b/tools/e2e-tests/apps/monorepo/app/.meteor/release
similarity index 100%
rename from tools/modern-tests/apps/monorepo/app/.meteor/release
rename to tools/e2e-tests/apps/monorepo/app/.meteor/release
diff --git a/tools/modern-tests/apps/monorepo/app/.meteorignore b/tools/e2e-tests/apps/monorepo/app/.meteorignore
similarity index 100%
rename from tools/modern-tests/apps/monorepo/app/.meteorignore
rename to tools/e2e-tests/apps/monorepo/app/.meteorignore
diff --git a/tools/modern-tests/apps/monorepo/app/.swcrc b/tools/e2e-tests/apps/monorepo/app/.swcrc
similarity index 100%
rename from tools/modern-tests/apps/monorepo/app/.swcrc
rename to tools/e2e-tests/apps/monorepo/app/.swcrc
diff --git a/tools/modern-tests/apps/monorepo/app/client/client.test.js b/tools/e2e-tests/apps/monorepo/app/client/client.test.js
similarity index 100%
rename from tools/modern-tests/apps/monorepo/app/client/client.test.js
rename to tools/e2e-tests/apps/monorepo/app/client/client.test.js
diff --git a/tools/modern-tests/apps/monorepo/app/client/main.css b/tools/e2e-tests/apps/monorepo/app/client/main.css
similarity index 100%
rename from tools/modern-tests/apps/monorepo/app/client/main.css
rename to tools/e2e-tests/apps/monorepo/app/client/main.css
diff --git a/tools/modern-tests/apps/monorepo/app/client/main.html b/tools/e2e-tests/apps/monorepo/app/client/main.html
similarity index 100%
rename from tools/modern-tests/apps/monorepo/app/client/main.html
rename to tools/e2e-tests/apps/monorepo/app/client/main.html
diff --git a/tools/modern-tests/apps/monorepo/app/client/main.jsx b/tools/e2e-tests/apps/monorepo/app/client/main.jsx
similarity index 100%
rename from tools/modern-tests/apps/monorepo/app/client/main.jsx
rename to tools/e2e-tests/apps/monorepo/app/client/main.jsx
diff --git a/tools/modern-tests/apps/monorepo/app/ignored/ignore.test.js b/tools/e2e-tests/apps/monorepo/app/ignored/ignore.test.js
similarity index 100%
rename from tools/modern-tests/apps/monorepo/app/ignored/ignore.test.js
rename to tools/e2e-tests/apps/monorepo/app/ignored/ignore.test.js
diff --git a/tools/modern-tests/apps/monorepo/app/imports/api/links.js b/tools/e2e-tests/apps/monorepo/app/imports/api/links.js
similarity index 100%
rename from tools/modern-tests/apps/monorepo/app/imports/api/links.js
rename to tools/e2e-tests/apps/monorepo/app/imports/api/links.js
diff --git a/tools/modern-tests/apps/monorepo/app/imports/emails/TestEmail.jsx b/tools/e2e-tests/apps/monorepo/app/imports/emails/TestEmail.jsx
similarity index 100%
rename from tools/modern-tests/apps/monorepo/app/imports/emails/TestEmail.jsx
rename to tools/e2e-tests/apps/monorepo/app/imports/emails/TestEmail.jsx
diff --git a/tools/modern-tests/apps/monorepo/app/imports/ui/App.jsx b/tools/e2e-tests/apps/monorepo/app/imports/ui/App.jsx
similarity index 100%
rename from tools/modern-tests/apps/monorepo/app/imports/ui/App.jsx
rename to tools/e2e-tests/apps/monorepo/app/imports/ui/App.jsx
diff --git a/tools/modern-tests/apps/monorepo/app/imports/ui/Hello.jsx b/tools/e2e-tests/apps/monorepo/app/imports/ui/Hello.jsx
similarity index 100%
rename from tools/modern-tests/apps/monorepo/app/imports/ui/Hello.jsx
rename to tools/e2e-tests/apps/monorepo/app/imports/ui/Hello.jsx
diff --git a/tools/modern-tests/apps/monorepo/app/imports/ui/Info.jsx b/tools/e2e-tests/apps/monorepo/app/imports/ui/Info.jsx
similarity index 100%
rename from tools/modern-tests/apps/monorepo/app/imports/ui/Info.jsx
rename to tools/e2e-tests/apps/monorepo/app/imports/ui/Info.jsx
diff --git a/tools/modern-tests/apps/monorepo/app/package.json b/tools/e2e-tests/apps/monorepo/app/package.json
similarity index 100%
rename from tools/modern-tests/apps/monorepo/app/package.json
rename to tools/e2e-tests/apps/monorepo/app/package.json
diff --git a/tools/modern-tests/apps/monorepo/app/plugins/CustomConsoleLogPlugin.js b/tools/e2e-tests/apps/monorepo/app/plugins/CustomConsoleLogPlugin.js
similarity index 100%
rename from tools/modern-tests/apps/monorepo/app/plugins/CustomConsoleLogPlugin.js
rename to tools/e2e-tests/apps/monorepo/app/plugins/CustomConsoleLogPlugin.js
diff --git a/tools/modern-tests/apps/monorepo/app/public/1x1.png b/tools/e2e-tests/apps/monorepo/app/public/1x1.png
similarity index 100%
rename from tools/modern-tests/apps/monorepo/app/public/1x1.png
rename to tools/e2e-tests/apps/monorepo/app/public/1x1.png
diff --git a/tools/modern-tests/apps/monorepo/app/public/docs/text.md b/tools/e2e-tests/apps/monorepo/app/public/docs/text.md
similarity index 100%
rename from tools/modern-tests/apps/monorepo/app/public/docs/text.md
rename to tools/e2e-tests/apps/monorepo/app/public/docs/text.md
diff --git a/tools/modern-tests/apps/monorepo/app/public/images/1x1.png b/tools/e2e-tests/apps/monorepo/app/public/images/1x1.png
similarity index 100%
rename from tools/modern-tests/apps/monorepo/app/public/images/1x1.png
rename to tools/e2e-tests/apps/monorepo/app/public/images/1x1.png
diff --git a/tools/modern-tests/apps/monorepo/app/rspack.config.cjs b/tools/e2e-tests/apps/monorepo/app/rspack.config.cjs
similarity index 100%
rename from tools/modern-tests/apps/monorepo/app/rspack.config.cjs
rename to tools/e2e-tests/apps/monorepo/app/rspack.config.cjs
diff --git a/tools/modern-tests/apps/monorepo/app/rspack.config.override.cjs b/tools/e2e-tests/apps/monorepo/app/rspack.config.override.cjs
similarity index 100%
rename from tools/modern-tests/apps/monorepo/app/rspack.config.override.cjs
rename to tools/e2e-tests/apps/monorepo/app/rspack.config.override.cjs
diff --git a/tools/modern-tests/apps/monorepo/app/server/main.js b/tools/e2e-tests/apps/monorepo/app/server/main.js
similarity index 100%
rename from tools/modern-tests/apps/monorepo/app/server/main.js
rename to tools/e2e-tests/apps/monorepo/app/server/main.js
diff --git a/tools/modern-tests/apps/monorepo/app/server/server.test.js b/tools/e2e-tests/apps/monorepo/app/server/server.test.js
similarity index 100%
rename from tools/modern-tests/apps/monorepo/app/server/server.test.js
rename to tools/e2e-tests/apps/monorepo/app/server/server.test.js
diff --git a/tools/modern-tests/apps/monorepo/app/tests/ignore-test.test.js b/tools/e2e-tests/apps/monorepo/app/tests/ignore-test.test.js
similarity index 100%
rename from tools/modern-tests/apps/monorepo/app/tests/ignore-test.test.js
rename to tools/e2e-tests/apps/monorepo/app/tests/ignore-test.test.js
diff --git a/tools/modern-tests/apps/monorepo/app/tests/ignored/ignore-nested.test.js b/tools/e2e-tests/apps/monorepo/app/tests/ignored/ignore-nested.test.js
similarity index 100%
rename from tools/modern-tests/apps/monorepo/app/tests/ignored/ignore-nested.test.js
rename to tools/e2e-tests/apps/monorepo/app/tests/ignored/ignore-nested.test.js
diff --git a/tools/modern-tests/apps/monorepo/app/tests/main.test.js b/tools/e2e-tests/apps/monorepo/app/tests/main.test.js
similarity index 100%
rename from tools/modern-tests/apps/monorepo/app/tests/main.test.js
rename to tools/e2e-tests/apps/monorepo/app/tests/main.test.js
diff --git a/tools/modern-tests/apps/monorepo/package.json b/tools/e2e-tests/apps/monorepo/package.json
similarity index 100%
rename from tools/modern-tests/apps/monorepo/package.json
rename to tools/e2e-tests/apps/monorepo/package.json
diff --git a/tools/modern-tests/apps/react-router/.gitignore b/tools/e2e-tests/apps/react-router/.gitignore
similarity index 100%
rename from tools/modern-tests/apps/react-router/.gitignore
rename to tools/e2e-tests/apps/react-router/.gitignore
diff --git a/tools/modern-tests/apps/react-router/.meteor/.gitignore b/tools/e2e-tests/apps/react-router/.meteor/.gitignore
similarity index 100%
rename from tools/modern-tests/apps/react-router/.meteor/.gitignore
rename to tools/e2e-tests/apps/react-router/.meteor/.gitignore
diff --git a/tools/modern-tests/apps/react-router/.meteor/.id b/tools/e2e-tests/apps/react-router/.meteor/.id
similarity index 100%
rename from tools/modern-tests/apps/react-router/.meteor/.id
rename to tools/e2e-tests/apps/react-router/.meteor/.id
diff --git a/tools/modern-tests/apps/react-router/.meteor/packages b/tools/e2e-tests/apps/react-router/.meteor/packages
similarity index 100%
rename from tools/modern-tests/apps/react-router/.meteor/packages
rename to tools/e2e-tests/apps/react-router/.meteor/packages
diff --git a/tools/modern-tests/apps/react-router/.meteor/platforms b/tools/e2e-tests/apps/react-router/.meteor/platforms
similarity index 100%
rename from tools/modern-tests/apps/react-router/.meteor/platforms
rename to tools/e2e-tests/apps/react-router/.meteor/platforms
diff --git a/tools/modern-tests/apps/react-router/.meteor/release b/tools/e2e-tests/apps/react-router/.meteor/release
similarity index 100%
rename from tools/modern-tests/apps/react-router/.meteor/release
rename to tools/e2e-tests/apps/react-router/.meteor/release
diff --git a/tools/modern-tests/apps/react-router/.meteor/versions b/tools/e2e-tests/apps/react-router/.meteor/versions
similarity index 100%
rename from tools/modern-tests/apps/react-router/.meteor/versions
rename to tools/e2e-tests/apps/react-router/.meteor/versions
diff --git a/tools/e2e-tests/apps/react-router/.meteorignore b/tools/e2e-tests/apps/react-router/.meteorignore
new file mode 100644
index 0000000000..269a4e9cc4
--- /dev/null
+++ b/tools/e2e-tests/apps/react-router/.meteorignore
@@ -0,0 +1,16 @@
+# Folder combination patterns
+react-router*
+tests/ignored/react-router*-ignored
+tests/ignored/folder-to-ignored/
+tests/ignored/file-to-ignored.app-test.js
+**/glob-ignored/*.app-test.js
+tests/ignored/prefix-*-ignored
+tests/ignored/*-ignored-suffix
+
+# File combination patterns
+tests/ignored/specific-file-ignored.app-test.js
+tests/ignored/*.temp-ignored.app-test.js
+**/unit/*.app-test.js
+tests/ignored/integration/*-ignored.app-test.js
+tests/ignored/test-*-ignored.app-test.js
+!important.app-test.js
diff --git a/tools/modern-tests/apps/react-router/babel.config.js b/tools/e2e-tests/apps/react-router/babel.config.js
similarity index 100%
rename from tools/modern-tests/apps/react-router/babel.config.js
rename to tools/e2e-tests/apps/react-router/babel.config.js
diff --git a/tools/modern-tests/apps/react-router/client/client.app-test.js b/tools/e2e-tests/apps/react-router/client/client.app-test.js
similarity index 100%
rename from tools/modern-tests/apps/react-router/client/client.app-test.js
rename to tools/e2e-tests/apps/react-router/client/client.app-test.js
diff --git a/tools/modern-tests/apps/react-router/client/main.css b/tools/e2e-tests/apps/react-router/client/main.css
similarity index 100%
rename from tools/modern-tests/apps/react-router/client/main.css
rename to tools/e2e-tests/apps/react-router/client/main.css
diff --git a/tools/modern-tests/apps/react-router/client/main.html b/tools/e2e-tests/apps/react-router/client/main.html
similarity index 100%
rename from tools/modern-tests/apps/react-router/client/main.html
rename to tools/e2e-tests/apps/react-router/client/main.html
diff --git a/tools/modern-tests/apps/react-router/client/main.jsx b/tools/e2e-tests/apps/react-router/client/main.jsx
similarity index 100%
rename from tools/modern-tests/apps/react-router/client/main.jsx
rename to tools/e2e-tests/apps/react-router/client/main.jsx
diff --git a/tools/e2e-tests/apps/react-router/important.app-test.js b/tools/e2e-tests/apps/react-router/important.app-test.js
new file mode 100644
index 0000000000..a9cb841908
--- /dev/null
+++ b/tools/e2e-tests/apps/react-router/important.app-test.js
@@ -0,0 +1,7 @@
+import assert from "assert";
+
+describe("!important.app-test.js pattern", () => {
+ it("should run as it's not ignored (negation)", () => {
+ assert(true, "should run");
+ });
+});
diff --git a/tools/modern-tests/apps/react-router/imports/api/links.js b/tools/e2e-tests/apps/react-router/imports/api/links.js
similarity index 100%
rename from tools/modern-tests/apps/react-router/imports/api/links.js
rename to tools/e2e-tests/apps/react-router/imports/api/links.js
diff --git a/tools/modern-tests/apps/react-router/imports/helpers/alias.js b/tools/e2e-tests/apps/react-router/imports/helpers/alias.js
similarity index 100%
rename from tools/modern-tests/apps/react-router/imports/helpers/alias.js
rename to tools/e2e-tests/apps/react-router/imports/helpers/alias.js
diff --git a/tools/modern-tests/apps/react-router/imports/ui/App.jsx b/tools/e2e-tests/apps/react-router/imports/ui/App.jsx
similarity index 100%
rename from tools/modern-tests/apps/react-router/imports/ui/App.jsx
rename to tools/e2e-tests/apps/react-router/imports/ui/App.jsx
diff --git a/tools/modern-tests/apps/react-router/imports/ui/Global.less b/tools/e2e-tests/apps/react-router/imports/ui/Global.less
similarity index 100%
rename from tools/modern-tests/apps/react-router/imports/ui/Global.less
rename to tools/e2e-tests/apps/react-router/imports/ui/Global.less
diff --git a/tools/modern-tests/apps/react-router/imports/ui/Hello.jsx b/tools/e2e-tests/apps/react-router/imports/ui/Hello.jsx
similarity index 100%
rename from tools/modern-tests/apps/react-router/imports/ui/Hello.jsx
rename to tools/e2e-tests/apps/react-router/imports/ui/Hello.jsx
diff --git a/tools/modern-tests/apps/react-router/imports/ui/Home.jsx b/tools/e2e-tests/apps/react-router/imports/ui/Home.jsx
similarity index 100%
rename from tools/modern-tests/apps/react-router/imports/ui/Home.jsx
rename to tools/e2e-tests/apps/react-router/imports/ui/Home.jsx
diff --git a/tools/modern-tests/apps/react-router/imports/ui/Info.jsx b/tools/e2e-tests/apps/react-router/imports/ui/Info.jsx
similarity index 100%
rename from tools/modern-tests/apps/react-router/imports/ui/Info.jsx
rename to tools/e2e-tests/apps/react-router/imports/ui/Info.jsx
diff --git a/tools/modern-tests/apps/react-router/imports/ui/NotFound.jsx b/tools/e2e-tests/apps/react-router/imports/ui/NotFound.jsx
similarity index 100%
rename from tools/modern-tests/apps/react-router/imports/ui/NotFound.jsx
rename to tools/e2e-tests/apps/react-router/imports/ui/NotFound.jsx
diff --git a/tools/modern-tests/apps/react-router/my-packages/custom-package/custom-package.js b/tools/e2e-tests/apps/react-router/my-packages/custom-package/custom-package.js
similarity index 100%
rename from tools/modern-tests/apps/react-router/my-packages/custom-package/custom-package.js
rename to tools/e2e-tests/apps/react-router/my-packages/custom-package/custom-package.js
diff --git a/tools/modern-tests/apps/react-router/my-packages/custom-package/package.js b/tools/e2e-tests/apps/react-router/my-packages/custom-package/package.js
similarity index 100%
rename from tools/modern-tests/apps/react-router/my-packages/custom-package/package.js
rename to tools/e2e-tests/apps/react-router/my-packages/custom-package/package.js
diff --git a/tools/modern-tests/apps/react-router/package.json b/tools/e2e-tests/apps/react-router/package.json
similarity index 100%
rename from tools/modern-tests/apps/react-router/package.json
rename to tools/e2e-tests/apps/react-router/package.json
diff --git a/tools/modern-tests/apps/react-router/packages/default-package/default-package.js b/tools/e2e-tests/apps/react-router/packages/default-package/default-package.js
similarity index 100%
rename from tools/modern-tests/apps/react-router/packages/default-package/default-package.js
rename to tools/e2e-tests/apps/react-router/packages/default-package/default-package.js
diff --git a/tools/modern-tests/apps/react-router/packages/default-package/package.js b/tools/e2e-tests/apps/react-router/packages/default-package/package.js
similarity index 100%
rename from tools/modern-tests/apps/react-router/packages/default-package/package.js
rename to tools/e2e-tests/apps/react-router/packages/default-package/package.js
diff --git a/tools/modern-tests/apps/react-router/plugins/CustomConsoleLogPlugin.js b/tools/e2e-tests/apps/react-router/plugins/CustomConsoleLogPlugin.js
similarity index 100%
rename from tools/modern-tests/apps/react-router/plugins/CustomConsoleLogPlugin.js
rename to tools/e2e-tests/apps/react-router/plugins/CustomConsoleLogPlugin.js
diff --git a/tools/modern-tests/apps/react-router/public/1x1.png b/tools/e2e-tests/apps/react-router/public/1x1.png
similarity index 100%
rename from tools/modern-tests/apps/react-router/public/1x1.png
rename to tools/e2e-tests/apps/react-router/public/1x1.png
diff --git a/tools/modern-tests/apps/react-router/public/docs/text.md b/tools/e2e-tests/apps/react-router/public/docs/text.md
similarity index 100%
rename from tools/modern-tests/apps/react-router/public/docs/text.md
rename to tools/e2e-tests/apps/react-router/public/docs/text.md
diff --git a/tools/modern-tests/apps/react-router/public/images/1x1.png b/tools/e2e-tests/apps/react-router/public/images/1x1.png
similarity index 100%
rename from tools/modern-tests/apps/react-router/public/images/1x1.png
rename to tools/e2e-tests/apps/react-router/public/images/1x1.png
diff --git a/tools/e2e-tests/apps/react-router/react-router-wxyz-ignored/ignore.app-test.js b/tools/e2e-tests/apps/react-router/react-router-wxyz-ignored/ignore.app-test.js
new file mode 100644
index 0000000000..bf67b299e7
--- /dev/null
+++ b/tools/e2e-tests/apps/react-router/react-router-wxyz-ignored/ignore.app-test.js
@@ -0,0 +1,5 @@
+describe("react-router*-ignored pattern", () => {
+ it("should not run as ignored", () => {
+ throw new Error("test should be ignored by eager test loading");
+ });
+});
diff --git a/tools/modern-tests/apps/react-router/rspack.config.js b/tools/e2e-tests/apps/react-router/rspack.config.js
similarity index 100%
rename from tools/modern-tests/apps/react-router/rspack.config.js
rename to tools/e2e-tests/apps/react-router/rspack.config.js
diff --git a/tools/modern-tests/apps/react-router/rspack.config.override.js b/tools/e2e-tests/apps/react-router/rspack.config.override.js
similarity index 100%
rename from tools/modern-tests/apps/react-router/rspack.config.override.js
rename to tools/e2e-tests/apps/react-router/rspack.config.override.js
diff --git a/tools/modern-tests/apps/react-router/server/browser-tests/browser.app-test.js b/tools/e2e-tests/apps/react-router/server/browser-tests/browser.app-test.js
similarity index 100%
rename from tools/modern-tests/apps/react-router/server/browser-tests/browser.app-test.js
rename to tools/e2e-tests/apps/react-router/server/browser-tests/browser.app-test.js
diff --git a/tools/modern-tests/apps/react-router/server/main.js b/tools/e2e-tests/apps/react-router/server/main.js
similarity index 100%
rename from tools/modern-tests/apps/react-router/server/main.js
rename to tools/e2e-tests/apps/react-router/server/main.js
diff --git a/tools/modern-tests/apps/react-router/server/resolve-extensions/first.jsx b/tools/e2e-tests/apps/react-router/server/resolve-extensions/first.jsx
similarity index 100%
rename from tools/modern-tests/apps/react-router/server/resolve-extensions/first.jsx
rename to tools/e2e-tests/apps/react-router/server/resolve-extensions/first.jsx
diff --git a/tools/modern-tests/apps/react-router/server/resolve-extensions/first.tsx b/tools/e2e-tests/apps/react-router/server/resolve-extensions/first.tsx
similarity index 100%
rename from tools/modern-tests/apps/react-router/server/resolve-extensions/first.tsx
rename to tools/e2e-tests/apps/react-router/server/resolve-extensions/first.tsx
diff --git a/tools/modern-tests/apps/react-router/server/server.app-test.js b/tools/e2e-tests/apps/react-router/server/server.app-test.js
similarity index 100%
rename from tools/modern-tests/apps/react-router/server/server.app-test.js
rename to tools/e2e-tests/apps/react-router/server/server.app-test.js
diff --git a/tools/modern-tests/apps/react-router/server/ts/helpers.ts b/tools/e2e-tests/apps/react-router/server/ts/helpers.ts
similarity index 100%
rename from tools/modern-tests/apps/react-router/server/ts/helpers.ts
rename to tools/e2e-tests/apps/react-router/server/ts/helpers.ts
diff --git a/tools/modern-tests/apps/react-router/styles/module.css b/tools/e2e-tests/apps/react-router/styles/module.css
similarity index 100%
rename from tools/modern-tests/apps/react-router/styles/module.css
rename to tools/e2e-tests/apps/react-router/styles/module.css
diff --git a/tools/e2e-tests/apps/react-router/tests/ignored/file-to-ignored.app-test.js b/tools/e2e-tests/apps/react-router/tests/ignored/file-to-ignored.app-test.js
new file mode 100644
index 0000000000..c67d43d5d9
--- /dev/null
+++ b/tools/e2e-tests/apps/react-router/tests/ignored/file-to-ignored.app-test.js
@@ -0,0 +1,5 @@
+describe("tests/ignored/file-to-ignored.app-test.js pattern", () => {
+ it("should not run as ignored", () => {
+ throw new Error("test should be ignored by eager test loading");
+ });
+});
diff --git a/tools/e2e-tests/apps/react-router/tests/ignored/folder-to-ignored/ignore.app-test.js b/tools/e2e-tests/apps/react-router/tests/ignored/folder-to-ignored/ignore.app-test.js
new file mode 100644
index 0000000000..5878c51755
--- /dev/null
+++ b/tools/e2e-tests/apps/react-router/tests/ignored/folder-to-ignored/ignore.app-test.js
@@ -0,0 +1,5 @@
+describe("tests/ignored/folder-to-ignored/ pattern", () => {
+ it("should not run as ignored", () => {
+ throw new Error("test should be ignored by eager test loading");
+ });
+});
diff --git a/tools/e2e-tests/apps/react-router/tests/ignored/integration/test-ignored.app-test.js b/tools/e2e-tests/apps/react-router/tests/ignored/integration/test-ignored.app-test.js
new file mode 100644
index 0000000000..6c85dda4f6
--- /dev/null
+++ b/tools/e2e-tests/apps/react-router/tests/ignored/integration/test-ignored.app-test.js
@@ -0,0 +1,5 @@
+describe("tests/ignored/integration/*-ignored.app-test.js pattern", () => {
+ it("should not run as ignored", () => {
+ throw new Error("test should be ignored by eager test loading");
+ });
+});
diff --git a/tools/e2e-tests/apps/react-router/tests/ignored/prefix-test-ignored/ignore.app-test.js b/tools/e2e-tests/apps/react-router/tests/ignored/prefix-test-ignored/ignore.app-test.js
new file mode 100644
index 0000000000..a8a1c30c5a
--- /dev/null
+++ b/tools/e2e-tests/apps/react-router/tests/ignored/prefix-test-ignored/ignore.app-test.js
@@ -0,0 +1,5 @@
+describe("tests/ignored/prefix-*-ignored pattern", () => {
+ it("should not run as ignored", () => {
+ throw new Error("test should be ignored by eager test loading");
+ });
+});
diff --git a/tools/e2e-tests/apps/react-router/tests/ignored/react-router-wxyz-ignored/ignore.app-test.js b/tools/e2e-tests/apps/react-router/tests/ignored/react-router-wxyz-ignored/ignore.app-test.js
new file mode 100644
index 0000000000..217570f7b1
--- /dev/null
+++ b/tools/e2e-tests/apps/react-router/tests/ignored/react-router-wxyz-ignored/ignore.app-test.js
@@ -0,0 +1,5 @@
+describe("tests/ignored/react-router*-ignored pattern", () => {
+ it("should not run as ignored", () => {
+ throw new Error("test should be ignored by eager test loading");
+ });
+});
diff --git a/tools/e2e-tests/apps/react-router/tests/ignored/some-test-ignored-suffix/ignore.app-test.js b/tools/e2e-tests/apps/react-router/tests/ignored/some-test-ignored-suffix/ignore.app-test.js
new file mode 100644
index 0000000000..ad7167d7c5
--- /dev/null
+++ b/tools/e2e-tests/apps/react-router/tests/ignored/some-test-ignored-suffix/ignore.app-test.js
@@ -0,0 +1,5 @@
+describe("tests/ignored/*-ignored-suffix pattern", () => {
+ it("should not run as ignored", () => {
+ throw new Error("test should be ignored by eager test loading");
+ });
+});
diff --git a/tools/e2e-tests/apps/react-router/tests/ignored/some/nested/glob-ignored/ignore.app-test.js b/tools/e2e-tests/apps/react-router/tests/ignored/some/nested/glob-ignored/ignore.app-test.js
new file mode 100644
index 0000000000..403124d0b5
--- /dev/null
+++ b/tools/e2e-tests/apps/react-router/tests/ignored/some/nested/glob-ignored/ignore.app-test.js
@@ -0,0 +1,5 @@
+describe("tests/ignored/some/nested/glob-ignored/ignore.app-test.js pattern", () => {
+ it("should not run as ignored", () => {
+ throw new Error("test should be ignored by eager test loading");
+ });
+});
diff --git a/tools/e2e-tests/apps/react-router/tests/ignored/some/unit/test-ignored.app-test.js b/tools/e2e-tests/apps/react-router/tests/ignored/some/unit/test-ignored.app-test.js
new file mode 100644
index 0000000000..3d8cab2346
--- /dev/null
+++ b/tools/e2e-tests/apps/react-router/tests/ignored/some/unit/test-ignored.app-test.js
@@ -0,0 +1,5 @@
+describe("tests/ignored/some/unit/test-ignored.app-test.js pattern", () => {
+ it("should not run as ignored", () => {
+ throw new Error("test should be ignored by eager test loading");
+ });
+});
diff --git a/tools/e2e-tests/apps/react-router/tests/ignored/specific-file-ignored.app-test.js b/tools/e2e-tests/apps/react-router/tests/ignored/specific-file-ignored.app-test.js
new file mode 100644
index 0000000000..4eda02edf5
--- /dev/null
+++ b/tools/e2e-tests/apps/react-router/tests/ignored/specific-file-ignored.app-test.js
@@ -0,0 +1,5 @@
+describe("tests/ignored/specific-file-ignored.app-test.js pattern", () => {
+ it("should not run as ignored", () => {
+ throw new Error("test should be ignored by eager test loading");
+ });
+});
diff --git a/tools/e2e-tests/apps/react-router/tests/ignored/test-example-ignored.app-test.js b/tools/e2e-tests/apps/react-router/tests/ignored/test-example-ignored.app-test.js
new file mode 100644
index 0000000000..9fdbbda403
--- /dev/null
+++ b/tools/e2e-tests/apps/react-router/tests/ignored/test-example-ignored.app-test.js
@@ -0,0 +1,5 @@
+describe("tests/ignored/test-*-ignored.app-test.js pattern", () => {
+ it("should not run as ignored", () => {
+ throw new Error("test should be ignored by eager test loading");
+ });
+});
diff --git a/tools/e2e-tests/apps/react-router/tests/ignored/test.temp-ignored.app-test.js b/tools/e2e-tests/apps/react-router/tests/ignored/test.temp-ignored.app-test.js
new file mode 100644
index 0000000000..72d4d3d0e1
--- /dev/null
+++ b/tools/e2e-tests/apps/react-router/tests/ignored/test.temp-ignored.app-test.js
@@ -0,0 +1,5 @@
+describe("tests/ignored/*.temp-ignored.app-test.js pattern", () => {
+ it("should not run as ignored", () => {
+ throw new Error("test should be ignored by eager test loading");
+ });
+});
diff --git a/tools/modern-tests/apps/react-router/tests/main.app-test.js b/tools/e2e-tests/apps/react-router/tests/main.app-test.js
similarity index 100%
rename from tools/modern-tests/apps/react-router/tests/main.app-test.js
rename to tools/e2e-tests/apps/react-router/tests/main.app-test.js
diff --git a/tools/modern-tests/apps/react/.gitignore b/tools/e2e-tests/apps/react/.gitignore
similarity index 100%
rename from tools/modern-tests/apps/react/.gitignore
rename to tools/e2e-tests/apps/react/.gitignore
diff --git a/tools/modern-tests/apps/react/.meteor/.gitignore b/tools/e2e-tests/apps/react/.meteor/.gitignore
similarity index 100%
rename from tools/modern-tests/apps/react/.meteor/.gitignore
rename to tools/e2e-tests/apps/react/.meteor/.gitignore
diff --git a/tools/modern-tests/apps/react/.meteor/.id b/tools/e2e-tests/apps/react/.meteor/.id
similarity index 100%
rename from tools/modern-tests/apps/react/.meteor/.id
rename to tools/e2e-tests/apps/react/.meteor/.id
diff --git a/tools/modern-tests/apps/react/.meteor/packages b/tools/e2e-tests/apps/react/.meteor/packages
similarity index 100%
rename from tools/modern-tests/apps/react/.meteor/packages
rename to tools/e2e-tests/apps/react/.meteor/packages
diff --git a/tools/modern-tests/apps/react/.meteor/platforms b/tools/e2e-tests/apps/react/.meteor/platforms
similarity index 100%
rename from tools/modern-tests/apps/react/.meteor/platforms
rename to tools/e2e-tests/apps/react/.meteor/platforms
diff --git a/tools/modern-tests/apps/react/.meteor/release b/tools/e2e-tests/apps/react/.meteor/release
similarity index 100%
rename from tools/modern-tests/apps/react/.meteor/release
rename to tools/e2e-tests/apps/react/.meteor/release
diff --git a/tools/modern-tests/apps/react/.meteor/versions b/tools/e2e-tests/apps/react/.meteor/versions
similarity index 100%
rename from tools/modern-tests/apps/react/.meteor/versions
rename to tools/e2e-tests/apps/react/.meteor/versions
diff --git a/tools/modern-tests/apps/react/client/main.html b/tools/e2e-tests/apps/react/client/main.html
similarity index 100%
rename from tools/modern-tests/apps/react/client/main.html
rename to tools/e2e-tests/apps/react/client/main.html
diff --git a/tools/modern-tests/apps/react/client/main.jsx b/tools/e2e-tests/apps/react/client/main.jsx
similarity index 100%
rename from tools/modern-tests/apps/react/client/main.jsx
rename to tools/e2e-tests/apps/react/client/main.jsx
diff --git a/tools/modern-tests/apps/react/imports/api/links.js b/tools/e2e-tests/apps/react/imports/api/links.js
similarity index 100%
rename from tools/modern-tests/apps/react/imports/api/links.js
rename to tools/e2e-tests/apps/react/imports/api/links.js
diff --git a/tools/modern-tests/apps/react/imports/ui/App.jsx b/tools/e2e-tests/apps/react/imports/ui/App.jsx
similarity index 100%
rename from tools/modern-tests/apps/react/imports/ui/App.jsx
rename to tools/e2e-tests/apps/react/imports/ui/App.jsx
diff --git a/tools/modern-tests/apps/react/imports/ui/Hello.jsx b/tools/e2e-tests/apps/react/imports/ui/Hello.jsx
similarity index 100%
rename from tools/modern-tests/apps/react/imports/ui/Hello.jsx
rename to tools/e2e-tests/apps/react/imports/ui/Hello.jsx
diff --git a/tools/modern-tests/apps/react/imports/ui/Info.jsx b/tools/e2e-tests/apps/react/imports/ui/Info.jsx
similarity index 100%
rename from tools/modern-tests/apps/react/imports/ui/Info.jsx
rename to tools/e2e-tests/apps/react/imports/ui/Info.jsx
diff --git a/tools/modern-tests/apps/react/imports/ui/images/1x1-js.png b/tools/e2e-tests/apps/react/imports/ui/images/1x1-js.png
similarity index 100%
rename from tools/modern-tests/apps/react/imports/ui/images/1x1-js.png
rename to tools/e2e-tests/apps/react/imports/ui/images/1x1-js.png
diff --git a/tools/modern-tests/apps/react/imports/ui/main.css b/tools/e2e-tests/apps/react/imports/ui/main.css
similarity index 100%
rename from tools/modern-tests/apps/react/imports/ui/main.css
rename to tools/e2e-tests/apps/react/imports/ui/main.css
diff --git a/tools/modern-tests/apps/react/package.json b/tools/e2e-tests/apps/react/package.json
similarity index 100%
rename from tools/modern-tests/apps/react/package.json
rename to tools/e2e-tests/apps/react/package.json
diff --git a/tools/modern-tests/apps/react/plugins/CustomConsoleLogPlugin.js b/tools/e2e-tests/apps/react/plugins/CustomConsoleLogPlugin.js
similarity index 100%
rename from tools/modern-tests/apps/react/plugins/CustomConsoleLogPlugin.js
rename to tools/e2e-tests/apps/react/plugins/CustomConsoleLogPlugin.js
diff --git a/tools/modern-tests/apps/react/public/1x1-css.png b/tools/e2e-tests/apps/react/public/1x1-css.png
similarity index 100%
rename from tools/modern-tests/apps/react/public/1x1-css.png
rename to tools/e2e-tests/apps/react/public/1x1-css.png
diff --git a/tools/modern-tests/apps/react/public/1x1-public.jpg b/tools/e2e-tests/apps/react/public/1x1-public.jpg
similarity index 100%
rename from tools/modern-tests/apps/react/public/1x1-public.jpg
rename to tools/e2e-tests/apps/react/public/1x1-public.jpg
diff --git a/tools/modern-tests/apps/react/rspack.config.cjs b/tools/e2e-tests/apps/react/rspack.config.cjs
similarity index 100%
rename from tools/modern-tests/apps/react/rspack.config.cjs
rename to tools/e2e-tests/apps/react/rspack.config.cjs
diff --git a/tools/modern-tests/apps/react/server/main.js b/tools/e2e-tests/apps/react/server/main.js
similarity index 100%
rename from tools/modern-tests/apps/react/server/main.js
rename to tools/e2e-tests/apps/react/server/main.js
diff --git a/tools/modern-tests/apps/react/tests/main.js b/tools/e2e-tests/apps/react/tests/main.js
similarity index 100%
rename from tools/modern-tests/apps/react/tests/main.js
rename to tools/e2e-tests/apps/react/tests/main.js
diff --git a/tools/modern-tests/apps/solid/.meteor/.gitignore b/tools/e2e-tests/apps/server-only/.meteor/.gitignore
similarity index 100%
rename from tools/modern-tests/apps/solid/.meteor/.gitignore
rename to tools/e2e-tests/apps/server-only/.meteor/.gitignore
diff --git a/tools/e2e-tests/apps/server-only/.meteor/.id b/tools/e2e-tests/apps/server-only/.meteor/.id
new file mode 100644
index 0000000000..33386e121f
--- /dev/null
+++ b/tools/e2e-tests/apps/server-only/.meteor/.id
@@ -0,0 +1,7 @@
+# This file contains a token that is unique to your project.
+# Check it into your repository along with the rest of this directory.
+# It can be used for purposes such as:
+# - ensuring you don't accidentally deploy one app on top of another
+# - providing package authors with aggregated statistics
+
+l2d6s2mpmf9d.hefjva6hrzxq
diff --git a/tools/e2e-tests/apps/server-only/.meteor/packages b/tools/e2e-tests/apps/server-only/.meteor/packages
new file mode 100644
index 0000000000..0743c87631
--- /dev/null
+++ b/tools/e2e-tests/apps/server-only/.meteor/packages
@@ -0,0 +1,20 @@
+# Meteor packages used by this project, one per line.
+# Check this file (and the other files in this directory) into your repository.
+#
+# 'meteor add' and 'meteor remove' will edit this file for you,
+# but you can also edit it by hand.
+
+meteor-base@1.5.2 # Packages every Meteor app needs to have
+mobile-experience@1.1.2 # Packages for a great mobile UX
+mongo@2.2.0 # The database Meteor supports right now
+static-html@1.5.0 # Define static page content in .html files
+reactive-var@1.0.13 # Reactive variable for tracker
+tracker@1.3.4 # Meteor's client-side reactive programming library
+
+standard-minifier-css@1.10.0 # CSS minifier run for production mode
+standard-minifier-js@3.2.0 # JS minifier run for production mode
+es5-shim@4.8.1 # ECMAScript 5 compatibility for older browsers
+ecmascript@0.17.0 # Enable ECMAScript2015+ syntax in app code
+typescript@5.9.3 # Enable TypeScript syntax in .ts and .tsx modules
+shell-server@0.7.0 # Server-side component of the `meteor shell` command
+rspack
diff --git a/tools/modern-tests/apps/solid/.meteor/platforms b/tools/e2e-tests/apps/server-only/.meteor/platforms
similarity index 100%
rename from tools/modern-tests/apps/solid/.meteor/platforms
rename to tools/e2e-tests/apps/server-only/.meteor/platforms
diff --git a/tools/modern-tests/apps/solid/.meteor/release b/tools/e2e-tests/apps/server-only/.meteor/release
similarity index 100%
rename from tools/modern-tests/apps/solid/.meteor/release
rename to tools/e2e-tests/apps/server-only/.meteor/release
diff --git a/tools/e2e-tests/apps/server-only/.meteor/versions b/tools/e2e-tests/apps/server-only/.meteor/versions
new file mode 100644
index 0000000000..7e24fc06e4
--- /dev/null
+++ b/tools/e2e-tests/apps/server-only/.meteor/versions
@@ -0,0 +1,66 @@
+allow-deny@2.1.0
+autoupdate@2.0.1
+babel-compiler@7.13.0
+babel-runtime@1.5.2
+base64@1.0.13
+binary-heap@1.0.12
+boilerplate-generator@2.1.0
+caching-compiler@2.0.1
+callback-hook@1.6.1
+check@1.5.0
+core-runtime@1.0.0
+ddp@1.4.2
+ddp-client@3.1.1
+ddp-common@1.4.4
+ddp-server@3.1.2
+diff-sequence@1.1.3
+dynamic-import@0.7.4
+ecmascript@0.17.0
+ecmascript-runtime@0.8.3
+ecmascript-runtime-client@0.12.3
+ecmascript-runtime-server@0.11.1
+ejson@1.1.5
+es5-shim@4.8.1
+facts-base@1.0.2
+fetch@0.1.6
+geojson-utils@1.0.12
+hot-code-push@1.0.5
+id-map@1.2.0
+inter-process-messaging@0.1.2
+launch-screen@2.0.1
+logging@1.3.6
+meteor@2.2.0
+meteor-base@1.5.2
+minifier-css@2.0.1
+minifier-js@3.1.0
+minimongo@2.0.5
+mobile-experience@1.1.2
+mobile-status-bar@1.1.1
+modern-browsers@0.2.3
+modules@0.20.3
+modules-runtime@0.13.2
+mongo@2.2.0
+mongo-decimal@0.2.0
+mongo-dev-server@1.1.1
+mongo-id@1.0.9
+npm-mongo@6.16.1
+ordered-dict@1.2.0
+promise@1.0.0
+random@1.2.2
+react-fast-refresh@0.3.0
+reactive-var@1.0.13
+reload@1.3.2
+retry@1.1.1
+routepolicy@1.1.2
+rspack@1.0.0
+shell-server@0.7.0
+socket-stream-client@0.6.1
+standard-minifier-css@1.10.0
+standard-minifier-js@3.2.0
+static-html@1.5.0
+static-html-tools@1.0.0
+tools-core@1.0.0
+tracker@1.3.4
+typescript@5.9.3
+webapp@2.1.0
+webapp-hashing@1.1.2
diff --git a/tools/e2e-tests/apps/server-only/package.json b/tools/e2e-tests/apps/server-only/package.json
new file mode 100644
index 0000000000..2b9704d1f7
--- /dev/null
+++ b/tools/e2e-tests/apps/server-only/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "server-only",
+ "private": true,
+ "scripts": {
+ "start": "meteor run"
+ },
+ "dependencies": {
+ "@babel/runtime": "^7.23.5",
+ "meteor-node-stubs": "^1.2.12"
+ },
+ "meteor": {
+ "mainModule": {
+ "server": "server/main.js"
+ },
+ "modern": true
+ }
+}
diff --git a/tools/e2e-tests/apps/server-only/rspack.config.js b/tools/e2e-tests/apps/server-only/rspack.config.js
new file mode 100644
index 0000000000..a6434b4e45
--- /dev/null
+++ b/tools/e2e-tests/apps/server-only/rspack.config.js
@@ -0,0 +1,5 @@
+const { defineConfig } = require('@meteorjs/rspack');
+
+module.exports = defineConfig(() => {
+ return {};
+});
diff --git a/tools/e2e-tests/apps/server-only/server/main.js b/tools/e2e-tests/apps/server-only/server/main.js
new file mode 100644
index 0000000000..f24ca7f09f
--- /dev/null
+++ b/tools/e2e-tests/apps/server-only/server/main.js
@@ -0,0 +1 @@
+console.log('server/main.js loaded');
\ No newline at end of file
diff --git a/tools/modern-tests/apps/solid/.gitignore b/tools/e2e-tests/apps/solid/.gitignore
similarity index 100%
rename from tools/modern-tests/apps/solid/.gitignore
rename to tools/e2e-tests/apps/solid/.gitignore
diff --git a/tools/modern-tests/apps/svelte/.meteor/.gitignore b/tools/e2e-tests/apps/solid/.meteor/.gitignore
similarity index 100%
rename from tools/modern-tests/apps/svelte/.meteor/.gitignore
rename to tools/e2e-tests/apps/solid/.meteor/.gitignore
diff --git a/tools/modern-tests/apps/solid/.meteor/.id b/tools/e2e-tests/apps/solid/.meteor/.id
similarity index 100%
rename from tools/modern-tests/apps/solid/.meteor/.id
rename to tools/e2e-tests/apps/solid/.meteor/.id
diff --git a/tools/modern-tests/apps/solid/.meteor/packages b/tools/e2e-tests/apps/solid/.meteor/packages
similarity index 100%
rename from tools/modern-tests/apps/solid/.meteor/packages
rename to tools/e2e-tests/apps/solid/.meteor/packages
diff --git a/tools/modern-tests/apps/svelte/.meteor/platforms b/tools/e2e-tests/apps/solid/.meteor/platforms
similarity index 100%
rename from tools/modern-tests/apps/svelte/.meteor/platforms
rename to tools/e2e-tests/apps/solid/.meteor/platforms
diff --git a/tools/modern-tests/apps/svelte/.meteor/release b/tools/e2e-tests/apps/solid/.meteor/release
similarity index 100%
rename from tools/modern-tests/apps/svelte/.meteor/release
rename to tools/e2e-tests/apps/solid/.meteor/release
diff --git a/tools/modern-tests/apps/solid/.meteor/versions b/tools/e2e-tests/apps/solid/.meteor/versions
similarity index 100%
rename from tools/modern-tests/apps/solid/.meteor/versions
rename to tools/e2e-tests/apps/solid/.meteor/versions
diff --git a/tools/modern-tests/apps/solid/client/main.html b/tools/e2e-tests/apps/solid/client/main.html
similarity index 100%
rename from tools/modern-tests/apps/solid/client/main.html
rename to tools/e2e-tests/apps/solid/client/main.html
diff --git a/tools/modern-tests/apps/solid/client/main.js b/tools/e2e-tests/apps/solid/client/main.js
similarity index 100%
rename from tools/modern-tests/apps/solid/client/main.js
rename to tools/e2e-tests/apps/solid/client/main.js
diff --git a/tools/modern-tests/apps/solid/imports/api/links.js b/tools/e2e-tests/apps/solid/imports/api/links.js
similarity index 100%
rename from tools/modern-tests/apps/solid/imports/api/links.js
rename to tools/e2e-tests/apps/solid/imports/api/links.js
diff --git a/tools/modern-tests/apps/solid/imports/ui/App.jsx b/tools/e2e-tests/apps/solid/imports/ui/App.jsx
similarity index 100%
rename from tools/modern-tests/apps/solid/imports/ui/App.jsx
rename to tools/e2e-tests/apps/solid/imports/ui/App.jsx
diff --git a/tools/modern-tests/apps/solid/imports/ui/Hello.jsx b/tools/e2e-tests/apps/solid/imports/ui/Hello.jsx
similarity index 100%
rename from tools/modern-tests/apps/solid/imports/ui/Hello.jsx
rename to tools/e2e-tests/apps/solid/imports/ui/Hello.jsx
diff --git a/tools/modern-tests/apps/solid/imports/ui/Info.jsx b/tools/e2e-tests/apps/solid/imports/ui/Info.jsx
similarity index 100%
rename from tools/modern-tests/apps/solid/imports/ui/Info.jsx
rename to tools/e2e-tests/apps/solid/imports/ui/Info.jsx
diff --git a/tools/modern-tests/apps/solid/imports/ui/main.css b/tools/e2e-tests/apps/solid/imports/ui/main.css
similarity index 100%
rename from tools/modern-tests/apps/solid/imports/ui/main.css
rename to tools/e2e-tests/apps/solid/imports/ui/main.css
diff --git a/tools/modern-tests/apps/solid/imports/ui/main.jsx b/tools/e2e-tests/apps/solid/imports/ui/main.jsx
similarity index 100%
rename from tools/modern-tests/apps/solid/imports/ui/main.jsx
rename to tools/e2e-tests/apps/solid/imports/ui/main.jsx
diff --git a/tools/modern-tests/apps/solid/package.json b/tools/e2e-tests/apps/solid/package.json
similarity index 95%
rename from tools/modern-tests/apps/solid/package.json
rename to tools/e2e-tests/apps/solid/package.json
index 104f5258ef..5ce93de638 100644
--- a/tools/modern-tests/apps/solid/package.json
+++ b/tools/e2e-tests/apps/solid/package.json
@@ -22,7 +22,7 @@
"modern": true
},
"devDependencies": {
- "@meteorjs/rspack": "^0.0.29",
+ "@meteorjs/rspack": "^1.1.0-beta.31",
"@rspack/cli": "^1.4.8",
"@rspack/core": "^1.4.8",
"babel-loader": "10.0.0",
diff --git a/tools/modern-tests/apps/solid/rspack.config.js b/tools/e2e-tests/apps/solid/rspack.config.js
similarity index 100%
rename from tools/modern-tests/apps/solid/rspack.config.js
rename to tools/e2e-tests/apps/solid/rspack.config.js
diff --git a/tools/modern-tests/apps/solid/server/main.js b/tools/e2e-tests/apps/solid/server/main.js
similarity index 100%
rename from tools/modern-tests/apps/solid/server/main.js
rename to tools/e2e-tests/apps/solid/server/main.js
diff --git a/tools/modern-tests/apps/solid/tests/main.js b/tools/e2e-tests/apps/solid/tests/main.js
similarity index 100%
rename from tools/modern-tests/apps/solid/tests/main.js
rename to tools/e2e-tests/apps/solid/tests/main.js
diff --git a/tools/modern-tests/apps/svelte/.gitignore b/tools/e2e-tests/apps/svelte/.gitignore
similarity index 100%
rename from tools/modern-tests/apps/svelte/.gitignore
rename to tools/e2e-tests/apps/svelte/.gitignore
diff --git a/tools/modern-tests/apps/typescript/.meteor/.gitignore b/tools/e2e-tests/apps/svelte/.meteor/.gitignore
similarity index 100%
rename from tools/modern-tests/apps/typescript/.meteor/.gitignore
rename to tools/e2e-tests/apps/svelte/.meteor/.gitignore
diff --git a/tools/modern-tests/apps/svelte/.meteor/.id b/tools/e2e-tests/apps/svelte/.meteor/.id
similarity index 100%
rename from tools/modern-tests/apps/svelte/.meteor/.id
rename to tools/e2e-tests/apps/svelte/.meteor/.id
diff --git a/tools/modern-tests/apps/svelte/.meteor/packages b/tools/e2e-tests/apps/svelte/.meteor/packages
similarity index 100%
rename from tools/modern-tests/apps/svelte/.meteor/packages
rename to tools/e2e-tests/apps/svelte/.meteor/packages
diff --git a/tools/modern-tests/apps/typescript/.meteor/platforms b/tools/e2e-tests/apps/svelte/.meteor/platforms
similarity index 100%
rename from tools/modern-tests/apps/typescript/.meteor/platforms
rename to tools/e2e-tests/apps/svelte/.meteor/platforms
diff --git a/tools/modern-tests/apps/typescript/.meteor/release b/tools/e2e-tests/apps/svelte/.meteor/release
similarity index 100%
rename from tools/modern-tests/apps/typescript/.meteor/release
rename to tools/e2e-tests/apps/svelte/.meteor/release
diff --git a/tools/modern-tests/apps/svelte/.meteor/versions b/tools/e2e-tests/apps/svelte/.meteor/versions
similarity index 100%
rename from tools/modern-tests/apps/svelte/.meteor/versions
rename to tools/e2e-tests/apps/svelte/.meteor/versions
diff --git a/tools/modern-tests/apps/svelte/client/main.css b/tools/e2e-tests/apps/svelte/client/main.css
similarity index 100%
rename from tools/modern-tests/apps/svelte/client/main.css
rename to tools/e2e-tests/apps/svelte/client/main.css
diff --git a/tools/modern-tests/apps/svelte/client/main.html b/tools/e2e-tests/apps/svelte/client/main.html
similarity index 100%
rename from tools/modern-tests/apps/svelte/client/main.html
rename to tools/e2e-tests/apps/svelte/client/main.html
diff --git a/tools/modern-tests/apps/svelte/client/main.js b/tools/e2e-tests/apps/svelte/client/main.js
similarity index 100%
rename from tools/modern-tests/apps/svelte/client/main.js
rename to tools/e2e-tests/apps/svelte/client/main.js
diff --git a/tools/modern-tests/apps/svelte/imports/api/links.js b/tools/e2e-tests/apps/svelte/imports/api/links.js
similarity index 100%
rename from tools/modern-tests/apps/svelte/imports/api/links.js
rename to tools/e2e-tests/apps/svelte/imports/api/links.js
diff --git a/tools/modern-tests/apps/svelte/imports/ui/App.svelte b/tools/e2e-tests/apps/svelte/imports/ui/App.svelte
similarity index 100%
rename from tools/modern-tests/apps/svelte/imports/ui/App.svelte
rename to tools/e2e-tests/apps/svelte/imports/ui/App.svelte
diff --git a/tools/modern-tests/apps/svelte/package.json b/tools/e2e-tests/apps/svelte/package.json
similarity index 95%
rename from tools/modern-tests/apps/svelte/package.json
rename to tools/e2e-tests/apps/svelte/package.json
index a9873d4aa5..cba0d4e7a0 100644
--- a/tools/modern-tests/apps/svelte/package.json
+++ b/tools/e2e-tests/apps/svelte/package.json
@@ -13,7 +13,7 @@
"meteor-node-stubs": "^1.2.12"
},
"devDependencies": {
- "@meteorjs/rspack": "^0.0.28",
+ "@meteorjs/rspack": "^1.1.0-beta.31",
"@rspack/cli": "^1.4.8",
"@rspack/core": "^1.4.8",
"playwright": "1.58.0",
diff --git a/tools/modern-tests/apps/svelte/rspack.config.js b/tools/e2e-tests/apps/svelte/rspack.config.js
similarity index 100%
rename from tools/modern-tests/apps/svelte/rspack.config.js
rename to tools/e2e-tests/apps/svelte/rspack.config.js
diff --git a/tools/modern-tests/apps/svelte/server/main.js b/tools/e2e-tests/apps/svelte/server/main.js
similarity index 100%
rename from tools/modern-tests/apps/svelte/server/main.js
rename to tools/e2e-tests/apps/svelte/server/main.js
diff --git a/tools/modern-tests/apps/svelte/tests/main.js b/tools/e2e-tests/apps/svelte/tests/main.js
similarity index 100%
rename from tools/modern-tests/apps/svelte/tests/main.js
rename to tools/e2e-tests/apps/svelte/tests/main.js
diff --git a/tools/modern-tests/apps/svelte/tsconfig.json b/tools/e2e-tests/apps/svelte/tsconfig.json
similarity index 100%
rename from tools/modern-tests/apps/svelte/tsconfig.json
rename to tools/e2e-tests/apps/svelte/tsconfig.json
diff --git a/tools/modern-tests/apps/typescript/.gitignore b/tools/e2e-tests/apps/typescript/.gitignore
similarity index 100%
rename from tools/modern-tests/apps/typescript/.gitignore
rename to tools/e2e-tests/apps/typescript/.gitignore
diff --git a/tools/modern-tests/apps/vue/.meteor/.gitignore b/tools/e2e-tests/apps/typescript/.meteor/.gitignore
similarity index 100%
rename from tools/modern-tests/apps/vue/.meteor/.gitignore
rename to tools/e2e-tests/apps/typescript/.meteor/.gitignore
diff --git a/tools/modern-tests/apps/typescript/.meteor/.id b/tools/e2e-tests/apps/typescript/.meteor/.id
similarity index 100%
rename from tools/modern-tests/apps/typescript/.meteor/.id
rename to tools/e2e-tests/apps/typescript/.meteor/.id
diff --git a/tools/modern-tests/apps/typescript/.meteor/packages b/tools/e2e-tests/apps/typescript/.meteor/packages
similarity index 100%
rename from tools/modern-tests/apps/typescript/.meteor/packages
rename to tools/e2e-tests/apps/typescript/.meteor/packages
diff --git a/tools/modern-tests/apps/vue/.meteor/platforms b/tools/e2e-tests/apps/typescript/.meteor/platforms
similarity index 100%
rename from tools/modern-tests/apps/vue/.meteor/platforms
rename to tools/e2e-tests/apps/typescript/.meteor/platforms
diff --git a/tools/modern-tests/apps/vue/.meteor/release b/tools/e2e-tests/apps/typescript/.meteor/release
similarity index 100%
rename from tools/modern-tests/apps/vue/.meteor/release
rename to tools/e2e-tests/apps/typescript/.meteor/release
diff --git a/tools/modern-tests/apps/typescript/.meteor/versions b/tools/e2e-tests/apps/typescript/.meteor/versions
similarity index 100%
rename from tools/modern-tests/apps/typescript/.meteor/versions
rename to tools/e2e-tests/apps/typescript/.meteor/versions
diff --git a/tools/modern-tests/apps/typescript/client/main.html b/tools/e2e-tests/apps/typescript/client/main.html
similarity index 100%
rename from tools/modern-tests/apps/typescript/client/main.html
rename to tools/e2e-tests/apps/typescript/client/main.html
diff --git a/tools/modern-tests/apps/typescript/client/main.scss b/tools/e2e-tests/apps/typescript/client/main.scss
similarity index 100%
rename from tools/modern-tests/apps/typescript/client/main.scss
rename to tools/e2e-tests/apps/typescript/client/main.scss
diff --git a/tools/modern-tests/apps/typescript/client/main.tsx b/tools/e2e-tests/apps/typescript/client/main.tsx
similarity index 90%
rename from tools/modern-tests/apps/typescript/client/main.tsx
rename to tools/e2e-tests/apps/typescript/client/main.tsx
index e576e1b803..4eb40f49d1 100644
--- a/tools/modern-tests/apps/typescript/client/main.tsx
+++ b/tools/e2e-tests/apps/typescript/client/main.tsx
@@ -1,4 +1,3 @@
-import React from 'react';
import { createRoot } from 'react-dom/client';
import { Meteor } from 'meteor/meteor';
import { App } from '@ui/App';
diff --git a/tools/modern-tests/apps/typescript/imports/api/links.ts b/tools/e2e-tests/apps/typescript/imports/api/links.ts
similarity index 100%
rename from tools/modern-tests/apps/typescript/imports/api/links.ts
rename to tools/e2e-tests/apps/typescript/imports/api/links.ts
diff --git a/tools/modern-tests/apps/typescript/imports/ui/App.tsx b/tools/e2e-tests/apps/typescript/imports/ui/App.tsx
similarity index 87%
rename from tools/modern-tests/apps/typescript/imports/ui/App.tsx
rename to tools/e2e-tests/apps/typescript/imports/ui/App.tsx
index 52f71448cc..94679c5547 100644
--- a/tools/modern-tests/apps/typescript/imports/ui/App.tsx
+++ b/tools/e2e-tests/apps/typescript/imports/ui/App.tsx
@@ -1,4 +1,3 @@
-import React from 'react';
import './Global.scss';
import { Hello } from './Hello';
import { Info } from './Info';
diff --git a/tools/modern-tests/apps/typescript/imports/ui/Global.scss b/tools/e2e-tests/apps/typescript/imports/ui/Global.scss
similarity index 100%
rename from tools/modern-tests/apps/typescript/imports/ui/Global.scss
rename to tools/e2e-tests/apps/typescript/imports/ui/Global.scss
diff --git a/tools/modern-tests/apps/typescript/imports/ui/Hello.tsx b/tools/e2e-tests/apps/typescript/imports/ui/Hello.tsx
similarity index 87%
rename from tools/modern-tests/apps/typescript/imports/ui/Hello.tsx
rename to tools/e2e-tests/apps/typescript/imports/ui/Hello.tsx
index 15e0f185ac..527d5af607 100644
--- a/tools/modern-tests/apps/typescript/imports/ui/Hello.tsx
+++ b/tools/e2e-tests/apps/typescript/imports/ui/Hello.tsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import { useState } from 'react';
export const Hello = () => {
const [counter, setCounter] = useState(0);
diff --git a/tools/modern-tests/apps/typescript/imports/ui/Info.tsx b/tools/e2e-tests/apps/typescript/imports/ui/Info.tsx
similarity index 95%
rename from tools/modern-tests/apps/typescript/imports/ui/Info.tsx
rename to tools/e2e-tests/apps/typescript/imports/ui/Info.tsx
index 23cb8f07a3..809fbc6716 100644
--- a/tools/modern-tests/apps/typescript/imports/ui/Info.tsx
+++ b/tools/e2e-tests/apps/typescript/imports/ui/Info.tsx
@@ -1,4 +1,3 @@
-import React from "react";
import { useFind, useSubscribe } from "meteor/react-meteor-data";
import { LinksCollection, Link } from "../api/links";
diff --git a/tools/modern-tests/apps/typescript/package.json b/tools/e2e-tests/apps/typescript/package.json
similarity index 97%
rename from tools/modern-tests/apps/typescript/package.json
rename to tools/e2e-tests/apps/typescript/package.json
index d1ac310541..f0359b6094 100644
--- a/tools/modern-tests/apps/typescript/package.json
+++ b/tools/e2e-tests/apps/typescript/package.json
@@ -15,6 +15,7 @@
"react-dom": "^18.2.0"
},
"devDependencies": {
+ "@swc/core": "^1.15.18",
"@types/meteor": "^2.9.9",
"@types/mocha": "^8.2.3",
"@types/node": "^22.10.6",
diff --git a/tools/modern-tests/apps/typescript/rspack.config.ts b/tools/e2e-tests/apps/typescript/rspack.config.ts
similarity index 96%
rename from tools/modern-tests/apps/typescript/rspack.config.ts
rename to tools/e2e-tests/apps/typescript/rspack.config.ts
index 35ec5ffdad..f4be271f64 100644
--- a/tools/modern-tests/apps/typescript/rspack.config.ts
+++ b/tools/e2e-tests/apps/typescript/rspack.config.ts
@@ -16,6 +16,7 @@ const require = createRequire(import.meta.url);
*/
export default defineConfig(Meteor => {
return {
+ ...Meteor.enablePortableBuild(),
...Meteor.extendSwcConfig({
jsc: {
baseUrl: process.cwd(),
diff --git a/tools/modern-tests/apps/typescript/server/main.ts b/tools/e2e-tests/apps/typescript/server/main.ts
similarity index 100%
rename from tools/modern-tests/apps/typescript/server/main.ts
rename to tools/e2e-tests/apps/typescript/server/main.ts
diff --git a/tools/e2e-tests/apps/typescript/swc.config.ts b/tools/e2e-tests/apps/typescript/swc.config.ts
new file mode 100644
index 0000000000..699a33a40d
--- /dev/null
+++ b/tools/e2e-tests/apps/typescript/swc.config.ts
@@ -0,0 +1,13 @@
+import type { Config } from "@swc/core";
+
+const config: Config = {
+ jsc: {
+ transform: {
+ react: {
+ runtime: "automatic",
+ },
+ },
+ },
+};
+
+export default config;
diff --git a/tools/modern-tests/apps/typescript/tests/client.ts b/tools/e2e-tests/apps/typescript/tests/client.ts
similarity index 100%
rename from tools/modern-tests/apps/typescript/tests/client.ts
rename to tools/e2e-tests/apps/typescript/tests/client.ts
diff --git a/tools/modern-tests/apps/typescript/tests/server.ts b/tools/e2e-tests/apps/typescript/tests/server.ts
similarity index 100%
rename from tools/modern-tests/apps/typescript/tests/server.ts
rename to tools/e2e-tests/apps/typescript/tests/server.ts
diff --git a/tools/modern-tests/apps/typescript/tsconfig.json b/tools/e2e-tests/apps/typescript/tsconfig.json
similarity index 100%
rename from tools/modern-tests/apps/typescript/tsconfig.json
rename to tools/e2e-tests/apps/typescript/tsconfig.json
diff --git a/tools/modern-tests/apps/vue/.gitignore b/tools/e2e-tests/apps/vue/.gitignore
similarity index 100%
rename from tools/modern-tests/apps/vue/.gitignore
rename to tools/e2e-tests/apps/vue/.gitignore
diff --git a/tools/e2e-tests/apps/vue/.meteor/.gitignore b/tools/e2e-tests/apps/vue/.meteor/.gitignore
new file mode 100644
index 0000000000..4083037423
--- /dev/null
+++ b/tools/e2e-tests/apps/vue/.meteor/.gitignore
@@ -0,0 +1 @@
+local
diff --git a/tools/modern-tests/apps/vue/.meteor/.id b/tools/e2e-tests/apps/vue/.meteor/.id
similarity index 100%
rename from tools/modern-tests/apps/vue/.meteor/.id
rename to tools/e2e-tests/apps/vue/.meteor/.id
diff --git a/tools/modern-tests/apps/vue/.meteor/packages b/tools/e2e-tests/apps/vue/.meteor/packages
similarity index 100%
rename from tools/modern-tests/apps/vue/.meteor/packages
rename to tools/e2e-tests/apps/vue/.meteor/packages
diff --git a/tools/e2e-tests/apps/vue/.meteor/platforms b/tools/e2e-tests/apps/vue/.meteor/platforms
new file mode 100644
index 0000000000..efeba1b50c
--- /dev/null
+++ b/tools/e2e-tests/apps/vue/.meteor/platforms
@@ -0,0 +1,2 @@
+server
+browser
diff --git a/tools/e2e-tests/apps/vue/.meteor/release b/tools/e2e-tests/apps/vue/.meteor/release
new file mode 100644
index 0000000000..621e94f0ec
--- /dev/null
+++ b/tools/e2e-tests/apps/vue/.meteor/release
@@ -0,0 +1 @@
+none
diff --git a/tools/modern-tests/apps/vue/.meteor/versions b/tools/e2e-tests/apps/vue/.meteor/versions
similarity index 100%
rename from tools/modern-tests/apps/vue/.meteor/versions
rename to tools/e2e-tests/apps/vue/.meteor/versions
diff --git a/tools/modern-tests/apps/vue/.meteorignore b/tools/e2e-tests/apps/vue/.meteorignore
similarity index 100%
rename from tools/modern-tests/apps/vue/.meteorignore
rename to tools/e2e-tests/apps/vue/.meteorignore
diff --git a/tools/modern-tests/apps/vue/client/main.css b/tools/e2e-tests/apps/vue/client/main.css
similarity index 100%
rename from tools/modern-tests/apps/vue/client/main.css
rename to tools/e2e-tests/apps/vue/client/main.css
diff --git a/tools/modern-tests/apps/vue/client/main.html b/tools/e2e-tests/apps/vue/client/main.html
similarity index 100%
rename from tools/modern-tests/apps/vue/client/main.html
rename to tools/e2e-tests/apps/vue/client/main.html
diff --git a/tools/modern-tests/apps/vue/client/main.js b/tools/e2e-tests/apps/vue/client/main.js
similarity index 100%
rename from tools/modern-tests/apps/vue/client/main.js
rename to tools/e2e-tests/apps/vue/client/main.js
diff --git a/tools/modern-tests/apps/vue/client/meteor.css b/tools/e2e-tests/apps/vue/client/meteor.css
similarity index 100%
rename from tools/modern-tests/apps/vue/client/meteor.css
rename to tools/e2e-tests/apps/vue/client/meteor.css
diff --git a/tools/modern-tests/apps/vue/imports/api/links.js b/tools/e2e-tests/apps/vue/imports/api/links.js
similarity index 100%
rename from tools/modern-tests/apps/vue/imports/api/links.js
rename to tools/e2e-tests/apps/vue/imports/api/links.js
diff --git a/tools/modern-tests/apps/vue/imports/ui/App.vue b/tools/e2e-tests/apps/vue/imports/ui/App.vue
similarity index 100%
rename from tools/modern-tests/apps/vue/imports/ui/App.vue
rename to tools/e2e-tests/apps/vue/imports/ui/App.vue
diff --git a/tools/modern-tests/apps/vue/imports/ui/components/AppMenu.vue b/tools/e2e-tests/apps/vue/imports/ui/components/AppMenu.vue
similarity index 100%
rename from tools/modern-tests/apps/vue/imports/ui/components/AppMenu.vue
rename to tools/e2e-tests/apps/vue/imports/ui/components/AppMenu.vue
diff --git a/tools/modern-tests/apps/vue/imports/ui/components/Hello.vue b/tools/e2e-tests/apps/vue/imports/ui/components/Hello.vue
similarity index 100%
rename from tools/modern-tests/apps/vue/imports/ui/components/Hello.vue
rename to tools/e2e-tests/apps/vue/imports/ui/components/Hello.vue
diff --git a/tools/modern-tests/apps/vue/imports/ui/components/Info.vue b/tools/e2e-tests/apps/vue/imports/ui/components/Info.vue
similarity index 100%
rename from tools/modern-tests/apps/vue/imports/ui/components/Info.vue
rename to tools/e2e-tests/apps/vue/imports/ui/components/Info.vue
diff --git a/tools/modern-tests/apps/vue/imports/ui/main.css b/tools/e2e-tests/apps/vue/imports/ui/main.css
similarity index 100%
rename from tools/modern-tests/apps/vue/imports/ui/main.css
rename to tools/e2e-tests/apps/vue/imports/ui/main.css
diff --git a/tools/modern-tests/apps/vue/imports/ui/main.js b/tools/e2e-tests/apps/vue/imports/ui/main.js
similarity index 100%
rename from tools/modern-tests/apps/vue/imports/ui/main.js
rename to tools/e2e-tests/apps/vue/imports/ui/main.js
diff --git a/tools/modern-tests/apps/vue/imports/ui/router.js b/tools/e2e-tests/apps/vue/imports/ui/router.js
similarity index 100%
rename from tools/modern-tests/apps/vue/imports/ui/router.js
rename to tools/e2e-tests/apps/vue/imports/ui/router.js
diff --git a/tools/modern-tests/apps/vue/imports/ui/views/About.vue b/tools/e2e-tests/apps/vue/imports/ui/views/About.vue
similarity index 100%
rename from tools/modern-tests/apps/vue/imports/ui/views/About.vue
rename to tools/e2e-tests/apps/vue/imports/ui/views/About.vue
diff --git a/tools/modern-tests/apps/vue/imports/ui/views/Home.vue b/tools/e2e-tests/apps/vue/imports/ui/views/Home.vue
similarity index 100%
rename from tools/modern-tests/apps/vue/imports/ui/views/Home.vue
rename to tools/e2e-tests/apps/vue/imports/ui/views/Home.vue
diff --git a/tools/modern-tests/apps/vue/package.json b/tools/e2e-tests/apps/vue/package.json
similarity index 96%
rename from tools/modern-tests/apps/vue/package.json
rename to tools/e2e-tests/apps/vue/package.json
index 3f7c62a14e..31c39838ad 100644
--- a/tools/modern-tests/apps/vue/package.json
+++ b/tools/e2e-tests/apps/vue/package.json
@@ -17,7 +17,7 @@
"vue-router": "^4.2.5"
},
"devDependencies": {
- "@meteorjs/rspack": "^1.0.1",
+ "@meteorjs/rspack": "^1.1.0-beta.31",
"@rspack/cli": "^1.4.8",
"@rspack/core": "^1.4.8",
"@tailwindcss/postcss": "^4.1.12",
diff --git a/tools/modern-tests/apps/vue/postcss.config.js b/tools/e2e-tests/apps/vue/postcss.config.js
similarity index 100%
rename from tools/modern-tests/apps/vue/postcss.config.js
rename to tools/e2e-tests/apps/vue/postcss.config.js
diff --git a/tools/modern-tests/apps/vue/rspack.config.js b/tools/e2e-tests/apps/vue/rspack.config.js
similarity index 100%
rename from tools/modern-tests/apps/vue/rspack.config.js
rename to tools/e2e-tests/apps/vue/rspack.config.js
diff --git a/tools/modern-tests/apps/vue/server/entry-meteor.js b/tools/e2e-tests/apps/vue/server/entry-meteor.js
similarity index 100%
rename from tools/modern-tests/apps/vue/server/entry-meteor.js
rename to tools/e2e-tests/apps/vue/server/entry-meteor.js
diff --git a/tools/modern-tests/apps/vue/server/main.js b/tools/e2e-tests/apps/vue/server/main.js
similarity index 100%
rename from tools/modern-tests/apps/vue/server/main.js
rename to tools/e2e-tests/apps/vue/server/main.js
diff --git a/tools/modern-tests/apps/vue/tests/main.js b/tools/e2e-tests/apps/vue/tests/main.js
similarity index 100%
rename from tools/modern-tests/apps/vue/tests/main.js
rename to tools/e2e-tests/apps/vue/tests/main.js
diff --git a/tools/modern-tests/assertions.js b/tools/e2e-tests/assertions.js
similarity index 88%
rename from tools/modern-tests/assertions.js
rename to tools/e2e-tests/assertions.js
index 4b873f6d9e..5abaad4daf 100644
--- a/tools/modern-tests/assertions.js
+++ b/tools/e2e-tests/assertions.js
@@ -102,7 +102,7 @@ export async function assertFileExist(tempDir, filePath, options = {}) {
return checkFile();
}
// If we've exceeded the timeout, fail the test
- expect(fileExists).toBe(true);
+ throw new Error(`Expected file to exist but it was not found: ${fullPath}`);
return false;
}
@@ -145,6 +145,43 @@ export async function assertFileExist(tempDir, filePath, options = {}) {
await checkFile();
}
+/**
+ * Helper function to assert that a path does NOT exist
+ * Retries until the path is gone or the timeout is exceeded
+ * @param {string} basePath - Base directory path
+ * @param {string} relPath - Relative path from basePath to check
+ * @param {Object} options - Additional options
+ * @param {number} options.timeout - Maximum time to wait in milliseconds (default: 5000)
+ * @param {number} options.checkInterval - Interval between checks in milliseconds (default: 100)
+ * @returns {Promise}
+ */
+export async function assertPathNotExist(basePath, relPath, options = {}) {
+ const { timeout = 5000, checkInterval = 100 } = options;
+ const fullPath = path.join(basePath, relPath);
+ const startTime = Date.now();
+
+ const check = async () => {
+ const exists = await fs.pathExists(fullPath);
+ if (exists && Date.now() - startTime < timeout) {
+ await new Promise(r => setTimeout(r, checkInterval));
+ return check();
+ }
+ if (exists) {
+ const stat = await fs.stat(fullPath);
+ const isDir = stat.isDirectory();
+ let contents = '';
+ if (isDir) {
+ const entries = await fs.readdir(fullPath);
+ contents = ` (contains: ${entries.join(', ')})`;
+ }
+ console.error(`assertPathNotExist FAILED: ${relPath} still exists at ${fullPath} [${isDir ? 'dir' : 'file'}, ${stat.size} bytes]${contents}`);
+ }
+ expect(exists).toBe(false);
+ };
+
+ await check();
+}
+
/**
* Helper function to evaluate JavaScript code in the browser console and assert the result
* @param {string} code - JavaScript code to evaluate in the browser console
diff --git a/tools/e2e-tests/babel.config.js b/tools/e2e-tests/babel.config.js
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tools/modern-tests/babel.test.js b/tools/e2e-tests/babel.test.js
similarity index 50%
rename from tools/modern-tests/babel.test.js
rename to tools/e2e-tests/babel.test.js
index a414d7bbb0..4565410d1f 100644
--- a/tools/modern-tests/babel.test.js
+++ b/tools/e2e-tests/babel.test.js
@@ -7,15 +7,27 @@ describe('Babel App Bundling /', () => {
describe('Meteor+Rspack Bundler /', testMeteorRspackBundler({
appName: 'babel',
port: 3122,
- filePaths: {
- client: 'client/main.jsx',
+ filePaths: {
+ client: 'client/main.jsx',
server: 'server/main.js',
test: 'tests/main.js'
},
configFile: 'rspack.config.mjs',
+ skipEnvCheck: true,
+ // Test custom NODE_ENV compilation
+ env: {
+ meteorRun: { NODE_ENV: 'development' },
+ meteorRunProduction: { NODE_ENV: 'production' },
+ meteorTest: { NODE_ENV: 'development' },
+ meteorTestOnce: { NODE_ENV: 'test' },
+ meteorBuild: { NODE_ENV: 'development' },
+ },
customAssertions: {
afterRun: async ({ result }) => {
await assertFileExtensionModuleRules(result.outputLines);
+ await waitForMeteorOutput(result.outputLines, /\[i\] Rspack mode: development/);
+ await waitForMeteorOutput(result.outputLines, /[^ ]*Meteor.isDevelopment[^ ]*: [^ ]*true[^ ]*/);
+ await waitForMeteorOutput(result.outputLines, /[^ ]*Meteor.isProduction[^ ]*: [^ ]*false[^ ]*/);
},
afterRunRebuildClient: async ({ allConsoleLogs }) => {
// Check for HMR output as enabled by default
@@ -23,6 +35,15 @@ describe('Babel App Bundling /', () => {
},
afterRunProduction: async ({ result }) => {
await assertFileExtensionModuleRules(result.outputLines);
+ await waitForMeteorOutput(result.outputLines, /\[i\] Rspack mode: production/);
+ await waitForMeteorOutput(
+ result.outputLines,
+ /[^ ]*Meteor.isDevelopment[^ ]*: [^ ]*false[^ ]*/
+ );
+ await waitForMeteorOutput(
+ result.outputLines,
+ /[^ ]*Meteor.isProduction[^ ]*: [^ ]*true[^ ]*/
+ );
},
afterRunProductionRebuildClient: async ({ allConsoleLogs }) => {
// Check for HMR to not be enabled in production-like mode
@@ -30,12 +51,22 @@ describe('Babel App Bundling /', () => {
},
afterTest: async ({ result }) => {
await assertFileExtensionModuleRules(result.outputLines);
+ await waitForMeteorOutput(result.outputLines, /\[i\] Rspack mode: development/);
+ await waitForMeteorOutput(result.outputLines, /[^ ]*Meteor.isDevelopment[^ ]*: [^ ]*true[^ ]*/);
+ await waitForMeteorOutput(result.outputLines, /[^ ]*Meteor.isProduction[^ ]*: [^ ]*false[^ ]*/);
},
afterTestOnce: async ({ result }) => {
await assertFileExtensionModuleRules(result.outputLines);
+ await waitForMeteorOutput(result.outputLines, /\[i\] Rspack mode: development/);
+ await waitForMeteorOutput(result.outputLines, /[^ ]*Meteor.isDevelopment[^ ]*: [^ ]*true[^ ]*/);
+ await waitForMeteorOutput(result.outputLines, /[^ ]*Meteor.isProduction[^ ]*: [^ ]*false[^ ]*/);
},
afterBuild: async ({ result }) => {
await assertFileExtensionModuleRules(result.outputLines);
+ // Force development mode on build
+ await waitForMeteorOutput(result.outputLines, /\[i\] Rspack mode: development/);
+ await waitForMeteorOutput(result.outputLines, /[^ ]*Meteor.isDevelopment[^ ]*: [^ ]*true[^ ]*/);
+ await waitForMeteorOutput(result.outputLines, /[^ ]*Meteor.isProduction[^ ]*: [^ ]*false[^ ]*/);
},
}
}));
diff --git a/tools/modern-tests/blaze.test.js b/tools/e2e-tests/blaze.test.js
similarity index 100%
rename from tools/modern-tests/blaze.test.js
rename to tools/e2e-tests/blaze.test.js
diff --git a/tools/modern-tests/coffeescript.test.js b/tools/e2e-tests/coffeescript.test.js
similarity index 100%
rename from tools/modern-tests/coffeescript.test.js
rename to tools/e2e-tests/coffeescript.test.js
diff --git a/tools/modern-tests/full-blaze.test.js b/tools/e2e-tests/full-blaze.test.js
similarity index 100%
rename from tools/modern-tests/full-blaze.test.js
rename to tools/e2e-tests/full-blaze.test.js
diff --git a/tools/modern-tests/helpers.js b/tools/e2e-tests/helpers.js
similarity index 96%
rename from tools/modern-tests/helpers.js
rename to tools/e2e-tests/helpers.js
index cbacc9e7d0..dd62124f30 100644
--- a/tools/modern-tests/helpers.js
+++ b/tools/e2e-tests/helpers.js
@@ -86,7 +86,7 @@ export async function setupMeteorApp(appName, options = {}) {
* @returns {Object} - The meteor process and output lines
*/
export async function runMeteorApp(tempDir, port, options = {}) {
- const { isMonorepo = false } = options;
+ const { isMonorepo = false, env = {} } = options;
// Start Meteor CLI in dev mode
console.log(`Starting Meteor app on port ${port}...`);
@@ -105,11 +105,12 @@ export async function runMeteorApp(tempDir, port, options = {}) {
// Run the meteor command
const { meteorProcess, outputLines } = await runMeteorCommand(
- 'run',
- args,
+ 'run',
+ args,
appDir,
{
- captureOutput
+ captureOutput,
+ execaOptions: { env: { ...process.env, ...env } }
}
);
@@ -123,11 +124,13 @@ export async function runMeteorApp(tempDir, port, options = {}) {
}
// Wait for server to be up
- console.log(`Waiting for app to be available on port ${port}...`);
- await waitOn({
- resources: [`http-get://localhost:${port}`],
- timeout: 90000
- });
+ if (!options.skipWaitOn) {
+ console.log(`Waiting for app to be available on port ${port}...`);
+ await waitOn({
+ resources: [`http-get://localhost:${port}`],
+ timeout: 90000
+ });
+ }
return { meteorProcess, outputLines };
}
@@ -179,7 +182,7 @@ async function killSingleProcessByPort(port) {
// Different commands based on OS
const command = process.platform === 'win32'
? `FOR /F "tokens=5" %a in ('netstat -ano ^| find "LISTENING" ^| find ":${port}"') do taskkill /F /PID %a`
- : `lsof -i :${port} -t | xargs -r kill -9`;
+ : `lsof -i :${port} -t | grep -v ^${process.pid}$ | xargs -r kill -9`;
console.log(`Killing process on port ${port}...`);
try {
@@ -216,10 +219,14 @@ async function killSingleProcessByPort(port) {
export async function runMeteorCommand(command, args = [], cwd, options = {}) {
console.log(`Running Meteor command: ${command} ${args.join(' ')}...`);
- const { captureOutput = false, checkExitCode = false, execaOptions: extraExecaOptions = {} } = options;
+ const { captureOutput = false, checkExitCode = false, execaOptions: extraExecaOptions = {}, env = {} } = options;
const execaOptions = {
cwd,
+ env: {
+ ...process.env,
+ ...env
+ },
...extraExecaOptions
};
@@ -523,12 +530,13 @@ export async function appendFileContent(tempDir, filePath, options = {}) {
* @param {string|RegExp} options.waitForOutput - Output pattern to wait for
* @param {Object} options.waitOptions - Options for waitForMeteorOutput
* @param {string[]} options.commandOptions - Additional command line options for the test command
+ * @param {boolean} options.testClient - Whether to enable client-side tests with a browser driver
* @param {boolean} options.checkTestResults - Whether to check test results and propagate failures to Jest
* @param {boolean} options.isMonorepo - Whether the app is a monorepo
* @returns {Object} - The meteor process and output lines
*/
export async function runMeteorTests(tempDir, port, options = {}) {
- const { isMonorepo = false } = options;
+ const { isMonorepo = false, env = {} } = options;
// Start Meteor tests
console.log(`Starting Meteor tests on port ${port}...`);
@@ -547,14 +555,15 @@ export async function runMeteorTests(tempDir, port, options = {}) {
// Run the meteor test command
const { meteorProcess, outputLines, processResult } = await runMeteorCommand(
- 'test',
- args,
+ 'test',
+ args,
appDir,
{
execaOptions: {
env: {
...process.env,
- TEST_BROWSER_DRIVER: 'playwright'
+ ...(options.testClient ? { TEST_BROWSER_DRIVER: 'playwright' } : { TEST_CLIENT: 0 }),
+ ...env,
}
},
captureOutput,
@@ -705,7 +714,7 @@ export async function waitForPlaywrightConsole(pattern, options = {}) {
* @returns {Object} - The build output directory and the meteor process result
*/
export async function buildMeteorApp(tempDir, options = {}) {
- const { isMonorepo = false } = options;
+ const { isMonorepo = false, env = {} } = options;
// Create a unique temporary directory for the build output
const randomSuffix = Math.random().toString(36).substring(2, 10);
@@ -729,11 +738,11 @@ export async function buildMeteorApp(tempDir, options = {}) {
// Run the meteor build command with automatic exit code checking
const result = await runMeteorCommand(
- 'build',
- args,
+ 'build',
+ args,
appDir,
{
- execaOptions: options.execaOptions || {},
+ execaOptions: { ...(options.execaOptions || {}), env: { ...process.env, ...(options.execaOptions?.env || {}), ...env } },
captureOutput: options.captureOutput !== undefined ? options.captureOutput : true,
checkExitCode: true // Automatically check exit code
}
diff --git a/tools/modern-tests/jest.config.js b/tools/e2e-tests/jest.config.js
similarity index 100%
rename from tools/modern-tests/jest.config.js
rename to tools/e2e-tests/jest.config.js
diff --git a/tools/modern-tests/jest.setup.js b/tools/e2e-tests/jest.setup.js
similarity index 81%
rename from tools/modern-tests/jest.setup.js
rename to tools/e2e-tests/jest.setup.js
index 1f0cf6e1fa..8b3f830a1f 100644
--- a/tools/modern-tests/jest.setup.js
+++ b/tools/e2e-tests/jest.setup.js
@@ -7,6 +7,9 @@ if (isCI) {
console.log('Set 2 retries on Jest level');
}
+// Clear NODE_ENV so meteor commands don't inherit any value from the test runner environment
+process.env.NODE_ENV = '';
+
// Set fixed ports for all tests
process.env.RSPACK_DEVSERVER_PORT = '8080';
process.env.RSDOCTOR_CLIENT_PORT = '8888';
diff --git a/tools/modern-tests/monorepo.test.js b/tools/e2e-tests/monorepo.test.js
similarity index 100%
rename from tools/modern-tests/monorepo.test.js
rename to tools/e2e-tests/monorepo.test.js
diff --git a/tools/modern-tests/package-lock.json b/tools/e2e-tests/package-lock.json
similarity index 99%
rename from tools/modern-tests/package-lock.json
rename to tools/e2e-tests/package-lock.json
index eee71dcd5c..341c3bf556 100644
--- a/tools/modern-tests/package-lock.json
+++ b/tools/e2e-tests/package-lock.json
@@ -1,11 +1,11 @@
{
- "name": "meteor-modern-tests",
+ "name": "meteor-e2e-tests",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "meteor-modern-tests",
+ "name": "meteor-e2e-tests",
"version": "1.0.0",
"devDependencies": {
"@swc/core": "^1.15.18",
@@ -15,7 +15,7 @@
"fs-extra": "^11.3.1",
"jest": "^29.0.0",
"jest-playwright-preset": "^3.0.1",
- "playwright": "1.58.0",
+ "playwright": "^1.58.0",
"semver": "^7.7.4",
"underscore": "^1.13.8",
"wait-on": "^7.0.0"
diff --git a/tools/modern-tests/package.json b/tools/e2e-tests/package.json
similarity index 88%
rename from tools/modern-tests/package.json
rename to tools/e2e-tests/package.json
index 3a116c0bd9..1893ece2e4 100644
--- a/tools/modern-tests/package.json
+++ b/tools/e2e-tests/package.json
@@ -1,5 +1,5 @@
{
- "name": "meteor-modern-tests",
+ "name": "meteor-e2e-tests",
"version": "1.0.0",
"description": "Isolated Jest + Playwright environment for Meteor E2E tests",
"scripts": {
@@ -13,7 +13,7 @@
"fs-extra": "^11.3.1",
"jest": "^29.0.0",
"jest-playwright-preset": "^3.0.1",
- "playwright": "1.58.0",
+ "playwright": "^1.58.0",
"semver": "^7.7.4",
"underscore": "^1.13.8",
"wait-on": "^7.0.0"
diff --git a/tools/modern-tests/react-router.test.js b/tools/e2e-tests/react-router.test.js
similarity index 100%
rename from tools/modern-tests/react-router.test.js
rename to tools/e2e-tests/react-router.test.js
diff --git a/tools/modern-tests/react.test.js b/tools/e2e-tests/react.test.js
similarity index 59%
rename from tools/modern-tests/react.test.js
rename to tools/e2e-tests/react.test.js
index 896d587af3..f10ececa9c 100644
--- a/tools/modern-tests/react.test.js
+++ b/tools/e2e-tests/react.test.js
@@ -9,7 +9,7 @@ import {
import { testMeteorBundler, testMeteorRspackBundler } from './test-helpers';
import fs from 'fs-extra';
import path from 'path';
-import { assertMeteorReactApp, assertConsoleEval } from "./assertions";
+import { assertMeteorReactApp, assertConsoleEval, assertFileExist } from "./assertions";
describe('React App Bundling /', () => {
@@ -64,62 +64,104 @@ describe('React App Bundling /', () => {
});
});
- describe('Meteor+Rspack Bundler /', testMeteorRspackBundler({
- appName: 'react',
- port: 3102,
- filePaths: {
- client: 'client/main.jsx',
- server: 'server/main.js',
- test: 'tests/main.js'
- },
- configFile: 'rspack.config.cjs',
- customAssertions: {
- afterRun: async ({ result }) => {
- await waitForReactEnvs(result.outputLines, { isJsxEnabled: true });
-
- // Check if images exist and return 200 status code
- await assertImagesExistAndLoad();
-
- // Check custom plugin is disabled with Meteor.disablePlugins
- await waitForMeteorOutput(result.outputLines, /.*CustomConsoleLogPlugin.*/, { negate: true });
+ describe(
+ "Meteor+Rspack Bundler /",
+ testMeteorRspackBundler({
+ appName: "react",
+ port: 3102,
+ filePaths: {
+ client: "client/main.jsx",
+ server: "server/main.js",
+ test: "tests/main.js",
},
- afterRunRebuildClient: async ({ allConsoleLogs }) => {
- // Check for HMR output as enabled by default
- await waitForMeteorOutput(allConsoleLogs, /.*HMR.*Updated modules:.*/);
- },
- afterRunProduction: async ({ result }) => {
- await waitForReactEnvs(result.outputLines, { isJsxEnabled: true });
+ configFile: "rspack.config.cjs",
+ buildDir: "_build-local-custom",
+ env: { METEOR_LOCAL_DIR: ".meteor/local-custom" },
+ customAssertions: {
+ afterRun: async ({ result, tempDir }) => {
+ const appDir = tempDir; // testMeteorRspackBundler uses tempDir as appDir if not monorepo
- // Check if images exist and return 200 status code
- await assertImagesExistAndLoad();
+ const localDir = path.join(appDir, ".meteor", "local-custom");
+ const buildDir = path.join(appDir, "_build-local-custom");
- // Check custom plugin is disabled with Meteor.disablePlugins
- await waitForMeteorOutput(result.outputLines, /.*CustomConsoleLogPlugin.*/, { negate: true });
- },
- afterRunProductionRebuildClient: async ({ allConsoleLogs }) => {
- // Check for HMR to not be enabled in production-like mode
- await waitForMeteorOutput(allConsoleLogs, /.*HMR.*Updated modules:*/, { negate: true });
- },
- afterTest: async ({ result }) => {
- await waitForReactEnvs(result.outputLines);
+ expect(await fs.pathExists(buildDir)).toBe(true);
+ expect(await fs.pathExists(localDir)).toBe(true);
- // Check custom plugin is disabled with Meteor.disablePlugins
- await waitForMeteorOutput(result.outputLines, /.*CustomConsoleLogPlugin.*/, { negate: true });
- },
- afterTestOnce: async ({ result }) => {
- await waitForReactEnvs(result.outputLines);
+ await assertFileExist(appDir, '.gitignore', { content: '.meteor/local-custom' });
- // Check custom plugin is disabled with Meteor.disablePlugins
- await waitForMeteorOutput(result.outputLines, /.*CustomConsoleLogPlugin.*/, { negate: true });
- },
- afterBuild: async ({ result }) => {
- await waitForReactEnvs(result.outputLines, { isJsxEnabled: true });
+ await waitForReactEnvs(result.outputLines, { isJsxEnabled: true });
- // Check custom plugin is disabled with Meteor.disablePlugins
- await waitForMeteorOutput(result.outputLines, /.*CustomConsoleLogPlugin.*/, { negate: true });
+ // Check if images exist and return 200 status code
+ await assertImagesExistAndLoad();
+
+ // Check custom plugin is disabled with Meteor.disablePlugins
+ await waitForMeteorOutput(
+ result.outputLines,
+ /.*CustomConsoleLogPlugin.*/,
+ { negate: true }
+ );
+ },
+ afterRunRebuildClient: async ({ allConsoleLogs }) => {
+ // Check for HMR output as enabled by default
+ await waitForMeteorOutput(
+ allConsoleLogs,
+ /.*HMR.*Updated modules:.*/
+ );
+ },
+ afterRunProduction: async ({ result }) => {
+ await waitForReactEnvs(result.outputLines, { isJsxEnabled: true });
+
+ // Check if images exist and return 200 status code
+ await assertImagesExistAndLoad();
+
+ // Check custom plugin is disabled with Meteor.disablePlugins
+ await waitForMeteorOutput(
+ result.outputLines,
+ /.*CustomConsoleLogPlugin.*/,
+ { negate: true }
+ );
+ },
+ afterRunProductionRebuildClient: async ({ allConsoleLogs }) => {
+ // Check for HMR to not be enabled in production-like mode
+ await waitForMeteorOutput(
+ allConsoleLogs,
+ /.*HMR.*Updated modules:*/,
+ { negate: true }
+ );
+ },
+ afterTest: async ({ result }) => {
+ await waitForReactEnvs(result.outputLines);
+
+ // Check custom plugin is disabled with Meteor.disablePlugins
+ await waitForMeteorOutput(
+ result.outputLines,
+ /.*CustomConsoleLogPlugin.*/,
+ { negate: true }
+ );
+ },
+ afterTestOnce: async ({ result }) => {
+ await waitForReactEnvs(result.outputLines);
+
+ // Check custom plugin is disabled with Meteor.disablePlugins
+ await waitForMeteorOutput(
+ result.outputLines,
+ /.*CustomConsoleLogPlugin.*/,
+ { negate: true }
+ );
+ },
+ afterBuild: async ({ result }) => {
+ await waitForReactEnvs(result.outputLines, { isJsxEnabled: true });
+
+ // Check custom plugin is disabled with Meteor.disablePlugins
+ await waitForMeteorOutput(
+ result.outputLines,
+ /.*CustomConsoleLogPlugin.*/,
+ { negate: true }
+ );
+ },
},
- }
- }));
+ })
+ );
});
/**
diff --git a/tools/e2e-tests/scripts/create-app.js b/tools/e2e-tests/scripts/create-app.js
new file mode 100644
index 0000000000..fc11504356
--- /dev/null
+++ b/tools/e2e-tests/scripts/create-app.js
@@ -0,0 +1,483 @@
+#!/usr/bin/env node
+
+/**
+ * Script to create a Meteor test app for manual testing without automatic cleanup.
+ *
+ * Sources apps from:
+ * - tools/e2e-tests/apps/ (use --app flag)
+ * - meteor create -- (use --skeleton flag)
+ *
+ * Usage:
+ * npm run create-app:e2e -- --app react
+ * npm run create-app:e2e -- --app react --output ./dist/my-react-app
+ * npm run create-app:e2e -- --app monorepo --monorepo
+ * npm run create-app:e2e -- --skeleton react
+ * npm run create-app:e2e -- --skeleton react --output ./my-apps/custom-name
+ */
+
+const path = require('path');
+const fs = require('fs-extra');
+const execa = require('execa');
+const { linkLocalRspack } = require('./link-rspack');
+
+const REPO_ROOT = path.resolve(__dirname, '../../..');
+const METEOR_EXECUTABLE = path.join(REPO_ROOT, 'meteor');
+const E2E_TESTS_DIR = path.join(__dirname, '..');
+const APPS_DIR = path.join(E2E_TESTS_DIR, 'apps');
+const DEFAULT_OUTPUT_DIR = path.join(REPO_ROOT, 'dist');
+
+// ANSI color helpers
+const c = {
+ reset: '\x1b[0m',
+ bold: '\x1b[1m',
+ dim: '\x1b[2m',
+ green: '\x1b[32m',
+ yellow: '\x1b[33m',
+ blue: '\x1b[34m',
+ cyan: '\x1b[36m',
+ red: '\x1b[31m',
+ magenta: '\x1b[35m',
+};
+const log = {
+ step: (msg) => console.log(`${c.cyan}>${c.reset} ${msg}`),
+ success: (msg) => console.log(`${c.green}>${c.reset} ${msg}`),
+ info: (msg) => console.log(`${c.blue}>${c.reset} ${msg}`),
+ warn: (msg) => console.log(`${c.yellow}>${c.reset} ${msg}`),
+ error: (msg) => console.error(`${c.red}>${c.reset} ${msg}`),
+ detail: (msg) => console.log(` ${c.dim}${msg}${c.reset}`),
+};
+
+function parseArgs(argv) {
+ const args = { monorepo: false };
+ for (let i = 0; i < argv.length; i++) {
+ if (argv[i] === '--app') {
+ args.app = argv[++i];
+ } else if (argv[i] === '--skeleton') {
+ args.skeleton = argv[++i];
+ } else if (argv[i] === '--output') {
+ args.output = argv[++i];
+ } else if (argv[i] === '--monorepo') {
+ args.monorepo = true;
+ } else if (argv[i] === '--force' || argv[i] === '-f') {
+ args.force = true;
+ } else if (argv[i] === '--help' || argv[i] === '-h') {
+ args.help = true;
+ }
+ }
+ return args;
+}
+
+function printHelp() {
+ const availableApps = fs.existsSync(APPS_DIR)
+ ? fs.readdirSync(APPS_DIR).join(', ')
+ : '(none found)';
+
+ console.log(`
+${c.bold}Usage:${c.reset} npm run create-app:e2e -- [options]
+
+${c.bold}Options:${c.reset}
+ ${c.cyan}--app${c.reset} Copy an existing app from tools/e2e-tests/apps/
+ ${c.cyan}--skeleton${c.reset} Create a new app via "meteor create --"
+ ${c.cyan}--output${c.reset} Full destination path for the app (default: ./dist/)
+ ${c.cyan}--monorepo${c.reset} Treat the app as a monorepo (runs npm install at both root and app/ levels)
+ ${c.cyan}--force${c.reset}, ${c.cyan}-f${c.reset} Remove the destination directory if it already exists before creating the app
+ ${c.cyan}--help${c.reset}, ${c.cyan}-h${c.reset} Show this help message
+
+${c.bold}Available apps:${c.reset} ${c.green}${availableApps}${c.reset}
+
+${c.bold}Examples:${c.reset}
+ ${c.dim}npm run create-app:e2e -- --app react${c.reset}
+ ${c.dim}npm run create-app:e2e -- --app react --output ./dist/my-react-app${c.reset}
+ ${c.dim}npm run create-app:e2e -- --app monorepo --monorepo${c.reset}
+ ${c.dim}npm run create-app:e2e -- --skeleton react${c.reset}
+ ${c.dim}npm run create-app:e2e -- --skeleton react --output ./my-apps/custom-name${c.reset}
+`);
+}
+
+/**
+ * Find a test-helper function call block (e.g., testMeteorSkeleton({ skeletonName: 'react', ... }))
+ * that contains a matching name key/value, and return the content of its options object.
+ */
+function findTestHelperBlock(content, fnName, nameKey, nameValue) {
+ let searchStart = 0;
+
+ while (searchStart < content.length) {
+ const fnIdx = content.indexOf(fnName + '(', searchStart);
+ if (fnIdx === -1) return null;
+
+ const braceIdx = content.indexOf('{', fnIdx + fnName.length);
+ if (braceIdx === -1) return null;
+
+ // Find matching closing brace
+ let depth = 0;
+ let endIdx = -1;
+ for (let i = braceIdx; i < content.length; i++) {
+ if (content[i] === '{') depth++;
+ else if (content[i] === '}') {
+ depth--;
+ if (depth === 0) {
+ endIdx = i;
+ break;
+ }
+ }
+ }
+
+ if (endIdx !== -1) {
+ const block = content.substring(braceIdx, endIdx + 1);
+ const namePattern = new RegExp(`${nameKey}:\\s*['"]${nameValue}['"]`);
+ if (namePattern.test(block)) {
+ return block;
+ }
+ }
+
+ searchStart = endIdx !== -1 ? endIdx + 1 : fnIdx + 1;
+ }
+
+ return null;
+}
+
+/**
+ * Parse environment variable patterns from a code string.
+ * Matches:
+ * - process.env.KEY = 'value' (non-empty string values only)
+ * - env: { KEY: 'value', ... }
+ * Only includes envs that have an actual non-empty value.
+ */
+function parseEnvVars(code) {
+ const envVars = {};
+
+ // Pattern 1: process.env.KEY = 'value' or "value" (non-empty values only)
+ const processEnvRegex = /process\.env\.(\w+)\s*=\s*['"]([^'"]+)['"]/g;
+ let match;
+ while ((match = processEnvRegex.exec(code)) !== null) {
+ envVars[match[1]] = match[2];
+ }
+
+ // Pattern 2: env: { KEY: 'value', ... }
+ const envObjRegex = /\benv:\s*\{([^}]+)\}/g;
+ while ((match = envObjRegex.exec(code)) !== null) {
+ const envContent = match[1];
+ const kvRegex = /(\w+)\s*:\s*['"]([^'"]+)['"]/g;
+ let kvMatch;
+ while ((kvMatch = kvRegex.exec(envContent)) !== null) {
+ envVars[kvMatch[1]] = kvMatch[2];
+ }
+ }
+
+ return envVars;
+}
+
+/**
+ * Read the corresponding test file for an app or skeleton and extract
+ * environment variables that the tests set (so the manually created app
+ * behaves the same way).
+ *
+ * For --app : reads tools/e2e-tests/.test.js (whole file)
+ * For --skeleton : reads tools/e2e-tests/skeleton.test.js and
+ * scopes to the testMeteorSkeleton({ skeletonName: '' }) block.
+ */
+function extractEnvVarsFromTestFile(sourceName, isApp) {
+ const testFile = isApp
+ ? path.join(E2E_TESTS_DIR, `${sourceName}.test.js`)
+ : path.join(E2E_TESTS_DIR, 'skeleton.test.js');
+
+ if (!fs.existsSync(testFile)) return {};
+
+ const content = fs.readFileSync(testFile, 'utf8');
+ let scope;
+
+ if (isApp) {
+ scope = content;
+ } else {
+ scope = findTestHelperBlock(content, 'testMeteorSkeleton', 'skeletonName', sourceName);
+ if (!scope) return {};
+ }
+
+ return parseEnvVars(scope);
+}
+
+/**
+ * Build a shell env prefix string from an env vars object.
+ * e.g., { METEOR_LOCAL_DIR: '.meteor/local-custom' } => "METEOR_LOCAL_DIR=.meteor/local-custom"
+ */
+function buildEnvPrefix(envVars) {
+ const entries = Object.entries(envVars);
+ if (entries.length === 0) return '';
+ return entries.map(([key, value]) => `${key}=${value}`).join(' ');
+}
+
+/**
+ * Replace bare `meteor` command occurrences in a script string with the full
+ * checkout path. Matches `meteor` only as a standalone command word:
+ * - not preceded by `/` or a word character (avoids already-resolved paths
+ * and things like `something-meteor`)
+ * - not followed by a word character (avoids `meteor-node-stubs` etc.)
+ */
+function rewriteMeteorCmd(scriptValue, meteorExecutable) {
+ return scriptValue.replace(/(? {
+ const inv = n === 'start' ? 'npm start' : `npm run ${n}`;
+ return Math.max(max, inv.length);
+ }, 0);
+
+ const line = `${c.dim}${'─'.repeat(55)}${c.reset}`;
+
+ console.log('');
+ console.log(line);
+ console.log(` ${c.green}${c.bold}App ready at:${c.reset} ${c.cyan}${destDir}${c.reset}`);
+ console.log(line);
+ console.log('');
+ console.log(` ${c.yellow}cd ${destDir}${c.reset}`);
+ console.log('');
+ console.log(` ${c.bold}Run commands${c.reset} ${c.dim}(meteor checkout binary):${c.reset}`);
+ console.log(` ${c.dim}${m} run${c.reset}`);
+ console.log(` ${c.dim}${m} run --production${c.reset}`);
+
+ if (hasTestModule) {
+ console.log(` ${c.dim}${m} test --driver-package meteortesting:mocha${c.reset}`);
+ console.log(` ${c.dim}${m} test --once --driver-package meteortesting:mocha${c.reset}`);
+ if (hasClient) {
+ console.log(` ${c.dim}${m} test --full-app --driver-package meteortesting:mocha${c.reset}`);
+ console.log(` ${c.dim}${m} test --full-app --once --driver-package meteortesting:mocha${c.reset}`);
+ }
+ }
+
+ console.log(` ${c.dim}${m} build ./_build --directory${c.reset}`);
+ console.log('');
+ console.log(` ${c.bold}npm scripts${c.reset} ${c.dim}(run from the app directory):${c.reset}`);
+
+ for (const [name, cmd] of Object.entries(scripts)) {
+ const invocation = name === 'start' ? 'npm start' : `npm run ${name}`;
+ const padding = ' '.repeat(maxLen - invocation.length);
+ console.log(` ${c.cyan}${invocation}${c.reset}${padding} ${c.dim}# ${cmd}${c.reset}`);
+ }
+
+ console.log(line);
+ console.log('');
+}
+
+async function setupFromApp(appName, destDir, { isMonorepo = false, force = false } = {}) {
+ const sourceDir = path.join(APPS_DIR, appName);
+
+ if (!fs.existsSync(sourceDir)) {
+ const available = fs.readdirSync(APPS_DIR).join(', ');
+ throw new Error(
+ `App '${appName}' not found in tools/e2e-tests/apps/\nAvailable apps: ${available}`
+ );
+ }
+
+ if (fs.existsSync(destDir)) {
+ if (force) {
+ log.warn(`Removing existing destination: ${c.cyan}${destDir}${c.reset}`);
+ await fs.remove(destDir);
+ } else {
+ log.error(`Destination already exists: ${c.cyan}${destDir}${c.reset}`);
+ log.detail('Remove it first or use --force to replace it.');
+ process.exit(1);
+ }
+ }
+
+ await fs.ensureDir(path.dirname(destDir));
+
+ log.step(`Copying app ${c.bold}${appName}${c.reset} to ${c.cyan}${destDir}${c.reset}`);
+ await fs.copy(sourceDir, destDir, {
+ dereference: true,
+ preserveTimestamps: true,
+ overwrite: true,
+ });
+ log.success('Copy complete.');
+
+ const appPackageJsonPath = isMonorepo
+ ? path.join(destDir, 'app', 'package.json')
+ : path.join(destDir, 'package.json');
+
+ const envVars = extractEnvVarsFromTestFile(appName, true);
+ if (Object.keys(envVars).length > 0) {
+ log.detail(`env from test file: ${c.magenta}${Object.entries(envVars).map(([k, v]) => `${k}=${v}`).join(' ')}${c.reset}`);
+ }
+
+ const meteorAppDir = isMonorepo ? path.join(destDir, 'app') : destDir;
+ const execEnv = Object.keys(envVars).length > 0 ? { env: { ...process.env, ...envVars } } : {};
+
+ log.step('Adding rspack package...');
+ await execa(METEOR_EXECUTABLE, ['add', 'rspack'], {
+ cwd: meteorAppDir,
+ stdio: 'inherit',
+ ...execEnv,
+ });
+
+ log.step('Linking local @meteorjs/rspack...');
+ await linkLocalRspack(meteorAppDir, { env: envVars });
+
+ if (isMonorepo) {
+ log.step('Running meteor npm install at root level...');
+ await execa(METEOR_EXECUTABLE, ['npm', 'install'], { cwd: destDir, stdio: 'inherit', ...execEnv });
+ log.step('Running meteor npm install at app level...');
+ await execa(METEOR_EXECUTABLE, ['npm', 'install'], {
+ cwd: path.join(destDir, 'app'),
+ stdio: 'inherit',
+ ...execEnv,
+ });
+ } else {
+ log.step('Running meteor npm install...');
+ await execa(METEOR_EXECUTABLE, ['npm', 'install'], { cwd: destDir, stdio: 'inherit', ...execEnv });
+ }
+
+ if (fs.existsSync(appPackageJsonPath)) {
+ log.step('Injecting npm scripts into package.json...');
+ await injectNpmScripts(appPackageJsonPath, envVars);
+ }
+
+ return { destDir, appPackageJsonPath };
+}
+
+async function setupFromSkeleton(skeletonName, destDir, { force = false } = {}) {
+ if (fs.existsSync(destDir)) {
+ if (force) {
+ log.warn(`Removing existing destination: ${c.cyan}${destDir}${c.reset}`);
+ await fs.remove(destDir);
+ } else {
+ log.error(`Destination already exists: ${c.cyan}${destDir}${c.reset}`);
+ log.detail('Remove it first or use --force to replace it.');
+ process.exit(1);
+ }
+ }
+
+ const parentDir = path.dirname(destDir);
+ const appDirName = path.basename(destDir);
+
+ await fs.ensureDir(parentDir);
+
+ log.step(`Creating Meteor app ${c.bold}${appDirName}${c.reset} via ${c.dim}meteor create --${skeletonName}${c.reset}`);
+ await execa(METEOR_EXECUTABLE, ['create', `--${skeletonName}`, appDirName], {
+ cwd: parentDir,
+ stdio: 'inherit',
+ });
+
+ const appPackageJsonPath = path.join(destDir, 'package.json');
+
+ const envVars = extractEnvVarsFromTestFile(skeletonName, false);
+ if (Object.keys(envVars).length > 0) {
+ log.detail(`env from test file: ${c.magenta}${Object.entries(envVars).map(([k, v]) => `${k}=${v}`).join(' ')}${c.reset}`);
+ }
+ const execEnv = Object.keys(envVars).length > 0 ? { env: { ...process.env, ...envVars } } : {};
+
+ log.step('Adding rspack package...');
+ await execa(METEOR_EXECUTABLE, ['add', 'rspack'], {
+ cwd: destDir,
+ stdio: 'inherit',
+ ...execEnv,
+ });
+
+ log.step('Linking local @meteorjs/rspack...');
+ await linkLocalRspack(destDir, { env: envVars });
+
+ log.step('Running meteor npm install...');
+ await execa(METEOR_EXECUTABLE, ['npm', 'install'], { cwd: destDir, stdio: 'inherit', ...execEnv });
+
+ if (fs.existsSync(appPackageJsonPath)) {
+ log.step('Injecting npm scripts into package.json...');
+ await injectNpmScripts(appPackageJsonPath, envVars);
+ }
+
+ return { destDir, appPackageJsonPath };
+}
+
+async function main() {
+ const args = parseArgs(process.argv.slice(2));
+
+ if (args.help) {
+ printHelp();
+ process.exit(0);
+ }
+
+ if (!args.app && !args.skeleton) {
+ log.error('You must provide --app or --skeleton ');
+ printHelp();
+ process.exit(1);
+ }
+
+ if (args.app && args.skeleton) {
+ log.error('--app and --skeleton are mutually exclusive');
+ process.exit(1);
+ }
+
+ const sourceName = args.app || args.skeleton;
+
+ // --output is the full destination path; if omitted, default to ./dist/
+ const destDir = args.output
+ ? path.resolve(REPO_ROOT, args.output)
+ : path.join(DEFAULT_OUTPUT_DIR, sourceName);
+
+ let result;
+ if (args.app) {
+ result = await setupFromApp(args.app, destDir, { isMonorepo: args.monorepo, force: args.force });
+ } else {
+ result = await setupFromSkeleton(args.skeleton, destDir, { force: args.force });
+ }
+
+ printCommandSummary(result.destDir, result.appPackageJsonPath);
+}
+
+main().catch(err => {
+ log.error(err.message);
+ process.exit(1);
+});
diff --git a/tools/e2e-tests/scripts/link-rspack.js b/tools/e2e-tests/scripts/link-rspack.js
new file mode 100644
index 0000000000..5e8b6bbb97
--- /dev/null
+++ b/tools/e2e-tests/scripts/link-rspack.js
@@ -0,0 +1,77 @@
+#!/usr/bin/env node
+
+/**
+ * Links the local npm-packages/meteor-rspack into a Meteor app so it runs
+ * against the latest dev version.
+ *
+ * Steps:
+ * 1. Run `meteor update --npm` in the app
+ * 2. Install the matching @rspack/core and @rspack/cli versions into the
+ * local meteor-rspack package (read from packages/rspack/lib/constants.js)
+ * 3. Install `ignore-loader` in the app
+ * 4. `npm link` the local meteor-rspack into the app
+ *
+ */
+
+const path = require('path');
+const fs = require('fs');
+const execa = require('execa');
+
+const REPO_ROOT = path.resolve(__dirname, '..', '..', '..');
+const METEOR_EXECUTABLE = path.join(REPO_ROOT, 'meteor');
+const RSPACK_PACKAGE_DIR = path.join(REPO_ROOT, 'npm-packages', 'meteor-rspack');
+const CONSTANTS_PATH = path.join(REPO_ROOT, 'packages', 'rspack', 'lib', 'constants.js');
+
+async function linkLocalRspack(appDir, { env } = {}) {
+ const execOpts = env ? { env: { ...process.env, ...env } } : {};
+
+ console.log(`Running meteor update --npm in ${appDir}...`);
+ await execa(METEOR_EXECUTABLE, ['update', '--npm'], {
+ cwd: appDir,
+ stdio: 'inherit',
+ ...execOpts,
+ });
+
+ const constantsContent = fs.readFileSync(CONSTANTS_PATH, 'utf8');
+ const rspackVersionMatch = constantsContent.match(
+ /DEFAULT_RSPACK_VERSION\s*=\s*['"]([^'"]+)['"]/
+ );
+ const rspackVersion = rspackVersionMatch?.[1];
+ if (rspackVersion) {
+ console.log(`Installing @rspack/core@${rspackVersion} and @rspack/cli@${rspackVersion}...`);
+ await execa(
+ 'npm',
+ [
+ 'install',
+ `@rspack/core@${rspackVersion}`,
+ `@rspack/cli@${rspackVersion}`,
+ '--no-save',
+ '--no-package-lock',
+ ],
+ { cwd: RSPACK_PACKAGE_DIR }
+ );
+ }
+
+ console.log('Installing ignore-loader in the app...');
+ await execa('npm', ['install', 'ignore-loader', '--save'], { cwd: appDir });
+
+ console.log(`Linking local meteor-rspack from ${RSPACK_PACKAGE_DIR}...`);
+ await execa('npm', ['link', RSPACK_PACKAGE_DIR], { cwd: appDir });
+
+ console.log('Local meteor-rspack linked successfully.');
+}
+
+module.exports = { linkLocalRspack, REPO_ROOT, METEOR_EXECUTABLE, RSPACK_PACKAGE_DIR };
+
+// CLI mode
+if (require.main === module) {
+ const appDir = process.argv[2];
+ if (!appDir) {
+ console.error('Usage: node link-rspack.js ');
+ process.exit(1);
+ }
+ linkLocalRspack(path.resolve(appDir)).catch(err => {
+ console.error(err.message);
+ process.exit(1);
+ });
+}
diff --git a/tools/e2e-tests/server-only.test.js b/tools/e2e-tests/server-only.test.js
new file mode 100644
index 0000000000..53eca14d53
--- /dev/null
+++ b/tools/e2e-tests/server-only.test.js
@@ -0,0 +1,27 @@
+import {
+ waitForMeteorOutput,
+} from './helpers';
+import { testMeteorRspackBundler } from './test-helpers';
+
+describe('Other / Server-only App Bundling /', () => {
+ describe(
+ 'Meteor+Rspack Bundler /',
+ testMeteorRspackBundler({
+ appName: 'server-only',
+ port: 3123,
+ skipClient: true,
+ skipTestClient: true,
+ filePaths: {
+ server: 'server/main.js',
+ },
+ customAssertions: {
+ afterRun: async ({ result }) => {
+ await waitForMeteorOutput(
+ result.outputLines,
+ 'server/main.js loaded'
+ );
+ },
+ },
+ })
+ );
+});
diff --git a/tools/modern-tests/skeleton.test.js b/tools/e2e-tests/skeleton.test.js
similarity index 91%
rename from tools/modern-tests/skeleton.test.js
rename to tools/e2e-tests/skeleton.test.js
index 3306c1d1aa..1556a00c51 100644
--- a/tools/modern-tests/skeleton.test.js
+++ b/tools/e2e-tests/skeleton.test.js
@@ -52,6 +52,18 @@ describe('Meteor Skeletons /', () => {
}),
);
+ describe(
+ "Other / Bare Skeleton /",
+ testMeteorSkeleton({
+ skeletonName: "bare",
+ port: 3219,
+ checkAppTitle: false,
+ checkBodyStyles: false,
+ skipTestClient: true,
+ skipBuildCacheCheck: true,
+ })
+ );
+
describe(
'Blaze Skeleton /',
testMeteorSkeleton({
@@ -93,7 +105,7 @@ describe('Meteor Skeletons /', () => {
);
describe(
- 'Full Library Skeleton /',
+ 'Other / Full Skeleton /',
testMeteorSkeleton({
skeletonName: 'full',
port: 3204,
@@ -102,7 +114,7 @@ describe('Meteor Skeletons /', () => {
server: 'server/main.js',
test: 'imports/api/links/methods.tests.js',
},
- }),
+ })
);
describe(
@@ -151,7 +163,7 @@ describe('Meteor Skeletons /', () => {
);
describe(
- 'Tailwind Library Skeleton /',
+ 'Other / Tailwind Skeleton /',
testMeteorSkeleton({
skeletonName: 'tailwind',
port: 3208,
@@ -162,19 +174,19 @@ describe('Meteor Skeletons /', () => {
},
customAssertions: {
afterRun: async () => {
- // Verify Tailwind styles for ".bg-gray-100" element
+ // Verify Tailwind styles for '.bg-gray-100' element
await assertStyles('.bg-gray-100', {
['background-color']: 'oklch(0.967 0.003 264.542)',
});
},
afterRunProduction: async () => {
- // Verify Tailwind styles for ".bg-gray-100" element
+ // Verify Tailwind styles for '.bg-gray-100' element
await assertStyles('.bg-gray-100', {
['background-color']: 'lab(96.1596 -0.0823438 -1.13575)',
});
},
},
- }),
+ })
);
describe(
diff --git a/tools/modern-tests/solid.test.js b/tools/e2e-tests/solid.test.js
similarity index 100%
rename from tools/modern-tests/solid.test.js
rename to tools/e2e-tests/solid.test.js
diff --git a/tools/modern-tests/svelte.test.js b/tools/e2e-tests/svelte.test.js
similarity index 100%
rename from tools/modern-tests/svelte.test.js
rename to tools/e2e-tests/svelte.test.js
diff --git a/tools/modern-tests/test-helpers.js b/tools/e2e-tests/test-helpers.js
similarity index 67%
rename from tools/modern-tests/test-helpers.js
rename to tools/e2e-tests/test-helpers.js
index 598112eb42..52c11201f3 100644
--- a/tools/modern-tests/test-helpers.js
+++ b/tools/e2e-tests/test-helpers.js
@@ -23,6 +23,7 @@ import {
assertFileExist,
assertMeteorApp,
assertMeteorReactApp,
+ assertPathNotExist,
assertRspackScriptTag
} from "./assertions";
import fs from "fs-extra";
@@ -31,9 +32,25 @@ import execa from "execa";
import waitOn from "wait-on";
const isCI = process.env.GITHUB_ACTIONS === "true";
+// Link local npm-packages/meteor-rspack so tests run against the latest dev version.
+// Set NPM_LINK_RSPACK=false to disable.
+const npmLinkLocalRspack = process.env.NPM_LINK_RSPACK !== 'false';
+if (!npmLinkLocalRspack) {
+ console.warn(
+ '\x1b[33m⚠ NPM_LINK_RSPACK=false — tests will install @meteorjs/rspack from npm.\n' +
+ ' If CI fails, ensure the latest @meteorjs/rspack version has been published.\x1b[0m'
+ );
+}
const WAIT_ON = isCI ? 2000 : 500;
+const { linkLocalRspack: _linkLocalRspack } = require('./scripts/link-rspack');
+
+async function linkLocalRspack(appDir) {
+ if (!npmLinkLocalRspack) return;
+ await _linkLocalRspack(appDir);
+}
+
/**
* Helper function to set up and run tests for the Meteor Bundler
* @param {Object} options - Options for the test
@@ -45,7 +62,7 @@ const WAIT_ON = isCI ? 2000 : 500;
* @returns {Function} - Jest test function
*/
export function testMeteorBundler(options) {
- const { appName, port, customAssertions, beforeAllBehavior, afterAllBehavior } = options;
+ const { appName, port, customAssertions, beforeAllBehavior, afterAllBehavior, env = {} } = options;
return () => {
let meteorProcess;
@@ -62,6 +79,9 @@ export function testMeteorBundler(options) {
// Setup the Meteor app
tempDir = (await setupMeteorApp(appName))?.tempDir;
+
+ // Link local meteor-rspack so the app picks up the latest dev version
+ await linkLocalRspack(tempDir);
});
afterAll(async () => {
@@ -81,7 +101,7 @@ export function testMeteorBundler(options) {
test(`"meteor run" / should start the app`, async () => {
// Run the Meteor app
- meteorProcess = (await runMeteorApp(tempDir, port))?.meteorProcess;
+ meteorProcess = (await runMeteorApp(tempDir, port, { env: env.meteorRun }))?.meteorProcess;
// Assert that the Meteor app is running correctly
await assertMeteorReactApp(port, { title: appName });
@@ -119,6 +139,8 @@ export function testMeteorBundler(options) {
* @param {boolean} options.verbose - Whether to enable verbose output (default: true)
* @param {boolean} options.testFullApp - Whether to run tests with the --full-app flag (default: false)
* @param {boolean} options.testBundleVisualizer - Whether to run tests with bundle-visualizer in production mode (default: false)
+ * @param {boolean} options.skipClient - Whether to skip client-specific assertions (default: false)
+ * @param {boolean} options.skipTestClient - Whether to skip client-side tests (default: false)
* @param {string[]} options.checkBundleFilePaths - Array of file paths to check for existence in the bundle
* @param {Function} options.beforeAllBehavior - Additional behavior to run in beforeAll
* @param {Function} options.afterAllBehavior - Additional behavior to run in afterAll
@@ -166,8 +188,21 @@ export function testMeteorRspackBundler(options) {
afterAllBehavior,
// Build directory (default: '_build')
buildDir = '_build',
+ // Assets context directory (default: 'build-assets')
+ assetsContext = 'build-assets',
+ // Chunks context directory (default: 'build-chunks')
+ chunksContext = 'build-chunks',
// Rspack config file (default: 'rspack.config.js')
configFile = 'rspack.config.js',
+ // Custom environment variables
+ // Per-phase env vars: { meteorRun, meteorRunProduction, meteorTest, meteorTestOnce, meteorBuild }
+ env = {},
+ // Whether to skip client-specific assertions
+ skipClient = false,
+ // Whether to skip client-side tests
+ skipTestClient = false,
+ // Skip isDevelopment/isProduction/isRun/isTest/isBuild verbose output checks
+ skipEnvCheck = false,
} = options;
return () => {
@@ -197,7 +232,13 @@ export function testMeteorRspackBundler(options) {
// Add Rspack package
appDir = isMonorepo ? path.join(tempDir, 'app') : tempDir;
- await runMeteorCommand('add', ['rspack'], appDir, { checkExitCode: true });
+
+ await runMeteorCommand("add", ["rspack"], appDir, {
+ checkExitCode: true,
+ });
+
+ // Link local meteor-rspack so the app picks up the latest dev version
+ await linkLocalRspack(appDir);
// Set meteor.modern.verbose to true
if (verbose) {
@@ -207,8 +248,9 @@ export function testMeteorRspackBundler(options) {
// Run the Meteor app to install Rspack
const result = await runMeteorApp(tempDir, port, {
- waitForOutput: "=> App running at:",
- isMonorepo
+ waitForOutput: "=> App running at",
+ isMonorepo,
+ env: { ...env, ...(env.meteorRun || {}) },
});
meteorProcess = result.meteorProcess;
@@ -249,8 +291,10 @@ export function testMeteorRspackBundler(options) {
test(`"meteor run" / should run and rebuild the app with Rspack`, async () => {
// Run the Meteor app and wait for "restarted at" output
const result = await runMeteorApp(tempDir, port, {
- waitForOutput: "=> App running at:",
- isMonorepo
+ waitForOutput: "=> App running at",
+ isMonorepo,
+ skipWaitOn: skipClient,
+ env: { ...env, ...(env.meteorRun || {}) },
});
meteorProcess = result.meteorProcess;
@@ -258,24 +302,28 @@ export function testMeteorRspackBundler(options) {
await wait(WAIT_ON);
// Assert that the app files exists
- await assertFileExist(appDir, `${buildDir}/main-dev/client-entry.js`);
- await assertFileExist(appDir, `${buildDir}/main-dev/client-rspack.js`);
- await assertFileExist(appDir, `${buildDir}/main-dev/client-meteor.js`);
+ if (!skipClient) {
+ await assertFileExist(appDir, `${buildDir}/main-dev/client-entry.js`);
+ await assertFileExist(appDir, `${buildDir}/main-dev/client-rspack.js`);
+ await assertFileExist(appDir, `${buildDir}/main-dev/client-meteor.js`);
+ }
await assertFileExist(appDir, `${buildDir}/main-dev/server-entry.js`);
await assertFileExist(appDir, `${buildDir}/main-dev/server-rspack.js`);
await assertFileExist(appDir, `${buildDir}/main-dev/server-meteor.js`);
- // Assert that the Meteor app is running correctly
- await assertMeteorReactApp(port, { title: appName });
+ if (!skipClient) {
+ // Assert that the Meteor app is running correctly
+ await assertMeteorReactApp(port, { title: appName });
- // Assert that the app is using Rspack
- await assertRspackScriptTag(port, true);
+ // Assert that the app is using Rspack
+ await assertRspackScriptTag(port, true);
- // Assert that the body has the expected CSS styles
- await assertBodyStyles({
- 'padding': '10px',
- 'font-family': 'sans-serif'
- });
+ // Assert that the body has the expected CSS styles
+ await assertBodyStyles({
+ 'padding': '10px',
+ 'font-family': 'sans-serif'
+ });
+ }
// Run custom assertions if provided
if (customAssertions && customAssertions.afterRun) {
@@ -283,20 +331,22 @@ export function testMeteorRspackBundler(options) {
}
// Update the client code
- await appendFileContent(tempDir, filePaths.client, {
- content: customUpdates.devClient(customMessages.devClient),
- });
- const consoleLogs = await waitForPlaywrightConsole(customMessages.devClient, { returnAllLogs: true });
-
- // Run custom assertions if provided
- if (customAssertions && customAssertions.afterRunRebuildClient) {
- await customAssertions.afterRunRebuildClient({
- tempDir,
- port,
- meteorProcess,
- result,
- allConsoleLogs: consoleLogs.allLogs
+ if (!skipClient) {
+ await appendFileContent(tempDir, filePaths.client, {
+ content: customUpdates.devClient(customMessages.devClient),
});
+ const consoleLogs = await waitForPlaywrightConsole(customMessages.devClient, { returnAllLogs: true });
+
+ // Run custom assertions if provided
+ if (customAssertions && customAssertions.afterRunRebuildClient) {
+ await customAssertions.afterRunRebuildClient({
+ tempDir,
+ port,
+ meteorProcess,
+ result,
+ allConsoleLogs: consoleLogs.allLogs
+ });
+ }
}
// Update the server code
@@ -313,7 +363,7 @@ export function testMeteorRspackBundler(options) {
await customAssertions.afterRunRebuildServer({ tempDir, port, meteorProcess, result });
}
- if (verbose) {
+ if (verbose && !skipEnvCheck) {
await waitForMeteorOutput(
result.outputLines,
/.*isDevelopment:.*true.*/
@@ -337,9 +387,11 @@ export function testMeteorRspackBundler(options) {
test(`"meteor run --production" / should run and rebuild the app with Rspack in production`, async () => {
// Run the Meteor app and wait for "restarted at" output
const result = await runMeteorApp(tempDir, port, {
- waitForOutput: "=> App running at:",
+ waitForOutput: "=> App running at",
commandOptions: ['--production'],
- isMonorepo
+ isMonorepo,
+ skipWaitOn: skipClient,
+ env: { ...env, ...(env.meteorRunProduction || {}) },
});
meteorProcess = result.meteorProcess;
@@ -347,27 +399,34 @@ export function testMeteorRspackBundler(options) {
await wait(WAIT_ON);
// Assert that the app files exists
- await assertFileExist(appDir, `${buildDir}/main-prod/client-entry.js`);
- await assertFileExist(appDir, `${buildDir}/main-prod/client-rspack.js`);
- await assertFileExist(appDir, `${buildDir}/main-prod/client-meteor.js`);
+ if (!skipClient) {
+ await assertFileExist(appDir, `${buildDir}/main-prod/client-entry.js`);
+ await assertFileExist(appDir, `${buildDir}/main-prod/client-rspack.js`);
+ await assertFileExist(appDir, `${buildDir}/main-prod/client-meteor.js`);
+ }
await assertFileExist(appDir, `${buildDir}/main-prod/server-entry.js`);
await assertFileExist(appDir, `${buildDir}/main-prod/server-rspack.js`);
await assertFileExist(appDir, `${buildDir}/main-prod/server-meteor.js`);
- await assertFileExist(appDir, `${buildDir}/main-prod/index.html`);
+
+ if (!skipClient) {
+ await assertFileExist(appDir, `${buildDir}/main-prod/index.html`);
+ }
await assertFileExist(tempDir, filePaths.server);
- // Assert that the Meteor app is running correctly
- await assertMeteorReactApp(port, { title: appName });
+ if (!skipClient) {
+ // Assert that the Meteor app is running correctly
+ await assertMeteorReactApp(port, { title: appName });
- // Assert that the app is using Rspack
- await assertRspackScriptTag(port, false);
+ // Assert that the app is using Rspack
+ await assertRspackScriptTag(port, false);
- // Assert that the body has the expected CSS styles
- await assertBodyStyles({
- 'padding': '10px',
- 'font-family': 'sans-serif'
- });
+ // Assert that the body has the expected CSS styles
+ await assertBodyStyles({
+ 'padding': '10px',
+ 'font-family': 'sans-serif'
+ });
+ }
// Run custom assertions if provided
if (customAssertions && customAssertions.afterRunProduction) {
@@ -375,20 +434,22 @@ export function testMeteorRspackBundler(options) {
}
// Update the client code
- await appendFileContent(tempDir, filePaths.client, {
- content: customUpdates.prodClient(customMessages.prodClient),
- });
- const consoleLogs = await waitForPlaywrightConsole(customMessages.prodClient, { returnAllLogs: true });
-
- // Run custom assertions if provided
- if (customAssertions && customAssertions.afterRunProductionRebuildClient) {
- await customAssertions.afterRunProductionRebuildClient({
- tempDir,
- port,
- meteorProcess,
- result,
- allConsoleLogs: consoleLogs.allLogs
+ if (!skipClient) {
+ await appendFileContent(tempDir, filePaths.client, {
+ content: customUpdates.prodClient(customMessages.prodClient),
});
+ const consoleLogs = await waitForPlaywrightConsole(customMessages.prodClient, { returnAllLogs: true });
+
+ // Run custom assertions if provided
+ if (customAssertions && customAssertions.afterRunProductionRebuildClient) {
+ await customAssertions.afterRunProductionRebuildClient({
+ tempDir,
+ port,
+ meteorProcess,
+ result,
+ allConsoleLogs: consoleLogs.allLogs
+ });
+ }
}
// Update the server code
@@ -405,7 +466,7 @@ export function testMeteorRspackBundler(options) {
await customAssertions.afterRunProductionRebuildServer({ tempDir, port, meteorProcess, result });
}
- if (verbose) {
+ if (verbose && !skipEnvCheck) {
await waitForMeteorOutput(
result.outputLines,
/.*isProduction:.*true.*/
@@ -431,9 +492,10 @@ export function testMeteorRspackBundler(options) {
test(`"meteor run --extra-packages bundle-visualizer --production" / should run with bundle-visualizer in production mode`, async () => {
// Run the Meteor app with bundle-visualizer in production mode
const result = await runMeteorApp(tempDir, port, {
- waitForOutput: "=> App running at:",
+ waitForOutput: "=> App running at",
commandOptions: ['--extra-packages', 'bundle-visualizer', '--production'],
- isMonorepo
+ isMonorepo,
+ env: env.meteorRunProduction
});
meteorProcess = result.meteorProcess;
@@ -486,10 +548,14 @@ export function testMeteorRspackBundler(options) {
test(`"meteor test${testFullApp ? ' --full-app' : ''}" / should run tests with Rspack`, async () => {
const result = await runMeteorTests(tempDir, port, {
- waitForOutput: "=> App running at:",
+ waitForOutput: skipTestClient
+ ? 'TEST_CLIENT=0'
+ : '=> App running at',
commandOptions: testFullApp ? ['--full-app'] : [],
checkTestResults: false,
- isMonorepo
+ isMonorepo,
+ testClient: !skipTestClient,
+ env: { ...env, ...(env.meteorTest || {}) },
});
meteorProcess = result.meteorProcess;
@@ -499,9 +565,11 @@ export function testMeteorRspackBundler(options) {
const isTestModule = filePaths.test && !filePaths.testClient && !filePaths.testServer;
// Assert that the app files exists
- await assertFileExist(appDir, `${buildDir}/test/client-entry.js`);
- await assertFileExist(appDir, `${buildDir}/test/client-rspack.js`);
- await assertFileExist(appDir, `${buildDir}/test/client-meteor.js`);
+ if (!skipClient) {
+ await assertFileExist(appDir, `${buildDir}/test/client-entry.js`);
+ await assertFileExist(appDir, `${buildDir}/test/client-rspack.js`);
+ await assertFileExist(appDir, `${buildDir}/test/client-meteor.js`);
+ }
await assertFileExist(appDir, `${buildDir}/test/server-entry.js`);
await assertFileExist(appDir, `${buildDir}/test/server-rspack.js`);
await assertFileExist(appDir, `${buildDir}/test/server-meteor.js`);
@@ -513,29 +581,35 @@ export function testMeteorRspackBundler(options) {
// Update the test code
if (isTestModule) {
- await appendFileContent(tempDir, filePaths.test, {
- content: customUpdates.test(customMessages.test),
- });
- await waitForMeteorOutput(result.outputLines, customMessages.test);
+ if (filePaths.test) {
+ await appendFileContent(tempDir, filePaths.test, {
+ content: customUpdates.test(customMessages.test),
+ });
+ await waitForMeteorOutput(result.outputLines, customMessages.test);
+ }
} else {
- await appendFileContent(tempDir, filePaths.testClient, {
- content: customUpdates.test(customMessages.testClient),
- });
- await waitForMeteorOutput(
- result.outputLines,
- customMessages.testClient
- );
+ if (!skipClient && filePaths.testClient) {
+ await appendFileContent(tempDir, filePaths.testClient, {
+ content: customUpdates.test(customMessages.testClient),
+ });
+ await waitForMeteorOutput(
+ result.outputLines,
+ customMessages.testClient
+ );
+ }
- await appendFileContent(tempDir, filePaths.testServer, {
- content: customUpdates.test(customMessages.testServer),
- });
- await waitForMeteorOutput(
- result.outputLines,
- customMessages.testServer
- );
+ if (filePaths.testServer) {
+ await appendFileContent(tempDir, filePaths.testServer, {
+ content: customUpdates.test(customMessages.testServer),
+ });
+ await waitForMeteorOutput(
+ result.outputLines,
+ customMessages.testServer
+ );
+ }
}
- if (verbose) {
+ if (verbose && !skipEnvCheck) {
await waitForMeteorOutput(
result.outputLines,
/.*isDevelopment:.*true.*/
@@ -561,10 +635,14 @@ export function testMeteorRspackBundler(options) {
test(`"meteor test${testFullApp ? ' --full-app' : ''} --once" / should run tests once with Rspack`, async () => {
// Test the app with Rspack once
const result = await runMeteorTests(tempDir, port, {
- waitForOutput: "=> App running at:",
+ waitForOutput: skipTestClient
+ ? 'TEST_CLIENT=0'
+ : '=> App running at',
commandOptions: testFullApp ? ['--full-app', '--once'] : ['--once'],
checkTestResults: true,
- isMonorepo
+ isMonorepo,
+ testClient: !skipTestClient,
+ env: { ...env, ...(env.meteorTestOnce || {}) },
});
// Wait for a margin
@@ -578,7 +656,7 @@ export function testMeteorRspackBundler(options) {
await assertFileExist(appDir, `${buildDir}/test/server-rspack.js`);
await assertFileExist(appDir, `${buildDir}/test/server-meteor.js`);
- if (verbose) {
+ if (verbose && !skipEnvCheck) {
await waitForMeteorOutput(
result.outputLines,
/.*isDevelopment:.*true.*/
@@ -603,13 +681,14 @@ export function testMeteorRspackBundler(options) {
const { buildOutputDir, processResult: result } = await buildMeteorApp(tempDir, {
commandOptions: ['--directory'],
captureOutput: true,
- isMonorepo
+ isMonorepo,
+ env: env.meteorBuild
});
// Wait for a margin
await wait(WAIT_ON);
- if (verbose) {
+ if (verbose && !skipEnvCheck) {
await waitForMeteorOutput(
result.outputLines,
/.*isProduction:.*true.*/
@@ -678,6 +757,68 @@ export function testMeteorRspackBundler(options) {
await cleanupTempDir(buildOutputDir);
}
});
+
+ test(`"meteor reset" / should clear all caches and build artifacts`, async () => {
+ // Derive METEOR_LOCAL_DIR-aware paths for assertions
+ const resetEnv = { ...env, ...(env.meteorReset || {}) };
+ const meteorLocalDirEnv = resetEnv.METEOR_LOCAL_DIR;
+ const meteorLocalDirName = meteorLocalDirEnv
+ ? path.basename(meteorLocalDirEnv.replace(/\\/g, '/'))
+ : '';
+ const localDirSuffix = meteorLocalDirName ? `-${meteorLocalDirName}` : '';
+
+ // Verify build artifacts exist from previous tests
+ await assertFileExist(appDir, buildDir);
+ await assertFileExist(appDir, 'node_modules/.cache/rspack');
+
+ // Run meteor reset
+ await runMeteorCommand("reset", [], appDir, {
+ checkExitCode: true,
+ env: resetEnv,
+ });
+
+ // Verify Rspack build artifacts removed
+ await assertPathNotExist(appDir, buildDir);
+ await assertPathNotExist(appDir, 'node_modules/.cache/rspack');
+ await assertPathNotExist(appDir, assetsContext);
+ await assertPathNotExist(appDir, chunksContext);
+ await assertPathNotExist(appDir, `public/${assetsContext}`);
+ await assertPathNotExist(appDir, `public/${chunksContext}`);
+
+ // Also verify defaults are cleaned to prevent regressions
+ await assertPathNotExist(appDir, '_build');
+ await assertPathNotExist(appDir, 'public/build-assets');
+ await assertPathNotExist(appDir, 'public/build-chunks');
+
+ // When METEOR_LOCAL_DIR is set, also verify suffixed paths are cleaned
+ if (localDirSuffix) {
+ await assertPathNotExist(appDir, `${buildDir}${localDirSuffix}`);
+ await assertPathNotExist(appDir, `${assetsContext}${localDirSuffix}`);
+ await assertPathNotExist(appDir, `${chunksContext}${localDirSuffix}`);
+ await assertPathNotExist(appDir, `public/${assetsContext}${localDirSuffix}`);
+ await assertPathNotExist(appDir, `public/${chunksContext}${localDirSuffix}`);
+ await assertPathNotExist(appDir, `_build${localDirSuffix}`);
+ await assertPathNotExist(appDir, `public/build-assets${localDirSuffix}`);
+ await assertPathNotExist(appDir, `public/build-chunks${localDirSuffix}`);
+ }
+
+ // Verify default .meteor/local caches are always cleaned
+ await assertPathNotExist(appDir, '.meteor/local/build');
+ await assertPathNotExist(appDir, '.meteor/local/bundler-cache');
+ await assertPathNotExist(appDir, '.meteor/local/plugin-cache');
+
+ // When METEOR_LOCAL_DIR is set, also verify custom local dir is cleaned
+ if (meteorLocalDirEnv && meteorLocalDirEnv !== '.meteor/local') {
+ await assertPathNotExist(appDir, `${meteorLocalDirEnv}/build`);
+ await assertPathNotExist(appDir, `${meteorLocalDirEnv}/bundler-cache`);
+ await assertPathNotExist(appDir, `${meteorLocalDirEnv}/plugin-cache`);
+ }
+
+ // Run custom assertions if provided
+ if (customAssertions && customAssertions.afterReset) {
+ await customAssertions.afterReset({ tempDir, appDir });
+ }
+ });
};
}
@@ -697,6 +838,10 @@ export function testMeteorRspackBundler(options) {
* @param {Function} options.customAssertions.afterRunProduction - Custom assertions to run after running the app in production mode
* @param {Function} options.customAssertions.afterTestOnce - Custom assertions to run after running tests once
* @param {Function} options.customAssertions.afterBuild - Custom assertions to run after building the app
+ * @param {boolean} options.checkBodyStyles - Whether to check the body styles (default: true)
+ * @param {boolean} options.checkAppTitle - Whether to check the Meteor app title (default: true)
+ * @param {Object} options.bodyStyles - Expected CSS styles for the body
+ * @param {boolean} options.skipTestClient - Whether to skip client-side tests (default: false)
* @param {string[]} options.checkBundleFilePaths - Array of file paths to check for existence in the bundle
* @param {Function} options.beforeAllBehavior - Additional behavior to run in beforeAll
* @param {Function} options.afterAllBehavior - Additional behavior to run in afterAll
@@ -714,10 +859,20 @@ export function testMeteorSkeleton(options) {
},
customAssertions = {},
checkBodyStyles = true,
+ checkAppTitle = true,
bodyStyles,
+ skipTestClient = false,
checkBundleFilePaths = [],
beforeAllBehavior,
afterAllBehavior,
+ // Per-phase env vars: { meteorRun, meteorRunProduction, meteorTest, meteorBuild }
+ env = {},
+ // Bare skeleton may not create build artifacts (e.g. _build, node_modules/.cache/rspack)
+ skipBuildCacheCheck = false,
+ // Assets context directory (default: 'build-assets')
+ assetsContext = 'build-assets',
+ // Chunks context directory (default: 'build-chunks')
+ chunksContext = 'build-chunks',
} = options;
return () => {
@@ -769,6 +924,9 @@ export function testMeteorSkeleton(options) {
const packageJsonExists = await fs.pathExists(packageJsonPath);
expect(packageJsonExists).toBe(true);
+ // Link local meteor-rspack so the app picks up the latest dev version
+ await linkLocalRspack(tempDir);
+
// Run custom assertions if provided
if (customAssertions.afterCreate) {
await customAssertions.afterCreate({ tempDir, packageJsonPath });
@@ -778,7 +936,8 @@ export function testMeteorSkeleton(options) {
test(`"meteor run" / should run the ${skeletonName} app`, async () => {
// Run the newly created app
const result = await runMeteorApp(tempDir, port, {
- waitForOutput: "=> App running at:"
+ waitForOutput: "=> App running at",
+ env: env.meteorRun
});
meteorProcess = result.meteorProcess;
@@ -786,7 +945,9 @@ export function testMeteorSkeleton(options) {
await wait(WAIT_ON);
// Assert that the Meteor app is running correctly
- await assertMeteorApp(port, { title });
+ if (checkAppTitle) {
+ await assertMeteorApp(port, { title });
+ }
if (checkBodyStyles) {
// Assert that the body has the expected CSS styles
@@ -811,8 +972,9 @@ export function testMeteorSkeleton(options) {
test(`"meteor run --production" / should run the ${skeletonName} app in production mode`, async () => {
// Run the app in production mode
const result = await runMeteorApp(tempDir, port, {
- waitForOutput: "=> App running at:",
- commandOptions: ["--production"]
+ waitForOutput: "=> App running at",
+ commandOptions: ["--production"],
+ env: env.meteorRunProduction
});
meteorProcess = result.meteorProcess;
@@ -820,7 +982,9 @@ export function testMeteorSkeleton(options) {
await wait(WAIT_ON);
// Assert that the Meteor app is running correctly
- await assertMeteorApp(port, { title });
+ if (checkAppTitle) {
+ await assertMeteorApp(port, { title });
+ }
if (checkBodyStyles) {
// Assert that the body has the expected CSS styles
@@ -852,12 +1016,15 @@ export function testMeteorSkeleton(options) {
stdio: "inherit",
shell: true
});
+ await linkLocalRspack(tempDir);
// Run tests once for the app
const result = await runMeteorTests(tempDir, port, {
- waitForOutput: "=> App running at:",
+ waitForOutput: skipTestClient ? "TEST_CLIENT=0" : "=> App running at",
commandOptions: ["--once"],
- checkTestResults: true
+ checkTestResults: true,
+ testClient: !skipTestClient,
+ env: { ...env, ...(env.meteorTest || {}) },
});
// Wait for a margin
@@ -876,7 +1043,8 @@ export function testMeteorSkeleton(options) {
// Build the app
const { buildOutputDir, processResult: result } = await buildMeteorApp(tempDir, {
commandOptions: ["--directory"],
- captureOutput: true
+ captureOutput: true,
+ env: { ...env, ...(env.meteorBuild || {}) },
});
// Wait for a margin
@@ -928,5 +1096,68 @@ export function testMeteorSkeleton(options) {
await cleanupTempDir(buildOutputDir);
}
});
+
+ test(`"meteor reset" / should clear all caches and build artifacts`, async () => {
+ // Derive METEOR_LOCAL_DIR-aware paths for assertions
+ const resetEnv = { ...env, ...(env.meteorReset || {}) };
+ const meteorLocalDirEnv = resetEnv.METEOR_LOCAL_DIR;
+ const meteorLocalDirName = meteorLocalDirEnv
+ ? path.basename(meteorLocalDirEnv.replace(/\\/g, '/'))
+ : '';
+ const localDirSuffix = meteorLocalDirName ? `-${meteorLocalDirName}` : '';
+
+ // Verify build artifacts exist from previous tests
+ if (!skipBuildCacheCheck) {
+ await assertFileExist(tempDir, "_build");
+ await assertFileExist(tempDir, "node_modules/.cache/rspack");
+ }
+
+ // Run meteor reset
+ await runMeteorCommand('reset', [], tempDir, {
+ checkExitCode: true,
+ env: resetEnv,
+ });
+
+ // Verify Rspack build artifacts removed
+ await assertPathNotExist(tempDir, '_build');
+ await assertPathNotExist(tempDir, 'node_modules/.cache/rspack');
+ await assertPathNotExist(tempDir, 'node_modules/.cache/meteor');
+ await assertPathNotExist(tempDir, assetsContext);
+ await assertPathNotExist(tempDir, chunksContext);
+ await assertPathNotExist(tempDir, `public/${assetsContext}`);
+ await assertPathNotExist(tempDir, `public/${chunksContext}`);
+
+ // Also verify defaults are cleaned to prevent regressions
+ await assertPathNotExist(tempDir, 'public/build-assets');
+ await assertPathNotExist(tempDir, 'public/build-chunks');
+
+ // When METEOR_LOCAL_DIR is set, also verify suffixed paths are cleaned
+ if (localDirSuffix) {
+ await assertPathNotExist(tempDir, `_build${localDirSuffix}`);
+ await assertPathNotExist(tempDir, `${assetsContext}${localDirSuffix}`);
+ await assertPathNotExist(tempDir, `${chunksContext}${localDirSuffix}`);
+ await assertPathNotExist(tempDir, `public/${assetsContext}${localDirSuffix}`);
+ await assertPathNotExist(tempDir, `public/${chunksContext}${localDirSuffix}`);
+ await assertPathNotExist(tempDir, `public/build-assets${localDirSuffix}`);
+ await assertPathNotExist(tempDir, `public/build-chunks${localDirSuffix}`);
+ }
+
+ // Verify default .meteor/local caches are always cleaned
+ await assertPathNotExist(tempDir, '.meteor/local/build');
+ await assertPathNotExist(tempDir, '.meteor/local/bundler-cache');
+ await assertPathNotExist(tempDir, '.meteor/local/plugin-cache');
+
+ // When METEOR_LOCAL_DIR is set, also verify custom local dir is cleaned
+ if (meteorLocalDirEnv && meteorLocalDirEnv !== '.meteor/local') {
+ await assertPathNotExist(tempDir, `${meteorLocalDirEnv}/build`);
+ await assertPathNotExist(tempDir, `${meteorLocalDirEnv}/bundler-cache`);
+ await assertPathNotExist(tempDir, `${meteorLocalDirEnv}/plugin-cache`);
+ }
+
+ // Run custom assertions if provided
+ if (customAssertions.afterReset) {
+ await customAssertions.afterReset({ tempDir });
+ }
+ });
};
}
diff --git a/tools/modern-tests/typescript.test.js b/tools/e2e-tests/typescript.test.js
similarity index 76%
rename from tools/modern-tests/typescript.test.js
rename to tools/e2e-tests/typescript.test.js
index 8814237c39..7be3de5bed 100644
--- a/tools/modern-tests/typescript.test.js
+++ b/tools/e2e-tests/typescript.test.js
@@ -19,6 +19,8 @@ describe('TypeScript App Bundling /', () => {
testServer: 'tests/server.ts',
},
buildDir: 'build',
+ assetsContext: 'assets',
+ chunksContext: 'chunks',
configFile: 'rspack.config.ts',
customAssertions: {
afterCreate({ tempDir }) {
@@ -40,7 +42,18 @@ describe('TypeScript App Bundling /', () => {
});
await waitForTypeScriptEnvs(result.outputLines, { isTsxEnabled: true });
await waitForTypeScriptErrorFree(result.outputLines);
- await assertFileExist(tempDir, '.meteor/local/types');
+ await assertFileExist(tempDir, ".meteor/local/types");
+ // Portable build: Meteor.isDevelopment and Meteor.isProduction must not be defined
+ await waitForMeteorOutput(
+ result.outputLines,
+ /[^ ]*Meteor.isDevelopment[^ ]*: [^ ]*false[^ ]*/,
+ { negate: true }
+ );
+ await waitForMeteorOutput(
+ result.outputLines,
+ /[^ ]*Meteor.isProduction[^ ]*: [^ ]*true[^ ]*/,
+ { negate: true }
+ );
},
afterRunRebuildClient: async ({ allConsoleLogs }) => {
// Check for HMR output as enabled by default
@@ -52,6 +65,17 @@ describe('TypeScript App Bundling /', () => {
'white-space': 'break-spaces',
});
await waitForTypeScriptEnvs(result.outputLines, { isTsxEnabled: true });
+ // Portable build: Meteor.isDevelopment and Meteor.isProduction must not be defined
+ await waitForMeteorOutput(
+ result.outputLines,
+ /[^ ]*Meteor.isDevelopment[^ ]*: [^ ]*false[^ ]*/,
+ { negate: true }
+ );
+ await waitForMeteorOutput(
+ result.outputLines,
+ /[^ ]*Meteor.isProduction[^ ]*: [^ ]*true[^ ]*/,
+ { negate: true }
+ );
},
afterRunProductionRebuildClient: async ({ allConsoleLogs }) => {
// Check for HMR to not be enabled in production-like mode
@@ -65,6 +89,17 @@ describe('TypeScript App Bundling /', () => {
},
afterBuild: async ({ result }) => {
await waitForTypeScriptEnvs(result.outputLines, { isTsxEnabled: true });
+ // Portable build: Meteor.isDevelopment and Meteor.isProduction must not be defined
+ await waitForMeteorOutput(
+ result.outputLines,
+ /[^ ]*Meteor.isDevelopment[^ ]*: [^ ]*false[^ ]*/,
+ { negate: true }
+ );
+ await waitForMeteorOutput(
+ result.outputLines,
+ /[^ ]*Meteor.isProduction[^ ]*: [^ ]*true[^ ]*/,
+ { negate: true }
+ );
},
}
}));
diff --git a/tools/modern-tests/vue.test.js b/tools/e2e-tests/vue.test.js
similarity index 100%
rename from tools/modern-tests/vue.test.js
rename to tools/e2e-tests/vue.test.js
diff --git a/tools/fs/safe-watcher.ts b/tools/fs/safe-watcher.ts
index 6e2bebc023..38e57d0bc6 100644
--- a/tools/fs/safe-watcher.ts
+++ b/tools/fs/safe-watcher.ts
@@ -312,9 +312,13 @@ async function ensureWatchRoot(dirPath: string): Promise {
if (/Events were dropped/.test(err.message)) {
return;
}
+ if (/RootResolveError/.test(err.message) || /failed to resolve root/.test(err.message)) {
+ console.warn(`Parcel watcher root resolve error on ${osDirPath}, ignoring: ${err.message}`);
+ ignoredWatchRoots.add(dirPath);
+ watchRoots.delete(dirPath);
+ return;
+ }
console.error(`Parcel watcher error on ${osDirPath}:`, err);
- // Only disable native watching for critical errors (like ENOSPC).
- // @ts-ignore
if (err.code === "ENOSPC" || err.errno === require("constants").ENOSPC) {
fallbackToPolling();
}
@@ -340,9 +344,11 @@ async function ensureWatchRoot(dirPath: string): Promise {
(e.code === "ENOTDIR" ||
/Not a directory/.test(e.message) ||
e.code === "EBADF" ||
- /Bad file descriptor/.test(e.message))
+ /Bad file descriptor/.test(e.message) ||
+ /RootResolveError/.test(e.message) ||
+ /failed to resolve root/.test(e.message))
) {
- console.warn(`Skipping watcher for ${osDirPath}: not a directory`);
+ console.warn(`Skipping watcher for ${osDirPath}: ${e.message || 'not watchable'}`);
ignoredWatchRoots.add(dirPath);
} else {
console.error(`Failed to start watcher for ${osDirPath}:`, e);
diff --git a/tools/isobuild/isopack.js b/tools/isobuild/isopack.js
index 7bfb88b57e..91f62b9faf 100644
--- a/tools/isobuild/isopack.js
+++ b/tools/isobuild/isopack.js
@@ -23,6 +23,7 @@ import { requestGarbageCollection } from "../utils/gc.js";
import { Unibuild } from "./unibuild.js";
import rspackHelpers from "../tool-env/rspack";
import { getCurrentNodeBinDir, getDevBundle } from "../fs/files";
+import { runLogInstance } from "../runners/run-log";
var rejectBadPath = function (p) {
if (p.match(/\.\./)) {
@@ -530,6 +531,9 @@ Object.assign(Isopack.prototype, {
// Share the rspackHelpers as part of plugin API
rspackHelpers,
+ // Share the runLogInstance as part of plugin API
+ runLogInstance,
+
// 'extension' is a file extension without the separation dot
// (eg 'js', 'coffee', 'coffee.md')
//
diff --git a/tools/project-context.js b/tools/project-context.js
index 83633ec6a1..d8e85fe45b 100644
--- a/tools/project-context.js
+++ b/tools/project-context.js
@@ -1470,8 +1470,7 @@ Object.assign(exports.PlatformList.prototype, {
getCordovaPlatforms: function () {
var self = this;
- return _.difference(self._platforms,
- exports.PlatformList.DEFAULT_PLATFORMS);
+ return _.intersection(self._platforms, ['ios', 'android']);
},
usesCordova: function () {
diff --git a/tools/runners/run-all.js b/tools/runners/run-all.js
index 594e20589c..b2b2567b48 100644
--- a/tools/runners/run-all.js
+++ b/tools/runners/run-all.js
@@ -218,7 +218,7 @@ class Runner {
{ arrow: true }
);
} else {
- runLog.log("App running at: " + self.rootUrl, { arrow: true });
+ runLog.log("App running at " + self.rootUrl, { arrow: true });
}
if (process.platform === "win32") {
diff --git a/tools/runners/run-log.js b/tools/runners/run-log.js
index 51efd8a4b3..f533b1787a 100644
--- a/tools/runners/run-log.js
+++ b/tools/runners/run-log.js
@@ -158,7 +158,7 @@ Object.assign(RunLog.prototype, {
self.consecutiveRestartMessages = 1;
}
- var message = "=> Meteor server restarted at: " + options.rootUrl;
+ var message = "=> Meteor server restarted at " + options.rootUrl;
if (self.consecutiveRestartMessages > 1) {
message += " (x" + self.consecutiveRestartMessages + ")";
}
@@ -225,3 +225,6 @@ var runLogInstance = new RunLog;
function (method) {
exports[method] = runLogInstance[method].bind(runLogInstance);
});
+
+// Export the singleton instance for use in plugins
+exports.runLogInstance = runLogInstance;
diff --git a/tools/static-assets/skel-angular/package.json b/tools/static-assets/skel-angular/package.json
index 757dc6798c..fa3105b024 100644
--- a/tools/static-assets/skel-angular/package.json
+++ b/tools/static-assets/skel-angular/package.json
@@ -22,7 +22,7 @@
},
"devDependencies": {
"@angular/compiler-cli": "^20.0.0",
- "@meteorjs/rspack": "^1.0.1",
+ "@meteorjs/rspack": "^1.1.0-beta.31",
"@nx/angular-rspack": "^21.1.0",
"@rsdoctor/rspack-plugin": "^1.2.3",
"@rspack/cli": "^1.7.1",
diff --git a/tools/static-assets/skel-apollo/package.json b/tools/static-assets/skel-apollo/package.json
index 53df518efa..5fd18f2131 100644
--- a/tools/static-assets/skel-apollo/package.json
+++ b/tools/static-assets/skel-apollo/package.json
@@ -20,7 +20,7 @@
"devDependencies": {
"@graphql-tools/webpack-loader": "^7.0.0",
"@rsdoctor/rspack-plugin": "^1.2.3",
- "@meteorjs/rspack": "^1.0.1",
+ "@meteorjs/rspack": "^1.1.0-beta.31",
"@rspack/cli": "^1.7.1",
"@rspack/core": "^1.7.1",
"@rspack/plugin-react-refresh": "^1.4.3",
diff --git a/tools/static-assets/skel-babel/package.json b/tools/static-assets/skel-babel/package.json
index 4d7826b580..8c9c45753f 100644
--- a/tools/static-assets/skel-babel/package.json
+++ b/tools/static-assets/skel-babel/package.json
@@ -17,7 +17,7 @@
"devDependencies": {
"@babel/preset-env": "^7.28.3",
"@babel/preset-react": "^7.23.3",
- "@meteorjs/rspack": "^1.0.1",
+ "@meteorjs/rspack": "^1.1.0-beta.31",
"@rsdoctor/rspack-plugin": "^1.2.3",
"@rspack/cli": "^1.7.1",
"@rspack/core": "^1.7.1",
diff --git a/tools/static-assets/skel-blaze/package.json b/tools/static-assets/skel-blaze/package.json
index 87a7e45e29..6dd838aaf6 100644
--- a/tools/static-assets/skel-blaze/package.json
+++ b/tools/static-assets/skel-blaze/package.json
@@ -14,7 +14,7 @@
"meteor-node-stubs": "^1.2.12"
},
"devDependencies": {
- "@meteorjs/rspack": "^1.0.1",
+ "@meteorjs/rspack": "^1.1.0-beta.31",
"@rsdoctor/rspack-plugin": "^1.2.3",
"@rspack/cli": "^1.7.1",
"@rspack/core": "^1.7.1",
diff --git a/tools/static-assets/skel-chakra-ui/package.json b/tools/static-assets/skel-chakra-ui/package.json
index 63ec45405e..a30ef9f180 100644
--- a/tools/static-assets/skel-chakra-ui/package.json
+++ b/tools/static-assets/skel-chakra-ui/package.json
@@ -21,7 +21,7 @@
"react-dom": "^18.2.0"
},
"devDependencies": {
- "@meteorjs/rspack": "^1.0.1",
+ "@meteorjs/rspack": "^1.1.0-beta.31",
"@rsdoctor/rspack-plugin": "^1.2.3",
"@rspack/cli": "^1.7.1",
"@rspack/core": "^1.7.1",
diff --git a/tools/static-assets/skel-coffeescript/package.json b/tools/static-assets/skel-coffeescript/package.json
index adbcf48a9e..6f2ebbce93 100644
--- a/tools/static-assets/skel-coffeescript/package.json
+++ b/tools/static-assets/skel-coffeescript/package.json
@@ -15,7 +15,7 @@
"react-dom": "^18.2.0"
},
"devDependencies": {
- "@meteorjs/rspack": "^1.0.1",
+ "@meteorjs/rspack": "^1.1.0-beta.31",
"@rsdoctor/rspack-plugin": "^1.2.3",
"@rspack/cli": "^1.7.1",
"@rspack/core": "^1.7.1",
diff --git a/tools/static-assets/skel-full/package.json b/tools/static-assets/skel-full/package.json
index 541293d7b5..e7f8bb952e 100644
--- a/tools/static-assets/skel-full/package.json
+++ b/tools/static-assets/skel-full/package.json
@@ -12,7 +12,7 @@
"meteor-node-stubs": "^1.2.12"
},
"devDependencies": {
- "@meteorjs/rspack": "^1.0.1",
+ "@meteorjs/rspack": "^1.1.0-beta.31",
"@rsdoctor/rspack-plugin": "^1.2.3",
"@rspack/cli": "^1.7.1",
"@rspack/core": "^1.7.1",
diff --git a/tools/static-assets/skel-react/package.json b/tools/static-assets/skel-react/package.json
index a54a989670..1091b1727a 100644
--- a/tools/static-assets/skel-react/package.json
+++ b/tools/static-assets/skel-react/package.json
@@ -15,7 +15,7 @@
"react-dom": "^18.2.0"
},
"devDependencies": {
- "@meteorjs/rspack": "^1.0.1",
+ "@meteorjs/rspack": "^1.1.0-beta.31",
"@rsdoctor/rspack-plugin": "^1.2.3",
"@rspack/cli": "^1.7.1",
"@rspack/core": "^1.7.1",
diff --git a/tools/static-assets/skel-solid/package.json b/tools/static-assets/skel-solid/package.json
index 335c56716e..d14262c517 100644
--- a/tools/static-assets/skel-solid/package.json
+++ b/tools/static-assets/skel-solid/package.json
@@ -14,7 +14,7 @@
"picocolors": "^1.1.1"
},
"devDependencies": {
- "@meteorjs/rspack": "^1.0.1",
+ "@meteorjs/rspack": "^1.1.0-beta.31",
"@rsdoctor/rspack-plugin": "^1.2.3",
"@rspack/cli": "^1.7.1",
"@rspack/core": "^1.7.1",
diff --git a/tools/static-assets/skel-svelte/package.json b/tools/static-assets/skel-svelte/package.json
index 02e36fd2d8..d864cf7c23 100644
--- a/tools/static-assets/skel-svelte/package.json
+++ b/tools/static-assets/skel-svelte/package.json
@@ -13,7 +13,7 @@
"meteor-node-stubs": "^1.2.12"
},
"devDependencies": {
- "@meteorjs/rspack": "^1.0.1",
+ "@meteorjs/rspack": "^1.1.0-beta.31",
"@rsdoctor/rspack-plugin": "^1.2.3",
"@rspack/cli": "^1.7.1",
"@rspack/core": "^1.7.1",
diff --git a/tools/static-assets/skel-tailwind/package.json b/tools/static-assets/skel-tailwind/package.json
index 10589551f4..79935caba4 100644
--- a/tools/static-assets/skel-tailwind/package.json
+++ b/tools/static-assets/skel-tailwind/package.json
@@ -16,7 +16,7 @@
"react-dom": "^17.0.2"
},
"devDependencies": {
- "@meteorjs/rspack": "^1.0.1",
+ "@meteorjs/rspack": "^1.1.0-beta.31",
"@rsdoctor/rspack-plugin": "^1.2.3",
"@rspack/cli": "^1.7.1",
"@rspack/core": "^1.7.1",
diff --git a/tools/static-assets/skel-typescript/package.json b/tools/static-assets/skel-typescript/package.json
index 0080cdfb3b..59017498e0 100644
--- a/tools/static-assets/skel-typescript/package.json
+++ b/tools/static-assets/skel-typescript/package.json
@@ -15,7 +15,7 @@
"react-dom": "^18.2.0"
},
"devDependencies": {
- "@meteorjs/rspack": "^1.0.1",
+ "@meteorjs/rspack": "^1.1.0-beta.31",
"@rsdoctor/rspack-plugin": "^1.2.3",
"@rspack/cli": "^1.7.1",
"@rspack/core": "^1.7.1",
diff --git a/tools/static-assets/skel-typescript/tsconfig.json b/tools/static-assets/skel-typescript/tsconfig.json
index 4cc0e0d73b..aff98434d9 100644
--- a/tools/static-assets/skel-typescript/tsconfig.json
+++ b/tools/static-assets/skel-typescript/tsconfig.json
@@ -36,7 +36,8 @@
"resolveJsonModule": true,
"types": ["node", "mocha"],
"esModuleInterop": true,
- "preserveSymlinks": true
+ "preserveSymlinks": true,
+ "skipLibCheck": true
},
"exclude": [
"./.meteor/**",
diff --git a/tools/static-assets/skel-vue/package.json b/tools/static-assets/skel-vue/package.json
index 4ef2501d63..a156645d98 100644
--- a/tools/static-assets/skel-vue/package.json
+++ b/tools/static-assets/skel-vue/package.json
@@ -17,7 +17,7 @@
"vue-router": "^4.2.5"
},
"devDependencies": {
- "@meteorjs/rspack": "^1.0.1",
+ "@meteorjs/rspack": "^1.1.0-beta.31",
"@rsdoctor/rspack-plugin": "^1.2.3",
"@rspack/cli": "^1.7.1",
"@rspack/core": "^1.7.1",
diff --git a/tools/tests/run.js b/tools/tests/run.js
index 0093676d50..e5c7ffeab6 100644
--- a/tools/tests/run.js
+++ b/tools/tests/run.js
@@ -463,12 +463,12 @@ selftest.define("'meteor run --port' accepts/rejects proper values", async funct
run = s.run("run", "--port", "3500");
run.waitSecs(30);
- await run.match('App running at: http://localhost:3500/');
+ await run.match('App running at http://localhost:3500/');
await run.stop();
run = s.run("run", "--port", "127.0.0.1:3500");
run.waitSecs(30);
- await run.match('App running at: http://127.0.0.1:3500/');
+ await run.match('App running at http://127.0.0.1:3500/');
await run.stop();
});
@@ -491,7 +491,7 @@ selftest.define("update package during run", async function () {
var runRun = s.run();
runRun.waitSecs(3);
- await runRun.match("App running at:");
+ await runRun.match("App running at");
var updateRun = s.run("update", "glasser:package-for-selftest");
await updateRun.match(
diff --git a/tools/tests/server-restart-port.js b/tools/tests/server-restart-port.js
index 47a7e3a49c..7a02aea685 100644
--- a/tools/tests/server-restart-port.js
+++ b/tools/tests/server-restart-port.js
@@ -23,5 +23,5 @@ async function testHelper(server) {
(match, n) => `module.id, ${ ++n }`,
));
- await run.match("Meteor server restarted at: http://localhost:21000/");
+ await run.match("Meteor server restarted at http://localhost:21000/");
}
diff --git a/tools/tests/test-modes.js b/tools/tests/test-modes.js
index f06eccebeb..eca226e461 100644
--- a/tools/tests/test-modes.js
+++ b/tools/tests/test-modes.js
@@ -20,17 +20,17 @@ selftest.define("'meteor test --port' accepts/rejects proper values", async func
run = s.run("test", "--port", "3700", "--driver-package", "tmeasday:acceptance-test-driver");
run.waitSecs(30);
- await run.match('App running at: http://localhost:3700/');
+ await run.match('App running at http://localhost:3700/');
await run.stop();
run = s.run("test", "--port", "127.0.0.1:3700", "--driver-package", "tmeasday:acceptance-test-driver");
run.waitSecs(30);
- await run.match('App running at: http://127.0.0.1:3700/');
+ await run.match('App running at http://127.0.0.1:3700/');
await run.stop();
run = s.run("test", "--port", "[::]:3700", "--driver-package", "tmeasday:acceptance-test-driver");
run.waitSecs(30);
- await run.match('App running at: http://[::]:3700/');
+ await run.match('App running at http://[::]:3700/');
await run.stop();
});
diff --git a/tools/tests/typescript.js b/tools/tests/typescript.js
index 2828cc6a2b..7933ee5265 100644
--- a/tools/tests/typescript.js
+++ b/tools/tests/typescript.js
@@ -1,27 +1,28 @@
-// var selftest = require('../tool-testing/selftest.js');
-// var Sandbox = selftest.Sandbox;
-//
-// selftest.define("typescript template works", function () {
-// const s = new Sandbox;
-//
-// let run = s.run("create", "--typescript", "typescript");
-//
-// run.waitSecs(60);
-// run.match("Created a new Meteor app in 'typescript'.");
-// run.match("To run your new app");
-//
-// s.cd("typescript");
-//
-// run = s.run("npm", "install");
-// run.expectExit(0);
-//
-// run = s.run("lint");
-// run.waitSecs(60);
-// run.match("[zodern:types] Exiting \"meteor lint\" early");
-// run.expectExit(0);
-//
-// run = s.run("npx", "tsc");
-// run.waitSecs(60);
-// run.expectEnd();
-// run.expectExit(0);
-// });
+var selftest = require('../tool-testing/selftest.js');
+var Sandbox = selftest.Sandbox;
+
+selftest.define("typescript template works", async function () {
+ const s = new Sandbox();
+ await s.init();
+
+ let run = s.run("create", "--typescript", "typescript");
+
+ run.waitSecs(60);
+ await run.match("Created a new Meteor app in 'typescript'.");
+ await run.match("To run your new app");
+
+ s.cd("typescript");
+
+ run = s.run("npm", "install");
+ await run.expectExit(0);
+
+ run = s.run("lint");
+ run.waitSecs(60);
+ await run.match('[zodern:types] Exiting "meteor lint" early');
+ await run.expectExit(0);
+
+ run = s.run("npx", "tsc");
+ run.waitSecs(60);
+ await run.expectEnd();
+ await run.expectExit(0);
+});
diff --git a/tools/tool-env/rspack.js b/tools/tool-env/rspack.js
index 766ed44ec0..be7794dfba 100644
--- a/tools/tool-env/rspack.js
+++ b/tools/tool-env/rspack.js
@@ -1,17 +1,25 @@
// Helper functions for Rspack integration
const files = require('../fs/files');
+const path = require('path');
const { getMeteorConfig } = require("./meteor-config");
const config = getMeteorConfig();
+// Derive the METEOR_LOCAL_DIR suffix the same way packages/rspack/lib/constants.js does,
+// so reset cleans the correct directories when running multiple instances.
+const meteorLocalDirName = process.env.METEOR_LOCAL_DIR
+ ? path.basename(process.env.METEOR_LOCAL_DIR.replace(/\\/g, '/'))
+ : '';
+const localDirSuffix = meteorLocalDirName ? `-${meteorLocalDirName}` : '';
+
// Get the build context from environment variable or use default "_build"
-const rspackBuildContext = config?.buildContext || process.env.RSPACK_BUILD_CONTEXT || "_build";
+const rspackBuildContext = config?.buildContext || process.env.RSPACK_BUILD_CONTEXT || `_build${localDirSuffix}`;
// Get the assets context from environment variable or use default "build-assets"
-const rspackAssetsContext = config?.assetsContext || process.env.RSPACK_ASSETS_CONTEXT || "build-assets";
+const rspackAssetsContext = config?.assetsContext || process.env.RSPACK_ASSETS_CONTEXT || `build-assets${localDirSuffix}`;
// Get the bundles context from environment variable or use default "build-chunks"
-const rspackChunksContext = config?.chunksContext || process.env.RSPACK_CHUNKS_CONTEXT || "build-chunks";
+const rspackChunksContext = config?.chunksContext || process.env.RSPACK_CHUNKS_CONTEXT || `build-chunks${localDirSuffix}`;
// Cache the regex pattern for performance
const rspackFilePattern = new RegExp(`^${rspackBuildContext}\\/.*\\/[^\\/]*-rspack\\.js$`);
@@ -35,16 +43,51 @@ exports.getRspackResourcesContexts = function() {
];
};
-// Function to get the rspack app contexts
+// Function to get the rspack app contexts for cleanup.
+// Reads the app's package.json meteor config to resolve custom context names,
+// and always includes the default paths to prevent regressions.
exports.getRspackAppContexts = function(appDir) {
- const rspackResourcesContexts = exports.getRspackResourcesContexts();
- return [
+ let appConfig = null;
+ try {
+ const pkgPath = files.pathJoin(appDir, 'package.json');
+ if (files.exists(pkgPath)) {
+ const pkg = JSON.parse(files.readFile(pkgPath, 'utf8'));
+ appConfig = pkg?.meteor || null;
+ }
+ } catch (e) {
+ // Fall back to defaults if package.json can't be read
+ }
+
+ const appBuildContext = appConfig?.buildContext || process.env.RSPACK_BUILD_CONTEXT || `_build${localDirSuffix}`;
+ const appAssetsContext = appConfig?.assetsContext || process.env.RSPACK_ASSETS_CONTEXT || `build-assets${localDirSuffix}`;
+ const appChunksContext = appConfig?.chunksContext || process.env.RSPACK_CHUNKS_CONTEXT || `build-chunks${localDirSuffix}`;
+
+ const contexts = [
files.pathJoin(appDir, "node_modules", ".cache", "rspack"),
- files.pathJoin(appDir, rspackBuildContext),
- ...rspackResourcesContexts.reduce((arr, context) => [
- ...arr,
- files.pathJoin(appDir, `public/${context}`),
- files.pathJoin(appDir, `public/${context}`)
- ], [])
];
+
+ // Collect unique context names (configured + defaults to prevent regressions)
+ const allNames = new Set([
+ appBuildContext, '_build',
+ appAssetsContext, 'build-assets',
+ appChunksContext, 'build-chunks',
+ ]);
+
+ for (const name of allNames) {
+ contexts.push(files.pathJoin(appDir, name));
+ contexts.push(files.pathJoin(appDir, `public/${name}`));
+ contexts.push(files.pathJoin(appDir, `private/${name}`));
+ }
+
+ // When METEOR_LOCAL_DIR is set, also include suffixed paths
+ if (localDirSuffix) {
+ for (const name of allNames) {
+ const suffixed = `${name}${localDirSuffix}`;
+ contexts.push(files.pathJoin(appDir, suffixed));
+ contexts.push(files.pathJoin(appDir, `public/${suffixed}`));
+ contexts.push(files.pathJoin(appDir, `private/${suffixed}`));
+ }
+ }
+
+ return contexts;
};
diff --git a/tools/unit-tests/jest.config.js b/tools/unit-tests/jest.config.js
index a21cc6c896..f78071ebc9 100644
--- a/tools/unit-tests/jest.config.js
+++ b/tools/unit-tests/jest.config.js
@@ -10,13 +10,13 @@ module.exports = {
],
testPathIgnorePatterns: [
"/node_modules/",
- "/tools/modern-tests/",
+ "/tools/e2e-tests/",
"/tools/tests/",
"/packages/",
"/.github/",
],
modulePathIgnorePatterns: [
- "/tools/modern-tests/",
+ "/tools/e2e-tests/",
"/tools/tests/",
"/tools/static-assets/",
"/npm-packages/",
diff --git a/tools/utils/utils.js b/tools/utils/utils.js
index 54bbfe0b31..81344b1196 100644
--- a/tools/utils/utils.js
+++ b/tools/utils/utils.js
@@ -34,11 +34,7 @@ exports.parseUrl = function (str, defaults) {
}
var hasScheme = exports.hasScheme(str);
- if (! hasScheme) {
- str = "http://" + str;
- }
-
- var parsed = url.parse(str);
+ const parsed = url.parse(hasScheme ? str : `http://${str}`);
// for consistency remove colon at the end of protocol
parsed.protocol = parsed.protocol.replace(/\:$/, '');
@@ -59,10 +55,7 @@ exports.parseUrl = function (str, defaults) {
exports.formatUrl = function (options) {
// For consistency with `Meteor.absoluteUrl`, add a trailing slash to make
// this a valid URL
- if (!options.pathname)
- options.pathname = "/";
-
- return url.format(options);
+ return url.format({ ...options, pathname: options.pathname || "/" });
};
exports.ipAddress = function () {
@@ -88,11 +81,6 @@ ${addressEntries.map(entry => entry.address).join(', ')}`);
return addressEntries[0].address;
};
-exports.hasScheme = function (str) {
- return !! str.match(/^[A-Za-z][A-Za-z0-9+-\.]*\:\/\//);
-};
-
-
exports.hasScheme = function (str) {
return !! str.match(/^[A-Za-z][A-Za-z0-9+-\.]*\:\/\//);
};
@@ -105,20 +93,8 @@ exports.isIPv4Address = function (str) {
// Prints a package list in a nice format.
// Input is an array of objects with keys 'name' and 'description'.
exports.printPackageList = function (items, options) {
- options = options || {};
-
- var rows = _.map(items, function (item) {
- var name = item.name;
- var description = item.description || 'No description';
- return [name, description];
- });
-
- var alphaSort = function (row) {
- return row[0];
- };
- rows = _.sortBy(rows, alphaSort);
-
- var Console = require('../console/console.js').Console;
+ const rows = _.sortBy(items.map(item => [item.name, item.description || 'No description']), row => row[0]);
+ const Console = require('../console/console.js').Console;
return Console.printTwoColumns(rows, options);
};
@@ -773,3 +749,4 @@ export function isEmacs() {
emacsDetected = !! (process.env.EMACS === "t" || process.env.INSIDE_EMACS);
return emacsDetected;
}
+
diff --git a/v3-docs/docs/about/modern-build-stack/meteor-bundler-optimizations.md b/v3-docs/docs/about/modern-build-stack/meteor-bundler-optimizations.md
index 6c76240f18..6c3d84cb53 100644
--- a/v3-docs/docs/about/modern-build-stack/meteor-bundler-optimizations.md
+++ b/v3-docs/docs/about/modern-build-stack/meteor-bundler-optimizations.md
@@ -165,7 +165,9 @@ You can use `.swcrc` config in the root of your project to describe specific [SW
You can also configure other options using the `.swcrc` format. For custom SWC configs, see the [SWC configuration API](https://swc.rs/docs/configuration/compilation).
-Use `swc.config.js` in your project root for dynamic configuration. Meteor will import and apply the SWC config automatically. This lets you choose a config based on environment variables or other runtime factors.
+Use `swc.config.js` in your project root for dynamic configuration. Meteor will import and apply the SWC config automatically. This lets you choose a config based on environment variables or other runtime factors. If you prefer TypeScript, `swc.config.ts` is also supported, Meteor will transpile and load it automatically.
+
+Meteor checks for config files in this order: `.swcrc` > `swc.config.js` > `swc.config.ts`. Only the first one found is used.
You can also review these migration topics that use custom `.swcrc` configs:
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..f053475cf8 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
@@ -157,21 +157,54 @@ You can use flags to control the final configuration based on the environment. T
| `compileWithRspack` | function | Forces given npm deps ([Condition](https://rspack.rs/config/module#condition)[]) to be compiled by Rspack |
| `setCache` | function | Enables or disables cache. Accepts true (persistent, default), false, or 'memory' |
| `splitVendorChunk` | function | Splits vendor libraries so they are automatically served from a separate chunk |
-| `extendSwcConfig` | function | Extends the [SWC loader configuration](https://rspack.rs/guide/features/builtin-swc-loader#options) to apply only to the app code |
+| `extendSwcConfig` | function | Smart-merges custom options into Meteor's default [SWC loader configuration](https://rspack.rs/guide/features/builtin-swc-loader#options), applying only to app code |
+| `replaceSwcConfig` | function | Replaces Meteor's default [SWC loader configuration](https://rspack.rs/guide/features/builtin-swc-loader#options) entirely with the provided options, applying only to app code |
| `extendConfig` | function | Extends the config by applying merged object configs |
+| `enablePortableBuild` | function | Omits `Meteor.isDevelopment` and `Meteor.isProduction` from the bundle, making it portable across environments |
Some configurations in the Rspack config are reserved for the Meteor-Rspack setup to work, such as Rspack options inside the `entry` and `output` objects. These will trigger warnings if modified. All other settings can be overridden, giving you the flexibility to make any setup compatible with the modern bundler.
-If you want to see the final Rspack config applying your overrides, you can enable verbose mode in the modern build stack.
+If you want to see the final Rspack config applying your overrides, you can enable [verbose mode](#enable-verbose-mode) in the modern build stack.
+
+## Logging
+
+Starting with Meteor 3.4.1, the log output for the default Meteor-Rspack app is simplified to stay as close as possible to Meteor's native experience. By default, logs are less verbose and only show essential information like server restarts and client modifications.
+
+If there are any compilation warnings or errors, Rspack logs will be shown with their own style and colors.
+
+### Enable Verbose Mode
+
+If you need more details about Meteor and Rspack processes, you can enable verbose mode in your `package.json`:
```json
-"meteor": {
- "modern": {
- "verbose": true
+{
+ "meteor": {
+ "modern": {
+ "verbose": true
+ }
}
}
```
+### Advanced Rspack Logging
+
+For even deeper insights into the Rspack compilation process, you can configure [`stats`](https://rspack.rs/config/stats#stats) and [`infrastructureLogging`](https://rspack.rs/config/infrastructure-logging#infrastructurelogging) directly in your `rspack.config.js`.
+
+- **`stats`**: Controls what bundle information is displayed on each compilation.
+- **`infrastructureLogging`**: Controls Rspack infrastructure logs, including HMR verbosity in both the terminal and the browser. To enable detailed logs for updates and serving client code changes, set `infrastructureLogging.level` to `'info'` or higher (it is not enabled by default).
+
+```javascript
+module.exports = defineConfig(Meteor => {
+ return {
+ stats: 'detailed', // or other Rspack stats options
+ infrastructureLogging: {
+ level: 'info',
+ },
+ // ... rest of your config
+ };
+});
+```
+
## Migration Topics
### Entry Points
@@ -232,15 +265,7 @@ if (condition) {
For background, see: [Why nested import](https://github.com/benjamn/reify/blob/main/WHY_NEST_IMPORTS.md).
-To use Rspack, migrate your nested imports to a standard form. To identify and fix nested imports in your project, [use verbose mode in Meteor 3.3’s modern transpiler](./meteor-bundler-optimizations.md#optimize-swc-and-handle-fallbacks). Enable it with:
-
-```json
-"meteor": {
- "modern": {
- "verbose": true
- }
-}
-```
+To use Rspack, migrate your nested imports to a standard form. To identify and fix nested imports in your project, use [verbose mode](#enable-verbose-mode) to see which files are failing.
When you run your app, `[Transpiler]` logs will show each file. Focus on `(app)` files that fail with messages like:
@@ -604,27 +629,62 @@ module.exports = defineConfig(Meteor => ({
This is a quick configuration for split chunks all within `node_modules` as a `vendor` chunk, if you need more control you can use the [official Rspack split chunks integration guide](https://rspack.rs/guide/optimization/code-splitting#splitchunksplugin).
-### Extending SWC config
+### Customizing SWC config
Rspack uses the SWC configuration to transpile your app code. By default, it inherits any settings from the `.swcrc` file, which also [impacts how Meteor transpiles core and package code](meteor-bundler-optimizations.md#custom-swcrc).
-If you want a configuration to apply only to your app code, you can extend the SWC setup using the `Meteor.extendSwcConfig` helper:
+If you want a configuration to apply only to your app code (not Meteor packages), two helpers are available:
+
+#### `Meteor.extendSwcConfig` - smart merge (recommended)
+
+Merges your custom options on top of Meteor's defaults using a deep merge strategy (the same used by `Meteor.extendConfig`). Only the properties you specify are overridden; everything else (parser settings, React refresh, external helpers, etc) is preserved.
```js
const { defineConfig } = require('@meteorjs/rspack');
module.exports = defineConfig(Meteor => ({
- // Extend SWC config
+ // Add decorator support while keeping all Meteor defaults
...Meteor.extendSwcConfig({
jsc: {
parser: {
- syntax: 'typescript',
+ decorators: true,
},
},
}),
}));
```
+#### `Meteor.replaceSwcConfig` - full replacement
+
+Discards Meteor's defaults entirely and uses the provided config as-is. Use this when you need complete control over SWC and the smart merge doesn't fit your use case.
+
+```js
+const { defineConfig } = require('@meteorjs/rspack');
+
+module.exports = defineConfig(Meteor => ({
+ // Full SWC config — no Meteor defaults applied
+ ...Meteor.replaceSwcConfig({
+ jsc: {
+ parser: {
+ syntax: 'typescript',
+ tsx: true,
+ decorators: true,
+ },
+ target: 'es2020',
+ transform: {
+ react: {
+ runtime: 'automatic',
+ },
+ },
+ },
+ }),
+}));
+```
+
+:::warning
+When using `replaceSwcConfig`, you are responsible for providing all necessary SWC options. Features like React refresh, external helpers and parser defaults that Meteor configures (`Meteor.swcConfigOptions`) will not be applied unless you include them yourself.
+:::
+
### Interop for Default Imports
Meteor originally handled default imports from CommonJS modules automatically. This allowed you to write:
@@ -671,7 +731,7 @@ Meteor cache remains active and continues to handle Atmosphere packages and inte
This Rspack cache is enabled by default in persistent mode. If you [encounter issues](https://github.com/web-infra-dev/rspack/issues/11804) or prefer to disable it, you can do so in your `rspack.config.js` using the helper:
-```json
+```javascript
const { defineConfig } = require('@meteorjs/rspack');
const { rspack } = require('@rspack/core');
const NodePolyfillPlugin = require('node-polyfill-webpack-plugin');
@@ -759,6 +819,40 @@ module.exports = defineConfig(Meteor => ({
}));
```
+### Portable Build
+
+By default, Meteor-Rspack replaces `Meteor.isDevelopment` and `Meteor.isProduction` with static values at build time. This follows modern bundler conventions where `mode: "production"` enables aggressive dead-code elimination, any code inside `if (Meteor.isDevelopment) { ... }` blocks is stripped entirely from production builds.
+
+This is the recommended default. It produces smaller, more secure bundles by ensuring development-only code never ships to production. `meteor build` benefits directly from this, as the final output is as lean as possible.
+
+If you need a single build that works across environments (for example, building once and deploying to both staging and production without rebuilding), you can opt in to portable builds. This omits `Meteor.isDevelopment` and `Meteor.isProduction` from compile-time replacement, keeping them as runtime checks instead.
+
+```js
+const { defineConfig } = require('@meteorjs/rspack');
+
+module.exports = defineConfig(Meteor => ({
+ ...Meteor.enablePortableBuild(),
+}));
+```
+
+Note that this trades build optimization for portability — dead-code elimination for development/production branches will no longer apply, resulting in larger bundles. Other flags like `Meteor.isClient`, `Meteor.isServer`, and `Meteor.isTest` are always replaced at build time, since they depend on the build target.
+
+### Running Multiple Instances
+
+By default, Meteor and Rspack use fixed directories for their build caches (`.meteor/local` and `_build`). If you try to run multiple instances of the same app simultaneously, they may conflict by attempting to write to the same folders.
+
+To run multiple instances, you can use the `METEOR_LOCAL_DIR` environment variable to specify a unique local directory for each instance. When this variable is set, the Meteor-Rspack integration automatically extracts the directory name and uses it as a suffix for Rspack's build contexts (`_build`, `build-chunks`, and `build-assets`), ensuring complete isolation between instances.
+
+```bash
+# Instance 1
+PORT=3000 METEOR_LOCAL_DIR=.meteor/local-1 meteor run
+
+# Instance 2
+PORT=3001 METEOR_LOCAL_DIR=.meteor/local-2 meteor run
+```
+
+For more details on how this variable affects Rspack, see the [`METEOR_LOCAL_DIR`](../../cli/environment-variables.md#meteor_local_dir) documentation.
+
## Benefits
Meteor–Rspack integration sends your app code to Rspack to use modern bundler features. Meteor then uses Rspack’s output to handle Meteor-specific tasks (like Atmosphere package compilation) and create the final bundle.
@@ -804,9 +898,13 @@ You can compare performance before and after enabling `modern` by running [`mete
Large apps are more likely to hit memory limits during Meteor-Rspack builds, but this can also happen on smaller projects depending on the number of dependencies, cache size, and available system memory. If you experience crashes or out-of-memory errors, it's likely that the Rspack child process is running out of heap memory.
-A common first reaction is to set [`TOOL_NODE_FLAGS`](../../cli/environment-variables.md#tool-node-flags)` (`TOOL_NODE_FLAGS="--max-old-space-size=8192"`), but this flag is mainly for the Meteor tool's own Node.js process at startup. Rspack runs as a spawned child process and may not inherit it.
+Starting from Meteor 3.4.1, you can use [`TOOL_NODE_FLAGS`](../../cli/environment-variables.md#tool-node-flags) to set memory limits that will be automatically inherited by Rspack and other tool processes:
-Instead, use the standard `NODE_OPTIONS` environment variable, which Node.js propagates to child processes:
+```bash
+TOOL_NODE_FLAGS="--max-old-space-size=16384" meteor run
+```
+
+For Meteor 3.4, you should use the standard `NODE_OPTIONS` environment variable, which Node.js propagates to child processes:
```bash
NODE_OPTIONS="--max-old-space-size=16384" meteor run
@@ -814,10 +912,6 @@ NODE_OPTIONS="--max-old-space-size=16384" meteor run
This raises the heap limit for the Rspack process and should reduce how often memory-related crashes occur. Adjust the value according to your machine's available memory.
-:::info
-For the Meteor 3.4.x series, as `NODE_OPTIONS` is confirmed to help, one option being considered is to automatically inherit memory settings from `TOOL_NODE_FLAGS` into the spawned Rspack process.
-:::
-
Another approach is to disable Rspack's persistent cache, which is enabled by default and can be memory-intensive. See the [Cache](#cache) migration topic to disable it:
```js
@@ -828,7 +922,7 @@ module.exports = defineConfig(Meteor => ({
}));
```
-You can combine both solutions: raise the heap limit with `NODE_OPTIONS` and disable persistent cache to reduce overall memory pressure.
+You can combine both solutions: raise the heap limit with `TOOL_NODE_FLAGS` (3.4.1+) or `NODE_OPTIONS` (3.4) and disable persistent cache to reduce overall memory pressure.
Rspack itself has reported plans to optimize persistent cache and overall RAM consumption in [Rspack 2.0](https://rspack.rs/misc/planning/roadmap), which should improve memory behavior in future Meteor-Rspack releases.
diff --git a/v3-docs/docs/cli/environment-variables.md b/v3-docs/docs/cli/environment-variables.md
index b67bc28d91..72bfb8924a 100644
--- a/v3-docs/docs/cli/environment-variables.md
+++ b/v3-docs/docs/cli/environment-variables.md
@@ -95,6 +95,20 @@ This way each command only processes the files it actually needs, reducing build
`METEOR_IGNORE` is automatically set when using the [Rspack bundler integration](../about/modern-build-stack/rspack-bundler-integration.md). Since Rspack handles the client and server app bundling, Meteor's bundler should only worry about what it strictly needs for the Meteor-Rspack integration. By using `METEOR_IGNORE` to exclude folders and dependencies that Rspack already manages or that are irrelevant to Meteor's side of the build, you ensure the most speed is gained from the Rspack delegation.
:::
+## METEOR_LOCAL_DIR
+(_development_)
+
+This environment variable allows you to change the location of the `.meteor/local` directory, which Meteor uses to store its build cache and other local state. This is useful for running multiple instances of the same app with different local states or for redirecting the local directory to a different drive or path.
+
+When using the [Rspack bundler integration](../about/modern-build-stack/rspack-bundler-integration.md), `METEOR_LOCAL_DIR` also influences the Rspack build context. It extracts the name of the folder represented in the path and appends it as a suffix to the following Rspack constants:
+- `RSPACK_BUILD_CONTEXT`
+- `RSPACK_CHUNKS_CONTEXT`
+- `RSPACK_ASSETS_CONTEXT`
+
+For example, if `METEOR_LOCAL_DIR` is set to `/path/to/.meteor/local-custom`, Rspack will use `_build-local-custom`, `build-chunks-local-custom`, and `build-assets-local-custom` as its context directories, ensuring that build artifacts remain isolated for that specific local environment.
+
+For more information, see the [Running Multiple Instances](../about/modern-build-stack/rspack-bundler-integration.md#running-multiple-instances) section in the Rspack documentation.
+
## METEOR_PROFILE
(_development_)
@@ -146,9 +160,22 @@ Used to generate URLs to your application by, among others, the accounts package
## TOOL_NODE_FLAGS
(_development, production_)
-Used to pass flags/variables to Node inside Meteor build. For example you can use this to pass a link to icu data: `TOOL_NODE_FLAGS="--icu-data-dir=node_modules/full-icu"`
+Used to pass Node.js flags that Meteor will inherit and spread to other tool processes like Rspack. For example, to increase memory limits for all tools: `TOOL_NODE_FLAGS="--max-old-space-size=4096"`
+
+By default, these flags are automatically spread to `NODE_OPTIONS` so that tools like Rspack inherit them. This behavior can be controlled using [`TOOL_NODE_FLAGS_INHERIT`](#tool-node-flags-spread).
+
For full list of available flags see the [Node documentation](https://nodejs.org/dist/latest-v12.x/docs/api/cli.html).
+## TOOL_NODE_FLAGS_INHERIT
+(_development, production_)
+
+Controls whether `TOOL_NODE_FLAGS` are prepended to `NODE_OPTIONS`. Enabled by default.
+
+```bash
+# Disable inheritance - Rspack won't inherit the heap limit
+TOOL_NODE_FLAGS_INHERIT=0 TOOL_NODE_FLAGS="--max-old-space-size=4096" meteor run
+```
+
## UNIX_SOCKET_GROUP
(_production_)
diff --git a/v3-docs/docs/components/helpers/ParamTable.vue b/v3-docs/docs/components/helpers/ParamTable.vue
index 8251d3a7a1..65a6c6d4b9 100644
--- a/v3-docs/docs/components/helpers/ParamTable.vue
+++ b/v3-docs/docs/components/helpers/ParamTable.vue
@@ -1,14 +1,14 @@
@@ -51,9 +55,7 @@ const sourceCode = `https://github.com/meteor/meteor/blob/devel/packages/${props