@dxos/plugin-registry 0.8.4-staging.60fe92afc8 → 0.9.1-main.c7dcc2e112
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/dist/lib/neutral/RegistryPlugin.node.mjs +709 -18
- package/dist/lib/neutral/RegistryPlugin.node.mjs.map +4 -4
- package/dist/lib/neutral/chunk-ZY37XLVF.mjs +46 -0
- package/dist/lib/neutral/chunk-ZY37XLVF.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +1 -1
- package/dist/lib/neutral/meta.json +1 -1
- package/dist/lib/neutral/{open-plugin-registry-II735N76.mjs → open-plugin-registry-ID7LV3SF.mjs} +3 -3
- package/dist/lib/neutral/open-plugin-registry-ID7LV3SF.mjs.map +7 -0
- package/dist/lib/neutral/plugin.mjs +2 -2
- package/dist/lib/neutral/translations.mjs +1 -1
- package/dist/lib/neutral/translations.mjs.map +3 -3
- package/dist/types/dx.config.d.ts +28 -0
- package/dist/types/dx.config.d.ts.map +1 -0
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
- package/dist/types/src/capabilities/index.d.ts +4 -12
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts +2 -2
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
- package/dist/types/src/categories.d.ts.map +1 -1
- package/dist/types/src/commands/index.d.ts +2 -2
- package/dist/types/src/commands/index.d.ts.map +1 -1
- package/dist/types/src/commands/plugin/disable.d.ts.map +1 -0
- package/dist/types/src/commands/plugin/enable.d.ts.map +1 -0
- package/dist/types/src/commands/plugin/index.d.ts +3 -0
- package/dist/types/src/commands/plugin/index.d.ts.map +1 -0
- package/dist/types/src/commands/plugin/list.d.ts.map +1 -0
- package/dist/types/src/commands/plugin/util.d.ts.map +1 -0
- package/dist/types/src/commands/registry/index.d.ts +16 -0
- package/dist/types/src/commands/registry/index.d.ts.map +1 -0
- package/dist/types/src/commands/registry/publish-package.d.ts +29 -0
- package/dist/types/src/commands/registry/publish-package.d.ts.map +1 -0
- package/dist/types/src/commands/registry/publish-publisher.d.ts +19 -0
- package/dist/types/src/commands/registry/publish-publisher.d.ts.map +1 -0
- package/dist/types/src/commands/registry/publish.d.ts +25 -0
- package/dist/types/src/commands/registry/publish.d.ts.map +1 -0
- package/dist/types/src/commands/registry/records.d.ts +14 -0
- package/dist/types/src/commands/registry/records.d.ts.map +1 -0
- package/dist/types/src/commands/registry/unpublish.d.ts +15 -0
- package/dist/types/src/commands/registry/unpublish.d.ts.map +1 -0
- package/dist/types/src/commands/registry/util.d.ts +113 -0
- package/dist/types/src/commands/registry/util.d.ts.map +1 -0
- package/dist/types/src/commands/registry/verify.d.ts +20 -0
- package/dist/types/src/commands/registry/verify.d.ts.map +1 -0
- package/dist/types/src/components/PluginDetail/PluginDetail.d.ts +2 -2
- package/dist/types/src/components/PluginDetail/PluginDetail.d.ts.map +1 -1
- package/dist/types/src/components/PluginDetail/PluginDetail.stories.d.ts.map +1 -1
- package/dist/types/src/components/PluginList/PluginItem.d.ts.map +1 -1
- package/dist/types/src/components/PluginList/PluginList.stories.d.ts.map +1 -1
- package/dist/types/src/containers/LoadPluginDialog/LoadPluginDialog.d.ts.map +1 -1
- package/dist/types/src/containers/PluginArticle/PluginArticle.d.ts.map +1 -1
- package/dist/types/src/containers/PublicRegistryArticle/PublicRegistryArticle.d.ts.map +1 -1
- package/dist/types/src/hooks/useAutoTags.d.ts +2 -2
- package/dist/types/src/hooks/useAutoTags.d.ts.map +1 -1
- package/dist/types/src/hooks/useCatalogEntry.d.ts +4 -5
- package/dist/types/src/hooks/useCatalogEntry.d.ts.map +1 -1
- package/dist/types/src/hooks/usePluginActions.d.ts +3 -3
- package/dist/types/src/hooks/usePluginActions.d.ts.map +1 -1
- package/dist/types/src/hooks/useUpdateAvailable.d.ts +2 -2
- package/dist/types/src/hooks/useUpdateAvailable.d.ts.map +1 -1
- package/dist/types/src/hooks/useVersionPicker.d.ts +7 -8
- package/dist/types/src/hooks/useVersionPicker.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +28 -2
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/operations/open-plugin-registry.d.ts.map +1 -1
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/dx.config.ts +20 -0
- package/package.json +31 -24
- package/src/RegistryPlugin.node.ts +2 -2
- package/src/RegistryPlugin.test.ts +1 -1
- package/src/capabilities/app-graph-builder.ts +43 -39
- package/src/capabilities/dev-plugin-loader.ts +1 -1
- package/src/capabilities/react-surface.tsx +1 -4
- package/src/capabilities/settings.ts +2 -2
- package/src/categories.ts +5 -4
- package/src/commands/index.ts +2 -10
- package/src/commands/{disable.ts → plugin/disable.ts} +3 -3
- package/src/commands/{enable.ts → plugin/enable.ts} +3 -3
- package/src/commands/plugin/index.ts +14 -0
- package/src/commands/{list.ts → plugin/list.ts} +4 -4
- package/src/commands/registry/index.ts +30 -0
- package/src/commands/registry/publish-package.ts +119 -0
- package/src/commands/registry/publish-publisher.ts +73 -0
- package/src/commands/registry/publish.ts +278 -0
- package/src/commands/registry/records.ts +73 -0
- package/src/commands/registry/unpublish.ts +70 -0
- package/src/commands/registry/util.ts +395 -0
- package/src/commands/registry/verify.ts +76 -0
- package/src/components/DisableDependentsAlert/DisableDependentsAlert.tsx +1 -1
- package/src/components/PluginDetail/PluginDetail.stories.tsx +5 -3
- package/src/components/PluginDetail/PluginDetail.tsx +21 -23
- package/src/components/PluginFailureBadge/PluginFailureBadge.tsx +1 -1
- package/src/components/PluginList/PluginItem.tsx +5 -3
- package/src/components/PluginList/PluginList.stories.tsx +1 -2
- package/src/components/PluginList/PluginList.tsx +4 -4
- package/src/components/RegistrySettings/RegistrySettings.tsx +1 -1
- package/src/containers/BaseRegistryArticle/BaseRegistryArticle.tsx +2 -2
- package/src/containers/LoadPluginDialog/LoadPluginDialog.tsx +3 -3
- package/src/containers/PluginArticle/PluginArticle.tsx +8 -6
- package/src/containers/PublicRegistryArticle/PublicRegistryArticle.tsx +35 -28
- package/src/containers/RegistryArticle/RegistryArticle.tsx +1 -1
- package/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.tsx +1 -1
- package/src/hooks/useAutoTags.ts +3 -3
- package/src/hooks/useCatalogEntry.ts +6 -7
- package/src/hooks/useDisableConfirmation.ts +1 -1
- package/src/hooks/usePluginActions.ts +13 -11
- package/src/hooks/useUpdateAvailable.ts +5 -5
- package/src/hooks/useVersionPicker.ts +22 -30
- package/src/meta.ts +6 -15
- package/src/operations/open-plugin-registry.ts +2 -2
- package/src/translations.ts +1 -1
- package/src/types.ts +3 -1
- package/dist/lib/neutral/chunk-GRQI6MLI.mjs +0 -36
- package/dist/lib/neutral/chunk-GRQI6MLI.mjs.map +0 -7
- package/dist/lib/neutral/open-plugin-registry-II735N76.mjs.map +0 -7
- package/dist/types/src/commands/disable.d.ts.map +0 -1
- package/dist/types/src/commands/enable.d.ts.map +0 -1
- package/dist/types/src/commands/list.d.ts.map +0 -1
- package/dist/types/src/commands/util.d.ts.map +0 -1
- /package/dist/types/src/commands/{disable.d.ts → plugin/disable.d.ts} +0 -0
- /package/dist/types/src/commands/{enable.d.ts → plugin/enable.d.ts} +0 -0
- /package/dist/types/src/commands/{list.d.ts → plugin/list.d.ts} +0 -0
- /package/dist/types/src/commands/{util.d.ts → plugin/util.d.ts} +0 -0
- /package/src/commands/{util.ts → plugin/util.ts} +0 -0
|
@@ -37,7 +37,7 @@ export const RegistrySettings = ({
|
|
|
37
37
|
onEnableDev,
|
|
38
38
|
onDisableDev,
|
|
39
39
|
}: RegistrySettingsProps) => {
|
|
40
|
-
const { t } = useTranslation(meta.
|
|
40
|
+
const { t } = useTranslation(meta.profile.key);
|
|
41
41
|
const [busy, setBusy] = useState(false);
|
|
42
42
|
const enabled = !!settings.devPluginEnabled;
|
|
43
43
|
const url = settings.devPluginUrl ?? '';
|
|
@@ -20,7 +20,7 @@ import { getPluginPath, meta } from '#meta';
|
|
|
20
20
|
import { useDisableConfirmation } from '../../hooks';
|
|
21
21
|
|
|
22
22
|
const matchesFilter = (plugin: Plugin.Plugin, query: string) => {
|
|
23
|
-
const haystack = `${plugin.meta.name ?? ''} ${plugin.meta.
|
|
23
|
+
const haystack = `${plugin.meta.profile.name ?? ''} ${plugin.meta.profile.key}`.toLowerCase();
|
|
24
24
|
return haystack.includes(query);
|
|
25
25
|
};
|
|
26
26
|
|
|
@@ -67,7 +67,7 @@ export const BaseRegistryArticle = composable<HTMLDivElement, BaseRegistryArticl
|
|
|
67
67
|
},
|
|
68
68
|
forwardedRef,
|
|
69
69
|
) => {
|
|
70
|
-
const { t } = useTranslation(meta.
|
|
70
|
+
const { t } = useTranslation(meta.profile.key);
|
|
71
71
|
const manager = usePluginManager();
|
|
72
72
|
const { invoke, invokePromise } = useOperationInvoker();
|
|
73
73
|
const allSettings = useCapabilities(AppCapabilities.Settings);
|
|
@@ -12,11 +12,11 @@ import { Button, Dialog, Input, useTranslation } from '@dxos/react-ui';
|
|
|
12
12
|
|
|
13
13
|
import { meta } from '../../meta';
|
|
14
14
|
|
|
15
|
-
export const LOAD_PLUGIN_DIALOG = DXN.make(`${meta.
|
|
15
|
+
export const LOAD_PLUGIN_DIALOG = DXN.make(`${meta.profile.key}.loadPluginDialog`);
|
|
16
16
|
|
|
17
17
|
export const LoadPluginDialog = () => {
|
|
18
18
|
const manager = usePluginManager();
|
|
19
|
-
const { t } = useTranslation(meta.
|
|
19
|
+
const { t } = useTranslation(meta.profile.key);
|
|
20
20
|
const [url, setUrl] = useState('');
|
|
21
21
|
const [loading, setLoading] = useState(false);
|
|
22
22
|
const [error, setError] = useState<string | null>(null);
|
|
@@ -33,7 +33,7 @@ export const LoadPluginDialog = () => {
|
|
|
33
33
|
|
|
34
34
|
void Effect.gen(function* () {
|
|
35
35
|
const plugin = yield* manager.add(trimmed);
|
|
36
|
-
yield* manager.enable(plugin.meta.
|
|
36
|
+
yield* manager.enable(plugin.meta.profile.key);
|
|
37
37
|
closeRef.current?.click();
|
|
38
38
|
}).pipe(
|
|
39
39
|
Effect.catchAll((err) =>
|
|
@@ -24,19 +24,18 @@ import {
|
|
|
24
24
|
export type PluginArticleProps = { subject: Plugin.Plugin };
|
|
25
25
|
|
|
26
26
|
export const PluginArticle = ({ subject: plugin }: PluginArticleProps) => {
|
|
27
|
-
const pluginId = plugin.meta.
|
|
27
|
+
const pluginId = plugin.meta.profile.key;
|
|
28
28
|
const manager = usePluginManager();
|
|
29
29
|
const plugins = useAtomValue(manager.plugins);
|
|
30
30
|
const remotePluginIds = useRemotePluginIds();
|
|
31
31
|
const provider = useRegistryPluginProvider();
|
|
32
32
|
const { invokePromise } = useOperationInvoker();
|
|
33
33
|
|
|
34
|
-
const { catalogEntry, moduleUrl
|
|
34
|
+
const { catalogEntry, moduleUrl } = useCatalogEntry(pluginId);
|
|
35
35
|
const { installedVersionTag, syncInstalledVersion } = useInstalledVersionTag(pluginId, plugins);
|
|
36
36
|
const { pickerVersions, selectedVersionTag, setSelectedVersionTag } = useVersionPicker({
|
|
37
37
|
provider,
|
|
38
38
|
pluginId,
|
|
39
|
-
repo,
|
|
40
39
|
moduleUrl,
|
|
41
40
|
installedVersionTag,
|
|
42
41
|
});
|
|
@@ -44,11 +43,14 @@ export const PluginArticle = ({ subject: plugin }: PluginArticleProps) => {
|
|
|
44
43
|
const enabled = manager.getEnabled().includes(pluginId);
|
|
45
44
|
const failed = useAtomValue(manager.failed);
|
|
46
45
|
const failure = useMemo(() => failed.find((entry) => entry.id === pluginId), [failed, pluginId]);
|
|
47
|
-
const isInstalled = useMemo(
|
|
46
|
+
const isInstalled = useMemo(
|
|
47
|
+
() => plugins.some((candidate) => candidate.meta.profile.key === pluginId),
|
|
48
|
+
[plugins, pluginId],
|
|
49
|
+
);
|
|
48
50
|
const isCore = manager.getCore().includes(pluginId);
|
|
49
51
|
const canUninstall = isInstalled && !isCore && remotePluginIds.has(pluginId);
|
|
50
52
|
const hasUpdate =
|
|
51
|
-
isInstalled && !!catalogEntry && !!installedVersionTag && installedVersionTag !== catalogEntry.version;
|
|
53
|
+
isInstalled && !!catalogEntry && !!installedVersionTag && installedVersionTag !== catalogEntry.release?.version;
|
|
52
54
|
|
|
53
55
|
// Recompute graph slices whenever the plugin list changes, so installs /
|
|
54
56
|
// removals through other surfaces (or this article's own actions) keep the
|
|
@@ -66,7 +68,7 @@ export const PluginArticle = ({ subject: plugin }: PluginArticleProps) => {
|
|
|
66
68
|
// only loaded when it is activated, so the always-available `meta.name`
|
|
67
69
|
// from the registered plugin set is the right source for chip labels.
|
|
68
70
|
const handleResolvePluginName = useCallback(
|
|
69
|
-
(id: string): string => plugins.find((candidate) => candidate.meta.
|
|
71
|
+
(id: string): string => plugins.find((candidate) => candidate.meta.profile.key === id)?.meta.profile.name ?? id,
|
|
70
72
|
[plugins],
|
|
71
73
|
);
|
|
72
74
|
|
|
@@ -6,7 +6,7 @@ import { useAtomValue } from '@effect-atom/atom-react';
|
|
|
6
6
|
import * as Effect from 'effect/Effect';
|
|
7
7
|
import React, { useCallback, useMemo, useState } from 'react';
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { Plugin, UrlLoader } from '@dxos/app-framework';
|
|
10
10
|
import { useOperationInvoker, usePluginManager } from '@dxos/app-framework/ui';
|
|
11
11
|
import { EffectEx } from '@dxos/effect';
|
|
12
12
|
import { DXN } from '@dxos/keys';
|
|
@@ -19,29 +19,30 @@ import { meta } from '#meta';
|
|
|
19
19
|
import { useAutoTags, useRegistryPlugins, useUpdateAvailableIds } from '../../hooks';
|
|
20
20
|
import { BaseRegistryArticle } from '../BaseRegistryArticle';
|
|
21
21
|
|
|
22
|
-
const sortEntries = (a:
|
|
22
|
+
const sortEntries = (a: Plugin.Meta, b: Plugin.Meta) =>
|
|
23
|
+
(a.profile.name ?? a.profile.key).localeCompare(b.profile.name ?? b.profile.key);
|
|
23
24
|
|
|
24
25
|
const sortPlugins = (a: Plugin.Plugin, b: Plugin.Plugin) =>
|
|
25
|
-
(a.meta.name ?? a.meta.
|
|
26
|
+
(a.meta.profile.name ?? a.meta.profile.key).localeCompare(b.meta.profile.name ?? b.meta.profile.key);
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* Turns a registry catalog entry into a minimal Plugin object so we can reuse
|
|
29
30
|
* {@link BaseRegistryArticle} for rendering. The synthesized plugin has no
|
|
30
31
|
* modules — it exists only for display until the user installs it.
|
|
31
32
|
*/
|
|
32
|
-
const toDisplayPlugin = (plugin:
|
|
33
|
+
const toDisplayPlugin = (plugin: Plugin.Meta): Plugin.Plugin =>
|
|
33
34
|
({
|
|
34
35
|
[Plugin.PluginTypeId]: Plugin.PluginTypeId,
|
|
35
36
|
meta: Plugin.makeMeta({
|
|
36
|
-
key: DXN.make(plugin.
|
|
37
|
-
name: plugin.name,
|
|
38
|
-
description: plugin.description,
|
|
39
|
-
homePage: plugin.homePage,
|
|
40
|
-
source: plugin.source,
|
|
41
|
-
screenshots: plugin.screenshots,
|
|
42
|
-
tags: plugin.tags,
|
|
43
|
-
icon: plugin.icon,
|
|
44
|
-
|
|
37
|
+
key: DXN.make(plugin.profile.key),
|
|
38
|
+
name: plugin.profile.name,
|
|
39
|
+
description: plugin.profile.description,
|
|
40
|
+
homePage: plugin.profile.homePage,
|
|
41
|
+
source: plugin.profile.source,
|
|
42
|
+
screenshots: plugin.profile.screenshots,
|
|
43
|
+
tags: plugin.profile.tags,
|
|
44
|
+
icon: plugin.profile.icon,
|
|
45
|
+
author: plugin.profile.author,
|
|
45
46
|
}),
|
|
46
47
|
modules: [],
|
|
47
48
|
}) as Plugin.Plugin;
|
|
@@ -52,18 +53,18 @@ export type PublicRegistryArticleProps = {
|
|
|
52
53
|
|
|
53
54
|
export const PublicRegistryArticle = composable<HTMLDivElement, PublicRegistryArticleProps>(
|
|
54
55
|
({ id, ...props }, forwardedRef) => {
|
|
55
|
-
const { t } = useTranslation(meta.
|
|
56
|
+
const { t } = useTranslation(meta.profile.key);
|
|
56
57
|
const manager = usePluginManager();
|
|
57
58
|
const { invoke } = useOperationInvoker();
|
|
58
59
|
const { entries, loading, error } = useRegistryPlugins();
|
|
59
60
|
const plugins = useAtomValue(manager.plugins);
|
|
60
|
-
const installedIds = useMemo(() => plugins.map((plugin) => plugin.meta.
|
|
61
|
+
const installedIds = useMemo(() => plugins.map((plugin) => plugin.meta.profile.key), [plugins]);
|
|
61
62
|
const extraTagsById = useAutoTags(entries);
|
|
62
63
|
|
|
63
64
|
// Snapshot of installed plugin ids at mount time. Used to sort installed
|
|
64
65
|
// plugins to the top without having newly-installed rows jump up mid-session.
|
|
65
66
|
const [installedSnapshot] = useState<ReadonlySet<string>>(
|
|
66
|
-
() => new Set(manager.getPlugins().map((plugin) => plugin.meta.
|
|
67
|
+
() => new Set(manager.getPlugins().map((plugin) => plugin.meta.profile.key)),
|
|
67
68
|
);
|
|
68
69
|
|
|
69
70
|
const [installingIds, setInstallingIds] = useState<readonly string[]>([]);
|
|
@@ -74,7 +75,9 @@ export const PublicRegistryArticle = composable<HTMLDivElement, PublicRegistryAr
|
|
|
74
75
|
const moduleUrlById = useMemo(() => {
|
|
75
76
|
const map: Record<string, string> = {};
|
|
76
77
|
for (const entry of sortedEntries) {
|
|
77
|
-
|
|
78
|
+
if (entry.release?.moduleUrl) {
|
|
79
|
+
map[entry.profile.key] = entry.release.moduleUrl;
|
|
80
|
+
}
|
|
78
81
|
}
|
|
79
82
|
return map;
|
|
80
83
|
}, [sortedEntries]);
|
|
@@ -83,7 +86,9 @@ export const PublicRegistryArticle = composable<HTMLDivElement, PublicRegistryAr
|
|
|
83
86
|
const versionById = useMemo(() => {
|
|
84
87
|
const map: Record<string, string> = {};
|
|
85
88
|
for (const entry of sortedEntries) {
|
|
86
|
-
|
|
89
|
+
if (entry.release?.version) {
|
|
90
|
+
map[entry.profile.key] = entry.release.version;
|
|
91
|
+
}
|
|
87
92
|
}
|
|
88
93
|
return map;
|
|
89
94
|
}, [sortedEntries]);
|
|
@@ -92,13 +97,15 @@ export const PublicRegistryArticle = composable<HTMLDivElement, PublicRegistryAr
|
|
|
92
97
|
// is based on the mount-time snapshot so rows don't jump as the user
|
|
93
98
|
// installs plugins during the session.
|
|
94
99
|
const items = useMemo(() => {
|
|
95
|
-
const catalogIds = new Set(sortedEntries.map((entry) => entry.
|
|
100
|
+
const catalogIds = new Set(sortedEntries.map((entry) => entry.profile.key));
|
|
96
101
|
const remoteIds = new Set(UrlLoader.getRemoteEntries().map((entry) => entry.id));
|
|
97
102
|
const fromCatalog = sortedEntries.map(toDisplayPlugin);
|
|
98
|
-
const fromUrlOnly = plugins.filter(
|
|
103
|
+
const fromUrlOnly = plugins.filter(
|
|
104
|
+
(plugin) => remoteIds.has(plugin.meta.profile.key) && !catalogIds.has(plugin.meta.profile.key),
|
|
105
|
+
);
|
|
99
106
|
const installedFirst = [...fromCatalog, ...fromUrlOnly].sort((a, b) => {
|
|
100
|
-
const aInstalled = installedSnapshot.has(a.meta.
|
|
101
|
-
const bInstalled = installedSnapshot.has(b.meta.
|
|
107
|
+
const aInstalled = installedSnapshot.has(a.meta.profile.key);
|
|
108
|
+
const bInstalled = installedSnapshot.has(b.meta.profile.key);
|
|
102
109
|
if (aInstalled !== bInstalled) {
|
|
103
110
|
return aInstalled ? -1 : 1;
|
|
104
111
|
}
|
|
@@ -117,14 +124,14 @@ export const PublicRegistryArticle = composable<HTMLDivElement, PublicRegistryAr
|
|
|
117
124
|
setInstallingIds((prev) => (prev.includes(pluginId) ? prev : [...prev, pluginId]));
|
|
118
125
|
void Effect.gen(function* () {
|
|
119
126
|
const plugin = yield* manager.add(moduleUrl);
|
|
120
|
-
yield* manager.enable(plugin.meta.
|
|
127
|
+
yield* manager.enable(plugin.meta.profile.key);
|
|
121
128
|
// Persist the installed version for future update detection.
|
|
122
129
|
if (version) {
|
|
123
|
-
UrlLoader.setInstalledVersion(plugin.meta.
|
|
130
|
+
UrlLoader.setInstalledVersion(plugin.meta.profile.key, version);
|
|
124
131
|
}
|
|
125
132
|
yield* invoke(ObservabilityOperation.SendEvent, {
|
|
126
133
|
name: 'plugins.install',
|
|
127
|
-
properties: { plugin: plugin.meta.
|
|
134
|
+
properties: { plugin: plugin.meta.profile.key, source: 'registry' },
|
|
128
135
|
});
|
|
129
136
|
}).pipe(
|
|
130
137
|
Effect.ensuring(Effect.sync(() => setInstallingIds((prev) => prev.filter((pid) => pid !== pluginId)))),
|
|
@@ -146,13 +153,13 @@ export const PublicRegistryArticle = composable<HTMLDivElement, PublicRegistryAr
|
|
|
146
153
|
// Unload the old version then re-install from the new URL.
|
|
147
154
|
yield* manager.remove(pluginId);
|
|
148
155
|
const plugin = yield* manager.add(moduleUrl);
|
|
149
|
-
yield* manager.enable(plugin.meta.
|
|
156
|
+
yield* manager.enable(plugin.meta.profile.key);
|
|
150
157
|
if (version) {
|
|
151
|
-
UrlLoader.setInstalledVersion(plugin.meta.
|
|
158
|
+
UrlLoader.setInstalledVersion(plugin.meta.profile.key, version);
|
|
152
159
|
}
|
|
153
160
|
yield* invoke(ObservabilityOperation.SendEvent, {
|
|
154
161
|
name: 'plugins.update',
|
|
155
|
-
properties: { plugin: plugin.meta.
|
|
162
|
+
properties: { plugin: plugin.meta.profile.key, source: 'registry' },
|
|
156
163
|
});
|
|
157
164
|
}).pipe(
|
|
158
165
|
Effect.ensuring(Effect.sync(() => setUpdatingIds((prev) => prev.filter((pid) => pid !== pluginId)))),
|
|
@@ -12,7 +12,7 @@ import { composable } from '@dxos/react-ui';
|
|
|
12
12
|
import { BaseRegistryArticle } from '../BaseRegistryArticle';
|
|
13
13
|
|
|
14
14
|
const sortByPluginMeta = (a: Plugin.Plugin, b: Plugin.Plugin) =>
|
|
15
|
-
(a.meta.name ?? a.meta.
|
|
15
|
+
(a.meta.profile.name ?? a.meta.profile.key).localeCompare(b.meta.profile.name ?? b.meta.profile.key);
|
|
16
16
|
|
|
17
17
|
export type RegistryArticleProps = {
|
|
18
18
|
id: string;
|
|
@@ -33,7 +33,7 @@ export const RegistrySettingsContainer = ({ subject }: RegistrySettingsContainer
|
|
|
33
33
|
await EffectEx.runAndForwardErrors(
|
|
34
34
|
Effect.gen(function* () {
|
|
35
35
|
const plugin = yield* manager.add(url);
|
|
36
|
-
yield* manager.enable(plugin.meta.
|
|
36
|
+
yield* manager.enable(plugin.meta.profile.key);
|
|
37
37
|
}),
|
|
38
38
|
);
|
|
39
39
|
},
|
package/src/hooks/useAutoTags.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { useMemo } from 'react';
|
|
6
6
|
|
|
7
|
-
import { type
|
|
7
|
+
import { type Plugin, UrlLoader } from '@dxos/app-framework';
|
|
8
8
|
|
|
9
9
|
import { getRemotePluginIds } from '../categories';
|
|
10
10
|
|
|
@@ -21,7 +21,7 @@ export type AutoTagsMap = Record<string, readonly string[]>;
|
|
|
21
21
|
* Reads the persisted remote-plugin entries synchronously to derive `local`, and
|
|
22
22
|
* adds `registry` for any plugin id found in the supplied registry manifest entries.
|
|
23
23
|
*/
|
|
24
|
-
export const useAutoTags = (registryEntries: readonly
|
|
24
|
+
export const useAutoTags = (registryEntries: readonly Plugin.Meta[]): AutoTagsMap =>
|
|
25
25
|
useMemo(() => {
|
|
26
26
|
const byId: Record<string, string[]> = {};
|
|
27
27
|
const addTag = (id: string, tag: string) => {
|
|
@@ -39,7 +39,7 @@ export const useAutoTags = (registryEntries: readonly Registry.Plugin[]): AutoTa
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
for (const entry of registryEntries) {
|
|
42
|
-
addTag(entry.
|
|
42
|
+
addTag(entry.profile.key, 'registry');
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
return byId;
|
|
@@ -4,22 +4,21 @@
|
|
|
4
4
|
|
|
5
5
|
import { useMemo } from 'react';
|
|
6
6
|
|
|
7
|
-
import { type
|
|
7
|
+
import { type Plugin } from '@dxos/app-framework';
|
|
8
8
|
|
|
9
9
|
import { useRegistryPlugins } from './useRegistryPlugins';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Resolves the registry catalog entry for a plugin by id, plus the
|
|
13
|
-
*
|
|
12
|
+
* Resolves the registry catalog entry for a plugin by id, plus the `moduleUrl`
|
|
13
|
+
* the install/update actions need.
|
|
14
14
|
*/
|
|
15
15
|
export const useCatalogEntry = (
|
|
16
16
|
pluginId: string,
|
|
17
17
|
): {
|
|
18
|
-
catalogEntry:
|
|
18
|
+
catalogEntry: Plugin.Meta | undefined;
|
|
19
19
|
moduleUrl: string | undefined;
|
|
20
|
-
repo: string | undefined;
|
|
21
20
|
} => {
|
|
22
21
|
const { entries } = useRegistryPlugins();
|
|
23
|
-
const catalogEntry = useMemo(() => entries.find((entry) => entry.
|
|
24
|
-
return { catalogEntry, moduleUrl: catalogEntry?.moduleUrl
|
|
22
|
+
const catalogEntry = useMemo(() => entries.find((entry) => entry.profile.key === pluginId), [entries, pluginId]);
|
|
23
|
+
return { catalogEntry, moduleUrl: catalogEntry?.release?.moduleUrl };
|
|
25
24
|
};
|
|
@@ -37,7 +37,7 @@ export const useDisableConfirmation = (manager: PluginManager.PluginManager, dis
|
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
39
|
const resolveName = (id: string): string =>
|
|
40
|
-
manager.getPlugins().find((plugin) => plugin.meta.
|
|
40
|
+
manager.getPlugins().find((plugin) => plugin.meta.profile.key === id)?.meta.profile.name ?? id;
|
|
41
41
|
void invokePromise(LayoutOperation.UpdateDialog, {
|
|
42
42
|
subject: DISABLE_DEPENDENTS_DIALOG,
|
|
43
43
|
state: true,
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import * as Effect from 'effect/Effect';
|
|
6
6
|
import { useCallback, useState } from 'react';
|
|
7
7
|
|
|
8
|
-
import { type
|
|
8
|
+
import { type Plugin, type PluginManager, UrlLoader } from '@dxos/app-framework';
|
|
9
9
|
import { useOperationInvoker } from '@dxos/app-framework/ui';
|
|
10
10
|
import { LayoutOperation } from '@dxos/app-toolkit';
|
|
11
11
|
import { useAppGraph } from '@dxos/app-toolkit/ui';
|
|
@@ -21,8 +21,8 @@ export type PluginActionsProps = {
|
|
|
21
21
|
pluginId: string;
|
|
22
22
|
isInstalled: boolean;
|
|
23
23
|
moduleUrl: string | undefined;
|
|
24
|
-
catalogEntry:
|
|
25
|
-
pickerVersions: readonly
|
|
24
|
+
catalogEntry: Plugin.Meta | undefined;
|
|
25
|
+
pickerVersions: readonly Plugin.Release[];
|
|
26
26
|
selectedVersionTag: string | undefined;
|
|
27
27
|
syncInstalledVersion: () => void;
|
|
28
28
|
};
|
|
@@ -101,9 +101,9 @@ export const usePluginActions = ({
|
|
|
101
101
|
setInstalling(true);
|
|
102
102
|
void Effect.gen(function* () {
|
|
103
103
|
const added = yield* manager.add(moduleUrl);
|
|
104
|
-
yield* manager.enable(added.meta.
|
|
105
|
-
if (catalogEntry) {
|
|
106
|
-
UrlLoader.setInstalledVersion(added.meta.
|
|
104
|
+
yield* manager.enable(added.meta.profile.key);
|
|
105
|
+
if (catalogEntry?.release?.version) {
|
|
106
|
+
UrlLoader.setInstalledVersion(added.meta.profile.key, catalogEntry.release.version);
|
|
107
107
|
}
|
|
108
108
|
}).pipe(
|
|
109
109
|
Effect.ensuring(
|
|
@@ -125,8 +125,10 @@ export const usePluginActions = ({
|
|
|
125
125
|
void Effect.gen(function* () {
|
|
126
126
|
yield* manager.remove(pluginId);
|
|
127
127
|
const added = yield* manager.add(moduleUrl);
|
|
128
|
-
yield* manager.enable(added.meta.
|
|
129
|
-
|
|
128
|
+
yield* manager.enable(added.meta.profile.key);
|
|
129
|
+
if (catalogEntry.release?.version) {
|
|
130
|
+
UrlLoader.setInstalledVersion(added.meta.profile.key, catalogEntry.release.version);
|
|
131
|
+
}
|
|
130
132
|
}).pipe(
|
|
131
133
|
Effect.ensuring(
|
|
132
134
|
Effect.sync(() => {
|
|
@@ -139,7 +141,7 @@ export const usePluginActions = ({
|
|
|
139
141
|
}, [manager, pluginId, moduleUrl, catalogEntry, syncInstalledVersion]);
|
|
140
142
|
|
|
141
143
|
const handleInstallVersion = useCallback(() => {
|
|
142
|
-
const version = pickerVersions.find((candidate) => candidate.
|
|
144
|
+
const version = pickerVersions.find((candidate) => candidate.version === selectedVersionTag);
|
|
143
145
|
if (!version || !version.moduleUrl) {
|
|
144
146
|
return;
|
|
145
147
|
}
|
|
@@ -151,8 +153,8 @@ export const usePluginActions = ({
|
|
|
151
153
|
yield* manager.remove(pluginId);
|
|
152
154
|
}
|
|
153
155
|
const added = yield* manager.add(version.moduleUrl);
|
|
154
|
-
yield* manager.enable(added.meta.
|
|
155
|
-
UrlLoader.setInstalledVersion(added.meta.
|
|
156
|
+
yield* manager.enable(added.meta.profile.key);
|
|
157
|
+
UrlLoader.setInstalledVersion(added.meta.profile.key, version.version);
|
|
156
158
|
}).pipe(
|
|
157
159
|
Effect.ensuring(
|
|
158
160
|
Effect.sync(() => {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2026 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { type
|
|
5
|
+
import { type Plugin, UrlLoader } from '@dxos/app-framework';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Returns a set of plugin ids for which the registry has a newer version
|
|
@@ -19,12 +19,12 @@ import { type Registry, UrlLoader } from '@dxos/app-framework';
|
|
|
19
19
|
* reappears after Updating…"). The loop is O(catalog size) sync reads — cheap
|
|
20
20
|
* enough to do every render.
|
|
21
21
|
*/
|
|
22
|
-
export const useUpdateAvailableIds = (entries: readonly
|
|
22
|
+
export const useUpdateAvailableIds = (entries: readonly Plugin.Meta[]): ReadonlySet<string> => {
|
|
23
23
|
const ids = new Set<string>();
|
|
24
24
|
for (const entry of entries) {
|
|
25
|
-
const installedVersion = UrlLoader.getInstalledVersion(entry.
|
|
26
|
-
if (installedVersion !== undefined && installedVersion !== entry.version) {
|
|
27
|
-
ids.add(entry.
|
|
25
|
+
const installedVersion = UrlLoader.getInstalledVersion(entry.profile.key);
|
|
26
|
+
if (installedVersion !== undefined && installedVersion !== entry.release?.version) {
|
|
27
|
+
ids.add(entry.profile.key);
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
return ids;
|
|
@@ -5,53 +5,45 @@
|
|
|
5
5
|
import * as Effect from 'effect/Effect';
|
|
6
6
|
import { type Dispatch, type SetStateAction, useEffect, useMemo, useState } from 'react';
|
|
7
7
|
|
|
8
|
-
import { type Registry, UrlLoader } from '@dxos/app-framework';
|
|
8
|
+
import { type Plugin, type Registry, UrlLoader } from '@dxos/app-framework';
|
|
9
9
|
import { EffectEx } from '@dxos/effect';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Owns the version picker's state machine: fetches the available versions list
|
|
13
|
-
* from the provider
|
|
14
|
-
*
|
|
15
|
-
* an external update doesn't strand the trigger on a
|
|
16
|
-
* available
|
|
13
|
+
* from the provider (served from the inlined `releases` array on each entry —
|
|
14
|
+
* no separate endpoint needed) and keeps the selection clamped to whatever's
|
|
15
|
+
* currently in the list so an external update doesn't strand the trigger on a
|
|
16
|
+
* tag that's no longer available.
|
|
17
17
|
*/
|
|
18
18
|
export const useVersionPicker = ({
|
|
19
19
|
provider,
|
|
20
20
|
pluginId,
|
|
21
|
-
repo,
|
|
22
21
|
moduleUrl,
|
|
23
22
|
installedVersionTag,
|
|
24
23
|
}: {
|
|
25
24
|
provider: Registry.Manager;
|
|
26
25
|
pluginId: string;
|
|
27
|
-
repo: string | undefined;
|
|
28
26
|
moduleUrl: string | undefined;
|
|
29
27
|
installedVersionTag: string | undefined;
|
|
30
28
|
}): {
|
|
31
|
-
pickerVersions: readonly
|
|
29
|
+
pickerVersions: readonly Plugin.Release[];
|
|
32
30
|
selectedVersionTag: string | undefined;
|
|
33
31
|
setSelectedVersionTag: Dispatch<SetStateAction<string | undefined>>;
|
|
34
32
|
} => {
|
|
35
|
-
const [versions, setVersions] = useState<readonly
|
|
33
|
+
const [versions, setVersions] = useState<readonly Plugin.Release[]>([]);
|
|
36
34
|
const [selectedVersionTag, setSelectedVersionTag] = useState<string | undefined>();
|
|
37
35
|
|
|
38
|
-
// Load version list
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
// `cancelled` flag guards against a stale in-flight `listVersions`
|
|
42
|
-
// response overwriting the newer state after `repo`/`pluginId` changes.
|
|
36
|
+
// Load version list whenever pluginId changes. The `cancelled` flag guards
|
|
37
|
+
// against a stale in-flight response overwriting newer state when the user
|
|
38
|
+
// navigates between plugins quickly.
|
|
43
39
|
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
40
|
setVersions([]);
|
|
52
41
|
setSelectedVersionTag(undefined);
|
|
42
|
+
if (!pluginId) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
53
45
|
let cancelled = false;
|
|
54
|
-
void provider.listVersions(
|
|
46
|
+
void provider.listVersions(pluginId).pipe(
|
|
55
47
|
Effect.match({
|
|
56
48
|
onSuccess: (vs) => {
|
|
57
49
|
if (cancelled) {
|
|
@@ -60,7 +52,7 @@ export const useVersionPicker = ({
|
|
|
60
52
|
setVersions(vs);
|
|
61
53
|
// Default selection: the currently installed version, or the latest.
|
|
62
54
|
const installedVersion = UrlLoader.getInstalledVersion(pluginId);
|
|
63
|
-
setSelectedVersionTag(installedVersion ?? vs[0]?.
|
|
55
|
+
setSelectedVersionTag(installedVersion ?? vs[0]?.version);
|
|
64
56
|
},
|
|
65
57
|
onFailure: () => {
|
|
66
58
|
if (cancelled) {
|
|
@@ -75,19 +67,19 @@ export const useVersionPicker = ({
|
|
|
75
67
|
return () => {
|
|
76
68
|
cancelled = true;
|
|
77
69
|
};
|
|
78
|
-
}, [provider,
|
|
70
|
+
}, [provider, pluginId]);
|
|
79
71
|
|
|
80
72
|
// Make sure the picker always lists the installed version, even if the catalog
|
|
81
73
|
// hasn't surfaced it (the current `listVersions` stub only returns latest).
|
|
82
|
-
const pickerVersions = useMemo<readonly
|
|
74
|
+
const pickerVersions = useMemo<readonly Plugin.Release[]>(() => {
|
|
83
75
|
if (!installedVersionTag) {
|
|
84
76
|
return versions;
|
|
85
77
|
}
|
|
86
|
-
if (versions.some((entry) => entry.
|
|
78
|
+
if (versions.some((entry) => entry.version === installedVersionTag)) {
|
|
87
79
|
return versions;
|
|
88
80
|
}
|
|
89
|
-
const installedEntry:
|
|
90
|
-
|
|
81
|
+
const installedEntry: Plugin.Release = {
|
|
82
|
+
version: installedVersionTag,
|
|
91
83
|
// Picker won't be re-installing this entry unless the user selects + clicks Install,
|
|
92
84
|
// and the catalog moduleUrl is the closest stand-in we have.
|
|
93
85
|
moduleUrl: moduleUrl ?? '',
|
|
@@ -102,10 +94,10 @@ export const useVersionPicker = ({
|
|
|
102
94
|
if (pickerVersions.length === 0) {
|
|
103
95
|
return;
|
|
104
96
|
}
|
|
105
|
-
if (selectedVersionTag && pickerVersions.some((entry) => entry.
|
|
97
|
+
if (selectedVersionTag && pickerVersions.some((entry) => entry.version === selectedVersionTag)) {
|
|
106
98
|
return;
|
|
107
99
|
}
|
|
108
|
-
setSelectedVersionTag(installedVersionTag ?? pickerVersions[0]?.
|
|
100
|
+
setSelectedVersionTag(installedVersionTag ?? pickerVersions[0]?.version);
|
|
109
101
|
}, [pickerVersions, selectedVersionTag, installedVersionTag]);
|
|
110
102
|
|
|
111
103
|
return { pickerVersions, selectedVersionTag, setSelectedVersionTag };
|
package/src/meta.ts
CHANGED
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { Plugin } from '@dxos/app-framework';
|
|
6
|
-
import {
|
|
6
|
+
import { Paths } from '@dxos/app-toolkit';
|
|
7
7
|
import { DXN } from '@dxos/keys';
|
|
8
|
-
import { trim } from '@dxos/util';
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
import config from '../dx.config';
|
|
10
|
+
|
|
11
|
+
export const REGISTRY_ID = Paths.pinnedWorkspaceId('dxos:plugin-registry');
|
|
11
12
|
export const REGISTRY_KEY = 'plugin-registry';
|
|
12
13
|
|
|
13
14
|
// TODO(wittjosiah): Should this be a special separator or use the standard path separator?
|
|
@@ -31,17 +32,7 @@ export const getPluginPath = (pluginId: string): string => `root/${REGISTRY_ID}/
|
|
|
31
32
|
*/
|
|
32
33
|
export const getPluginSpecPath = (pluginId: string): string => `${getPluginPath(pluginId)}/spec`;
|
|
33
34
|
|
|
34
|
-
export const meta = Plugin.
|
|
35
|
-
key: DXN.make('org.dxos.plugin.registry'),
|
|
36
|
-
name: 'Plugins',
|
|
37
|
-
author: 'DXOS',
|
|
38
|
-
description: trim`
|
|
39
|
-
Plugin management system for discovering, installing, and configuring workspace extensions.
|
|
40
|
-
Browse available plugins and customize your workspace capabilities.
|
|
41
|
-
`,
|
|
42
|
-
icon: 'ph--squares-four--regular',
|
|
43
|
-
tags: ['system'],
|
|
44
|
-
});
|
|
35
|
+
export const meta = Plugin.getMetaFromConfig(config);
|
|
45
36
|
|
|
46
37
|
/** Cascade-disable confirmation dialog surface id. */
|
|
47
|
-
export const DISABLE_DEPENDENTS_DIALOG = DXN.make(`${meta.
|
|
38
|
+
export const DISABLE_DEPENDENTS_DIALOG = DXN.make(`${meta.profile.key}.disableDependentsDialog`);
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import * as Effect from 'effect/Effect';
|
|
6
6
|
|
|
7
|
-
import { LayoutOperation,
|
|
7
|
+
import { LayoutOperation, Paths, SettingsOperation } from '@dxos/app-toolkit';
|
|
8
8
|
import { Operation } from '@dxos/compute';
|
|
9
9
|
|
|
10
10
|
import { REGISTRY_ID } from '#meta';
|
|
@@ -13,7 +13,7 @@ const handler: Operation.WithHandler<typeof SettingsOperation.OpenPluginRegistry
|
|
|
13
13
|
SettingsOperation.OpenPluginRegistry.pipe(
|
|
14
14
|
Operation.withHandler(
|
|
15
15
|
Effect.fnUntraced(function* () {
|
|
16
|
-
yield* Operation.invoke(LayoutOperation.SwitchWorkspace, { subject: getSpacePath(REGISTRY_ID) });
|
|
16
|
+
yield* Operation.invoke(LayoutOperation.SwitchWorkspace, { subject: Paths.getSpacePath(REGISTRY_ID) });
|
|
17
17
|
}),
|
|
18
18
|
),
|
|
19
19
|
);
|
package/src/translations.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -7,6 +7,8 @@ import * as Schema from 'effect/Schema';
|
|
|
7
7
|
|
|
8
8
|
import { Capability } from '@dxos/app-framework';
|
|
9
9
|
|
|
10
|
+
import { meta } from './meta';
|
|
11
|
+
|
|
10
12
|
export const RegistrySettingsSchema = Schema.mutable(
|
|
11
13
|
Schema.Struct({
|
|
12
14
|
experimental: Schema.optional(Schema.Boolean),
|
|
@@ -32,7 +34,7 @@ export type RegistrySettings = Schema.Schema.Type<typeof RegistrySettingsSchema>
|
|
|
32
34
|
* Per-plugin capabilities exposed by `@dxos/plugin-registry`.
|
|
33
35
|
*/
|
|
34
36
|
export namespace RegistryCapabilities {
|
|
35
|
-
export const Settings = Capability.make<Atom.Writable<RegistrySettings>>(
|
|
37
|
+
export const Settings = Capability.make<Atom.Writable<RegistrySettings>>(`${meta.profile.key}.capability.settings`);
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
export const RegistryTagType = Schema.Literal(
|