From 79adb92b4e313c3f826b3d77379ba8b63810dc1c Mon Sep 17 00:00:00 2001 From: kapelame <168134658+kapelame@users.noreply.github.com> Date: Mon, 14 Sep 2026 11:29:41 -0400 Subject: [PATCH] fix(cli): retain the audio track when scaffolding from an audio file --- packages/cli/src/commands/init.test.ts | 24 ++++++++++++++++++++++++ packages/cli/src/commands/init.ts | 16 ++++++++++++---- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/commands/init.test.ts b/packages/cli/src/commands/init.test.ts index e9f0e1b75a..fdcd94b0a4 100644 --- a/packages/cli/src/commands/init.test.ts +++ b/packages/cli/src/commands/init.test.ts @@ -249,6 +249,30 @@ describe("hyperframes init flag rename", () => { } }); + it("wires --audio into the composition without creating a video clip", () => { + const dir = mkdtempSync(join(tmpdir(), "hf-init-audio-test-")); + const target = join(dir, "proj"); + const audio = join(dir, "track.wav"); + copyFileSync( + resolve( + fileURLToPath(import.meta.url), + "../../../../producer/tests/audio-mux-parity/src/assets/tone.wav", + ), + audio, + ); + try { + const res = runInit([target, "--non-interactive", "--skip-transcribe", "--audio", audio]); + expect(res.status).toBe(0); + const html = readFileSync(join(target, "index.html"), "utf-8"); + expect(html).toMatch(/]*src="track\.wav"/); + expect(html).not.toMatch(/ { expect( resolveVideoDurationSeconds({ diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index d3f614b7ed..a383ee0c22 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -347,6 +347,7 @@ function patchVideoSrc( dir: string, videoFilename: string | undefined, durationSeconds?: number, + audioFilename?: string, ): void { const htmlFiles = readdirSync(dir, { withFileTypes: true, recursive: true }) .filter((e) => e.isFile() && e.name.endsWith(".html")) @@ -360,9 +361,13 @@ function patchVideoSrc( // Remove video elements with placeholder src content = content.replace(/]*src="__VIDEO_SRC__"[^>]*>[\s\S]*?<\/video>/g, ""); content = content.replace(/]*src="__VIDEO_SRC__"[^>]*>/g, ""); - // Remove audio elements with placeholder src - content = content.replace(/]*src="__VIDEO_SRC__"[^>]*>[\s\S]*?<\/audio>/g, ""); - content = content.replace(/]*src="__VIDEO_SRC__"[^>]*>/g, ""); + if (audioFilename) { + content = content.replaceAll("__VIDEO_SRC__", audioFilename); + } else { + // Remove audio elements with placeholder src + content = content.replace(/]*src="__VIDEO_SRC__"[^>]*>[\s\S]*?<\/audio>/g, ""); + content = content.replace(/]*src="__VIDEO_SRC__"[^>]*>/g, ""); + } } // Patch duration — use probed duration or default const dur = durationSeconds ? String(Math.round(durationSeconds * 100) / 100) : "10"; @@ -561,6 +566,7 @@ async function scaffoldProject( tailwind = false, resolution?: CanvasResolution, authoringSkill?: string, + localAudioName?: string, ): Promise { mkdirSync(destDir, { recursive: true }); @@ -573,7 +579,7 @@ async function scaffoldProject( } else { await fetchRemoteTemplate(templateId, destDir); } - patchVideoSrc(destDir, localVideoName, durationSeconds); + patchVideoSrc(destDir, localVideoName, durationSeconds, localAudioName); if (tailwind) writeTailwindSupport(destDir); if (resolution) applyResolutionPreset(destDir, resolution); @@ -913,6 +919,7 @@ export default defineCommand({ tailwind, resolutionPreset, args.skill, + audioFlag ? basename(audioFlag) : undefined, ); } catch (err) { console.error( @@ -1128,6 +1135,7 @@ export default defineCommand({ tailwind, resolutionPreset, args.skill, + audioFlag ? basename(audioFlag) : undefined, ); if (!isBundled) { spin.stop(c.success(`Downloaded ${templateId}`));