feat(smartfs.directory): Add directory treeHash: deterministic content-based hashing of directory trees with streaming and algorithm option
This commit is contained in:
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartfs',
|
||||
version: '1.1.3',
|
||||
version: '1.2.0',
|
||||
description: 'a cross platform extendable fs module'
|
||||
}
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
* Directory builder for fluent directory operations
|
||||
*/
|
||||
|
||||
import * as crypto from 'crypto';
|
||||
import type { ISmartFsProvider } from '../interfaces/mod.provider.js';
|
||||
import type {
|
||||
TFileMode,
|
||||
IFileStats,
|
||||
IDirectoryEntry,
|
||||
IListOptions,
|
||||
ITreeHashOptions,
|
||||
} from '../interfaces/mod.types.js';
|
||||
|
||||
/**
|
||||
@@ -136,4 +138,55 @@ export class SmartFsDirectory {
|
||||
public getPath(): string {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a hash of all files in the directory tree
|
||||
* Uses configured filter and recursive options
|
||||
* @param options - Hash options (algorithm defaults to 'sha256')
|
||||
* @returns Hex-encoded hash string
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Hash all files recursively
|
||||
* const hash = await fs.directory('/assets').recursive().treeHash();
|
||||
*
|
||||
* // Hash only TypeScript files
|
||||
* const hash = await fs.directory('/src').filter('*.ts').recursive().treeHash();
|
||||
*
|
||||
* // Use different algorithm
|
||||
* const hash = await fs.directory('/data').recursive().treeHash({ algorithm: 'sha512' });
|
||||
* ```
|
||||
*/
|
||||
public async treeHash(options?: ITreeHashOptions): Promise<string> {
|
||||
const { algorithm = 'sha256' } = options ?? {};
|
||||
const hash = crypto.createHash(algorithm);
|
||||
|
||||
// Get all entries using existing filter/recursive configuration
|
||||
const entries = await this.list();
|
||||
|
||||
// Filter to files only and sort by path for deterministic ordering
|
||||
const files = entries
|
||||
.filter((entry) => entry.isFile)
|
||||
.sort((a, b) => a.path.localeCompare(b.path));
|
||||
|
||||
// Hash each file's relative path and contents
|
||||
for (const file of files) {
|
||||
// Compute relative path from directory root
|
||||
const relativePath = file.path.slice(this.path.length + 1);
|
||||
|
||||
// Hash the relative path (with null separator)
|
||||
hash.update(relativePath + '\0');
|
||||
|
||||
// Stream file contents and update hash incrementally
|
||||
const stream = await this.provider.createReadStream(file.path);
|
||||
const reader = stream.getReader();
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
hash.update(value);
|
||||
}
|
||||
}
|
||||
|
||||
return hash.digest('hex');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,3 +215,13 @@ export interface IWatchOptions {
|
||||
filter?: string | RegExp | ((path: string) => boolean);
|
||||
debounce?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tree hash options interface
|
||||
*/
|
||||
export interface ITreeHashOptions {
|
||||
/**
|
||||
* Hash algorithm to use (default: 'sha256')
|
||||
*/
|
||||
algorithm?: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user