-v raises the CLI's log level per repeat; -q prints only fatal errors (closes #212)
check / check (push) Waiting to run
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:
@@ -51,6 +51,7 @@ import {
|
||||
listMissingThumbnailsCommand,
|
||||
fixMissingThumbnailsCommand,
|
||||
} from "../../src/cli-commands.js";
|
||||
import { stderrLog } from "../../src/cli-log.js";
|
||||
import { run } from "../../src/cli-run.js";
|
||||
import { loadSession } from "../../src/cli-session.js";
|
||||
import {
|
||||
@@ -208,9 +209,11 @@ let root: string;
|
||||
let stdout: Output;
|
||||
let stderr: Output;
|
||||
|
||||
// The commands log at the default level, info, as `quak` without `-v` or `-q`
|
||||
// does; the levels have their own tests, in `log.test.ts`.
|
||||
const context = (client: Client | null = fakeClient()): CliContext => ({
|
||||
stdout,
|
||||
stderr,
|
||||
log: stderrLog(stderr, "info"),
|
||||
sessionDir: join(root, "session"),
|
||||
cacheDir: join(root, "cache"),
|
||||
loadSession: () => client,
|
||||
@@ -566,7 +569,7 @@ describe("files", () => {
|
||||
[101, 1],
|
||||
[200, 2],
|
||||
]);
|
||||
expect(stderr.text).toBe("");
|
||||
expect(stderr.text).toBe(FETCHED_BOTH_ALBUMS);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -910,6 +913,8 @@ describe("backup", () => {
|
||||
);
|
||||
});
|
||||
|
||||
// `--json` shapes stdout only: the log lines stay on stderr, and the JSON
|
||||
// takes the place of the summary.
|
||||
it("prints the result as JSON with --json, still exiting 1 on a failure", async () => {
|
||||
const ctx = context(fakeClient({ failID: 101 }));
|
||||
const code = await backupCommand(ctx, join(root, "backup"), {
|
||||
@@ -924,7 +929,31 @@ describe("backup", () => {
|
||||
failed: 1,
|
||||
});
|
||||
expect(result.errors[0].fileID).toBe(101);
|
||||
expect(stderr.text).toBe("Starting backup...\n");
|
||||
expect(stderr.text).toContain(
|
||||
"Fetching original sunset.jpg (101)...\n",
|
||||
);
|
||||
expect(stderr.text).not.toContain("Backup complete");
|
||||
});
|
||||
|
||||
// On an empty cache the library's first refresh fetches the files of
|
||||
// every album before the backup can start, which on a large account takes
|
||||
// a while; each album it has fetched the files of is counted on stderr,
|
||||
// with `--json` too.
|
||||
it("counts on stderr each album whose files its first refresh fetched, with --json too", async () => {
|
||||
const start =
|
||||
"Starting backup...\n" +
|
||||
FETCHED_BOTH_ALBUMS +
|
||||
"Refreshing library...\n";
|
||||
for (const json of [false, true]) {
|
||||
stderr.text = "";
|
||||
const dir = mkdtempSync(join(root, "backup-"));
|
||||
const ctx = {
|
||||
...context(),
|
||||
cacheDir: mkdtempSync(join(root, "cache-")),
|
||||
};
|
||||
expect(await backupCommand(ctx, dir, { json })).toBe(0);
|
||||
expect(stderr.text.slice(0, start.length)).toBe(start);
|
||||
}
|
||||
});
|
||||
|
||||
it("--verify downloads again an original that does not match its hash, prints the counts, and exits 0", async () => {
|
||||
@@ -1014,6 +1043,7 @@ describe("backup", () => {
|
||||
backupCommand(ctx, dir, {}),
|
||||
new PassThrough(),
|
||||
runStderr,
|
||||
stderrLog(runStderr, "info"),
|
||||
resolve,
|
||||
);
|
||||
});
|
||||
@@ -1254,7 +1284,9 @@ describe("helper list-missing-thumbnails", () => {
|
||||
expect(stderr.text).toContain("No missing thumbnails found.\n");
|
||||
});
|
||||
|
||||
it("prints a JSON array with --json and no progress", async () => {
|
||||
// `--json` shapes stdout only: the progress stays on stderr, and the JSON
|
||||
// takes the place of the count and the list.
|
||||
it("prints a JSON array with --json, and its progress on stderr", async () => {
|
||||
const ctx = context(fakeClient({ emptyThumbID: 200 }));
|
||||
expect(await listMissingThumbnailsCommand(ctx, { json: true })).toBe(0);
|
||||
expect(JSON.parse(stdout.text)).toEqual([
|
||||
@@ -1265,7 +1297,9 @@ describe("helper list-missing-thumbnails", () => {
|
||||
reason: "empty thumbnail (0 bytes)",
|
||||
},
|
||||
]);
|
||||
expect(stderr.text).toBe("");
|
||||
expect(stderr.text).toContain(FETCHED_BOTH_ALBUMS);
|
||||
expect(stderr.text).toContain("[Work] Checking thumbnails...\n");
|
||||
expect(stderr.text).not.toContain("with missing thumbnails");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1624,9 +1658,9 @@ describe("an account with many albums", () => {
|
||||
expect(stderr.text).toBe(progress);
|
||||
});
|
||||
|
||||
// With `--json` the refresh's progress is not printed, as `backup` and
|
||||
// the thumbnail helpers print none of theirs.
|
||||
it("files --json prints no progress", async () => {
|
||||
// `--json` shapes stdout only, so the refresh's progress is still counted
|
||||
// on stderr.
|
||||
it("files --json counts on stderr each album whose files its refresh fetched", async () => {
|
||||
expect(await filesCommand(accountContext(), "7", { json: true })).toBe(
|
||||
0,
|
||||
);
|
||||
@@ -1640,6 +1674,10 @@ describe("an account with many albums", () => {
|
||||
collectionID: 7,
|
||||
},
|
||||
]);
|
||||
expect(stderr.text).toBe("");
|
||||
let progress = "";
|
||||
for (let n = 1; n <= ALBUMS; n++) {
|
||||
progress += `Fetched the files of ${n} of ${ALBUMS} collections\n`;
|
||||
}
|
||||
expect(stderr.text).toBe(progress);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user