Files
FreeCADWasmDev/tools/save_workspace_snapshot.mjs
wangdequan 9a7c4f0335 Save FreeCAD Web workspace sources and user validation evidence
IFC native binding remains incomplete; preserve accurate test results.
2026-09-15 22:01:58 +08:00

91 lines
5.1 KiB
JavaScript

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 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 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', parent], native,
'Save FreeCAD Web workspace sources and user validation evidence\n\nIFC native binding remains incomplete; preserve accurate test results.\n');
git(['update-ref', `refs/heads/${branch}`, commit, '0000000000000000000000000000000000000000']);
const receipt = { status: 'committed_locally', branch, commit, 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') }));