@remediator/core
Version:
Remix/React Router 7 Mediator
202 lines (186 loc) • 7.76 kB
JavaScript
import fs from "fs";
import path from "path";
const generateItems = async (options) => {
const { path: searchPath = "app", includeFileNames = [
"*.mediator.ts",
"*.mediator.server.ts",
"*.server.mediator.ts",
], outputDir = "remediator", } = options;
console.log("reMediator: Generating client...");
try {
// Find all mediator files
const allFiles = [];
for (const pattern of includeFileNames) {
const files = await findFilesRecursively(searchPath, [pattern]);
allFiles.push(...files);
}
if (allFiles.length === 0) {
console.warn("reMediator: No files found for auto-registration");
return;
}
console.log(`reMediator: Found ${allFiles.length} mediator files`);
// Generate manifest
const manifest = {
generatedAt: new Date().toISOString(),
files: allFiles.map((filePath) => ({
path: path.relative(process.cwd(), filePath).replace(/\\/g, "/"),
name: path.basename(filePath),
})),
};
// --- Start of new logic ---
const outputDirAbs = path.resolve(process.cwd(), outputDir);
// 1. Generate imports with paths relative to the output directory
const imports = allFiles.map((filePath) => {
// Get path relative to the output directory for the import statement
const importPath = path
.relative(outputDirAbs, filePath)
.replace(/\\/g, "/")
.replace(/\.ts$/, "");
// Get a unique module name from the path relative to the project root
const moduleName = getModuleName(path.relative(process.cwd(), filePath));
return `import * as ${moduleName} from "${importPath}";`;
});
const moduleNames = allFiles.map((filePath) => getModuleName(path.relative(process.cwd(), filePath)));
// 2. Generate registration logic that aggregates all exports first
const registrationLogic = `
const allExports = new Map();
const allModules = [${moduleNames.join(", ")}];
for (const module of allModules) {
for (const [key, value] of Object.entries(module)) {
if (!allExports.has(key)) {
allExports.set(key, value);
}
}
}
//console.log('reMediator: All found exports:', Array.from(allExports.keys()));
const registeredItems: { Type: string; Name: string; Details: string }[] = [];
// Register middleware before handlers
allExports.forEach((value, key) => {
// Convention: middleware functions end with 'Middleware'
if (key.endsWith('Middleware') && typeof value === 'function') {
reMediator.use(value as any);
registeredItems.push({ Type: 'Middleware', Name: key, Details: 'Registered' });
}
});
allExports.forEach((value, key) => {
// Convention: handler classes end with 'Handler'
if (key.endsWith('Handler')) {
const handler = value;
if (handler !== null && typeof handler === 'function') {
const requestKey = key.replace('Handler', '');
const requestCtor = allExports.get(requestKey);
if (requestCtor && typeof requestCtor === 'function') {
reMediator.register(requestCtor as any, new (handler as any)());
registeredItems.push({ Type: 'Handler', Name: key, Details: \`Handles -> \${requestKey}\` });
}
}
}
});
console.table(registeredItems);
`;
// 3. Generate the client code
const clientCode = `// Auto-generated by reMediator plugin
// Generated at: ${new Date().toISOString()}
// Files found: ${allFiles.length}
${imports.join("\n")}
import { reMediator as ReMediatorClass } from '@remediator/core';
// Create a new instance for the client
export const reMediator = new ReMediatorClass();
// Auto-registration function
function registerAllHandlers() {
console.log('reMediator: Auto-registering ${allFiles.length} modules...');
${registrationLogic}
console.log('reMediator: Auto-registration complete');
}
// Auto-register when this code runs
registerAllHandlers();
// No need to export reMediator again, it's already exported as a const`;
// --- End of new logic ---
// Create output directory
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// Write files
fs.writeFileSync(path.join(outputDir, "manifest.json"), JSON.stringify(manifest, null, 2));
fs.writeFileSync(path.join(outputDir, "client.ts"), clientCode);
console.log(`reMediator: Generated client at ${outputDir}/client.ts`);
console.log(`reMediator: Generated manifest at ${outputDir}/manifest.json`);
}
catch (error) {
console.error("reMediator: Error generating client:", error);
}
};
export function reMediatorPlugin(options = {}) {
const { path: searchPath = "app", includeFileNames = [
"*.mediator.ts",
"*.mediator.server.ts",
"*.server.mediator.ts",
], outputDir = "remediator", } = options;
return {
name: "remediator-auto-registration",
async buildStart() {
await generateItems(options);
},
async configureServer(server) {
// Generate client on server start
await generateItems(options);
// In development, watch for changes and regenerate
const watchPatterns = includeFileNames;
watchPatterns.forEach((pattern) => {
server.watcher.add(`${searchPath}/**/${pattern}`);
});
server.watcher.on("change", async (file) => {
// Check if the changed file matches any of our patterns
const fileName = path.basename(file);
const fileMatchesPattern = includeFileNames.some((pattern) => matchesPattern(fileName, pattern));
if (fileMatchesPattern) {
console.log("reMediator: File changed, regenerating client...");
await generateItems(options);
}
});
},
};
}
// Simple recursive file search
async function findFilesRecursively(dirPath, patterns) {
const files = [];
try {
const items = fs.readdirSync(dirPath);
for (const item of items) {
const fullPath = path.join(dirPath, item);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
if (item === "node_modules" ||
item === "dist" ||
item === "build" ||
item === ".git") {
continue;
}
const subFiles = await findFilesRecursively(fullPath, patterns);
files.push(...subFiles);
}
else if (stat.isFile()) {
const fileName = item;
if (patterns.some((pattern) => matchesPattern(fileName, pattern))) {
files.push(fullPath);
}
}
}
}
catch (error) {
// Only log if the directory doesn't exist, not for other errors
if (error.code !== "ENOENT") {
console.warn(`reMediator: Error reading directory ${dirPath}:`, error.message);
}
}
return files;
}
// Simple pattern matching
function matchesPattern(fileName, pattern) {
const regexPattern = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*");
const regex = new RegExp(`^${regexPattern}$`);
return regex.test(fileName);
}
function getModuleName(modulePath) {
return modulePath.replace(/[^a-zA-Z0-9]/g, "_");
}