Add prompt to migrate workspace extensions (#7065)

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
christine betts
2025-08-27 00:43:02 +00:00
committed by GitHub
parent be48414518
commit c79f145b37
8 changed files with 377 additions and 21 deletions

View File

@@ -103,6 +103,8 @@ import { SettingsDialog } from './components/SettingsDialog.js';
import { setUpdateHandler } from '../utils/handleAutoUpdate.js';
import { appEvents, AppEvent } from '../utils/events.js';
import { isNarrowWidth } from './utils/isNarrowWidth.js';
import { useWorkspaceMigration } from './hooks/useWorkspaceMigration.js';
import { WorkspaceMigrationDialog } from './components/WorkspaceMigrationDialog.js';
const CTRL_EXIT_PROMPT_DURATION_MS = 1000;
// Maximum number of queued messages to display in UI to prevent performance issues
@@ -223,6 +225,12 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
>();
const [showEscapePrompt, setShowEscapePrompt] = useState(false);
const [isProcessing, setIsProcessing] = useState<boolean>(false);
const {
showWorkspaceMigrationDialog,
workspaceExtensions,
onWorkspaceMigrationDialogOpen,
onWorkspaceMigrationDialogClose,
} = useWorkspaceMigration(settings);
useEffect(() => {
const unsubscribe = ideContext.subscribeToIdeContext(setIdeContextState);
@@ -1018,8 +1026,13 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
))}
</Box>
)}
{shouldShowIdePrompt && currentIDE ? (
{showWorkspaceMigrationDialog ? (
<WorkspaceMigrationDialog
workspaceExtensions={workspaceExtensions}
onOpen={onWorkspaceMigrationDialogOpen}
onClose={onWorkspaceMigrationDialogClose}
/>
) : shouldShowIdePrompt && currentIDE ? (
<IdeIntegrationNudge
ide={currentIDE}
onComplete={handleIdePromptComplete}
@@ -1167,8 +1180,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
<Box paddingLeft={2}>
<Text dimColor>
... (+
{messageQueue.length -
MAX_DISPLAYED_QUEUED_MESSAGES}{' '}
{messageQueue.length - MAX_DISPLAYED_QUEUED_MESSAGES}
more)
</Text>
</Box>

View File

@@ -0,0 +1,108 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { Box, Text, useInput } from 'ink';
import {
type Extension,
performWorkspaceExtensionMigration,
} from '../../config/extension.js';
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
import { Colors } from '../colors.js';
import { useState } from 'react';
export function WorkspaceMigrationDialog(props: {
workspaceExtensions: Extension[];
onOpen: () => void;
onClose: () => void;
}) {
const { workspaceExtensions, onOpen, onClose } = props;
const [migrationComplete, setMigrationComplete] = useState(false);
const [failedExtensions, setFailedExtensions] = useState<string[]>([]);
onOpen();
const onMigrate = async () => {
const failed =
await performWorkspaceExtensionMigration(workspaceExtensions);
setFailedExtensions(failed);
setMigrationComplete(true);
};
useInput((input) => {
if (migrationComplete && input === 'q') {
process.exit(0);
}
});
if (migrationComplete) {
return (
<Box
flexDirection="column"
borderStyle="round"
borderColor={Colors.Gray}
padding={1}
>
{failedExtensions.length > 0 ? (
<>
<Text>
The following extensions failed to migrate. Please try installing
them manually. To see other changes, Gemini CLI must be restarted.
Press {"'q'"} to quit.
</Text>
<Box flexDirection="column" marginTop={1} marginLeft={2}>
{failedExtensions.map((failed) => (
<Text key={failed}>- {failed}</Text>
))}
</Box>
</>
) : (
<Text>
Migration complete. To see changes, Gemini CLI must be restarted.
Press {"'q'"} to quit.
</Text>
)}
</Box>
);
}
return (
<Box
flexDirection="column"
borderStyle="round"
borderColor={Colors.Gray}
padding={1}
>
<Text bold>Workspace-level extensions are deprecated{'\n'}</Text>
<Text>Would you like to install them at the user level?</Text>
<Text>
The extension definition will remain in your workspace directory.
</Text>
<Text>
If you opt to skip, you can install them manually using the extensions
install command.
</Text>
<Box flexDirection="column" marginTop={1} marginLeft={2}>
{workspaceExtensions.map((extension) => (
<Text key={extension.config.name}>- {extension.config.name}</Text>
))}
</Box>
<Box marginTop={1}>
<RadioButtonSelect
items={[
{ label: 'Install all', value: 'migrate' },
{ label: 'Skip', value: 'skip' },
]}
onSelect={(value: string) => {
if (value === 'migrate') {
onMigrate();
} else {
onClose();
}
}}
/>
</Box>
</Box>
);
}

View File

@@ -0,0 +1,66 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { useState, useEffect } from 'react';
import {
type Extension,
getWorkspaceExtensions,
} from '../../config/extension.js';
import { type LoadedSettings, SettingScope } from '../../config/settings.js';
import process from 'node:process';
export function useWorkspaceMigration(settings: LoadedSettings) {
const [showWorkspaceMigrationDialog, setShowWorkspaceMigrationDialog] =
useState(false);
const [workspaceExtensions, setWorkspaceExtensions] = useState<Extension[]>(
[],
);
useEffect(() => {
if (!settings.merged.extensionManagement) {
return;
}
const cwd = process.cwd();
const extensions = getWorkspaceExtensions(cwd);
if (
extensions.length > 0 &&
!settings.merged.extensions?.workspacesWithMigrationNudge?.includes(cwd)
) {
setWorkspaceExtensions(extensions);
setShowWorkspaceMigrationDialog(true);
console.log(settings.merged.extensions);
}
}, [settings.merged.extensions, settings.merged.extensionManagement]);
const onWorkspaceMigrationDialogOpen = () => {
const userSettings = settings.forScope(SettingScope.User);
const extensionSettings = userSettings.settings.extensions || {
disabled: [],
};
const workspacesWithMigrationNudge =
extensionSettings.workspacesWithMigrationNudge || [];
const cwd = process.cwd();
if (!workspacesWithMigrationNudge.includes(cwd)) {
workspacesWithMigrationNudge.push(cwd);
}
extensionSettings.workspacesWithMigrationNudge =
workspacesWithMigrationNudge;
settings.setValue(SettingScope.User, 'extensions', extensionSettings);
};
const onWorkspaceMigrationDialogClose = () => {
setShowWorkspaceMigrationDialog(false);
};
return {
showWorkspaceMigrationDialog,
workspaceExtensions,
onWorkspaceMigrationDialogOpen,
onWorkspaceMigrationDialogClose,
};
}