@dxos/plugin-registry 0.8.4-main.c85a9c8dae → 0.8.4-main.d9fc60f731
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +102 -5
- package/dist/lib/neutral/RegistryPlugin.mjs +27 -0
- package/dist/lib/neutral/RegistryPlugin.mjs.map +7 -0
- package/dist/lib/{browser/cli/index.mjs → neutral/RegistryPlugin.node.mjs} +28 -99
- package/dist/lib/neutral/RegistryPlugin.node.mjs.map +7 -0
- package/dist/lib/neutral/RegistryPlugin.workerd.mjs +10 -0
- package/dist/lib/neutral/RegistryPlugin.workerd.mjs.map +7 -0
- package/dist/lib/neutral/chunk-GRQI6MLI.mjs +36 -0
- package/dist/lib/neutral/chunk-GRQI6MLI.mjs.map +7 -0
- package/dist/lib/neutral/chunk-XK2JMH3H.mjs +33 -0
- package/dist/lib/neutral/chunk-XK2JMH3H.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +25 -0
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/neutral/open-plugin-registry-II735N76.mjs +15 -0
- package/dist/lib/neutral/open-plugin-registry-II735N76.mjs.map +7 -0
- package/dist/lib/neutral/plugin.mjs +18 -0
- package/dist/lib/neutral/plugin.mjs.map +7 -0
- package/dist/lib/neutral/translations.mjs +72 -0
- package/dist/lib/neutral/translations.mjs.map +7 -0
- package/dist/types/src/RegistryPlugin.d.ts +1 -0
- package/dist/types/src/RegistryPlugin.d.ts.map +1 -1
- package/dist/types/src/{cli/plugin.d.ts → RegistryPlugin.node.d.ts} +2 -1
- package/dist/types/src/RegistryPlugin.node.d.ts.map +1 -0
- package/dist/types/src/RegistryPlugin.test.d.ts +2 -0
- package/dist/types/src/RegistryPlugin.test.d.ts.map +1 -0
- package/dist/types/src/RegistryPlugin.workerd.d.ts +4 -0
- package/dist/types/src/RegistryPlugin.workerd.d.ts.map +1 -0
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
- package/dist/types/src/capabilities/dev-plugin-loader.d.ts +11 -0
- package/dist/types/src/capabilities/dev-plugin-loader.d.ts.map +1 -0
- package/dist/types/src/capabilities/index.d.ts +15 -3
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
- package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
- package/dist/types/src/capabilities/settings.d.ts +14 -0
- package/dist/types/src/capabilities/settings.d.ts.map +1 -0
- package/dist/types/src/categories.d.ts +18 -0
- package/dist/types/src/categories.d.ts.map +1 -0
- package/dist/types/src/{cli/commands → commands}/disable.d.ts +2 -2
- package/dist/types/src/commands/disable.d.ts.map +1 -0
- package/dist/types/src/{cli/commands → commands}/enable.d.ts +2 -2
- package/dist/types/src/commands/enable.d.ts.map +1 -0
- package/dist/types/src/commands/index.d.ts.map +1 -0
- package/dist/types/src/commands/list.d.ts +7 -0
- package/dist/types/src/commands/list.d.ts.map +1 -0
- package/dist/types/src/commands/util.d.ts.map +1 -0
- package/dist/types/src/components/DisableDependentsAlert/DisableDependentsAlert.d.ts +24 -0
- package/dist/types/src/components/DisableDependentsAlert/DisableDependentsAlert.d.ts.map +1 -0
- package/dist/types/src/components/DisableDependentsAlert/index.d.ts +2 -0
- package/dist/types/src/components/DisableDependentsAlert/index.d.ts.map +1 -0
- package/dist/types/src/components/PluginDetail/PluginDetail.d.ts +75 -2
- package/dist/types/src/components/PluginDetail/PluginDetail.d.ts.map +1 -1
- package/dist/types/src/components/PluginDetail/PluginDetail.stories.d.ts +62 -16
- package/dist/types/src/components/PluginDetail/PluginDetail.stories.d.ts.map +1 -1
- package/dist/types/src/components/PluginFailureBadge/PluginFailureBadge.d.ts +20 -0
- package/dist/types/src/components/PluginFailureBadge/PluginFailureBadge.d.ts.map +1 -0
- package/dist/types/src/components/PluginFailureBadge/index.d.ts +2 -0
- package/dist/types/src/components/PluginFailureBadge/index.d.ts.map +1 -0
- package/dist/types/src/components/PluginList/PluginItem.d.ts +29 -2
- package/dist/types/src/components/PluginList/PluginItem.d.ts.map +1 -1
- package/dist/types/src/components/PluginList/PluginList.d.ts +18 -3
- package/dist/types/src/components/PluginList/PluginList.d.ts.map +1 -1
- package/dist/types/src/components/PluginList/PluginList.stories.d.ts +60 -16
- package/dist/types/src/components/PluginList/PluginList.stories.d.ts.map +1 -1
- package/dist/types/src/components/RegistrySettings/RegistrySettings.d.ts +18 -0
- package/dist/types/src/components/RegistrySettings/RegistrySettings.d.ts.map +1 -0
- package/dist/types/src/components/RegistrySettings/index.d.ts +2 -0
- package/dist/types/src/components/RegistrySettings/index.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +3 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/containers/BaseRegistryArticle/BaseRegistryArticle.d.ts +20 -0
- package/dist/types/src/containers/BaseRegistryArticle/BaseRegistryArticle.d.ts.map +1 -0
- package/dist/types/src/containers/BaseRegistryArticle/index.d.ts +2 -0
- package/dist/types/src/containers/BaseRegistryArticle/index.d.ts.map +1 -0
- package/dist/types/src/containers/LoadPluginDialog/LoadPluginDialog.d.ts +5 -0
- package/dist/types/src/containers/LoadPluginDialog/LoadPluginDialog.d.ts.map +1 -0
- package/dist/types/src/containers/LoadPluginDialog/index.d.ts +2 -0
- package/dist/types/src/containers/LoadPluginDialog/index.d.ts.map +1 -0
- package/dist/types/src/containers/PluginArticle/PluginArticle.d.ts.map +1 -1
- package/dist/types/src/containers/PluginArticle/index.d.ts +1 -2
- package/dist/types/src/containers/PluginArticle/index.d.ts.map +1 -1
- package/dist/types/src/containers/PublicRegistryArticle/PublicRegistryArticle.d.ts +8 -0
- package/dist/types/src/containers/PublicRegistryArticle/PublicRegistryArticle.d.ts.map +1 -0
- package/dist/types/src/containers/PublicRegistryArticle/index.d.ts +2 -0
- package/dist/types/src/containers/PublicRegistryArticle/index.d.ts.map +1 -0
- package/dist/types/src/containers/RegistryArticle/RegistryArticle.d.ts +14 -0
- package/dist/types/src/containers/RegistryArticle/RegistryArticle.d.ts.map +1 -0
- package/dist/types/src/containers/RegistryArticle/index.d.ts +2 -0
- package/dist/types/src/containers/RegistryArticle/index.d.ts.map +1 -0
- package/dist/types/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.d.ts +13 -0
- package/dist/types/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.d.ts.map +1 -0
- package/dist/types/src/containers/RegistrySettingsContainer/index.d.ts +2 -0
- package/dist/types/src/containers/RegistrySettingsContainer/index.d.ts.map +1 -0
- package/dist/types/src/containers/index.d.ts +5 -1
- package/dist/types/src/containers/index.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +9 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -0
- package/dist/types/src/hooks/useAutoTags.d.ts +20 -0
- package/dist/types/src/hooks/useAutoTags.d.ts.map +1 -0
- package/dist/types/src/hooks/useCatalogEntry.d.ts +11 -0
- package/dist/types/src/hooks/useCatalogEntry.d.ts.map +1 -0
- package/dist/types/src/hooks/useDisableConfirmation.d.ts +19 -0
- package/dist/types/src/hooks/useDisableConfirmation.d.ts.map +1 -0
- package/dist/types/src/hooks/useInstalledVersionTag.d.ts +18 -0
- package/dist/types/src/hooks/useInstalledVersionTag.d.ts.map +1 -0
- package/dist/types/src/hooks/usePluginActions.d.ts +36 -0
- package/dist/types/src/hooks/usePluginActions.d.ts.map +1 -0
- package/dist/types/src/hooks/useRegistryPlugins.d.ts +15 -0
- package/dist/types/src/hooks/useRegistryPlugins.d.ts.map +1 -0
- package/dist/types/src/hooks/useUpdateAvailable.d.ts +18 -0
- package/dist/types/src/hooks/useUpdateAvailable.d.ts.map +1 -0
- package/dist/types/src/hooks/useVersionPicker.d.ts +21 -0
- package/dist/types/src/hooks/useVersionPicker.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +20 -2
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/operations/index.d.ts +3 -0
- package/dist/types/src/operations/index.d.ts.map +1 -0
- package/dist/types/src/operations/open-plugin-registry.d.ts +5 -0
- package/dist/types/src/operations/open-plugin-registry.d.ts.map +1 -0
- package/dist/types/src/plugin.d.ts +4 -0
- package/dist/types/src/plugin.d.ts.map +1 -0
- package/dist/types/src/{cli/storage.d.ts → storage.d.ts} +4 -4
- package/dist/types/src/storage.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +59 -16
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +30 -1
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +63 -39
- package/src/{cli/plugin.ts → RegistryPlugin.node.ts} +3 -1
- package/src/RegistryPlugin.test.ts +24 -0
- package/src/RegistryPlugin.tsx +13 -5
- package/src/RegistryPlugin.workerd.ts +11 -0
- package/src/capabilities/app-graph-builder.ts +219 -0
- package/src/capabilities/dev-plugin-loader.ts +39 -0
- package/src/capabilities/index.ts +11 -3
- package/src/capabilities/operation-handler.ts +16 -0
- package/src/capabilities/react-surface.tsx +126 -0
- package/src/capabilities/settings.ts +34 -0
- package/src/categories.ts +44 -0
- package/src/components/DisableDependentsAlert/DisableDependentsAlert.tsx +69 -0
- package/src/components/DisableDependentsAlert/index.ts +5 -0
- package/src/components/PluginDetail/PluginDetail.stories.tsx +18 -14
- package/src/components/PluginDetail/PluginDetail.tsx +323 -61
- package/src/components/PluginFailureBadge/PluginFailureBadge.tsx +68 -0
- package/src/components/PluginFailureBadge/index.ts +5 -0
- package/src/components/PluginList/PluginItem.tsx +124 -25
- package/src/components/PluginList/PluginList.stories.tsx +22 -16
- package/src/components/PluginList/PluginList.tsx +32 -4
- package/src/components/RegistrySettings/RegistrySettings.tsx +120 -0
- package/src/components/RegistrySettings/index.ts +5 -0
- package/src/components/index.ts +3 -0
- package/src/containers/BaseRegistryArticle/BaseRegistryArticle.tsx +178 -0
- package/src/containers/BaseRegistryArticle/index.ts +5 -0
- package/src/containers/LoadPluginDialog/LoadPluginDialog.tsx +88 -0
- package/src/containers/LoadPluginDialog/index.ts +5 -0
- package/src/containers/PluginArticle/PluginArticle.tsx +102 -13
- package/src/containers/PluginArticle/index.ts +1 -3
- package/src/containers/PublicRegistryArticle/PublicRegistryArticle.tsx +193 -0
- package/src/containers/PublicRegistryArticle/index.ts +5 -0
- package/src/containers/RegistryArticle/RegistryArticle.tsx +53 -0
- package/src/containers/RegistryArticle/index.ts +5 -0
- package/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.tsx +59 -0
- package/src/containers/RegistrySettingsContainer/index.ts +5 -0
- package/src/containers/index.ts +5 -1
- package/src/hooks/index.ts +12 -0
- package/src/hooks/useAutoTags.ts +52 -0
- package/src/hooks/useCatalogEntry.ts +25 -0
- package/src/hooks/useDisableConfirmation.ts +58 -0
- package/src/hooks/useInstalledVersionTag.ts +41 -0
- package/src/hooks/usePluginActions.ts +174 -0
- package/src/hooks/useRegistryPlugins.ts +29 -0
- package/src/hooks/useUpdateAvailable.ts +31 -0
- package/src/hooks/useVersionPicker.ts +112 -0
- package/src/index.ts +1 -1
- package/src/meta.ts +34 -8
- package/src/operations/index.ts +7 -0
- package/src/operations/open-plugin-registry.ts +21 -0
- package/src/plugin.ts +11 -0
- package/src/{cli/storage.ts → storage.ts} +14 -21
- package/src/translations.ts +64 -15
- package/src/types.ts +33 -1
- package/dist/lib/browser/PluginArticle-MLBF7AFA.mjs +0 -29
- package/dist/lib/browser/PluginArticle-MLBF7AFA.mjs.map +0 -7
- package/dist/lib/browser/PluginRegistry-ENAI4SAY.mjs +0 -77
- package/dist/lib/browser/PluginRegistry-ENAI4SAY.mjs.map +0 -7
- package/dist/lib/browser/app-graph-builder-2HXD7SOS.mjs +0 -167
- package/dist/lib/browser/app-graph-builder-2HXD7SOS.mjs.map +0 -7
- package/dist/lib/browser/chunk-TSNK3SAR.mjs +0 -191
- package/dist/lib/browser/chunk-TSNK3SAR.mjs.map +0 -7
- package/dist/lib/browser/chunk-VOAOTNGS.mjs +0 -19
- package/dist/lib/browser/chunk-VOAOTNGS.mjs.map +0 -7
- package/dist/lib/browser/cli/index.mjs.map +0 -7
- package/dist/lib/browser/index.mjs +0 -63
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/operation-resolver-7P3NNVSR.mjs +0 -25
- package/dist/lib/browser/operation-resolver-7P3NNVSR.mjs.map +0 -7
- package/dist/lib/browser/react-surface-DH25DIRH.mjs +0 -85
- package/dist/lib/browser/react-surface-DH25DIRH.mjs.map +0 -7
- package/dist/lib/node-esm/PluginArticle-TP2G6GGI.mjs +0 -30
- package/dist/lib/node-esm/PluginArticle-TP2G6GGI.mjs.map +0 -7
- package/dist/lib/node-esm/PluginRegistry-CWIZIQNX.mjs +0 -78
- package/dist/lib/node-esm/PluginRegistry-CWIZIQNX.mjs.map +0 -7
- package/dist/lib/node-esm/app-graph-builder-2LNPN72O.mjs +0 -168
- package/dist/lib/node-esm/app-graph-builder-2LNPN72O.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-4XO63XB6.mjs +0 -192
- package/dist/lib/node-esm/chunk-4XO63XB6.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-SALMIZ2W.mjs +0 -21
- package/dist/lib/node-esm/chunk-SALMIZ2W.mjs.map +0 -7
- package/dist/lib/node-esm/cli/index.mjs +0 -225
- package/dist/lib/node-esm/cli/index.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -64
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/operation-resolver-B24QW6IF.mjs +0 -26
- package/dist/lib/node-esm/operation-resolver-B24QW6IF.mjs.map +0 -7
- package/dist/lib/node-esm/react-surface-3YBEVL65.mjs +0 -86
- package/dist/lib/node-esm/react-surface-3YBEVL65.mjs.map +0 -7
- package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts.map +0 -1
- package/dist/types/src/capabilities/app-graph-builder/index.d.ts +0 -3
- package/dist/types/src/capabilities/app-graph-builder/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/operation-resolver/index.d.ts +0 -3
- package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +0 -5
- package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +0 -1
- package/dist/types/src/capabilities/react-surface/index.d.ts +0 -3
- package/dist/types/src/capabilities/react-surface/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +0 -1
- package/dist/types/src/cli/commands/disable.d.ts.map +0 -1
- package/dist/types/src/cli/commands/enable.d.ts.map +0 -1
- package/dist/types/src/cli/commands/index.d.ts.map +0 -1
- package/dist/types/src/cli/commands/list.d.ts +0 -7
- package/dist/types/src/cli/commands/list.d.ts.map +0 -1
- package/dist/types/src/cli/commands/util.d.ts.map +0 -1
- package/dist/types/src/cli/index.d.ts +0 -3
- package/dist/types/src/cli/index.d.ts.map +0 -1
- package/dist/types/src/cli/plugin.d.ts.map +0 -1
- package/dist/types/src/cli/storage.d.ts.map +0 -1
- package/dist/types/src/containers/PluginRegistry/PluginRegistry.d.ts +0 -8
- package/dist/types/src/containers/PluginRegistry/PluginRegistry.d.ts.map +0 -1
- package/dist/types/src/containers/PluginRegistry/index.d.ts +0 -3
- package/dist/types/src/containers/PluginRegistry/index.d.ts.map +0 -1
- package/src/capabilities/app-graph-builder/app-graph-builder.ts +0 -137
- package/src/capabilities/app-graph-builder/index.ts +0 -7
- package/src/capabilities/operation-resolver/index.ts +0 -7
- package/src/capabilities/operation-resolver/operation-resolver.ts +0 -24
- package/src/capabilities/react-surface/index.ts +0 -7
- package/src/capabilities/react-surface/react-surface.tsx +0 -88
- package/src/cli/index.ts +0 -6
- package/src/containers/PluginRegistry/PluginRegistry.tsx +0 -86
- package/src/containers/PluginRegistry/index.ts +0 -7
- /package/dist/types/src/capabilities/{app-graph-builder/app-graph-builder.d.ts → app-graph-builder.d.ts} +0 -0
- /package/dist/types/src/capabilities/{react-surface/react-surface.d.ts → react-surface.d.ts} +0 -0
- /package/dist/types/src/{cli/commands → commands}/index.d.ts +0 -0
- /package/dist/types/src/{cli/commands → commands}/util.d.ts +0 -0
- /package/src/{cli/commands → commands}/disable.ts +0 -0
- /package/src/{cli/commands → commands}/enable.ts +0 -0
- /package/src/{cli/commands → commands}/index.ts +0 -0
- /package/src/{cli/commands → commands}/list.ts +0 -0
- /package/src/{cli/commands → commands}/util.ts +0 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useCallback } from 'react';
|
|
6
|
+
|
|
7
|
+
import { type PluginManager } from '@dxos/app-framework';
|
|
8
|
+
import { useOperationInvoker } from '@dxos/app-framework/ui';
|
|
9
|
+
import { LayoutOperation } from '@dxos/app-toolkit';
|
|
10
|
+
|
|
11
|
+
import { DISABLE_DEPENDENTS_DIALOG } from '#meta';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Returns `requestDisable(pluginId)` that gates `manager.disable` on a
|
|
15
|
+
* confirmation prompt when the target has currently-enabled dependents.
|
|
16
|
+
*
|
|
17
|
+
* `PluginManager.disable` cascades by default; this hook is the UX safeguard
|
|
18
|
+
* around that. The caller supplies a `dispatch` callback that performs the
|
|
19
|
+
* actual disable (wrapping it with observability or any other side effects).
|
|
20
|
+
* When there are no enabled dependents, dispatch runs immediately; otherwise
|
|
21
|
+
* we open the shared {@link DISABLE_DEPENDENTS_DIALOG} surface — its
|
|
22
|
+
* `onConfirm` runs the dispatch and closes the dialog.
|
|
23
|
+
*
|
|
24
|
+
* Display-name resolution for the dialog is delegated to the registered
|
|
25
|
+
* plugin set (`Plugin.Meta.name`). Per-plugin i18n translations only load
|
|
26
|
+
* when a plugin is activated, so `meta.name` is the only label always
|
|
27
|
+
* available regardless of enabled state.
|
|
28
|
+
*/
|
|
29
|
+
export const useDisableConfirmation = (manager: PluginManager.PluginManager, dispatch: (id: string) => void) => {
|
|
30
|
+
const { invokePromise } = useOperationInvoker();
|
|
31
|
+
|
|
32
|
+
return useCallback(
|
|
33
|
+
(pluginId: string): void => {
|
|
34
|
+
const enabledDependents = manager.getDependents(pluginId, { transitive: true, enabledOnly: true });
|
|
35
|
+
if (enabledDependents.length === 0) {
|
|
36
|
+
dispatch(pluginId);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const resolveName = (id: string): string =>
|
|
40
|
+
manager.getPlugins().find((plugin) => plugin.meta.id === id)?.meta.name ?? id;
|
|
41
|
+
void invokePromise(LayoutOperation.UpdateDialog, {
|
|
42
|
+
subject: DISABLE_DEPENDENTS_DIALOG,
|
|
43
|
+
state: true,
|
|
44
|
+
type: 'alert',
|
|
45
|
+
props: {
|
|
46
|
+
pluginId,
|
|
47
|
+
dependents: enabledDependents,
|
|
48
|
+
onResolvePluginName: resolveName,
|
|
49
|
+
onConfirm: () => {
|
|
50
|
+
dispatch(pluginId);
|
|
51
|
+
void invokePromise(LayoutOperation.UpdateDialog, { state: false });
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
[manager, dispatch, invokePromise],
|
|
57
|
+
);
|
|
58
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import { type Plugin, UrlLoader } from '@dxos/app-framework';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Tracks the per-plugin installed version (sourced from `UrlLoader` localStorage)
|
|
11
|
+
* as React state so post-install/update re-renders are deterministic — the value
|
|
12
|
+
* lives in component state rather than being read inline on every render.
|
|
13
|
+
*
|
|
14
|
+
* - Initialised from localStorage at mount.
|
|
15
|
+
* - Auto-resyncs when the plugin manager's plugin list changes (catches external
|
|
16
|
+
* installs / removes triggered by the list view).
|
|
17
|
+
* - `syncInstalledVersion()` is the explicit handle install/update operations
|
|
18
|
+
* call inside their `Effect.ensuring` so the post-mutation render sees both
|
|
19
|
+
* the new version and the cleared `installing`/`updating` flag together.
|
|
20
|
+
*/
|
|
21
|
+
export const useInstalledVersionTag = (
|
|
22
|
+
pluginId: string,
|
|
23
|
+
plugins: readonly Plugin.Plugin[],
|
|
24
|
+
): {
|
|
25
|
+
installedVersionTag: string | undefined;
|
|
26
|
+
syncInstalledVersion: () => void;
|
|
27
|
+
} => {
|
|
28
|
+
const [installedVersionTag, setInstalledVersionTag] = useState<string | undefined>(() =>
|
|
29
|
+
UrlLoader.getInstalledVersion(pluginId),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const syncInstalledVersion = useCallback(() => {
|
|
33
|
+
setInstalledVersionTag(UrlLoader.getInstalledVersion(pluginId));
|
|
34
|
+
}, [pluginId]);
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
syncInstalledVersion();
|
|
38
|
+
}, [plugins, syncInstalledVersion]);
|
|
39
|
+
|
|
40
|
+
return { installedVersionTag, syncInstalledVersion };
|
|
41
|
+
};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
import { useCallback, useState } from 'react';
|
|
7
|
+
|
|
8
|
+
import { type PluginManager, type Registry, UrlLoader } from '@dxos/app-framework';
|
|
9
|
+
import { useOperationInvoker } from '@dxos/app-framework/ui';
|
|
10
|
+
import { LayoutOperation } from '@dxos/app-toolkit';
|
|
11
|
+
import { useAppGraph } from '@dxos/app-toolkit/ui';
|
|
12
|
+
import { runAndForwardErrors } from '@dxos/effect';
|
|
13
|
+
import { useNode } from '@dxos/plugin-graph';
|
|
14
|
+
|
|
15
|
+
import { getPluginSpecPath } from '#meta';
|
|
16
|
+
|
|
17
|
+
import { useDisableConfirmation } from './useDisableConfirmation';
|
|
18
|
+
|
|
19
|
+
export type PluginActionsProps = {
|
|
20
|
+
manager: PluginManager.PluginManager;
|
|
21
|
+
pluginId: string;
|
|
22
|
+
isInstalled: boolean;
|
|
23
|
+
moduleUrl: string | undefined;
|
|
24
|
+
catalogEntry: Registry.Plugin | undefined;
|
|
25
|
+
pickerVersions: readonly Registry.PluginVersion[];
|
|
26
|
+
selectedVersionTag: string | undefined;
|
|
27
|
+
syncInstalledVersion: () => void;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type PluginActions = {
|
|
31
|
+
installing: boolean;
|
|
32
|
+
updating: boolean;
|
|
33
|
+
handleEnableChange: (enabled: boolean) => void;
|
|
34
|
+
handleUninstall: () => void;
|
|
35
|
+
handleInstall: () => void;
|
|
36
|
+
handleUpdate: () => void;
|
|
37
|
+
handleInstallVersion: () => void;
|
|
38
|
+
handleOpenSpec?: () => void;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The user-facing actions the article exposes — enable/disable, uninstall,
|
|
43
|
+
* install (latest), update (to latest catalog version), and install-from-picker —
|
|
44
|
+
* plus the `installing`/`updating` flags they drive. Each handler ends with a
|
|
45
|
+
* `syncInstalledVersion()` call inside its `Effect.ensuring` so the cached
|
|
46
|
+
* installed version snaps to the freshly-written localStorage value in the same
|
|
47
|
+
* render that clears the in-flight flag.
|
|
48
|
+
*
|
|
49
|
+
* `handleOpenSpec` is included only when a `spec` child graph node exists for
|
|
50
|
+
* this plugin — contributed by whichever plugin can render MDL (today
|
|
51
|
+
* `plugin-code`). When that contributor isn't enabled, the action is absent
|
|
52
|
+
* and the detail view hides its "Open Specification" chip.
|
|
53
|
+
*/
|
|
54
|
+
export const usePluginActions = ({
|
|
55
|
+
manager,
|
|
56
|
+
pluginId,
|
|
57
|
+
isInstalled,
|
|
58
|
+
moduleUrl,
|
|
59
|
+
catalogEntry,
|
|
60
|
+
pickerVersions,
|
|
61
|
+
selectedVersionTag,
|
|
62
|
+
syncInstalledVersion,
|
|
63
|
+
}: PluginActionsProps): PluginActions => {
|
|
64
|
+
const { invokePromise } = useOperationInvoker();
|
|
65
|
+
const { graph } = useAppGraph();
|
|
66
|
+
|
|
67
|
+
const [installing, setInstalling] = useState(false);
|
|
68
|
+
const [updating, setUpdating] = useState(false);
|
|
69
|
+
const requestDisable = useDisableConfirmation(manager, (id) => void runAndForwardErrors(manager.disable(id)));
|
|
70
|
+
|
|
71
|
+
const specPath = getPluginSpecPath(pluginId);
|
|
72
|
+
const hasSpecNode = !!useNode(graph, specPath);
|
|
73
|
+
const handleOpenSpec = useCallback(() => {
|
|
74
|
+
void invokePromise(LayoutOperation.Open, { subject: [specPath] });
|
|
75
|
+
}, [invokePromise, specPath]);
|
|
76
|
+
|
|
77
|
+
const handleEnableChange = useCallback(
|
|
78
|
+
(enabled: boolean) => {
|
|
79
|
+
if (enabled) {
|
|
80
|
+
void runAndForwardErrors(manager.enable(pluginId));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
requestDisable(pluginId);
|
|
85
|
+
},
|
|
86
|
+
[manager, pluginId, requestDisable],
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const handleUninstall = useCallback(() => {
|
|
90
|
+
void runAndForwardErrors(manager.remove(pluginId));
|
|
91
|
+
}, [manager, pluginId]);
|
|
92
|
+
|
|
93
|
+
const handleInstall = useCallback(() => {
|
|
94
|
+
if (!moduleUrl) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
setInstalling(true);
|
|
99
|
+
void Effect.gen(function* () {
|
|
100
|
+
const added = yield* manager.add(moduleUrl);
|
|
101
|
+
yield* manager.enable(added.meta.id);
|
|
102
|
+
if (catalogEntry) {
|
|
103
|
+
UrlLoader.setInstalledVersion(added.meta.id, catalogEntry.version);
|
|
104
|
+
}
|
|
105
|
+
}).pipe(
|
|
106
|
+
Effect.ensuring(
|
|
107
|
+
Effect.sync(() => {
|
|
108
|
+
setInstalling(false);
|
|
109
|
+
syncInstalledVersion();
|
|
110
|
+
}),
|
|
111
|
+
),
|
|
112
|
+
runAndForwardErrors,
|
|
113
|
+
);
|
|
114
|
+
}, [manager, moduleUrl, catalogEntry, syncInstalledVersion]);
|
|
115
|
+
|
|
116
|
+
const handleUpdate = useCallback(() => {
|
|
117
|
+
if (!moduleUrl || !catalogEntry) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
setUpdating(true);
|
|
122
|
+
void Effect.gen(function* () {
|
|
123
|
+
yield* manager.remove(pluginId);
|
|
124
|
+
const added = yield* manager.add(moduleUrl);
|
|
125
|
+
yield* manager.enable(added.meta.id);
|
|
126
|
+
UrlLoader.setInstalledVersion(added.meta.id, catalogEntry.version);
|
|
127
|
+
}).pipe(
|
|
128
|
+
Effect.ensuring(
|
|
129
|
+
Effect.sync(() => {
|
|
130
|
+
setUpdating(false);
|
|
131
|
+
syncInstalledVersion();
|
|
132
|
+
}),
|
|
133
|
+
),
|
|
134
|
+
runAndForwardErrors,
|
|
135
|
+
);
|
|
136
|
+
}, [manager, pluginId, moduleUrl, catalogEntry, syncInstalledVersion]);
|
|
137
|
+
|
|
138
|
+
const handleInstallVersion = useCallback(() => {
|
|
139
|
+
const version = pickerVersions.find((candidate) => candidate.tag === selectedVersionTag);
|
|
140
|
+
if (!version || !version.moduleUrl) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
setInstalling(true);
|
|
145
|
+
void Effect.gen(function* () {
|
|
146
|
+
// Remove existing version before installing a different one.
|
|
147
|
+
if (isInstalled) {
|
|
148
|
+
yield* manager.remove(pluginId);
|
|
149
|
+
}
|
|
150
|
+
const added = yield* manager.add(version.moduleUrl);
|
|
151
|
+
yield* manager.enable(added.meta.id);
|
|
152
|
+
UrlLoader.setInstalledVersion(added.meta.id, version.tag);
|
|
153
|
+
}).pipe(
|
|
154
|
+
Effect.ensuring(
|
|
155
|
+
Effect.sync(() => {
|
|
156
|
+
setInstalling(false);
|
|
157
|
+
syncInstalledVersion();
|
|
158
|
+
}),
|
|
159
|
+
),
|
|
160
|
+
runAndForwardErrors,
|
|
161
|
+
);
|
|
162
|
+
}, [manager, pluginId, isInstalled, selectedVersionTag, pickerVersions, syncInstalledVersion]);
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
installing,
|
|
166
|
+
updating,
|
|
167
|
+
handleEnableChange,
|
|
168
|
+
handleUninstall,
|
|
169
|
+
handleInstall,
|
|
170
|
+
handleUpdate,
|
|
171
|
+
handleInstallVersion,
|
|
172
|
+
handleOpenSpec: hasSpecNode ? handleOpenSpec : undefined,
|
|
173
|
+
};
|
|
174
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useAtomValue } from '@effect-atom/atom-react';
|
|
6
|
+
|
|
7
|
+
import { type Registry } from '@dxos/app-framework';
|
|
8
|
+
import { usePluginManager } from '@dxos/app-framework/ui';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Reads the registry plugin catalog from `manager.pluginRegistry.plugins`.
|
|
12
|
+
* The catalog is loaded once when the plugin manager is constructed (with a
|
|
13
|
+
* provider) so this hook is purely reactive — subscribers re-render as the
|
|
14
|
+
* `{entries, loading, error}` state settles.
|
|
15
|
+
*/
|
|
16
|
+
export const useRegistryPlugins = (): Registry.PluginsState => {
|
|
17
|
+
const manager = usePluginManager();
|
|
18
|
+
return useAtomValue(manager.pluginRegistry.plugins);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Returns the live `Registry.Manager` from the plugin manager so callers can
|
|
23
|
+
* issue `listVersions` / `getPlugin` calls directly. The catalog state itself
|
|
24
|
+
* is best read through {@link useRegistryPlugins} above.
|
|
25
|
+
*/
|
|
26
|
+
export const useRegistryPluginProvider = (): Registry.Manager => {
|
|
27
|
+
const manager = usePluginManager();
|
|
28
|
+
return manager.pluginRegistry;
|
|
29
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Registry, UrlLoader } from '@dxos/app-framework';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Returns a set of plugin ids for which the registry has a newer version
|
|
9
|
+
* than what was recorded at install time.
|
|
10
|
+
*
|
|
11
|
+
* Only flags plugins where a version was recorded at install time AND it differs
|
|
12
|
+
* from the catalog's current version. Legacy installs (no version stored) are ignored.
|
|
13
|
+
*
|
|
14
|
+
* NOTE: this is intentionally NOT memoised. The installed-version side of the
|
|
15
|
+
* comparison lives in localStorage and is mutated by `manager.add` /
|
|
16
|
+
* `manager.remove` / `UrlLoader.setInstalledVersion` outside React's view, so a
|
|
17
|
+
* memo keyed on `entries` would happily return stale data for the rest of the
|
|
18
|
+
* session after an update completes (the bug that produced "Update button
|
|
19
|
+
* reappears after Updating…"). The loop is O(catalog size) sync reads — cheap
|
|
20
|
+
* enough to do every render.
|
|
21
|
+
*/
|
|
22
|
+
export const useUpdateAvailableIds = (entries: readonly Registry.Plugin[]): ReadonlySet<string> => {
|
|
23
|
+
const ids = new Set<string>();
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
const installedVersion = UrlLoader.getInstalledVersion(entry.id);
|
|
26
|
+
if (installedVersion !== undefined && installedVersion !== entry.version) {
|
|
27
|
+
ids.add(entry.id);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return ids;
|
|
31
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
import { type Dispatch, type SetStateAction, useEffect, useMemo, useState } from 'react';
|
|
7
|
+
|
|
8
|
+
import { type Registry, UrlLoader } from '@dxos/app-framework';
|
|
9
|
+
import { runAndForwardErrors } from '@dxos/effect';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Owns the version picker's state machine: fetches the available versions list
|
|
13
|
+
* from the provider, prepends the installed version when the catalog stub omits
|
|
14
|
+
* it, and keeps the selection clamped to whatever's currently in the list (so
|
|
15
|
+
* an external update doesn't strand the trigger on a tag that's no longer
|
|
16
|
+
* available).
|
|
17
|
+
*/
|
|
18
|
+
export const useVersionPicker = ({
|
|
19
|
+
provider,
|
|
20
|
+
pluginId,
|
|
21
|
+
repo,
|
|
22
|
+
moduleUrl,
|
|
23
|
+
installedVersionTag,
|
|
24
|
+
}: {
|
|
25
|
+
provider: Registry.Manager;
|
|
26
|
+
pluginId: string;
|
|
27
|
+
repo: string | undefined;
|
|
28
|
+
moduleUrl: string | undefined;
|
|
29
|
+
installedVersionTag: string | undefined;
|
|
30
|
+
}): {
|
|
31
|
+
pickerVersions: readonly Registry.PluginVersion[];
|
|
32
|
+
selectedVersionTag: string | undefined;
|
|
33
|
+
setSelectedVersionTag: Dispatch<SetStateAction<string | undefined>>;
|
|
34
|
+
} => {
|
|
35
|
+
const [versions, setVersions] = useState<readonly Registry.PluginVersion[]>([]);
|
|
36
|
+
const [selectedVersionTag, setSelectedVersionTag] = useState<string | undefined>();
|
|
37
|
+
|
|
38
|
+
// Load version list once the catalog entry's repo is known. Reset state
|
|
39
|
+
// when `repo` is absent or the fetch fails so a previous plugin's
|
|
40
|
+
// versions don't leak into the picker for the current plugin. The
|
|
41
|
+
// `cancelled` flag guards against a stale in-flight `listVersions`
|
|
42
|
+
// response overwriting the newer state after `repo`/`pluginId` changes.
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (!repo) {
|
|
45
|
+
setVersions([]);
|
|
46
|
+
setSelectedVersionTag(undefined);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
// Clear stale picker state before fetching so the UI doesn't display the
|
|
50
|
+
// previous plugin's versions/selection while `listVersions` is pending.
|
|
51
|
+
setVersions([]);
|
|
52
|
+
setSelectedVersionTag(undefined);
|
|
53
|
+
let cancelled = false;
|
|
54
|
+
void provider.listVersions(repo).pipe(
|
|
55
|
+
Effect.match({
|
|
56
|
+
onSuccess: (vs) => {
|
|
57
|
+
if (cancelled) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
setVersions(vs);
|
|
61
|
+
// Default selection: the currently installed version, or the latest.
|
|
62
|
+
const installedVersion = UrlLoader.getInstalledVersion(pluginId);
|
|
63
|
+
setSelectedVersionTag(installedVersion ?? vs[0]?.tag);
|
|
64
|
+
},
|
|
65
|
+
onFailure: () => {
|
|
66
|
+
if (cancelled) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
setVersions([]);
|
|
70
|
+
setSelectedVersionTag(undefined);
|
|
71
|
+
},
|
|
72
|
+
}),
|
|
73
|
+
runAndForwardErrors,
|
|
74
|
+
);
|
|
75
|
+
return () => {
|
|
76
|
+
cancelled = true;
|
|
77
|
+
};
|
|
78
|
+
}, [provider, repo, pluginId]);
|
|
79
|
+
|
|
80
|
+
// Make sure the picker always lists the installed version, even if the catalog
|
|
81
|
+
// hasn't surfaced it (the current `listVersions` stub only returns latest).
|
|
82
|
+
const pickerVersions = useMemo<readonly Registry.PluginVersion[]>(() => {
|
|
83
|
+
if (!installedVersionTag) {
|
|
84
|
+
return versions;
|
|
85
|
+
}
|
|
86
|
+
if (versions.some((entry) => entry.tag === installedVersionTag)) {
|
|
87
|
+
return versions;
|
|
88
|
+
}
|
|
89
|
+
const installedEntry: Registry.PluginVersion = {
|
|
90
|
+
tag: installedVersionTag,
|
|
91
|
+
// Picker won't be re-installing this entry unless the user selects + clicks Install,
|
|
92
|
+
// and the catalog moduleUrl is the closest stand-in we have.
|
|
93
|
+
moduleUrl: moduleUrl ?? '',
|
|
94
|
+
};
|
|
95
|
+
return [installedEntry, ...versions];
|
|
96
|
+
}, [versions, installedVersionTag, moduleUrl]);
|
|
97
|
+
|
|
98
|
+
// Keep `selectedVersionTag` in sync when picker contents change (e.g. the user
|
|
99
|
+
// updated the plugin from the list view — installed version moved, picker
|
|
100
|
+
// shrunk, prior selection is no longer in the list).
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
if (pickerVersions.length === 0) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (selectedVersionTag && pickerVersions.some((entry) => entry.tag === selectedVersionTag)) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
setSelectedVersionTag(installedVersionTag ?? pickerVersions[0]?.tag);
|
|
109
|
+
}, [pickerVersions, selectedVersionTag, installedVersionTag]);
|
|
110
|
+
|
|
111
|
+
return { pickerVersions, selectedVersionTag, setSelectedVersionTag };
|
|
112
|
+
};
|
package/src/index.ts
CHANGED
package/src/meta.ts
CHANGED
|
@@ -2,20 +2,46 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Plugin } from '@dxos/app-framework';
|
|
6
|
+
import { pinnedWorkspaceId } from '@dxos/app-toolkit';
|
|
7
|
+
import { DXN } from '@dxos/keys';
|
|
6
8
|
import { trim } from '@dxos/util';
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
// TODO(wittjosiah): This is a hack to prevent the previous deck from being set for pinned items.
|
|
10
|
-
// Ideally this should be worked into the data model in a generic way.
|
|
11
|
-
export const REGISTRY_ID = '!dxos:plugin-registry';
|
|
10
|
+
export const REGISTRY_ID = pinnedWorkspaceId('dxos:plugin-registry');
|
|
12
11
|
export const REGISTRY_KEY = 'plugin-registry';
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
// TODO(wittjosiah): Should this be a special separator or use the standard path separator?
|
|
14
|
+
const CATEGORY_SEPARATOR = '>';
|
|
15
|
+
|
|
16
|
+
/** Build a registry category node ID. */
|
|
17
|
+
export const registryCategoryId = (category: string): string => `${REGISTRY_KEY}${CATEGORY_SEPARATOR}${category}`;
|
|
18
|
+
|
|
19
|
+
/** Qualified graph path to a specific plugin node. */
|
|
20
|
+
export const getPluginPath = (pluginId: string): string => `root/${REGISTRY_ID}/${pluginId}`;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Qualified graph path to the bundled MDL spec child node for a plugin.
|
|
24
|
+
*
|
|
25
|
+
* The child is contributed by whichever plugin can render MDL (today:
|
|
26
|
+
* `plugin-code`). plugin-registry only knows the path convention so it can:
|
|
27
|
+
* - dispatch `LayoutOperation.Open` to open the spec viewer, and
|
|
28
|
+
* - probe the app graph for the child's existence to gate the "View
|
|
29
|
+
* specification" button. If no plugin contributes a renderer for the
|
|
30
|
+
* child, the node is absent and the button stays hidden.
|
|
31
|
+
*/
|
|
32
|
+
export const getPluginSpecPath = (pluginId: string): string => `${getPluginPath(pluginId)}/spec`;
|
|
33
|
+
|
|
34
|
+
export const meta = Plugin.makeMeta({
|
|
35
|
+
key: DXN.make('org.dxos.plugin.registry'),
|
|
16
36
|
name: 'Plugins',
|
|
37
|
+
author: 'DXOS',
|
|
17
38
|
description: trim`
|
|
18
39
|
Plugin management system for discovering, installing, and configuring workspace extensions.
|
|
19
40
|
Browse available plugins and customize your workspace capabilities.
|
|
20
41
|
`,
|
|
21
|
-
|
|
42
|
+
icon: 'ph--squares-four--regular',
|
|
43
|
+
tags: ['system'],
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
/** Cascade-disable confirmation dialog surface id. */
|
|
47
|
+
export const DISABLE_DEPENDENTS_DIALOG = DXN.make(`${meta.id}.disableDependentsDialog`);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
|
|
7
|
+
import { LayoutOperation, SettingsOperation, getSpacePath } from '@dxos/app-toolkit';
|
|
8
|
+
import { Operation } from '@dxos/compute';
|
|
9
|
+
|
|
10
|
+
import { REGISTRY_ID } from '#meta';
|
|
11
|
+
|
|
12
|
+
const handler: Operation.WithHandler<typeof SettingsOperation.OpenPluginRegistry> =
|
|
13
|
+
SettingsOperation.OpenPluginRegistry.pipe(
|
|
14
|
+
Operation.withHandler(
|
|
15
|
+
Effect.fnUntraced(function* () {
|
|
16
|
+
yield* Operation.invoke(LayoutOperation.SwitchWorkspace, { subject: getSpacePath(REGISTRY_ID) });
|
|
17
|
+
}),
|
|
18
|
+
),
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
export default handler;
|
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Plugin } from '@dxos/app-framework';
|
|
6
|
+
|
|
7
|
+
import { meta } from './meta';
|
|
8
|
+
|
|
9
|
+
export const RegistryPlugin = Plugin.lazy(meta, () => import('#plugin'));
|
|
10
|
+
|
|
11
|
+
export { RegistryOperationHandlerSet } from './operations';
|
|
@@ -6,37 +6,31 @@ import * as FileSystem from '@effect/platform/FileSystem';
|
|
|
6
6
|
import * as Path from '@effect/platform/Path';
|
|
7
7
|
import * as Effect from 'effect/Effect';
|
|
8
8
|
import * as Schema from 'effect/Schema';
|
|
9
|
+
import * as Yaml from 'yaml';
|
|
9
10
|
|
|
10
|
-
import { DX_CONFIG
|
|
11
|
+
import { DX_CONFIG } from '@dxos/client-protocol';
|
|
11
12
|
|
|
12
13
|
// TODO(wittjosiah): Factor out to app-framework?
|
|
13
14
|
|
|
14
|
-
const PLUGINS_FILE = 'plugins.json';
|
|
15
|
-
|
|
16
15
|
const PluginsSchema = Schema.Array(Schema.String);
|
|
17
16
|
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
*/
|
|
21
|
-
const getPluginsPath = (profile: string): Effect.Effect<string, never, Path.Path> =>
|
|
22
|
-
Effect.gen(function* () {
|
|
23
|
-
const path = yield* Path.Path;
|
|
24
|
-
return path.join(getProfilePath(DX_CONFIG, profile), PLUGINS_FILE);
|
|
25
|
-
});
|
|
17
|
+
/** CLI-only: path to plugins/<profile>.yml (sibling to profile/). */
|
|
18
|
+
const getPluginsConfigPath = (profile: string) => `${DX_CONFIG}/plugins/${profile}.yml`;
|
|
26
19
|
|
|
27
20
|
/**
|
|
28
|
-
* Load enabled plugins from profile
|
|
21
|
+
* Load enabled plugins from plugins/<profile>.yml (sibling to profile/).
|
|
29
22
|
* Returns the list of enabled plugin IDs, or an empty array if the file doesn't exist.
|
|
30
23
|
*/
|
|
31
24
|
export const loadEnabledPlugins = Effect.fn(function* ({ profile }: { profile: string }) {
|
|
32
25
|
const fs = yield* FileSystem.FileSystem;
|
|
33
|
-
const pluginsPath =
|
|
26
|
+
const pluginsPath = getPluginsConfigPath(profile);
|
|
34
27
|
|
|
35
28
|
const content = yield* fs
|
|
36
29
|
.readFileString(pluginsPath)
|
|
37
30
|
.pipe(Effect.catchTag('SystemError', () => Effect.succeed('[]')));
|
|
38
31
|
|
|
39
|
-
const
|
|
32
|
+
const raw = Yaml.parse(content);
|
|
33
|
+
const parsed = yield* Schema.decodeUnknown(PluginsSchema)(raw ?? []).pipe(
|
|
40
34
|
Effect.catchAll(() => Effect.succeed([] as string[])),
|
|
41
35
|
);
|
|
42
36
|
|
|
@@ -44,14 +38,13 @@ export const loadEnabledPlugins = Effect.fn(function* ({ profile }: { profile: s
|
|
|
44
38
|
});
|
|
45
39
|
|
|
46
40
|
/**
|
|
47
|
-
* Save enabled plugins to profile
|
|
41
|
+
* Save enabled plugins to plugins/<profile>.yml.
|
|
48
42
|
*/
|
|
49
43
|
export const saveEnabledPlugins = Effect.fn(function* ({ profile, enabled }: { profile: string; enabled: string[] }) {
|
|
50
44
|
const fs = yield* FileSystem.FileSystem;
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
yield* fs.writeFileString(pluginsPath, encoded + '\n');
|
|
45
|
+
const path = yield* Path.Path;
|
|
46
|
+
const pluginsPath = getPluginsConfigPath(profile);
|
|
47
|
+
yield* fs.makeDirectory(path.dirname(pluginsPath), { recursive: true });
|
|
48
|
+
const encoded = Yaml.stringify(enabled);
|
|
49
|
+
yield* fs.writeFileString(pluginsPath, encoded);
|
|
57
50
|
});
|