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.
54 lines
2.5 KiB
JavaScript
54 lines
2.5 KiB
JavaScript
/**
|
|
* Platform-specific paths and environment variable configuration
|
|
*/
|
|
const path = require('path');
|
|
const platformPaths = require('../../platform-paths');
|
|
|
|
const CADDYFILE_PATH = process.env.CADDYFILE_PATH || platformPaths.caddyfile;
|
|
const CADDY_ADMIN_URL = process.env.CADDY_ADMIN_URL || platformPaths.caddyAdminUrl;
|
|
const SERVICES_FILE = process.env.SERVICES_FILE || platformPaths.servicesFile;
|
|
const SERVICES_DIR = path.dirname(SERVICES_FILE);
|
|
const CONFIG_FILE = process.env.CONFIG_FILE || path.join(SERVICES_DIR, 'config.json');
|
|
const DNS_CREDENTIALS_FILE = process.env.DNS_CREDENTIALS_FILE || path.join(SERVICES_DIR, 'dns-credentials.json');
|
|
const TAILSCALE_CONFIG_FILE = process.env.TAILSCALE_CONFIG_FILE || path.join(SERVICES_DIR, 'tailscale-config.json');
|
|
const NOTIFICATIONS_FILE = process.env.NOTIFICATIONS_FILE || path.join(SERVICES_DIR, 'notifications.json');
|
|
const TOTP_CONFIG_FILE = process.env.TOTP_CONFIG_FILE || path.join(SERVICES_DIR, 'totp-config.json');
|
|
const ERROR_LOG_FILE = process.env.ERROR_LOG_FILE || path.join(platformPaths.dataDir, 'error.log');
|
|
const LICENSE_SECRET_FILE = process.env.LICENSE_SECRET_FILE || path.join(platformPaths.dataDir, '.license-secret');
|
|
|
|
const BROWSE_ROOTS = (process.env.MEDIA_BROWSE_ROOTS || '')
|
|
.split(',')
|
|
.filter(r => r.includes('='))
|
|
.map(r => {
|
|
const eqIndex = r.indexOf('=');
|
|
const containerPath = r.slice(0, eqIndex).trim();
|
|
const hostPath = r.slice(eqIndex + 1).trim();
|
|
return { containerPath, hostPath };
|
|
});
|
|
|
|
module.exports = {
|
|
CADDYFILE_PATH,
|
|
CADDY_ADMIN_URL,
|
|
SERVICES_FILE,
|
|
SERVICES_DIR,
|
|
// Re-export the resolved data directory so other modules (notably
|
|
// src/utilities/nesting-guard.js) can locate `/app/data` without having to
|
|
// also require('../../platform-paths') — keeps a single source of truth for
|
|
// the data dir on the src/config/paths surface. Without this, `dataDir`
|
|
// resolves to `undefined`, and `path.join(undefined, 'data')` throws
|
|
// `TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type
|
|
// string. Received undefined` at startup (DC-077 fingerprint). Fall back to
|
|
// platformPaths.dataDir if SERVICES_DIR is somehow not a string (defensive —
|
|
// SERVICES_DIR is computed from a path.dirname() of a string so it always
|
|
// is, but the cost of guarding is one branch).
|
|
dataDir: typeof SERVICES_DIR === 'string' && SERVICES_DIR ? SERVICES_DIR : platformPaths.dataDir,
|
|
CONFIG_FILE,
|
|
DNS_CREDENTIALS_FILE,
|
|
TAILSCALE_CONFIG_FILE,
|
|
NOTIFICATIONS_FILE,
|
|
TOTP_CONFIG_FILE,
|
|
ERROR_LOG_FILE,
|
|
LICENSE_SECRET_FILE,
|
|
BROWSE_ROOTS,
|
|
};
|