From ec10aae54fd3bcadaebb7e935a2b53d788fee074 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Wed, 28 Oct 2026 18:57:53 +0000 Subject: [PATCH] fix(cli): beats --json answers with no track when the composition has no music (#6690) * fix(cli): beats --json answers with no track when the composition has no music * fix(cli): drop a comment the name already says --- packages/cli/src/commands/beats.test.ts | 21 ++++++++++++++++++-- packages/cli/src/commands/beats.ts | 26 +++++++++++++++---------- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/packages/cli/src/commands/beats.test.ts b/packages/cli/src/commands/beats.test.ts index 6269f151ef..5498768337 100644 --- a/packages/cli/src/commands/beats.test.ts +++ b/packages/cli/src/commands/beats.test.ts @@ -44,7 +44,7 @@ function project(name: string, bpm: number | null, seconds = 12): string { return dir; } -async function runBeats(dir: string): Promise<{ exitCode: number; out: string }> { +async function runBeats(dir: string, json = true): Promise<{ exitCode: number; out: string }> { const command = (await import("./beats.js")).default as unknown as { run: (context: { args: Record }) => Promise; }; @@ -54,7 +54,7 @@ async function runBeats(dir: string): Promise<{ exitCode: number; out: string }> const error = vi.spyOn(console, "error").mockImplementation(capture); let exitCode = 0; try { - await command.run({ args: { dir, json: true } }); + await command.run({ args: { dir, json } }); } catch (thrown) { exitCode = (thrown as { result?: { exitCode?: number } }).result?.exitCode ?? -1; } finally { @@ -105,6 +105,23 @@ describe.skipIf(!canDecode)("hyperframes beats", () => { expect(out).not.toContain("hyperframes audio"); }); + it("answers --json with no track, not a failure, when the composition has no music", async () => { + const dir = project("no-track", 120); + writeFileSync(join(dir, "index.html"), `
`); + const { exitCode, out } = await runBeats(dir); + expect(exitCode).toBe(0); + expect(JSON.parse(out)).toEqual({ ok: true, track: null, file: null, count: 0, bpm: null }); + expect(existsSync(join(dir, "beats"))).toBe(false); + }); + + it("still tells a person with no music track how to mark one", async () => { + const dir = project("no-track-person", 120); + writeFileSync(join(dir, "index.html"), `
`); + const { exitCode, out } = await runBeats(dir, false); + expect(exitCode).toBe(1); + expect(out).toMatch(/No music track found/); + }); + it("writes nothing for a track with no steady beat", async () => { const dir = project("silent", null); const { exitCode, out } = await runBeats(dir); diff --git a/packages/cli/src/commands/beats.ts b/packages/cli/src/commands/beats.ts index 0be4a54dd2..5b81c9cb9a 100644 --- a/packages/cli/src/commands/beats.ts +++ b/packages/cli/src/commands/beats.ts @@ -50,16 +50,15 @@ function findMusicSrc(html: string): string | null { return findMusicAudioSrc(document.toString()); } -/** Locate the music track + its on-disk audio, or fail with a clear message. */ -function resolveMusicTarget(project: ProjectDir): { rel: string; audioPath: string } { +const NO_MUSIC_TRACK = + 'No music track found. Add data-timeline-role="music" to the