@termix-ssh/plugin-sdk 1.0.0
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/README.md +52 -0
- package/cli/commands/build.mjs +246 -0
- package/cli/commands/migrations.mjs +248 -0
- package/cli/commands/pack.mjs +42 -0
- package/cli/commands/sign.mjs +83 -0
- package/cli/commands/test.mjs +33 -0
- package/cli/commands/validate.mjs +215 -0
- package/cli/index.mjs +65 -0
- package/cli/lib/changelog.mjs +126 -0
- package/cli/lib/externals.mjs +46 -0
- package/cli/lib/plugin-dir.mjs +29 -0
- package/cli/lib/signing.mjs +86 -0
- package/cli/lib/static-url-imports.mjs +54 -0
- package/cli/lib/tmxplug.mjs +106 -0
- package/cli/patches/guacamole-common-js-flush.cjs +76 -0
- package/cli/patches/xterm-android-ime.cjs +130 -0
- package/client.d.ts +13 -0
- package/dist/backend.d.ts +1777 -0
- package/dist/backend.js +84 -0
- package/dist/capabilities.d.ts +38 -0
- package/dist/capabilities.js +71 -0
- package/dist/db.d.ts +112 -0
- package/dist/db.js +189 -0
- package/dist/ddl.d.ts +76 -0
- package/dist/ddl.js +678 -0
- package/dist/desktop.d.ts +346 -0
- package/dist/desktop.js +5 -0
- package/dist/frontend.d.ts +1206 -0
- package/dist/frontend.js +388 -0
- package/dist/host/chunk-F3SDZDDG.js +6017 -0
- package/dist/host/chunk-XGWE7BTJ.js +90 -0
- package/dist/host/chunk-XH5NXL6Y.js +56314 -0
- package/dist/host/chunk-YC2MAKSP.js +14 -0
- package/dist/host/linked-server-FZGWQW32.js +12 -0
- package/dist/host/rbac-api-6KDMBVDZ.js +52 -0
- package/dist/host/testing-host.js +414 -0
- package/dist/host/theme.css +91 -0
- package/dist/host/types/src/types/auth-protocols.d.ts +10 -0
- package/dist/host/types/src/types/connection-log.d.ts +13 -0
- package/dist/host/types/src/types/credential-sidebar-preferences.d.ts +38 -0
- package/dist/host/types/src/types/host-defaults.d.ts +60 -0
- package/dist/host/types/src/types/host-sidebar-preferences.d.ts +55 -0
- package/dist/host/types/src/types/index.d.ts +298 -0
- package/dist/host/types/src/types/keybindings.d.ts +27 -0
- package/dist/host/types/src/types/ui-preferences.d.ts +102 -0
- package/dist/host/types/src/types/ui-types.d.ts +260 -0
- package/dist/host/types/src/ui/api/admin-user-data-api.d.ts +11 -0
- package/dist/host/types/src/ui/api/auth-methods-api.d.ts +63 -0
- package/dist/host/types/src/ui/api/credential-sidebar-preferences-api.d.ts +3 -0
- package/dist/host/types/src/ui/api/credentials-api.d.ts +56 -0
- package/dist/host/types/src/ui/api/dashboard-api.d.ts +23 -0
- package/dist/host/types/src/ui/api/external-account-api.d.ts +8 -0
- package/dist/host/types/src/ui/api/host-sidebar-preferences-api.d.ts +3 -0
- package/dist/host/types/src/ui/api/host-status-api.d.ts +11 -0
- package/dist/host/types/src/ui/api/open-tabs-api.d.ts +74 -0
- package/dist/host/types/src/ui/api/plugins-api.d.ts +128 -0
- package/dist/host/types/src/ui/api/rbac-api.d.ts +167 -0
- package/dist/host/types/src/ui/api/settings-api.d.ts +22 -0
- package/dist/host/types/src/ui/api/ssh-host-management-api.d.ts +65 -0
- package/dist/host/types/src/ui/api/sync-api.d.ts +93 -0
- package/dist/host/types/src/ui/api/system-status-api.d.ts +49 -0
- package/dist/host/types/src/ui/api/ui-preferences-api.d.ts +8 -0
- package/dist/host/types/src/ui/api/user-management-api.d.ts +80 -0
- package/dist/host/types/src/ui/auth/Auth.d.ts +12 -0
- package/dist/host/types/src/ui/auth/auth-bits.d.ts +11 -0
- package/dist/host/types/src/ui/auth/silent-signin.d.ts +2 -0
- package/dist/host/types/src/ui/components/FullScreenAppWrapper.d.ts +9 -0
- package/dist/host/types/src/ui/components/alert-dialog.d.ts +14 -0
- package/dist/host/types/src/ui/components/alert.d.ts +9 -0
- package/dist/host/types/src/ui/components/badge.d.ts +9 -0
- package/dist/host/types/src/ui/components/button.d.ts +13 -0
- package/dist/host/types/src/ui/components/card-grid/CardGridCanvas.d.ts +22 -0
- package/dist/host/types/src/ui/components/card-grid/layout-utils.d.ts +26 -0
- package/dist/host/types/src/ui/components/card-grid/types.d.ts +29 -0
- package/dist/host/types/src/ui/components/card-masonry.d.ts +16 -0
- package/dist/host/types/src/ui/components/card.d.ts +11 -0
- package/dist/host/types/src/ui/components/charts/BarSeries.d.ts +16 -0
- package/dist/host/types/src/ui/components/charts/LineChart.d.ts +17 -0
- package/dist/host/types/src/ui/components/charts/RadialGauge.d.ts +18 -0
- package/dist/host/types/src/ui/components/charts/Sparkline.d.ts +22 -0
- package/dist/host/types/src/ui/components/charts/StatRow.d.ts +15 -0
- package/dist/host/types/src/ui/components/charts/geometry.d.ts +20 -0
- package/dist/host/types/src/ui/components/charts/index.d.ts +4 -0
- package/dist/host/types/src/ui/components/checkbox.d.ts +4 -0
- package/dist/host/types/src/ui/components/connection/ConnectionGate.d.ts +56 -0
- package/dist/host/types/src/ui/components/connection/ConnectionLogPanel.d.ts +9 -0
- package/dist/host/types/src/ui/components/connection/ConnectionScreen.d.ts +34 -0
- package/dist/host/types/src/ui/components/connection/connection-status.d.ts +1 -0
- package/dist/host/types/src/ui/components/data-view.d.ts +32 -0
- package/dist/host/types/src/ui/components/dialog.d.ts +17 -0
- package/dist/host/types/src/ui/components/dropdown-menu.d.ts +29 -0
- package/dist/host/types/src/ui/components/empty-state.d.ts +19 -0
- package/dist/host/types/src/ui/components/form-fields.d.ts +115 -0
- package/dist/host/types/src/ui/components/input.d.ts +3 -0
- package/dist/host/types/src/ui/components/kbd.d.ts +12 -0
- package/dist/host/types/src/ui/components/label.d.ts +4 -0
- package/dist/host/types/src/ui/components/manager-card.d.ts +25 -0
- package/dist/host/types/src/ui/components/meter.d.ts +14 -0
- package/dist/host/types/src/ui/components/metric-card.d.ts +18 -0
- package/dist/host/types/src/ui/components/panel-layout.d.ts +83 -0
- package/dist/host/types/src/ui/components/password-input.d.ts +4 -0
- package/dist/host/types/src/ui/components/popover.d.ts +7 -0
- package/dist/host/types/src/ui/components/scroll-area.d.ts +5 -0
- package/dist/host/types/src/ui/components/section-card.d.ts +29 -0
- package/dist/host/types/src/ui/components/select.d.ts +15 -0
- package/dist/host/types/src/ui/components/select2.d.ts +8 -0
- package/dist/host/types/src/ui/components/separator.d.ts +4 -0
- package/dist/host/types/src/ui/components/sheet.d.ts +14 -0
- package/dist/host/types/src/ui/components/sidebar.d.ts +69 -0
- package/dist/host/types/src/ui/components/skeleton.d.ts +2 -0
- package/dist/host/types/src/ui/components/slider.d.ts +4 -0
- package/dist/host/types/src/ui/components/sonner.d.ts +4 -0
- package/dist/host/types/src/ui/components/surface/prompt-styles.d.ts +4 -0
- package/dist/host/types/src/ui/components/surface/surface-scope.d.ts +79 -0
- package/dist/host/types/src/ui/components/switch.d.ts +4 -0
- package/dist/host/types/src/ui/components/tab-strip.d.ts +19 -0
- package/dist/host/types/src/ui/components/tabs.d.ts +7 -0
- package/dist/host/types/src/ui/components/textarea.d.ts +4 -0
- package/dist/host/types/src/ui/components/theme-provider.d.ts +14 -0
- package/dist/host/types/src/ui/components/tooltip.d.ts +7 -0
- package/dist/host/types/src/ui/contexts/BrandingContext.d.ts +15 -0
- package/dist/host/types/src/ui/contexts/UiPreferencesContext.d.ts +38 -0
- package/dist/host/types/src/ui/contexts/branding-document.d.ts +10 -0
- package/dist/host/types/src/ui/dashboard/dashboard-cards-registry.d.ts +30 -0
- package/dist/host/types/src/ui/hooks/use-action-slot.d.ts +10 -0
- package/dist/host/types/src/ui/hooks/use-adaptive-polling.d.ts +5 -0
- package/dist/host/types/src/ui/hooks/use-mobile.d.ts +1 -0
- package/dist/host/types/src/ui/hooks/use-permissions.d.ts +29 -0
- package/dist/host/types/src/ui/hooks/use-status-color-scheme.d.ts +12 -0
- package/dist/host/types/src/ui/hooks/use-sync-status.d.ts +4 -0
- package/dist/host/types/src/ui/hooks/useSshAuthProviders.d.ts +17 -0
- package/dist/host/types/src/ui/i18n/i18n.d.ts +52 -0
- package/dist/host/types/src/ui/lib/ServerStatusContext.d.ts +33 -0
- package/dist/host/types/src/ui/lib/adaptive-polling.d.ts +29 -0
- package/dist/host/types/src/ui/lib/adaptive-resource-budget.d.ts +39 -0
- package/dist/host/types/src/ui/lib/app-keyboard-shortcuts.d.ts +8 -0
- package/dist/host/types/src/ui/lib/base-path.d.ts +1 -0
- package/dist/host/types/src/ui/lib/clipboard-provider.d.ts +9 -0
- package/dist/host/types/src/ui/lib/clipboard.d.ts +20 -0
- package/dist/host/types/src/ui/lib/connection-origin.d.ts +47 -0
- package/dist/host/types/src/ui/lib/db-health-monitor.d.ts +34 -0
- package/dist/host/types/src/ui/lib/device-id.d.ts +1 -0
- package/dist/host/types/src/ui/lib/electron.d.ts +1 -0
- package/dist/host/types/src/ui/lib/embedded-server-status.d.ts +15 -0
- package/dist/host/types/src/ui/lib/error-message.d.ts +9 -0
- package/dist/host/types/src/ui/lib/frontend-logger.d.ts +67 -0
- package/dist/host/types/src/ui/lib/global-shortcut-handler.d.ts +3 -0
- package/dist/host/types/src/ui/lib/host-defaults-context.d.ts +48 -0
- package/dist/host/types/src/ui/lib/hosts-request-cache.d.ts +9 -0
- package/dist/host/types/src/ui/lib/keybinding-match.d.ts +3 -0
- package/dist/host/types/src/ui/lib/linked-server.d.ts +10 -0
- package/dist/host/types/src/ui/lib/local-adaptive-engine.d.ts +40 -0
- package/dist/host/types/src/ui/lib/permissions.d.ts +16 -0
- package/dist/host/types/src/ui/lib/plugin-i18n.d.ts +11 -0
- package/dist/host/types/src/ui/lib/plugin-icon.d.ts +11 -0
- package/dist/host/types/src/ui/lib/plugin-transport.d.ts +50 -0
- package/dist/host/types/src/ui/lib/registry.d.ts +29 -0
- package/dist/host/types/src/ui/lib/rem.d.ts +7 -0
- package/dist/host/types/src/ui/lib/remote-server-api.d.ts +11 -0
- package/dist/host/types/src/ui/lib/server-status-store.d.ts +37 -0
- package/dist/host/types/src/ui/lib/sync-events.d.ts +2 -0
- package/dist/host/types/src/ui/lib/sync-status.d.ts +6 -0
- package/dist/host/types/src/ui/lib/tab-jump-hotkey.d.ts +4 -0
- package/dist/host/types/src/ui/lib/theme.d.ts +21 -0
- package/dist/host/types/src/ui/lib/ttl-request-cache.d.ts +10 -0
- package/dist/host/types/src/ui/lib/usage-color.d.ts +5 -0
- package/dist/host/types/src/ui/lib/utils.d.ts +2 -0
- package/dist/host/types/src/ui/lib/visible-interval.d.ts +5 -0
- package/dist/host/types/src/ui/lib/widget-title.d.ts +8 -0
- package/dist/host/types/src/ui/lib/ws-auth.d.ts +1 -0
- package/dist/host/types/src/ui/main-axios.d.ts +170 -0
- package/dist/host/types/src/ui/plugin-host/PluginViewPlaceholder.d.ts +13 -0
- package/dist/host/types/src/ui/plugin-host/app.d.ts +18 -0
- package/dist/host/types/src/ui/plugin-host/auth-registry.d.ts +50 -0
- package/dist/host/types/src/ui/plugin-host/bridge.d.ts +26 -0
- package/dist/host/types/src/ui/plugin-host/component-registry.d.ts +24 -0
- package/dist/host/types/src/ui/plugin-host/desktop.d.ts +7 -0
- package/dist/host/types/src/ui/plugin-host/extension-registry.d.ts +16 -0
- package/dist/host/types/src/ui/plugin-host/host-scope.d.ts +26 -0
- package/dist/host/types/src/ui/plugin-host/loader.d.ts +52 -0
- package/dist/host/types/src/ui/plugin-host/plugin-store.d.ts +40 -0
- package/dist/host/types/src/ui/plugin-host/scope.d.ts +20 -0
- package/dist/host/types/src/ui/plugin-host/sdk-ui.d.ts +99 -0
- package/dist/host/types/src/ui/plugin-host/shell-bridge.d.ts +67 -0
- package/dist/host/types/src/ui/plugin-host/view-ownership.d.ts +31 -0
- package/dist/host/types/src/ui/settings/HostPluginSections.d.ts +31 -0
- package/dist/host/types/src/ui/settings/SettingsFields.d.ts +22 -0
- package/dist/host/types/src/ui/settings/host-feature-tabs.d.ts +11 -0
- package/dist/host/types/src/ui/settings/settings-components.d.ts +32 -0
- package/dist/host/types/src/ui/settings/settings-fields-util.d.ts +11 -0
- package/dist/host/types/src/ui/shell/ActionSlot.d.ts +41 -0
- package/dist/host/types/src/ui/shell/TabContext.d.ts +30 -0
- package/dist/host/types/src/ui/shell/action-registry.d.ts +99 -0
- package/dist/host/types/src/ui/shell/keybinding-registry.d.ts +62 -0
- package/dist/host/types/src/ui/shell/palette-registry.d.ts +55 -0
- package/dist/host/types/src/ui/shell/panel-registry.d.ts +24 -0
- package/dist/host/types/src/ui/shell/tab-registry.d.ts +92 -0
- package/dist/host/types/src/ui/sidebar/HostManagerTabs.d.ts +65 -0
- package/dist/host/types/src/ui/sidebar/host-contributions.d.ts +72 -0
- package/dist/host/types/src/ui/sidebar/host-defaults/catalog.d.ts +17 -0
- package/dist/host/types/src/ui/sidebar/host-protocols.d.ts +49 -0
- package/dist/host/types/src/ui/sidebar/quick-connect-host.d.ts +21 -0
- package/dist/host/types/src/ui/sidebar/rail-items.d.ts +103 -0
- package/dist/host/types/src/ui/ssh/connection-log/ConnectionLogContext.d.ts +17 -0
- package/dist/host/types/src/ui/ssh/dialogs/BrowserSignInDialog.d.ts +18 -0
- package/dist/host/types/src/ui/ssh/dialogs/HostKeyVerificationDialog.d.ts +17 -0
- package/dist/host/types/src/ui/ssh/dialogs/PassphraseDialog.d.ts +15 -0
- package/dist/host/types/src/ui/ssh/dialogs/SSHAuthDialog.d.ts +20 -0
- package/dist/host/types/src/ui/ssh/dialogs/TOTPDialog.d.ts +13 -0
- package/dist/host/ui.d.ts +1 -0
- package/dist/host/ui.js +31754 -0
- package/dist/host/use-permissions-NULZL43J.js +18 -0
- package/dist/host-commands.d.ts +89 -0
- package/dist/host-commands.js +387 -0
- package/dist/manifest.d.ts +461 -0
- package/dist/manifest.js +1499 -0
- package/dist/settings.d.ts +43 -0
- package/dist/settings.js +168 -0
- package/dist/ssh-certs.d.ts +25 -0
- package/dist/ssh-certs.js +234 -0
- package/dist/ssh-options.d.ts +23 -0
- package/dist/ssh-options.js +1 -0
- package/dist/table-builder.d.ts +26 -0
- package/dist/table-builder.js +87 -0
- package/dist/testing/safe-ports.d.ts +10 -0
- package/dist/testing/safe-ports.js +59 -0
- package/dist/testing/setup.d.ts +7 -0
- package/dist/testing/setup.js +146 -0
- package/dist/testing.d.ts +537 -0
- package/dist/testing.js +1451 -0
- package/dist/vitest-preset.d.ts +54 -0
- package/dist/vitest-preset.js +96 -0
- package/package.json +143 -0
- package/schema/changelog.schema.json +67 -0
- package/schema/manifest.schema.json +1160 -0
- package/tsconfig.plugin.json +31 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared vitest setup for core and for every plugin suite.
|
|
3
|
+
*
|
|
4
|
+
* The root vitest.setup.ts re-exports this so there is one copy of the
|
|
5
|
+
* polyfills rather than two that drift apart.
|
|
6
|
+
*/
|
|
7
|
+
import { afterEach, vi } from "vitest";
|
|
8
|
+
import { installSafePorts } from "./safe-ports.js";
|
|
9
|
+
installSafePorts();
|
|
10
|
+
if (typeof window !== "undefined" && !window.matchMedia) {
|
|
11
|
+
window.matchMedia = vi.fn().mockImplementation((query) => ({
|
|
12
|
+
matches: false,
|
|
13
|
+
media: query,
|
|
14
|
+
onchange: null,
|
|
15
|
+
addEventListener: vi.fn(),
|
|
16
|
+
removeEventListener: vi.fn(),
|
|
17
|
+
addListener: vi.fn(),
|
|
18
|
+
removeListener: vi.fn(),
|
|
19
|
+
dispatchEvent: vi.fn(),
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* jsdom in this project ships without a `localStorage` global, and Node only
|
|
24
|
+
* provides one when started with --localstorage-file (which persists to disk
|
|
25
|
+
* and is shared across test files -- both wrong for tests). Suites that touch
|
|
26
|
+
* storage therefore threw `Cannot read properties of undefined`, taking out
|
|
27
|
+
* every test in the file rather than just the ones that used it.
|
|
28
|
+
*
|
|
29
|
+
* A per-process in-memory implementation is enough: it satisfies the Storage
|
|
30
|
+
* interface the app uses and keeps nothing on disk.
|
|
31
|
+
*/
|
|
32
|
+
if (typeof globalThis.localStorage === "undefined") {
|
|
33
|
+
const createMemoryStorage = () => {
|
|
34
|
+
const store = new Map();
|
|
35
|
+
return {
|
|
36
|
+
get length() {
|
|
37
|
+
return store.size;
|
|
38
|
+
},
|
|
39
|
+
key: (index) => Array.from(store.keys())[index] ?? null,
|
|
40
|
+
getItem: (key) => (store.has(key) ? store.get(key) : null),
|
|
41
|
+
setItem: (key, value) => {
|
|
42
|
+
store.set(key, String(value));
|
|
43
|
+
},
|
|
44
|
+
removeItem: (key) => {
|
|
45
|
+
store.delete(key);
|
|
46
|
+
},
|
|
47
|
+
clear: () => {
|
|
48
|
+
store.clear();
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
Object.defineProperty(globalThis, "localStorage", {
|
|
53
|
+
value: createMemoryStorage(),
|
|
54
|
+
configurable: true,
|
|
55
|
+
writable: true,
|
|
56
|
+
});
|
|
57
|
+
Object.defineProperty(globalThis, "sessionStorage", {
|
|
58
|
+
value: createMemoryStorage(),
|
|
59
|
+
configurable: true,
|
|
60
|
+
writable: true,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* A plain plugin host for components a test renders on its own: keys come
|
|
65
|
+
* back as text, every permission is held, and nothing reaches a server.
|
|
66
|
+
* renderWithApp installs Termix's real host instead, which takes precedence.
|
|
67
|
+
*/
|
|
68
|
+
const noop = () => { };
|
|
69
|
+
const offline = () => Promise.reject(new Error("No plugin API in a unit test; mock it"));
|
|
70
|
+
globalThis.__termixTestPluginHost = {
|
|
71
|
+
usePluginId: () => "test-plugin",
|
|
72
|
+
useTranslation: () => ({
|
|
73
|
+
t: (key) => key,
|
|
74
|
+
language: "en",
|
|
75
|
+
}),
|
|
76
|
+
usePermission: () => true,
|
|
77
|
+
useSettings: () => ({ values: {}, loaded: true, save: async () => { } }),
|
|
78
|
+
useHost: () => null,
|
|
79
|
+
useHosts: () => ({ hosts: [], loaded: true }),
|
|
80
|
+
useHostStatus: () => null,
|
|
81
|
+
useCurrentUser: () => null,
|
|
82
|
+
useTheme: () => ({ theme: "dark" }),
|
|
83
|
+
toast: { success: noop, error: noop, info: noop, warning: noop },
|
|
84
|
+
getApi: () => ({
|
|
85
|
+
get: offline,
|
|
86
|
+
delete: offline,
|
|
87
|
+
post: offline,
|
|
88
|
+
put: offline,
|
|
89
|
+
patch: offline,
|
|
90
|
+
}),
|
|
91
|
+
useTabs: () => ({
|
|
92
|
+
openTab: noop,
|
|
93
|
+
openSingletonTab: noop,
|
|
94
|
+
connectHost: noop,
|
|
95
|
+
closeTab: noop,
|
|
96
|
+
getLayout: () => null,
|
|
97
|
+
applyLayout: async () => ({ skipped: [] }),
|
|
98
|
+
onChange: () => noop,
|
|
99
|
+
onReady: () => noop,
|
|
100
|
+
}),
|
|
101
|
+
invokeAction: async () => undefined,
|
|
102
|
+
useSshAuthTypes: () => ({
|
|
103
|
+
types: ["password", "key", "credential", "agent", "none"].map((type) => ({
|
|
104
|
+
type,
|
|
105
|
+
labelKey: type,
|
|
106
|
+
pluginId: "core",
|
|
107
|
+
credentialType: type === "password" || type === "key",
|
|
108
|
+
supportsBackground: type !== "none",
|
|
109
|
+
quickConnect: true,
|
|
110
|
+
})),
|
|
111
|
+
loaded: true,
|
|
112
|
+
}),
|
|
113
|
+
useSlotContributions: () => [],
|
|
114
|
+
usePluginComponent: () => undefined,
|
|
115
|
+
getApiFor: () => ({
|
|
116
|
+
get: offline,
|
|
117
|
+
delete: offline,
|
|
118
|
+
post: offline,
|
|
119
|
+
put: offline,
|
|
120
|
+
patch: offline,
|
|
121
|
+
}),
|
|
122
|
+
hostProtocols: () => ["ssh"],
|
|
123
|
+
useHostActions: () => [],
|
|
124
|
+
useActivityTypes: () => [],
|
|
125
|
+
activityTarget: () => undefined,
|
|
126
|
+
useExtensions: () => [],
|
|
127
|
+
getExtension: () => undefined,
|
|
128
|
+
usePluginUiPreferences: () => ({ values: {}, set: noop }),
|
|
129
|
+
core: {
|
|
130
|
+
logActivity: async () => { },
|
|
131
|
+
getHostPassword: async () => null,
|
|
132
|
+
patchOpenTab: async () => { },
|
|
133
|
+
getCustomKeybindings: async () => [],
|
|
134
|
+
runKeybindingAction: () => false,
|
|
135
|
+
getClientPreference: () => undefined,
|
|
136
|
+
setClientPreference: () => { },
|
|
137
|
+
listHosts: async () => [],
|
|
138
|
+
listCredentials: async () => [],
|
|
139
|
+
notifyHostsChanged: () => { },
|
|
140
|
+
getHostStatusColorScheme: () => "accent",
|
|
141
|
+
getLocalAuthToken: () => null,
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
afterEach(() => {
|
|
145
|
+
vi.restoreAllMocks();
|
|
146
|
+
});
|
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test helpers for plugin authors.
|
|
3
|
+
*
|
|
4
|
+
* createMockCtx is the one to reach for: it enforces capabilities the way the
|
|
5
|
+
* real runtime does. createFakeContext stays for tests that only need a
|
|
6
|
+
* context-shaped object and do not care about the gates.
|
|
7
|
+
*
|
|
8
|
+
* The vitest config these run under comes from @termix-ssh/plugin-sdk/vitest-preset.
|
|
9
|
+
*/
|
|
10
|
+
import type { PluginContext } from "./backend.js";
|
|
11
|
+
import type { HostActionContribution, TermixApp } from "./frontend.js";
|
|
12
|
+
import type { PluginManifest } from "./manifest.js";
|
|
13
|
+
import type { PluginTableDefinition } from "./db.js";
|
|
14
|
+
import type { PluginRouterOptions, PluginWebSocketHandler, PluginWebSockets, PluginWebSocketOptions } from "./backend.js";
|
|
15
|
+
/** A ctx.ws registration as the test doubles record it. */
|
|
16
|
+
export interface FakeWsRoute {
|
|
17
|
+
path: string;
|
|
18
|
+
raw: boolean;
|
|
19
|
+
/** For ctx.ws.route. */
|
|
20
|
+
handler?: PluginWebSocketHandler;
|
|
21
|
+
/** For ctx.ws.upgrade. */
|
|
22
|
+
upgrade?: Parameters<PluginWebSockets["upgrade"]>[1];
|
|
23
|
+
options?: PluginWebSocketOptions;
|
|
24
|
+
}
|
|
25
|
+
import type { PluginLoginMethod, PluginVerifiedIdentity, PluginSecondFactor, PluginSshAuthProvider, PluginSshConnectOptions, PluginSshHost, PluginHostSummary, PluginShareableUser, PluginShareableRole, PluginSshKeyCredential, PluginSshKeyCredentialInput } from "./backend.js";
|
|
26
|
+
import type { SyncEntityRegistration } from "./backend.js";
|
|
27
|
+
import type { PluginDatabase } from "./backend.js";
|
|
28
|
+
import type { PluginExternalClientRequest, PluginProtocolTarget, PluginHostStatusEntry, PluginNotification, PluginNotificationChannel, PluginNotifyHub, PluginFetchInit, PluginBinarySpec, PluginTlsCertificateInfo, PluginTlsStatus, PluginSummary, PluginKeyboardInteractiveHandler, PluginProcessHandle, PluginProcessOptions } from "./backend.js";
|
|
29
|
+
/** A timer registered through ctx.schedule, run by hand with runScheduled. */
|
|
30
|
+
export interface FakeScheduledJob {
|
|
31
|
+
kind: "every" | "after";
|
|
32
|
+
ms: number;
|
|
33
|
+
fn: () => void | Promise<void>;
|
|
34
|
+
stopped: boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface FakeContextOptions {
|
|
37
|
+
pluginId?: string;
|
|
38
|
+
manifest?: Partial<PluginManifest>;
|
|
39
|
+
/** Acting user returned by currentActor and used by asUser. */
|
|
40
|
+
actor?: string;
|
|
41
|
+
/** Seed for admin-scope ctx.settings. */
|
|
42
|
+
settings?: Record<string, unknown>;
|
|
43
|
+
/** Seed for the core settings ctx.settings.readCore can reach. */
|
|
44
|
+
coreSettings?: Record<string, string>;
|
|
45
|
+
/** What ctx.ssh.connect and withConnection hand back as the client. */
|
|
46
|
+
sshClient?: unknown;
|
|
47
|
+
/** A real database behind ctx.db, usually createTestDb().database. */
|
|
48
|
+
db?: PluginDatabase;
|
|
49
|
+
/** What ctx.http.router returns, e.g. () => express.Router(). */
|
|
50
|
+
router?: () => unknown;
|
|
51
|
+
/**
|
|
52
|
+
* Role permissions the acting user holds, as full ids or this plugin's
|
|
53
|
+
* short names. When set, ctx.rbac enforces them and require() answers 403;
|
|
54
|
+
* when omitted every check passes, so tests that do not care stay simple.
|
|
55
|
+
*/
|
|
56
|
+
permissions?: string[];
|
|
57
|
+
/**
|
|
58
|
+
* Services other plugins provide, for testing a consumer. Keyed by service
|
|
59
|
+
* name, or "<service>#<provider>" for a named provider.
|
|
60
|
+
*/
|
|
61
|
+
services?: Record<string, object>;
|
|
62
|
+
/** Hosts ctx.hosts.list/get/checkAccess answer with. */
|
|
63
|
+
hosts?: PluginHostSummary[];
|
|
64
|
+
/** Users and roles ctx.hosts.listUsers/listRoles answer with. */
|
|
65
|
+
shareableUsers?: PluginShareableUser[];
|
|
66
|
+
shareableRoles?: PluginShareableRole[];
|
|
67
|
+
/** Hosts ctx.ssh.resolveHost answers with, secrets included. */
|
|
68
|
+
sshHosts?: PluginSshHost[];
|
|
69
|
+
/**
|
|
70
|
+
* What ctx.credentials.resolveHostProtocol answers, keyed
|
|
71
|
+
* "<hostId>:<protocol>". A missing key answers null.
|
|
72
|
+
*/
|
|
73
|
+
protocolTargets?: Record<string, PluginProtocolTarget>;
|
|
74
|
+
/** What ctx.credentials.listSshKeys answers. */
|
|
75
|
+
sshKeyCredentials?: PluginSshKeyCredential[];
|
|
76
|
+
/** What ctx.desktop.available() answers. Defaults to false. */
|
|
77
|
+
desktopAvailable?: boolean;
|
|
78
|
+
/** What ctx.hosts.status.get and check answer, by host id. */
|
|
79
|
+
hostStatuses?: Record<number, PluginHostStatusEntry>;
|
|
80
|
+
/** The acting user's channels, what ctx.notify.channels answers. */
|
|
81
|
+
notificationChannels?: PluginNotificationChannel[];
|
|
82
|
+
/**
|
|
83
|
+
* Answers ctx.fetch. Without it every fetch rejects, so a test never
|
|
84
|
+
* reaches the network by accident.
|
|
85
|
+
*/
|
|
86
|
+
fetch?: (url: string, init?: PluginFetchInit) => Promise<Response>;
|
|
87
|
+
/**
|
|
88
|
+
* Answers ctx.process. Without run every ctx.process.run rejects; without
|
|
89
|
+
* ensureBinary it answers "/tmp/<pluginId>/bin/<name>".
|
|
90
|
+
*/
|
|
91
|
+
process?: FakeProcessOptions;
|
|
92
|
+
/**
|
|
93
|
+
* What ctx.system.tlsStatus answers before any write. renewal is filled in
|
|
94
|
+
* from registerTlsRenewer calls.
|
|
95
|
+
*/
|
|
96
|
+
tlsStatus?: Omit<PluginTlsStatus, "renewal">;
|
|
97
|
+
/** What ctx.plugins.list answers. Default: only this plugin, active. */
|
|
98
|
+
installedPlugins?: PluginSummary[];
|
|
99
|
+
/**
|
|
100
|
+
* Checks a ctx.system.writeTlsCertificate call. Throw to reject the pair.
|
|
101
|
+
* Without it every pair is accepted and described with placeholder info.
|
|
102
|
+
*/
|
|
103
|
+
validateTls?: (certificatePem: string, privateKeyPem: string) => PluginTlsCertificateInfo;
|
|
104
|
+
/**
|
|
105
|
+
* Makes ctx.auth.recordEnrollment refuse the way core's policy does (trusted
|
|
106
|
+
* proxy login on, password login off), with this message and a 409.
|
|
107
|
+
*/
|
|
108
|
+
refuseEnrollment?: string;
|
|
109
|
+
/** What ctx.http.baseUrl answers. Defaults to "https://termix.test". */
|
|
110
|
+
baseUrl?: string;
|
|
111
|
+
/** What ctx.auth.countLinkedUsers answers, by provider. Defaults to 0. */
|
|
112
|
+
linkedUsers?: Record<string, number>;
|
|
113
|
+
/** Failures before ctx.auth.loginRateLimit locks a key. Defaults to 5. */
|
|
114
|
+
loginAttemptLimit?: number;
|
|
115
|
+
}
|
|
116
|
+
export interface FakeProcessOptions {
|
|
117
|
+
run?: (file: string, args: readonly string[], options?: PluginProcessOptions) => PluginProcessHandle | Promise<PluginProcessHandle>;
|
|
118
|
+
ensureBinary?: (spec: PluginBinarySpec) => Promise<string>;
|
|
119
|
+
}
|
|
120
|
+
/** A ctx.process handle a test drives by hand. */
|
|
121
|
+
export interface FakeProcess extends PluginProcessHandle {
|
|
122
|
+
emitStdout: (chunk: string) => void;
|
|
123
|
+
emitStderr: (chunk: string) => void;
|
|
124
|
+
exit: (code: number | null, signal?: string | null) => void;
|
|
125
|
+
killed: string[];
|
|
126
|
+
}
|
|
127
|
+
export declare function createFakeProcess(pid?: number): FakeProcess;
|
|
128
|
+
export interface FakeAuthRegistrations {
|
|
129
|
+
sshAuthProviders: PluginSshAuthProvider[];
|
|
130
|
+
keyboardInteractiveHandlers: PluginKeyboardInteractiveHandler[];
|
|
131
|
+
loginMethods: PluginLoginMethod[];
|
|
132
|
+
secondFactors: PluginSecondFactor[];
|
|
133
|
+
/** "<userId>:<factorId>" for every recorded enrolment. */
|
|
134
|
+
enrollments: Set<string>;
|
|
135
|
+
/** Secret schemes registered through ctx.credentials.registerSecretResolver. */
|
|
136
|
+
secretResolvers: Map<string, (userId: string, reference: string) => Promise<string>>;
|
|
137
|
+
/** Identities handed to ctx.auth.completeRedirectLogin, in order. */
|
|
138
|
+
completedLogins: Array<{
|
|
139
|
+
methodId: string;
|
|
140
|
+
identity: PluginVerifiedIdentity;
|
|
141
|
+
}>;
|
|
142
|
+
/** Every ctx.auth.revokeSessions call, in order. */
|
|
143
|
+
revokedSessions: Array<{
|
|
144
|
+
providerId?: number | null;
|
|
145
|
+
sub?: string | null;
|
|
146
|
+
sid?: string | null;
|
|
147
|
+
}>;
|
|
148
|
+
/** Failures recorded through ctx.auth.loginRateLimit, by "<ip>|<key>". */
|
|
149
|
+
loginFailures: Map<string, number>;
|
|
150
|
+
}
|
|
151
|
+
export interface FakePluginContext {
|
|
152
|
+
ctx: PluginContext;
|
|
153
|
+
/** Runs what ctx.settings.onValidate registered, as a settings save would. */
|
|
154
|
+
validateSettings: (scope: "admin" | "user" | "host", values: Record<string, unknown>, context?: {
|
|
155
|
+
hostId?: number;
|
|
156
|
+
}) => Promise<Record<string, string>>;
|
|
157
|
+
/** Everything the plugin registered through ctx.disposables. */
|
|
158
|
+
disposals: Array<() => void | Promise<void>>;
|
|
159
|
+
/** Topics emitted through ctx.events.emit, in order. */
|
|
160
|
+
emitted: Array<{
|
|
161
|
+
topic: string;
|
|
162
|
+
payload: unknown;
|
|
163
|
+
}>;
|
|
164
|
+
/** Backing store behind ctx.kv. */
|
|
165
|
+
kv: Map<string, unknown>;
|
|
166
|
+
/** Table definitions registered through ctx.db.define, in order. */
|
|
167
|
+
tables: PluginTableDefinition[];
|
|
168
|
+
/** Sync entities registered through ctx.sync.registerEntity, in order. */
|
|
169
|
+
syncEntities: SyncEntityRegistration[];
|
|
170
|
+
/**
|
|
171
|
+
* WebSocket routes registered through ctx.ws, in order, with the handler
|
|
172
|
+
* and options, so a test can hand a route a fake socket.
|
|
173
|
+
*/
|
|
174
|
+
wsRoutes: FakeWsRoute[];
|
|
175
|
+
/** Router options passed to ctx.http.router, in order. */
|
|
176
|
+
httpRouters: Array<PluginRouterOptions | undefined>;
|
|
177
|
+
/** Backing store behind ctx.settings, keyed "<scope>:<scopeId>:<key>". */
|
|
178
|
+
settings: Map<string, unknown>;
|
|
179
|
+
/** Core settings readable through ctx.settings.readCore. */
|
|
180
|
+
coreSettings: Map<string, string>;
|
|
181
|
+
/** Every ctx.ssh.connect and withConnection call, in order. */
|
|
182
|
+
sshConnections: Array<{
|
|
183
|
+
host: number | PluginSshHost;
|
|
184
|
+
pool?: string;
|
|
185
|
+
options?: PluginSshConnectOptions;
|
|
186
|
+
}>;
|
|
187
|
+
/** Every ctx.hosts.share call, in order. */
|
|
188
|
+
hostShares: Array<{
|
|
189
|
+
hostId: number;
|
|
190
|
+
targets: unknown[];
|
|
191
|
+
permissionLevel: string;
|
|
192
|
+
durationHours?: number;
|
|
193
|
+
}>;
|
|
194
|
+
/** Everything registered through ctx.auth. */
|
|
195
|
+
auth: FakeAuthRegistrations;
|
|
196
|
+
/** Every ctx.desktop.openIsolatedWindow call, in order. */
|
|
197
|
+
desktopWindows: Array<{
|
|
198
|
+
url: string;
|
|
199
|
+
partition?: string;
|
|
200
|
+
title?: string;
|
|
201
|
+
ignoreCert?: boolean;
|
|
202
|
+
}>;
|
|
203
|
+
/** Every ctx.desktop.launchExternalClient call, in order. */
|
|
204
|
+
externalClientLaunches: PluginExternalClientRequest[];
|
|
205
|
+
/** Every ctx.credentials.resolveHostProtocol call, in order. */
|
|
206
|
+
credentialReads: Array<{
|
|
207
|
+
hostId: number;
|
|
208
|
+
protocol: string;
|
|
209
|
+
}>;
|
|
210
|
+
/** Every ctx.credentials.createSshKey call, with the id it answered. */
|
|
211
|
+
createdSshKeys: Array<PluginSshKeyCredentialInput & {
|
|
212
|
+
id: number;
|
|
213
|
+
}>;
|
|
214
|
+
/** Every ctx.notify.send call, with the actor it ran as. */
|
|
215
|
+
notifications: Array<{
|
|
216
|
+
actor: string | undefined;
|
|
217
|
+
notification: PluginNotification;
|
|
218
|
+
}>;
|
|
219
|
+
/** The hub the plugin registered with ctx.notify.serve, if any. */
|
|
220
|
+
notifyHub: {
|
|
221
|
+
current: PluginNotifyHub | null;
|
|
222
|
+
};
|
|
223
|
+
/** Backing store behind ctx.secrets.get/set, keyed "<userId>:<key>". */
|
|
224
|
+
secretStore: Map<string, string>;
|
|
225
|
+
/** Every ctx.fetch call, in order. */
|
|
226
|
+
fetches: Array<{
|
|
227
|
+
url: string;
|
|
228
|
+
init?: PluginFetchInit;
|
|
229
|
+
}>;
|
|
230
|
+
/** Every ctx.process.run call, in order. */
|
|
231
|
+
processRuns: Array<{
|
|
232
|
+
file: string;
|
|
233
|
+
args: readonly string[];
|
|
234
|
+
options?: PluginProcessOptions;
|
|
235
|
+
}>;
|
|
236
|
+
/** Every ctx.process.ensureBinary call, in order. */
|
|
237
|
+
binaries: PluginBinarySpec[];
|
|
238
|
+
/** What ctx.system did: accepted writes, reloads, live challenges and renewers. */
|
|
239
|
+
tls: {
|
|
240
|
+
writes: Array<{
|
|
241
|
+
certificatePem: string;
|
|
242
|
+
privateKeyPem: string;
|
|
243
|
+
}>;
|
|
244
|
+
reloads: number;
|
|
245
|
+
/** ctx.system calls by name, in order. */
|
|
246
|
+
calls: string[];
|
|
247
|
+
challenges: Map<string, string>;
|
|
248
|
+
renewers: number;
|
|
249
|
+
};
|
|
250
|
+
/** Every ctx.audit.record entry, in order. */
|
|
251
|
+
audits: Array<{
|
|
252
|
+
action: string;
|
|
253
|
+
success: boolean;
|
|
254
|
+
[key: string]: unknown;
|
|
255
|
+
}>;
|
|
256
|
+
/** Every ctx.hosts.recordActivity call, in order. */
|
|
257
|
+
activities: Array<{
|
|
258
|
+
hostId: number;
|
|
259
|
+
type: string;
|
|
260
|
+
hostName: string;
|
|
261
|
+
}>;
|
|
262
|
+
/** Host ids with a live ctx.hosts.trackSession, one entry per session. */
|
|
263
|
+
trackedSessions: number[];
|
|
264
|
+
/** Every ctx.ssh.startInteraction / cancelInteraction call, in order. */
|
|
265
|
+
interactions: Array<{
|
|
266
|
+
action: "start" | "cancel";
|
|
267
|
+
interaction: string;
|
|
268
|
+
request: Record<string, unknown>;
|
|
269
|
+
}>;
|
|
270
|
+
/** Every ctx.hosts.status.reportLogin call, in order. */
|
|
271
|
+
statusReports: Array<{
|
|
272
|
+
hostId: number;
|
|
273
|
+
ok: boolean;
|
|
274
|
+
hostKeyChanged?: boolean;
|
|
275
|
+
}>;
|
|
276
|
+
/** ctx.hosts.status.registerPort resolvers, by connection type. */
|
|
277
|
+
statusPorts: Map<string, (hostId: number) => unknown>;
|
|
278
|
+
/** Backing store behind ctx.hosts.status.get and check. */
|
|
279
|
+
hostStatuses: Map<number, PluginHostStatusEntry>;
|
|
280
|
+
/** Every ctx.ssh.dropPooled call, in order. */
|
|
281
|
+
droppedPools: Array<{
|
|
282
|
+
pool: string;
|
|
283
|
+
hostId: number;
|
|
284
|
+
}>;
|
|
285
|
+
/** Every ctx.schedule timer, stopped ones included. */
|
|
286
|
+
scheduled: FakeScheduledJob[];
|
|
287
|
+
/** Runs every live ctx.schedule job once; "after" jobs are then done. */
|
|
288
|
+
runScheduled: () => Promise<void>;
|
|
289
|
+
/** Changes the acting user, as core's request middleware would. */
|
|
290
|
+
setActor: (userId: string | undefined) => void;
|
|
291
|
+
/**
|
|
292
|
+
* Runs `fn` as `userId`, the way a request from that user would. For test
|
|
293
|
+
* code standing in for a caller; unlike ctx.asUser it needs no capability.
|
|
294
|
+
*/
|
|
295
|
+
actAs: <T>(userId: string, fn: () => Promise<T> | T) => Promise<T>;
|
|
296
|
+
/** Provided or seeded services, by name or "<service>#<provider>". */
|
|
297
|
+
services: Map<string, object>;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* A context double good enough to call activate() against in a unit test.
|
|
301
|
+
* Nothing here is capability-checked: a test asserting a capability gate
|
|
302
|
+
* should use the real runtime, not this.
|
|
303
|
+
*/
|
|
304
|
+
export declare function createFakeContext(options?: FakeContextOptions): FakePluginContext;
|
|
305
|
+
export interface MockContextOptions {
|
|
306
|
+
pluginId?: string;
|
|
307
|
+
manifest?: Partial<PluginManifest>;
|
|
308
|
+
/** Capabilities the plugin is granted. Anything else throws. */
|
|
309
|
+
capabilities?: string[];
|
|
310
|
+
/** Seed for ctx.kv. */
|
|
311
|
+
kv?: Record<string, unknown>;
|
|
312
|
+
/** Seed for this plugin's own admin-scope settings. */
|
|
313
|
+
settings?: Record<string, unknown>;
|
|
314
|
+
/** Seed for the core settings readCore can reach. */
|
|
315
|
+
coreSettings?: Record<string, string>;
|
|
316
|
+
/** Acting user returned by currentActor and used by asUser. */
|
|
317
|
+
actor?: string;
|
|
318
|
+
/** What ctx.ssh.connect and withConnection hand back as the client. */
|
|
319
|
+
sshClient?: unknown;
|
|
320
|
+
/** A real database behind ctx.db, usually createTestDb().database. */
|
|
321
|
+
db?: PluginDatabase;
|
|
322
|
+
/** What ctx.http.router returns, e.g. () => express.Router(). */
|
|
323
|
+
router?: () => unknown;
|
|
324
|
+
/** Role permissions the acting user holds. See FakeContextOptions. */
|
|
325
|
+
permissions?: string[];
|
|
326
|
+
/** Hosts ctx.hosts.list/get/checkAccess serve. See FakeContextOptions. */
|
|
327
|
+
hosts?: PluginHostSummary[];
|
|
328
|
+
/** Users and roles ctx.hosts.listUsers/listRoles serve. See FakeContextOptions. */
|
|
329
|
+
shareableUsers?: PluginShareableUser[];
|
|
330
|
+
shareableRoles?: PluginShareableRole[];
|
|
331
|
+
/** Other plugins' services. See FakeContextOptions. */
|
|
332
|
+
services?: Record<string, object>;
|
|
333
|
+
/** Hosts ctx.ssh.resolveHost serves. See FakeContextOptions. */
|
|
334
|
+
sshHosts?: PluginSshHost[];
|
|
335
|
+
/** What ctx.credentials serves. See FakeContextOptions. */
|
|
336
|
+
protocolTargets?: Record<string, PluginProtocolTarget>;
|
|
337
|
+
/** What ctx.credentials.listSshKeys serves. */
|
|
338
|
+
sshKeyCredentials?: PluginSshKeyCredential[];
|
|
339
|
+
/** What ctx.desktop.available() answers. */
|
|
340
|
+
desktopAvailable?: boolean;
|
|
341
|
+
/** What ctx.hosts.status answers. See FakeContextOptions. */
|
|
342
|
+
hostStatuses?: Record<number, PluginHostStatusEntry>;
|
|
343
|
+
/** The acting user's channels, what ctx.notify.channels answers. */
|
|
344
|
+
notificationChannels?: PluginNotificationChannel[];
|
|
345
|
+
/**
|
|
346
|
+
* Answers ctx.fetch. Without it every fetch rejects, so a test never
|
|
347
|
+
* reaches the network by accident.
|
|
348
|
+
*/
|
|
349
|
+
fetch?: (url: string, init?: PluginFetchInit) => Promise<Response>;
|
|
350
|
+
/** See FakeContextOptions. */
|
|
351
|
+
process?: FakeProcessOptions;
|
|
352
|
+
/** See FakeContextOptions. */
|
|
353
|
+
tlsStatus?: FakeContextOptions["tlsStatus"];
|
|
354
|
+
/** See FakeContextOptions. */
|
|
355
|
+
installedPlugins?: FakeContextOptions["installedPlugins"];
|
|
356
|
+
/** See FakeContextOptions. */
|
|
357
|
+
validateTls?: FakeContextOptions["validateTls"];
|
|
358
|
+
/** See FakeContextOptions. */
|
|
359
|
+
refuseEnrollment?: string;
|
|
360
|
+
/** See FakeContextOptions. */
|
|
361
|
+
baseUrl?: string;
|
|
362
|
+
/** See FakeContextOptions. */
|
|
363
|
+
linkedUsers?: Record<string, number>;
|
|
364
|
+
/** See FakeContextOptions. */
|
|
365
|
+
loginAttemptLimit?: number;
|
|
366
|
+
}
|
|
367
|
+
export interface MockPluginContext extends FakePluginContext {
|
|
368
|
+
/** Capability ids checked during the test, in order, denied ones included. */
|
|
369
|
+
checked: string[];
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* A context double that gates on capabilities the way src/backend/plugins/ctx.ts
|
|
373
|
+
* does, so a test can prove a plugin fails closed without a running server.
|
|
374
|
+
*
|
|
375
|
+
* The gates on today's ctx surface: kv:own on every ctx.kv call, db:own on
|
|
376
|
+
* ctx.db, network:serve on ctx.http and ctx.ws, events:core on emitting a
|
|
377
|
+
* topic outside the plugin's own namespace, settings:read-core on
|
|
378
|
+
* ctx.settings.readCore, ssh:connect plus credentials:use on ctx.ssh,
|
|
379
|
+
* notify:send on ctx.notify (notify:hub on serve), network:outbound on ctx.fetch, process:spawn on
|
|
380
|
+
* ctx.process, system:tls on ctx.system, plugins:read on ctx.plugins, and auth:provide on ctx.auth. Reading a plugin's own settings is deliberately
|
|
381
|
+
* ungated. As the SDK grows a member, add its gate here in the same shape.
|
|
382
|
+
*/
|
|
383
|
+
export declare function createMockCtx(options?: MockContextOptions): MockPluginContext;
|
|
384
|
+
/** A sqlite handle as createTestDb exposes it. Structural, like better-sqlite3's. */
|
|
385
|
+
export interface TestSqlite {
|
|
386
|
+
exec: (sql: string) => unknown;
|
|
387
|
+
prepare: (sql: string) => {
|
|
388
|
+
run: (...params: unknown[]) => unknown;
|
|
389
|
+
get: (...params: unknown[]) => unknown;
|
|
390
|
+
all: (...params: unknown[]) => unknown[];
|
|
391
|
+
};
|
|
392
|
+
close: () => void;
|
|
393
|
+
}
|
|
394
|
+
export interface TestDbOptions {
|
|
395
|
+
/**
|
|
396
|
+
* Runs after the core stub tables exist and before the plugin's migrations,
|
|
397
|
+
* e.g. to create the legacy table an adoption migration renames.
|
|
398
|
+
*/
|
|
399
|
+
before?: (sqlite: TestSqlite) => unknown;
|
|
400
|
+
/** Skip applying the migrations, to apply them by hand later. */
|
|
401
|
+
skipMigrations?: boolean;
|
|
402
|
+
}
|
|
403
|
+
export interface TestDb {
|
|
404
|
+
/** The raw better-sqlite3 handle, for asserting on tables directly. */
|
|
405
|
+
sqlite: TestSqlite;
|
|
406
|
+
/** The Drizzle handle ctx.db.client() returns. */
|
|
407
|
+
drizzle: unknown;
|
|
408
|
+
/** Pass this as createMockCtx({ db }). */
|
|
409
|
+
database: PluginDatabase;
|
|
410
|
+
/** How many times ctx.db.persist() was called. */
|
|
411
|
+
readonly persisted: number;
|
|
412
|
+
/** The migration ids applied, in order. */
|
|
413
|
+
applied: string[];
|
|
414
|
+
/** Applies the plugin's sqlite migrations, when skipMigrations was set. */
|
|
415
|
+
migrate: () => Promise<string[]>;
|
|
416
|
+
close: () => void;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* An in-memory SQLite database with a plugin's own migrations applied.
|
|
420
|
+
*
|
|
421
|
+
* Core's users, ssh_data, roles and user_roles exist as minimal stubs with
|
|
422
|
+
* foreign keys on, so a refUser or refHost column cascades exactly as it
|
|
423
|
+
* does on the server, and ctx.db.refs() has something real to join against.
|
|
424
|
+
* The migrations are read from <pluginDir>/migrations/sqlite and split with
|
|
425
|
+
* the same splitter the server's runner uses.
|
|
426
|
+
*/
|
|
427
|
+
export declare function createTestDb(pluginDir: string, options?: TestDbOptions): Promise<TestDb>;
|
|
428
|
+
/**
|
|
429
|
+
* Rendering a plugin frontend in a test.
|
|
430
|
+
*
|
|
431
|
+
* renderWithApp activates the plugin against the real registration surface,
|
|
432
|
+
* so a test sees exactly what the shell would: which rail items, tabs, host
|
|
433
|
+
* actions and cards it registered, and each of those rendered with the SDK
|
|
434
|
+
* hooks working. It is implemented by the Termix host, which is where the
|
|
435
|
+
* registries live; the vitest preset points "@termix-ssh/plugin-host/testing" at
|
|
436
|
+
* core's implementation.
|
|
437
|
+
*/
|
|
438
|
+
export interface RenderWithAppOptions {
|
|
439
|
+
pluginId?: string;
|
|
440
|
+
manifest?: Partial<PluginManifest>;
|
|
441
|
+
/** Role permissions the current user holds. */
|
|
442
|
+
permissions?: string[];
|
|
443
|
+
isAdmin?: boolean;
|
|
444
|
+
/** Hosts useHosts() returns. */
|
|
445
|
+
hosts?: Array<Record<string, unknown>>;
|
|
446
|
+
/** The plugin's locales/en.json, loaded into its namespace. */
|
|
447
|
+
locales?: Record<string, unknown>;
|
|
448
|
+
/** Render as an anonymous guest page. */
|
|
449
|
+
guest?: boolean;
|
|
450
|
+
/** What app.tabs.getLayout returns until the plugin applies another. */
|
|
451
|
+
layout?: import("./frontend.js").ShellLayout;
|
|
452
|
+
/** Fire app.tabs.onReady after activation, as the shell does after login. */
|
|
453
|
+
ready?: boolean;
|
|
454
|
+
/** Stands in for app.api and usePluginApi(), e.g. a stub of the routes. */
|
|
455
|
+
api?: import("./frontend.js").PluginApiClient;
|
|
456
|
+
/** What app.desktop.remoteServerUrl() answers. Read on every call. */
|
|
457
|
+
remoteServerUrl?: () => string | null | Promise<string | null>;
|
|
458
|
+
}
|
|
459
|
+
/** A call a plugin made on the shell, recorded instead of performed. */
|
|
460
|
+
export interface ShellCall {
|
|
461
|
+
method: string;
|
|
462
|
+
args: unknown[];
|
|
463
|
+
}
|
|
464
|
+
export interface RenderedPluginApp {
|
|
465
|
+
app: TermixApp;
|
|
466
|
+
/** Everything this plugin registered, by kind. */
|
|
467
|
+
registered: {
|
|
468
|
+
railItems: () => Array<{
|
|
469
|
+
id: string;
|
|
470
|
+
hidden?: boolean;
|
|
471
|
+
/** The full permission id the item is gated on, if any. */
|
|
472
|
+
permission?: string;
|
|
473
|
+
}>;
|
|
474
|
+
tabs: () => string[];
|
|
475
|
+
panels: () => string[];
|
|
476
|
+
hostActions: () => Array<{
|
|
477
|
+
id: string;
|
|
478
|
+
tabType?: string;
|
|
479
|
+
}>;
|
|
480
|
+
/** This plugin's host actions that apply to `host`, as the shell sees them. */
|
|
481
|
+
hostActionsFor: (host: unknown) => HostActionContribution[];
|
|
482
|
+
hostProtocols: () => string[];
|
|
483
|
+
hostEditorSections: () => string[];
|
|
484
|
+
dashboardCards: () => string[];
|
|
485
|
+
/** Ids this plugin added to an extension point. */
|
|
486
|
+
extensions: (pointId: string) => string[];
|
|
487
|
+
settingsComponents: () => string[];
|
|
488
|
+
slot: (slotId: string) => string[];
|
|
489
|
+
actions: () => string[];
|
|
490
|
+
loginMethods: () => string[];
|
|
491
|
+
secondFactors: () => string[];
|
|
492
|
+
paletteGroups: () => string[];
|
|
493
|
+
keybindingActions: () => string[];
|
|
494
|
+
keybindingDefaults: () => string[];
|
|
495
|
+
};
|
|
496
|
+
/** A registered palette group's items, as the palette loads them on open. */
|
|
497
|
+
loadPaletteGroup: (id: string) => Promise<import("./frontend.js").PaletteItem[]>;
|
|
498
|
+
/** Shell calls made by the plugin's code, applyLayout included. */
|
|
499
|
+
shellCalls: ShellCall[];
|
|
500
|
+
/**
|
|
501
|
+
* Tabs the last app.tabs.applyLayout opened, under the shell's own restore
|
|
502
|
+
* rules: a tab whose host is gone is skipped, a tab no running plugin
|
|
503
|
+
* registered is kept.
|
|
504
|
+
*/
|
|
505
|
+
openedTabs: () => Array<{
|
|
506
|
+
type: string;
|
|
507
|
+
hostId?: number;
|
|
508
|
+
label: string;
|
|
509
|
+
}>;
|
|
510
|
+
/**
|
|
511
|
+
* Renders an opened tab the way the shell would, which for a tab no running
|
|
512
|
+
* plugin registered is the "needs the plugin" placeholder.
|
|
513
|
+
*/
|
|
514
|
+
renderOpenedTab: (index: number) => HTMLElement;
|
|
515
|
+
renderTab: (type: string, props?: Record<string, unknown>) => HTMLElement;
|
|
516
|
+
renderPanel: (id: string, props?: Record<string, unknown>) => HTMLElement;
|
|
517
|
+
renderDashboardCard: (id: string) => HTMLElement;
|
|
518
|
+
/** Renders one component of a contributed extension, "view" by default. */
|
|
519
|
+
renderExtension: (pointId: string, id: string, component?: string, props?: Record<string, unknown>) => HTMLElement;
|
|
520
|
+
renderHostEditorSection: (id: string, props?: Record<string, unknown>) => HTMLElement;
|
|
521
|
+
renderSettingsComponent: (componentId: string, props?: Record<string, unknown>) => HTMLElement;
|
|
522
|
+
/** Renders a login method's UI with login screen props, overridable. */
|
|
523
|
+
renderLoginMethod: (id: string, props?: Record<string, unknown>) => HTMLElement;
|
|
524
|
+
/** Renders a second factor's challenge with overridable props. */
|
|
525
|
+
renderSecondFactor: (id: string, props?: Record<string, unknown>) => HTMLElement;
|
|
526
|
+
/** Renders the Settings > Security section a factor or method brought. */
|
|
527
|
+
renderEnrollment: (id: string) => HTMLElement;
|
|
528
|
+
/** Renders a slot the way its owner would. */
|
|
529
|
+
renderSlot: (slotId: string, props?: Record<string, unknown>) => HTMLElement;
|
|
530
|
+
/** Runs deactivate and every disposer, as disabling the plugin does. */
|
|
531
|
+
deactivate: () => Promise<void>;
|
|
532
|
+
}
|
|
533
|
+
export type FrontendPluginModule = {
|
|
534
|
+
activate: (app: TermixApp) => void | Promise<void>;
|
|
535
|
+
deactivate?: () => void | Promise<void>;
|
|
536
|
+
};
|
|
537
|
+
export declare function renderWithApp(plugin: FrontendPluginModule, options?: RenderWithAppOptions): Promise<RenderedPluginApp>;
|