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

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 fatal lines, and is refused with -v; both are program options. Every non-zero exit logs one fatal line, so a run that exits 1 because files failed says how many and where to look. Every line reporting a failure is at error. --json changes stdout only, so backup and --json runs log their progress on stderr. withRetry and ApiClient report retries and requests through new callbacks.

Model: opus-5-5
This commit was merged in pull request #216.
This commit is contained in:
2026-10-18 21:46:59 +02:00
parent 92e0c6c791
commit 68d22b9927
21 changed files with 1099 additions and 188 deletions
+15 -5
View File
@@ -101,7 +101,10 @@ import type { MLData } from "./mldata-fetch.js";
import type { Collection, EnteFile, FileMetadata } from "./model/types.js";
import { hasTransportCode } from "./retry.js";
export type ProgressCallback = (message: string) => void;
// Reports one line of the backup's progress. `level` is "error" when the line
// reports something that failed, such as a file or an album, and is left out
// for every other line.
export type ProgressCallback = (message: string, level?: "error") => void;
// The lock in a backup directory, as `lockBackupDirectory` returns it.
export interface BackupLock {
@@ -654,7 +657,7 @@ const runLockedBackup = async (
for (const record of lib.undecryptable()) {
const error = `could not be decrypted: ${record.error}`;
if (record.fileID === undefined) {
log(`FAILED album ${record.collectionID}: ${error}`);
log(`FAILED album ${record.collectionID}: ${error}`, "error");
undecryptableAlbums.push(record.collectionID);
} else if (scope.has(record.collectionID)) {
const copies = memberships.get(record.fileID);
@@ -699,6 +702,7 @@ const runLockedBackup = async (
} catch (err) {
log(
`FAILED verifying original ${file.metadata.title}: ${describeError(err)}`,
"error",
);
recordFailure(
file.id,
@@ -732,6 +736,7 @@ const runLockedBackup = async (
} catch (err) {
log(
`FAILED original ${file.metadata.title}: ${describeError(err)}`,
"error",
);
recordFailure(
file.id,
@@ -807,7 +812,7 @@ const runLockedBackup = async (
await lib.fetchMLData();
} catch (err) {
mlDataFailure = err;
log(`FAILED ML data: ${describeError(err)}`);
log(`FAILED ML data: ${describeError(err)}`, "error");
}
for (const file of distinct.values()) {
if (lock.lost) throw lock.lost;
@@ -868,7 +873,10 @@ const runLockedBackup = async (
downloadDirectory,
);
} catch (err) {
log(`FAILED removing old album directories: ${describeError(err)}`);
log(
`FAILED removing old album directories: ${describeError(err)}`,
"error",
);
}
// An album whose folder or JSON cannot be written, such as one whose
@@ -897,6 +905,7 @@ const runLockedBackup = async (
} catch (err) {
log(
`FAILED removing old links in ${c.name}: ${describeError(err)}`,
"error",
);
}
@@ -915,6 +924,7 @@ const runLockedBackup = async (
} catch (err) {
log(
`FAILED symlink ${c.name}/${linkName}: ${describeError(err)}`,
"error",
);
recordFailure(
link.file.id,
@@ -931,7 +941,7 @@ const runLockedBackup = async (
metaFiles,
);
} catch (err) {
log(`FAILED album ${c.name}: ${describeError(err)}`);
log(`FAILED album ${c.name}: ${describeError(err)}`, "error");
failedAlbums.push(c.id);
}
}