From 67930d90d57e9cd6015efd0c846f638be5868347 Mon Sep 17 00:00:00 2001 From: igm Date: Mon, 12 Jan 2026 15:42:39 +0800 Subject: [PATCH] Simplify apps/cli/ code - config.ts: Convert arrow functions to function declarations - api-client.ts: Extract duplicated RPCLink logic into buildClient helper - format-error.ts: Add centralized ORPCError handling - complete-login.ts: Remove redundant error handling (now in formatError) - status.ts: Simplify formatRelativeTime, improve whitespace - create.ts: Rename validRoles to VALID_ROLES, add as const, early return - completions.ts: Derive Shell type from SUPPORTED_SHELLS array Co-Authored-By: Claude Opus 4.5 --- apps/cli/src/routes/admin/complete-login.ts | 8 +----- apps/cli/src/routes/auth/status.ts | 8 +++--- apps/cli/src/routes/completions.ts | 5 ++-- apps/cli/src/routes/user/create.ts | 14 ++++++----- apps/cli/src/utils/api-client.ts | 28 ++++++++------------- apps/cli/src/utils/config.ts | 16 ++++++------ apps/cli/src/utils/format-error.ts | 8 +++++- 7 files changed, 41 insertions(+), 46 deletions(-) diff --git a/apps/cli/src/routes/admin/complete-login.ts b/apps/cli/src/routes/admin/complete-login.ts index 19f3833..8f2c3e1 100644 --- a/apps/cli/src/routes/admin/complete-login.ts +++ b/apps/cli/src/routes/admin/complete-login.ts @@ -1,5 +1,4 @@ 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"; @@ -21,12 +20,7 @@ async function completeLogin( console.log(`Completed login request for: ${flags.email}`); } catch (error) { - if (error instanceof ORPCError) { - // 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:", formatError(error)); - } + console.error("Error:", formatError(error)); this.process.exit(1); } } diff --git a/apps/cli/src/routes/auth/status.ts b/apps/cli/src/routes/auth/status.ts index 2bd0233..a18211d 100644 --- a/apps/cli/src/routes/auth/status.ts +++ b/apps/cli/src/routes/auth/status.ts @@ -17,16 +17,16 @@ function formatRelativeTime(date: Date): string { if (diffDays < 0) { 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 ${diffHours.toLocaleString()} hours`; + return diffHours <= 0 ? "expired" : `in ${diffHours.toLocaleString()} hours`; } + if (diffDays === 1) { return "tomorrow"; } + return `in ${diffDays.toLocaleString()} days`; } diff --git a/apps/cli/src/routes/completions.ts b/apps/cli/src/routes/completions.ts index 06280f5..3b4d189 100644 --- a/apps/cli/src/routes/completions.ts +++ b/apps/cli/src/routes/completions.ts @@ -1,9 +1,8 @@ import type { LocalContext } from "../context.js"; import { buildCommand } from "@stricli/core"; -type Shell = "bash" | "zsh" | "fish"; - -const SUPPORTED_SHELLS: readonly Shell[] = ["bash", "zsh", "fish"] as const; +const SUPPORTED_SHELLS = ["bash", "zsh", "fish"] as const; +type Shell = (typeof SUPPORTED_SHELLS)[number]; function parseShell(value: string): Shell { const shell = value.toLowerCase(); diff --git a/apps/cli/src/routes/user/create.ts b/apps/cli/src/routes/user/create.ts index 249ea49..e53b400 100644 --- a/apps/cli/src/routes/user/create.ts +++ b/apps/cli/src/routes/user/create.ts @@ -5,18 +5,20 @@ import { formatError } from "../../utils/format-error.js"; type OrgRole = "owner" | "admin" | "member"; -const validRoles: OrgRole[] = ["owner", "admin", "member"]; +const VALID_ROLES: readonly OrgRole[] = ["owner", "admin", "member"] as const; function parseRole(role: string | undefined): OrgRole | undefined { if (!role) { return undefined; } - if (validRoles.includes(role as OrgRole)) { - return role as OrgRole; + + if (!VALID_ROLES.includes(role as OrgRole)) { + throw new Error( + `Invalid role: ${role}. Must be one of: ${VALID_ROLES.join(", ")}`, + ); } - throw new Error( - `Invalid role: ${role}. Must be one of: ${validRoles.join(", ")}`, - ); + + return role as OrgRole; } interface CreateUserFlags { diff --git a/apps/cli/src/utils/api-client.ts b/apps/cli/src/utils/api-client.ts index 00cec6c..41e6a9e 100644 --- a/apps/cli/src/utils/api-client.ts +++ b/apps/cli/src/utils/api-client.ts @@ -10,6 +10,14 @@ import { readConfig } from "./config.js"; export type ApiClient = ContractRouterClient; +function buildClient(apiUrl: string, token: string): ApiClient { + const link = new RPCLink({ + url: `${apiUrl}/api/v1/rpc`, + headers: { "X-API-Key": token }, + }); + return createORPCClient(link) as unknown as ApiClient; +} + /** * Create an oRPC API client with provided credentials */ @@ -25,18 +33,10 @@ export function createApiClient( apiUrl?: string, token?: string, ): ApiClient | Promise { - // If both arguments are provided, create client directly if (apiUrl !== undefined && token !== undefined) { - const link = new RPCLink({ - url: `${apiUrl}/api/v1/rpc`, - headers: { - "X-API-Key": token, - }, - }); - return createORPCClient(link) as unknown as ApiClient; + return buildClient(apiUrl, token); } - // Otherwise, read from config asynchronously return (async (): Promise => { const config = await readConfig(); if (!config) { @@ -44,14 +44,6 @@ export function createApiClient( "Not logged in. Run 'reviq bootstrap' or 'reviq auth login' first.", ); } - - const link = new RPCLink({ - url: `${config.apiUrl}/api/v1/rpc`, - headers: { - "X-API-Key": config.token, - }, - }); - - return createORPCClient(link) as unknown as ApiClient; + return buildClient(config.apiUrl, config.token); })(); } diff --git a/apps/cli/src/utils/config.ts b/apps/cli/src/utils/config.ts index 9b47e42..374e7fa 100644 --- a/apps/cli/src/utils/config.ts +++ b/apps/cli/src/utils/config.ts @@ -19,40 +19,42 @@ const CONFIG_FILE = join(CONFIG_DIR, "credentials.json"); /** * Get the path to the config file */ -export const getConfigPath = (): string => CONFIG_FILE; +export function getConfigPath(): string { + return CONFIG_FILE; +} /** * Read the config file * Returns null if the file doesn't exist or is invalid */ -export const readConfig = async (): Promise => { +export async function readConfig(): Promise { try { const data = await readFile(CONFIG_FILE, "utf-8"); return JSON.parse(data) as Config; } catch { return null; } -}; +} /** * Write the config file * Creates the config directory if it doesn't exist */ -export const writeConfig = async (config: Config): Promise => { +export async function writeConfig(config: Config): Promise { await mkdir(CONFIG_DIR, { recursive: true, mode: 0o700 }); await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600, }); -}; +} /** * Delete the config file * Ignores errors if the file doesn't exist */ -export const deleteConfig = async (): Promise => { +export async function deleteConfig(): Promise { try { await unlink(CONFIG_FILE); } catch { // Ignore if doesn't exist } -}; +} diff --git a/apps/cli/src/utils/format-error.ts b/apps/cli/src/utils/format-error.ts index a033552..2b5ced5 100644 --- a/apps/cli/src/utils/format-error.ts +++ b/apps/cli/src/utils/format-error.ts @@ -1,8 +1,14 @@ +import { ORPCError } from "@orpc/client"; + /** * Format an unknown error value into a string message. - * Handles Error instances, strings, and other types safely. + * Handles ORPCError, Error instances, strings, and other types safely. */ export function formatError(error: unknown): string { + if (error instanceof ORPCError) { + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions -- ORPCError.code is typed as any + return `[${error.code}] ${error.message}`; + } if (error instanceof Error) { return error.message; }