mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-03 04:07:03 +00:00
fix(cli): preview inside a host app's agent run no longer outlives the run (#5852)
A background preview detaches, so one started by an agent inside a host app's run kept running after the run and the app ended, and held the project's history lock for its whole life. Inside a host app's run, preview now refuses to start in the background and points to the host app's preview or --foreground, which stays attached to the run.
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
prebuildPreview,
|
||||
handlePreviewKillAll,
|
||||
handlePreviewList,
|
||||
previewHostAppError,
|
||||
previewLaunchMode,
|
||||
previewLaunchModeError,
|
||||
previewPortError,
|
||||
@@ -601,3 +602,36 @@ describe("studio dev-server spawns", () => {
|
||||
expect(spawn.mock.calls[0]?.[2]).toMatchObject({ windowsHide: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe("preview inside a host app's agent run", () => {
|
||||
const hostRun = { HYPERFRAMES_DESKTOP_PROJECT: "/projects/demo" };
|
||||
|
||||
it("refuses a background preview, which would outlive the run", () => {
|
||||
expect(previewHostAppError("background", hostRun)).toMatch(/host app .* owns its preview/);
|
||||
expect(previewHostAppError("background", hostRun)).toContain("--foreground");
|
||||
});
|
||||
|
||||
it("allows a preview attached to the run, and background outside a host app", () => {
|
||||
expect(previewHostAppError("embedded", hostRun)).toBeNull();
|
||||
expect(previewHostAppError("local", hostRun)).toBeNull();
|
||||
expect(previewHostAppError("background", {})).toBeNull();
|
||||
});
|
||||
|
||||
it("refuses before spawning anything", async () => {
|
||||
vi.stubEnv("HYPERFRAMES_DESKTOP_PROJECT", "/projects/demo");
|
||||
const project = mkdtempSync(join(tmpdir(), "hf-host-preview-"));
|
||||
writeFileSync(join(project, "index.html"), "<html><body></body></html>");
|
||||
const log = vi.spyOn(console, "log").mockImplementation(() => {});
|
||||
let printed = "";
|
||||
try {
|
||||
await runCommand(previewCommand, { rawArgs: [project, "--background", "--json"] });
|
||||
printed = String(log.mock.calls.at(-1)?.[0]);
|
||||
} finally {
|
||||
log.mockRestore();
|
||||
vi.unstubAllEnvs();
|
||||
rmSync(project, { recursive: true, force: true });
|
||||
}
|
||||
const payload = JSON.parse(printed);
|
||||
expect(payload).toMatchObject({ ok: false, error: { code: "host-app-owns-preview" } });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -39,7 +39,7 @@ import { fileURLToPath } from "node:url";
|
||||
import { createRequire } from "node:module";
|
||||
import * as clack from "@clack/prompts";
|
||||
import { c } from "../ui/colors.js";
|
||||
import { desktopHint } from "../utils/desktopApp.js";
|
||||
import { desktopHint, insideHostApp } from "../utils/desktopApp.js";
|
||||
import { isDevMode } from "../utils/env.js";
|
||||
import { normalizeErrorMessage as errorMessage } from "../utils/errorMessage.js";
|
||||
import { buildNpxCommand } from "../utils/npxCommand.js";
|
||||
@@ -488,9 +488,6 @@ export default defineCommand({
|
||||
);
|
||||
}
|
||||
|
||||
// Runs in the CLI worker while Studio starts, so it never delays the opening; --json skips it.
|
||||
const startupLint = args.json ? null : printStartupLint(dir, Boolean(args["lint-verbose"]));
|
||||
|
||||
const launchMode = previewLaunchMode({
|
||||
background: Boolean(args.background),
|
||||
foreground: Boolean(args.foreground),
|
||||
@@ -499,6 +496,15 @@ export default defineCommand({
|
||||
localStudio: hasLocalStudio(dir),
|
||||
});
|
||||
|
||||
const hostAppError = previewHostAppError(launchMode);
|
||||
if (hostAppError) {
|
||||
reportPreviewFailure(Boolean(args.json), "start", "host-app-owns-preview", hostAppError);
|
||||
return;
|
||||
}
|
||||
|
||||
// Runs in the CLI worker while Studio starts, so it never delays the opening; --json skips it.
|
||||
const startupLint = args.json ? null : printStartupLint(dir, Boolean(args["lint-verbose"]));
|
||||
|
||||
if (launchMode === "background") {
|
||||
let background;
|
||||
try {
|
||||
@@ -643,6 +649,18 @@ export function previewLaunchMode(options: {
|
||||
return options.localStudio ? "local" : "embedded";
|
||||
}
|
||||
|
||||
export function previewHostAppError(
|
||||
mode: PreviewLaunchMode,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): string | null {
|
||||
if (mode !== "background" || !insideHostApp(env)) return null;
|
||||
return (
|
||||
"A host app is running this project and owns its preview, so a background preview " +
|
||||
"here would outlive this run and keep the project's history locked. Use the host " +
|
||||
"app's preview, or run with --foreground to keep a preview attached to this run."
|
||||
);
|
||||
}
|
||||
|
||||
export function previewLaunchModeError(options: {
|
||||
background: boolean;
|
||||
foreground: boolean;
|
||||
|
||||
@@ -221,6 +221,10 @@ export function desktopInstalled({
|
||||
return DESKTOP_BUNDLE_IDS.some(spotlight);
|
||||
}
|
||||
|
||||
export function insideHostApp(env: NodeJS.ProcessEnv = process.env): boolean {
|
||||
return Boolean(env.HYPERFRAMES_DESKTOP_PROJECT);
|
||||
}
|
||||
|
||||
/** The app line render and preview print; null inside the app, or with nothing here to open or download. */
|
||||
export function desktopHint(
|
||||
dir: string,
|
||||
@@ -236,7 +240,7 @@ export function desktopHint(
|
||||
platform?: NodeJS.Platform;
|
||||
} = {},
|
||||
): string | null {
|
||||
if (env.HYPERFRAMES_DESKTOP_PROJECT) return null;
|
||||
if (insideHostApp(env)) return null;
|
||||
if (ready && (installed ?? desktopInstalled({ platform, env })))
|
||||
return `Keep editing by chatting with Framey in the desktop app: ${openCommandFor(dir)}`;
|
||||
const url = desktopDownloadUrl(platform);
|
||||
|
||||
Reference in New Issue
Block a user