baodan/.agents/skills/impeccable/scripts/lib/is-generated.mjs
wsb1224 e3479f0546 上线阻断问题全部修复
#	问题	修复	文件
1	前端构建失败(引号错误)	size="small type=" → size="small" type="	PosterHistoryPage.vue
2	migrate_014 ORM vs 缺失列	全部改为原始 SQL,不再引用 ORM 模型	migrate_014.py
3	cleanup 字段名错误	output_path → ppt_path	cleanup.py
4	文案生成 case 越权	添加 case.user_id != user_id 校验	poster/service.py
5	存储路径未接通持久化卷	全部改用 get_storage_root()(默认 /app/api/storage/insurance)	config.py, ppt/routes.py, poster/service.py, poster/tasks.py
高风险问题修复
#	问题	修复	文件
6	migrate_019 rollback 撤销成功字段	每个 ALTER 后立即 commit,失败只回滚当前语句	migrate_019.py
7	迁移锁 Windows 不兼容 + 句柄未持久化	全局变量保存锁句柄,支持 Windows msvcrt	api/insurance/db/__init__.py
8	PDF 校验异常时放行	异常返回 False(文件损坏)	security.py
9	健康检查始终返回成功	缺少关键资源时返回 503 + missing 列表	poster/routes.py
10	短密钥掩码泄露原值	≤4 字符返回 ****	ppt_admin_service.py
11	设置无键名白名单	添加 _ALLOWED_SETTING_KEYS 白名单	ppt_admin_service.py
12	容器重启任务永久 stuck	添加 recover_stale_tasks() 启动恢复函数	poster/tasks.py, ppt/parse_worker.py
2026-07-27 13:52:09 +08:00

70 lines
2.2 KiB
JavaScript

/**
* Decide whether a given file is "generated" (regenerated by a build step,
* unsafe to write variants into) or "source" (safe to edit, changes persist).
*
* Why this matters: when the user picks an element on a page whose underlying
* file is regenerated by a build step (e.g. `scripts/build-sub-pages.js`
* rewriting `public/docs/*.html`), writing variants or accepted changes into
* that file is silent data loss — the next build wipes them.
*
* Signals, in order of reliability:
* 1. Git check-ignore: gitignored files are assumed generated.
* 2. File-header markers ("GENERATED", "DO NOT EDIT", "AUTO-GENERATED")
* within the first ~300 characters — catches non-git projects.
*/
import { execSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
const HEADER_SCAN_BYTES = 300;
const HEADER_MARKERS = [
/@generated\b/i,
/\bGENERATED\s+FILE\b/,
/\bAUTO-?GENERATED\b/i,
/\bDO\s+NOT\s+EDIT\b/i,
];
/**
* @param {string} filePath - absolute or cwd-relative path
* @param {object} [options]
* @param {string} [options.cwd] - project root (defaults to process.cwd())
*/
export function isGeneratedFile(filePath, options = {}) {
const cwd = options.cwd || process.cwd();
const absPath = path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath);
if (isGitIgnored(absPath, cwd)) return true;
if (hasGeneratedHeader(absPath)) return true;
return false;
}
function isGitIgnored(absPath, cwd) {
try {
execSync(`git check-ignore --quiet ${JSON.stringify(absPath)}`, {
cwd,
stdio: 'ignore',
});
return true; // exit 0 = ignored
} catch (err) {
// Exit code 1 = not ignored. Exit code 128 = not a git repo or other error.
// In both cases, treat as "not known to be ignored."
return false;
}
}
function hasGeneratedHeader(absPath) {
let fd;
try {
fd = fs.openSync(absPath, 'r');
const buf = Buffer.alloc(HEADER_SCAN_BYTES);
const bytesRead = fs.readSync(fd, buf, 0, HEADER_SCAN_BYTES, 0);
const head = buf.slice(0, bytesRead).toString('utf-8');
return HEADER_MARKERS.some((re) => re.test(head));
} catch {
return false;
} finally {
if (fd !== undefined) { try { fs.closeSync(fd); } catch {} }
}
}