27 lines
1.7 KiB
JavaScript
27 lines
1.7 KiB
JavaScript
import { resolve, relative, isAbsolute } from 'node:path'
|
|
import { existsSync, realpathSync, statSync, unlinkSync } from 'node:fs'
|
|
import { scopedFile } from '../shared/studio2/contracts.mjs'
|
|
|
|
/** Exact Studio 2 files only: no folder fallback, sibling deletion, or sweeping. */
|
|
export function purgeStudio2Files({prefix, files}, rootsForType) {
|
|
if (!Array.isArray(files) || !files.length || files.length>100 || !prefix) throw new Error('Invalid Studio 2 purge manifest')
|
|
const targets=[]
|
|
for (const file of files) {
|
|
if (!['input','output'].includes(file.type) || !scopedFile(file,prefix) || !String(file.subfolder).replaceAll('\\','/').startsWith(String(prefix).replace(/\/$/,'')+'/studio2/')) throw new Error('Purge file is outside the Studio 2 prefix')
|
|
const roots=rootsForType(file.type)
|
|
if(!roots.length)throw new Error('No media root is configured for cleanup')
|
|
for (const root of roots) {
|
|
const target=resolve(root,file.subfolder,file.filename),rel=relative(resolve(root),target)
|
|
if (!rel || rel.startsWith('..') || isAbsolute(rel)) throw new Error('Purge path escapes its media root')
|
|
if (!existsSync(target)) continue
|
|
const realRoot=realpathSync(root),realTarget=realpathSync(target),realRel=relative(realRoot,realTarget)
|
|
if (!realRel || realRel.startsWith('..') || isAbsolute(realRel) || !statSync(realTarget).isFile()) throw new Error('Purge target is not a media file inside its root')
|
|
const prefixRel=relative(resolve(realRoot,prefix),realTarget)
|
|
if(!prefixRel || prefixRel.startsWith('..') || isAbsolute(prefixRel))throw new Error('Purge target resolves outside the instance prefix')
|
|
targets.push(target)
|
|
}
|
|
}
|
|
for(const target of new Set(targets))unlinkSync(target)
|
|
return {ok:true,cleared:files.length,deleted:[...new Set(targets)]}
|
|
}
|