mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-01 19:42:03 +00:00
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:
@@ -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" };
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,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";
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user