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 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
import type { LocalContext } from "../../context.js";
|
import type { LocalContext } from "../../context.js";
|
||||||
import { ORPCError } from "@orpc/client";
|
|
||||||
import { buildCommand } from "@stricli/core";
|
import { buildCommand } from "@stricli/core";
|
||||||
import { createApiClient } from "../../utils/api-client.js";
|
import { createApiClient } from "../../utils/api-client.js";
|
||||||
import { formatError } from "../../utils/format-error.js";
|
import { formatError } from "../../utils/format-error.js";
|
||||||
@@ -21,12 +20,7 @@ async function completeLogin(
|
|||||||
|
|
||||||
console.log(`Completed login request for: ${flags.email}`);
|
console.log(`Completed login request for: ${flags.email}`);
|
||||||
} catch (error) {
|
} 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);
|
this.process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,16 +17,16 @@ function formatRelativeTime(date: Date): string {
|
|||||||
if (diffDays < 0) {
|
if (diffDays < 0) {
|
||||||
return `${Math.abs(diffDays).toLocaleString()} days ago`;
|
return `${Math.abs(diffDays).toLocaleString()} days ago`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (diffDays === 0) {
|
if (diffDays === 0) {
|
||||||
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
||||||
if (diffHours <= 0) {
|
return diffHours <= 0 ? "expired" : `in ${diffHours.toLocaleString()} hours`;
|
||||||
return "expired";
|
|
||||||
}
|
|
||||||
return `in ${diffHours.toLocaleString()} hours`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (diffDays === 1) {
|
if (diffDays === 1) {
|
||||||
return "tomorrow";
|
return "tomorrow";
|
||||||
}
|
}
|
||||||
|
|
||||||
return `in ${diffDays.toLocaleString()} days`;
|
return `in ${diffDays.toLocaleString()} days`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import type { LocalContext } from "../context.js";
|
import type { LocalContext } from "../context.js";
|
||||||
import { buildCommand } from "@stricli/core";
|
import { buildCommand } from "@stricli/core";
|
||||||
|
|
||||||
type Shell = "bash" | "zsh" | "fish";
|
const SUPPORTED_SHELLS = ["bash", "zsh", "fish"] as const;
|
||||||
|
type Shell = (typeof SUPPORTED_SHELLS)[number];
|
||||||
const SUPPORTED_SHELLS: readonly Shell[] = ["bash", "zsh", "fish"] as const;
|
|
||||||
|
|
||||||
function parseShell(value: string): Shell {
|
function parseShell(value: string): Shell {
|
||||||
const shell = value.toLowerCase();
|
const shell = value.toLowerCase();
|
||||||
|
|||||||
@@ -5,18 +5,20 @@ import { formatError } from "../../utils/format-error.js";
|
|||||||
|
|
||||||
type OrgRole = "owner" | "admin" | "member";
|
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 {
|
function parseRole(role: string | undefined): OrgRole | undefined {
|
||||||
if (!role) {
|
if (!role) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
if (validRoles.includes(role as OrgRole)) {
|
|
||||||
return role as OrgRole;
|
if (!VALID_ROLES.includes(role as OrgRole)) {
|
||||||
}
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid role: ${role}. Must be one of: ${validRoles.join(", ")}`,
|
`Invalid role: ${role}. Must be one of: ${VALID_ROLES.join(", ")}`,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return role as OrgRole;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateUserFlags {
|
interface CreateUserFlags {
|
||||||
|
|||||||
@@ -10,6 +10,14 @@ import { readConfig } from "./config.js";
|
|||||||
|
|
||||||
export type ApiClient = ContractRouterClient<typeof contract>;
|
export type ApiClient = ContractRouterClient<typeof contract>;
|
||||||
|
|
||||||
|
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
|
* Create an oRPC API client with provided credentials
|
||||||
*/
|
*/
|
||||||
@@ -25,18 +33,10 @@ export function createApiClient(
|
|||||||
apiUrl?: string,
|
apiUrl?: string,
|
||||||
token?: string,
|
token?: string,
|
||||||
): ApiClient | Promise<ApiClient> {
|
): ApiClient | Promise<ApiClient> {
|
||||||
// If both arguments are provided, create client directly
|
|
||||||
if (apiUrl !== undefined && token !== undefined) {
|
if (apiUrl !== undefined && token !== undefined) {
|
||||||
const link = new RPCLink({
|
return buildClient(apiUrl, token);
|
||||||
url: `${apiUrl}/api/v1/rpc`,
|
|
||||||
headers: {
|
|
||||||
"X-API-Key": token,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return createORPCClient(link) as unknown as ApiClient;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, read from config asynchronously
|
|
||||||
return (async (): Promise<ApiClient> => {
|
return (async (): Promise<ApiClient> => {
|
||||||
const config = await readConfig();
|
const config = await readConfig();
|
||||||
if (!config) {
|
if (!config) {
|
||||||
@@ -44,14 +44,6 @@ export function createApiClient(
|
|||||||
"Not logged in. Run 'reviq bootstrap' or 'reviq auth login' first.",
|
"Not logged in. Run 'reviq bootstrap' or 'reviq auth login' first.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return buildClient(config.apiUrl, config.token);
|
||||||
const link = new RPCLink({
|
|
||||||
url: `${config.apiUrl}/api/v1/rpc`,
|
|
||||||
headers: {
|
|
||||||
"X-API-Key": config.token,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return createORPCClient(link) as unknown as ApiClient;
|
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,40 +19,42 @@ const CONFIG_FILE = join(CONFIG_DIR, "credentials.json");
|
|||||||
/**
|
/**
|
||||||
* Get the path to the config file
|
* Get the path to the config file
|
||||||
*/
|
*/
|
||||||
export const getConfigPath = (): string => CONFIG_FILE;
|
export function getConfigPath(): string {
|
||||||
|
return CONFIG_FILE;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read the config file
|
* Read the config file
|
||||||
* Returns null if the file doesn't exist or is invalid
|
* Returns null if the file doesn't exist or is invalid
|
||||||
*/
|
*/
|
||||||
export const readConfig = async (): Promise<Config | null> => {
|
export async function readConfig(): Promise<Config | null> {
|
||||||
try {
|
try {
|
||||||
const data = await readFile(CONFIG_FILE, "utf-8");
|
const data = await readFile(CONFIG_FILE, "utf-8");
|
||||||
return JSON.parse(data) as Config;
|
return JSON.parse(data) as Config;
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write the config file
|
* Write the config file
|
||||||
* Creates the config directory if it doesn't exist
|
* Creates the config directory if it doesn't exist
|
||||||
*/
|
*/
|
||||||
export const writeConfig = async (config: Config): Promise<void> => {
|
export async function writeConfig(config: Config): Promise<void> {
|
||||||
await mkdir(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
await mkdir(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
||||||
await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), {
|
await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), {
|
||||||
mode: 0o600,
|
mode: 0o600,
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete the config file
|
* Delete the config file
|
||||||
* Ignores errors if the file doesn't exist
|
* Ignores errors if the file doesn't exist
|
||||||
*/
|
*/
|
||||||
export const deleteConfig = async (): Promise<void> => {
|
export async function deleteConfig(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await unlink(CONFIG_FILE);
|
await unlink(CONFIG_FILE);
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore if doesn't exist
|
// Ignore if doesn't exist
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
|
import { ORPCError } from "@orpc/client";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format an unknown error value into a string message.
|
* 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 {
|
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) {
|
if (error instanceof Error) {
|
||||||
return error.message;
|
return error.message;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user