fix: use event-driven renderer for build progress
This commit is contained in:
+9
-3
@@ -738,7 +738,7 @@ export async function runUp(
|
|||||||
};
|
};
|
||||||
const workspacePath = `/workspaces/${encodeURIComponent(project)}`;
|
const workspacePath = `/workspaces/${encodeURIComponent(project)}`;
|
||||||
|
|
||||||
const taskCtx = await new Listr<UpContext>(
|
const taskCtx = await new Listr<UpContext, "default" | "simple">(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
title: "Read compose",
|
title: "Read compose",
|
||||||
@@ -796,7 +796,7 @@ export async function runUp(
|
|||||||
cwd,
|
cwd,
|
||||||
{
|
{
|
||||||
progress: (message) => {
|
progress: (message) => {
|
||||||
task.output = message;
|
if (task.output !== message) task.output = message;
|
||||||
},
|
},
|
||||||
service: (name) => {
|
service: (name) => {
|
||||||
let stream: ReturnType<ListrTaskWrapper<UpContext, any, any>["stdout"]> | undefined;
|
let stream: ReturnType<ListrTaskWrapper<UpContext, any, any>["stdout"]> | undefined;
|
||||||
@@ -1071,7 +1071,13 @@ export async function runUp(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
{ rendererOptions: { collapseErrors: false } },
|
{
|
||||||
|
// Build progress can remain queued across many polls. The default TTY
|
||||||
|
// spinner redraws every child on every tick, which duplicates those
|
||||||
|
// frames in captured terminal output. Simple emits only task events.
|
||||||
|
renderer: build ? "simple" : "default",
|
||||||
|
rendererOptions: { collapseErrors: false },
|
||||||
|
},
|
||||||
).run();
|
).run();
|
||||||
|
|
||||||
return taskCtx;
|
return taskCtx;
|
||||||
|
|||||||
@@ -26,6 +26,56 @@ const snapshot = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe("up API pipeline", () => {
|
describe("up API pipeline", () => {
|
||||||
|
test("does not append queued build frames on repeated TTY progress ticks", async () => {
|
||||||
|
const root = await mkdtemp(join(tmpdir(), "kuber-up-api-"));
|
||||||
|
const previousCwd = process.cwd();
|
||||||
|
const tty = Object.getOwnPropertyDescriptor(process.stdout, "isTTY");
|
||||||
|
const rendered: string[] = [];
|
||||||
|
const writes = spyOn(process.stdout, "write").mockImplementation(((chunk: string | Uint8Array) => {
|
||||||
|
rendered.push(String(chunk));
|
||||||
|
return true;
|
||||||
|
}) as typeof process.stdout.write);
|
||||||
|
let polls = 0;
|
||||||
|
try {
|
||||||
|
Object.defineProperty(process.stdout, "isTTY", { configurable: true, value: true });
|
||||||
|
await writeFile(join(root, "compose.yml"), "services:\n app:\n build: .\n");
|
||||||
|
await writeFile(join(root, ".kuberrc.ts"), 'export default { project: "shop" };\n');
|
||||||
|
process.chdir(root);
|
||||||
|
const trust = await resolveTrustIdentity("shop", root);
|
||||||
|
const request: ApiRequester = async <T>(path: string) => {
|
||||||
|
if (path === "/snapshots/negotiate") return { ready: true } as T;
|
||||||
|
if (path === "/builds") return { state: "queued" } as T;
|
||||||
|
if (path.includes("/events"))
|
||||||
|
return [{ type: "status", status: {
|
||||||
|
state: "queued",
|
||||||
|
phase: ["queued", "preparing", "waiting", "waiting"][polls],
|
||||||
|
} }] as T;
|
||||||
|
if (path.endsWith("/reconcile")) {
|
||||||
|
polls++;
|
||||||
|
await Bun.sleep(110);
|
||||||
|
return polls < 3
|
||||||
|
? { state: "queued" } as T
|
||||||
|
: { state: "failed", error: "build stopped" } as T;
|
||||||
|
}
|
||||||
|
throw new Error(path);
|
||||||
|
};
|
||||||
|
await expect(provideContext(() => runUp(true, request, { trust }))).rejects.toThrow("build stopped");
|
||||||
|
const output = rendered.join("");
|
||||||
|
expect(polls).toBe(3);
|
||||||
|
expect(output.match(/Build app/g)).toHaveLength(1);
|
||||||
|
expect(output.match(/Build queued/g)).toHaveLength(1);
|
||||||
|
expect(output).toContain("Build preparing");
|
||||||
|
expect(output).toContain("Build waiting");
|
||||||
|
expect(output).toContain("build stopped");
|
||||||
|
} finally {
|
||||||
|
writes.mockRestore();
|
||||||
|
if (tty) Object.defineProperty(process.stdout, "isTTY", tty);
|
||||||
|
else Reflect.deleteProperty(process.stdout, "isTTY");
|
||||||
|
process.chdir(previousCwd);
|
||||||
|
await rm(root, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
}, 5_000);
|
||||||
|
|
||||||
test("attaches each build log to its started image child", async () => {
|
test("attaches each build log to its started image child", async () => {
|
||||||
const root = await mkdtemp(join(tmpdir(), "kuber-up-api-"));
|
const root = await mkdtemp(join(tmpdir(), "kuber-up-api-"));
|
||||||
const previousCwd = process.cwd();
|
const previousCwd = process.cwd();
|
||||||
|
|||||||
Reference in New Issue
Block a user