bump @meteorjs/rspack version to 0.0.54 fixing ignore mechanism on eager tests

This commit is contained in:
Nacho Codoñer
2025-09-22 16:46:10 +02:00
parent 14b4596315
commit fd28e7a39a
19 changed files with 153 additions and 185 deletions

View File

@@ -1,172 +1,139 @@
var fs = require('fs');
var path = require('path');
// Cleans an entry from wildcard patterns (*/**)
function cleanWildcardEntry(entry) {
// If it's an extension pattern like *.ext, skip it
if (entry.match(/\*\.[^\/]+$/)) {
return null;
}
// Handle patterns like my-folder/**/* by extracting the folder part
if (entry.includes('/**/')) {
const folderContext = entry.split('/**/')[0].replace(/\/+$/, '');
if (folderContext) {
return folderContext;
}
}
// Otherwise, extract the folder context by removing the wildcard part
if (entry.includes('*')) {
const folderContext = entry.split('*')[0].replace(/\/+$/, '');
if (folderContext) {
return folderContext;
}
return null;
}
return entry;
}
/**
* Reads the .meteorignore file from the given project directory and returns
* the parsed entries.
* the parsed entries. Empty lines and comment lines (starting with #) are filtered out.
*
* @param {string} projectDir - The project directory path
* @returns {Object} - Object with rootFolders and nestedFolders arrays
* @returns {string[]} - Array of ignore patterns
*/
const getMeteorIgnoreEntries = function (projectDir) {
const meteorIgnorePath = path.join(projectDir, '.meteorignore');
// Check if .meteorignore file exists
let entries = [];
try {
const fileContent = fs.readFileSync(meteorIgnorePath, 'utf8');
// Process each line in the file
entries = fileContent.split(/\r?\n/).filter(line => {
// Trim the line
const trimmedLine = line.trim();
// Skip empty lines, comments, and negation entries (starting with !)
return trimmedLine !== '' && !trimmedLine.startsWith('#') && !trimmedLine.startsWith('!');
}).map(line => line.trim()); // Ensure all lines are trimmed
const entries = fileContent.split(/\r?\n/)
.map(line => line.trim())
.filter(line => line !== '' && !line.startsWith('#'));
// Clean all entries from wildcard patterns (*/** parts)
entries = entries.map(entry => {
return cleanWildcardEntry(entry);
}).filter(entry => entry !== null);
// Separate entries into rootFolders and nestedFolders
const rootFolders = [];
const nestedFolders = [];
entries.forEach(entry => {
// If entry starts with / or ./, it's a root folder
if (entry.startsWith('/') || entry.startsWith('./')) {
rootFolders.push(entry);
} else {
// Otherwise, it's a nested folder
nestedFolders.push(entry);
}
});
return { rootFolders, nestedFolders };
return entries;
} catch (e) {
// If the file doesn't exist or can't be read, return empty arrays
return { rootFolders: [], nestedFolders: [] };
// If the file doesn't exist or can't be read, return empty array
return [];
}
};
/**
* Creates a regex pattern to ignore specified folders.
* The pattern will match paths where the specified folders appear as complete path segments.
* Special regex characters in folder names are automatically escaped.
* @param {Object|string[]} options - Options object
* @param {string[]} [options.nestedFolders] - Array of folder names to ignore anywhere in the path
* @param {string[]} [options.rootFolders] - Array of folder names that should only match at the root level
* @returns {RegExp} - Regex pattern to ignore the specified folders
* Creates a glob config array for ignoring specified patterns.
* Transforms .gitignore-style entries into chokidar-compatible glob patterns.
* @param {string[]} entries - Array of .gitignore-style patterns
* @returns {string[]} - Array of glob patterns for chokidar
*/
function createIgnoreFoldersRegex(options) {
const nestedFolders = options.nestedFolders || [];
const rootFolders = options.rootFolders || [];
if (!Array.isArray(nestedFolders) || nestedFolders.length === 0) {
throw new Error('nestedFolders must be a non-empty array');
function createIgnoreGlobConfig(entries = []) {
if (!Array.isArray(entries)) {
throw new Error('Entries must be an array');
}
// If rootFolders is not provided or empty, use the original behavior
if (!rootFolders || !Array.isArray(rootFolders) || rootFolders.length === 0) {
// Escape special regex characters in folder names
const escapedFolders = nestedFolders.map(folder =>
folder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
);
// Join folder names with | for the regex pattern
const foldersPattern = escapedFolders.join('|');
// Create a regex that matches paths where the specified folders appear as complete path segments
// Format: /(^|\/)(folder1|folder2|folder3)(\/|$)/
return new RegExp(`(^|\\/)(${foldersPattern})(\\/|$)`);
}
// Handle both rootFolders and nestedFolders
// Escape special regex characters in folder names
const escapedNestedFolders = nestedFolders.map(folder =>
folder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
);
const escapedRootFolders = rootFolders.map(folder =>
folder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
);
// Join folder names with | for the regex patterns
const nestedFoldersPattern = escapedNestedFolders.join('|');
const rootFoldersPattern = escapedRootFolders.join('|');
// Create a regex that matches:
// 1. Root folders at the beginning of the path: /^(folderRootOnly)(\/|$)/
// 2. Nested folders anywhere in the path: /(^|\/)(folderAny1|folderAny2)(\/|$)/
const pattern = `^(${rootFoldersPattern})(\\/|$)|(^|\\/)(${nestedFoldersPattern})(\\/|$)`;
return new RegExp(pattern);
}
/**
* Creates a glob config array for ignoring specified folders.
* For nested folders, the pattern will be "**/" + folder + "/**".
* For root folders, the pattern will be folder + "/**".
* @param {Object} options - Options object
* @param {string[]} [options.nestedFolders] - Array of folder names to ignore anywhere in the path
* @param {string[]} [options.rootFolders] - Array of folder names that should only match at the root level
* @returns {string[]} - Array of glob patterns to ignore the specified folders
*/
function createIgnoreGlobConfig(options = {}) {
const nestedFolders = options.nestedFolders || [];
const rootFolders = options.rootFolders || [];
const globPatterns = [];
// Create glob patterns for nested folders: **/{nestedFolder}/**
if (Array.isArray(nestedFolders) && nestedFolders.length > 0) {
nestedFolders.forEach(folder => {
// Remove leading ./ or / if present
const cleanFolder = folder.replace(/^(\.\/|\/)/g, '');
globPatterns.push(`**/${cleanFolder}/**`);
});
}
entries.forEach(entry => {
// Skip empty entries
if (!entry.trim()) {
return;
}
// Create glob patterns for root folders: {rootFolder}/**
if (Array.isArray(rootFolders) && rootFolders.length > 0) {
rootFolders.forEach(folder => {
// Remove leading ./ or / if present
const cleanFolder = folder.replace(/^(\.\/|\/)/g, '');
globPatterns.push(`${cleanFolder}/**`);
});
}
// Handle comments
if (entry.startsWith('#')) {
return;
}
// Check if it's a negation pattern
const isNegation = entry.startsWith('!');
let pattern = isNegation ? entry.substring(1).trim() : entry.trim();
// Remove leading ./ or / if present
pattern = pattern.replace(/^(\.\/|\/)/g, '');
// If it ends with /, it's a directory pattern, add ** to match all contents
if (pattern.endsWith('/')) {
pattern = pattern.slice(0, -1) + '/**';
}
// If it doesn't include a /, it could match anywhere in the path
if (!pattern.includes('/')) {
pattern = '**/' + pattern;
} else if (!pattern.startsWith('**/') && !pattern.startsWith('/')) {
// If it has a / but doesn't start with **/, add **/ to match anywhere
pattern = '**/' + pattern;
}
// Add the negation back if it was present
if (isNegation) {
pattern = '!' + pattern;
}
globPatterns.push(pattern);
});
return globPatterns;
}
/**
* Creates a regex pattern to match the specified glob patterns.
* Converts glob patterns with * and ** into regex equivalents.
*
* @param {string[]} globPatterns - Array of glob patterns from createIgnoreGlobConfig
* @returns {RegExp} - Regex pattern to match the specified patterns
*/
function createIgnoreRegex(globPatterns) {
if (!Array.isArray(globPatterns) || globPatterns.length === 0) {
throw new Error('globPatterns must be a non-empty array');
}
// Process each glob pattern and convert to regex
const regexPatterns = globPatterns.map(pattern => {
// Skip negation patterns for the regex
if (pattern.startsWith('!')) {
return null;
}
// Escape special regex characters, but not * and /
let regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
// Use a temporary placeholder for ** that won't be affected by the * replacement
// This is necessary because if we directly replace ** with .* and then replace * with [^/]*
const DOUBLE_ASTERISK_PLACEHOLDER = '__DOUBLE_ASTERISK__';
regexPattern = regexPattern.replace(/\*\*/g, DOUBLE_ASTERISK_PLACEHOLDER);
// Convert * to regex equivalent (any number of characters except /)
regexPattern = regexPattern.replace(/\*/g, '[^/]*');
// Convert the ** placeholder to its regex equivalent (any number of characters including /)
regexPattern = regexPattern.replace(new RegExp(DOUBLE_ASTERISK_PLACEHOLDER, 'g'), '.*');
// 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 + '$';
return regexPattern;
}).filter(pattern => pattern !== null);
if (regexPatterns.length === 0) {
// If all patterns were negations, return a regex that matches nothing
return new RegExp('^$');
}
// Join all patterns with | to create a single regex
const combinedPattern = regexPatterns.join('|');
return new RegExp(combinedPattern);
}
module.exports = {
createIgnoreFoldersRegex,
createIgnoreRegex,
getMeteorIgnoreEntries,
createIgnoreGlobConfig,
};

View File

@@ -1,6 +1,6 @@
const fs = require('fs');
const path = require('path');
const { createIgnoreFoldersRegex } = require("./ignore.js");
const { createIgnoreRegex, createIgnoreGlobConfig } = require("./ignore.js");
/**
* Generates eager test files dynamically
@@ -8,33 +8,34 @@ const { createIgnoreFoldersRegex } = require("./ignore.js");
* @param {boolean} options.isAppTest - Whether this is an app test
* @param {string} options.projectDir - The project directory
* @param {string} options.buildContext - The build context
* @param {string} options.rootFolders
* @param {string} options.nestedFolders
* @param {string[]} options.entries - Array of ignore patterns
* @returns {string} The path to the generated file
*/
const generateEagerTestFile = ({
isAppTest,
projectDir,
buildContext,
rootFolders,
nestedFolders,
entries = [],
}) => {
const distDir = path.resolve(projectDir, ".meteor/local/test");
if (!fs.existsSync(distDir)) {
fs.mkdirSync(distDir, { recursive: true });
}
const excludeFoldersRegex = createIgnoreFoldersRegex({
nestedFolders: [
"node_modules",
".meteor",
"public",
"private",
buildContext,
...nestedFolders,
],
rootFolders,
});
// Combine all ignore entries
const ignoreEntries = [
"**/node_modules/**",
"**/.meteor/**",
"**/public/**",
"**/private/**",
`**/${buildContext}/**`,
...entries,
];
// Create regex from ignore entries
const excludeFoldersRegex = createIgnoreRegex(
createIgnoreGlobConfig(ignoreEntries)
);
const filename = isAppTest ? "eager-app-tests.mjs" : "eager-tests.mjs";
const filePath = path.resolve(distDir, filename);

View File

@@ -1,12 +1,12 @@
{
"name": "@meteorjs/rspack",
"version": "0.0.53",
"version": "0.0.54",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@meteorjs/rspack",
"version": "0.0.53",
"version": "0.0.54",
"license": "ISC",
"dependencies": {
"ignore-loader": "^0.1.2",

View File

@@ -1,6 +1,6 @@
{
"name": "@meteorjs/rspack",
"version": "0.0.53",
"version": "0.0.54",
"description": "Configuration logic for using Rspack in Meteor projects",
"main": "index.js",
"type": "commonjs",

View File

@@ -195,22 +195,24 @@ module.exports = async function (inMeteor = {}, argv = {}) {
};
// Get Meteor ignore entries
const { rootFolders, nestedFolders } = getMeteorIgnoreEntries(projectDir);
const meteorIgnoreEntries = getMeteorIgnoreEntries(projectDir);
// Additional ignore entries
const additionalEntries = [
"**/.meteor/local/**",
"**/dist/**",
...(isTest && isTestEager
? [`**/${buildContext}/**`, "**/.meteor/local/**", "node_modules/**"]
: []),
];
// Set default watch options
const watchOptions = {
ignored: [
...createIgnoreGlobConfig({
rootFolders,
nestedFolders: [
".meteor/local",
"dist",
...(isTest && isTestEager
? [buildContext, ".meteor/local", "node_modules"]
: []),
...(nestedFolders || []),
],
}),
...createIgnoreGlobConfig([
...meteorIgnoreEntries,
...additionalEntries,
]),
],
};
@@ -379,16 +381,14 @@ module.exports = async function (inMeteor = {}, argv = {}) {
isAppTest: true,
projectDir,
buildContext,
rootFolders,
nestedFolders,
entries: meteorIgnoreEntries,
})
: isTest && isTestEager
? generateEagerTestFile({
isAppTest: false,
projectDir,
buildContext,
rootFolders,
nestedFolders,
entries: meteorIgnoreEntries,
})
: path.resolve(projectDir, buildContext, entryPath);
const serverNameConfig = `[${(isTest && 'test-') || ''}${

View File

@@ -5,7 +5,7 @@
export const DEFAULT_RSPACK_VERSION = '1.5.3';
export const DEFAULT_METEOR_RSPACK_VERSION = '0.0.53';
export const DEFAULT_METEOR_RSPACK_VERSION = '0.0.54';
export const DEFAULT_METEOR_RSPACK_REACT_HMR_VERSION = '1.4.3';

View File

@@ -17,7 +17,7 @@
"vue-router": "^4.2.5"
},
"devDependencies": {
"@meteorjs/rspack": "^0.0.53",
"@meteorjs/rspack": "^0.0.54",
"@rspack/cli": "^1.4.8",
"@rspack/core": "^1.4.8",
"@tailwindcss/postcss": "^4.1.12",

View File

@@ -20,7 +20,7 @@
"devDependencies": {
"@graphql-tools/webpack-loader": "^7.0.0",
"@rsdoctor/rspack-plugin": "^1.2.3",
"@meteorjs/rspack": "^0.0.53",
"@meteorjs/rspack": "^0.0.54",
"@rspack/cli": "^1.5.3",
"@rspack/core": "^1.5.3",
"@rspack/plugin-react-refresh": "^1.4.3",

View File

@@ -17,7 +17,7 @@
"devDependencies": {
"@babel/preset-env": "^7.28.3",
"@babel/preset-react": "^7.23.3",
"@meteorjs/rspack": "^0.0.53",
"@meteorjs/rspack": "^0.0.54",
"@rsdoctor/rspack-plugin": "^1.2.3",
"@rspack/cli": "^1.5.3",
"@rspack/core": "^1.5.3",

View File

@@ -14,7 +14,7 @@
"meteor-node-stubs": "^1.2.12"
},
"devDependencies": {
"@meteorjs/rspack": "^0.0.53",
"@meteorjs/rspack": "^0.0.54",
"@rsdoctor/rspack-plugin": "^1.2.3",
"@rspack/cli": "^1.5.3",
"@rspack/core": "^1.5.3",

View File

@@ -21,7 +21,7 @@
"react-dom": "^18.2.0"
},
"devDependencies": {
"@meteorjs/rspack": "^0.0.53",
"@meteorjs/rspack": "^0.0.54",
"@rsdoctor/rspack-plugin": "^1.2.3",
"@rspack/cli": "^1.5.3",
"@rspack/core": "^1.5.3",

View File

@@ -15,7 +15,7 @@
"react-dom": "^18.2.0"
},
"devDependencies": {
"@meteorjs/rspack": "^0.0.53",
"@meteorjs/rspack": "^0.0.54",
"@rsdoctor/rspack-plugin": "^1.2.3",
"@rspack/cli": "^1.5.3",
"@rspack/core": "^1.5.3",

View File

@@ -12,7 +12,7 @@
"meteor-node-stubs": "^1.2.12"
},
"devDependencies": {
"@meteorjs/rspack": "^0.0.53",
"@meteorjs/rspack": "^0.0.54",
"@rsdoctor/rspack-plugin": "^1.2.3",
"@rspack/cli": "^1.5.3",
"@rspack/core": "^1.5.3",

View File

@@ -15,7 +15,7 @@
"react-dom": "^18.2.0"
},
"devDependencies": {
"@meteorjs/rspack": "^0.0.53",
"@meteorjs/rspack": "^0.0.54",
"@rsdoctor/rspack-plugin": "^1.2.3",
"@rspack/cli": "^1.5.3",
"@rspack/core": "^1.5.3",

View File

@@ -14,7 +14,7 @@
"picocolors": "^1.1.1"
},
"devDependencies": {
"@meteorjs/rspack": "^0.0.53",
"@meteorjs/rspack": "^0.0.54",
"@rsdoctor/rspack-plugin": "^1.2.3",
"@rspack/cli": "^1.5.3",
"@rspack/core": "^1.5.3",

View File

@@ -13,7 +13,7 @@
"meteor-node-stubs": "^1.2.12"
},
"devDependencies": {
"@meteorjs/rspack": "^0.0.53",
"@meteorjs/rspack": "^0.0.54",
"@rsdoctor/rspack-plugin": "^1.2.3",
"@rspack/cli": "^1.5.3",
"@rspack/core": "^1.5.3",

View File

@@ -16,7 +16,7 @@
"react-dom": "^17.0.2"
},
"devDependencies": {
"@meteorjs/rspack": "^0.0.53",
"@meteorjs/rspack": "^0.0.54",
"@rsdoctor/rspack-plugin": "^1.2.3",
"@rspack/cli": "^1.5.3",
"@rspack/core": "^1.5.3",

View File

@@ -15,7 +15,7 @@
"react-dom": "^18.2.0"
},
"devDependencies": {
"@meteorjs/rspack": "^0.0.53",
"@meteorjs/rspack": "^0.0.54",
"@rsdoctor/rspack-plugin": "^1.2.3",
"@rspack/cli": "^1.5.3",
"@rspack/core": "^1.5.3",

View File

@@ -17,7 +17,7 @@
"vue-router": "^4.2.5"
},
"devDependencies": {
"@meteorjs/rspack": "^0.0.53",
"@meteorjs/rspack": "^0.0.54",
"@rsdoctor/rspack-plugin": "^1.2.3",
"@rspack/cli": "^1.5.3",
"@rspack/core": "^1.5.3",