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)
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Fix the remaining broken require paths after DC-005 refactor.
|
||||
|
||||
Two patterns to fix:
|
||||
1. `require('./src/...')` and `require('../../src/...')` and `require('../../../src/...')`
|
||||
in files inside `src/` directories → should be `require('../...')` (relative to src/)
|
||||
2. `require('../../../src/...')` in test files in `__tests__/` → should be `require('../src/...')`
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
DASHCADDY_API = Path('/root/dashcaddy-krystie/dashcaddy-api')
|
||||
|
||||
# Pattern to match require('../../../src/X/Y') and capture
|
||||
# We need to detect the file's location and rewrite based on that
|
||||
# A simple approach: find any require that contains 'src/' in the path,
|
||||
# and rewrite it to be relative to the file's location.
|
||||
|
||||
def fix_file(filepath: Path) -> bool:
|
||||
"""Returns True if file was changed."""
|
||||
content = filepath.read_text()
|
||||
original = content
|
||||
|
||||
# Find the file's directory relative to dashcaddy-api root
|
||||
rel_dir = filepath.parent.relative_to(DASHCADDY_API)
|
||||
depth = len(rel_dir.parts)
|
||||
|
||||
# If file is in src/X/Y/file.js, depth is 3 (src, X, Y)
|
||||
# If file is in __tests__/file.js, depth is 1
|
||||
# If file is in __tests__/routes/file.js, depth is 2
|
||||
|
||||
# Find all require() calls that contain 'src/'
|
||||
# Pattern: require('(.....)*src/path')
|
||||
def replacer(match):
|
||||
quote = match.group(1) # the quote char
|
||||
path = match.group(2) # the path inside quotes
|
||||
# Calculate what the path SHOULD be
|
||||
if 'src/' not in path:
|
||||
return match.group(0)
|
||||
|
||||
# Extract the part after 'src/'
|
||||
idx = path.find('src/')
|
||||
after_src = path[idx + 4:] # everything after 'src/'
|
||||
|
||||
if filepath.parts[-3] == 'src':
|
||||
# File is in src/X/file.js - depth 3
|
||||
# Should be '../<after_src>'
|
||||
new_path = '../' + after_src
|
||||
elif filepath.parts[-4] == 'src':
|
||||
# File is in src/X/Y/file.js - depth 4
|
||||
# Should be '../../<after_src>'
|
||||
new_path = '../../' + after_src
|
||||
elif filepath.parts[-2] == '__tests__' or filepath.parent.name == '__tests__':
|
||||
# File is in __tests__/file.js - depth 1 (relative to api root)
|
||||
# Should be '../src/<after_src>'
|
||||
new_path = '../src/' + after_src
|
||||
elif filepath.parts[-2] == 'routes' and filepath.parts[-3] == '__tests__':
|
||||
# File is in __tests__/routes/file.js - depth 2
|
||||
# Should be '../../src/<after_src>'
|
||||
new_path = '../../src/' + after_src
|
||||
elif 'src' in rel_dir.parts:
|
||||
# Other src nested location
|
||||
# Count how many .. we need
|
||||
src_depth = len(rel_dir.parts) - list(rel_dir.parts).index('src') - 1
|
||||
new_path = '../' * src_depth + after_src
|
||||
else:
|
||||
# Other location, leave it
|
||||
return match.group(0)
|
||||
|
||||
return f"require({quote}{new_path}{quote})"
|
||||
|
||||
new_content = re.sub(
|
||||
r"require\((['\"])([^'\"]*src/[^'\"]*)\1\)",
|
||||
replacer,
|
||||
content
|
||||
)
|
||||
|
||||
if new_content != original:
|
||||
filepath.write_text(new_content)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
changed = []
|
||||
for js_file in DASHCADDY_API.rglob('*.js'):
|
||||
# Skip node_modules
|
||||
if 'node_modules' in js_file.parts:
|
||||
continue
|
||||
if fix_file(js_file):
|
||||
changed.append(str(js_file.relative_to(DASHCADDY_API)))
|
||||
|
||||
print(f"Changed {len(changed)} files:")
|
||||
for f in changed:
|
||||
print(f" {f}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,180 @@
|
||||
#!/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).`);
|
||||
Reference in New Issue
Block a user