mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 12:54:07 -07:00
Reinstate support for updating locally-installed extensions (#8833)
This commit is contained in:
@@ -20,7 +20,6 @@ interface InstallArgs {
|
|||||||
export async function handleInstall(args: InstallArgs) {
|
export async function handleInstall(args: InstallArgs) {
|
||||||
try {
|
try {
|
||||||
let installMetadata: ExtensionInstallMetadata;
|
let installMetadata: ExtensionInstallMetadata;
|
||||||
|
|
||||||
if (args.source) {
|
if (args.source) {
|
||||||
const { source } = args;
|
const { source } = args;
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -35,28 +35,7 @@ export async function handleUpdate(args: UpdateArgs) {
|
|||||||
allExtensions.map((e) => e.config.name),
|
allExtensions.map((e) => e.config.name),
|
||||||
workingDir,
|
workingDir,
|
||||||
);
|
);
|
||||||
|
if (args.name) {
|
||||||
if (args.all) {
|
|
||||||
try {
|
|
||||||
let updateInfos = await updateAllUpdatableExtensions(
|
|
||||||
workingDir,
|
|
||||||
extensions,
|
|
||||||
await checkForAllExtensionUpdates(extensions, new Map(), (_) => {}),
|
|
||||||
() => {},
|
|
||||||
);
|
|
||||||
updateInfos = updateInfos.filter(
|
|
||||||
(info) => info.originalVersion !== info.updatedVersion,
|
|
||||||
);
|
|
||||||
if (updateInfos.length === 0) {
|
|
||||||
console.log('No extensions to update.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log(updateInfos.map((info) => updateOutput(info)).join('\n'));
|
|
||||||
} catch (error) {
|
|
||||||
console.error(getErrorMessage(error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (args.name)
|
|
||||||
try {
|
try {
|
||||||
const extension = extensions.find(
|
const extension = extensions.find(
|
||||||
(extension) => extension.name === args.name,
|
(extension) => extension.name === args.name,
|
||||||
@@ -99,10 +78,31 @@ export async function handleUpdate(args: UpdateArgs) {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(getErrorMessage(error));
|
console.error(getErrorMessage(error));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (args.all) {
|
||||||
|
try {
|
||||||
|
let updateInfos = await updateAllUpdatableExtensions(
|
||||||
|
workingDir,
|
||||||
|
extensions,
|
||||||
|
await checkForAllExtensionUpdates(extensions, new Map(), (_) => {}),
|
||||||
|
() => {},
|
||||||
|
);
|
||||||
|
updateInfos = updateInfos.filter(
|
||||||
|
(info) => info.originalVersion !== info.updatedVersion,
|
||||||
|
);
|
||||||
|
if (updateInfos.length === 0) {
|
||||||
|
console.log('No extensions to update.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(updateInfos.map((info) => updateOutput(info)).join('\n'));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(getErrorMessage(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateCommand: CommandModule = {
|
export const updateCommand: CommandModule = {
|
||||||
command: 'update [--all] [name]',
|
command: 'update [<name>] [--all]',
|
||||||
describe:
|
describe:
|
||||||
'Updates all extensions or a named extension to the latest version.',
|
'Updates all extensions or a named extension to the latest version.',
|
||||||
builder: (yargs) =>
|
builder: (yargs) =>
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ describe('git extension helpers', () => {
|
|||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
isActive: true,
|
isActive: true,
|
||||||
installMetadata: {
|
installMetadata: {
|
||||||
type: 'local',
|
type: 'link',
|
||||||
source: '',
|
source: '',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import * as https from 'node:https';
|
|||||||
import * as fs from 'node:fs';
|
import * as fs from 'node:fs';
|
||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
import { execSync } from 'node:child_process';
|
import { execSync } from 'node:child_process';
|
||||||
|
import { loadExtension } from '../extension.js';
|
||||||
|
|
||||||
function getGitHubToken(): string | undefined {
|
function getGitHubToken(): string | undefined {
|
||||||
return process.env['GITHUB_TOKEN'];
|
return process.env['GITHUB_TOKEN'];
|
||||||
@@ -115,9 +116,29 @@ async function fetchFromGithub(
|
|||||||
export async function checkForExtensionUpdate(
|
export async function checkForExtensionUpdate(
|
||||||
extension: GeminiCLIExtension,
|
extension: GeminiCLIExtension,
|
||||||
setExtensionUpdateState: (updateState: ExtensionUpdateState) => void,
|
setExtensionUpdateState: (updateState: ExtensionUpdateState) => void,
|
||||||
|
cwd: string = process.cwd(),
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
setExtensionUpdateState(ExtensionUpdateState.CHECKING_FOR_UPDATES);
|
setExtensionUpdateState(ExtensionUpdateState.CHECKING_FOR_UPDATES);
|
||||||
const installMetadata = extension.installMetadata;
|
const installMetadata = extension.installMetadata;
|
||||||
|
if (installMetadata?.type === 'local') {
|
||||||
|
const newExtension = loadExtension({
|
||||||
|
extensionDir: installMetadata.source,
|
||||||
|
workspaceDir: cwd,
|
||||||
|
});
|
||||||
|
if (!newExtension) {
|
||||||
|
console.error(
|
||||||
|
`Failed to check for update for local extension "${extension.name}". Could not load extension from source path: ${installMetadata.source}`,
|
||||||
|
);
|
||||||
|
setExtensionUpdateState(ExtensionUpdateState.ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (newExtension.config.version !== extension.version) {
|
||||||
|
setExtensionUpdateState(ExtensionUpdateState.UPDATE_AVAILABLE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setExtensionUpdateState(ExtensionUpdateState.UP_TO_DATE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
!installMetadata ||
|
!installMetadata ||
|
||||||
(installMetadata.type !== 'git' &&
|
(installMetadata.type !== 'git' &&
|
||||||
|
|||||||
@@ -341,17 +341,24 @@ describe('update tests', () => {
|
|||||||
expect(result).toBe(ExtensionUpdateState.UP_TO_DATE);
|
expect(result).toBe(ExtensionUpdateState.UP_TO_DATE);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return NotUpdatable for a non-git extension', async () => {
|
it('should return UpToDate for a local extension with no updates', async () => {
|
||||||
const extensionDir = createExtension({
|
const localExtensionSourcePath = path.join(tempHomeDir, 'local-source');
|
||||||
|
const sourceExtensionDir = createExtension({
|
||||||
|
extensionsDir: localExtensionSourcePath,
|
||||||
|
name: 'my-local-ext',
|
||||||
|
version: '1.0.0',
|
||||||
|
});
|
||||||
|
|
||||||
|
const installedExtensionDir = createExtension({
|
||||||
extensionsDir: userExtensionsDir,
|
extensionsDir: userExtensionsDir,
|
||||||
name: 'local-extension',
|
name: 'local-extension',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
installMetadata: { source: '/local/path', type: 'local' },
|
installMetadata: { source: sourceExtensionDir, type: 'local' },
|
||||||
});
|
});
|
||||||
const extension = annotateActiveExtensions(
|
const extension = annotateActiveExtensions(
|
||||||
[
|
[
|
||||||
loadExtension({
|
loadExtension({
|
||||||
extensionDir,
|
extensionDir: installedExtensionDir,
|
||||||
workspaceDir: tempWorkspaceDir,
|
workspaceDir: tempWorkspaceDir,
|
||||||
})!,
|
})!,
|
||||||
],
|
],
|
||||||
@@ -369,9 +376,51 @@ describe('update tests', () => {
|
|||||||
extensionState = newState;
|
extensionState = newState;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
tempWorkspaceDir,
|
||||||
);
|
);
|
||||||
const result = results.get('local-extension');
|
const result = results.get('local-extension');
|
||||||
expect(result).toBe(ExtensionUpdateState.NOT_UPDATABLE);
|
expect(result).toBe(ExtensionUpdateState.UP_TO_DATE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return UpdateAvailable for a local extension with updates', async () => {
|
||||||
|
const localExtensionSourcePath = path.join(tempHomeDir, 'local-source');
|
||||||
|
const sourceExtensionDir = createExtension({
|
||||||
|
extensionsDir: localExtensionSourcePath,
|
||||||
|
name: 'my-local-ext',
|
||||||
|
version: '1.1.0',
|
||||||
|
});
|
||||||
|
|
||||||
|
const installedExtensionDir = createExtension({
|
||||||
|
extensionsDir: userExtensionsDir,
|
||||||
|
name: 'local-extension',
|
||||||
|
version: '1.0.0',
|
||||||
|
installMetadata: { source: sourceExtensionDir, type: 'local' },
|
||||||
|
});
|
||||||
|
const extension = annotateActiveExtensions(
|
||||||
|
[
|
||||||
|
loadExtension({
|
||||||
|
extensionDir: installedExtensionDir,
|
||||||
|
workspaceDir: tempWorkspaceDir,
|
||||||
|
})!,
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
process.cwd(),
|
||||||
|
)[0];
|
||||||
|
let extensionState = new Map();
|
||||||
|
const results = await checkForAllExtensionUpdates(
|
||||||
|
[extension],
|
||||||
|
extensionState,
|
||||||
|
(newState) => {
|
||||||
|
if (typeof newState === 'function') {
|
||||||
|
newState(extensionState);
|
||||||
|
} else {
|
||||||
|
extensionState = newState;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tempWorkspaceDir,
|
||||||
|
);
|
||||||
|
const result = results.get('local-extension');
|
||||||
|
expect(result).toBe(ExtensionUpdateState.UPDATE_AVAILABLE);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return Error when git check fails', async () => {
|
it('should return Error when git check fails', async () => {
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ export async function checkForAllExtensionUpdates(
|
|||||||
setExtensionsUpdateState: Dispatch<
|
setExtensionsUpdateState: Dispatch<
|
||||||
SetStateAction<Map<string, ExtensionUpdateState>>
|
SetStateAction<Map<string, ExtensionUpdateState>>
|
||||||
>,
|
>,
|
||||||
|
cwd: string = process.cwd(),
|
||||||
): Promise<Map<string, ExtensionUpdateState>> {
|
): Promise<Map<string, ExtensionUpdateState>> {
|
||||||
for (const extension of extensions) {
|
for (const extension of extensions) {
|
||||||
const initialState = extensionsUpdateState.get(extension.name);
|
const initialState = extensionsUpdateState.get(extension.name);
|
||||||
@@ -143,13 +144,17 @@ export async function checkForAllExtensionUpdates(
|
|||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
await checkForExtensionUpdate(extension, (updatedState) => {
|
await checkForExtensionUpdate(
|
||||||
setExtensionsUpdateState((prev) => {
|
extension,
|
||||||
extensionsUpdateState = new Map(prev);
|
(updatedState) => {
|
||||||
extensionsUpdateState.set(extension.name, updatedState);
|
setExtensionsUpdateState((prev) => {
|
||||||
return extensionsUpdateState;
|
extensionsUpdateState = new Map(prev);
|
||||||
});
|
extensionsUpdateState.set(extension.name, updatedState);
|
||||||
});
|
return extensionsUpdateState;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
cwd,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return extensionsUpdateState;
|
return extensionsUpdateState;
|
||||||
|
|||||||
Reference in New Issue
Block a user