mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-03 04:07:03 +00:00
fix: cache paths follow HYPERFRAMES_CACHE_DIR (#6716)
* fix: cache paths follow HYPERFRAMES_CACHE_DIR Every place HyperFrames keeps downloaded or built files now takes its folder from one helper, hyperframesCacheDir() in @hyperframes/core. The default stays ~/.cache/hyperframes; HYPERFRAMES_CACHE_DIR moves it. * fix: register the cache-dir subpath for bun and node, and build inspector from core source * test: the cache-dir override test uses a folder that is absolute on every platform * fix: the opencv cache follows HYPERFRAMES_CACHE_DIR * fix(cli): npm installs run in their staging dir, so no cache path reaches a command line * fix(cli): whisper.cpp is cloned inside its build dir, so no cache path is a git argument * test(cli): the whisper clone test finds no system whisper on Windows either
This commit is contained in:
@@ -1920,6 +1920,9 @@ default 3); `--json` prints the results as JSON.
|
||||
|
||||
## Keep the environment healthy
|
||||
|
||||
HyperFrames keeps what it downloads and builds (the headless browser, models, optional packages, fonts and project
|
||||
history) under `~/.cache/hyperframes`. Set `HYPERFRAMES_CACHE_DIR` to an absolute path to keep it somewhere else.
|
||||
|
||||
### `doctor`
|
||||
|
||||
Check the machine for everything a render needs.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { existsSync } from "node:fs";
|
||||
import { mkdir, rename, writeFile } from "node:fs/promises";
|
||||
import { homedir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import type { AudioAnalysisProvider, AudioSignal } from "@hyperframes/core/audio-analysis";
|
||||
import { AVAILABLE, runFile, unavailable, withWavFile } from "./shared.js";
|
||||
import { hyperframesCacheDir } from "@hyperframes/core/cache-dir";
|
||||
|
||||
const SOUND_ANALYSIS = "/System/Library/Frameworks/SoundAnalysis.framework";
|
||||
const MUSIC_UNDERSTANDING_MACOS = 27;
|
||||
@@ -62,7 +62,7 @@ function atLeastOneWindow(signal: AudioSignal): AudioSignal {
|
||||
};
|
||||
}
|
||||
|
||||
const helperDir = () => join(homedir(), ".cache", "hyperframes", "audio-helpers");
|
||||
const helperDir = () => hyperframesCacheDir("audio-helpers");
|
||||
|
||||
function helperPath(name: string, source: string): string {
|
||||
const hash = createHash("sha256").update(source).digest("hex").slice(0, 12);
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
import { mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { homedir, tmpdir } from "node:os";
|
||||
import { tmpdir } from "node:os";
|
||||
import { hyperframesCacheDir } from "@hyperframes/core/cache-dir";
|
||||
import { basename, join } from "node:path";
|
||||
import { processIsAlive } from "../utils/processTree.js";
|
||||
|
||||
const COMPILED_MODEL = /^onnxruntime-.*\.mlmodelc?$/;
|
||||
|
||||
const COREML_TEMP_MANIFESTS = join(
|
||||
homedir(),
|
||||
".cache",
|
||||
"hyperframes",
|
||||
"background-removal",
|
||||
"coreml-temp",
|
||||
);
|
||||
const COREML_TEMP_MANIFESTS = hyperframesCacheDir("background-removal", "coreml-temp");
|
||||
|
||||
interface Manifest {
|
||||
pid: number;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { existsSync, mkdirSync } from "node:fs";
|
||||
import { homedir, platform, arch } from "node:os";
|
||||
import { platform, arch } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { downloadFile } from "../utils/download.js";
|
||||
import { hyperframesCacheDir } from "@hyperframes/core/cache-dir";
|
||||
|
||||
export const MODELS_DIR = join(homedir(), ".cache", "hyperframes", "background-removal", "models");
|
||||
export const MODELS_DIR = hyperframesCacheDir("background-removal", "models");
|
||||
|
||||
export const DEFAULT_MODEL = "u2net_human_seg" as const;
|
||||
export type ModelId = typeof DEFAULT_MODEL;
|
||||
|
||||
@@ -24,6 +24,7 @@ import { BrowserDownloadStalledError, installInChild } from "./installInChild.js
|
||||
import { normalizeErrorMessage } from "../utils/errorMessage.js";
|
||||
import { runRenderSetupWorker } from "../utils/cancellableProcess.js";
|
||||
import { unzipArchive } from "./unzipArchive.js";
|
||||
import { hyperframesCacheDir } from "@hyperframes/core/cache-dir";
|
||||
|
||||
type PuppeteerBrowsers = typeof import("@puppeteer/browsers");
|
||||
|
||||
@@ -39,8 +40,8 @@ async function loadPuppeteerBrowsers(): Promise<PuppeteerBrowsers> {
|
||||
}
|
||||
}
|
||||
|
||||
const CACHE_ROOT_DIR = join(homedir(), ".cache", "hyperframes");
|
||||
const CACHE_DIR = join(homedir(), ".cache", "hyperframes", "chrome");
|
||||
const CACHE_ROOT_DIR = hyperframesCacheDir();
|
||||
const CACHE_DIR = hyperframesCacheDir("chrome");
|
||||
const ARCHIVE_PLUS_UNPACKED_SHELL_MB = 512;
|
||||
// Puppeteer's managed cache — where `@puppeteer/browsers install
|
||||
// chrome-headless-shell` (and `puppeteer install`) drop binaries. The engine's
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
rmSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { homedir, tmpdir } from "node:os";
|
||||
import { tmpdir } from "node:os";
|
||||
import { basename, dirname, extname, join, posix, relative, resolve } from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import { parseHTML } from "linkedom";
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
stagedSize,
|
||||
TEXT_FILE_EXTS,
|
||||
} from "../utils/publishProject.js";
|
||||
import { hyperframesCacheDir } from "@hyperframes/core/cache-dir";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
@@ -202,7 +203,7 @@ interface CacheEntry {
|
||||
}
|
||||
|
||||
function defaultOffloadCachePath(): string {
|
||||
return join(homedir(), ".cache", "hyperframes", "cloud-assets.json");
|
||||
return hyperframesCacheDir("cloud-assets.json");
|
||||
}
|
||||
|
||||
function readCache(path: string): Record<string, CacheEntry> {
|
||||
@@ -652,7 +653,7 @@ async function prepareEntryGifs(
|
||||
downloadDir: outputDir,
|
||||
outputDir,
|
||||
outputSrcPrefix: posix.relative(posix.dirname(entryPath), GIF_PREFIX),
|
||||
cacheDir: join(homedir(), ".cache", "hyperframes", "animated-gif"),
|
||||
cacheDir: hyperframesCacheDir("animated-gif"),
|
||||
sourceAssets,
|
||||
});
|
||||
if (result.preparedGifs.length === 0) return;
|
||||
|
||||
@@ -11,14 +11,14 @@ import {
|
||||
renameSync,
|
||||
unlinkSync,
|
||||
} from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { dirname, join } from "node:path";
|
||||
import { downloadFile } from "../../utils/download.js";
|
||||
import { isMissingPackageError } from "../../utils/missingPackage.js";
|
||||
import { VERSION } from "../../version.js";
|
||||
import { hyperframesCacheDir } from "@hyperframes/core/cache-dir";
|
||||
|
||||
const DOCS_URL = "https://hyperframes.heygen.com/deploy/aws-lambda";
|
||||
const HANDLER_CACHE_DIR = join(homedir(), ".cache", "hyperframes", "lambda-handler");
|
||||
const HANDLER_CACHE_DIR = hyperframesCacheDir("lambda-handler");
|
||||
|
||||
type AdapterSdk = typeof import("@hyperframes/aws-lambda/sdk");
|
||||
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
// Haar face models, pinned by size and sha256, into ~/.cache/hyperframes/opencv/<version>/.
|
||||
import { mkdirSync, readFileSync, renameSync, rmSync } from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import { homedir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { hyperframesCacheDir } from "@hyperframes/core/cache-dir";
|
||||
import { downloadToFile } from "../cloud/download.js";
|
||||
import { verifies, type ModelFile } from "../whisper/modelFile.js";
|
||||
import { Fix } from "./media.js";
|
||||
|
||||
const VERSION = "4.10.0";
|
||||
export const OPENCV_DIR = join(homedir(), ".cache", "hyperframes", "opencv", VERSION);
|
||||
export const OPENCV_DIR = hyperframesCacheDir("opencv", VERSION);
|
||||
|
||||
interface Pinned extends ModelFile {
|
||||
url: string;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { existsSync, mkdirSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { downloadFile } from "../utils/download.js";
|
||||
import { hyperframesCacheDir } from "@hyperframes/core/cache-dir";
|
||||
|
||||
const CACHE_DIR = join(homedir(), ".cache", "hyperframes", "tts");
|
||||
const CACHE_DIR = hyperframesCacheDir("tts");
|
||||
const MODELS_DIR = join(CACHE_DIR, "models");
|
||||
const VOICES_DIR = join(CACHE_DIR, "voices");
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { existsSync, mkdirSync, readdirSync, unlinkSync } from "node:fs";
|
||||
import { join, dirname, basename } from "node:path";
|
||||
import { homedir } from "node:os";
|
||||
import {
|
||||
ensureModel,
|
||||
ensureVoices,
|
||||
@@ -12,6 +11,7 @@ import {
|
||||
} from "./manager.js";
|
||||
import { findPython, hasPythonPackage, workingPythonEnv } from "./python.js";
|
||||
import { writeNewFileSync } from "../utils/writeNewFile.js";
|
||||
import { hyperframesCacheDir } from "@hyperframes/core/cache-dir";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Inline Python script for Kokoro synthesis
|
||||
@@ -115,7 +115,7 @@ const ESPEAK_LANG_OVERRIDES: Partial<Record<SupportedLang, string>> = {
|
||||
// Cache the script to avoid rewriting it on every invocation.
|
||||
// The filename carries a version suffix so older installs automatically
|
||||
// upgrade when the script body changes (e.g., adding the `lang` kwarg).
|
||||
const SCRIPT_DIR = join(homedir(), ".cache", "hyperframes", "tts");
|
||||
const SCRIPT_DIR = hyperframesCacheDir("tts");
|
||||
const SCRIPT_PATH = join(SCRIPT_DIR, "synth-v4.py");
|
||||
|
||||
function ensureSynthScript(): string {
|
||||
|
||||
@@ -611,9 +611,8 @@ describe("install", () => {
|
||||
function setup() {
|
||||
const cache = mkdtempSync(join(tmpdir(), "hf-optional-"));
|
||||
const dir = join(cache, "pkg@1.0.0");
|
||||
const stubNpm = async (args: string[]) => {
|
||||
const stubNpm = async (_args: string[], prefix: string) => {
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
const prefix = args[args.indexOf("--prefix") + 1] as string;
|
||||
if (!existsSync(prefix)) throw new Error("ENOENT: staging dir vanished mid-install");
|
||||
mkdirSync(join(prefix, "node_modules", name), { recursive: true });
|
||||
writeFileSync(join(prefix, "node_modules", name, "package.json"), "{}");
|
||||
@@ -621,6 +620,22 @@ describe("install", () => {
|
||||
return { cache, dir, stubNpm };
|
||||
}
|
||||
|
||||
it("runs npm inside the staging dir, with no path on its command line", async () => {
|
||||
const { cache, dir } = setup();
|
||||
const seen: { args: string[]; cwd: string }[] = [];
|
||||
try {
|
||||
await install(dir, name, version, async (args, cwd) => {
|
||||
seen.push({ args, cwd });
|
||||
});
|
||||
expect(seen).toHaveLength(1);
|
||||
expect(seen[0]!.args).not.toContain("--prefix");
|
||||
expect(seen[0]!.args.some((arg) => arg.includes(cache))).toBe(false);
|
||||
expect(seen[0]!.cwd.startsWith(`${dir}.tmp-`)).toBe(true);
|
||||
} finally {
|
||||
rmSync(cache, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("gives each overlapping install in one process its own staging dir", async () => {
|
||||
const { cache, dir, stubNpm } = setup();
|
||||
try {
|
||||
|
||||
@@ -12,9 +12,9 @@ import {
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import { homedir } from "node:os";
|
||||
import { basename, dirname, join, sep } from "node:path";
|
||||
import { buildNpmCommand } from "./npxCommand.js";
|
||||
import { hyperframesCacheDir } from "@hyperframes/core/cache-dir";
|
||||
|
||||
/** Module type of each optional package; the keys are the only names the loader accepts. */
|
||||
export interface OptionalPackageModules {
|
||||
@@ -37,7 +37,7 @@ export const PINNED_PACKAGES = {
|
||||
|
||||
type PinnedPackage = keyof typeof PINNED_PACKAGES;
|
||||
|
||||
export const CACHE_DIR = join(homedir(), ".cache", "hyperframes", "optional");
|
||||
export const CACHE_DIR = hyperframesCacheDir("optional");
|
||||
|
||||
/** Installed on first use only when a host bundled the CLI without its sharp dependency. */
|
||||
export const SHARP_VERSION = "0.35.3";
|
||||
@@ -286,9 +286,9 @@ export function loadBesideCli(name: PinnedPackage, cliUrl = import.meta.url): un
|
||||
return copy?.resolution === "entry" ? createRequire(cliUrl)(copy.path) : null;
|
||||
}
|
||||
|
||||
export function runNpm(args: string[], signal?: AbortSignal): Promise<void> {
|
||||
export function runNpm(args: string[], signal?: AbortSignal, cwd?: string): Promise<void> {
|
||||
const npm = buildNpmCommand(args);
|
||||
const child = spawn(npm.command, npm.args, { stdio: ["ignore", "pipe", "pipe"], signal });
|
||||
const child = spawn(npm.command, npm.args, { stdio: ["ignore", "pipe", "pipe"], signal, cwd });
|
||||
return new Promise((resolve, reject) => {
|
||||
let output = "";
|
||||
child.stdout.on("data", (chunk) => (output += chunk));
|
||||
@@ -340,26 +340,27 @@ export async function install(
|
||||
dir: string,
|
||||
name: string,
|
||||
version: string,
|
||||
run: (args: string[]) => Promise<void> = runNpm,
|
||||
run: (args: string[], cwd: string) => Promise<void> = (args, cwd) => runNpm(args, undefined, cwd),
|
||||
): Promise<void> {
|
||||
sweepStaleStaging(dir);
|
||||
const staging = `${dir}.tmp-${process.pid}-${randomUUID().slice(0, 8)}`;
|
||||
mkdirSync(staging, { recursive: true });
|
||||
writeFileSync(join(staging, "package.json"), "{}");
|
||||
try {
|
||||
await run([
|
||||
"install",
|
||||
`${name}@${version}`,
|
||||
"--prefix",
|
||||
await run(
|
||||
[
|
||||
"install",
|
||||
`${name}@${version}`,
|
||||
"--no-audit",
|
||||
"--no-fund",
|
||||
"--loglevel=error",
|
||||
// A first-use install is interactive, not a CI resolve: fail fast and name the
|
||||
// manual command instead of sitting through npm's default multi-minute backoff.
|
||||
"--fetch-retries=0",
|
||||
"--fetch-timeout=20000",
|
||||
],
|
||||
staging,
|
||||
"--no-audit",
|
||||
"--no-fund",
|
||||
"--loglevel=error",
|
||||
// A first-use install is interactive, not a CI resolve: fail fast and name the
|
||||
// manual command instead of sitting through npm's default multi-minute backoff.
|
||||
"--fetch-retries=0",
|
||||
"--fetch-timeout=20000",
|
||||
]);
|
||||
);
|
||||
if (isInstalled(dir, name)) return;
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
renameSync(staging, dir);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { homedir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { Writable } from "node:stream";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { hyperframesCacheDir } from "@hyperframes/core/cache-dir";
|
||||
|
||||
export const WARM_IDLE_MS = 5 * 60_000;
|
||||
export const HANDSHAKE_TIMEOUT_MS = 2_000;
|
||||
@@ -50,7 +51,7 @@ export function warmCheckDirectory(env = process.env): string | undefined {
|
||||
? join(env.LOCALAPPDATA ?? join(homedir(), "AppData", "Local"), "hyperframes", "warm")
|
||||
: env.XDG_RUNTIME_DIR
|
||||
? join(env.XDG_RUNTIME_DIR, "hyperframes-warm")
|
||||
: join(homedir(), ".cache", "hyperframes", "warm");
|
||||
: hyperframesCacheDir("warm");
|
||||
try {
|
||||
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
||||
} catch {
|
||||
|
||||
@@ -38,3 +38,28 @@ it("never installs when the runtime vanishes after discovery and installation is
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("clones whisper.cpp inside its build dir, with no cache path on the command line", async () => {
|
||||
const cache = mkdtempSync(join(tmpdir(), "hf-whisper-cache-"));
|
||||
vi.stubEnv("HYPERFRAMES_CACHE_DIR", cache);
|
||||
vi.resetModules();
|
||||
const { ensureWhisper: ensure, WhisperUnavailableError: Unavailable } =
|
||||
await import("./manager.js");
|
||||
calls.exec.mockImplementation((command: string, args: string[]) => {
|
||||
const lookup = command === "which" || command === "where";
|
||||
if (lookup && (args[0] === "brew" || args[0] === "whisper-cli"))
|
||||
throw new Error("not available");
|
||||
if (lookup) return `/bin/${args[0]}\n`;
|
||||
if (command === "git" && args[0] === "clone") throw new Error("stop after the clone");
|
||||
return "";
|
||||
});
|
||||
try {
|
||||
await expect(ensure()).rejects.toBeInstanceOf(Unavailable);
|
||||
const clone = calls.exec.mock.calls.find(([c, a]) => c === "git" && a[0] === "clone");
|
||||
const [, args, options] = clone as [string, string[], { cwd?: string }];
|
||||
expect(args.some((arg) => arg.includes(cache))).toBe(false);
|
||||
expect(options.cwd).toBe(join(cache, "whisper", "whisper.cpp"));
|
||||
} finally {
|
||||
rmSync(cache, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
// fallow-ignore-file code-duplication complexity
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { existsSync, mkdirSync, readdirSync, rmSync, statSync } from "node:fs";
|
||||
import { homedir, platform } from "node:os";
|
||||
import { platform } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { findFFmpeg } from "../browser/ffmpeg.js";
|
||||
import { downloadFile } from "../utils/download.js";
|
||||
import { stoppableExecFile } from "../utils/stoppableExecFile.js";
|
||||
import { verifies, type ModelFile } from "./modelFile.js";
|
||||
import { hyperframesCacheDir } from "@hyperframes/core/cache-dir";
|
||||
|
||||
const MODELS_DIR = join(homedir(), ".cache", "hyperframes", "whisper", "models");
|
||||
const MODELS_DIR = hyperframesCacheDir("whisper", "models");
|
||||
const DEFAULT_MODEL = "small.en";
|
||||
|
||||
export type WhisperSource = "env" | "system" | "brew" | "build";
|
||||
@@ -144,7 +145,7 @@ function findFromSystem(): WhisperResult | undefined {
|
||||
|
||||
// --- Build from source ------------------------------------------------------
|
||||
|
||||
const BUILD_DIR = join(homedir(), ".cache", "hyperframes", "whisper", "whisper.cpp");
|
||||
const BUILD_DIR = hyperframesCacheDir("whisper", "whisper.cpp");
|
||||
const WHISPER_REPO = "https://github.com/ggml-org/whisper.cpp.git";
|
||||
|
||||
function findBuiltBinary(): WhisperResult | undefined {
|
||||
@@ -168,10 +169,9 @@ async function buildFromSource(
|
||||
|
||||
if (!existsSync(BUILD_DIR)) {
|
||||
onProgress?.("Downloading whisper.cpp...");
|
||||
mkdirSync(join(homedir(), ".cache", "hyperframes", "whisper"), {
|
||||
recursive: true,
|
||||
});
|
||||
await stoppableExecFile("git", ["clone", "--depth", "1", WHISPER_REPO, BUILD_DIR], {
|
||||
mkdirSync(BUILD_DIR, { recursive: true });
|
||||
await stoppableExecFile("git", ["clone", "--depth", "1", WHISPER_REPO, "."], {
|
||||
cwd: BUILD_DIR,
|
||||
stdio: "ignore",
|
||||
timeout: 60_000,
|
||||
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
|
||||
|
||||
@@ -92,8 +92,8 @@ describe("installSherpaRuntime", () => {
|
||||
expect(await sherpaRuntimeLoadError(dir)).toBe(
|
||||
"libonnxruntime.so: cannot open shared object",
|
||||
);
|
||||
const run = vi.fn(async (args: string[]) =>
|
||||
fakeRuntime(args[args.indexOf("--prefix") + 1]!, LOADS),
|
||||
const run = vi.fn(async (_args: string[], _signal?: AbortSignal, cwd?: string) =>
|
||||
fakeRuntime(cwd!, LOADS),
|
||||
);
|
||||
|
||||
expect(await installSherpaRuntime({ run, dir })).toEqual({
|
||||
@@ -125,8 +125,8 @@ describe("installSherpaRuntime", () => {
|
||||
it("fails with the loader's error when the reinstalled runtime still does not load", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "hf-sherpa-runtime-"));
|
||||
try {
|
||||
const run = vi.fn(async (args: string[]) =>
|
||||
fakeRuntime(args[args.indexOf("--prefix") + 1]!, BROKEN),
|
||||
const run = vi.fn(async (_args: string[], _signal?: AbortSignal, cwd?: string) =>
|
||||
fakeRuntime(cwd!, BROKEN),
|
||||
);
|
||||
await expect(installSherpaRuntime({ run, dir })).rejects.toThrow(
|
||||
"The sherpa-onnx runtime was reinstalled but still does not load (libonnxruntime.so: cannot open shared object). Use --engine whisper for now.",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { execFile, spawn, type ExecFileException } from "node:child_process";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { mkdirSync, renameSync, rmSync, statSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { hyperframesCacheDir } from "@hyperframes/core/cache-dir";
|
||||
import { join } from "node:path";
|
||||
import { createInterface } from "node:readline";
|
||||
import { fileURLToPath } from "node:url";
|
||||
@@ -45,13 +45,7 @@ export const SHERPA_RUNTIME_DIR = join(
|
||||
|
||||
const MODEL_REPO = "csukuangfj/sherpa-onnx-nemo-parakeet-tdt-0.6b-v3-int8";
|
||||
const MODEL_REVISION = "2bda32ec70b097a55adaa07d9a7173915b43cc78";
|
||||
export const PARAKEET_MODEL_DIR = join(
|
||||
homedir(),
|
||||
".cache",
|
||||
"hyperframes",
|
||||
"parakeet",
|
||||
"parakeet-tdt-0.6b-v3-int8",
|
||||
);
|
||||
export const PARAKEET_MODEL_DIR = hyperframesCacheDir("parakeet", "parakeet-tdt-0.6b-v3-int8");
|
||||
|
||||
export type { ModelFile };
|
||||
|
||||
@@ -252,7 +246,7 @@ export async function installSherpaRuntime({
|
||||
// install() keeps any dir holding the runtime manifest, so a broken one goes first.
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
const native = sherpaPlatformPackage();
|
||||
await install(dir, RUNTIME, RUNTIME_VERSION, (args) => run([...args, native], signal));
|
||||
await install(dir, RUNTIME, RUNTIME_VERSION, (args, cwd) => run([...args, native], signal, cwd));
|
||||
const selected = await cachedRuntime(dir, 60_000, signal);
|
||||
if (selected.status === "unavailable") {
|
||||
throw new Error(
|
||||
|
||||
@@ -68,6 +68,12 @@
|
||||
"types": "./dist/atomicFile.d.ts",
|
||||
"environments": ["bun", "node"]
|
||||
},
|
||||
"./cache-dir": {
|
||||
"source": "./src/cacheDir.ts",
|
||||
"runtime": "./dist/cacheDir.js",
|
||||
"types": "./dist/cacheDir.d.ts",
|
||||
"environments": ["bun", "node"]
|
||||
},
|
||||
"./remove-tree": {
|
||||
"source": "./src/removeTree.ts",
|
||||
"runtime": "./dist/removeTree.js",
|
||||
|
||||
@@ -80,6 +80,12 @@
|
||||
"import": "./src/atomicFile.ts",
|
||||
"types": "./src/atomicFile.ts"
|
||||
},
|
||||
"./cache-dir": {
|
||||
"bun": "./src/cacheDir.ts",
|
||||
"node": "./dist/cacheDir.js",
|
||||
"import": "./src/cacheDir.ts",
|
||||
"types": "./src/cacheDir.ts"
|
||||
},
|
||||
"./remove-tree": {
|
||||
"bun": "./src/removeTree.ts",
|
||||
"node": "./dist/removeTree.js",
|
||||
@@ -662,6 +668,10 @@
|
||||
"import": "./dist/atomicFile.js",
|
||||
"types": "./dist/atomicFile.d.ts"
|
||||
},
|
||||
"./cache-dir": {
|
||||
"import": "./dist/cacheDir.js",
|
||||
"types": "./dist/cacheDir.d.ts"
|
||||
},
|
||||
"./remove-tree": {
|
||||
"import": "./dist/removeTree.js",
|
||||
"types": "./dist/removeTree.d.ts"
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { readdirSync, readFileSync } from "node:fs";
|
||||
import { homedir, tmpdir } from "node:os";
|
||||
import { join, resolve } from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { hyperframesCacheDir } from "./cacheDir.js";
|
||||
|
||||
afterEach(() => vi.unstubAllEnvs());
|
||||
|
||||
describe("hyperframesCacheDir", () => {
|
||||
it("defaults to ~/.cache/hyperframes", () => {
|
||||
vi.stubEnv("HYPERFRAMES_CACHE_DIR", "");
|
||||
expect(hyperframesCacheDir()).toBe(join(homedir(), ".cache", "hyperframes"));
|
||||
expect(hyperframesCacheDir("chrome")).toBe(join(homedir(), ".cache", "hyperframes", "chrome"));
|
||||
});
|
||||
|
||||
it("follows HYPERFRAMES_CACHE_DIR, read on each call, a relative one resolved", () => {
|
||||
const elsewhere = join(tmpdir(), "elsewhere-cache");
|
||||
vi.stubEnv("HYPERFRAMES_CACHE_DIR", elsewhere);
|
||||
expect(hyperframesCacheDir("whisper", "models")).toBe(join(elsewhere, "whisper", "models"));
|
||||
vi.stubEnv("HYPERFRAMES_CACHE_DIR", "rel/cache");
|
||||
expect(hyperframesCacheDir("tts")).toBe(join(resolve("rel/cache"), "tts"));
|
||||
});
|
||||
});
|
||||
|
||||
/** Source files under every package's src, tests and test helpers left out. */
|
||||
function sources(dir: string): string[] {
|
||||
return readdirSync(dir, { recursive: true, withFileTypes: true })
|
||||
.filter((entry) => entry.isFile() && /\.(m?[jt]sx?)$/.test(entry.name))
|
||||
.filter((entry) => !/\.test[.-]|__tests__/.test(join(entry.parentPath, entry.name)))
|
||||
.map((entry) => join(entry.parentPath, entry.name));
|
||||
}
|
||||
|
||||
it("every cache path in every package goes through hyperframesCacheDir", () => {
|
||||
const packages = resolve(__dirname, "..", "..");
|
||||
const built: string[] = [];
|
||||
for (const name of readdirSync(packages)) {
|
||||
let files: string[];
|
||||
try {
|
||||
files = sources(join(packages, name, "src"));
|
||||
} catch {
|
||||
continue; // a package without src
|
||||
}
|
||||
for (const file of files) {
|
||||
if (file.endsWith(join("core", "src", "cacheDir.ts"))) continue;
|
||||
// Prose about the default path is fine; code that builds it is not.
|
||||
const code = readFileSync(file, "utf8")
|
||||
.split("\n")
|
||||
.filter((line) => !/^\s*(\/\/|\*|\/\*)/.test(line))
|
||||
.join("\n");
|
||||
if (
|
||||
/["'`]\.cache["'`]\s*,\s*["'`]hyperframes["'`]|["'`][^"'`\n]*\.cache\/hyperframes/.test(
|
||||
code,
|
||||
)
|
||||
)
|
||||
built.push(file.slice(packages.length + 1));
|
||||
}
|
||||
}
|
||||
expect(built).toEqual([]);
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import { homedir } from "node:os";
|
||||
import { join, resolve } from "node:path";
|
||||
|
||||
export function hyperframesCacheDir(...parts: string[]): string {
|
||||
const named = process.env["HYPERFRAMES_CACHE_DIR"];
|
||||
return join(named ? resolve(named) : join(homedir(), ".cache", "hyperframes"), ...parts);
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
rmSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { homedir, tmpdir } from "node:os";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { FONT_ALIAS_MAP, resolveAliasDisplayName } from "./aliases.js";
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
type FontDiagnostics,
|
||||
type FontDiagPath,
|
||||
} from "./fontDiagnostics.js";
|
||||
import { hyperframesCacheDir } from "../cacheDir.js";
|
||||
|
||||
export type { FontDiagnostics };
|
||||
|
||||
@@ -1123,7 +1124,7 @@ function resolveFontCacheRoot(): string {
|
||||
lambdaFontCacheRoot ??= mkdtempSync(join(tmpdir(), "hyperframes-fonts-"));
|
||||
return lambdaFontCacheRoot;
|
||||
}
|
||||
return join(homedir(), ".cache", "hyperframes", "fonts");
|
||||
return hyperframesCacheDir("fonts");
|
||||
}
|
||||
|
||||
// Chrome UA triggers woff2 responses from Google Fonts CSS API
|
||||
|
||||
@@ -10,8 +10,9 @@ import {
|
||||
rmSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { homedir, tmpdir } from "node:os";
|
||||
import { tmpdir } from "node:os";
|
||||
import { dirname, join } from "node:path";
|
||||
import { hyperframesCacheDir } from "../cacheDir.js";
|
||||
|
||||
const { compress } = wawoff2 as {
|
||||
compress: (input: Buffer | Uint8Array) => Promise<Uint8Array>;
|
||||
@@ -41,7 +42,7 @@ function defaultCacheDir(): string {
|
||||
process.env.HYPERFRAMES_FONT_CACHE_DIR ??
|
||||
(process.env.AWS_LAMBDA_FUNCTION_NAME
|
||||
? join(tmpdir(), "hyperframes", "fonts")
|
||||
: join(homedir(), ".cache", "hyperframes", "fonts"));
|
||||
: hyperframesCacheDir("fonts"));
|
||||
return join(root, "local-compression-v1");
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ export type {
|
||||
BrowserPoolState,
|
||||
CaptureMode,
|
||||
} from "./browserLeasePool.js";
|
||||
import { hyperframesCacheDir } from "@hyperframes/core/cache-dir";
|
||||
|
||||
let _puppeteer: PuppeteerNode | undefined;
|
||||
|
||||
@@ -271,7 +272,7 @@ export function resolveHeadlessShellPath(
|
||||
}
|
||||
return fromEnv.path;
|
||||
}
|
||||
const ours = join(homedir(), ".cache", "hyperframes", "chrome", "chrome-headless-shell");
|
||||
const ours = hyperframesCacheDir("chrome", "chrome-headless-shell");
|
||||
const shared = join(homedir(), ".cache", "puppeteer", "chrome-headless-shell");
|
||||
return (
|
||||
findManagedHeadlessShell(ours) ??
|
||||
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
readdirSync,
|
||||
} from "node:fs";
|
||||
import { resolve, join, extname, sep } from "node:path";
|
||||
import { homedir } from "node:os";
|
||||
import { getHyperframeRuntimeScript } from "../../core/src/generated/runtime-inline";
|
||||
import puppeteer, { type Browser, type Page } from "puppeteer-core";
|
||||
import {
|
||||
@@ -32,6 +31,7 @@ import {
|
||||
import { closeServer } from "./pageServer";
|
||||
import { Sources, fingerprint } from "./sources";
|
||||
import type { InspectRequest, Snippet } from "./types";
|
||||
import { hyperframesCacheDir } from "../../core/src/cacheDir";
|
||||
|
||||
const pageRunner = (page: Page): PageRunner => ({
|
||||
run: <A, R>(fn: (arg: A) => R, arg: A) =>
|
||||
@@ -59,7 +59,7 @@ function chromePath() {
|
||||
"Set HF_INSPECT_CHROME to an installed Chrome executable; inspection does not download a browser.",
|
||||
);
|
||||
}
|
||||
const root = join(homedir(), ".cache/hyperframes/chrome/chrome-headless-shell");
|
||||
const root = hyperframesCacheDir("chrome", "chrome-headless-shell");
|
||||
if (existsSync(root))
|
||||
for (const version of readdirSync(root).sort().reverse()) {
|
||||
const dir = join(root, version);
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
realpathSync,
|
||||
rmSync,
|
||||
} from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
||||
import { replaceFileAtomically } from "@hyperframes/core/atomic-file";
|
||||
import {
|
||||
@@ -64,9 +63,10 @@ import {
|
||||
type LogRecord,
|
||||
type Manifest,
|
||||
} from "./historyLog.js";
|
||||
import { hyperframesCacheDir } from "@hyperframes/core/cache-dir";
|
||||
|
||||
/** Where hosts keep project histories unless told otherwise: outside every project, so no tidy-up takes one away. */
|
||||
export const DEFAULT_HISTORY_ROOT = join(homedir(), ".cache", "hyperframes", "history");
|
||||
export const DEFAULT_HISTORY_ROOT = hyperframesCacheDir("history");
|
||||
|
||||
export interface ProjectHistoryOptions {
|
||||
projectDir: string;
|
||||
|
||||
Reference in New Issue
Block a user