Re-request consent if necessary when updating extensions (#9517)

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
Jacob MacDonald
2025-09-25 10:57:59 -07:00
committed by GitHub
parent e209724789
commit a0c8e3bf2b
14 changed files with 279 additions and 74 deletions

View File

@@ -153,11 +153,14 @@ describe('extensionsCommand', () => {
it('should update a single extension by name', async () => {
const extension: GeminiCLIExtension = {
name: 'ext-one',
type: 'git',
version: '1.0.0',
isActive: true,
path: '/test/dir/ext-one',
autoUpdate: false,
installMetadata: {
type: 'git',
autoUpdate: false,
source: 'https://github.com/some/extension.git',
},
};
mockUpdateExtension.mockResolvedValue({
name: extension.name,
@@ -173,6 +176,7 @@ describe('extensionsCommand', () => {
expect(mockUpdateExtension).toHaveBeenCalledWith(
extension,
'/test/dir',
expect.any(Function),
ExtensionUpdateState.UPDATE_AVAILABLE,
expect.any(Function),
);
@@ -194,19 +198,25 @@ describe('extensionsCommand', () => {
it('should update multiple extensions by name', async () => {
const extensionOne: GeminiCLIExtension = {
name: 'ext-one',
type: 'git',
version: '1.0.0',
isActive: true,
path: '/test/dir/ext-one',
autoUpdate: false,
installMetadata: {
type: 'git',
autoUpdate: false,
source: 'https://github.com/some/extension.git',
},
};
const extensionTwo: GeminiCLIExtension = {
name: 'ext-two',
type: 'git',
version: '1.0.0',
isActive: true,
path: '/test/dir/ext-two',
autoUpdate: false,
installMetadata: {
type: 'git',
autoUpdate: false,
source: 'https://github.com/some/extension.git',
},
};
mockGetExtensions.mockReturnValue([extensionOne, extensionTwo]);
mockContext.ui.extensionsUpdateState.set(

View File

@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { requestConsentInteractive } from '../../config/extension.js';
import {
updateAllUpdatableExtensions,
type ExtensionUpdateInfo,
@@ -51,6 +52,9 @@ async function updateAction(context: CommandContext, args: string) {
if (all) {
updateInfos = await updateAllUpdatableExtensions(
context.services.config!.getWorkingDir(),
// We don't have the ability to prompt for consent yet in this flow.
(description) =>
requestConsentInteractive(description, context.ui.addItem),
context.services.config!.getExtensions(),
context.ui.extensionsUpdateState,
context.ui.setExtensionsUpdateState,
@@ -75,6 +79,8 @@ async function updateAction(context: CommandContext, args: string) {
const updateInfo = await updateExtension(
extension,
workingDir,
(description) =>
requestConsentInteractive(description, context.ui.addItem),
context.ui.extensionsUpdateState.get(extension.name) ??
ExtensionUpdateState.UNKNOWN,
(updateState) => {

View File

@@ -23,8 +23,11 @@ export function WorkspaceMigrationDialog(props: {
const [failedExtensions, setFailedExtensions] = useState<string[]>([]);
onOpen();
const onMigrate = async () => {
const failed =
await performWorkspaceExtensionMigration(workspaceExtensions);
const failed = await performWorkspaceExtensionMigration(
workspaceExtensions,
// We aren't updating extensions, just moving them around, don't need to ask for consent.
async (_) => true,
);
setFailedExtensions(failed);
setMigrationComplete(true);
};

View File

@@ -91,7 +91,7 @@ describe('<ExtensionsList />', () => {
},
{
state: ExtensionUpdateState.ERROR,
expectedText: '(error checking for updates)',
expectedText: '(error)',
},
{
state: ExtensionUpdateState.UP_TO_DATE,

View File

@@ -14,6 +14,7 @@ import {
checkForAllExtensionUpdates,
updateExtension,
} from '../../config/extensions/update.js';
import { requestConsentInteractive } from '../../config/extension.js';
export const useExtensionUpdates = (
extensions: GeminiCLIExtension[],
@@ -40,13 +41,19 @@ export const useExtensionUpdates = (
continue;
}
if (extension.installMetadata?.autoUpdate) {
updateExtension(extension, cwd, currentState, (newState) => {
setExtensionsUpdateState((prev) => {
const finalState = new Map(prev);
finalState.set(extension.name, newState);
return finalState;
});
})
updateExtension(
extension,
cwd,
(description) => requestConsentInteractive(description, addItem),
currentState,
(newState) => {
setExtensionsUpdateState((prev) => {
const finalState = new Map(prev);
finalState.set(extension.name, newState);
return finalState;
});
},
)
.then((result) => {
if (!result) return;
addItem(

View File

@@ -10,7 +10,7 @@ export enum ExtensionUpdateState {
UPDATING = 'updating',
UPDATE_AVAILABLE = 'update available',
UP_TO_DATE = 'up to date',
ERROR = 'error checking for updates',
ERROR = 'error',
NOT_UPDATABLE = 'not updatable',
UNKNOWN = 'unknown',
}