UNPKG

@cspell/cspell-tools

Version:
150 lines 6.21 kB
import { readFile, writeFile } from 'node:fs/promises'; import { resolve, sep as pathSep } from 'node:path'; import { toError } from '../util/errors.js'; import { isDefined } from '../util/index.js'; import { calcFileChecksum, checkFile } from './checksum.js'; export async function shasumFile(filename, root) { try { const file = resolve(root || '.', filename); const checksum = await calcFileChecksum(file); return `${checksum} ${filename}`; } catch { // const err = toError(error); // Reject with a string. // eslint-disable-next-line unicorn/no-useless-promise-resolve-reject return Promise.reject(`shasum: ${filename}: Unable to read file.`); } } /** * * @param filename - name of checksum file * @param files - optional list of files to check * @param root - optional root, default cwd. */ export async function checkShasumFile(filename, files, root) { files = !files ? files : files.length ? files : undefined; const shaFiles = await readAndParseShasumFile(filename); const filesToCheck = !files ? shaFiles.map(({ filename }) => filename) : files; const mapNameToChecksum = new Map(shaFiles.map((r) => [normalizeFilename(r.filename), r.checksum])); const resolvedRoot = resolve(root || '.'); const results = await Promise.all(filesToCheck.map(normalizeFilename).map((filename) => { return tryToCheckFile(filename, resolvedRoot, mapNameToChecksum.get(filename)); })); const passed = !results.some((v) => !v.passed); return { passed, results }; } async function tryToCheckFile(filename, root, checksum) { if (!checksum) { return { filename, passed: false, error: new Error('Missing Checksum.') }; } const file = resolve(root, filename); try { const passed = await checkFile(checksum, file); return { filename, passed }; } catch { return { filename, passed: false, error: new Error('Failed to read file.') }; } } const regLine = /([a-f0-9]{40,}) {2}(.*)/; export async function readAndParseShasumFile(filename) { const content = await readFile(resolve(filename), 'utf8'); const shaFiles = parseShasumFile(content); return shaFiles; } export function parseShasumFile(content) { const lines = content.split(/\r?\n|\r/g); return lines.map(parseLine).filter(isDefined); function parseLine(line, index) { const m = line.match(regLine); const lineNumber = index + 1; if (!m) { if (line.trim()) { throw new Error(`Failed to parse line ${lineNumber} of checksum file.`); } return undefined; } const checksum = m[1]; const filename = m[2]; return { checksum, filename, lineNumber }; } } export async function reportChecksumForFiles(files, options) { const root = options.root; const filesToCheck = await resolveFileList(files, options.listFile); let numFailed = 0; const result = await Promise.all(filesToCheck.map((file) => shasumFile(file, root).catch((e) => { ++numFailed; if (typeof e !== 'string') throw e; return e; }))); const report = result.join('\n'); const passed = !numFailed; return { report, passed }; } export async function reportCheckChecksumFile(filename, files, options) { const root = options.root; const filesToCheck = await resolveFileList(files, options.listFile); const checkResult = await checkShasumFile(filename, filesToCheck, root); const results = checkResult.results; const lines = results.map(({ filename, passed, error }) => `${filename}: ${passed ? 'OK' : 'FAILED'} ${error ? '- ' + error.message : ''}`.trim()); const withErrors = results.filter((a) => !a.passed); const passed = !withErrors.length; if (!passed) { lines.push(`shasum: WARNING: ${withErrors.length} computed checksum${withErrors.length > 1 ? 's' : ''} did NOT match`); } return { report: lines.join('\n'), passed }; } async function resolveFileList(files, listFile) { files = files || []; listFile = listFile || []; const setOfFiles = new Set(files); const pending = listFile.map((filename) => readFile(filename, 'utf8')); for await (const content of pending) { content .split('\n') .map((a) => a.trim()) .filter((a) => a) .forEach((file) => setOfFiles.add(file)); } return [...setOfFiles].map(normalizeFilename); } export async function calcUpdateChecksumForFiles(filename, files, options) { const root = options.root || '.'; const filesToCheck = await resolveFileList(files, options.listFile); const currentEntries = (await readAndParseShasumFile(filename).catch((err) => { const e = toError(err); if (e.code !== 'ENOENT') throw e; return []; })).map((entry) => ({ ...entry, filename: normalizeFilename(entry.filename) })); const entriesToUpdate = new Set([...filesToCheck, ...currentEntries.map((e) => e.filename)]); const mustExist = new Set(filesToCheck); const checksumMap = new Map(currentEntries.map(({ filename, checksum }) => [filename, checksum])); for (const file of entriesToUpdate) { try { const checksum = await calcFileChecksum(resolve(root, file)); checksumMap.set(file, checksum); } catch (e) { if (mustExist.has(file) || toError(e).code !== 'ENOENT') throw e; checksumMap.delete(file); } } const updatedEntries = [...checksumMap] .map(([filename, checksum]) => ({ filename, checksum })) .sort((a, b) => (a.filename < b.filename ? -1 : 1)); return updatedEntries.map((e) => `${e.checksum} ${e.filename}`).join('\n') + '\n'; } export async function updateChecksumForFiles(filename, files, options) { const content = await calcUpdateChecksumForFiles(filename, files, options); await writeFile(filename, content); return { passed: true, report: content }; } function normalizeFilename(filename) { return filename.split(pathSep).join('/'); } //# sourceMappingURL=shasum.js.map