const fs = require('fs'); const path = require('path'); const PACKAGE_ROOT = path.resolve(__dirname, '..', '..', '..'); const OUTPUT_CATEGORIES = Object.freeze([ 'login', 'api-calls', 'subscription', 'meetings', 'docs', 'messages', 'smoke', 'tmp' ]); function outputsRoot() { const override = process.env.QIWEI_OUTPUTS_DIR; if (override && String(override).trim()) return path.resolve(String(override).trim()); return path.join(PACKAGE_ROOT, 'outputs'); } function ensureDir(dirPath) { fs.mkdirSync(dirPath, { recursive: true }); return dirPath; } function assertCategory(category) { if (!OUTPUT_CATEGORIES.includes(category)) { throw new Error(`未知输出类别 "${category}",允许的类别:${OUTPUT_CATEGORIES.join(', ')}`); } return category; } function slugify(value, fallback = 'run') { const slug = String(value || '') .trim() .toLowerCase() .replace(/[^a-z0-9\u4e00-\u9fa5]+/g, '-') .replace(/^-+|-+$/g, '') .slice(0, 60); return slug || fallback; } function dateStamp(date = new Date()) { return date.toISOString().slice(0, 10); } function timeStamp(date = new Date()) { return date.toISOString().slice(11, 19).replace(/:/g, ''); } function categoryDir(category) { return ensureDir(path.join(outputsRoot(), assertCategory(category))); } function latestPath(category, filename) { return path.join(categoryDir(category), filename); } function createRunDir(category, slug, date = new Date()) { const dir = path.join( categoryDir(category), dateStamp(date), `${timeStamp(date)}-${slugify(slug)}` ); return ensureDir(dir); } function writeRunManifest(runDir, manifest = {}) { const filePath = path.join(runDir, 'manifest.json'); const body = { createdAt: new Date().toISOString(), ...manifest, files: (manifest.files || []).map(file => path.relative(runDir, path.resolve(runDir, file))) }; fs.writeFileSync(filePath, JSON.stringify(body, null, 2), 'utf8'); return filePath; } module.exports = { PACKAGE_ROOT, OUTPUT_CATEGORIES, outputsRoot, ensureDir, slugify, dateStamp, timeStamp, categoryDir, latestPath, createRunDir, writeRunManifest };