Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | 81x 81x 81x 81x 439x 439x 439x 407x 32x 32x 32x 32x 32x 31x 31x 30x 30x 30x 19x 32x 62x | /**
* File Scanner Utilities
*
* Shared utilities for scanning specific files and extracting DSNs.
* Used by env-file detection for scanning .env file variants.
*/
import { open } from "node:fs/promises";
import { join } from "node:path";
import { handleFileError, isRegularFile } from "./fs-utils.js";
import type { DetectedDsn } from "./types.js";
/** Result of processing a single file for DSN extraction. */
export type FileProcessResult = {
/** Extracted DSN string (null if not found) */
dsn: string | null;
/** Additional metadata to attach to the DetectedDsn */
metadata?: {
packagePath?: string;
};
};
/** Result of scanning specific files, including mtimes for caching. */
export type SpecificFileScanResult = {
/** Detected DSNs */
dsns: DetectedDsn[];
/** Map of source file paths to their mtimes (only files containing DSNs) */
sourceMtimes: Record<string, number>;
};
/**
* Function that processes file content and extracts DSN.
*
* @param relativePath - Path relative to scan root
* @param content - File content
* @returns Extraction result or null to skip this file
*/
export type FileProcessor = (
relativePath: string,
content: string
) => FileProcessResult | null;
/**
* Scan specific files (not glob) and extract DSNs.
*
* Used when scanning a known list of files (e.g., .env variants).
*
* @param cwd - Root directory
* @param filenames - List of filenames to check (relative to cwd)
* @param options - Processing options
* @returns Object with detected DSNs and source file mtimes
*/
export async function scanSpecificFiles(
cwd: string,
filenames: string[],
options: {
stopOnFirst?: boolean;
processFile: FileProcessor;
createDsn: (
raw: string,
relativePath: string,
metadata?: { packagePath?: string }
) => DetectedDsn | null;
}
): Promise<SpecificFileScanResult> {
const { stopOnFirst = false, processFile, createDsn } = options;
const dsns: DetectedDsn[] = [];
const sourceMtimes: Record<string, number> = {};
for (const filename of filenames) {
const filepath = join(cwd, filename);
try {
// Guard: skip non-regular files (FIFOs, sockets, etc.) that would block.
// 1Password streams secrets via symlinked named pipes; open() on a FIFO
// blocks indefinitely waiting for a writer.
if (!(await isRegularFile(filepath, "scanSpecificFiles.stat"))) {
continue;
}
// Use a single file handle for atomic read + stat to avoid TOCTOU:
// reading content and mtime from the same open handle ensures they
// correspond to the same file version.
const fh = await open(filepath, "r");
try {
const [content, stats] = await Promise.all([
fh.readFile("utf-8"),
fh.stat(),
]);
const result = processFile(filename, content);
if (result?.dsn) {
const detected = createDsn(result.dsn, filename, result.metadata);
if (detected) {
dsns.push(detected);
// Record mtime for cache invalidation (from same handle as content).
// Floor to integer — all mtime comparisons in dsn-cache.ts use Math.floor.
sourceMtimes[filename] = Math.floor(stats.mtimeMs);
if (stopOnFirst) {
return { dsns, sourceMtimes };
}
}
}
} finally {
await fh.close();
}
} catch (error) {
handleFileError(error, {
operation: "scanSpecificFiles",
path: filepath,
});
}
}
return { dsns, sourceMtimes };
}
|