Test quak login and backup-metadata --exif (closes #110)
check / check (push) Successful in 42s
check / check (push) Successful in 42s
loginCommand now takes its login function and prompts from CliContext; bin/quak.ts passes Client.login and the terminal prompts, so behaviour is unchanged. Tests cover a login from QUAK_EMAIL/QUAK_PASSWORD with no prompt, the TOTP prompt, a failed login, and the saved session's modes, and show that --exif and --all each turn on EXIF extraction. Also rewraps the header comment of src/cli-commands.ts. Model: opus-5-5
This commit was merged in pull request #125.
This commit is contained in:
+17
-14
@@ -4,10 +4,8 @@
|
||||
// code; a thrown error is left to the caller. Nothing here calls
|
||||
// `process.exit`: `bin/quak.ts` wires these to the command line, and `run` in
|
||||
// `cli-run.ts` prints a thrown error as one line and exits once output has
|
||||
// drained. Output must stay byte-identical
|
||||
// (see `cli-output.ts`).
|
||||
// drained. Output must stay byte-identical (see `cli-output.ts`).
|
||||
|
||||
import { input, password as passwordPrompt } from "@inquirer/prompts";
|
||||
import {
|
||||
copyFileSync,
|
||||
existsSync,
|
||||
@@ -16,7 +14,11 @@ import {
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { Client, type ClientSnapshot } from "./client.js";
|
||||
import {
|
||||
type Client,
|
||||
type ClientSnapshot,
|
||||
type LoginOptions,
|
||||
} from "./client.js";
|
||||
import { init } from "./crypto/index.js";
|
||||
import {
|
||||
defaultCacheDirectory,
|
||||
@@ -44,6 +46,12 @@ export interface CliContext {
|
||||
// Reads the session file into a client, or null when there is none. The
|
||||
// CLI passes `loadSession` from `cli-session.ts`; tests pass a fake client.
|
||||
loadSession: (path: string) => Client | null;
|
||||
// Used by `login` only. The CLI passes `Client.login` and terminal
|
||||
// prompts; tests pass fakes.
|
||||
login: (opts: LoginOptions) => Promise<Client>;
|
||||
prompt: (message: string) => Promise<string>;
|
||||
// Like `prompt`, but the answer is masked as it is typed.
|
||||
promptSecret: (message: string) => Promise<string>;
|
||||
}
|
||||
|
||||
const sessionPath = (ctx: CliContext): string =>
|
||||
@@ -109,24 +117,19 @@ const openReadLibrary = (ctx: CliContext, client: Client): Promise<Library> =>
|
||||
precacheOriginals: false,
|
||||
});
|
||||
|
||||
const prompt = async (message: string): Promise<string> => input({ message });
|
||||
|
||||
const promptSecret = async (message: string): Promise<string> =>
|
||||
passwordPrompt({ message, mask: true });
|
||||
|
||||
export const loginCommand = async (ctx: CliContext): Promise<number> => {
|
||||
await init();
|
||||
const email = process.env.QUAK_EMAIL ?? (await prompt("Email"));
|
||||
const email = process.env.QUAK_EMAIL ?? (await ctx.prompt("Email"));
|
||||
const password =
|
||||
process.env.QUAK_PASSWORD ?? (await promptSecret("Password"));
|
||||
process.env.QUAK_PASSWORD ?? (await ctx.promptSecret("Password"));
|
||||
|
||||
ctx.stderr.write("Authenticating...\n");
|
||||
try {
|
||||
const client = await Client.login({
|
||||
const client = await ctx.login({
|
||||
email,
|
||||
password,
|
||||
totp: async () => prompt("TOTP code: "),
|
||||
emailOTP: async () => prompt("Email verification code: "),
|
||||
totp: async () => ctx.prompt("TOTP code: "),
|
||||
emailOTP: async () => ctx.prompt("Email verification code: "),
|
||||
});
|
||||
|
||||
saveSession(ctx.sessionDir, client.toJSON());
|
||||
|
||||
Reference in New Issue
Block a user