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:
Miguel Ángel
2026-11-01 22:44:11 +00:00
committed by GitHub
parent 57d23598a7
commit de6ec3811e
26 changed files with 196 additions and 74 deletions
+3
View File
@@ -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;
+3 -2
View File
@@ -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
+4 -3
View File
@@ -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");
+2 -2
View File
@@ -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;
+2 -2
View File
@@ -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 -2
View File
@@ -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 {
+18 -17
View File
@@ -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);
+2 -1
View File
@@ -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 });
}
});
+7 -7
View File
@@ -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" },
+4 -4
View File
@@ -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.",
+3 -9
View File
@@ -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(
+6
View File
@@ -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",
+10
View File
@@ -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"
+59
View File
@@ -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([]);
});
+7
View File
@@ -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
+3 -2
View File
@@ -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) ??
+2 -2
View File
@@ -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;