@peers-app/peers-ui 0.26.4 → 0.26.6
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/components/group-switcher.d.ts +6 -0
- package/dist/components/group-switcher.js +8 -1
- package/dist/components/markdown-with-mentions.d.ts +4 -0
- package/dist/components/markdown-with-mentions.js +6 -1
- package/dist/components/save-button.d.ts +15 -0
- package/dist/components/save-button.js +14 -3
- package/dist/globals.js +34 -8
- package/dist/host-shell/host-shell-consumer.d.ts +21 -0
- package/dist/host-shell/host-shell-consumer.js +50 -0
- package/dist/host-shell/host-shell-consumer.test.d.ts +1 -0
- package/dist/host-shell/host-shell-consumer.test.js +28 -0
- package/dist/mention-configs.js +2 -1
- package/dist/screens/groups/group-list.js +4 -1
- package/dist/screens/packages/package-details.d.ts +1 -0
- package/dist/screens/packages/package-details.js +27 -8
- package/dist/screens/packages/package-helpers.d.ts +3 -23
- package/dist/screens/packages/package-helpers.js +12 -46
- package/dist/screens/packages/package-helpers.test.d.ts +1 -0
- package/dist/screens/packages/package-helpers.test.js +53 -0
- package/dist/screens/packages/package-info.js +3 -2
- package/dist/screens/packages/package-list.d.ts +4 -2
- package/dist/screens/packages/package-list.js +50 -10
- package/dist/screens/packages/package-new-local.js +7 -4
- package/dist/screens/packages/package-runtime-status.d.ts +12 -0
- package/dist/screens/packages/package-runtime-status.js +33 -0
- package/dist/screens/packages/package-runtime-status.test.d.ts +1 -0
- package/dist/screens/packages/package-runtime-status.test.js +50 -0
- package/dist/screens/packages/package-versions.js +8 -55
- package/dist/screens/packages/packages-ipc.d.ts +41 -0
- package/dist/screens/packages/packages-ipc.js +102 -0
- package/dist/screens/packages/packages-ipc.test.d.ts +1 -0
- package/dist/screens/packages/packages-ipc.test.js +43 -0
- package/dist/screens/tools/tool-catalog-ipc.test.js +6 -0
- package/dist/screens/variables/variable-details.d.ts +1 -0
- package/dist/screens/variables/variable-details.js +11 -5
- package/dist/ui-router/ui-loader.js +10 -37
- package/package.json +3 -3
- package/src/components/group-switcher.tsx +12 -4
- package/src/components/markdown-with-mentions.tsx +7 -2
- package/src/components/save-button.tsx +24 -3
- package/src/globals.tsx +32 -11
- package/src/host-shell/host-shell-consumer.test.ts +32 -0
- package/src/host-shell/host-shell-consumer.ts +53 -0
- package/src/mention-configs.ts +2 -2
- package/src/screens/groups/group-list.tsx +6 -4
- package/src/screens/packages/package-details.tsx +31 -8
- package/src/screens/packages/package-helpers.test.ts +57 -0
- package/src/screens/packages/package-helpers.ts +13 -55
- package/src/screens/packages/package-info.tsx +3 -3
- package/src/screens/packages/package-list.tsx +51 -16
- package/src/screens/packages/package-new-local.tsx +8 -13
- package/src/screens/packages/package-runtime-status.test.tsx +58 -0
- package/src/screens/packages/package-runtime-status.tsx +68 -0
- package/src/screens/packages/package-versions.tsx +9 -72
- package/src/screens/packages/packages-ipc.test.ts +47 -0
- package/src/screens/packages/packages-ipc.ts +125 -0
- package/src/screens/tools/tool-catalog-ipc.test.ts +7 -1
- package/src/screens/variables/variable-details.tsx +11 -6
- package/src/ui-router/ui-loader.tsx +12 -51
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
interface GroupSwitcherProps {
|
|
2
|
+
/** Current theme (`light` or `dark`). */
|
|
2
3
|
colorMode: string;
|
|
4
|
+
/** Use the compact mobile layout. */
|
|
3
5
|
isMobile?: boolean;
|
|
4
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* Header dropdown for switching the active group (or the personal context) and creating a
|
|
9
|
+
* new group, which also installs the default packages into it.
|
|
10
|
+
*/
|
|
5
11
|
export declare function GroupSwitcher({ colorMode, isMobile }: GroupSwitcherProps): import("react/jsx-runtime").JSX.Element;
|
|
6
12
|
export {};
|
|
@@ -38,6 +38,11 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
38
38
|
const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
39
39
|
const react_1 = __importStar(require("react"));
|
|
40
40
|
const hooks_1 = require("../hooks");
|
|
41
|
+
const packages_ipc_1 = require("../screens/packages/packages-ipc");
|
|
42
|
+
/**
|
|
43
|
+
* Header dropdown for switching the active group (or the personal context) and creating a
|
|
44
|
+
* new group, which also installs the default packages into it.
|
|
45
|
+
*/
|
|
41
46
|
function GroupSwitcher({ colorMode, isMobile = false }) {
|
|
42
47
|
const [showDropdown, setShowDropdown] = (0, react_1.useState)(false);
|
|
43
48
|
const [showCreateModal, setShowCreateModal] = (0, react_1.useState)(false);
|
|
@@ -183,7 +188,9 @@ function CreateGroupModal({ colorMode, userContext, onClose, onGroupCreated, })
|
|
|
183
188
|
await (0, peers_sdk_1.Groups)(groupDataContext).signAndSave(newGroup);
|
|
184
189
|
// Auto-install peers-core for the new group (copy from another context that
|
|
185
190
|
// already has it, else fall back to the remote updateUrl / bundle).
|
|
186
|
-
await
|
|
191
|
+
await (0, packages_ipc_1.packagesTools)(groupId)
|
|
192
|
+
.then((tools) => tools.installDefaultPackages())
|
|
193
|
+
.catch((err) => {
|
|
187
194
|
console.error("Error auto-installing peers-core for group:", err);
|
|
188
195
|
});
|
|
189
196
|
// Notify parent
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Render markdown (GitHub-flavored) with `@mentions` turned into links. Web links open in
|
|
4
|
+
* the system browser through System Host Shell instead of navigating the app window.
|
|
5
|
+
*/
|
|
2
6
|
export declare const MarkdownWithMentions: React.FC<{
|
|
3
7
|
content: string;
|
|
4
8
|
}>;
|
|
@@ -10,6 +10,7 @@ const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
|
10
10
|
const react_1 = __importDefault(require("react"));
|
|
11
11
|
const react_markdown_1 = __importDefault(require("react-markdown"));
|
|
12
12
|
const remark_gfm_1 = __importDefault(require("remark-gfm"));
|
|
13
|
+
const host_shell_consumer_1 = require("../host-shell/host-shell-consumer");
|
|
13
14
|
const mention_configs_1 = require("../mention-configs");
|
|
14
15
|
// Custom Link Renderer
|
|
15
16
|
const LinkRenderer = (props) => {
|
|
@@ -17,7 +18,7 @@ const LinkRenderer = (props) => {
|
|
|
17
18
|
return ((0, jsx_runtime_1.jsx)("a", { href: href, onClick: (e) => {
|
|
18
19
|
if (href?.startsWith("http")) {
|
|
19
20
|
e.preventDefault();
|
|
20
|
-
|
|
21
|
+
void (0, host_shell_consumer_1.openOnHost)(href);
|
|
21
22
|
}
|
|
22
23
|
}, children: props.children }));
|
|
23
24
|
};
|
|
@@ -114,6 +115,10 @@ const createElementWithMentions = (Element) => {
|
|
|
114
115
|
}) }));
|
|
115
116
|
};
|
|
116
117
|
};
|
|
118
|
+
/**
|
|
119
|
+
* Render markdown (GitHub-flavored) with `@mentions` turned into links. Web links open in
|
|
120
|
+
* the system browser through System Host Shell instead of navigating the app window.
|
|
121
|
+
*/
|
|
117
122
|
const MarkdownWithMentions = ({ content }) => {
|
|
118
123
|
const components = {
|
|
119
124
|
p: createElementWithMentions("p"),
|
|
@@ -1,14 +1,29 @@
|
|
|
1
1
|
import type { IDoc } from "@peers-app/peers-sdk";
|
|
2
2
|
import type React from "react";
|
|
3
|
+
/** Props for {@link SaveButton}. */
|
|
3
4
|
export interface ISaveButtonProps {
|
|
4
5
|
/** Used with many concrete table row shapes; IDoc stays generic at call sites. */
|
|
5
6
|
doc: IDoc<any>;
|
|
7
|
+
/** Save handler; defaults to `doc.save`. */
|
|
6
8
|
onClick?: () => void | Promise<void>;
|
|
9
|
+
/** Extra dropdown actions: buttons, or arbitrary nodes such as dividers. */
|
|
7
10
|
addActions?: ({
|
|
8
11
|
label: string;
|
|
9
12
|
onClick: () => void;
|
|
10
13
|
className?: string;
|
|
11
14
|
} | React.ReactNode)[];
|
|
15
|
+
/** Hide the built-in Discard Changes and Delete actions. */
|
|
12
16
|
noBuiltInActions?: boolean;
|
|
17
|
+
/** Label for the built-in delete action. Default: `Delete`. */
|
|
18
|
+
deleteLabel?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Replace the built-in delete (`doc.delete()`), for records whose removal must go through
|
|
21
|
+
* a Runtime contract. Runs after the user confirms; the screen then navigates up.
|
|
22
|
+
*/
|
|
23
|
+
onDelete?: () => void | Promise<void>;
|
|
13
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Save button with a dropdown of extra actions plus built-in Discard Changes and Delete.
|
|
27
|
+
* Enabled only while the document has unsaved changes.
|
|
28
|
+
*/
|
|
14
29
|
export declare const SaveButton: (props: ISaveButtonProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -4,6 +4,10 @@ exports.SaveButton = void 0;
|
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
5
|
const globals_1 = require("../globals");
|
|
6
6
|
const hooks_1 = require("../hooks");
|
|
7
|
+
/**
|
|
8
|
+
* Save button with a dropdown of extra actions plus built-in Discard Changes and Delete.
|
|
9
|
+
* Enabled only while the document has unsaved changes.
|
|
10
|
+
*/
|
|
7
11
|
const SaveButton = (props) => {
|
|
8
12
|
let { doc, addActions } = props;
|
|
9
13
|
(0, hooks_1.useObservable)(doc.q);
|
|
@@ -29,12 +33,19 @@ const SaveButton = (props) => {
|
|
|
29
33
|
if (allActions.length) {
|
|
30
34
|
allActions.push((0, jsx_runtime_1.jsx)("hr", { className: "dropdown-divider mt-3" }));
|
|
31
35
|
}
|
|
36
|
+
const deleteLabel = props.deleteLabel ?? "Delete";
|
|
32
37
|
allActions.push({
|
|
33
|
-
label:
|
|
38
|
+
label: deleteLabel,
|
|
34
39
|
className: `text-bg-danger`,
|
|
35
40
|
onClick: async () => {
|
|
36
|
-
if (confirm(
|
|
37
|
-
|
|
41
|
+
if (confirm(`Are you sure you want to ${deleteLabel.toLowerCase()} this?`)) {
|
|
42
|
+
try {
|
|
43
|
+
await (props.onDelete ? props.onDelete() : doc.delete());
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
alert(`${deleteLabel} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
38
49
|
const pathParts = (0, globals_1.mainContentPath)().split("/");
|
|
39
50
|
pathParts.pop();
|
|
40
51
|
const path = pathParts.join("/") ?? "";
|
package/dist/globals.js
CHANGED
|
@@ -8,6 +8,7 @@ exports.initializeHashNavigation = initializeHashNavigation;
|
|
|
8
8
|
exports.loadGlobals = loadGlobals;
|
|
9
9
|
const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
10
10
|
const identity_routes_1 = require("./screens/identity/identity-routes");
|
|
11
|
+
const packages_ipc_1 = require("./screens/packages/packages-ipc");
|
|
11
12
|
const tabs_state_1 = require("./tabs-layout/tabs-state");
|
|
12
13
|
const utils_1 = require("./utils");
|
|
13
14
|
/** Counter incremented when installed package UI should reload. */
|
|
@@ -90,13 +91,11 @@ async function openThreadInTab(thread) {
|
|
|
90
91
|
iconClassName: "bi-chat-dots",
|
|
91
92
|
});
|
|
92
93
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
openThread(threadId);
|
|
99
|
-
};
|
|
94
|
+
// A notification clicked in the desktop app's main process opens its thread here.
|
|
95
|
+
(0, peers_sdk_1.subscribe)("ui:openThread", (event) => {
|
|
96
|
+
if (event.data?.threadId)
|
|
97
|
+
void openThread(event.data.threadId);
|
|
98
|
+
});
|
|
100
99
|
/** Signed profile for the active user after {@link loadGlobals}. */
|
|
101
100
|
exports.me = null;
|
|
102
101
|
/** Current browser viewport width. */
|
|
@@ -153,6 +152,33 @@ function initializeHashNavigation() {
|
|
|
153
152
|
updateWindowHash();
|
|
154
153
|
}
|
|
155
154
|
}
|
|
155
|
+
/** Longest page startup waits for package folders to be re-read. */
|
|
156
|
+
const PACKAGE_FOLDER_RELOAD_WAIT_MS = 30_000;
|
|
157
|
+
/**
|
|
158
|
+
* Re-read this device's package folders so rebuilt dev bundles load before the UI does.
|
|
159
|
+
* Skipped on hosts without package folders; failures are logged, and the wait is bounded so
|
|
160
|
+
* a slow host cannot stall startup.
|
|
161
|
+
*/
|
|
162
|
+
async function reloadPackageFolders() {
|
|
163
|
+
const reload = (async () => {
|
|
164
|
+
const tools = await (0, packages_ipc_1.packagesTools)();
|
|
165
|
+
const { capabilities } = await tools.getCapabilities();
|
|
166
|
+
if (!capabilities.fileSystem)
|
|
167
|
+
return;
|
|
168
|
+
const { skipped, results } = await tools.reloadAllPackages();
|
|
169
|
+
if (skipped)
|
|
170
|
+
console.warn(skipped);
|
|
171
|
+
for (const result of results) {
|
|
172
|
+
if (!result.ok)
|
|
173
|
+
console.warn(`[packages] Reloading ${result.name} failed: ${result.error}`);
|
|
174
|
+
if (result.warning)
|
|
175
|
+
console.warn(`[packages] ${result.name}: ${result.warning}`);
|
|
176
|
+
}
|
|
177
|
+
})().catch((err) => {
|
|
178
|
+
console.warn("[packages] Reloading package folders failed:", err);
|
|
179
|
+
});
|
|
180
|
+
await Promise.race([reload, (0, peers_sdk_1.sleep)(PACKAGE_FOLDER_RELOAD_WAIT_MS)]);
|
|
181
|
+
}
|
|
156
182
|
/** Load global user state required before the root layout renders. */
|
|
157
183
|
async function loadGlobals() {
|
|
158
184
|
if (globalsLoaded)
|
|
@@ -161,7 +187,7 @@ async function loadGlobals() {
|
|
|
161
187
|
exports.me = await (0, peers_sdk_1.getMe)();
|
|
162
188
|
await peers_sdk_1.reloadPackagesOnPageRefresh.loadingPromise;
|
|
163
189
|
if ((0, peers_sdk_1.reloadPackagesOnPageRefresh)()) {
|
|
164
|
-
await
|
|
190
|
+
await reloadPackageFolders();
|
|
165
191
|
}
|
|
166
192
|
await Promise.all([
|
|
167
193
|
(0, peers_sdk_1.Groups)()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type IHostShellConsumer } from "@peers-app/peers-sdk";
|
|
2
|
+
/**
|
|
3
|
+
* Shared handle for System Host Shell on the local host. Calls name no data context, so the
|
|
4
|
+
* host runs them in the personal context, where the signed-in user has Self access.
|
|
5
|
+
*/
|
|
6
|
+
export declare const hostShellContract: import("@peers-app/peers-sdk").IHostContractConsumer<IHostShellConsumer>;
|
|
7
|
+
/** Return the System Host Shell tools of the local host. */
|
|
8
|
+
export declare function hostShellTools(): Promise<IHostShellConsumer["tools"]>;
|
|
9
|
+
/**
|
|
10
|
+
* Open a web link in the browser, or a local file or folder with the OS. Failures (an
|
|
11
|
+
* unsupported host, a refused link scheme, a missing folder) are logged and shown.
|
|
12
|
+
*
|
|
13
|
+
* @param target - Link or local path (`~` allowed).
|
|
14
|
+
*/
|
|
15
|
+
export declare function openOnHost(target: string): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Open a folder in a code editor (VS Code when installed), else with the OS.
|
|
18
|
+
*
|
|
19
|
+
* @param path - Folder or file (`~` allowed).
|
|
20
|
+
*/
|
|
21
|
+
export declare function openInEditorOnHost(path: string): Promise<void>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hostShellContract = void 0;
|
|
4
|
+
exports.hostShellTools = hostShellTools;
|
|
5
|
+
exports.openOnHost = openOnHost;
|
|
6
|
+
exports.openInEditorOnHost = openInEditorOnHost;
|
|
7
|
+
const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
8
|
+
/**
|
|
9
|
+
* Shared handle for System Host Shell on the local host. Calls name no data context, so the
|
|
10
|
+
* host runs them in the personal context, where the signed-in user has Self access.
|
|
11
|
+
*/
|
|
12
|
+
exports.hostShellContract = (0, peers_sdk_1.hostContractConsumer)((0, peers_sdk_1.getHostShellContractDefinition)());
|
|
13
|
+
/** Return the System Host Shell tools of the local host. */
|
|
14
|
+
async function hostShellTools() {
|
|
15
|
+
return (await exports.hostShellContract.get()).tools;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Open a web link in the browser, or a local file or folder with the OS. Failures (an
|
|
19
|
+
* unsupported host, a refused link scheme, a missing folder) are logged and shown.
|
|
20
|
+
*
|
|
21
|
+
* @param target - Link or local path (`~` allowed).
|
|
22
|
+
*/
|
|
23
|
+
async function openOnHost(target) {
|
|
24
|
+
try {
|
|
25
|
+
await (await hostShellTools()).open({ target });
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
reportHostShellError(`Could not open ${target}`, error);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Open a folder in a code editor (VS Code when installed), else with the OS.
|
|
33
|
+
*
|
|
34
|
+
* @param path - Folder or file (`~` allowed).
|
|
35
|
+
*/
|
|
36
|
+
async function openInEditorOnHost(path) {
|
|
37
|
+
try {
|
|
38
|
+
await (await hostShellTools()).openInEditor({ path });
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
reportHostShellError(`Could not open ${path}`, error);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function reportHostShellError(what, error) {
|
|
45
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
46
|
+
console.error(`${what}:`, error);
|
|
47
|
+
if (typeof window !== "undefined" && typeof window.alert === "function") {
|
|
48
|
+
window.alert(`${what}: ${message}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const mockTools = { open: jest.fn(), openInEditor: jest.fn() };
|
|
4
|
+
jest.mock("@peers-app/peers-sdk", () => ({
|
|
5
|
+
getHostShellContractDefinition: () => ({ contractId: "host-shell" }),
|
|
6
|
+
hostContractConsumer: () => ({ get: async () => ({ tools: mockTools }), dispose: () => { } }),
|
|
7
|
+
}));
|
|
8
|
+
const host_shell_consumer_1 = require("./host-shell-consumer");
|
|
9
|
+
const alert = jest.fn();
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
Object.assign(window, { alert });
|
|
12
|
+
jest.spyOn(console, "error").mockImplementation(() => { });
|
|
13
|
+
mockTools.open.mockReset();
|
|
14
|
+
mockTools.openInEditor.mockReset();
|
|
15
|
+
alert.mockReset();
|
|
16
|
+
});
|
|
17
|
+
it("opens links and folders on the host", async () => {
|
|
18
|
+
await (0, host_shell_consumer_1.openOnHost)("https://peers.app");
|
|
19
|
+
expect(mockTools.open).toHaveBeenCalledWith({ target: "https://peers.app" });
|
|
20
|
+
await (0, host_shell_consumer_1.openInEditorOnHost)("~/peers/packages/desk");
|
|
21
|
+
expect(mockTools.openInEditor).toHaveBeenCalledWith({ path: "~/peers/packages/desk" });
|
|
22
|
+
expect(alert).not.toHaveBeenCalled();
|
|
23
|
+
});
|
|
24
|
+
it("shows why the host could not open something", async () => {
|
|
25
|
+
mockTools.open.mockRejectedValueOnce(new Error("open refuses git: links"));
|
|
26
|
+
await (0, host_shell_consumer_1.openOnHost)("git@github.com:me/pkg.git");
|
|
27
|
+
expect(alert).toHaveBeenCalledWith("Could not open git@github.com:me/pkg.git: open refuses git: links");
|
|
28
|
+
});
|
package/dist/mention-configs.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.mentionConfigs = exports.valueTypeMentionConfig = exports.workflowMentionConfig = exports.assistantMentionConfig = exports.userMentionConfig = exports.toolMentionConfig = void 0;
|
|
4
4
|
const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
5
|
+
const globals_1 = require("./globals");
|
|
5
6
|
const tool_catalog_ipc_1 = require("./screens/tools/tool-catalog-ipc");
|
|
6
7
|
const MENTIONS_MAX_RESULTS = 10;
|
|
7
8
|
/** Discover workflow-invocable tools while keeping historical mention IDs readable. */
|
|
@@ -111,7 +112,7 @@ exports.valueTypeMentionConfig = {
|
|
|
111
112
|
name: item.name,
|
|
112
113
|
}),
|
|
113
114
|
onClick(data) {
|
|
114
|
-
|
|
115
|
+
(0, globals_1.mainContentPath)(`peer-types/${data.id}`);
|
|
115
116
|
},
|
|
116
117
|
};
|
|
117
118
|
// Tasks().list({ title: { $matchWords: mentionString }, completeDT: { $exists: searchCompletedTasks } }, { pageSize: MAX_RESULTS })
|
|
@@ -10,6 +10,7 @@ const globals_1 = require("../../globals");
|
|
|
10
10
|
const hooks_1 = require("../../hooks");
|
|
11
11
|
const identity_routes_1 = require("../identity/identity-routes");
|
|
12
12
|
const activity_view_1 = require("../invites/activity-view");
|
|
13
|
+
const packages_ipc_1 = require("../packages/packages-ipc");
|
|
13
14
|
/** Search, summarize, and create groups owned by the signed-in user. */
|
|
14
15
|
function GroupList(props = {}) {
|
|
15
16
|
const userContext = (0, hooks_1.usePromise)(peers_sdk_1.getUserContext);
|
|
@@ -122,7 +123,9 @@ function GroupList(props = {}) {
|
|
|
122
123
|
});
|
|
123
124
|
await (0, peers_sdk_1.Groups)(groupDataContext).signAndSave(group);
|
|
124
125
|
// Auto-install peers-core for the new group
|
|
125
|
-
await
|
|
126
|
+
await (0, packages_ipc_1.packagesTools)(groupId)
|
|
127
|
+
.then((tools) => tools.installDefaultPackages())
|
|
128
|
+
.catch((err) => {
|
|
126
129
|
console.error("Error auto-installing peers-core for group:", err);
|
|
127
130
|
});
|
|
128
131
|
(0, globals_1.mainContentPath)(`${identity_routes_1.IDENTITY_GROUPS_PATH}/${group.groupId}`);
|
|
@@ -11,7 +11,9 @@ const hooks_1 = require("../../hooks");
|
|
|
11
11
|
const tabs_state_1 = require("../../tabs-layout/tabs-state");
|
|
12
12
|
const local_folder_grants_panel_1 = require("./local-folder-grants-panel");
|
|
13
13
|
const package_info_1 = require("./package-info");
|
|
14
|
+
const package_runtime_status_1 = require("./package-runtime-status");
|
|
14
15
|
const package_versions_1 = require("./package-versions");
|
|
16
|
+
const packages_ipc_1 = require("./packages-ipc");
|
|
15
17
|
/** Detail screen for a single package with Info, Versions, Components, and Dependencies tabs. */
|
|
16
18
|
const PackageDetails = (props) => {
|
|
17
19
|
const refresh = (0, hooks_1.useObservableState)(Date.now());
|
|
@@ -30,7 +32,17 @@ const PackageDetails = (props) => {
|
|
|
30
32
|
{
|
|
31
33
|
label: "Reload Package",
|
|
32
34
|
onClick: async () => {
|
|
33
|
-
|
|
35
|
+
try {
|
|
36
|
+
const { warning } = await (await (0, packages_ipc_1.packagesTools)()).reloadPackage({
|
|
37
|
+
packageId: pkg.packageId,
|
|
38
|
+
});
|
|
39
|
+
if (warning)
|
|
40
|
+
alert(warning);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
alert(`Reload failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
34
46
|
await pkg.load();
|
|
35
47
|
window.location.reload();
|
|
36
48
|
},
|
|
@@ -42,16 +54,21 @@ const PackageDetails = (props) => {
|
|
|
42
54
|
// className: 'text-bg-warning',
|
|
43
55
|
onClick: async () => {
|
|
44
56
|
if (confirm(`Create a remote repository from this local one: ${pkg.name}`)) {
|
|
57
|
+
const tools = await (0, packages_ipc_1.packagesTools)();
|
|
58
|
+
try {
|
|
59
|
+
await tools.linkRemoteRepo({ packageId: pkg.packageId });
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
alert(`Error linking remote repo: ${err instanceof Error ? err.message : err}`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
45
65
|
try {
|
|
46
|
-
await
|
|
47
|
-
linkRemote: true,
|
|
48
|
-
update: true,
|
|
49
|
-
});
|
|
50
|
-
refresh(Date.now());
|
|
66
|
+
await tools.reloadPackage({ packageId: pkg.packageId });
|
|
51
67
|
}
|
|
52
68
|
catch (err) {
|
|
53
|
-
alert(`
|
|
69
|
+
alert(`Linked the remote repo, but reloading failed: ${err instanceof Error ? err.message : err}`);
|
|
54
70
|
}
|
|
71
|
+
refresh(Date.now());
|
|
55
72
|
}
|
|
56
73
|
},
|
|
57
74
|
});
|
|
@@ -59,7 +76,9 @@ const PackageDetails = (props) => {
|
|
|
59
76
|
return ((0, jsx_runtime_1.jsxs)("div", { className: "container-fluid p-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "d-flex", children: [(0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)("h4", { children: (0, jsx_runtime_1.jsx)("i", { className: "bi bi-box-fill me-2" }) }) }), (0, jsx_runtime_1.jsx)("div", { className: "flex-grow-1", children: (0, jsx_runtime_1.jsx)("h4", { children: (0, jsx_runtime_1.jsx)(input_1.Input, { className: "border border-0", style: { width: "100%", outline: "none", backgroundColor: "transparent" }, value: pkg.qs.name }, pkg.packageId) }) }), (0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(save_button_1.SaveButton, { doc: pkg, onClick: async () => {
|
|
60
77
|
await (0, peers_sdk_1.Packages)().signAndSave(pkg.toJS());
|
|
61
78
|
pkg.q(0);
|
|
62
|
-
}, addActions: [...addActions]
|
|
79
|
+
}, addActions: [...addActions], deleteLabel: "Uninstall", onDelete: async () => {
|
|
80
|
+
await (await (0, packages_ipc_1.packagesTools)()).uninstallPackage({ packageId: pkg.packageId });
|
|
81
|
+
} }, pkg.packageId) })] }), (0, jsx_runtime_1.jsx)(package_runtime_status_1.PackageRuntimeStatus, { packageId: pkg.packageId }), (0, jsx_runtime_1.jsx)(tabs_1.Tabs, { tabs: [
|
|
63
82
|
{
|
|
64
83
|
name: "Info",
|
|
65
84
|
content: ((0, jsx_runtime_1.jsx)(tabs_1.ScreenTabBody, { children: (0, jsx_runtime_1.jsx)(package_info_1.PackageInfo, { pkg: pkg }) })),
|
|
@@ -22,33 +22,13 @@ export declare function isGroupAdmin(): Promise<boolean>;
|
|
|
22
22
|
/**
|
|
23
23
|
* Activate a specific package version, handling group-level vs device-local
|
|
24
24
|
* activation. When the device has no follow override and the user is a group
|
|
25
|
-
* admin, the group's `activePackageVersionId`
|
|
26
|
-
* stay in sync.
|
|
25
|
+
* admin, a release advances the group's `activePackageVersionId` so all devices
|
|
26
|
+
* stay in sync. Dev versions (including legacy missing or empty tags) and other
|
|
27
|
+
* selections only update the device-local prefs.
|
|
27
28
|
*
|
|
28
29
|
* @param packageId - The package to activate a version for.
|
|
29
30
|
* @param pv - The package version to activate.
|
|
30
31
|
*/
|
|
31
32
|
export declare function activatePackageVersion(packageId: string, pv: IPackageVersion): Promise<void>;
|
|
32
|
-
/**
|
|
33
|
-
* After promoting a version that is currently effective-active on this device,
|
|
34
|
-
* activate it when the new tag matches the group's auto-activate / follow rules.
|
|
35
|
-
*
|
|
36
|
-
* This closes the gotcha where a device is already running a `dev` build, promotes
|
|
37
|
-
* it to `beta`/`stable`, and expects the group active version to advance without a
|
|
38
|
-
* separate Activate click — but only when the group would accept that channel
|
|
39
|
-
* (e.g. default follow is `stable`; `beta` only when the group follows beta).
|
|
40
|
-
*
|
|
41
|
-
* @param packageId - Package that was promoted
|
|
42
|
-
* @param promotedPv - PV after the tag change (must include the new `versionTag`)
|
|
43
|
-
* @param opts.wasEffectiveActive - True when this PV was this device's effective active before promote
|
|
44
|
-
* @param opts.groupFollowTags - Group `IPackage.followVersionTags` (not device override)
|
|
45
|
-
* @param opts.groupPinned - True when group `versionFollowRange` is `pinned`
|
|
46
|
-
* @returns Whether activation was attempted
|
|
47
|
-
*/
|
|
48
|
-
export declare function maybeActivateAfterPromote(packageId: string, promotedPv: IPackageVersion, opts: {
|
|
49
|
-
wasEffectiveActive: boolean;
|
|
50
|
-
groupFollowTags: string | undefined;
|
|
51
|
-
groupPinned: boolean;
|
|
52
|
-
}): Promise<boolean>;
|
|
53
33
|
/** Numeric rank for sorting packages by update urgency. */
|
|
54
34
|
export declare const updateLevelRank: Record<string, number>;
|
|
@@ -4,8 +4,8 @@ exports.updateLevelRank = void 0;
|
|
|
4
4
|
exports.checkVersionStatus = checkVersionStatus;
|
|
5
5
|
exports.isGroupAdmin = isGroupAdmin;
|
|
6
6
|
exports.activatePackageVersion = activatePackageVersion;
|
|
7
|
-
exports.maybeActivateAfterPromote = maybeActivateAfterPromote;
|
|
8
7
|
const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
8
|
+
const packages_ipc_1 = require("./packages-ipc");
|
|
9
9
|
/**
|
|
10
10
|
* Check a package's version status: what's currently active, whether a newer
|
|
11
11
|
* version exists within the follow policy, and which version is the best
|
|
@@ -84,8 +84,9 @@ async function isGroupAdmin() {
|
|
|
84
84
|
/**
|
|
85
85
|
* Activate a specific package version, handling group-level vs device-local
|
|
86
86
|
* activation. When the device has no follow override and the user is a group
|
|
87
|
-
* admin, the group's `activePackageVersionId`
|
|
88
|
-
* stay in sync.
|
|
87
|
+
* admin, a release advances the group's `activePackageVersionId` so all devices
|
|
88
|
+
* stay in sync. Dev versions (including legacy missing or empty tags) and other
|
|
89
|
+
* selections only update the device-local prefs.
|
|
89
90
|
*
|
|
90
91
|
* @param packageId - The package to activate a version for.
|
|
91
92
|
* @param pv - The package version to activate.
|
|
@@ -94,49 +95,14 @@ async function activatePackageVersion(packageId, pv) {
|
|
|
94
95
|
const prefsVar = (0, peers_sdk_1.packagePrefsVar)(packageId);
|
|
95
96
|
await prefsVar.loadingPromise;
|
|
96
97
|
const devicePrefs = prefsVar();
|
|
97
|
-
const activatesGroup = pv.versionTag
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
await (0, peers_sdk_1.updatePackagePrefs)(packageId, { activePackageVersionId: pv.packageVersionId });
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* After promoting a version that is currently effective-active on this device,
|
|
112
|
-
* activate it when the new tag matches the group's auto-activate / follow rules.
|
|
113
|
-
*
|
|
114
|
-
* This closes the gotcha where a device is already running a `dev` build, promotes
|
|
115
|
-
* it to `beta`/`stable`, and expects the group active version to advance without a
|
|
116
|
-
* separate Activate click — but only when the group would accept that channel
|
|
117
|
-
* (e.g. default follow is `stable`; `beta` only when the group follows beta).
|
|
118
|
-
*
|
|
119
|
-
* @param packageId - Package that was promoted
|
|
120
|
-
* @param promotedPv - PV after the tag change (must include the new `versionTag`)
|
|
121
|
-
* @param opts.wasEffectiveActive - True when this PV was this device's effective active before promote
|
|
122
|
-
* @param opts.groupFollowTags - Group `IPackage.followVersionTags` (not device override)
|
|
123
|
-
* @param opts.groupPinned - True when group `versionFollowRange` is `pinned`
|
|
124
|
-
* @returns Whether activation was attempted
|
|
125
|
-
*/
|
|
126
|
-
async function maybeActivateAfterPromote(packageId, promotedPv, opts) {
|
|
127
|
-
if (!opts.wasEffectiveActive)
|
|
128
|
-
return false;
|
|
129
|
-
if (opts.groupPinned)
|
|
130
|
-
return false;
|
|
131
|
-
if (!promotedPv.versionTag || promotedPv.versionTag === "dev")
|
|
132
|
-
return false;
|
|
133
|
-
// Docs default: groups follow `stable` when followVersionTags is unset.
|
|
134
|
-
const followTags = opts.groupFollowTags ?? "stable";
|
|
135
|
-
if (!(0, peers_sdk_1.doesTagMatch)(undefined, promotedPv.versionTag, followTags, undefined)) {
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
await activatePackageVersion(packageId, promotedPv);
|
|
139
|
-
return true;
|
|
98
|
+
const activatesGroup = (pv.versionTag || "dev") !== "dev" &&
|
|
99
|
+
!(0, peers_sdk_1.hasDeviceFollowOverride)(devicePrefs) &&
|
|
100
|
+
(await isGroupAdmin());
|
|
101
|
+
await (await (0, packages_ipc_1.packagesTools)()).activatePackageVersion({
|
|
102
|
+
packageId,
|
|
103
|
+
packageVersionId: pv.packageVersionId,
|
|
104
|
+
scope: activatesGroup ? "group" : "device",
|
|
105
|
+
});
|
|
140
106
|
}
|
|
141
107
|
/** Numeric rank for sorting packages by update urgency. */
|
|
142
108
|
exports.updateLevelRank = { major: 3, minor: 2, patch: 1 };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
4
|
+
const package_helpers_1 = require("./package-helpers");
|
|
5
|
+
const mockActivate = jest.fn(async () => ({}));
|
|
6
|
+
const mockRole = jest.fn();
|
|
7
|
+
const mockPrefs = jest.fn();
|
|
8
|
+
jest.mock("./packages-ipc", () => ({
|
|
9
|
+
packagesTools: async () => ({ activatePackageVersion: mockActivate }),
|
|
10
|
+
}));
|
|
11
|
+
jest.mock("@peers-app/peers-sdk", () => {
|
|
12
|
+
const actual = jest.requireActual("@peers-app/peers-sdk");
|
|
13
|
+
return {
|
|
14
|
+
...actual,
|
|
15
|
+
getUserContext: async () => ({
|
|
16
|
+
userId: "user",
|
|
17
|
+
defaultDataContext: () => ({ dataContextId: "group" }),
|
|
18
|
+
}),
|
|
19
|
+
getUserRole: () => mockRole(),
|
|
20
|
+
packagePrefsVar: () => Object.assign(mockPrefs, { loadingPromise: Promise.resolve() }),
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
jest.clearAllMocks();
|
|
25
|
+
mockRole.mockResolvedValue(peers_sdk_1.GroupMemberRole.Admin);
|
|
26
|
+
mockPrefs.mockReturnValue({});
|
|
27
|
+
});
|
|
28
|
+
test.each([
|
|
29
|
+
"dev",
|
|
30
|
+
"",
|
|
31
|
+
undefined,
|
|
32
|
+
])("activates dev tag %p only on this device", async (versionTag) => {
|
|
33
|
+
const packageId = (0, peers_sdk_1.newid)();
|
|
34
|
+
const packageVersionId = (0, peers_sdk_1.newid)();
|
|
35
|
+
await (0, package_helpers_1.activatePackageVersion)(packageId, { packageVersionId, versionTag });
|
|
36
|
+
expect(mockActivate).toHaveBeenCalledWith({ packageId, packageVersionId, scope: "device" });
|
|
37
|
+
});
|
|
38
|
+
test.each(["beta", "stable"])("an admin activates %s for the group", async (versionTag) => {
|
|
39
|
+
const packageId = (0, peers_sdk_1.newid)();
|
|
40
|
+
const packageVersionId = (0, peers_sdk_1.newid)();
|
|
41
|
+
await (0, package_helpers_1.activatePackageVersion)(packageId, { packageVersionId, versionTag });
|
|
42
|
+
expect(mockActivate).toHaveBeenCalledWith({ packageId, packageVersionId, scope: "group" });
|
|
43
|
+
});
|
|
44
|
+
test("a device follow override keeps release activation local", async () => {
|
|
45
|
+
mockPrefs.mockReturnValue({ followTags: "stable,beta" });
|
|
46
|
+
const packageId = (0, peers_sdk_1.newid)();
|
|
47
|
+
const packageVersionId = (0, peers_sdk_1.newid)();
|
|
48
|
+
await (0, package_helpers_1.activatePackageVersion)(packageId, {
|
|
49
|
+
packageVersionId,
|
|
50
|
+
versionTag: "stable",
|
|
51
|
+
});
|
|
52
|
+
expect(mockActivate).toHaveBeenCalledWith({ packageId, packageVersionId, scope: "device" });
|
|
53
|
+
});
|
|
@@ -7,6 +7,7 @@ const input_1 = require("../../components/input");
|
|
|
7
7
|
const markdown_with_mentions_1 = require("../../components/markdown-with-mentions");
|
|
8
8
|
const tooltip_1 = require("../../components/tooltip");
|
|
9
9
|
const hooks_1 = require("../../hooks");
|
|
10
|
+
const host_shell_consumer_1 = require("../../host-shell/host-shell-consumer");
|
|
10
11
|
const package_network_panel_1 = require("./package-network-panel");
|
|
11
12
|
/** Info tab for the package details screen. */
|
|
12
13
|
const PackageInfo = (props) => {
|
|
@@ -70,9 +71,9 @@ const PackageInfo = (props) => {
|
|
|
70
71
|
}, undefined, [activeVersionId, pkg.packageId, effectiveFollowTags]);
|
|
71
72
|
const remoteRepoUrl = pkg.remoteRepo;
|
|
72
73
|
return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("small", { children: "Name:" }), (0, jsx_runtime_1.jsx)(input_1.Input, { value: pkg.qs.name, className: "form-control mb-3 p-0 ps-2", placeholder: "Package name", title: "Package name", disabled: true }), (0, jsx_runtime_1.jsxs)("div", { className: "mt-2", children: [(0, jsx_runtime_1.jsxs)("small", { children: ["Local Path:", (0, jsx_runtime_1.jsxs)("small", { children: [(0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { markdownContent: `The local path to the directory containing the package. This will open in VS Code if it's installed otherwise this will open in your native file system` }), (0, jsx_runtime_1.jsx)("button", { className: "btn btn-sm btn-link", onClick: () => {
|
|
73
|
-
|
|
74
|
+
void (0, host_shell_consumer_1.openInEditorOnHost)(localPath || peers_sdk_1.packagesRootDir);
|
|
74
75
|
}, children: "Open Local" })] })] }), (0, jsx_runtime_1.jsx)("input", { type: "text", className: "form-control mb-3 p-0 ps-2", placeholder: "~/peers/packages/my-package", value: localPath || "", onChange: (e) => setLocalPath(e.target.value) })] }), remoteRepoUrl ? ((0, jsx_runtime_1.jsxs)("div", { className: "mt-2", children: [(0, jsx_runtime_1.jsxs)("small", { children: ["Source URL:", (0, jsx_runtime_1.jsxs)("small", { children: [(0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { markdownContent: `The remote source of this package. Often a github repository URL or a link to the Peers page for the package.` }), (0, jsx_runtime_1.jsx)("button", { className: "btn btn-sm btn-link", onClick: () => {
|
|
75
|
-
|
|
76
|
+
void (0, host_shell_consumer_1.openOnHost)(remoteRepoUrl);
|
|
76
77
|
}, children: "Open Remote" })] })] }), (0, jsx_runtime_1.jsx)(input_1.Input, { value: pkg.qs.remoteRepo, className: "form-control mb-3 p-0 ps-2", disabled: true })] })) : null, (0, jsx_runtime_1.jsxs)("div", { className: "mt-2", children: [(0, jsx_runtime_1.jsxs)("small", { children: ["Update URL:", (0, jsx_runtime_1.jsx)("small", { children: (0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { markdownContent: `Base URL for remote package updates. Admin devices poll **<updateUrl>/latest-<tag>.json** on startup (for example **latest-stable.json**). Signed artifacts must be published to this host separately — for example via the publish-package tool or the peers-core full-release pipeline.` }) })] }), (0, jsx_runtime_1.jsx)(input_1.Input, { value: pkg.qs.updateUrl, className: "form-control mb-3 p-0 ps-2", placeholder: "https://example.com/my-package", title: "Update URL" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "mt-2", children: [(0, jsx_runtime_1.jsx)("hr", {}), (0, jsx_runtime_1.jsxs)("small", { children: ["Version:", (0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { markdownContent: "The currently active version of this package. Manage versions in the Versions tab." })] }), activeVersion ? ((0, jsx_runtime_1.jsxs)("div", { className: "d-flex align-items-center mt-1 mb-3", children: [(0, jsx_runtime_1.jsxs)("strong", { className: "me-2", children: ["v", activeVersion.version] }), activeVersion.versionTag && ((0, jsx_runtime_1.jsx)("span", { className: `badge ${activeVersion.versionTag === "dev" ? "text-bg-danger" : activeVersion.versionTag.startsWith("beta") ? "text-bg-warning" : "text-bg-success"} me-2`, children: activeVersion.versionTag })), (0, jsx_runtime_1.jsx)("code", { className: "text-muted small me-2", children: activeVersion.packageVersionHash?.substring(0, 8) }), newerLevel === "uptodate" ? ((0, jsx_runtime_1.jsx)("span", { className: "badge text-bg-success", children: "Up to date" })) : newerLevel ? ((0, jsx_runtime_1.jsxs)("span", { className: `badge text-bg-${newerLevel === "major" ? "danger" : newerLevel === "minor" ? "warning" : "info"}`, children: ["Newer ", newerLevel, " version available"] })) : null] })) : ((0, jsx_runtime_1.jsx)("div", { className: "text-muted small mt-1 mb-3", children: "No active version" })), (0, jsx_runtime_1.jsxs)("div", { className: "mb-3", children: [(0, jsx_runtime_1.jsxs)("small", { children: ["Auto-Update Range:", (0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { markdownContent: "Controls which new versions are auto-activated for the group. When an admin device auto-upgrades, the group's active version advances for everyone. **Pinned** = never auto-update. **Patch** = same major.minor (e.g. 1.2.x). **Minor** = same major (e.g. 1.x.x). **Latest** = always auto-update to newest." })] }), (0, jsx_runtime_1.jsxs)("select", { className: "form-select form-select-sm", value: groupRange, onChange: async (e) => {
|
|
77
78
|
const val = e.target.value;
|
|
78
79
|
const current = await (0, peers_sdk_1.Packages)().get(pkg.packageId);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Packages screen: search, create/import/install from the search box, local folder grants,
|
|
3
|
+
* and the installed package list.
|
|
4
|
+
*/
|
|
3
5
|
export declare const PackageList: () => import("react/jsx-runtime").JSX.Element;
|