-v raises the CLI's log level per repeat; -q prints only fatal errors (closes #212)
check / check (push) Waiting to run

Every line quak prints on stderr now goes through one log, in
src/cli-log.ts, at one of six levels. The default, info, prints what quak
printed before. -v adds each retry and the start and end of each refresh
and ML data fetch; -vv adds each request with its result. -q prints only
the line a failing command exits with, and is refused with -v. Both are
program options. withRetry reports retries through a new onRetry, and
ApiClient reports requests through a new onRequest; the CLI logs what
they report. --json now changes stdout only, so backup and --json runs
log their progress on stderr.

Model: opus-5-5
This commit is contained in:
2026-10-18 18:36:33 +00:00
parent 92e0c6c791
commit f9969b2525
17 changed files with 876 additions and 155 deletions
+62
View File
@@ -1222,3 +1222,65 @@ describe("ApiClient non-idempotent requests", () => {
}
});
});
// `onRequest` hears of each request the client sends, as its response arrives
// or as it fails. The CLI logs these with `-vv`. A retried request is reported
// once for each attempt.
describe("ApiClient onRequest", () => {
it("reports each request with its status, or with the error it failed with", async () => {
const refused = new TypeError("fetch failed");
const { fetch } = scriptedFetch(
refused,
textResponse("unavailable", 503),
jsonResponse({ collections: [] }),
jsonResponse({}),
streamResponse(new Uint8Array([1, 2, 3])),
);
const requests: unknown[] = [];
const client = new ApiClient({
fetch,
retry: noWait,
onRequest: (request) => requests.push(request),
});
await client.getJSON("/collections/v2", { sinceTime: 0 });
await client.postJSON("/users/logout", {});
await readAll(await client.getFileStream(7));
const collections = "https://api.ente.io/collections/v2?sinceTime=0";
expect(requests).toEqual([
{ method: "GET", url: collections, error: refused },
{ method: "GET", url: collections, status: 503 },
{ method: "GET", url: collections, status: 200 },
{
method: "POST",
url: "https://api.ente.io/users/logout",
status: 200,
},
{
method: "GET",
url: "https://files.ente.io/?fileID=7",
status: 200,
},
]);
});
it("reports a presigned upload without its query, which holds the upload's signature", async () => {
const { fetch } = scriptedFetch(new Response(null, { status: 200 }));
const requests: unknown[] = [];
const client = new ApiClient({
fetch,
retry: noWait,
onRequest: (request) => requests.push(request),
});
await client.putFile(
"https://s3.example/obj?X-Amz-Signature=abc",
new Uint8Array([1, 2]),
);
expect(requests).toEqual([
{ method: "PUT", url: "https://s3.example/obj", status: 200 },
]);
});
});