UNPKG

mira-app-server

Version:

Mira Server - standalone server application using mira-app-core

172 lines 6.21 kB
"use strict"; /** * 同步过滤器 * * 把原来散落在三处(LibraryWatcher chokidar ignored、LibraryWatcher.shouldIgnore、 * FsRouter scanDiskFiles)的硬编码忽略规则,统一成一份默认规则,并叠加用户在表单里 * 配置的「黑名单 / 白名单」(gitignore 语义)。 * * 语义(与 .gitignore 一致): * 1. 先算默认排除 + 用户黑名单 → 命中即视为「排除」 * 2. 再看白名单 → 命中则「强制包含」(覆盖第 1 步的排除) * 3. 没命中黑名单的文件 → 直接包含 * * 黑名单里支持以 `!` 开头的「反向规则」:直接把该项挪到白名单,符合 gitignore 直觉。 * * 模式语法(.gitignore 风格的子集): * - `*` 匹配单层任意字符(不含 `/`) * - `**` 匹配多层任意字符(含 `/`) * - `?` 匹配单个字符(不含 `/`) * - `foo/` 目录前缀:匹配 foo 目录及其下所有内容 * - 普通字面量按原样匹配 * - 以 `#` 开头的行视为注释,空行忽略 */ Object.defineProperty(exports, "__esModule", { value: true }); exports.DEFAULT_IGNORE_PATTERNS = void 0; exports.globToRegExp = globToRegExp; exports.parsePatternLines = parsePatternLines; exports.createSyncFilter = createSyncFilter; exports.matchGlob = matchGlob; exports.getIgnoreGlobs = getIgnoreGlobs; /** 默认排除规则(从原 3 处硬编码合并而来) */ exports.DEFAULT_IGNORE_PATTERNS = [ 'thumbs', 'thumbs/**', '**/thumbs/**', '**/.*', // 任意点文件 / 点目录 '.trash', '.trash/**', '**/.trash/**', '**/*.db', '**/*.db-journal', '**/*.db-wal', '**/*.db-shm', '**/*.tmp', '**/*.temp', ]; /** * 把 glob 模式编译成一条正则。支持 `*` / `**` / `?`,对其它特殊字符做字面转义。 * `**` 匹配任意层级(含 `/`),`*` 与 `?` 不跨 `/`。 */ function globToRegExp(pattern) { // 统一 Windows 反斜杠 const p = pattern.replace(/\\/g, '/'); // 检测是否为目录前缀(以 / 结尾,如 "foo/"): // 匹配 foo 自身,以及 foo/ 下的任意层级内容 const isDirPrefix = /\/+$/.test(p); const body = p.replace(/\/+$/, ''); let re = '^'; let i = 0; while (i < body.length) { const c = body[i]; if (c === '*') { if (body[i + 1] === '*') { // ** :跨任意层级 // 处理 "**/" 与 "/**" 的可选斜杠,避免多余的斜杠导致不匹配 if (body[i + 2] === '/') { re += '(?:.*/)?'; i += 3; } else { re += '.*'; i += 2; } } else { re += '[^/]*'; i += 1; } } else if (c === '?') { re += '[^/]'; i += 1; } else if ('.+^${}()|[]'.includes(c)) { re += '\\' + c; i += 1; } else if (c === '/') { re += '/'; i += 1; } else { re += c; i += 1; } } if (isDirPrefix) { // foo/ → 匹配 "foo" 或 "foo/..." re += '($|/.*)'; } else { re += '$'; } return new RegExp(re); } /** * 解析多行 textarea 文本:去掉空行与 `#` 注释,提取以 `!` 开头的反向规则。 * @returns { include: 普通规则, reinclude: 以 ! 开头的反向规则(去掉 ! 后的正文) } */ function parsePatternLines(raw) { const include = []; const reinclude = []; if (!raw) return { include, reinclude }; for (const line of raw.split(/\r?\n/)) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) continue; if (trimmed.startsWith('!')) { const body = trimmed.slice(1).trim(); if (body) reinclude.push(body); } else { include.push(trimmed); } } return { include, reinclude }; } /** * 构造一个同步判定函数。 * * @param customFields 库配置里的同步过滤字段 * @param extraIgnore 额外的黑名单规则(例如某些调用点临时需要追加排除) */ function createSyncFilter(customFields = {}, extraIgnore = []) { const { include: userBlack, reinclude: blackReinclude } = parsePatternLines(customFields?.syncBlacklist); const { include: userWhite } = parsePatternLines(customFields?.syncWhitelist); const blacklistPatterns = [...exports.DEFAULT_IGNORE_PATTERNS, ...extraIgnore, ...userBlack]; const whitelistPatterns = [...userWhite, ...blackReinclude]; const blacklistRe = blacklistPatterns.map(globToRegExp); const whitelistRe = whitelistPatterns.map(globToRegExp); // 归一化相对路径:统一成正斜杠,去掉开头的 ./ const norm = (rel) => rel.replace(/\\/g, '/').replace(/^\.\//, ''); return function shouldSync(relPath) { if (!relPath) return true; // 库根目录本身 const rel = norm(relPath); const excluded = blacklistRe.some((re) => re.test(rel)); if (!excluded) return true; return whitelistRe.some((re) => re.test(rel)); }; } /** 单条 glob 匹配(暴露出来便于外部 / 测试使用) */ function matchGlob(relPath, pattern) { if (!pattern) return false; const rel = relPath.replace(/\\/g, '/').replace(/^\.\//, ''); return globToRegExp(pattern).test(rel); } /** * 返回用于 fast-glob / chokidar 的「黑名单 glob 数组」(默认 + 用户黑名单)。 * 注意:fast-glob 的 ignore 只能表达「排除」,无法表达白名单的「强制包含」。 * 因此调用方在拿到 glob 结果后,还需用 createSyncFilter() 返回的 shouldSync * 做一次白名单覆盖判定。 */ function getIgnoreGlobs(customFields = {}) { const { include: userBlack } = parsePatternLines(customFields?.syncBlacklist); return [...exports.DEFAULT_IGNORE_PATTERNS, ...userBlack]; } //# sourceMappingURL=SyncFilter.js.map