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 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | 3x 7x 7x 7x 7x | import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { TtlCache } from "./cache.js";
const execFileAsync = promisify(execFile);
export class SrcmapError extends Error {
constructor(
message: string,
public readonly code: string,
public readonly details?: unknown,
) {
super(message);
this.name = "SrcmapError";
}
}
export interface RetryOptions {
maxRetries: number;
}
export class SrcmapClient {
private readonly binary: string;
private readonly cache: TtlCache;
private readonly cachingEnabled: boolean;
constructor(
binary?: string,
cacheTtlMs?: number,
) {
this.binary = binary ?? "srcmap";
const ttl = cacheTtlMs ?? 300_000;
this.cache = new TtlCache(ttl);
this.cachingEnabled = ttl > 0;
}
private async run(args: string[]): Promise<string> {
try {
const { stdout } = await execFileAsync(this.binary, args, {
maxBuffer: 50 * 1024 * 1024,
timeout: 60_000,
});
return stdout;
} catch (error: unknown) {
const err = error as { stderr?: string; code?: string; message?: string };
const stderr = err.stderr ?? "";
// Try to parse structured JSON error from stderr
try {
const parsed = JSON.parse(stderr) as { error: string; code: string };
throw new SrcmapError(parsed.error, parsed.code);
} catch (parseError) {
if (parseError instanceof SrcmapError) throw parseError;
throw new SrcmapError(
stderr.replace(/^error:\s*/i, "").trim() || err.message || "srcmap command failed",
"CLI_ERROR",
);
}
}
}
private async runJson<T>(args: string[]): Promise<T> {
const output = await this.run([...args, "--json"]);
return JSON.parse(output) as T;
}
private async cachedRunJson<T>(cacheKey: string, args: string[]): Promise<T> {
if (this.cachingEnabled) {
const cached = this.cache.get<T>(cacheKey);
if (cached !== undefined) return cached;
}
const result = await this.runJson<T>(args);
if (this.cachingEnabled) {
this.cache.set(cacheKey, result);
}
return result;
}
async info(file: string): Promise<Record<string, unknown>> {
return this.cachedRunJson(`info:${file}`, ["info", file]);
}
async validate(file: string): Promise<Record<string, unknown>> {
return this.cachedRunJson(`validate:${file}`, ["validate", file]);
}
async lookup(
file: string,
line: number,
column: number,
context?: number,
): Promise<Record<string, unknown>> {
const args = ["lookup", file, String(line), String(column)];
if (context !== undefined && context > 0) {
args.push("--context", String(context));
}
return this.runJson(args);
}
async resolve(
file: string,
source: string,
line: number,
column: number,
): Promise<Record<string, unknown>> {
return this.runJson(["resolve", file, "--source", source, String(line), String(column)]);
}
async sources(file: string): Promise<Record<string, unknown>> {
return this.cachedRunJson(`sources:${file}`, ["sources", file]);
}
async sourcesExtract(file: string, outputDir: string): Promise<Record<string, unknown>> {
return this.runJson(["sources", file, "--extract", "-o", outputDir]);
}
async fetch(url: string, outputDir: string): Promise<Record<string, unknown>> {
return this.runJson(["fetch", url, "-o", outputDir]);
}
async mappings(
file: string,
options?: { source?: string; limit?: number; offset?: number },
): Promise<Record<string, unknown>> {
const args = ["mappings", file];
if (options?.source) args.push("--source", options.source);
if (options?.limit !== undefined) args.push("--limit", String(options.limit));
if (options?.offset !== undefined) args.push("--offset", String(options.offset));
return this.runJson(args);
}
async symbolicate(input: string, maps: string[]): Promise<string> {
const args = ["symbolicate", input];
for (const map of maps) {
args.push("--map", map);
}
return this.run([...args, "--json"]);
}
}
|