trufflehog-js
Version:
TypeScript wrapper for TruffleHog secret scanner
211 lines (182 loc) • 5.88 kB
text/typescript
/**
* Copyright (c) 2025 maloma7. All rights reserved.
* SPDX-License-Identifier: MIT
*/
import type { BinaryCacheInfo, Logger, PlatformInfo } from "./types.ts";
import { TRUFFLEHOG_VERSION } from "./types.ts";
import { CacheError } from "./errors.ts";
import { defaultLogger } from "./logger.ts";
import { getPlatformKey } from "./platform.ts";
export class BinaryCache {
private readonly cacheDir: string;
private readonly logger: Logger;
constructor(cacheDir?: string, logger: Logger = defaultLogger) {
this.cacheDir = cacheDir ?? this.getDefaultCacheDir();
this.logger = logger;
}
private getDefaultCacheDir(): string {
const homeDir = Bun.env.HOME ?? Bun.env.USERPROFILE ?? "/tmp";
return `${homeDir}/.cache/trufflehog-js`;
}
async getBinaryPath(platformInfo: PlatformInfo): Promise<string | null> {
try {
const cacheInfo = await this.getCacheInfo(platformInfo);
if (!cacheInfo) {
return null;
}
const exists = await this.fileExists(cacheInfo.path);
if (!exists) {
this.logger.debug(`Cached binary not found at ${cacheInfo.path}`);
await this.removeCacheInfo(platformInfo);
return null;
}
if (!cacheInfo.verified) {
this.logger.debug("Cached binary not verified, re-downloading");
await this.removeBinary(platformInfo);
return null;
}
this.logger.debug(`Using cached binary: ${cacheInfo.path}`);
return cacheInfo.path;
} catch (_error) {
this.logger.warn("Error accessing cache, re-downloading binary");
return null;
}
}
async setCachedBinary(
platformInfo: PlatformInfo,
binaryPath: string,
): Promise<void> {
try {
const cacheInfo: BinaryCacheInfo = {
version: TRUFFLEHOG_VERSION,
platform: platformInfo.platform,
arch: platformInfo.arch,
path: binaryPath,
checksum: "", // Will be calculated if needed
downloadedAt: new Date(),
verified: true,
};
await this.saveCacheInfo(platformInfo, cacheInfo);
this.logger.debug(`Cached binary info saved for ${getPlatformKey()}`);
} catch (error) {
throw new CacheError("saving cache info", error as Error);
}
}
async removeBinary(platformInfo: PlatformInfo): Promise<void> {
try {
const cacheInfo = await this.getCacheInfo(platformInfo);
if (cacheInfo) {
await this.deleteFile(cacheInfo.path);
await this.removeCacheInfo(platformInfo);
this.logger.debug(`Removed cached binary for ${getPlatformKey()}`);
}
} catch (error) {
throw new CacheError("removing cached binary", error as Error);
}
}
async clearCache(): Promise<void> {
try {
await Bun.$`rm -rf ${this.cacheDir}`;
this.logger.debug("Cache cleared");
} catch (error) {
// Ignore errors when clearing cache (e.g., directory doesn't exist)
this.logger.debug(`Cache clear failed: ${error}`);
}
}
getCachedBinaryPath(platformInfo: PlatformInfo): string {
const platformKey = `${platformInfo.platform}-${platformInfo.arch}`;
const binaryName =
platformInfo.platform === "win32" ? "trufflehog.exe" : "trufflehog";
return `${this.cacheDir}/${TRUFFLEHOG_VERSION}/${platformKey}/${binaryName}`;
}
private async getCacheInfo(
platformInfo: PlatformInfo,
): Promise<BinaryCacheInfo | null> {
try {
const cacheFile = this.getCacheInfoPath(platformInfo);
const exists = await this.fileExists(cacheFile);
if (!exists) {
return null;
}
const content = await Bun.file(cacheFile).text();
const cacheInfo = JSON.parse(content) as BinaryCacheInfo;
// Verify cache is for current version
if (cacheInfo.version !== TRUFFLEHOG_VERSION) {
this.logger.debug(
`Cache version mismatch: ${cacheInfo.version} !== ${TRUFFLEHOG_VERSION}`,
);
await this.removeCacheInfo(platformInfo);
return null;
}
return cacheInfo;
} catch (error) {
this.logger.debug(
`Error reading cache info: ${(error as Error).message}`,
);
return null;
}
}
private async saveCacheInfo(
platformInfo: PlatformInfo,
cacheInfo: BinaryCacheInfo,
): Promise<void> {
const cacheFile = this.getCacheInfoPath(platformInfo);
const cacheDir = createPath(cacheFile).dir();
await Bun.$`mkdir -p ${cacheDir}`;
const content = JSON.stringify(cacheInfo, null, 2);
await Bun.write(cacheFile, content);
}
private async removeCacheInfo(platformInfo: PlatformInfo): Promise<void> {
const cacheFile = this.getCacheInfoPath(platformInfo);
await this.deleteFile(cacheFile);
}
private getCacheInfoPath(platformInfo: PlatformInfo): string {
const platformKey = `${platformInfo.platform}-${platformInfo.arch}`;
return `${this.cacheDir}/${TRUFFLEHOG_VERSION}/${platformKey}/cache-info.json`;
}
private async fileExists(path: string): Promise<boolean> {
try {
const file = Bun.file(path);
return await file.exists();
} catch {
return false;
}
}
private async deleteFile(path: string): Promise<void> {
try {
await Bun.$`rm -f ${path}`;
} catch {
// Ignore errors when deleting files
}
}
}
export function createPath(filePath: string) {
return {
dir(): string {
const lastSlash = filePath.lastIndexOf("/");
return lastSlash === -1 ? "." : filePath.substring(0, lastSlash);
},
basename(): string {
const lastSlash = filePath.lastIndexOf("/");
return lastSlash === -1 ? filePath : filePath.substring(lastSlash + 1);
},
join(...paths: string[]): string {
return [filePath, ...paths].join("/");
},
};
}
export async function getBinaryFromCache(
platformInfo: PlatformInfo,
logger?: Logger,
): Promise<string | null> {
const cache = new BinaryCache(undefined, logger);
return await cache.getBinaryPath(platformInfo);
}
export async function setCachedBinary(
platformInfo: PlatformInfo,
binaryPath: string,
logger?: Logger,
): Promise<void> {
const cache = new BinaryCache(undefined, logger);
await cache.setCachedBinary(platformInfo, binaryPath);
}