From b49f8c9f1190daa51f18c5e0d198b34764ea3068 Mon Sep 17 00:00:00 2001 From: luk Date: Wed, 7 Oct 2026 09:58:09 +0800 Subject: [PATCH] =?UTF-8?q?seafile=5Fignore=5Ffind2merge=20=E7=A7=BB?= =?UTF-8?q?=E6=A4=8D=20Node=20=E8=B7=A8=E5=B9=B3=E5=8F=B0=EF=BC=8Cglobal?= =?UTF-8?q?=20=E5=A2=9E=E5=8A=A0=20Flutter=20=E4=BA=A7=E7=89=A9=E5=BF=BD?= =?UTF-8?q?=E7=95=A5=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 seafile_ignore_find2merge.js:忠实移植 .sh 语义(深度3树搜索、跳过 node_modules 等目录、seafile-ignore.txt/.local.txt 触发合并、dry-run 模式), 修正 .sh 三个问题:默认 global 是 URL 时 cat 失败(改用 fetch)、去掉误抄的 .git 检测、根目录自身参与合并(find -name '[^.]*' 匹配不到根) - .sh 同步修正后两项(.git 检测、根目录),保持与 .js 行为一致 - nixhome/seafile-ignore.global.txt 增加 Flutter/Dart 通用机器本地产物: .dart_tool/、.flutter-plugins-dependencies、Pods/、local.properties、 .gradle/、.kotlin/(build/ 因 vue 仓库跟踪 build/ 而仅在项目层 local 忽略) - 验证:/tmp 夹具树 .sh 与 .js 产物逐字节一致;productX 全树 dry-run 候选 87 条一致 Co-Authored-By: AtomCode (qwen3.8-27b) --- nixhome/seafile-ignore.global.txt | 26 ++++- seafile_ignore_find2merge.js | 168 ++++++++++++++++++++++++++++++ seafile_ignore_find2merge.sh | 7 +- 3 files changed, 197 insertions(+), 4 deletions(-) create mode 100644 seafile_ignore_find2merge.js diff --git a/nixhome/seafile-ignore.global.txt b/nixhome/seafile-ignore.global.txt index 3ac1096..aff8425 100644 --- a/nixhome/seafile-ignore.global.txt +++ b/nixhome/seafile-ignore.global.txt @@ -7,6 +7,7 @@ # seafile-ignore.txt 只能忽略还没有被同步的文件。对于已经被同步的文件,如果后来把它添加到 seafile-ignore.txt 中,系统只会忽略后续更改,已经上传的版本不会受影响。 ### seafile-ignore.global.txt ### +### global ignore settings in opx/sysconfig/nixhome/seafile-ignore.global.txt # 自定义的后缀名,凡有 sfignore 后缀的都不进行同步 *.sfignore @@ -83,5 +84,28 @@ OneDrive/Icon .playwright-mcp/ */.playwright-mcp/ -### seafile-ignore.local.txt ### +# Flutter/Dart 机器本地产物(各端由 `flutter pub get` / `pod install` / 构建在本机重生成,不跨机同步)。 +# 双机开发时 .dart_tool/package_config.json 内是绝对路径(file:///Users/<用户>/.pub-cache/...),同步过去会让另一台机器编译报 No such file or directory。 +# 注:pub 缓存在 ~/.pub-cache,位于资料库根之外,seafile 同步不到,无需在此列出。 +# 注:build/ 未全局忽略——tic-www-vue / cox-user-vue / cox-team-vue 等把 build/ 纳入 git 跟踪,全局忽略会波及;按需放各项目 seafile-ignore.local.txt。 +# ---- Dart / Flutter:包解析 + 构建产物(核心,换机必跑 `flutter pub get` 重生成)---- +.dart_tool/ +*/.dart_tool/ +.flutter-plugins-dependencies +*/.flutter-plugins-dependencies + +# ---- iOS / macOS:CocoaPods 检出目录(Podfile.lock 仍提交共享,只忽略 Pods/ 实体)---- +Pods/ +*/Pods/ + +# ---- Android:本机 SDK 路径 + 构建缓存(换机必跑 `flutter build` 重生成 local.properties)---- +local.properties +*/local.properties +.gradle/ +*/.gradle/ +.kotlin/ +*/.kotlin/ + +### seafile-ignore.local.txt ### +### Followings are project-specific ignore settings merged by opx/sysconfig/seafile_ignore_find2merge.js diff --git a/seafile_ignore_find2merge.js b/seafile_ignore_find2merge.js new file mode 100644 index 0000000..7e04fd2 --- /dev/null +++ b/seafile_ignore_find2merge.js @@ -0,0 +1,168 @@ +#!/usr/bin/env node +// Cross-platform (node) port of seafile_ignore_find2merge.sh / .bat +// +// Search [ROOTPATH], merge [GLOBALPATH/seafile-ignore.global.txt] and [ROOTPATH//seafile-ignore.local.txt] +// into [ROOTPATH//seafile-ignore.txt], for every that has a seafile-ignore.txt OR a seafile-ignore.local.txt. +// (Unlike git_ignore_find2merge, a .git dir is NOT a trigger — seafile ignore rules are independent of git.) +// +// usage: +// node seafile_ignore_find2merge.js [rootPath] [globalPath] [y|other] +// rootPath start of tree search (default: current dir) +// globalPath path to seafile-ignore.global.txt, or its parent dir, or an http(s) URL +// (default: https://git.tic.cc/opx/sysconfig/raw/branch/main/nixhome/seafile-ignore.global.txt) +// yesNo 'y' to write, anything else = dry-run (default: dry-run) +// +// node seafile_ignore_find2merge.js # dry-run from cwd +// node seafile_ignore_find2merge.js ~/Shared y +// node seafile_ignore_find2merge.js /srv/projects /srv/proj/seafile-ignore.global.txt y +// +// Semantics ported from the .sh (two .sh gaps fixed): +// - the root dir itself is processed too (the .sh's find -name '[^.]*' never matched the root dir) +// - a dir is updated iff it has seafile-ignore.txt OR seafile-ignore.local.txt (the .sh's `.git` check was dropped) +// - tree search is max depth 3, skipping hidden dirs and the known build/dep dirs +// - the .sh does `cat $GLOBALPATH` which fails when the default is a URL; here http(s) is fetched with fetch(), +// a local file / parent-dir keeps the original behavior. + +const fs = require('fs') +const os = require('os') +const path = require('path') +const readline = require('readline') + +const DEFAULT_GLOBAL_URL = 'https://git.tic.cc/opx/sysconfig/raw/branch/main/nixhome/seafile-ignore.global.txt' +// same skip list as: grep -E -v 'node_modules|uni_modules|\.deploy_git|\.git|.svn|\.vscode|\.wrangler|unpackage|_webroot|_logstore|_datasotre|_archive|_filestore|_ssl' +const SKIP_DIR_PATTERN = /node_modules|uni_modules|\.deploy_git|\.git|\.svn|\.vscode|\.wrangler|unpackage|_webroot|_logstore|_datasotre|_archive|_filestore|_ssl/ +const MAX_DEPTH = 3 // relative to ROOTPATH, matching find -mindepth 0 -maxdepth 3 + +function resolveHome (p) { + if (p === '~') return os.homedir() + if (p.startsWith('~/')) return path.join(os.homedir(), p.slice(2)) + return p +} + +async function ask (question) { + const rl = readline.createInterface({ input: process.stdin, output: process.stdout }) + const answer = await new Promise(resolve => rl.question(question, resolve)) + rl.close() + return answer.trim() +} + +// find . -mindepth 0 -maxdepth 3 -type d -name '[^.]*' (relative paths, root itself excluded) +function collectCandidateDirs (rootPath) { + const candidates = [] + function walk (dir, depth) { + if (depth > MAX_DEPTH) return + let entries + try { + entries = fs.readdirSync(dir, { withFileTypes: true }) + } catch { + return + } + for (const entry of entries) { + if (!entry.isDirectory()) continue + if (entry.name.startsWith('.')) continue // -name '[^.]*' excludes hidden dirs + const absDir = path.join(dir, entry.name) + const relDir = path.relative(rootPath, absDir) + if (relDir.split(path.sep).length > MAX_DEPTH) continue + candidates.push(relDir) + walk(absDir, depth + 1) + } + } + walk(rootPath, 1) + return candidates +} + +async function loadGlobalContent (globalPathInput) { + const globalPath = resolveHome(globalPathInput) + if (/^https?:\/\//i.test(globalPath)) { + console.log('√√√ GLOBALPATH (url) = [[' + globalPath + ']]') + const response = await fetch(globalPath) + if (!response.ok) { + console.log('××× Fetch failed: HTTP ' + response.status + ' for [[' + globalPath + ']]. Exit now...') + process.exit(1) + } + return response.text() + } + let globalFile = globalPath + if (fs.existsSync(globalPath) && fs.statSync(globalPath).isDirectory()) { + globalFile = path.join(globalPath, 'seafile-ignore.global.txt') + } + if (!fs.existsSync(globalFile)) { + console.log('××× Not found [[' + globalFile + ']]. Exit now...') + process.exit(1) + } + console.log('√√√ GLOBALPATH = [[' + globalFile + ']]') + return fs.readFileSync(globalFile, 'utf8') +} + +async function main () { + console.log('') + console.log('Search in [ROOTPATH], Merge [GLOBALPATH/seafile-ignore.global.txt] and [ROOTPATH/*/seafile-ignore.local.txt] files to [seafile-ignore.txt]') + console.log('') + + // --- ROOTPATH --- + let rootPath = process.argv[2] + if (!rootPath) { + console.log('::*** Enter [root path] or [leave blank] for default [[' + process.cwd() + ']] to start tree search for seafile-ignore.txt files') + rootPath = await ask('***:: ') + } + if (rootPath) rootPath = path.resolve(resolveHome(rootPath)) + else rootPath = process.cwd() + if (!fs.existsSync(rootPath) || !fs.statSync(rootPath).isDirectory()) { + console.log('××× [[' + rootPath + ']] not exist! Exit now. ***') + process.exit(1) + } + console.log('√√√ ROOTPATH = [[' + rootPath + ']]') + console.log('') + + // --- GLOBALPATH --- + let globalPathInput = process.argv[3] + if (!globalPathInput) { + console.log('::*** Enter [path to seafile-ignore.global.txt] or [leave blank] for default [[' + DEFAULT_GLOBAL_URL + ']]') + globalPathInput = await ask('***:: ') + } + const globalContent = await loadGlobalContent(globalPathInput) + console.log('') + + // --- y/N --- + let yesNo = process.argv[4] + if (!yesNo) { + console.log('::*** Enter [y] to start updating, or [anything else] for dry-run') + yesNo = await ask('***:: ') + } + const willWrite = yesNo === 'y' + + console.log('*** Starting from [[' + rootPath + ']] ***') + console.log('') + + // --- search + merge --- + // The root dir itself is also a candidate: the old .sh's find -name '[^.]*' never matched the root, + // so ROOTPATH's own seafile-ignore.txt was never updated — fixed here (and in the .sh). + const candidateDirs = collectCandidateDirs(rootPath).sort() + if ( + fs.existsSync(path.join(rootPath, 'seafile-ignore.txt')) || + fs.existsSync(path.join(rootPath, 'seafile-ignore.local.txt')) + ) candidateDirs.unshift('.') + + let updatedCount = 0 + for (const relDir of candidateDirs) { + const absDir = relDir === '.' ? rootPath : path.join(rootPath, relDir) + if (relDir !== '.' && SKIP_DIR_PATTERN.test(relDir)) continue + const ignoreFile = path.join(absDir, 'seafile-ignore.txt') + const localFile = path.join(absDir, 'seafile-ignore.local.txt') + if (!fs.existsSync(ignoreFile) && !fs.existsSync(localFile)) continue + console.log('---- updating [[' + relDir + '/seafile-ignore.txt]] ----') + if (willWrite) { + let merged = globalContent + if (fs.existsSync(localFile)) merged += fs.readFileSync(localFile, 'utf8') + fs.writeFileSync(ignoreFile, merged) + updatedCount++ + } + console.log('') + } + if (willWrite) console.log('*** done: ' + updatedCount + ' file(s) updated ***') +} + +main().catch(err => { + console.error(err) + process.exit(1) +}) diff --git a/seafile_ignore_find2merge.sh b/seafile_ignore_find2merge.sh index fbb8da8..d395a46 100755 --- a/seafile_ignore_find2merge.sh +++ b/seafile_ignore_find2merge.sh @@ -55,9 +55,10 @@ cd $ROOTPATH echo "*** Starting from [[`pwd`]] ***" echo "" -find . -mindepth 0 -maxdepth 3 -type d -name '[^.]*' | grep -E -v 'node_modules|uni_modules|\.deploy_git|\.git|.svn|\.vscode|\.wrangler|unpackage|_webroot|_logstore|_datasotre|_archive|_filestore|_ssl' | while read repo -do - if [ -f "$repo/seafile-ignore.txt" ] || [ -d "$repo/.git" ] +# 根目录自身也是候选(find -name '[^.]*' 匹配不到根目录 .,手动补上) +{ echo "."; find . -mindepth 0 -maxdepth 3 -type d -name '[^.]*'; } | grep -E -v 'node_modules|uni_modules|\.deploy_git|\.git|.svn|\.vscode|\.wrangler|unpackage|_webroot|_logstore|_datasotre|_archive|_filestore|_ssl' | while read repo +do + if [ -f "$repo/seafile-ignore.txt" ] || [ -f "$repo/seafile-ignore.local.txt" ] then echo "---- updating [[$repo/seafile-ignore.txt]] ----" if [ "$YESNO" = 'y' ]