|
| 1 | +// File: utils/common.tsx |
| 2 | +// 公共工具函数,消除跨文件重复代码 |
| 3 | +import { Runtime } from "./runtime" |
| 4 | + |
| 5 | +// ===== 运行时访问 ===== |
| 6 | + |
| 7 | +export function FM(): any { |
| 8 | + return (globalThis as any).FileManager ?? Runtime.FileManager |
| 9 | +} |
| 10 | + |
| 11 | +export function storage(): any { |
| 12 | + return (globalThis as any).Storage ?? Runtime.Storage |
| 13 | +} |
| 14 | + |
| 15 | +// ===== 异步兼容 ===== |
| 16 | + |
| 17 | +export function sleep(ms: number) { |
| 18 | + return new Promise<void>((r) => setTimeout(r, ms)) |
| 19 | +} |
| 20 | + |
| 21 | +export async function callMaybeAsync(fn: any, thisArg: any, args: any[]) { |
| 22 | + try { |
| 23 | + const r = fn.apply(thisArg, args) |
| 24 | + if (r && typeof r === "object" && typeof r.then === "function") return await r |
| 25 | + return r |
| 26 | + } catch { |
| 27 | + return undefined |
| 28 | + } |
| 29 | +} |
| 30 | + |
| 31 | +// ===== 路径工具 ===== |
| 32 | + |
| 33 | +export function normalizePath(p: string): string { |
| 34 | + return String(p ?? "").trim().replace(/\/+$/, "") |
| 35 | +} |
| 36 | + |
| 37 | +export function basename(p: string): string { |
| 38 | + const x = String(p ?? "") |
| 39 | + const i = x.lastIndexOf("/") |
| 40 | + return i >= 0 ? x.slice(i + 1) : x |
| 41 | +} |
| 42 | + |
| 43 | +export function dirname(p: string): string { |
| 44 | + const x = String(p ?? "") |
| 45 | + const i = x.lastIndexOf("/") |
| 46 | + if (i <= 0) return "" |
| 47 | + return x.slice(0, i) |
| 48 | +} |
| 49 | + |
| 50 | +// ===== 文件操作兼容 ===== |
| 51 | + |
| 52 | +export async function removePathLoose(path: string) { |
| 53 | + const fm = FM() |
| 54 | + try { |
| 55 | + if (typeof fm?.removeSync === "function") { |
| 56 | + fm.removeSync(path) |
| 57 | + return |
| 58 | + } |
| 59 | + if (typeof fm?.remove === "function") { |
| 60 | + await fm.remove(path) |
| 61 | + return |
| 62 | + } |
| 63 | + if (typeof fm?.delete === "function") { |
| 64 | + await fm.delete(path) |
| 65 | + return |
| 66 | + } |
| 67 | + } catch { } |
| 68 | +} |
| 69 | + |
| 70 | +export function tempDownloadPath(fileName: string): string { |
| 71 | + const fm = FM() |
| 72 | + const base = String(fm?.temporaryDirectory ?? "/tmp") |
| 73 | + const safeName = String(fileName ?? "asset.bin").replace(/[\\/]/g, "_") |
| 74 | + return `${base}/wanxiang_tmp_${Date.now()}_${safeName}` |
| 75 | +} |
| 76 | + |
| 77 | +export async function getFileSize(fm: any, path: string): Promise<number> { |
| 78 | + if (typeof fm?.fileSizeSync === "function") return Number(fm.fileSizeSync(path) ?? 0) |
| 79 | + if (typeof fm?.fileSize === "function") return Number((await fm.fileSize(path)) ?? 0) |
| 80 | + if (typeof fm?.statSync === "function") return Number(fm.statSync(path)?.size ?? 0) |
| 81 | + if (typeof fm?.stat === "function") return Number((await fm.stat(path))?.size ?? 0) |
| 82 | + if (typeof fm?.attributesSync === "function") { |
| 83 | + const a = fm.attributesSync(path) |
| 84 | + return Number(a?.size ?? a?.fileSize ?? 0) |
| 85 | + } |
| 86 | + if (typeof fm?.attributes === "function") { |
| 87 | + const a = await fm.attributes(path) |
| 88 | + return Number(a?.size ?? a?.fileSize ?? 0) |
| 89 | + } |
| 90 | + throw new Error("无法获取文件大小(FileManager 缺少 fileSize/stat/attributes)") |
| 91 | +} |
| 92 | + |
| 93 | +// ===== 模式匹配 ===== |
| 94 | + |
| 95 | +export function escapeRe(s: string) { |
| 96 | + return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") |
| 97 | +} |
| 98 | + |
| 99 | +export function compilePatterns(patterns: string[]): RegExp[] { |
| 100 | + const list: RegExp[] = [] |
| 101 | + for (const raw of patterns ?? []) { |
| 102 | + const p = String(raw ?? "").trim() |
| 103 | + if (!p) continue |
| 104 | + try { |
| 105 | + list.push(new RegExp(p)) |
| 106 | + continue |
| 107 | + } catch { } |
| 108 | + list.push(new RegExp("^" + p.split("*").map(escapeRe).join(".*") + "$", "i")) |
| 109 | + } |
| 110 | + return list |
| 111 | +} |
| 112 | + |
| 113 | +export function matchAny(v: string, patterns: RegExp[]): boolean { |
| 114 | + for (const re of patterns) { |
| 115 | + if (re.test(v)) return true |
| 116 | + } |
| 117 | + return false |
| 118 | +} |
| 119 | + |
| 120 | +// ===== GitHub/CNB 工具 ===== |
| 121 | + |
| 122 | +export function pickGithubSha256FromDigest(digest?: string): string | undefined { |
| 123 | + if (!digest) return undefined |
| 124 | + const m = String(digest).match(/sha256\s*:\s*([0-9a-fA-F]{32,})/i) |
| 125 | + return m?.[1] |
| 126 | +} |
| 127 | + |
| 128 | +export function globToRegExp(glob: string): RegExp { |
| 129 | + const esc = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*") |
| 130 | + return new RegExp("^" + esc + "$", "i") |
| 131 | +} |
| 132 | + |
| 133 | +export function pickExpectedSize(asset: any): number | undefined { |
| 134 | + const candidates = [ |
| 135 | + asset?.size, |
| 136 | + asset?.fileSize, |
| 137 | + asset?.contentLength, |
| 138 | + asset?.bytes, |
| 139 | + asset?.asset?.size, |
| 140 | + asset?.asset?.fileSize, |
| 141 | + ] |
| 142 | + for (const v of candidates) { |
| 143 | + const n = typeof v === "string" ? Number(v) : v |
| 144 | + if (typeof n === "number" && Number.isFinite(n) && n > 0) return n |
| 145 | + } |
| 146 | + return undefined |
| 147 | +} |
0 commit comments