mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-20 16:57:46 +00:00
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:
@@ -15,7 +15,9 @@ import {
|
||||
disableExtension,
|
||||
enableExtension,
|
||||
installExtension,
|
||||
loadExtension,
|
||||
loadExtensions,
|
||||
performWorkspaceExtensionMigration,
|
||||
uninstallExtension,
|
||||
updateExtension,
|
||||
} from './extension.js';
|
||||
@@ -46,6 +48,7 @@ vi.mock('child_process', async (importOriginal) => {
|
||||
execSync: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const EXTENSIONS_DIRECTORY_NAME = path.join('.gemini', 'extensions');
|
||||
|
||||
describe('loadExtensions', () => {
|
||||
@@ -430,6 +433,80 @@ describe('uninstallExtension', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('performWorkspaceExtensionMigration', () => {
|
||||
let tempWorkspaceDir: string;
|
||||
let tempHomeDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
tempWorkspaceDir = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), 'gemini-cli-test-workspace-'),
|
||||
);
|
||||
tempHomeDir = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), 'gemini-cli-test-home-'),
|
||||
);
|
||||
vi.mocked(os.homedir).mockReturnValue(tempHomeDir);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(tempWorkspaceDir, { recursive: true, force: true });
|
||||
fs.rmSync(tempHomeDir, { recursive: true, force: true });
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should install the extensions in the user directory', async () => {
|
||||
const workspaceExtensionsDir = path.join(
|
||||
tempWorkspaceDir,
|
||||
EXTENSIONS_DIRECTORY_NAME,
|
||||
);
|
||||
fs.mkdirSync(workspaceExtensionsDir, { recursive: true });
|
||||
const ext1Path = createExtension(workspaceExtensionsDir, 'ext1', '1.0.0');
|
||||
const ext2Path = createExtension(workspaceExtensionsDir, 'ext2', '1.0.0');
|
||||
const extensionsToMigrate = [
|
||||
loadExtension(ext1Path)!,
|
||||
loadExtension(ext2Path)!,
|
||||
];
|
||||
const failed =
|
||||
await performWorkspaceExtensionMigration(extensionsToMigrate);
|
||||
|
||||
expect(failed).toEqual([]);
|
||||
|
||||
const userExtensionsDir = path.join(tempHomeDir, '.gemini', 'extensions');
|
||||
const userExt1Path = path.join(userExtensionsDir, 'ext1');
|
||||
const extensions = loadExtensions(tempWorkspaceDir);
|
||||
|
||||
expect(extensions).toHaveLength(2);
|
||||
const metadataPath = path.join(userExt1Path, INSTALL_METADATA_FILENAME);
|
||||
expect(fs.existsSync(metadataPath)).toBe(true);
|
||||
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf-8'));
|
||||
expect(metadata).toEqual({
|
||||
source: ext1Path,
|
||||
type: 'local',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the names of failed installations', async () => {
|
||||
const workspaceExtensionsDir = path.join(
|
||||
tempWorkspaceDir,
|
||||
EXTENSIONS_DIRECTORY_NAME,
|
||||
);
|
||||
fs.mkdirSync(workspaceExtensionsDir, { recursive: true });
|
||||
|
||||
const ext1Path = createExtension(workspaceExtensionsDir, 'ext1', '1.0.0');
|
||||
|
||||
const extensions = [
|
||||
loadExtension(ext1Path)!,
|
||||
{
|
||||
path: '/ext/path/1',
|
||||
config: { name: 'ext2', version: '1.0.0' },
|
||||
contextFiles: [],
|
||||
},
|
||||
];
|
||||
|
||||
const failed = await performWorkspaceExtensionMigration(extensions);
|
||||
expect(failed).toEqual(['ext2']);
|
||||
});
|
||||
});
|
||||
|
||||
function createExtension(
|
||||
extensionsDir: string,
|
||||
name: string,
|
||||
|
||||
@@ -77,15 +77,51 @@ export class ExtensionStorage {
|
||||
}
|
||||
}
|
||||
|
||||
export function getWorkspaceExtensions(workspaceDir: string): Extension[] {
|
||||
return loadExtensionsFromDir(workspaceDir);
|
||||
}
|
||||
|
||||
async function copyExtension(
|
||||
source: string,
|
||||
destination: string,
|
||||
): Promise<void> {
|
||||
await fs.promises.cp(source, destination, { recursive: true });
|
||||
}
|
||||
|
||||
export async function performWorkspaceExtensionMigration(
|
||||
extensions: Extension[],
|
||||
): Promise<string[]> {
|
||||
const failedInstallNames: string[] = [];
|
||||
|
||||
for (const extension of extensions) {
|
||||
try {
|
||||
const installMetadata: ExtensionInstallMetadata = {
|
||||
source: extension.path,
|
||||
type: 'local',
|
||||
};
|
||||
await installExtension(installMetadata);
|
||||
} catch (_) {
|
||||
failedInstallNames.push(extension.config.name);
|
||||
}
|
||||
}
|
||||
return failedInstallNames;
|
||||
}
|
||||
|
||||
export function loadExtensions(workspaceDir: string): Extension[] {
|
||||
const allExtensions = [
|
||||
...loadExtensionsFromDir(workspaceDir),
|
||||
...loadExtensionsFromDir(os.homedir()),
|
||||
];
|
||||
const settings = loadSettings(workspaceDir).merged;
|
||||
const disabledExtensions = settings.extensions?.disabled ?? [];
|
||||
const allExtensions = [...loadUserExtensions()];
|
||||
|
||||
if (!settings.extensionManagement) {
|
||||
allExtensions.push(...getWorkspaceExtensions(workspaceDir));
|
||||
}
|
||||
|
||||
const uniqueExtensions = new Map<string, Extension>();
|
||||
for (const extension of allExtensions) {
|
||||
if (!uniqueExtensions.has(extension.config.name)) {
|
||||
if (
|
||||
!uniqueExtensions.has(extension.config.name) &&
|
||||
!disabledExtensions.includes(extension.config.name)
|
||||
) {
|
||||
uniqueExtensions.set(extension.config.name, extension);
|
||||
}
|
||||
}
|
||||
@@ -283,18 +319,6 @@ async function cloneFromGit(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies an extension from a source to a destination path.
|
||||
* @param source The source path of the extension.
|
||||
* @param destination The destination path to copy the extension to.
|
||||
*/
|
||||
async function copyExtension(
|
||||
source: string,
|
||||
destination: string,
|
||||
): Promise<void> {
|
||||
await fs.promises.cp(source, destination, { recursive: true });
|
||||
}
|
||||
|
||||
export async function installExtension(
|
||||
installMetadata: ExtensionInstallMetadata,
|
||||
cwd: string = process.cwd(),
|
||||
|
||||
@@ -122,6 +122,10 @@ describe('Settings Loading and Merging', () => {
|
||||
mcpServers: {},
|
||||
includeDirectories: [],
|
||||
chatCompression: {},
|
||||
extensions: {
|
||||
disabled: [],
|
||||
workspacesWithMigrationNudge: [],
|
||||
},
|
||||
});
|
||||
expect(settings.errors.length).toBe(0);
|
||||
});
|
||||
@@ -157,6 +161,10 @@ describe('Settings Loading and Merging', () => {
|
||||
mcpServers: {},
|
||||
includeDirectories: [],
|
||||
chatCompression: {},
|
||||
extensions: {
|
||||
disabled: [],
|
||||
workspacesWithMigrationNudge: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -192,6 +200,10 @@ describe('Settings Loading and Merging', () => {
|
||||
mcpServers: {},
|
||||
includeDirectories: [],
|
||||
chatCompression: {},
|
||||
extensions: {
|
||||
disabled: [],
|
||||
workspacesWithMigrationNudge: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -225,6 +237,10 @@ describe('Settings Loading and Merging', () => {
|
||||
mcpServers: {},
|
||||
includeDirectories: [],
|
||||
chatCompression: {},
|
||||
extensions: {
|
||||
disabled: [],
|
||||
workspacesWithMigrationNudge: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -264,6 +280,10 @@ describe('Settings Loading and Merging', () => {
|
||||
mcpServers: {},
|
||||
includeDirectories: [],
|
||||
chatCompression: {},
|
||||
extensions: {
|
||||
disabled: [],
|
||||
workspacesWithMigrationNudge: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -315,6 +335,10 @@ describe('Settings Loading and Merging', () => {
|
||||
mcpServers: {},
|
||||
includeDirectories: [],
|
||||
chatCompression: {},
|
||||
extensions: {
|
||||
disabled: [],
|
||||
workspacesWithMigrationNudge: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -377,6 +401,10 @@ describe('Settings Loading and Merging', () => {
|
||||
'/system/dir',
|
||||
],
|
||||
chatCompression: {},
|
||||
extensions: {
|
||||
disabled: [],
|
||||
workspacesWithMigrationNudge: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -537,6 +565,7 @@ describe('Settings Loading and Merging', () => {
|
||||
);
|
||||
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
|
||||
expect(settings.user.settings.excludedProjectEnvVars).toEqual([
|
||||
'DEBUG',
|
||||
'NODE_ENV',
|
||||
@@ -944,6 +973,10 @@ describe('Settings Loading and Merging', () => {
|
||||
mcpServers: {},
|
||||
includeDirectories: [],
|
||||
chatCompression: {},
|
||||
extensions: {
|
||||
disabled: [],
|
||||
workspacesWithMigrationNudge: [],
|
||||
},
|
||||
});
|
||||
|
||||
// Check that error objects are populated in settings.errors
|
||||
@@ -1316,6 +1349,10 @@ describe('Settings Loading and Merging', () => {
|
||||
mcpServers: {},
|
||||
includeDirectories: [],
|
||||
chatCompression: {},
|
||||
extensions: {
|
||||
disabled: [],
|
||||
workspacesWithMigrationNudge: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -133,6 +133,28 @@ function mergeSettings(
|
||||
...(safeWorkspace.chatCompression || {}),
|
||||
...(system.chatCompression || {}),
|
||||
},
|
||||
extensions: {
|
||||
...(systemDefaults.extensions || {}),
|
||||
...(user.extensions || {}),
|
||||
...(safeWorkspace.extensions || {}),
|
||||
...(system.extensions || {}),
|
||||
disabled: [
|
||||
...new Set([
|
||||
...(systemDefaults.extensions?.disabled || []),
|
||||
...(user.extensions?.disabled || []),
|
||||
...(safeWorkspace.extensions?.disabled || []),
|
||||
...(system.extensions?.disabled || []),
|
||||
]),
|
||||
],
|
||||
workspacesWithMigrationNudge: [
|
||||
...new Set([
|
||||
...(systemDefaults.extensions?.workspacesWithMigrationNudge || []),
|
||||
...(user.extensions?.workspacesWithMigrationNudge || []),
|
||||
...(safeWorkspace.extensions?.workspacesWithMigrationNudge || []),
|
||||
...(system.extensions?.workspacesWithMigrationNudge || []),
|
||||
]),
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -572,6 +572,16 @@ export const SETTINGS_SCHEMA = {
|
||||
description: 'List of disabled extensions.',
|
||||
showInDialog: false,
|
||||
},
|
||||
workspacesWithMigrationNudge: {
|
||||
type: 'array',
|
||||
label: 'Workspaces with Migration Nudge',
|
||||
category: 'Extensions',
|
||||
requiresRestart: false,
|
||||
default: [] as string[],
|
||||
description:
|
||||
'List of workspaces for which the migration nudge has been shown.',
|
||||
showInDialog: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
skipNextSpeakerCheck: {
|
||||
|
||||
Reference in New Issue
Block a user