Created
January 28, 2026 11:24
-
-
Save Swader/12f8438d0f2af06129c6029845e209c1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import ipfsOnlyHashModule from "ipfs-only-hash"; | |
| import { resolve } from "node:path"; | |
| type HashOptions = { | |
| cidVersion: 0 | 1; | |
| rawLeaves: boolean; | |
| }; | |
| type ParsedArgs = { | |
| targetPath?: string; | |
| desiredCid?: string; | |
| maxVariants?: number; | |
| reportEvery: number; | |
| useJson: boolean; | |
| onlyJson: boolean; | |
| useRaw: boolean; | |
| sortKeys: boolean; | |
| indents: string[]; | |
| newlines: Array<"\n" | "\r\n">; | |
| lineBreaks: boolean[]; | |
| trailingNewline: boolean[]; | |
| cidVersion?: 0 | 1; | |
| rawLeaves?: boolean; | |
| writePath?: string; | |
| printMatch?: boolean; | |
| showHelp: boolean; | |
| error?: string; | |
| }; | |
| type JsonFormatConfig = { | |
| indent: string; | |
| newline: "\n" | "\r\n"; | |
| lineBreaks: boolean; | |
| trailingNewline: boolean; | |
| colonBefore: string; | |
| colonAfter: string; | |
| commaAfter: string; | |
| sortKeys: boolean; | |
| }; | |
| type Candidate = { | |
| content: string; | |
| label: string; | |
| details: Record<string, string>; | |
| }; | |
| const { of: ipfsOnlyHash } = ipfsOnlyHashModule as { | |
| of: (content: Uint8Array, options?: HashOptions) => Promise<string>; | |
| }; | |
| const DEFAULT_INDENT_SIZES = [0, 1, 2, 3, 4, 8]; | |
| const DEFAULT_REPORT_EVERY = 500; | |
| const DEFAULT_NEWLINES: Array<"\n" | "\r\n"> = ["\n", "\r\n"]; | |
| const DEFAULT_LINE_BREAKS = [true, false]; | |
| const DEFAULT_TRAILING_NEWLINE = [true, false]; | |
| const DEFAULT_COLON_BEFORE = ["", " "]; | |
| const DEFAULT_COLON_AFTER = ["", " ", "\t"]; | |
| const DEFAULT_COMMA_AFTER = ["", " ", "\t"]; | |
| const USAGE = `Usage: | |
| bun run ./ipfs-format-hunt.ts <file> <cid> [options] | |
| Options: | |
| --max <n> Stop after N variants | |
| --report <n> Log every N attempts (default: ${DEFAULT_REPORT_EVERY}) | |
| --no-json Skip JSON formatting variants | |
| --only-json Only JSON formatting variants (error if not JSON) | |
| --no-raw Skip raw text variants | |
| --sort-keys Sort object keys when formatting JSON | |
| --indents <list> Comma list of indent sizes (e.g. 0,1,2,4,8,tab) | |
| --newline <list> Comma list of newline styles: lf,crlf | |
| --pretty-only Only pretty JSON (line breaks on) | |
| --minified-only Only minified JSON (line breaks off) | |
| --cidv1 Use CIDv1 | |
| --raw-leaves Use raw leaves (forces CIDv1) | |
| --write <path> Write matched content to file | |
| --no-print Skip printing matched content to stdout | |
| --help Show this help text | |
| `; | |
| function parseIndentList(value: string): string[] { | |
| const tokens = value | |
| .split(",") | |
| .map((token) => token.trim()) | |
| .filter(Boolean); | |
| const indents: string[] = []; | |
| for (const token of tokens) { | |
| if (token.toLowerCase() === "tab") { | |
| indents.push("\t"); | |
| continue; | |
| } | |
| const size = Number.parseInt(token, 10); | |
| if (!Number.isFinite(size) || size < 0) continue; | |
| indents.push(" ".repeat(size)); | |
| } | |
| return indents; | |
| } | |
| function parseNewlineList(value: string): Array<"\n" | "\r\n"> { | |
| const tokens = value | |
| .split(",") | |
| .map((token) => token.trim().toLowerCase()) | |
| .filter(Boolean); | |
| const newlines: Array<"\n" | "\r\n"> = []; | |
| for (const token of tokens) { | |
| if (token === "lf") newlines.push("\n"); | |
| if (token === "crlf") newlines.push("\r\n"); | |
| } | |
| return newlines; | |
| } | |
| function parseArgs(args: string[]): ParsedArgs { | |
| let targetPath: string | undefined; | |
| let desiredCid: string | undefined; | |
| let maxVariants: number | undefined; | |
| let reportEvery = DEFAULT_REPORT_EVERY; | |
| let useJson = true; | |
| let onlyJson = false; | |
| let useRaw = true; | |
| let sortKeys = false; | |
| let indents = DEFAULT_INDENT_SIZES.map((size) => " ".repeat(size)); | |
| indents.push("\t"); | |
| let newlines = [...DEFAULT_NEWLINES]; | |
| let lineBreaks = [...DEFAULT_LINE_BREAKS]; | |
| let trailingNewline = [...DEFAULT_TRAILING_NEWLINE]; | |
| let cidVersion: 0 | 1 | undefined; | |
| let rawLeaves: boolean | undefined; | |
| let writePath: string | undefined; | |
| let printMatch = true; | |
| let showHelp = false; | |
| let stopFlags = false; | |
| const extra: string[] = []; | |
| for (let i = 0; i < args.length; i += 1) { | |
| const arg = args[i]; | |
| if (!stopFlags && (arg === "--help" || arg === "-h")) { | |
| showHelp = true; | |
| continue; | |
| } | |
| if (!stopFlags && arg === "--") { | |
| stopFlags = true; | |
| continue; | |
| } | |
| if (!stopFlags && arg === "--max") { | |
| const value = args[i + 1]; | |
| if (!value) { | |
| return { | |
| targetPath, | |
| desiredCid, | |
| maxVariants, | |
| reportEvery, | |
| useJson, | |
| onlyJson, | |
| useRaw, | |
| sortKeys, | |
| indents, | |
| newlines, | |
| lineBreaks, | |
| trailingNewline, | |
| cidVersion, | |
| rawLeaves, | |
| writePath, | |
| showHelp, | |
| error: "Missing value for --max", | |
| }; | |
| } | |
| maxVariants = Number.parseInt(value, 10); | |
| if (!Number.isFinite(maxVariants) || maxVariants <= 0) { | |
| return { | |
| targetPath, | |
| desiredCid, | |
| maxVariants, | |
| reportEvery, | |
| useJson, | |
| onlyJson, | |
| useRaw, | |
| sortKeys, | |
| indents, | |
| newlines, | |
| lineBreaks, | |
| trailingNewline, | |
| cidVersion, | |
| rawLeaves, | |
| writePath, | |
| showHelp, | |
| error: "Invalid value for --max", | |
| }; | |
| } | |
| i += 1; | |
| continue; | |
| } | |
| if (!stopFlags && arg === "--report") { | |
| const value = args[i + 1]; | |
| if (!value) { | |
| return { | |
| targetPath, | |
| desiredCid, | |
| maxVariants, | |
| reportEvery, | |
| useJson, | |
| onlyJson, | |
| useRaw, | |
| sortKeys, | |
| indents, | |
| newlines, | |
| lineBreaks, | |
| trailingNewline, | |
| cidVersion, | |
| rawLeaves, | |
| writePath, | |
| showHelp, | |
| error: "Missing value for --report", | |
| }; | |
| } | |
| reportEvery = Number.parseInt(value, 10); | |
| if (!Number.isFinite(reportEvery) || reportEvery < 0) { | |
| return { | |
| targetPath, | |
| desiredCid, | |
| maxVariants, | |
| reportEvery, | |
| useJson, | |
| onlyJson, | |
| useRaw, | |
| sortKeys, | |
| indents, | |
| newlines, | |
| lineBreaks, | |
| trailingNewline, | |
| cidVersion, | |
| rawLeaves, | |
| writePath, | |
| showHelp, | |
| error: "Invalid value for --report", | |
| }; | |
| } | |
| i += 1; | |
| continue; | |
| } | |
| if (!stopFlags && arg === "--no-json") { | |
| useJson = false; | |
| continue; | |
| } | |
| if (!stopFlags && arg === "--only-json") { | |
| useJson = true; | |
| useRaw = false; | |
| onlyJson = true; | |
| continue; | |
| } | |
| if (!stopFlags && arg === "--no-raw") { | |
| useRaw = false; | |
| continue; | |
| } | |
| if (!stopFlags && arg === "--sort-keys") { | |
| sortKeys = true; | |
| continue; | |
| } | |
| if (!stopFlags && arg === "--indents") { | |
| const value = args[i + 1]; | |
| if (!value) { | |
| return { | |
| targetPath, | |
| desiredCid, | |
| maxVariants, | |
| reportEvery, | |
| useJson, | |
| onlyJson, | |
| useRaw, | |
| sortKeys, | |
| indents, | |
| newlines, | |
| lineBreaks, | |
| trailingNewline, | |
| cidVersion, | |
| rawLeaves, | |
| writePath, | |
| showHelp, | |
| error: "Missing value for --indents", | |
| }; | |
| } | |
| const parsed = parseIndentList(value); | |
| if (parsed.length === 0) { | |
| return { | |
| targetPath, | |
| desiredCid, | |
| maxVariants, | |
| reportEvery, | |
| useJson, | |
| onlyJson, | |
| useRaw, | |
| sortKeys, | |
| indents, | |
| newlines, | |
| lineBreaks, | |
| trailingNewline, | |
| cidVersion, | |
| rawLeaves, | |
| writePath, | |
| showHelp, | |
| error: "Invalid value for --indents", | |
| }; | |
| } | |
| indents = parsed; | |
| i += 1; | |
| continue; | |
| } | |
| if (!stopFlags && arg === "--newline") { | |
| const value = args[i + 1]; | |
| if (!value) { | |
| return { | |
| targetPath, | |
| desiredCid, | |
| maxVariants, | |
| reportEvery, | |
| useJson, | |
| onlyJson, | |
| useRaw, | |
| sortKeys, | |
| indents, | |
| newlines, | |
| lineBreaks, | |
| trailingNewline, | |
| cidVersion, | |
| rawLeaves, | |
| writePath, | |
| showHelp, | |
| error: "Missing value for --newline", | |
| }; | |
| } | |
| const parsed = parseNewlineList(value); | |
| if (parsed.length === 0) { | |
| return { | |
| targetPath, | |
| desiredCid, | |
| maxVariants, | |
| reportEvery, | |
| useJson, | |
| onlyJson, | |
| useRaw, | |
| sortKeys, | |
| indents, | |
| newlines, | |
| lineBreaks, | |
| trailingNewline, | |
| cidVersion, | |
| rawLeaves, | |
| writePath, | |
| showHelp, | |
| error: "Invalid value for --newline", | |
| }; | |
| } | |
| newlines = parsed; | |
| i += 1; | |
| continue; | |
| } | |
| if (!stopFlags && arg === "--pretty-only") { | |
| lineBreaks = [true]; | |
| continue; | |
| } | |
| if (!stopFlags && arg === "--minified-only") { | |
| lineBreaks = [false]; | |
| continue; | |
| } | |
| if (!stopFlags && arg === "--cidv1") { | |
| cidVersion = 1; | |
| continue; | |
| } | |
| if (!stopFlags && arg === "--raw-leaves") { | |
| rawLeaves = true; | |
| continue; | |
| } | |
| if (!stopFlags && arg === "--no-print") { | |
| printMatch = false; | |
| continue; | |
| } | |
| if (!stopFlags && arg === "--write") { | |
| const value = args[i + 1]; | |
| if (!value) { | |
| return { | |
| targetPath, | |
| desiredCid, | |
| maxVariants, | |
| reportEvery, | |
| useJson, | |
| onlyJson, | |
| useRaw, | |
| sortKeys, | |
| indents, | |
| newlines, | |
| lineBreaks, | |
| trailingNewline, | |
| cidVersion, | |
| rawLeaves, | |
| writePath, | |
| showHelp, | |
| error: "Missing value for --write", | |
| }; | |
| } | |
| writePath = value; | |
| i += 1; | |
| continue; | |
| } | |
| if (!targetPath) { | |
| targetPath = arg; | |
| continue; | |
| } | |
| if (!desiredCid) { | |
| desiredCid = arg; | |
| continue; | |
| } | |
| extra.push(arg); | |
| } | |
| if (extra.length > 0) { | |
| return { | |
| targetPath, | |
| desiredCid, | |
| maxVariants, | |
| reportEvery, | |
| useJson, | |
| onlyJson, | |
| useRaw, | |
| sortKeys, | |
| indents, | |
| newlines, | |
| lineBreaks, | |
| trailingNewline, | |
| cidVersion, | |
| rawLeaves, | |
| writePath, | |
| showHelp, | |
| error: `Unexpected extra arguments: ${extra.join(" ")}`, | |
| }; | |
| } | |
| return { | |
| targetPath, | |
| desiredCid, | |
| maxVariants, | |
| reportEvery, | |
| useJson, | |
| onlyJson, | |
| useRaw, | |
| sortKeys, | |
| indents, | |
| newlines, | |
| lineBreaks, | |
| trailingNewline, | |
| cidVersion, | |
| rawLeaves, | |
| writePath, | |
| printMatch, | |
| showHelp, | |
| }; | |
| } | |
| function normalizeTargetCid(value: string): string { | |
| const trimmed = value.trim(); | |
| const withoutScheme = trimmed.replace(/^ipfs:\/\//i, ""); | |
| const withoutPath = withoutScheme.split(/[/?#]/)[0] ?? withoutScheme; | |
| const dotIndex = withoutPath.lastIndexOf("."); | |
| const withoutExt = dotIndex > 0 ? withoutPath.slice(0, dotIndex) : withoutPath; | |
| if (withoutExt.startsWith("b")) { | |
| return withoutExt.toLowerCase(); | |
| } | |
| return withoutExt; | |
| } | |
| function inferCidVersion(cid: string): 0 | 1 | undefined { | |
| if (cid.startsWith("Qm")) return 0; | |
| if (cid.startsWith("b")) return 1; | |
| return undefined; | |
| } | |
| function buildHashOptions( | |
| targetCid: string, | |
| options: ParsedArgs, | |
| ): HashOptions[] { | |
| if (options.cidVersion !== undefined || options.rawLeaves !== undefined) { | |
| let cidVersion = options.cidVersion ?? inferCidVersion(targetCid) ?? 0; | |
| let rawLeaves = options.rawLeaves ?? false; | |
| if (rawLeaves && cidVersion === 0) { | |
| cidVersion = 1; | |
| } | |
| return [{ cidVersion, rawLeaves }]; | |
| } | |
| const inferred = inferCidVersion(targetCid); | |
| if (inferred === 0) { | |
| return [{ cidVersion: 0, rawLeaves: false }]; | |
| } | |
| if (inferred === 1) { | |
| return [ | |
| { cidVersion: 1, rawLeaves: false }, | |
| { cidVersion: 1, rawLeaves: true }, | |
| ]; | |
| } | |
| return [ | |
| { cidVersion: 0, rawLeaves: false }, | |
| { cidVersion: 1, rawLeaves: false }, | |
| { cidVersion: 1, rawLeaves: true }, | |
| ]; | |
| } | |
| function normalizeLineEndings(text: string, newline: "\n" | "\r\n"): string { | |
| const normalized = text.replace(/\r\n?/g, "\n"); | |
| if (newline === "\n") return normalized; | |
| return normalized.replace(/\n/g, "\r\n"); | |
| } | |
| function applyTrailingNewline( | |
| text: string, | |
| newline: "\n" | "\r\n", | |
| trailing: boolean, | |
| ): string { | |
| const stripped = text.replace(/(\r\n|\n|\r)+$/, ""); | |
| return trailing ? `${stripped}${newline}` : stripped; | |
| } | |
| function stringifyWhitespace(value: string): string { | |
| if (value.length === 0) return "<empty>"; | |
| if (value === " ") return "<space>"; | |
| if (value === "\t") return "<tab>"; | |
| if (/^ +$/.test(value)) return `<${value.length} spaces>`; | |
| return value.replace(/\n/g, "\\n").replace(/\r/g, "\\r"); | |
| } | |
| function formatJson(value: unknown, config: JsonFormatConfig): string { | |
| const render = (current: unknown, depth: number): string => { | |
| if (current === null || typeof current !== "object") { | |
| return JSON.stringify(current); | |
| } | |
| if (Array.isArray(current)) { | |
| if (current.length === 0) return "[]"; | |
| const items = current.map((item) => render(item, depth + 1)); | |
| if (!config.lineBreaks) { | |
| return `[${items.join(`,${config.commaAfter}`)}]`; | |
| } | |
| const indentCurrent = config.indent.repeat(depth); | |
| const indentChild = config.indent.repeat(depth + 1); | |
| const joined = items | |
| .map((item) => `${indentChild}${item}`) | |
| .join(`,${config.commaAfter}${config.newline}`); | |
| return `[${config.newline}${joined}${config.newline}${indentCurrent}]`; | |
| } | |
| const obj = current as Record<string, unknown>; | |
| let keys = Object.keys(obj); | |
| if (keys.length === 0) return "{}"; | |
| if (config.sortKeys) { | |
| keys = [...keys].sort(); | |
| } | |
| if (!config.lineBreaks) { | |
| const items = keys.map( | |
| (key) => | |
| `${JSON.stringify(key)}${config.colonBefore}:${config.colonAfter}${render( | |
| obj[key], | |
| depth + 1, | |
| )}`, | |
| ); | |
| return `{${items.join(`,${config.commaAfter}`)}}`; | |
| } | |
| const indentCurrent = config.indent.repeat(depth); | |
| const indentChild = config.indent.repeat(depth + 1); | |
| const items = keys.map( | |
| (key) => | |
| `${indentChild}${JSON.stringify(key)}${config.colonBefore}:${config.colonAfter}${render( | |
| obj[key], | |
| depth + 1, | |
| )}`, | |
| ); | |
| return `{${config.newline}${items.join( | |
| `,${config.commaAfter}${config.newline}`, | |
| )}${config.newline}${indentCurrent}}`; | |
| }; | |
| let output = render(value, 0); | |
| if (config.trailingNewline) { | |
| output += config.newline; | |
| } | |
| return output; | |
| } | |
| function describeCandidate(candidate: Candidate): string { | |
| const details = Object.entries(candidate.details) | |
| .map(([key, value]) => `${key}=${value}`) | |
| .join(", "); | |
| return `${candidate.label}${details ? ` (${details})` : ""}`; | |
| } | |
| async function run(): Promise<void> { | |
| const parsed = parseArgs(process.argv.slice(2)); | |
| if (parsed.error) { | |
| console.error(parsed.error); | |
| console.log(USAGE); | |
| process.exit(1); | |
| } | |
| if (parsed.showHelp) { | |
| console.log(USAGE); | |
| process.exit(0); | |
| } | |
| if (!parsed.targetPath || !parsed.desiredCid) { | |
| console.log(USAGE); | |
| process.exit(1); | |
| } | |
| const targetCid = normalizeTargetCid(parsed.desiredCid); | |
| const targetPath = resolve(process.cwd(), parsed.targetPath); | |
| const hashOptionsList = buildHashOptions(targetCid, parsed); | |
| let originalText: string; | |
| try { | |
| originalText = await Bun.file(targetPath).text(); | |
| } catch (error) { | |
| console.error(`Failed to read file: ${parsed.targetPath}`); | |
| if (error instanceof Error) { | |
| console.error(error.message); | |
| } | |
| process.exit(1); | |
| return; | |
| } | |
| let parsedJson: unknown = undefined; | |
| let jsonError: Error | undefined; | |
| if (parsed.useJson) { | |
| try { | |
| parsedJson = JSON.parse(originalText); | |
| } catch (error) { | |
| jsonError = error instanceof Error ? error : new Error("Invalid JSON"); | |
| if (parsed.onlyJson) { | |
| console.error("JSON parse failed; --only-json requested."); | |
| console.error(jsonError.message); | |
| process.exit(1); | |
| return; | |
| } | |
| } | |
| } | |
| const encoder = new TextEncoder(); | |
| const seen = new Set<string>(); | |
| let attempts = 0; | |
| let found: Candidate | undefined; | |
| let foundCid: string | undefined; | |
| let foundHashOptions: HashOptions | undefined; | |
| let stoppedEarly = false; | |
| const tryCandidate = async (candidate: Candidate): Promise<boolean> => { | |
| if (seen.has(candidate.content)) { | |
| return false; | |
| } | |
| seen.add(candidate.content); | |
| attempts += 1; | |
| const bytes = encoder.encode(candidate.content); | |
| for (const options of hashOptionsList) { | |
| const cid = await ipfsOnlyHash(bytes, options); | |
| if (cid === targetCid) { | |
| found = candidate; | |
| foundCid = cid; | |
| foundHashOptions = options; | |
| return true; | |
| } | |
| } | |
| if (parsed.reportEvery > 0 && attempts % parsed.reportEvery === 0) { | |
| console.log(`Checked ${attempts} variants...`); | |
| } | |
| if (parsed.maxVariants && attempts >= parsed.maxVariants) { | |
| stoppedEarly = true; | |
| return true; | |
| } | |
| return false; | |
| }; | |
| if (parsed.useRaw) { | |
| const rawCandidate: Candidate = { | |
| content: originalText, | |
| label: "raw", | |
| details: { | |
| source: "original", | |
| }, | |
| }; | |
| if (await tryCandidate(rawCandidate)) { | |
| stoppedEarly = stoppedEarly || !!found; | |
| } | |
| for (const newline of parsed.newlines) { | |
| for (const trailingNewline of parsed.trailingNewline) { | |
| const normalized = applyTrailingNewline( | |
| normalizeLineEndings(originalText, newline), | |
| newline, | |
| trailingNewline, | |
| ); | |
| const candidate: Candidate = { | |
| content: normalized, | |
| label: "raw", | |
| details: { | |
| newline: newline === "\n" ? "LF" : "CRLF", | |
| trailingNewline: trailingNewline ? "on" : "off", | |
| }, | |
| }; | |
| if (await tryCandidate(candidate)) { | |
| stoppedEarly = stoppedEarly || !!found; | |
| } | |
| if (found || stoppedEarly) break; | |
| } | |
| if (found || stoppedEarly) break; | |
| } | |
| } | |
| if (parsed.useJson && parsedJson !== undefined && !found && !stoppedEarly) { | |
| for (const lineBreaks of parsed.lineBreaks) { | |
| const indents = lineBreaks ? parsed.indents : [""]; | |
| for (const indent of indents) { | |
| for (const newline of parsed.newlines) { | |
| for (const trailingNewline of parsed.trailingNewline) { | |
| for (const colonBefore of DEFAULT_COLON_BEFORE) { | |
| for (const colonAfter of DEFAULT_COLON_AFTER) { | |
| for (const commaAfter of DEFAULT_COMMA_AFTER) { | |
| const config: JsonFormatConfig = { | |
| indent, | |
| newline, | |
| lineBreaks, | |
| trailingNewline, | |
| colonBefore, | |
| colonAfter, | |
| commaAfter, | |
| sortKeys: parsed.sortKeys, | |
| }; | |
| const formatted = formatJson(parsedJson, config); | |
| const candidate: Candidate = { | |
| content: formatted, | |
| label: "json", | |
| details: { | |
| lineBreaks: lineBreaks ? "on" : "off", | |
| indent: stringifyWhitespace(indent), | |
| newline: newline === "\n" ? "LF" : "CRLF", | |
| trailingNewline: trailingNewline ? "on" : "off", | |
| colonBefore: stringifyWhitespace(colonBefore), | |
| colonAfter: stringifyWhitespace(colonAfter), | |
| commaAfter: stringifyWhitespace(commaAfter), | |
| sortKeys: parsed.sortKeys ? "on" : "off", | |
| }, | |
| }; | |
| if (await tryCandidate(candidate)) { | |
| stoppedEarly = stoppedEarly || !!found; | |
| } | |
| if (found || stoppedEarly) break; | |
| } | |
| if (found || stoppedEarly) break; | |
| } | |
| if (found || stoppedEarly) break; | |
| } | |
| if (found || stoppedEarly) break; | |
| } | |
| if (found || stoppedEarly) break; | |
| } | |
| if (found || stoppedEarly) break; | |
| } | |
| if (found || stoppedEarly) break; | |
| } | |
| } | |
| if (found) { | |
| console.log(`Match found after ${attempts} variants.`); | |
| console.log(`CID: ${foundCid}`); | |
| console.log(`Format: ${describeCandidate(found)}`); | |
| if (foundHashOptions) { | |
| console.log( | |
| `Hash options: cidVersion=${foundHashOptions.cidVersion}, rawLeaves=${foundHashOptions.rawLeaves}`, | |
| ); | |
| } | |
| if (parsed.printMatch ?? true) { | |
| console.log(""); | |
| console.log("-----BEGIN MATCHED CONTENT-----"); | |
| process.stdout.write(found.content); | |
| if ( | |
| !found.content.endsWith("\n") && | |
| !found.content.endsWith("\r\n") | |
| ) { | |
| process.stdout.write("\n"); | |
| } | |
| console.log("-----END MATCHED CONTENT-----"); | |
| } | |
| if (parsed.writePath) { | |
| try { | |
| await Bun.write(parsed.writePath, found.content); | |
| console.log(`Wrote content to ${parsed.writePath}`); | |
| } catch (error) { | |
| console.error(`Failed to write to ${parsed.writePath}`); | |
| if (error instanceof Error) { | |
| console.error(error.message); | |
| } | |
| } | |
| } | |
| return; | |
| } | |
| if (stoppedEarly) { | |
| console.log(`Stopped after ${attempts} variants (limit reached).`); | |
| process.exitCode = 1; | |
| return; | |
| } | |
| console.log(`No match found after ${attempts} variants.`); | |
| if (jsonError) { | |
| console.log("JSON parsing failed, so only raw variants were checked."); | |
| } | |
| process.exitCode = 1; | |
| } | |
| await run(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment