Files
hyperframes/packages/cli/src/server/runtimeSource.ts
T

99 lines
3.3 KiB
TypeScript

import { createHash } from "node:crypto";
import { existsSync, readFileSync } from "node:fs";
import { resolve, dirname } from "node:path";
const ARTIFACT_NAMES = ["hyperframe-runtime.js", "hyperframe.runtime.iife.js"];
/**
* Resolve the runtime JS source for the studio preview server.
*
* Three resolution strategies, in priority order:
*
* 1. esbuild from source (dev only — gated on entry.ts existence)
* 2. Inlined constant (production — baked into @hyperframes/core at build time)
* 3. Pre-built artifact (fallback — reads IIFE file from dist/)
*/
export async function loadRuntimeSource(): Promise<string | null> {
return (await buildFromSource()) ?? (await getInlinedRuntime()) ?? readPrebuiltArtifact();
}
export async function loadRuntimeSourceSignature(): Promise<string | null> {
const source = await loadRuntimeSource();
if (!source) return null;
return createHash("sha256").update(source).digest("hex");
}
export function hashSignatureParts(parts: Array<string | null | undefined>): string {
const hash = createHash("sha256");
for (const part of parts) {
hash.update(part ?? "");
hash.update("\n--hf-signature-part--\n");
}
return hash.digest("hex");
}
// ── Strategy 1: live build from source (dev only) ──────────────────────────
const ENTRY_TS = resolve(__dirname, "..", "..", "..", "core", "src", "runtime", "entry.ts");
async function buildFromSource(): Promise<string | null> {
if (!existsSync(ENTRY_TS)) return null;
try {
const mod = await import("@hyperframes/core");
if (typeof mod.loadHyperframeRuntimeSource === "function") {
const source = mod.loadHyperframeRuntimeSource();
if (source) return source;
}
} catch {
// esbuild failed — fall through to inlined / artifact
}
return null;
}
// ── Strategy 2: inlined constant from core build ──────────────────────────
async function getInlinedRuntime(): Promise<string | null> {
try {
const mod = await import("@hyperframes/core");
if (typeof mod.getHyperframeRuntimeScript === "function") {
return mod.getHyperframeRuntimeScript() ?? null;
}
} catch {
// Not available — fall through to artifact
}
return null;
}
// ── Strategy 3: pre-built IIFE artifact ───────────────────────────────────
function readPrebuiltArtifact(): string | null {
return readFromDir(__dirname) ?? readFromCoreDistDir() ?? readFromNodeModules();
}
function readFromDir(dir: string): string | null {
for (const name of ARTIFACT_NAMES) {
const path = resolve(dir, name);
if (existsSync(path)) return readFileSync(path, "utf-8");
}
return null;
}
function readFromCoreDistDir(): string | null {
return readFromDir(resolve(__dirname, "..", "..", "..", "core", "dist"));
}
function readFromNodeModules(): string | null {
const subPaths = ["node_modules/hyperframes/dist", "node_modules/@hyperframes/core/dist"];
let dir = __dirname;
for (;;) {
for (const sub of subPaths) {
const result = readFromDir(resolve(dir, sub));
if (result) return result;
}
const parent = dirname(dir);
if (parent === dir) break;
dir = parent;
}
return null;
}