eff30bd741
transform.js:MARKITDOWN_EXT(docx/pptx/pdf)走 markitdown CLI 子行程轉出 md,輸出路徑 副檔名換 .md;回傳 originalCopy 供 index.js 把原檔複製進 assets/originals/<相對路徑>。 git-sync.js:ensureGitAttributes() 啟動時冪等寫入 LFS 宣告(三種格式 filter=lfs)。 已驗(雲端 sandbox,兩輪煙測):用 python-docx 生真實 docx(含標題+段落)丟進 watch 資料夾, collector 呼叫 markitdown 轉出真實 md 內容(人工核對與原檔一致)、原檔複製進 assets/originals/、.gitattributes 正確含三種格式 LFS 宣告、commit+push 送達 bare remote。 誠實邊界:sandbox 無 git-lfs 二進位,驗不到真實 LFS smudge/clean filter 生效(只驗到 .gitattributes 內容正確);真實 Gitea remote push/憑證、真實客戶檔案樣本仍待本機/客戶環境。 其餘格式(xlsx/圖片)不在第一波承諾範圍,仍誠實丟 NotImplementedError。
91 lines
2.9 KiB
JavaScript
91 lines
2.9 KiB
JavaScript
const fs = require('fs');
|
||
const path = require('path');
|
||
const chokidar = require('chokidar');
|
||
|
||
const { loadConfig } = require('./config');
|
||
const { transformFile, NotImplementedError } = require('./transform');
|
||
const { GitSync } = require('./git-sync');
|
||
|
||
function ensureDir(p) {
|
||
fs.mkdirSync(p, { recursive: true });
|
||
}
|
||
|
||
function outputPathFor(config, relOutputPath) {
|
||
return path.join(config.targetRepoDir, config.targetSubdir, relOutputPath);
|
||
}
|
||
|
||
function handleAddOrChange(config, srcPath) {
|
||
try {
|
||
const { relOutputPath, content, originalCopy } = transformFile(srcPath, config.watchDir);
|
||
const outPath = outputPathFor(config, relOutputPath);
|
||
ensureDir(path.dirname(outPath));
|
||
fs.writeFileSync(outPath, content, 'utf8');
|
||
console.log(`[collector] 寫入 ${relOutputPath}`);
|
||
|
||
if (originalCopy) {
|
||
const origOutPath = outputPathFor(config, originalCopy.relPath);
|
||
ensureDir(path.dirname(origOutPath));
|
||
fs.copyFileSync(srcPath, origOutPath);
|
||
console.log(`[collector] 原檔複製 ${originalCopy.relPath}(LFS 追蹤需部署機器已裝 git-lfs)`);
|
||
}
|
||
} catch (e) {
|
||
if (e instanceof NotImplementedError) {
|
||
console.warn(`[collector] 跳過(待 T4):${e.message}`);
|
||
} else {
|
||
console.error(`[collector] 轉檔失敗 ${srcPath}:${e.message}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
function handleUnlink(config, srcPath) {
|
||
const relSrc = path.relative(config.watchDir, srcPath);
|
||
const outPath = outputPathFor(config, relSrc);
|
||
if (fs.existsSync(outPath)) {
|
||
fs.unlinkSync(outPath);
|
||
console.log(`[collector] 移除 ${relSrc}(來源已刪除,deprecated 標記交給 ingest workflow)`);
|
||
}
|
||
}
|
||
|
||
function startCollector(config, { git = new GitSync(config) } = {}) {
|
||
ensureDir(path.join(config.targetRepoDir, config.targetSubdir));
|
||
|
||
let pendingCount = 0;
|
||
let debounceTimer = null;
|
||
|
||
const flush = () => {
|
||
if (pendingCount === 0) return;
|
||
const n = pendingCount;
|
||
pendingCount = 0;
|
||
const result = git.commitAndPush(`collector: 同步 ${n} 項變更`);
|
||
if (result.committed) {
|
||
console.log(`[collector] 已 commit+push(${n} 項變更)`);
|
||
}
|
||
};
|
||
|
||
const schedule = () => {
|
||
pendingCount += 1;
|
||
if (debounceTimer) clearTimeout(debounceTimer);
|
||
debounceTimer = setTimeout(flush, config.debounceMs);
|
||
};
|
||
|
||
const watcher = chokidar.watch(config.watchDir, {
|
||
ignoreInitial: false, // 首次啟動=首灌,掃過整個資料夾
|
||
persistent: true,
|
||
});
|
||
|
||
watcher
|
||
.on('add', (p) => { handleAddOrChange(config, p); schedule(); })
|
||
.on('change', (p) => { handleAddOrChange(config, p); schedule(); })
|
||
.on('unlink', (p) => { handleUnlink(config, p); schedule(); });
|
||
|
||
console.log(`[collector] watch 中:${config.watchDir} → ${config.targetRepoDir}/${config.targetSubdir}`);
|
||
return watcher;
|
||
}
|
||
|
||
if (require.main === module) {
|
||
const config = loadConfig();
|
||
startCollector(config);
|
||
}
|
||
|
||
module.exports = { startCollector };
|