Merge pull request #2174 from heygen-com/07-10-refactor-repo-resolve-changed-code-audit

refactor(repo): resolve changed-code audit
This commit is contained in:
James Russo
2026-07-20 15:54:44 -04:00
committed by GitHub
21 changed files with 54 additions and 51 deletions
+3
View File
@@ -271,6 +271,7 @@ const commandStart = Date.now();
const runId = getRunId();
let finalized = false;
// Root-only lifecycle fan-in: telemetry, notices, flushing, then exit code.
// fallow-ignore-next-line complexity
async function finalizeCli(result: CommandResult): Promise<void> {
if (finalized) return;
@@ -437,6 +438,8 @@ function commandResultForError(error: unknown): CommandResult {
return { exitCode: 1, kind: "runtime_error" };
}
// Root-only command boundary; keeping every result path here prevents modules
// from bypassing output, telemetry, or finalizers.
// fallow-ignore-next-line complexity
async function executeCli(): Promise<void> {
let result: CommandResult = { exitCode: 0, kind: "success" };
+5 -16
View File
@@ -1,8 +1,8 @@
import { promises as fs } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { readStore, writeStore } from "../../auth/store.js";
import { setupTempAuthEnv, type EnvFixture } from "../../auth/_test-utils.js";
import { CliRuntimeError } from "../../utils/commandResult.js";
// Mock only AuthClient — keep the real store/resolver so the test
@@ -43,19 +43,13 @@ const telemetry = vi.hoisted(() => ({
}));
vi.mock("../../telemetry/index.js", () => telemetry);
const ENV_KEYS = ["HEYGEN_API_KEY", "HYPERFRAMES_API_KEY", "HEYGEN_CONFIG_DIR"] as const;
describe("auth login --api-key rollback", () => {
let dir: string;
const saved: Partial<Record<(typeof ENV_KEYS)[number], string | undefined>> = {};
let envFixture: EnvFixture;
beforeEach(async () => {
dir = await fs.mkdtemp(join(tmpdir(), "hf-login-"));
for (const k of ENV_KEYS) {
saved[k] = process.env[k];
delete process.env[k];
}
process.env["HEYGEN_CONFIG_DIR"] = dir;
envFixture = await setupTempAuthEnv("hf-login-");
dir = envFixture.dir;
verifyState.reject = false;
verifyState.user = { email: "alice@example.com" };
for (const fn of Object.values(telemetry)) fn.mockClear();
@@ -65,12 +59,7 @@ describe("auth login --api-key rollback", () => {
afterEach(async () => {
vi.restoreAllMocks();
for (const k of ENV_KEYS) {
const v = saved[k];
if (v === undefined) delete process.env[k];
else process.env[k] = v;
}
await fs.rm(dir, { recursive: true, force: true });
await envFixture.restore();
});
async function runLogin(apiKey: string): Promise<void> {
@@ -1,9 +1,6 @@
// fallow-ignore-file code-duplication
import { promises as fs } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { writeStore } from "../../auth/store.js";
import { setupTempAuthEnv, type EnvFixture } from "../../auth/_test-utils.js";
import { consumeCommandResult } from "../../utils/commandResult.js";
// Mock only AuthClient so the live /v3/users/me probe is controllable;
@@ -33,20 +30,12 @@ vi.mock("../../auth/index.js", async (orig) => {
return { ...actual, AuthClient: MockAuthClient };
});
const ENV_KEYS = ["HEYGEN_API_KEY", "HYPERFRAMES_API_KEY", "HEYGEN_CONFIG_DIR"] as const;
describe("auth status — persisted user block surface", () => {
let dir: string;
const saved: Partial<Record<(typeof ENV_KEYS)[number], string | undefined>> = {};
let envFixture: EnvFixture;
let stdout: string[];
beforeEach(async () => {
dir = await fs.mkdtemp(join(tmpdir(), "hf-status-"));
for (const k of ENV_KEYS) {
saved[k] = process.env[k];
delete process.env[k];
}
process.env["HEYGEN_CONFIG_DIR"] = dir;
envFixture = await setupTempAuthEnv("hf-status-");
probeState.apiReject = false;
probeState.user = { email: "live@example.com" };
stdout = [];
@@ -60,12 +49,7 @@ describe("auth status — persisted user block surface", () => {
afterEach(async () => {
consumeCommandResult();
vi.restoreAllMocks();
for (const k of ENV_KEYS) {
const v = saved[k];
if (v === undefined) delete process.env[k];
else process.env[k] = v;
}
await fs.rm(dir, { recursive: true, force: true });
await envFixture.restore();
});
async function runStatus(asJson: boolean): Promise<number> {
+14 -12
View File
@@ -525,6 +525,18 @@ async function runSites(args: Record<string, unknown>): Promise<void> {
// ── render ──────────────────────────────────────────────────────────────────
function resolveCloudRunFps(
args: Record<string, unknown>,
projectDir: string,
command: "render" | "render-batch",
): 24 | 30 | 60 {
const fps =
parseIntFlag(args.fps) ?? readAllowedCompositionFpsFromDir(projectDir, [24, 30, 60]) ?? 30;
if (fps === 24 || fps === 30 || fps === 60) return fps;
console.error(`[cloudrun ${command}] --fps must be 24, 30, or 60; got ${fps}.`);
failCommand();
}
// fallow-ignore-next-line complexity
async function runRender(args: Record<string, unknown>): Promise<void> {
const projectDir = args.target as string | undefined;
@@ -540,12 +552,7 @@ async function runRender(args: Record<string, unknown>): Promise<void> {
console.error("[cloudrun render] --width and --height are required.");
failCommand();
}
const fps =
parseIntFlag(args.fps) ?? readAllowedCompositionFpsFromDir(projectDir, [24, 30, 60]) ?? 30;
if (fps !== 24 && fps !== 30 && fps !== 60) {
console.error(`[cloudrun render] --fps must be 24, 30, or 60; got ${fps}.`);
failCommand();
}
const fps = resolveCloudRunFps(args, projectDir, "render");
const state = readState(args);
const variables = resolveAndValidateVariables(args, resolve(projectDir));
const config = buildRenderConfig(args, fps, width, height, variables);
@@ -651,12 +658,7 @@ async function runRenderBatch(args: Record<string, unknown>): Promise<void> {
console.error("[cloudrun render-batch] --width and --height are required.");
failCommand();
}
const fps =
parseIntFlag(args.fps) ?? readAllowedCompositionFpsFromDir(projectDir, [24, 30, 60]) ?? 30;
if (fps !== 24 && fps !== 30 && fps !== 60) {
console.error(`[cloudrun render-batch] --fps must be 24, 30, or 60; got ${fps}.`);
failCommand();
}
const fps = resolveCloudRunFps(args, projectDir, "render-batch");
if (!existsSync(resolve(batchPath))) {
console.error(`[cloudrun render-batch] batch file not found: ${batchPath}`);
failCommand();
+1
View File
@@ -5,6 +5,7 @@ import { mkdirSync, readdirSync, readFileSync, statSync, writeFileSync, rmSync }
import { createRenderPlan, resolveBrowserGpuForCli, type RenderFormat } from "./render/plan.js";
import { presentRenderPlan } from "./render/present.js";
import { executeRenderPlan, renderLintContinuationHint } from "./render/execute.js";
// Test-only seams retained at the command boundary for render behavior tests.
export { resolveBrowserGpuForCli, renderLintContinuationHint };
export const examples: Example[] = [
@@ -33,6 +33,8 @@ export interface RenderExecutionDependencies {
checkResolution: ResolutionPreflight;
}
// Exported only through render.ts so command tests can lock the user-facing guidance.
// fallow-ignore-next-line unused-export
export function renderLintContinuationHint(strictErrors: boolean): string {
return strictErrors
? " Continuing render despite lint warnings. Use --strict-all to block warnings."
@@ -156,6 +158,7 @@ async function ensureRenderBrowser(plan: RenderPlan): Promise<string> {
}
}
// fallow-ignore-next-line complexity
async function runRenderLint(plan: RenderPlan): Promise<void> {
// lintProject's explicit-entry contract is an absolute source path;
// entryFile remains project-relative for the producer.
+2
View File
@@ -454,6 +454,8 @@ export function renderOutputDirectory(plan: RenderPlan): string {
}
/** Resolve browser GPU mode from Docker, CLI, env, then the auto default. */
// Re-exported by render.ts to preserve its tested public seam.
// fallow-ignore-next-line unused-export
export function resolveBrowserGpuForCli(
useDocker: boolean,
browserGpuArg: boolean | undefined,
@@ -5,6 +5,8 @@ import { c } from "../../ui/colors.js";
import type { RenderPlan } from "./plan.js";
/** Present warnings and the human render plan. JSON batch output stays silent. */
// This phase intentionally owns every mutually exclusive human-output branch.
// fallow-ignore-next-line complexity
export async function presentRenderPlan(plan: RenderPlan): Promise<void> {
await presentRenderWarnings(plan);
if (plan.effectiveQuiet || plan.batchPath) return;
@@ -1,3 +1,4 @@
// fallow-ignore-file code-duplication
import { describe, expect, it, vi } from "vitest";
import {
RenderQualityError,
+1
View File
@@ -1,3 +1,4 @@
// fallow-ignore-file code-duplication
/**
* WS-C getElementTimings / setElementTiming / setHold tests.
*
@@ -5,7 +5,7 @@ import { CaptionPropertyPanel } from "../captions/components/CaptionPropertyPane
import { BlockParamsPanel } from "./editor/BlockParamsPanel";
import { RenderQueue } from "./renders/RenderQueue";
import { SlideshowPanel } from "./panels/SlideshowPanel";
import { VariablesPanel } from "./panels/VariablesPanel";
import { VariablesPanel, type StudioEditPersistenceProps } from "./panels/VariablesPanel";
import { PanelTabButton } from "./PanelTabButton";
import { usePreviewVariablesStore } from "../hooks/previewVariablesStore";
import type { RenderJob } from "./renders/useRenderQueue";
@@ -35,7 +35,7 @@ import type { BackgroundRemovalProgress } from "./editor/propertyPanelTypes";
import { timelineKeysForSelections, type ToggleHiddenHandler } from "../utils/studioHelpers";
import { useInspectorSplitResize } from "../hooks/useInspectorSplitResize";
export interface StudioRightPanelProps {
export interface StudioRightPanelProps extends StudioEditPersistenceProps {
designPanelActive: boolean;
activeBlockParams?: {
blockName: string;
@@ -31,7 +31,7 @@ function shellSingleQuote(value: string): string {
return `'${value.replace(/'/g, `'\\''`)}'`;
}
interface VariablesPanelProps {
export interface StudioEditPersistenceProps {
sdkSession: Composition | null;
publishSdkSession: PublishSdkSession;
reloadPreview: () => void;
@@ -43,6 +43,8 @@ interface VariablesPanelProps {
}) => Promise<void>;
}
type VariablesPanelProps = StudioEditPersistenceProps;
function formatIssue(issue: VariableValidationIssue): string {
switch (issue.kind) {
case "undeclared":
@@ -17,6 +17,7 @@ export function useFileManagerContextOptional(): FileManagerValue | null {
export function FileManagerProvider({
value: {
// fallow-ignore-next-line code-duplication
editingFile,
setEditingFile,
projectDir,
@@ -25,6 +25,7 @@ function renderKeyframeOps(over: {
}) {
const captured: { api: HookApi | null } = { api: null };
function Probe() {
// fallow-ignore-next-line code-duplication
captured.api = useGsapKeyframeOps({
activeCompPath: "index.html",
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- test doubles
@@ -1,3 +1,5 @@
// fallow-ignore-file code-duplication
// Add/remove operation-family transaction shapes stay parallel until SDK graduation.
import { useCallback } from "react";
import type { GsapAnimation } from "@hyperframes/core/gsap-parser";
import type { Composition } from "@hyperframes/sdk";
@@ -84,6 +84,7 @@ export function useGsapPropertyDebounce(
const mutation = { type: "update-property", animationId, property, value };
const label = `Edit GSAP ${property}`;
try {
// fallow-ignore-next-line code-duplication
const { sdkSession, sdkDeps, activeCompPath } = sdkRef.current ?? {};
if (sdkSession && sdkDeps) {
const targetPath = selection.sourceFile || activeCompPath || "index.html";
@@ -9,6 +9,7 @@ import type { RecordEditInput } from "./timelineEditingHelpers";
interface UseRazorSplitOptions {
projectId: string | null;
// fallow-ignore-next-line code-duplication
activeCompPath: string | null;
showToast: (message: string, tone?: "error" | "info") => void;
writeProjectFile: (path: string, content: string, expectedContent?: string) => Promise<void>;
@@ -1,3 +1,4 @@
// fallow-ignore-file code-duplication
// @vitest-environment happy-dom
import React, { act, useRef } from "react";
@@ -1,3 +1,5 @@
// fallow-ignore-file code-duplication
// Move/resize operation families remain parallel until SDK graduation.
import { useCallback, type MutableRefObject, type RefObject } from "react";
import type { Composition } from "@hyperframes/sdk";
import type { TimelineElement } from "../player";
@@ -10,6 +10,7 @@ import {
persistSdkCandidateMutation,
persistSdkSerialize,
} from "./sdkCutover";
// fallow-ignore-file code-duplication
import { openComposition } from "@hyperframes/sdk";
import { createMemoryAdapter } from "@hyperframes/sdk/adapters/memory";
import type { PatchOperation } from "./sourcePatcher";
+3
View File
@@ -10,6 +10,8 @@ export function listDirectProcessTermination(source, filename = "source.ts") {
const sourceFile = ts.createSourceFile(filename, source, ts.ScriptTarget.Latest, true);
const issues = [];
// AST traversal mirrors the small set of process-termination spellings.
// fallow-ignore-next-line complexity
function visit(node) {
if (
ts.isPropertyAccessExpression(node) &&
@@ -30,6 +32,7 @@ export function listDirectProcessTermination(source, filename = "source.ts") {
}
function listTypeScriptFiles(directory, rootCliPath) {
// fallow-ignore-next-line complexity
return readdirSync(directory).flatMap((name) => {
const path = join(directory, name);
if (statSync(path).isDirectory()) return listTypeScriptFiles(path, rootCliPath);