Merge branch 'test-coverage'
Some checks failed
CI / ci (push) Has been cancelled

Add test utilities and ast-grep rules for code quality.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
igm
2026-01-12 15:05:07 +08:00
38 changed files with 247 additions and 172 deletions

View File

@@ -2,6 +2,7 @@ import type { LocalContext } from "../../context.js";
import { ORPCError } from "@orpc/client";
import { buildCommand } from "@stricli/core";
import { createApiClient } from "../../utils/api-client.js";
import { formatError } from "../../utils/format-error.js";
interface CompleteLoginFlags {
email: string;
@@ -21,12 +22,10 @@ async function completeLogin(
console.log(`Completed login request for: ${flags.email}`);
} catch (error) {
if (error instanceof ORPCError) {
console.error(`Error [${String(error.code)}]:`, error.message);
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions -- ORPCError.code is typed as any
console.error(`Error [${error.code}]:`, error.message);
} else {
console.error(
"Error:",
error instanceof Error ? error.message : String(error),
);
console.error("Error:", formatError(error));
}
this.process.exit(1);
}

View File

@@ -2,6 +2,7 @@ import type { LocalContext } from "../../context.js";
import { buildCommand } from "@stricli/core";
import { createApiClient } from "../../utils/api-client.js";
import { readConfig, writeConfig } from "../../utils/config.js";
import { formatError } from "../../utils/format-error.js";
interface LoginFlags {
token: string;
@@ -47,10 +48,7 @@ async function login(this: LocalContext, flags: LoginFlags): Promise<void> {
console.log(`Logged in as ${authStatus.user.email}`);
console.log("Credentials saved to ~/.config/reviq/credentials.json");
} catch (error) {
console.error(
"Login failed:",
error instanceof Error ? error.message : String(error),
);
console.error("Login failed:", formatError(error));
console.log("\nMake sure your API token is valid.");
console.log("You can create a new token at: /account/api-tokens");
this.process.exit(1);

View File

@@ -2,6 +2,7 @@ import type { LocalContext } from "../../context.js";
import { buildCommand } from "@stricli/core";
import { createApiClient } from "../../utils/api-client.js";
import { getConfigPath, readConfig } from "../../utils/config.js";
import { formatError } from "../../utils/format-error.js";
import { TOKEN_PREFIX } from "../../utils/token.js";
function formatDate(date: Date): string {
@@ -14,19 +15,19 @@ function formatRelativeTime(date: Date): string {
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
if (diffDays < 0) {
return `${String(Math.abs(diffDays))} days ago`;
return `${Math.abs(diffDays).toLocaleString()} days ago`;
}
if (diffDays === 0) {
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
if (diffHours <= 0) {
return "expired";
}
return `in ${String(diffHours)} hours`;
return `in ${diffHours.toLocaleString()} hours`;
}
if (diffDays === 1) {
return "tomorrow";
}
return `in ${String(diffDays)} days`;
return `in ${diffDays.toLocaleString()} days`;
}
async function status(this: LocalContext): Promise<void> {
@@ -96,9 +97,7 @@ async function status(this: LocalContext): Promise<void> {
);
}
} catch (error) {
console.log(
` Error: ${error instanceof Error ? error.message : String(error)}`,
);
console.log(` Error: ${formatError(error)}`);
console.log(
"\n Unable to connect to API. Local credentials may be invalid.",
);

View File

@@ -2,6 +2,7 @@ import type { LocalContext } from "../context.js";
import { createDb, executeBootstrap } from "@reviq/db";
import { buildCommand } from "@stricli/core";
import { writeConfig } from "../utils/config.js";
import { formatError } from "../utils/format-error.js";
interface BootstrapFlags {
email: string;
@@ -47,10 +48,7 @@ async function bootstrap(
await db.destroy();
} catch (error) {
console.error(
"Error:",
error instanceof Error ? error.message : String(error),
);
console.error("Error:", formatError(error));
await db.destroy();
this.process.exit(1);
}

View File

@@ -1,6 +1,7 @@
import type { LocalContext } from "../../context.js";
import { buildCommand } from "@stricli/core";
import { createApiClient } from "../../utils/api-client.js";
import { formatError } from "../../utils/format-error.js";
interface AddSiteFlags {
org: string;
@@ -18,10 +19,7 @@ async function addSite(this: LocalContext, flags: AddSiteFlags): Promise<void> {
console.log(`Added site ${flags.domain} to org ${flags.org}`);
} catch (error) {
console.error(
"Error:",
error instanceof Error ? error.message : String(error),
);
console.error("Error:", formatError(error));
this.process.exit(1);
}
}

View File

@@ -1,6 +1,7 @@
import type { LocalContext } from "../../context.js";
import { buildCommand } from "@stricli/core";
import { createApiClient } from "../../utils/api-client.js";
import { formatError } from "../../utils/format-error.js";
interface CreateOrgFlags {
slug: string;
@@ -24,10 +25,7 @@ async function create(
console.log(`Created org: ${result.slug}`);
console.log(`Owner: ${flags.owner}`);
} catch (error) {
console.error(
"Error:",
error instanceof Error ? error.message : String(error),
);
console.error("Error:", formatError(error));
this.process.exit(1);
}
}

View File

@@ -1,6 +1,7 @@
import type { LocalContext } from "../../context.js";
import { buildCommand } from "@stricli/core";
import { createApiClient } from "../../utils/api-client.js";
import { formatError } from "../../utils/format-error.js";
async function list(this: LocalContext): Promise<void> {
try {
@@ -23,12 +24,9 @@ async function list(this: LocalContext): Promise<void> {
console.log();
}
console.log(`Total: ${String(orgs.length)} organization(s)`);
console.log(`Total: ${orgs.length.toLocaleString()} organization(s)`);
} catch (error) {
console.error(
"Error:",
error instanceof Error ? error.message : String(error),
);
console.error("Error:", formatError(error));
this.process.exit(1);
}
}

View File

@@ -1,6 +1,7 @@
import type { LocalContext } from "../../context.js";
import { buildCommand } from "@stricli/core";
import { createApiClient } from "../../utils/api-client.js";
import { formatError } from "../../utils/format-error.js";
interface ConfirmEmailFlags {
email: string;
@@ -19,10 +20,7 @@ async function confirmEmail(
console.log(`Confirmed email for: ${flags.email}`);
} catch (error) {
console.error(
"Error:",
error instanceof Error ? error.message : String(error),
);
console.error("Error:", formatError(error));
this.process.exit(1);
}
}

View File

@@ -1,6 +1,7 @@
import type { LocalContext } from "../../context.js";
import { buildCommand } from "@stricli/core";
import { createApiClient } from "../../utils/api-client.js";
import { formatError } from "../../utils/format-error.js";
type OrgRole = "owner" | "admin" | "member";
@@ -45,10 +46,7 @@ async function create(
console.log(`Added to org: ${flags.org} as ${flags.role ?? "member"}`);
}
} catch (error) {
console.error(
"Error:",
error instanceof Error ? error.message : String(error),
);
console.error("Error:", formatError(error));
this.process.exit(1);
}
}

View File

@@ -0,0 +1,14 @@
/**
* Format an unknown error value into a string message.
* Handles Error instances, strings, and other types safely.
*/
export function formatError(error: unknown): string {
if (error instanceof Error) {
return error.message;
}
if (typeof error === "string") {
return error;
}
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions -- intentional unknown coercion
return `${error}`;
}