Pre-fix, every dashcaddy-api container startup logged:
[nesting-guard] Skipped: The "path" argument must be of type string. Received undefined
because src/utilities/nesting-guard.js does require('../config/paths') and
calls paths.dataDir — but src/config/paths.js imported platformPaths and
only re-exported its specific files (SERVICES_FILE, CONFIG_FILE, etc);
dataDir was never re-exported, so paths.dataDir was undefined.
Result: path.join(undefined, 'data') threw TypeError, the outer try/catch
swallowed it, and the entire nesting-guard became a silent no-op. The
cleanup that prevents recursive data/data/data/... directory duplicates
never ran on any startup. Bug class is 'silent functional no-op' (same
family as DC-056 AggregateError visibility).
(1) src/config/paths.js (+11): re-export dataDir as
SERVICES_DIR-derived (with platformPaths.dataDir fallback). dataDir is
the dirname of SERVICES_FILE in container (env override wins), which
equals /app/data — same value platform-paths.dataDir computes for the
default config. Either path is fine; SERVICES_DIR is preferred because it
respects env-override.
(2) src/utilities/nesting-guard.js (+13/-2): defensive fallback to
require('../../platform-paths').dataDir if paths.dataDir is missing
(any future export-shape drift or older caller). Explicit skip-warn
instead of silent catch when both paths fail.
(3) __tests__/nesting-guard.test.js (NEW, 112 lines, 4/4 passing):
isolates module cache per test, exercises (a) cleanup when nested
data/data exists, (b) no-op when clean, (c) dataDir export contract,
(d) dataDir === dirname(SERVICES_FILE) under env override. No jest.doMock
leaks across tests (verified via 4-call probe sequence).
Verified: 4/4 tests passing. Full repo suite: 100/104 suites / 2335/2335
tests passing (4 pre-existing failures in __tests__/billing/* are
unrelated module-resolution issues in src/billing/invoice.js, confirmed
unaffected by this change via stash+rerun).
GLM-5.3 round 1: B (ship, one polish nit — trailing newline on test
file, folded in same commit per multi-round-fix-first protocol).
Deploy plan: container rebuild + atomic swap via /opt/dashcaddy/start.sh
on DNS2; live-verify status.sami=200, dashcaddy-api=Up+healthy, and
absence of [nesting-guard] Skipped log line in container logs.
50 lines
2.0 KiB
JavaScript
50 lines
2.0 KiB
JavaScript
/**
|
|
* Recursive data nesting guard.
|
|
*
|
|
* In past versions, a buggy update/restore path created data/data/data/...
|
|
* directories — each containing a full recursive copy of the parent.
|
|
* This module runs at startup, detects and removes nested duplicates.
|
|
*
|
|
* Add to app.js: require('./utilities/nesting-guard')();
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
module.exports = function nestingGuard() {
|
|
try {
|
|
const paths = require('../config/paths');
|
|
const dataDir = paths && paths.dataDir;
|
|
// Defensive: if paths.dataDir is undefined (older callers or a future
|
|
// export-shape drift), fall back to platformPaths.dataDir directly so the
|
|
// guard can still execute. Pre-fix this branch was swallowed silently by
|
|
// the outer try/catch, leaving the entire nesting-guard a no-op (DC-077).
|
|
const effectiveDataDir = typeof dataDir === 'string' && dataDir
|
|
? dataDir
|
|
: require('../../platform-paths').dataDir;
|
|
if (typeof effectiveDataDir !== 'string' || !effectiveDataDir) {
|
|
console.warn('[nesting-guard] Skipped: dataDir unavailable from src/config/paths and platform-paths');
|
|
return;
|
|
}
|
|
const dataDataPath = path.join(effectiveDataDir, 'data');
|
|
|
|
// If data/data exists, it's a recursive duplicate — remove it
|
|
if (fs.existsSync(dataDataPath)) {
|
|
const stat = fs.statSync(dataDataPath);
|
|
if (stat.isDirectory()) {
|
|
// Verify it's truly a duplicate (contains config.json like the parent)
|
|
const markerFile = path.join(dataDataPath, 'config.json');
|
|
const parentMarker = path.join(dataDir, 'config.json');
|
|
if (fs.existsSync(markerFile) && fs.existsSync(parentMarker)) {
|
|
console.log('[nesting-guard] Removing recursive data nesting: ' + dataDataPath);
|
|
fs.rmSync(dataDataPath, { recursive: true, force: true });
|
|
console.log('[nesting-guard] Recursive nesting removed');
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// Non-fatal — don't crash startup over cleanup
|
|
console.warn('[nesting-guard] Skipped: ' + e.message);
|
|
}
|
|
};
|