Files
hyperframes/packages/cli/src/cli.workspaceBuildGate.test.ts
T
Miguel Ángel de9206e0f8 fix(cli): detect a missing or stale studio build before it crashes (#4076)
* fix(cli): name the missing or stale workspace package before preview crashes

Running hyperframes preview from source with an unbuilt or stale
package previously crashed deep inside that package's own import
graph with a raw resolver error naming neither the package nor the
fix. Add a preflight that runs before the preview command's own
module import, so it also catches the crash that happens at CLI load
time before the studio dev server ever spawns.

* test(cli): cover the preview workspace-build gate wiring

Adds a test that exercises the real CLI dispatch path (mocked module
loaders, not source-text matching) to prove the preview command's
build-check runs before its module loads and that a failure exits
non-zero without double-printing. Also guards the exports-map reader
against a malformed package.json instead of crashing raw.

* fix(cli): correct why the audio-fx generated file is checked

The comment claimed core/src/index.ts imports both generated files
directly. Only runtime-inline.ts is; audio-fx-runtime-inline.ts is
reachable solely via core's own audio-fx-runtime export subpath,
which the engine package imports. The check itself was already
correct to require both, only the stated reason was wrong.
2026-09-18 09:27:39 -04:00

65 lines
2.2 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import { mockTelemetry } from "./cliDispatchTestUtils.js";
const originalArgv = [...process.argv];
const originalExitCode = process.exitCode;
afterEach(() => {
process.argv = [...originalArgv];
process.exitCode = originalExitCode;
vi.doUnmock("./commands/preview.js");
vi.doUnmock("./utils/env.js");
vi.doUnmock("./utils/workspaceBuildCheck.js");
vi.doUnmock("./telemetry/index.js");
vi.doUnmock("./telemetry/events.js");
vi.resetModules();
});
function mockPreviewGate(problems: Array<Record<string, string>>): () => boolean {
let previewImported = false;
vi.doMock("./commands/preview.js", () => {
previewImported = true;
return { default: { meta: { name: "preview" }, args: {}, run: vi.fn() } };
});
vi.doMock("./utils/env.js", () => ({ isDevMode: () => true }));
vi.doMock("./utils/workspaceBuildCheck.js", () => ({
checkStudioWorkspaceBuild: () => problems,
formatWorkspaceBuildProblems: () =>
problems.map((p) => ` - ${p.package}: ${p.detail}`).join("\n"),
}));
mockTelemetry();
return () => previewImported;
}
describe("CLI preview workspace build gate", () => {
it("blocks the preview command before its module loads, without printing the raw error twice", async () => {
const wasPreviewImported = mockPreviewGate([
{
package: "core",
kind: "missing",
detail: "dist/ is missing or empty",
fix: "bun run build",
},
]);
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
process.argv = ["node", "cli.ts", "preview", "--json"];
await import("./cli.js");
expect(wasPreviewImported()).toBe(false);
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("dist/ is missing or empty"));
expect(errorSpy).toHaveBeenCalledTimes(1);
expect(process.exitCode).toBe(1);
errorSpy.mockRestore();
});
it("imports the preview module once the workspace build check reports no problems", async () => {
const wasPreviewImported = mockPreviewGate([]);
process.argv = ["node", "cli.ts", "preview", "--json"];
await import("./cli.js");
expect(wasPreviewImported()).toBe(true);
});
});