import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { execFileSync } from 'node:child_process'; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const native = path.join(root, 'freecad-source'); const gitDir = fs.realpathSync(path.join(native, '.git')); const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'freecad-snapshot-index-')); const env = { ...process.env, GIT_INDEX_FILE: path.join(temp, 'index') }; const git = (args, cwd = native, input) => execFileSync('git', args, { cwd, env, input, encoding: 'utf8', maxBuffer: 32 * 1024 * 1024, }).trim(); const parent = git(['rev-parse', 'HEAD']); const previousSnapshot = process.env.FREECAD_SNAPSHOT_PARENT; const commitParent = previousSnapshot ? git(['rev-parse', '--verify', `${previousSnapshot}^{commit}`]) : parent; const branch = `workspace-snapshot-${new Date().toISOString().replace(/[:.]/g, '-')}`; const excluded = new Set(['node_modules', '.git', '__pycache__', 'dist', 'offline-package', 'continuation-runs', 'periodic-optimization', 'scheduler-bin']); const entries = []; function addFiles(relative, predicate = () => true) { const absolute = path.join(root, relative); if (!fs.existsSync(absolute)) return; const stat = fs.lstatSync(absolute); if (stat.isSymbolicLink()) return; if (stat.isDirectory()) { for (const name of fs.readdirSync(absolute)) { if (excluded.has(name) || name.startsWith('.tmp')) continue; addFiles(path.join(relative, name), predicate); } } else if (predicate(relative)) entries.push(relative); } addFiles('web/src'); addFiles('web/tools'); addFiles('web/public/api'); addFiles('web/public/samples'); for (const name of ['ifcopenshell-notice.txt', 'ifcopenshell-COPYING.txt', 'ifcopenshell-COPYING.LESSER.txt']) addFiles(`web/public/${name}`); for (const name of fs.readdirSync(path.join(root, 'web'))) { if (/\.(html|json|ts)$/.test(name)) addFiles(`web/${name}`); } addFiles('tools', name => /\.(py|mjs|js|sh|json)$/.test(name)); for (const folder of ['docs/business-acceptance', 'docs/p6', 'docs/current/user-tests-20260915']) { addFiles(folder, name => /\.(md|json|png|html|csv|tsv|FCStd|stp|step|stl|brep|iges)$/i.test(name) && !/(?:events|environment|checkpoint|manual-snapshot|chrome)\.(?:json|log)$/.test(name)); } for (const folder of ['docs', 'docs/current']) { for (const name of fs.readdirSync(path.join(root, folder))) { if (/\.(md|json)$/.test(name) && !/(?:scheduler|context|packet|continuation|failure-reconciliation|module-implementation|business-placeholder)/i.test(name)) if (fs.lstatSync(path.join(root, folder, name)).isFile()) entries.push(`${folder}/${name}`); } } for (const name of ['package.json', 'package-lock.json', 'WORKSPACE-SNAPSHOT.md']) addFiles(name); // Block actual credential-shaped assignments; diagnostics never print values. const credentialPatterns = [ /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/, /https?:\/\/[^\s/:]+:[^\s/@]+@/, /\bsk-[A-Za-z0-9_-]{25,}/, /(?:password|api_key|apikey|access_token)\s*["']?\s*[:=]\s*["'][A-Za-z0-9_+\/-]{12,}["']/i, ]; const suspicious = []; function scan(file, label) { if (!fs.lstatSync(file).isFile()) return; if (/\.(png|FCStd|stp|step|stl|brep|iges)$/i.test(file)) return; const text = fs.readFileSync(file, 'utf8').replaceAll('https://user:secret@example.test', 'https://example.test'); if (credentialPatterns.some(pattern => pattern.test(text))) suspicious.push(label); } for (const relative of new Set(entries)) scan(path.join(root, relative), relative); const nativeChanged = git(['ls-files', '-m', '-o', '--exclude-standard', 'src', 'cMake', 'tests']).split('\n') .filter(name => name && !/\.(qm|pyc|o|a|so|wasm)$/.test(name)); for (const relative of nativeChanged) scan(path.join(native, relative), `freecad-source/${relative}`); if (suspicious.length) { console.log(JSON.stringify({ status: 'credential_review_required', paths: suspicious })); process.exit(2); } git(['read-tree', parent]); for (let index = 0; index < nativeChanged.length; index += 150) git(['add', '--', ...nativeChanged.slice(index, index + 150)]); const sourceTree = git(['write-tree']); git(['read-tree', '--empty']); git(['read-tree', '--prefix=freecad-source/', sourceTree]); const unique = [...new Set(entries)].sort(); for (let index = 0; index < unique.length; index += 150) git([`--git-dir=${gitDir}`, `--work-tree=${root}`, 'add', '-f', '--', ...unique.slice(index, index + 150)], root); const tree = git(['write-tree']); const commit = git(['commit-tree', tree, '-p', commitParent], native, 'Save verified native IFC import and user validation evidence\n\nReal IFC2X3/IFC4 import tested. Full BIM authoring/export remains out of scope; 349-second building import performance remains to optimize.\n'); git(['update-ref', `refs/heads/${branch}`, commit, '0000000000000000000000000000000000000000']); const receipt = { status: 'committed_locally', branch, commit, parent: commitParent, sourceBase: parent, sourceTree, externalFiles: unique.length, nativeChangedFiles: nativeChanged.length, remote: 'https://gitea.meswork.com/wangdequan/FreeCADWasmDev.git', omissions: ['WASM/build artifacts', 'dependency sources', 'node_modules', 'agent/session logs', 'credentials', 'RAM-backed IfcOpenShell source'], primaryIndexPreserved: true, mainPreserved: true }; fs.writeFileSync(path.join(temp, 'receipt.json'), JSON.stringify(receipt, null, 2)+'\n'); console.log(JSON.stringify({ ...receipt, receipt: path.join(temp, 'receipt.json') }));