diff --git a/packages/cli/src/utils/compositionServer.ts b/packages/cli/src/utils/compositionServer.ts index 13b6ae4309..24c46a5036 100644 --- a/packages/cli/src/utils/compositionServer.ts +++ b/packages/cli/src/utils/compositionServer.ts @@ -3,6 +3,7 @@ // composition asset files, and binding to a free port. import { existsSync } from "node:fs"; import { resolve, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; /** Minimal surface of a listening server (satisfied by @hono/node-server's ServerType). */ interface PortBindable { @@ -15,7 +16,9 @@ interface PortBindable { } function helperDir(): string { - return dirname(new URL(import.meta.url).pathname); + // fileURLToPath (not URL.pathname) so the Windows "/D:/..." leading-slash form + // doesn't break the bundle-path resolution below. + return dirname(fileURLToPath(import.meta.url)); } export function resolveRuntimePath(): string | null { diff --git a/packages/core/src/lint/rules/slideshow.ts b/packages/core/src/lint/rules/slideshow.ts index 2731a47512..80b5d8f449 100644 --- a/packages/core/src/lint/rules/slideshow.ts +++ b/packages/core/src/lint/rules/slideshow.ts @@ -1,20 +1,11 @@ -// fallow-ignore-file code-duplication import type { LintContext, HyperframeLintFinding } from "../context"; import type { LintRule } from "../types"; import { readAttr } from "../utils"; import { parseSlideshowManifest, resolveSlideshow } from "../../slideshow/parseSlideshow"; +import { isSceneLikeCompositionId } from "../../slideshow/sceneId"; type Scene = { id: string; start: number; duration: number }; -/** Mirrors isSceneLikeCompositionId in packages/core/src/runtime/timeline.ts */ -function isSceneLikeCompositionId(compositionId: string): boolean { - const normalized = compositionId.trim().toLowerCase(); - if (!normalized || normalized === "main") return false; - if (normalized.includes("caption")) return false; - if (normalized.includes("ambient")) return false; - return true; -} - function parseTiming(raw: string): { start: number; duration: number } | null { const startStr = readAttr(raw, "data-start"); if (startStr === null) return null; diff --git a/packages/core/src/runtime/timeline.ts b/packages/core/src/runtime/timeline.ts index e6328d73a8..6f8e583818 100644 --- a/packages/core/src/runtime/timeline.ts +++ b/packages/core/src/runtime/timeline.ts @@ -7,6 +7,7 @@ import type { import { swallow } from "./diagnostics"; import { readElementPlaybackRate } from "./media"; import { createRuntimeStartTimeResolver } from "./startResolver"; +import { isSceneLikeCompositionId } from "../slideshow/sceneId"; const AUTHORED_DURATION_ATTR = "data-hf-authored-duration"; const AUTHORED_END_ATTR = "data-hf-authored-end"; @@ -230,13 +231,6 @@ export function collectRuntimeTimelinePayload(params: { } return maxWindowEndSeconds > 0 ? maxWindowEndSeconds : null; }; - const isSceneLikeCompositionId = (compositionId: string): boolean => { - const normalized = compositionId.trim().toLowerCase(); - if (!normalized || normalized === "main") return false; - if (normalized.includes("caption")) return false; - if (normalized.includes("ambient")) return false; - return true; - }; const resolveNearestCompositionContext = ( node: Element, root: Element | null, diff --git a/packages/core/src/slideshow/parseSlideshow.test.ts b/packages/core/src/slideshow/parseSlideshow.test.ts index d9207243fb..1633d58818 100644 --- a/packages/core/src/slideshow/parseSlideshow.test.ts +++ b/packages/core/src/slideshow/parseSlideshow.test.ts @@ -37,6 +37,11 @@ describe("parseSlideshowManifest", () => { expect(() => parseSlideshowManifest(html)).toThrow(); }); + it("rejects a non-object manifest (e.g. a JSON array)", () => { + const html = ``; + expect(() => parseSlideshowManifest(html)).toThrow(); + }); + it("throws when a slide entry is malformed (sceneId not a string)", () => { const html = `