feature: handle removed extensions (#8797)
This commit is contained in:
@@ -9,7 +9,7 @@ import {
|
|||||||
removeExtension as apiRemoveExtension,
|
removeExtension as apiRemoveExtension,
|
||||||
providers,
|
providers,
|
||||||
} from '../api';
|
} from '../api';
|
||||||
import { syncBundledExtensions } from './settings/extensions';
|
import { pruneDeprecatedBundledExtensions, syncBundledExtensions } from './settings/extensions';
|
||||||
import type {
|
import type {
|
||||||
ConfigResponse,
|
ConfigResponse,
|
||||||
UpsertConfigQuery,
|
UpsertConfigQuery,
|
||||||
@@ -88,16 +88,19 @@ export const ConfigProvider: React.FC<ConfigProviderProps> = ({ children }) => {
|
|||||||
[reloadConfig]
|
[reloadConfig]
|
||||||
);
|
);
|
||||||
|
|
||||||
const read = useCallback(async (key: string, is_secret: boolean = false, options?: { throwOnError?: boolean }) => {
|
const read = useCallback(
|
||||||
const query: ConfigKeyQuery = { key: key, is_secret: is_secret };
|
async (key: string, is_secret: boolean = false, options?: { throwOnError?: boolean }) => {
|
||||||
const response = await readConfig({
|
const query: ConfigKeyQuery = { key: key, is_secret: is_secret };
|
||||||
body: query,
|
const response = await readConfig({
|
||||||
});
|
body: query,
|
||||||
if (options?.throwOnError && response.error) {
|
});
|
||||||
throw response.error;
|
if (options?.throwOnError && response.error) {
|
||||||
}
|
throw response.error;
|
||||||
return response.data;
|
}
|
||||||
}, []);
|
return response.data;
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const remove = useCallback(
|
const remove = useCallback(
|
||||||
async (key: string, is_secret: boolean) => {
|
async (key: string, is_secret: boolean) => {
|
||||||
@@ -226,6 +229,10 @@ export const ConfigProvider: React.FC<ConfigProviderProps> = ({ children }) => {
|
|||||||
const query: ExtensionQuery = { name, config, enabled };
|
const query: ExtensionQuery = { name, config, enabled };
|
||||||
await apiAddExtension({ body: query });
|
await apiAddExtension({ body: query });
|
||||||
};
|
};
|
||||||
|
const removeExtensionForSync = async (name: string) => {
|
||||||
|
await apiRemoveExtension({ path: { name } });
|
||||||
|
};
|
||||||
|
extensions = await pruneDeprecatedBundledExtensions(extensions, removeExtensionForSync);
|
||||||
await syncBundledExtensions(extensions, addExtensionForSync);
|
await syncBundledExtensions(extensions, addExtensionForSync);
|
||||||
// Reload extensions after sync
|
// Reload extensions after sync
|
||||||
const refreshedResponse = await apiGetExtensions();
|
const refreshedResponse = await apiGetExtensions();
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Switch } from '../ui/switch';
|
|||||||
import { FixedExtensionEntry, useConfig } from '../ConfigContext';
|
import { FixedExtensionEntry, useConfig } from '../ConfigContext';
|
||||||
import { toastService } from '../../toasts';
|
import { toastService } from '../../toasts';
|
||||||
import { formatExtensionName } from '../settings/extensions/subcomponents/ExtensionList';
|
import { formatExtensionName } from '../settings/extensions/subcomponents/ExtensionList';
|
||||||
|
import { nameToKey } from '../settings/extensions/utils';
|
||||||
import { ExtensionConfig, getSessionExtensions } from '../../api';
|
import { ExtensionConfig, getSessionExtensions } from '../../api';
|
||||||
import { addToAgent, removeFromAgent } from '../settings/extensions/agent-api';
|
import { addToAgent, removeFromAgent } from '../settings/extensions/agent-api';
|
||||||
import {
|
import {
|
||||||
@@ -230,15 +231,29 @@ export const BottomMenuExtensionSelection = ({ sessionId }: BottomMenuExtensionS
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionExtensionNames = new Set(sessionExtensions.map((ext) => ext.name));
|
const sessionExtensionKeys = new Set(sessionExtensions.map((ext) => nameToKey(ext.name)));
|
||||||
|
const globalExtensionKeys = new Set(allExtensions.map((ext) => nameToKey(ext.name)));
|
||||||
|
|
||||||
return allExtensions.map(
|
const mergedExtensions = allExtensions.map(
|
||||||
(ext) =>
|
(ext) =>
|
||||||
({
|
({
|
||||||
...ext,
|
...ext,
|
||||||
enabled: sessionExtensionNames.has(ext.name),
|
enabled: sessionExtensionKeys.has(nameToKey(ext.name)),
|
||||||
}) as FixedExtensionEntry
|
}) as FixedExtensionEntry
|
||||||
);
|
);
|
||||||
|
|
||||||
|
for (const sessionExtension of sessionExtensions) {
|
||||||
|
if (globalExtensionKeys.has(nameToKey(sessionExtension.name))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
mergedExtensions.push({
|
||||||
|
...sessionExtension,
|
||||||
|
enabled: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergedExtensions;
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [allExtensions, sessionExtensions, isHubView, hubUpdateTrigger]);
|
}, [allExtensions, sessionExtensions, isHubView, hubUpdateTrigger]);
|
||||||
|
|
||||||
@@ -266,6 +281,9 @@ export const BottomMenuExtensionSelection = ({ sessionId }: BottomMenuExtensionS
|
|||||||
return extensionsList.filter((ext) => ext.enabled).length;
|
return extensionsList.filter((ext) => ext.enabled).length;
|
||||||
}, [extensionsList]);
|
}, [extensionsList]);
|
||||||
|
|
||||||
|
const shouldHideTrigger =
|
||||||
|
extensionsList.length === 0 || (!isHubView && !isSessionExtensionsLoaded);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
open={isOpen}
|
open={isOpen}
|
||||||
@@ -284,7 +302,7 @@ export const BottomMenuExtensionSelection = ({ sessionId }: BottomMenuExtensionS
|
|||||||
>
|
>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<button
|
<button
|
||||||
className={`flex items-center [&_svg]:size-4 text-text-primary/70 hover:text-text-primary hover:scale-100 hover:bg-transparent text-xs cursor-pointer ${allExtensions.length === 0 || (!isHubView && !isSessionExtensionsLoaded) ? 'invisible' : ''}`}
|
className={`flex items-center [&_svg]:size-4 text-text-primary/70 hover:text-text-primary hover:scale-100 hover:bg-transparent text-xs cursor-pointer ${shouldHideTrigger ? 'invisible' : ''}`}
|
||||||
title={intl.formatMessage(i18n.manageExtensions)}
|
title={intl.formatMessage(i18n.manageExtensions)}
|
||||||
>
|
>
|
||||||
<Puzzle className="mr-1 h-4 w-4" />
|
<Puzzle className="mr-1 h-4 w-4" />
|
||||||
@@ -309,7 +327,9 @@ export const BottomMenuExtensionSelection = ({ sessionId }: BottomMenuExtensionS
|
|||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-text-primary/60 mt-1.5">
|
<p className="text-xs text-text-primary/60 mt-1.5">
|
||||||
{intl.formatMessage(isHubView ? i18n.extensionsForNewChats : i18n.extensionsForThisSession)}
|
{intl.formatMessage(
|
||||||
|
isHubView ? i18n.extensionsForNewChats : i18n.extensionsForThisSession
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -319,7 +339,9 @@ export const BottomMenuExtensionSelection = ({ sessionId }: BottomMenuExtensionS
|
|||||||
>
|
>
|
||||||
{sortedExtensions.length === 0 ? (
|
{sortedExtensions.length === 0 ? (
|
||||||
<div className="px-2 py-4 text-center text-sm text-text-primary/70">
|
<div className="px-2 py-4 text-center text-sm text-text-primary/70">
|
||||||
{intl.formatMessage(searchQuery ? i18n.noExtensionsFound : i18n.noExtensionsAvailable)}
|
{intl.formatMessage(
|
||||||
|
searchQuery ? i18n.noExtensionsFound : i18n.noExtensionsAvailable
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
sortedExtensions.map((ext) => {
|
sortedExtensions.map((ext) => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, it, expect, vi } from 'vitest';
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
import { isDeprecatedGoogleDriveExtension, syncBundledExtensions } from './bundled-extensions';
|
import { pruneDeprecatedBundledExtensions, syncBundledExtensions } from './bundled-extensions';
|
||||||
import type { FixedExtensionEntry } from '../../ConfigContext';
|
import type { FixedExtensionEntry } from '../../ConfigContext';
|
||||||
|
|
||||||
vi.mock('./bundled-extensions.json', () => ({
|
vi.mock('./bundled-extensions.json', () => ({
|
||||||
@@ -28,143 +28,11 @@ vi.mock('./bundled-extensions.json', () => ({
|
|||||||
],
|
],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('isDeprecatedGoogleDriveExtension', () => {
|
vi.mock('./deprecated-bundled-extensions.json', () => ({
|
||||||
it('returns true for builtin googledrive', () => {
|
default: [{ id: 'googledrive' }, { id: 'old-bundled-extension' }],
|
||||||
const ext = {
|
}));
|
||||||
name: 'Google Drive',
|
|
||||||
type: 'builtin',
|
|
||||||
description: 'Google Drive extension',
|
|
||||||
enabled: true,
|
|
||||||
bundled: true,
|
|
||||||
} as FixedExtensionEntry;
|
|
||||||
expect(isDeprecatedGoogleDriveExtension(ext)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns true for builtin google_drive', () => {
|
|
||||||
const ext = {
|
|
||||||
name: 'google_drive',
|
|
||||||
type: 'builtin',
|
|
||||||
description: 'Google Drive extension',
|
|
||||||
enabled: true,
|
|
||||||
bundled: true,
|
|
||||||
} as FixedExtensionEntry;
|
|
||||||
expect(isDeprecatedGoogleDriveExtension(ext)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns true for stdio googledrive with GOOGLE_DRIVE_CREDENTIALS_PATH', () => {
|
|
||||||
const ext = {
|
|
||||||
name: 'Google Drive',
|
|
||||||
type: 'stdio',
|
|
||||||
description: 'Google Drive extension',
|
|
||||||
cmd: 'some-cmd',
|
|
||||||
args: [],
|
|
||||||
env_keys: ['GOOGLE_DRIVE_CREDENTIALS_PATH'],
|
|
||||||
enabled: true,
|
|
||||||
bundled: true,
|
|
||||||
} as FixedExtensionEntry;
|
|
||||||
expect(isDeprecatedGoogleDriveExtension(ext)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns true for stdio googledrive with GOOGLE_DRIVE_OAUTH_PATH', () => {
|
|
||||||
const ext = {
|
|
||||||
name: 'Google Drive',
|
|
||||||
type: 'stdio',
|
|
||||||
description: 'Google Drive extension',
|
|
||||||
cmd: 'some-cmd',
|
|
||||||
args: [],
|
|
||||||
env_keys: ['GOOGLE_DRIVE_OAUTH_PATH'],
|
|
||||||
enabled: true,
|
|
||||||
bundled: true,
|
|
||||||
} as FixedExtensionEntry;
|
|
||||||
expect(isDeprecatedGoogleDriveExtension(ext)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns false for stdio googledrive without deprecated env keys', () => {
|
|
||||||
const ext = {
|
|
||||||
name: 'Google Drive',
|
|
||||||
type: 'stdio',
|
|
||||||
description: 'Google Drive extension',
|
|
||||||
cmd: 'some-cmd',
|
|
||||||
args: [],
|
|
||||||
env_keys: [],
|
|
||||||
enabled: true,
|
|
||||||
bundled: true,
|
|
||||||
} as FixedExtensionEntry;
|
|
||||||
expect(isDeprecatedGoogleDriveExtension(ext)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns false for non-googledrive extensions', () => {
|
|
||||||
const ext = {
|
|
||||||
name: 'developer',
|
|
||||||
type: 'builtin',
|
|
||||||
description: 'Developer tools',
|
|
||||||
enabled: true,
|
|
||||||
bundled: true,
|
|
||||||
} as FixedExtensionEntry;
|
|
||||||
expect(isDeprecatedGoogleDriveExtension(ext)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns false for non-googledrive stdio with those env keys', () => {
|
|
||||||
const ext = {
|
|
||||||
name: 'some-other-ext',
|
|
||||||
type: 'stdio',
|
|
||||||
description: 'Other extension',
|
|
||||||
cmd: 'some-cmd',
|
|
||||||
args: [],
|
|
||||||
env_keys: ['GOOGLE_DRIVE_CREDENTIALS_PATH'],
|
|
||||||
enabled: true,
|
|
||||||
bundled: true,
|
|
||||||
} as FixedExtensionEntry;
|
|
||||||
expect(isDeprecatedGoogleDriveExtension(ext)).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('syncBundledExtensions', () => {
|
describe('syncBundledExtensions', () => {
|
||||||
it('overwrites deprecated builtin googledrive extension', async () => {
|
|
||||||
const addExtensionFn = vi.fn().mockResolvedValue(undefined);
|
|
||||||
const existingExtensions = [
|
|
||||||
{
|
|
||||||
name: 'googledrive',
|
|
||||||
type: 'builtin',
|
|
||||||
description: 'Google Drive',
|
|
||||||
enabled: true,
|
|
||||||
bundled: true,
|
|
||||||
},
|
|
||||||
] as FixedExtensionEntry[];
|
|
||||||
|
|
||||||
await syncBundledExtensions(existingExtensions, addExtensionFn);
|
|
||||||
|
|
||||||
expect(addExtensionFn).toHaveBeenCalledWith(
|
|
||||||
'googledrive',
|
|
||||||
expect.objectContaining({ type: 'stdio', bundled: true }),
|
|
||||||
true
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('overwrites stdio googledrive with deprecated env keys', async () => {
|
|
||||||
const addExtensionFn = vi.fn().mockResolvedValue(undefined);
|
|
||||||
const existingExtensions = [
|
|
||||||
{
|
|
||||||
name: 'googledrive',
|
|
||||||
type: 'stdio',
|
|
||||||
description: 'Google Drive',
|
|
||||||
cmd: 'some-cmd',
|
|
||||||
args: [],
|
|
||||||
env_keys: ['GOOGLE_DRIVE_CREDENTIALS_PATH'],
|
|
||||||
enabled: true,
|
|
||||||
bundled: true,
|
|
||||||
},
|
|
||||||
] as FixedExtensionEntry[];
|
|
||||||
|
|
||||||
await syncBundledExtensions(existingExtensions, addExtensionFn);
|
|
||||||
|
|
||||||
expect(addExtensionFn).toHaveBeenCalledWith(
|
|
||||||
'googledrive',
|
|
||||||
expect.objectContaining({ type: 'stdio', bundled: true, env_keys: [] }),
|
|
||||||
true
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('skips already bundled non-deprecated extensions', async () => {
|
it('skips already bundled non-deprecated extensions', async () => {
|
||||||
const addExtensionFn = vi.fn().mockResolvedValue(undefined);
|
const addExtensionFn = vi.fn().mockResolvedValue(undefined);
|
||||||
const existingExtensions = [
|
const existingExtensions = [
|
||||||
@@ -187,3 +55,82 @@ describe('syncBundledExtensions', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('pruneDeprecatedBundledExtensions', () => {
|
||||||
|
it('removes deprecated bundled extensions', async () => {
|
||||||
|
const removeExtensionFn = vi.fn().mockResolvedValue(undefined);
|
||||||
|
const existingExtensions = [
|
||||||
|
{
|
||||||
|
name: 'old-bundled-extension',
|
||||||
|
type: 'builtin',
|
||||||
|
description: 'Old bundled extension',
|
||||||
|
enabled: true,
|
||||||
|
bundled: true,
|
||||||
|
},
|
||||||
|
] as FixedExtensionEntry[];
|
||||||
|
|
||||||
|
const remainingExtensions = await pruneDeprecatedBundledExtensions(
|
||||||
|
existingExtensions,
|
||||||
|
removeExtensionFn
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(removeExtensionFn).toHaveBeenCalledWith('old-bundled-extension');
|
||||||
|
expect(remainingExtensions).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not remove non-bundled deprecated extensions', async () => {
|
||||||
|
const removeExtensionFn = vi.fn().mockResolvedValue(undefined);
|
||||||
|
const existingExtensions = [
|
||||||
|
{
|
||||||
|
name: 'old-bundled-extension',
|
||||||
|
type: 'builtin',
|
||||||
|
description: 'Old bundled extension',
|
||||||
|
enabled: true,
|
||||||
|
bundled: false,
|
||||||
|
},
|
||||||
|
] as FixedExtensionEntry[];
|
||||||
|
|
||||||
|
const remainingExtensions = await pruneDeprecatedBundledExtensions(
|
||||||
|
existingExtensions,
|
||||||
|
removeExtensionFn
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(removeExtensionFn).not.toHaveBeenCalled();
|
||||||
|
expect(remainingExtensions).toEqual(existingExtensions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows same-id bundled extensions to be re-added after prune', async () => {
|
||||||
|
const removeExtensionFn = vi.fn().mockResolvedValue(undefined);
|
||||||
|
const addExtensionFn = vi.fn().mockResolvedValue(undefined);
|
||||||
|
const existingExtensions = [
|
||||||
|
{
|
||||||
|
name: 'Google Drive',
|
||||||
|
type: 'stdio',
|
||||||
|
description: 'Google Drive extension',
|
||||||
|
cmd: 'some-cmd',
|
||||||
|
args: [],
|
||||||
|
env_keys: [],
|
||||||
|
enabled: true,
|
||||||
|
bundled: true,
|
||||||
|
},
|
||||||
|
] as FixedExtensionEntry[];
|
||||||
|
|
||||||
|
const remainingExtensions = await pruneDeprecatedBundledExtensions(
|
||||||
|
existingExtensions,
|
||||||
|
removeExtensionFn
|
||||||
|
);
|
||||||
|
|
||||||
|
await syncBundledExtensions(remainingExtensions, addExtensionFn);
|
||||||
|
|
||||||
|
expect(removeExtensionFn).toHaveBeenCalledWith('googledrive');
|
||||||
|
expect(addExtensionFn).toHaveBeenCalledWith(
|
||||||
|
'googledrive',
|
||||||
|
expect.objectContaining({
|
||||||
|
type: 'stdio',
|
||||||
|
name: 'googledrive',
|
||||||
|
bundled: true,
|
||||||
|
}),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { ExtensionConfig } from '../../../api/types.gen';
|
import type { ExtensionConfig } from '../../../api/types.gen';
|
||||||
import { FixedExtensionEntry } from '../../ConfigContext';
|
import { FixedExtensionEntry } from '../../ConfigContext';
|
||||||
import bundledExtensionsData from './bundled-extensions.json';
|
import bundledExtensionsData from './bundled-extensions.json';
|
||||||
|
import deprecatedBundledExtensionsData from './deprecated-bundled-extensions.json';
|
||||||
import { nameToKey } from './utils';
|
import { nameToKey } from './utils';
|
||||||
|
|
||||||
// Type definition for built-in extensions from JSON
|
// Type definition for built-in extensions from JSON
|
||||||
@@ -20,28 +21,40 @@ type BundledExtension = {
|
|||||||
allow_configure?: boolean;
|
allow_configure?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEPRECATED_GOOGLE_DRIVE_IDS = ['googledrive', 'google_drive'];
|
type DeprecatedBundledExtension = {
|
||||||
const DEPRECATED_GOOGLE_DRIVE_ENV_KEYS = [
|
id: string;
|
||||||
'GOOGLE_DRIVE_CREDENTIALS_PATH',
|
};
|
||||||
'GOOGLE_DRIVE_OAUTH_PATH',
|
|
||||||
];
|
|
||||||
|
|
||||||
export function isDeprecatedGoogleDriveExtension(ext: FixedExtensionEntry): boolean {
|
export function getDeprecatedBundledExtensions(): DeprecatedBundledExtension[] {
|
||||||
if (!DEPRECATED_GOOGLE_DRIVE_IDS.includes(nameToKey(ext.name))) {
|
return deprecatedBundledExtensionsData as DeprecatedBundledExtension[];
|
||||||
return false;
|
}
|
||||||
|
|
||||||
|
function isBundledExtension(extension: FixedExtensionEntry): boolean {
|
||||||
|
return 'bundled' in extension && extension.bundled === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function pruneDeprecatedBundledExtensions(
|
||||||
|
existingExtensions: FixedExtensionEntry[],
|
||||||
|
removeExtensionFn: (id: string) => Promise<void>
|
||||||
|
): Promise<FixedExtensionEntry[]> {
|
||||||
|
const deprecatedExtensionIds = new Set(getDeprecatedBundledExtensions().map((ext) => ext.id));
|
||||||
|
const remainingExtensions: FixedExtensionEntry[] = [];
|
||||||
|
|
||||||
|
for (const existingExt of existingExtensions) {
|
||||||
|
if (!isBundledExtension(existingExt)) {
|
||||||
|
remainingExtensions.push(existingExt);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!deprecatedExtensionIds.has(nameToKey(existingExt.name))) {
|
||||||
|
remainingExtensions.push(existingExt);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await removeExtensionFn(nameToKey(existingExt.name));
|
||||||
}
|
}
|
||||||
if (ext.type === 'builtin') {
|
|
||||||
return true;
|
return remainingExtensions;
|
||||||
}
|
|
||||||
if (
|
|
||||||
ext.type === 'stdio' &&
|
|
||||||
'env_keys' in ext &&
|
|
||||||
Array.isArray(ext.env_keys) &&
|
|
||||||
ext.env_keys.some((key: string) => DEPRECATED_GOOGLE_DRIVE_ENV_KEYS.includes(key))
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,14 +79,7 @@ export async function syncBundledExtensions(
|
|||||||
// Find if this extension already exists
|
// Find if this extension already exists
|
||||||
const existingExt = existingExtensions.find((ext) => nameToKey(ext.name) === bundledExt.id);
|
const existingExt = existingExtensions.find((ext) => nameToKey(ext.name) === bundledExt.id);
|
||||||
|
|
||||||
// Skip if extension exists and is already marked as bundled, except when
|
if (existingExt && isBundledExtension(existingExt)) {
|
||||||
// we must migrate deprecated extensions.
|
|
||||||
if (
|
|
||||||
existingExt &&
|
|
||||||
'bundled' in existingExt &&
|
|
||||||
existingExt.bundled &&
|
|
||||||
!isDeprecatedGoogleDriveExtension(existingExt)
|
|
||||||
) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,14 +129,3 @@ export async function syncBundledExtensions(
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Function to initialize all built-in extensions for a first-time user.
|
|
||||||
* This can be called when the application is first installed.
|
|
||||||
*/
|
|
||||||
export async function initializeBundledExtensions(
|
|
||||||
addExtensionFn: (name: string, config: ExtensionConfig, enabled: boolean) => Promise<void>
|
|
||||||
): Promise<void> {
|
|
||||||
// Call with an empty list to ensure all built-ins are added
|
|
||||||
await syncBundledExtensions([], addExtensionFn);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
[]
|
||||||
@@ -6,7 +6,7 @@ export {
|
|||||||
deleteExtension,
|
deleteExtension,
|
||||||
} from './extension-manager';
|
} from './extension-manager';
|
||||||
|
|
||||||
export { syncBundledExtensions, initializeBundledExtensions } from './bundled-extensions';
|
export { pruneDeprecatedBundledExtensions, syncBundledExtensions } from './bundled-extensions';
|
||||||
|
|
||||||
export { addExtensionFromDeepLink } from './deeplink';
|
export { addExtensionFromDeepLink } from './deeplink';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user