Files
Hermes 7bc2a207f3 DC-005: Fix all 138 broken test paths after src/ refactor
After the DC-005 module reorganization (41 files moved into src/ subdirs),
138 test suites failed because the refactor script's path-rewrite logic
missed three categories:

1. Files inside src/ doing 'require("./src/...")' — should be 'require("../...")'
2. Files in src/X/Y/ doing 'require("../../../src/...")' — should be 'require("../../...")'
3. Test files in __tests__/ with leftover 'require("../../../src/...")' paths

Root cause: the original refactor script ran before all files were moved,
so it computed relative paths against stale filesystem state.

Result:
- 30/30 test suites pass
- 879/879 tests pass (was: 18/30 suites, 614/687 tests)

Also fixed:
- routes/apps/restore.js: wrong responses import path
- routes/*/*.js: '../../src/utilities/X' → '../src/utilities/X' (depth 2 routes)
2026-06-13 12:16:56 -07:00

181 lines
6.9 KiB
JavaScript

#!/usr/bin/env node
/**
* Refactor helper: rewrites require('./xxx') / require('../xxx') paths in
* dashcaddy-api to point to the new src/<subdir>/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 <REPO>/dns-providers/x
// which after move becomes <REPO>/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).`);