add meteor reset E2E test coverage

This commit is contained in:
Nacho Codoñer
2026-03-04 18:26:42 +01:00
parent 81982483a9
commit f5d1c047bb
4 changed files with 211 additions and 27 deletions

View File

@@ -1865,6 +1865,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")
);
@@ -1879,19 +1888,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,
@@ -1901,11 +1914,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 = [

View File

@@ -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<void>}
*/
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

View File

@@ -23,6 +23,7 @@ import {
assertFileExist,
assertMeteorApp,
assertMeteorReactApp,
assertPathNotExist,
assertRspackScriptTag
} from "./assertions";
import fs from "fs-extra";
@@ -776,6 +777,57 @@ 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 (always check defaults)
await assertPathNotExist(appDir, buildDir);
await assertPathNotExist(appDir, 'node_modules/.cache/rspack');
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, `_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 });
}
});
};
}
@@ -1047,5 +1099,56 @@ 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
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 (always check defaults)
await assertPathNotExist(tempDir, '_build');
await assertPathNotExist(tempDir, 'node_modules/.cache/rspack');
await assertPathNotExist(tempDir, 'node_modules/.cache/meteor');
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, `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 });
}
});
};
}

View File

@@ -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,31 @@ exports.getRspackResourcesContexts = function() {
];
};
// Function to get the rspack app contexts
// Function to get the rspack app contexts for cleanup.
// Always includes the default paths (_build, build-assets, build-chunks) to
// prevent regressions, plus suffixed paths when METEOR_LOCAL_DIR is set.
exports.getRspackAppContexts = function(appDir) {
const rspackResourcesContexts = exports.getRspackResourcesContexts();
return [
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}`)
], [])
];
// Always include defaults
const defaults = ['_build', 'build-assets', 'build-chunks'];
for (const name of defaults) {
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) {
const suffixed = defaults.map(name => `${name}${localDirSuffix}`);
for (const name of suffixed) {
contexts.push(files.pathJoin(appDir, name));
contexts.push(files.pathJoin(appDir, `public/${name}`));
contexts.push(files.pathJoin(appDir, `private/${name}`));
}
}
return contexts;
};