fix(cli): resolve build failures and consolidate type guards

This commit is contained in:
mkorwel
2026-03-13 18:07:38 +00:00
committed by Matt Korwel
parent 5605670d6d
commit 112a4195bd
12 changed files with 59 additions and 57 deletions
@@ -95,6 +95,8 @@ export const uninstallCommand: CommandModule = {
let namesArray: string[] = [];
if (Array.isArray(names)) {
namesArray = names.filter((n): n is string => typeof n === 'string');
} else if (typeof names === 'string') {
namesArray = [names];
}
await handleUninstall({
names: namesArray,
+1 -4
View File
@@ -11,6 +11,7 @@ import { debugLogger, getErrorMessage } from '@google/gemini-cli-core';
import { loadSettings, SettingScope } from '../../config/settings.js';
import { exitCli } from '../utils.js';
import stripJsonComments from 'strip-json-comments';
import { isRecord } from '../../utils/typeGuards.js';
/**
* Mapping from Claude Code event names to Gemini event names
@@ -58,10 +59,6 @@ function transformMatcher(matcher: string | undefined): string | undefined {
return transformed;
}
function isRecord(obj: unknown): obj is Record<string, unknown> {
return typeof obj === 'object' && obj !== null && !Array.isArray(obj);
}
/**
* Migrate a Claude Code hook configuration to Gemini format
*/
+9 -1
View File
@@ -213,7 +213,15 @@ export const addCommand: CommandModule = {
// Handle -- separator args as server args if present
if (argv['--'] && Array.isArray(argv['--'])) {
const args = argv['args'];
const existingArgs = Array.isArray(args) ? args : [];
let existingArgs: Array<string | number> = [];
if (Array.isArray(args)) {
existingArgs = args.filter(
(a): a is string | number =>
typeof a === 'string' || typeof a === 'number',
);
} else if (typeof args === 'string' || typeof args === 'number') {
existingArgs = [args];
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
argv['args'] = [...existingArgs, ...argv['--']];
}
@@ -14,7 +14,7 @@ import {
type SlashCommand,
type CommandContext,
} from './types.js';
import { MessageType, type HistoryItem } from '../types.js';
import { MessageType, type HistoryItemWithoutId } from '../types.js';
import {
refreshServerHierarchicalMemory,
type Config,
@@ -29,10 +29,7 @@ import * as fs from 'node:fs';
async function finishAddingDirectories(
config: Config,
addItem: (
itemData: Omit<HistoryItem, 'id'>,
baseTimestamp?: number,
) => number,
addItem: (itemData: HistoryItemWithoutId, baseTimestamp?: number) => number,
added: string[],
errors: string[],
) {
@@ -209,8 +209,8 @@ async function restartAction(
const s = extensionsToRestart.length > 1 ? 's' : '';
const reloadingMessage = {
type: MessageType.INFO,
const reloadingMessage: HistoryItemInfo = {
type: 'info',
text: `Reloading ${extensionsToRestart.length} extension${s}...`,
color: theme.text.primary,
};
@@ -472,8 +472,7 @@ async function installAction(
args: string,
requestConsentOverride?: (consent: string) => Promise<boolean>,
) {
const extensionLoader =
context.services.agentContext?.config.getExtensionLoader();
const extensionLoader = context.services.config?.getExtensionLoader();
if (!(extensionLoader instanceof ExtensionManager)) {
debugLogger.error(
`Cannot ${context.invocation?.name} extensions in this environment`,
@@ -16,7 +16,7 @@ import { useKeypress } from '../hooks/useKeypress.js';
import { loadTrustedFolders, TrustLevel } from '../../config/trustedFolders.js';
import { expandHomeDir } from '../utils/directoryUtils.js';
import * as path from 'node:path';
import { MessageType, type HistoryItem } from '../types.js';
import { MessageType, type HistoryItemWithoutId } from '../types.js';
import { type Config } from '@google/gemini-cli-core';
export enum MultiFolderTrustChoice {
@@ -32,18 +32,12 @@ export interface MultiFolderTrustDialogProps {
errors: string[];
finishAddingDirectories: (
config: Config,
addItem: (
itemData: Omit<HistoryItem, 'id'>,
baseTimestamp?: number,
) => number,
addItem: (itemData: HistoryItemWithoutId, baseTimestamp?: number) => number,
added: string[],
errors: string[],
) => Promise<void>;
config: Config;
addItem: (
itemData: Omit<HistoryItem, 'id'>,
baseTimestamp?: number,
) => number;
addItem: (itemData: HistoryItemWithoutId, baseTimestamp?: number) => number;
}
export const MultiFolderTrustDialog: React.FC<MultiFolderTrustDialogProps> = ({
@@ -63,10 +63,7 @@ import {
LogoutChoice,
} from '../components/LogoutConfirmationDialog.js';
import { runExitCleanup } from '../../utils/cleanup.js';
function isRecord(obj: unknown): obj is Record<string, unknown> {
return typeof obj === 'object' && obj !== null && !Array.isArray(obj);
}
import { isRecord } from '../../utils/typeGuards.js';
interface SlashCommandProcessorActions {
openAuthDialog: () => void;
@@ -8,7 +8,7 @@ import { describe, it, expect } from 'vitest';
import { act } from 'react';
import { renderHook } from '../../test-utils/render.js';
import { useHistory } from './useHistoryManager.js';
import type { HistoryItem } from '../types.js';
import type { HistoryItem, HistoryItemWithoutId } from '../types.js';
describe('useHistoryManager', () => {
it('should initialize with an empty history', async () => {
@@ -19,7 +19,7 @@ describe('useHistoryManager', () => {
it('should add an item to history with a unique ID', async () => {
const { result } = await renderHook(() => useHistory());
const timestamp = Date.now();
const itemData: Omit<HistoryItem, 'id'> = {
const itemData: HistoryItemWithoutId = {
type: 'user', // Replaced HistoryItemType.User
text: 'Hello',
};
@@ -92,11 +92,11 @@ describe('useHistoryManager', () => {
it('should generate unique IDs for items added with the same base timestamp', async () => {
const { result } = await renderHook(() => useHistory());
const timestamp = Date.now();
const itemData1: Omit<HistoryItem, 'id'> = {
const itemData1: HistoryItemWithoutId = {
type: 'user', // Replaced HistoryItemType.User
text: 'First',
};
const itemData2: Omit<HistoryItem, 'id'> = {
const itemData2: HistoryItemWithoutId = {
type: 'gemini', // Replaced HistoryItemType.Gemini
text: 'Second',
};
@@ -120,7 +120,7 @@ describe('useHistoryManager', () => {
it('should update an existing history item', async () => {
const { result } = await renderHook(() => useHistory());
const timestamp = Date.now();
const initialItem: Omit<HistoryItem, 'id'> = {
const initialItem: HistoryItemWithoutId = {
type: 'gemini', // Replaced HistoryItemType.Gemini
text: 'Initial content',
};
@@ -146,7 +146,7 @@ describe('useHistoryManager', () => {
it('should not change history if updateHistoryItem is called with a nonexistent ID', async () => {
const { result } = await renderHook(() => useHistory());
const timestamp = Date.now();
const itemData: Omit<HistoryItem, 'id'> = {
const itemData: HistoryItemWithoutId = {
type: 'user', // Replaced HistoryItemType.User
text: 'Hello',
};
@@ -167,11 +167,11 @@ describe('useHistoryManager', () => {
it('should clear the history', async () => {
const { result } = await renderHook(() => useHistory());
const timestamp = Date.now();
const itemData1: Omit<HistoryItem, 'id'> = {
const itemData1: HistoryItemWithoutId = {
type: 'user', // Replaced HistoryItemType.User
text: 'First',
};
const itemData2: Omit<HistoryItem, 'id'> = {
const itemData2: HistoryItemWithoutId = {
type: 'gemini', // Replaced HistoryItemType.Gemini
text: 'Second',
};
@@ -193,19 +193,19 @@ describe('useHistoryManager', () => {
it('should not add consecutive duplicate user messages', async () => {
const { result } = await renderHook(() => useHistory());
const timestamp = Date.now();
const itemData1: Omit<HistoryItem, 'id'> = {
const itemData1: HistoryItemWithoutId = {
type: 'user', // Replaced HistoryItemType.User
text: 'Duplicate message',
};
const itemData2: Omit<HistoryItem, 'id'> = {
const itemData2: HistoryItemWithoutId = {
type: 'user', // Replaced HistoryItemType.User
text: 'Duplicate message',
};
const itemData3: Omit<HistoryItem, 'id'> = {
const itemData3: HistoryItemWithoutId = {
type: 'gemini', // Replaced HistoryItemType.Gemini
text: 'Gemini response',
};
const itemData4: Omit<HistoryItem, 'id'> = {
const itemData4: HistoryItemWithoutId = {
type: 'user', // Replaced HistoryItemType.User
text: 'Another user message',
};
@@ -226,15 +226,15 @@ describe('useHistoryManager', () => {
it('should add duplicate user messages if they are not consecutive', async () => {
const { result } = await renderHook(() => useHistory());
const timestamp = Date.now();
const itemData1: Omit<HistoryItem, 'id'> = {
const itemData1: HistoryItemWithoutId = {
type: 'user', // Replaced HistoryItemType.User
text: 'Message 1',
};
const itemData2: Omit<HistoryItem, 'id'> = {
const itemData2: HistoryItemWithoutId = {
type: 'gemini', // Replaced HistoryItemType.Gemini
text: 'Gemini response',
};
const itemData3: Omit<HistoryItem, 'id'> = {
const itemData3: HistoryItemWithoutId = {
type: 'user', // Replaced HistoryItemType.User
text: 'Message 1', // Duplicate text, but not consecutive
};
@@ -254,7 +254,7 @@ describe('useHistoryManager', () => {
it('should use Date.now() as default baseTimestamp if not provided', async () => {
const { result } = await renderHook(() => useHistory());
const before = Date.now();
const itemData: Omit<HistoryItem, 'id'> = {
const itemData: HistoryItemWithoutId = {
type: 'user',
text: 'Default timestamp test',
};
@@ -5,24 +5,24 @@
*/
import { useState, useRef, useCallback, useMemo } from 'react';
import type { HistoryItem } from '../types.js';
import type { HistoryItem, HistoryItemWithoutId } from '../types.js';
import type { ChatRecordingService } from '@google/gemini-cli-core/src/services/chatRecordingService.js';
// Type for the updater function passed to updateHistoryItem
type HistoryItemUpdater = (
prevItem: HistoryItem,
) => Partial<Omit<HistoryItem, 'id'>>;
) => Partial<HistoryItemWithoutId>;
export interface UseHistoryManagerReturn {
history: HistoryItem[];
addItem: (
itemData: Omit<HistoryItem, 'id'>,
itemData: HistoryItemWithoutId,
baseTimestamp?: number,
isResuming?: boolean,
) => number; // Returns the generated ID
updateItem: (
id: number,
updates: Partial<Omit<HistoryItem, 'id'>> | HistoryItemUpdater,
updates: Partial<HistoryItemWithoutId> | HistoryItemUpdater,
) => void;
clearItems: () => void;
loadHistory: (newHistory: HistoryItem[]) => void;
@@ -63,7 +63,7 @@ export function useHistory({
// Adds a new item to the history state with a unique ID.
const addItem = useCallback(
(
itemData: Omit<HistoryItem, 'id'>,
itemData: HistoryItemWithoutId,
baseTimestamp: number = Date.now(),
isResuming: boolean = false,
): number => {
@@ -138,7 +138,7 @@ export function useHistory({
const updateItem = useCallback(
(
id: number,
updates: Partial<Omit<HistoryItem, 'id'>> | HistoryItemUpdater,
updates: Partial<HistoryItemWithoutId> | HistoryItemUpdater,
) => {
setHistory((prevHistory) =>
prevHistory.map((item) => {
@@ -14,14 +14,11 @@ import {
} from '@google/gemini-cli-core';
import { MultiFolderTrustDialog } from '../components/MultiFolderTrustDialog.js';
import type { UseHistoryManagerReturn } from './useHistoryManager.js';
import { MessageType, type HistoryItem } from '../types.js';
import { MessageType, type HistoryItemWithoutId } from '../types.js';
async function finishAddingDirectories(
config: Config,
addItem: (
itemData: Omit<HistoryItem, 'id'>,
baseTimestamp?: number,
) => number,
addItem: (itemData: HistoryItemWithoutId, baseTimestamp?: number) => number,
added: string[],
errors: string[],
) {
+2 -3
View File
@@ -8,7 +8,7 @@ import type { UpdateObject } from '../ui/utils/updateCheck.js';
import type { LoadedSettings } from '../config/settings.js';
import { getInstallationInfo, PackageManager } from './installationInfo.js';
import { updateEventEmitter } from './updateEventEmitter.js';
import { MessageType, type HistoryItem } from '../ui/types.js';
import { MessageType, type HistoryItemWithoutId } from '../ui/types.js';
import { spawnWrapper } from './spawnWrapper.js';
import type { spawn } from 'node:child_process';
import { debugLogger } from '@google/gemini-cli-core';
@@ -115,7 +115,6 @@ export function handleAutoUpdate(
}
updateEventEmitter.emit('update-received', {
...info,
message: combinedMessage,
isUpdating: true,
});
if (_updateInProgress) {
@@ -163,7 +162,7 @@ export function handleAutoUpdate(
}
export function setUpdateHandler(
addItem: (item: Omit<HistoryItem, 'id'>, timestamp: number) => void,
addItem: (item: HistoryItemWithoutId, timestamp: number) => void,
setUpdateInfo: (info: UpdateObject | null) => void,
) {
let successfullyInstalled = false;
+12
View File
@@ -0,0 +1,12 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* Type guard to check if a value is a non-null object and not an array.
*/
export function isRecord(obj: unknown): obj is Record<string, unknown> {
return typeof obj === 'object' && obj !== null && !Array.isArray(obj);
}