49 lines
1.4 KiB
JavaScript
49 lines
1.4 KiB
JavaScript
import fs from 'fs'
|
|
import path from 'path'
|
|
|
|
function uniqueCandidates(candidates) {
|
|
return [...new Set(candidates.filter(Boolean))]
|
|
}
|
|
|
|
function hasServerDataDir(root) {
|
|
const normalizedRoot = String(root || '').replace(/\/+$/, '')
|
|
return fs.existsSync(`${normalizedRoot}/server/data`)
|
|
}
|
|
|
|
export function resolveProjectRoot() {
|
|
const envRoot = process.env.APP_ROOT ? process.env.APP_ROOT.trim() : ''
|
|
const cwd = process.cwd()
|
|
const parent = path.resolve(cwd, '..')
|
|
|
|
const candidates = uniqueCandidates([envRoot, cwd, parent])
|
|
for (const root of candidates) {
|
|
if (hasServerDataDir(root)) return root
|
|
}
|
|
|
|
return cwd
|
|
}
|
|
|
|
export function getProjectPath(...segments) {
|
|
return path.join(resolveProjectRoot(), ...segments)
|
|
}
|
|
|
|
export function getServerDataPath(...segments) {
|
|
// Validate segments: only allow simple filenames/dirnames (no path separators)
|
|
const SEGMENT_RE = /^[a-zA-Z0-9._-]+$/
|
|
for (const s of segments) {
|
|
if (!SEGMENT_RE.test(String(s || ''))) {
|
|
throw new Error(`Invalid data path segment: ${String(s)}`)
|
|
}
|
|
}
|
|
|
|
const dataDir = getProjectPath('server', 'data')
|
|
const candidate = path.join(dataDir, ...segments)
|
|
const resolved = path.resolve(candidate)
|
|
const resolvedDataDir = path.resolve(dataDir)
|
|
if (!resolved.startsWith(resolvedDataDir + path.sep) && resolved !== resolvedDataDir) {
|
|
throw new Error('Resolved data path is outside server/data')
|
|
}
|
|
|
|
return resolved
|
|
}
|