mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-10-30 17:31:54 +00:00
116 lines
4.4 KiB
JavaScript
116 lines
4.4 KiB
JavaScript
import { execFileSync } from 'node:child_process';
|
|
import { createHash } from 'node:crypto';
|
|
import { lstatSync, readFileSync, realpathSync } from 'node:fs';
|
|
import { basename, join } from 'node:path';
|
|
import { TorchError } from './errors.mjs';
|
|
|
|
function git(cwd, args, { optional = false } = {}) {
|
|
try {
|
|
return execFileSync('git', ['-C', cwd, ...args], {
|
|
encoding: 'utf8',
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
}).trim();
|
|
} catch (error) {
|
|
if (optional) return '';
|
|
throw new TorchError(`Git command failed: git ${args.join(' ')}`, {
|
|
code: 'GIT_COMMAND_FAILED',
|
|
details: error.stderr?.toString().trim() || undefined,
|
|
});
|
|
}
|
|
}
|
|
|
|
export function inspectRepository(cwd = process.cwd()) {
|
|
const rootText = git(cwd, ['rev-parse', '--show-toplevel'], { optional: true });
|
|
if (!rootText) {
|
|
throw new TorchError(`Not a Git repository: ${cwd}`, { code: 'NOT_GIT_REPOSITORY' });
|
|
}
|
|
|
|
const root = realpathSync(rootText);
|
|
const branch = git(root, ['branch', '--show-current'], { optional: true }) || null;
|
|
const head = git(root, ['rev-parse', 'HEAD'], { optional: true }) || null;
|
|
const initialCommit = head
|
|
? git(root, ['rev-list', '--max-parents=0', 'HEAD'], { optional: true }).split('\n')[0] || null
|
|
: null;
|
|
const status = git(root, ['status', '--porcelain=v1', '--untracked-files=all'], { optional: true });
|
|
const remotesText = git(root, ['remote', '-v'], { optional: true });
|
|
const remotes = remotesText
|
|
? remotesText.split('\n').map((line) => {
|
|
const [name, url, kindText = ''] = line.split(/\s+/);
|
|
return { name, url, kind: kindText.replace(/[()]/g, '') || 'unknown' };
|
|
})
|
|
: [];
|
|
|
|
return {
|
|
root,
|
|
name: basename(root),
|
|
branch,
|
|
head,
|
|
initialCommit,
|
|
dirtyEntries: status ? status.split('\n') : [],
|
|
remotes,
|
|
canonicalRemote: remotes.find((remote) => remote.name === 'origin' && remote.kind === 'fetch') ?? null,
|
|
};
|
|
}
|
|
|
|
export function trackedFiles(root) {
|
|
const output = git(root, ['ls-files', '-z'], { optional: true });
|
|
return output ? output.split('\0').filter(Boolean) : [];
|
|
}
|
|
|
|
const SOURCE_CONTENT = /\.(?:[cm]?[jt]sx?|py|rs|go|java|c|cc|cpp|h|hpp|cs|rb|php|swift|kt)$/i;
|
|
const MAX_ANALYZED_SOURCE_BYTES = 256_000;
|
|
const TEST_SURFACE = /(^|\/)(test|tests|spec|e2e)(\/|\.|$)/i;
|
|
const OPERATIONS_SURFACE = /(^|\/)(\.github|infra|ops|deploy|release|docker|systemd)(\/|\.|$)/i;
|
|
const SHARED_SURFACE = /(^|\/)(shared|common|types?|schemas?|contracts?|[^/]*config[^/]*)(\/|\.|$)/i;
|
|
const MANIFEST_PATHS = new Set([
|
|
'package.json', 'pyproject.toml', 'requirements.txt', 'Cargo.toml', 'go.mod',
|
|
'pom.xml', 'build.gradle', 'Makefile', 'CMakeLists.txt', 'Dockerfile',
|
|
]);
|
|
|
|
export function workingTreeFiles(root) {
|
|
const tracked = trackedFiles(root);
|
|
const untrackedOutput = git(root, ['ls-files', '--others', '--exclude-standard', '-z'], { optional: true });
|
|
const untracked = untrackedOutput ? untrackedOutput.split('\0').filter(Boolean) : [];
|
|
const trackedSet = new Set(tracked);
|
|
const files = [...new Set([...tracked, ...untracked])].filter((path) => {
|
|
try { return lstatSync(join(root, path)).isFile(); } catch { return false; }
|
|
}).sort();
|
|
const visible = new Set(files);
|
|
return {
|
|
files,
|
|
trackedFileCount: tracked.filter((path) => visible.has(path)).length,
|
|
untrackedFileCount: untracked.filter((path) => visible.has(path) && !trackedSet.has(path)).length,
|
|
};
|
|
}
|
|
|
|
export function workingTreeFingerprint(root, files = workingTreeFiles(root).files) {
|
|
const hash = createHash('sha256');
|
|
const analysisInputs = files.filter((path) => SOURCE_CONTENT.test(path)
|
|
|| TEST_SURFACE.test(path) || OPERATIONS_SURFACE.test(path)
|
|
|| SHARED_SURFACE.test(path) || MANIFEST_PATHS.has(path));
|
|
for (const path of [...analysisInputs].sort()) {
|
|
hash.update(path);
|
|
hash.update('\0');
|
|
if (!SOURCE_CONTENT.test(path) && basename(path) !== 'package.json') continue;
|
|
try {
|
|
const absolute = join(root, path);
|
|
const info = lstatSync(absolute);
|
|
if (!info.isFile()) {
|
|
hash.update('not-a-regular-file\0');
|
|
continue;
|
|
}
|
|
hash.update(String(info.size));
|
|
hash.update('\0');
|
|
if (SOURCE_CONTENT.test(path) && info.size > MAX_ANALYZED_SOURCE_BYTES) {
|
|
hash.update('source-content-not-inspected\0');
|
|
} else {
|
|
hash.update(readFileSync(absolute));
|
|
hash.update('\0');
|
|
}
|
|
} catch {
|
|
hash.update('unreadable\0');
|
|
}
|
|
}
|
|
return hash.digest('hex');
|
|
}
|