| 1 | 3 | import { createHash } from 'node:crypto'; |
| 2 | 0 | import { gzipSync } from 'node:zlib'; |
| 3 | | |
| 4 | | export const sha1hex = (s: string | Uint8Array): string => createHash('sha1').update(s).digest('hex'); |
| 5 | | |
| 6 | | export const gzip = (data: string | Uint8Array): Buffer => gzipSync(data, { level: 9 }); |
| 7 | | |
| 8 | | export const toPosix = (p: string): string => p.replace(/\\/g, '/'); |
| 9 | | |
| 10 | | export const stripAnsi = (s: string): string => |
| 11 | | // eslint-disable-next-line no-control-regex |
| 12 | | s.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g, ''); |
| 13 | | |
| 14 | | export const stripBom = (s: string): string => (s.charCodeAt(0) === 0xfeff ? s.slice(1) : s); |
| 15 | | |
| 16 | | /** Truncate to at most `max` UTF-8 bytes, marking the cut. */ |
| 17 | | export function truncBytes(s: string, max: number): string { |
| 18 | | if (Buffer.byteLength(s, 'utf8') <= max) return s; |
| 19 | | const mark = '\n…[truncated]'; |
| 20 | | const keep = Math.max(0, max - Buffer.byteLength(mark, 'utf8')); |
| 21 | | let cut = Buffer.from(s, 'utf8').subarray(0, keep).toString('utf8'); |
| 22 | | while (cut.endsWith('�')) cut = cut.slice(0, -1); |
| 23 | | return cut + mark; |
| 24 | | } |
| 25 | | |
| 26 | | export const jsonBytes = (v: unknown): number => Buffer.byteLength(JSON.stringify(v), 'utf8'); |
| 27 | | |
| 28 | | export const asArray = <T>(v: T | T[] | undefined | null): T[] => |
| 29 | | v === undefined || v === null ? [] : Array.isArray(v) ? v : [v]; |
| 30 | | |
| 31 | | export const sleep = (ms: number): Promise<void> => new Promise(r => setTimeout(r, ms)); |
| 32 | | |
| 33 | | export function errMsg(e: unknown): string { |
| 34 | | return e instanceof Error ? e.message : String(e); |
| 35 | | } |
| 36 | | |
| 37 | | export function pct(hit: number, total: number): string { |
| 38 | | if (total <= 0) return 'n/a'; |
| 39 | | return `${((hit / total) * 100).toFixed(1)}%`; |
| 40 | | } |