mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 14:50:02 +00:00
## Changes
- Add optional `entryFile` parameter to render API endpoints (`/v1/render` and `/v1/render-stream`)
- Enable rendering individual sub-compositions by extracting them from index.html context when the entry file is a `<template>` wrapper
- Change base64 audio/video linting from detecting "fabricated" media to prohibiting all inline base64 media
- Add manifest path resolution for bundled producer deployments
## API Changes
- `RenderConfig` — new optional `entryFile` field for specifying HTML file to render
- `server.ts` — parses `entryFile` from request body, validates file exists in project directory
- `executeRenderJob` — uses `entryFile` instead of hardcoded `"index.html"`
## Template Extraction
- `extractStandaloneEntryFromIndex` — extracts sub-composition hosts from index.html and creates standalone render context
- Handles `<template>` entry files by finding matching `data-composition-src` in index.html and isolating that host
- Resets `data-start` to 0 for standalone rendering
## Linting Updates
- Change rule #3.7 from detecting "fabricated" base64 media to prohibiting all inline base64 audio/video
- Lower detection threshold from 100+ to 20+ base64 characters
- All base64 media now triggers error severity with clearer messaging about file size bloat
## Usage
```json
POST /v1/render-stream
{ "projectDir": "/path/to/project", "entryFile": "compositions/intro.html" }
```
Omit `entryFile` for default behavior (renders `index.html`).
94 lines
3.0 KiB
TypeScript
94 lines
3.0 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import { existsSync, readFileSync } from "node:fs";
|
|
import { dirname, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const PRODUCER_DIR = dirname(fileURLToPath(import.meta.url));
|
|
const SIBLING_MANIFEST_PATH = resolve(PRODUCER_DIR, "hyperframe.manifest.json");
|
|
const MODULE_RELATIVE_MANIFEST_PATH = resolve(
|
|
PRODUCER_DIR,
|
|
"../../../core/dist/hyperframe.manifest.json",
|
|
);
|
|
const CWD_RELATIVE_MANIFEST_PATHS = [
|
|
// When bundled to a single file (dist/public-server.js), the manifest
|
|
// is copied as a sibling by build.mjs
|
|
resolve(PRODUCER_DIR, "hyperframe.manifest.json"),
|
|
resolve(process.cwd(), "packages/core/dist/hyperframe.manifest.json"),
|
|
resolve(process.cwd(), "../core/dist/hyperframe.manifest.json"),
|
|
resolve(process.cwd(), "core/dist/hyperframe.manifest.json"),
|
|
];
|
|
|
|
type HyperframeRuntimeManifest = {
|
|
sha256?: string;
|
|
artifacts?: {
|
|
iife?: string;
|
|
};
|
|
};
|
|
|
|
export type ResolvedHyperframeRuntime = {
|
|
manifestPath: string;
|
|
runtimePath: string;
|
|
expectedSha256: string;
|
|
actualSha256: string;
|
|
runtimeSource: string;
|
|
};
|
|
|
|
export function resolveHyperframeManifestPath(): string {
|
|
if (process.env.PRODUCER_HYPERFRAME_MANIFEST_PATH) {
|
|
return process.env.PRODUCER_HYPERFRAME_MANIFEST_PATH;
|
|
}
|
|
const candidates = [
|
|
SIBLING_MANIFEST_PATH,
|
|
...CWD_RELATIVE_MANIFEST_PATHS,
|
|
MODULE_RELATIVE_MANIFEST_PATH,
|
|
];
|
|
for (const candidate of candidates) {
|
|
if (existsSync(candidate)) {
|
|
return candidate;
|
|
}
|
|
}
|
|
return MODULE_RELATIVE_MANIFEST_PATH;
|
|
}
|
|
|
|
export function getVerifiedHyperframeRuntimeSource(): string {
|
|
return resolveVerifiedHyperframeRuntime().runtimeSource;
|
|
}
|
|
|
|
export function resolveVerifiedHyperframeRuntime(): ResolvedHyperframeRuntime {
|
|
const manifestPath = resolveHyperframeManifestPath();
|
|
if (!existsSync(manifestPath)) {
|
|
throw new Error(
|
|
`[HyperframeRuntimeLoader] Missing manifest at ${manifestPath}. Build core runtime artifacts before rendering.`,
|
|
);
|
|
}
|
|
|
|
const manifestRaw = readFileSync(manifestPath, "utf8");
|
|
const manifest = JSON.parse(manifestRaw) as HyperframeRuntimeManifest;
|
|
const runtimeFileName = manifest.artifacts?.iife;
|
|
if (!runtimeFileName || !manifest.sha256) {
|
|
throw new Error(
|
|
`[HyperframeRuntimeLoader] Invalid manifest at ${manifestPath}; missing iife artifact or sha256.`,
|
|
);
|
|
}
|
|
|
|
const runtimePath = resolve(dirname(manifestPath), runtimeFileName);
|
|
if (!existsSync(runtimePath)) {
|
|
throw new Error(`[HyperframeRuntimeLoader] Missing runtime artifact at ${runtimePath}.`);
|
|
}
|
|
|
|
const runtimeSource = readFileSync(runtimePath, "utf8");
|
|
const runtimeSha = createHash("sha256").update(runtimeSource, "utf8").digest("hex");
|
|
if (runtimeSha !== manifest.sha256) {
|
|
throw new Error(
|
|
`[HyperframeRuntimeLoader] Runtime checksum mismatch. expected=${manifest.sha256} actual=${runtimeSha}`,
|
|
);
|
|
}
|
|
return {
|
|
manifestPath,
|
|
runtimePath,
|
|
expectedSha256: manifest.sha256,
|
|
actualSha256: runtimeSha,
|
|
runtimeSource,
|
|
};
|
|
}
|