@neurolint/cli
Version:
NeuroLint CLI - Deterministic code fixing for TypeScript, JavaScript, React, and Next.js with 8-layer architecture including Security Forensics, Next.js 16, React Compiler, and Turbopack support
137 lines (116 loc) • 3.81 kB
JavaScript
/**
* Copyright (c) 2025 NeuroLint
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Layer 8: Security Forensics - Hash Utilities
*
* Provides cryptographic hashing utilities for file integrity verification.
*/
;
const crypto = require('crypto');
const fs = require('fs').promises;
const path = require('path');
class HashUtils {
static HASH_ALGORITHM = 'sha256';
static hashString(content) {
return crypto
.createHash(this.HASH_ALGORITHM)
.update(content, 'utf8')
.digest('hex');
}
static async hashFile(filePath) {
try {
const content = await fs.readFile(filePath);
return crypto
.createHash(this.HASH_ALGORITHM)
.update(content)
.digest('hex');
} catch (error) {
return null;
}
}
static async hashDirectory(dirPath, options = {}) {
const { include = ['**/*'], exclude = [], onProgress } = options;
const hashes = {};
let fileCount = 0;
let lastUpdate = Date.now();
async function walkDir(currentPath) {
try {
const entries = await fs.readdir(currentPath, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(currentPath, entry.name);
const relativePath = path.relative(dirPath, fullPath);
const shouldExclude = exclude.some(pattern => {
if (pattern.includes('*')) {
const regex = new RegExp(pattern.replace(/\*/g, '.*'));
return regex.test(relativePath);
}
return relativePath.includes(pattern);
});
if (shouldExclude) continue;
if (entry.isDirectory()) {
await walkDir(fullPath);
} else if (entry.isFile()) {
const hash = await HashUtils.hashFile(fullPath);
if (hash) {
hashes[relativePath] = hash;
fileCount++;
if (onProgress && Date.now() - lastUpdate > 500) {
onProgress({ filesScanned: fileCount, currentFile: relativePath });
lastUpdate = Date.now();
}
}
}
}
} catch (error) {
}
}
await walkDir(dirPath);
if (onProgress) {
onProgress({ filesScanned: fileCount, currentFile: null, done: true });
}
return hashes;
}
static compareHashes(baseline, current) {
const added = [];
const removed = [];
const modified = [];
const unchanged = [];
const allPaths = new Set([
...Object.keys(baseline),
...Object.keys(current)
]);
for (const filePath of allPaths) {
const baselineHash = baseline[filePath];
const currentHash = current[filePath];
if (!baselineHash && currentHash) {
added.push(filePath);
} else if (baselineHash && !currentHash) {
removed.push(filePath);
} else if (baselineHash !== currentHash) {
modified.push(filePath);
} else {
unchanged.push(filePath);
}
}
return {
added,
removed,
modified,
unchanged,
hasChanges: added.length > 0 || removed.length > 0 || modified.length > 0
};
}
}
module.exports = HashUtils;