#!/usr/bin/env node /** * Refactor helper: rewrites require('./xxx') / require('../xxx') paths in * dashcaddy-api to point to the new src//xxx.js locations. * * Algorithm: * 1. For each require() call with a relative spec: * 2. If the resolved file exists, leave it alone. * 3. If the resolved file does NOT exist, the bare name of the spec * (or the directory name 'dns-providers') might be one of the * modules that was moved out of the repo root. In that case, rewrite * the spec to the correct relative path to the new location. * 4. Otherwise leave alone. */ const fs = require('fs'); const path = require('path'); const REPO = process.cwd(); // Map: bare module name (no extension) -> new repo-relative path (no extension) const NEW_LOCATIONS = { 'auth-manager': 'src/managers/auth-manager', 'credential-manager': 'src/managers/credential-manager', 'license-manager': 'src/managers/license-manager', 'port-lock-manager': 'src/managers/port-lock-manager', 'state-manager': 'src/managers/state-manager', 'notification-manager': 'src/managers/notification-manager', 'resource-monitor': 'src/managers/resource-monitor', 'config-drift-detector': 'src/managers/config-drift-detector', 'auto-restart-manager': 'src/managers/auto-restart-manager', 'update-manager': 'src/managers/update-manager', 'dependency-manager': 'src/managers/dependency-manager', 'csrf-protection': 'src/security/csrf-protection', 'crypto-utils': 'src/security/crypto-utils', 'docker-security': 'src/security/docker-security', 'input-validator': 'src/security/input-validator', 'keychain-manager': 'src/security/keychain-manager', 'log-digest': 'src/security/log-digest', 'audit-logger': 'src/security/audit-logger', 'docker-maintenance': 'src/docker/docker-maintenance', 'app-templates': 'src/docker/app-templates', 'self-updater': 'src/docker/self-updater', 'dns-propagation': 'src/dns/dns-propagation', 'recipe-templates': 'src/recipes/recipe-templates', 'bundled-workflows': 'src/recipes/bundled-workflows', 'health-checker': 'src/monitoring/health-checker', 'metrics': 'src/monitoring/metrics', 'ssl-monitor': 'src/monitoring/ssl-monitor', 'backup-manager': 'src/utilities/backup-manager', 'error-handler': 'src/utilities/error-handler', 'errors': 'src/utilities/errors', 'fs-helpers': 'src/utilities/fs-helpers', 'pagination': 'src/utilities/pagination', 'url-resolver': 'src/utilities/url-resolver', 'config-schema': 'src/utilities/config-schema', 'constants': 'src/utilities/constants', 'middleware': 'src/utilities/middleware', 'startup-validator': 'src/utilities/startup-validator', 'cache-config': 'src/utilities/cache-config', }; const SKIP_DIRS = new Set(['node_modules', '.git']); const SKIP_FILE_PATTERNS = [/\/scripts\/refactor-requires\.js$/]; function* walk(dir) { for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { if (SKIP_DIRS.has(entry.name)) continue; const full = path.join(dir, entry.name); if (entry.isDirectory()) { yield* walk(full); } else if (entry.name.endsWith('.js')) { yield full; } } } function toRelativeFromFile(filePath, targetRel) { const fromDir = path.dirname(filePath); const targetAbs = path.resolve(REPO, targetRel); let rel = path.relative(fromDir, targetAbs); if (!rel.startsWith('.')) rel = './' + rel; return rel.split(path.sep).join('/'); } function fileExistsWithJsOrIndex(p) { // exists if p is a file, or p is a dir with index.js try { if (fs.existsSync(p) && fs.statSync(p).isFile()) return true; } catch (_) {} try { if (fs.existsSync(p + '.js') && fs.statSync(p + '.js').isFile()) return true; } catch (_) {} try { if ( fs.existsSync(p) && fs.statSync(p).isDirectory() && fs.existsSync(path.join(p, 'index.js')) ) {return true;} } catch (_) {} return false; } function refactor(filePath) { const relFile = path.relative(REPO, filePath); if (SKIP_FILE_PATTERNS.some((re) => re.test(relFile))) return false; const content = fs.readFileSync(filePath, 'utf8'); let changed = false; const requireRe = /require\(\s*(['"])([^'"]+)\1\s*\)/g; const newContent = content.replace(requireRe, (full, quote, spec) => { if (!spec.startsWith('.')) return full; // package require, leave alone const fromDir = path.dirname(filePath); const resolvedBase = path.resolve(fromDir, spec); // If the resolved file exists, the require is correct as-is. if (fileExistsWithJsOrIndex(resolvedBase)) { // But — check for the special case: require to /dns-providers/x // which after move becomes /src/dns/dns-providers/x — wait, // that doesn't exist anymore. The dir was moved. const dnsProvidersOld = path.resolve(REPO, 'dns-providers'); if ( resolvedBase === dnsProvidersOld || resolvedBase.startsWith(dnsProvidersOld + path.sep) ) { const subPath = resolvedBase === dnsProvidersOld ? '' : resolvedBase.slice(dnsProvidersOld.length + 1); const newResolved = path.resolve(REPO, 'src/dns/dns-providers', subPath); let rel = path.relative(fromDir, newResolved); if (!rel.startsWith('.')) rel = './' + rel; const newSpec = rel.split(path.sep).join('/'); changed = true; return `require(${quote}${newSpec}${quote})`; } return full; } // The file does not exist. Check if the bare name is a moved module. const bare = path.basename(resolvedBase); if (bare in NEW_LOCATIONS) { const target = NEW_LOCATIONS[bare]; const newSpec = toRelativeFromFile(filePath, target); changed = true; return `require(${quote}${newSpec}${quote})`; } // Bare not in map. Check for the special case: the spec points into // the OLD dns-providers dir (now src/dns/dns-providers). E.g. spec // could be '../dns-providers/registry' or './dns-providers/registry' // from somewhere else. if (spec.includes('dns-providers')) { const dnsProvidersOld = path.resolve(REPO, 'dns-providers'); if ( resolvedBase === dnsProvidersOld || resolvedBase.startsWith(dnsProvidersOld + path.sep) ) { const subPath = resolvedBase === dnsProvidersOld ? '' : resolvedBase.slice(dnsProvidersOld.length + 1); const newResolved = path.resolve(REPO, 'src/dns/dns-providers', subPath); let rel = path.relative(fromDir, newResolved); if (!rel.startsWith('.')) rel = './' + rel; const newSpec = rel.split(path.sep).join('/'); changed = true; return `require(${quote}${newSpec}${quote})`; } } return full; }); if (changed) { fs.writeFileSync(filePath, newContent); } return changed; } let count = 0; for (const file of walk(REPO)) { if (refactor(file)) { count += 1; console.log('rewrote', path.relative(REPO, file)); } } console.log(`\nDone: rewrote ${count} file(s).`);