@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.
Files changed (124) hide show
  1. package/dist/lib/neutral/RegistryPlugin.node.mjs +709 -18
  2. package/dist/lib/neutral/RegistryPlugin.node.mjs.map +4 -4
  3. package/dist/lib/neutral/chunk-ZY37XLVF.mjs +46 -0
  4. package/dist/lib/neutral/chunk-ZY37XLVF.mjs.map +7 -0
  5. package/dist/lib/neutral/index.mjs +1 -1
  6. package/dist/lib/neutral/meta.json +1 -1
  7. package/dist/lib/neutral/{open-plugin-registry-II735N76.mjs → open-plugin-registry-ID7LV3SF.mjs} +3 -3
  8. package/dist/lib/neutral/open-plugin-registry-ID7LV3SF.mjs.map +7 -0
  9. package/dist/lib/neutral/plugin.mjs +2 -2
  10. package/dist/lib/neutral/translations.mjs +1 -1
  11. package/dist/lib/neutral/translations.mjs.map +3 -3
  12. package/dist/types/dx.config.d.ts +28 -0
  13. package/dist/types/dx.config.d.ts.map +1 -0
  14. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  15. package/dist/types/src/capabilities/index.d.ts +4 -12
  16. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  17. package/dist/types/src/capabilities/react-surface.d.ts +2 -2
  18. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  19. package/dist/types/src/categories.d.ts.map +1 -1
  20. package/dist/types/src/commands/index.d.ts +2 -2
  21. package/dist/types/src/commands/index.d.ts.map +1 -1
  22. package/dist/types/src/commands/plugin/disable.d.ts.map +1 -0
  23. package/dist/types/src/commands/plugin/enable.d.ts.map +1 -0
  24. package/dist/types/src/commands/plugin/index.d.ts +3 -0
  25. package/dist/types/src/commands/plugin/index.d.ts.map +1 -0
  26. package/dist/types/src/commands/plugin/list.d.ts.map +1 -0
  27. package/dist/types/src/commands/plugin/util.d.ts.map +1 -0
  28. package/dist/types/src/commands/registry/index.d.ts +16 -0
  29. package/dist/types/src/commands/registry/index.d.ts.map +1 -0
  30. package/dist/types/src/commands/registry/publish-package.d.ts +29 -0
  31. package/dist/types/src/commands/registry/publish-package.d.ts.map +1 -0
  32. package/dist/types/src/commands/registry/publish-publisher.d.ts +19 -0
  33. package/dist/types/src/commands/registry/publish-publisher.d.ts.map +1 -0
  34. package/dist/types/src/commands/registry/publish.d.ts +25 -0
  35. package/dist/types/src/commands/registry/publish.d.ts.map +1 -0
  36. package/dist/types/src/commands/registry/records.d.ts +14 -0
  37. package/dist/types/src/commands/registry/records.d.ts.map +1 -0
  38. package/dist/types/src/commands/registry/unpublish.d.ts +15 -0
  39. package/dist/types/src/commands/registry/unpublish.d.ts.map +1 -0
  40. package/dist/types/src/commands/registry/util.d.ts +113 -0
  41. package/dist/types/src/commands/registry/util.d.ts.map +1 -0
  42. package/dist/types/src/commands/registry/verify.d.ts +20 -0
  43. package/dist/types/src/commands/registry/verify.d.ts.map +1 -0
  44. package/dist/types/src/components/PluginDetail/PluginDetail.d.ts +2 -2
  45. package/dist/types/src/components/PluginDetail/PluginDetail.d.ts.map +1 -1
  46. package/dist/types/src/components/PluginDetail/PluginDetail.stories.d.ts.map +1 -1
  47. package/dist/types/src/components/PluginList/PluginItem.d.ts.map +1 -1
  48. package/dist/types/src/components/PluginList/PluginList.stories.d.ts.map +1 -1
  49. package/dist/types/src/containers/LoadPluginDialog/LoadPluginDialog.d.ts.map +1 -1
  50. package/dist/types/src/containers/PluginArticle/PluginArticle.d.ts.map +1 -1
  51. package/dist/types/src/containers/PublicRegistryArticle/PublicRegistryArticle.d.ts.map +1 -1
  52. package/dist/types/src/hooks/useAutoTags.d.ts +2 -2
  53. package/dist/types/src/hooks/useAutoTags.d.ts.map +1 -1
  54. package/dist/types/src/hooks/useCatalogEntry.d.ts +4 -5
  55. package/dist/types/src/hooks/useCatalogEntry.d.ts.map +1 -1
  56. package/dist/types/src/hooks/usePluginActions.d.ts +3 -3
  57. package/dist/types/src/hooks/usePluginActions.d.ts.map +1 -1
  58. package/dist/types/src/hooks/useUpdateAvailable.d.ts +2 -2
  59. package/dist/types/src/hooks/useUpdateAvailable.d.ts.map +1 -1
  60. package/dist/types/src/hooks/useVersionPicker.d.ts +7 -8
  61. package/dist/types/src/hooks/useVersionPicker.d.ts.map +1 -1
  62. package/dist/types/src/meta.d.ts +28 -2
  63. package/dist/types/src/meta.d.ts.map +1 -1
  64. package/dist/types/src/operations/open-plugin-registry.d.ts.map +1 -1
  65. package/dist/types/src/types.d.ts.map +1 -1
  66. package/dist/types/tsconfig.tsbuildinfo +1 -1
  67. package/dx.config.ts +20 -0
  68. package/package.json +31 -24
  69. package/src/RegistryPlugin.node.ts +2 -2
  70. package/src/RegistryPlugin.test.ts +1 -1
  71. package/src/capabilities/app-graph-builder.ts +43 -39
  72. package/src/capabilities/dev-plugin-loader.ts +1 -1
  73. package/src/capabilities/react-surface.tsx +1 -4
  74. package/src/capabilities/settings.ts +2 -2
  75. package/src/categories.ts +5 -4
  76. package/src/commands/index.ts +2 -10
  77. package/src/commands/{disable.ts → plugin/disable.ts} +3 -3
  78. package/src/commands/{enable.ts → plugin/enable.ts} +3 -3
  79. package/src/commands/plugin/index.ts +14 -0
  80. package/src/commands/{list.ts → plugin/list.ts} +4 -4
  81. package/src/commands/registry/index.ts +30 -0
  82. package/src/commands/registry/publish-package.ts +119 -0
  83. package/src/commands/registry/publish-publisher.ts +73 -0
  84. package/src/commands/registry/publish.ts +278 -0
  85. package/src/commands/registry/records.ts +73 -0
  86. package/src/commands/registry/unpublish.ts +70 -0
  87. package/src/commands/registry/util.ts +395 -0
  88. package/src/commands/registry/verify.ts +76 -0
  89. package/src/components/DisableDependentsAlert/DisableDependentsAlert.tsx +1 -1
  90. package/src/components/PluginDetail/PluginDetail.stories.tsx +5 -3
  91. package/src/components/PluginDetail/PluginDetail.tsx +21 -23
  92. package/src/components/PluginFailureBadge/PluginFailureBadge.tsx +1 -1
  93. package/src/components/PluginList/PluginItem.tsx +5 -3
  94. package/src/components/PluginList/PluginList.stories.tsx +1 -2
  95. package/src/components/PluginList/PluginList.tsx +4 -4
  96. package/src/components/RegistrySettings/RegistrySettings.tsx +1 -1
  97. package/src/containers/BaseRegistryArticle/BaseRegistryArticle.tsx +2 -2
  98. package/src/containers/LoadPluginDialog/LoadPluginDialog.tsx +3 -3
  99. package/src/containers/PluginArticle/PluginArticle.tsx +8 -6
  100. package/src/containers/PublicRegistryArticle/PublicRegistryArticle.tsx +35 -28
  101. package/src/containers/RegistryArticle/RegistryArticle.tsx +1 -1
  102. package/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.tsx +1 -1
  103. package/src/hooks/useAutoTags.ts +3 -3
  104. package/src/hooks/useCatalogEntry.ts +6 -7
  105. package/src/hooks/useDisableConfirmation.ts +1 -1
  106. package/src/hooks/usePluginActions.ts +13 -11
  107. package/src/hooks/useUpdateAvailable.ts +5 -5
  108. package/src/hooks/useVersionPicker.ts +22 -30
  109. package/src/meta.ts +6 -15
  110. package/src/operations/open-plugin-registry.ts +2 -2
  111. package/src/translations.ts +1 -1
  112. package/src/types.ts +3 -1
  113. package/dist/lib/neutral/chunk-GRQI6MLI.mjs +0 -36
  114. package/dist/lib/neutral/chunk-GRQI6MLI.mjs.map +0 -7
  115. package/dist/lib/neutral/open-plugin-registry-II735N76.mjs.map +0 -7
  116. package/dist/types/src/commands/disable.d.ts.map +0 -1
  117. package/dist/types/src/commands/enable.d.ts.map +0 -1
  118. package/dist/types/src/commands/list.d.ts.map +0 -1
  119. package/dist/types/src/commands/util.d.ts.map +0 -1
  120. /package/dist/types/src/commands/{disable.d.ts → plugin/disable.d.ts} +0 -0
  121. /package/dist/types/src/commands/{enable.d.ts → plugin/enable.d.ts} +0 -0
  122. /package/dist/types/src/commands/{list.d.ts → plugin/list.d.ts} +0 -0
  123. /package/dist/types/src/commands/{util.d.ts → plugin/util.d.ts} +0 -0
  124. /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.id);
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.id}`.toLowerCase();
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.id);
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.id}.loadPluginDialog`);
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.id);
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.id);
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.id;
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, repo } = useCatalogEntry(pluginId);
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(() => plugins.some((candidate) => candidate.meta.id === pluginId), [plugins, pluginId]);
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.id === id)?.meta.name ?? id,
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 { type Registry, Plugin, UrlLoader } from '@dxos/app-framework';
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: Registry.Plugin, b: Registry.Plugin) => (a.name ?? a.id).localeCompare(b.name ?? b.id);
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.id).localeCompare(b.meta.name ?? b.meta.id);
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: Registry.Plugin): Plugin.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.id),
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
- iconHue: plugin.iconHue,
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.id);
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.id), [plugins]);
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.id)),
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
- map[entry.id] = entry.moduleUrl;
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
- map[entry.id] = entry.version;
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.id));
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((plugin) => remoteIds.has(plugin.meta.id) && !catalogIds.has(plugin.meta.id));
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.id);
101
- const bInstalled = installedSnapshot.has(b.meta.id);
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.id);
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.id, version);
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.id, source: 'registry' },
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.id);
156
+ yield* manager.enable(plugin.meta.profile.key);
150
157
  if (version) {
151
- UrlLoader.setInstalledVersion(plugin.meta.id, version);
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.id, source: 'registry' },
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.id).localeCompare(b.meta.name ?? b.meta.id);
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.id);
36
+ yield* manager.enable(plugin.meta.profile.key);
37
37
  }),
38
38
  );
39
39
  },
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { useMemo } from 'react';
6
6
 
7
- import { type Registry, UrlLoader } from '@dxos/app-framework';
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 Registry.Plugin[]): AutoTagsMap =>
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.id, 'registry');
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 Registry } from '@dxos/app-framework';
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 two fields
13
- * (`moduleUrl`, `repo`) the rest of the article cares about.
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: Registry.Plugin | undefined;
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.id === pluginId), [entries, pluginId]);
24
- return { catalogEntry, moduleUrl: catalogEntry?.moduleUrl, repo: catalogEntry?.repo };
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.id === id)?.meta.name ?? id;
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 PluginManager, type Registry, UrlLoader } from '@dxos/app-framework';
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: Registry.Plugin | undefined;
25
- pickerVersions: readonly Registry.PluginVersion[];
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.id);
105
- if (catalogEntry) {
106
- UrlLoader.setInstalledVersion(added.meta.id, catalogEntry.version);
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.id);
129
- UrlLoader.setInstalledVersion(added.meta.id, catalogEntry.version);
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.tag === selectedVersionTag);
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.id);
155
- UrlLoader.setInstalledVersion(added.meta.id, version.tag);
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 Registry, UrlLoader } from '@dxos/app-framework';
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 Registry.Plugin[]): ReadonlySet<string> => {
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.id);
26
- if (installedVersion !== undefined && installedVersion !== entry.version) {
27
- ids.add(entry.id);
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, 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).
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 Registry.PluginVersion[];
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 Registry.PluginVersion[]>([]);
33
+ const [versions, setVersions] = useState<readonly Plugin.Release[]>([]);
36
34
  const [selectedVersionTag, setSelectedVersionTag] = useState<string | undefined>();
37
35
 
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.
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(repo).pipe(
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]?.tag);
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, repo, pluginId]);
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 Registry.PluginVersion[]>(() => {
74
+ const pickerVersions = useMemo<readonly Plugin.Release[]>(() => {
83
75
  if (!installedVersionTag) {
84
76
  return versions;
85
77
  }
86
- if (versions.some((entry) => entry.tag === installedVersionTag)) {
78
+ if (versions.some((entry) => entry.version === installedVersionTag)) {
87
79
  return versions;
88
80
  }
89
- const installedEntry: Registry.PluginVersion = {
90
- tag: installedVersionTag,
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.tag === selectedVersionTag)) {
97
+ if (selectedVersionTag && pickerVersions.some((entry) => entry.version === selectedVersionTag)) {
106
98
  return;
107
99
  }
108
- setSelectedVersionTag(installedVersionTag ?? pickerVersions[0]?.tag);
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 { pinnedWorkspaceId } from '@dxos/app-toolkit';
6
+ import { Paths } from '@dxos/app-toolkit';
7
7
  import { DXN } from '@dxos/keys';
8
- import { trim } from '@dxos/util';
9
8
 
10
- export const REGISTRY_ID = pinnedWorkspaceId('dxos:plugin-registry');
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.makeMeta({
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.id}.disableDependentsDialog`);
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, SettingsOperation, getSpacePath } from '@dxos/app-toolkit';
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
  );
@@ -9,7 +9,7 @@ import { meta } from '#meta';
9
9
  export const translations = [
10
10
  {
11
11
  'en-US': {
12
- [meta.id]: {
12
+ [meta.profile.key]: {
13
13
  'plugin.name': 'Registry',
14
14
  'details.label': 'Details',
15
15
  'description.label': 'Description',
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>>('@dxos/plugin-registry/settings');
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(