@termix-ssh/plugin-sdk 1.0.11 → 1.0.13
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/CHANGELOG.md +37 -0
- package/PLUGIN-GUIDE.md +178 -54
- package/cli/commands/build.mjs +36 -22
- package/cli/commands/pack.mjs +12 -2
- package/cli/commands/validate.mjs +63 -3
- package/cli/lib/plugin-dir.mjs +11 -0
- package/cli/lib/tmxplug.mjs +9 -0
- package/dist/backend.d.ts +404 -0
- package/dist/capabilities.js +7 -0
- package/dist/db.js +1 -0
- package/dist/desktop.d.ts +0 -243
- package/dist/frontend.d.ts +175 -2
- package/dist/frontend.js +7 -0
- package/dist/host/PluginsTab-OXQQ7M4I.js +3266 -0
- package/dist/host/chunk-54KCK6IZ.js +3167 -0
- package/dist/host/chunk-CALNJCHN.js +10435 -0
- package/dist/host/{chunk-7RRTYD6B.js → chunk-CN5UBTBP.js} +1412 -14
- package/dist/host/{chunk-RLQBKJRK.js → chunk-ERGN4YW6.js} +9204 -11492
- package/dist/host/{chunk-LSWB3ZZV.js → chunk-T2PZWXFJ.js} +6 -1
- package/dist/host/chunk-YGYBM7SQ.js +58 -0
- package/dist/host/core-classes.json +1 -1
- package/dist/host/{linked-server-UCW74T2V.js → linked-server-5VFO4XQB.js} +2 -2
- package/dist/host/{rbac-api-6UYMFIFV.js → rbac-api-5YMBJAO5.js} +2 -2
- package/dist/host/testing-host.js +123 -57
- package/dist/host/types/src/types/onboarding.d.ts +0 -2
- package/dist/host/types/src/types/ui-types.d.ts +2 -1
- package/dist/host/types/src/ui/api/audit-log-api.d.ts +36 -0
- package/dist/host/types/src/ui/api/credentials-api.d.ts +0 -22
- package/dist/host/types/src/ui/api/host-defaults-api.d.ts +42 -0
- package/dist/host/types/src/ui/api/host-tags-api.d.ts +2 -0
- package/dist/host/types/src/ui/api/look-api.d.ts +40 -0
- package/dist/host/types/src/ui/api/open-tabs-api.d.ts +4 -0
- package/dist/host/types/src/ui/api/plugins-api.d.ts +30 -0
- package/dist/host/types/src/ui/api/private-endpoints-api.d.ts +6 -0
- package/dist/host/types/src/ui/api/settings-api.d.ts +0 -6
- package/dist/host/types/src/ui/api/ssh-host-management-api.d.ts +0 -28
- package/dist/host/types/src/ui/api/tls-api.d.ts +25 -0
- package/dist/host/types/src/ui/auth/ElectronLoginForm.d.ts +9 -0
- package/dist/host/types/src/ui/components/command.d.ts +78 -0
- package/dist/host/types/src/ui/components/folder-style.d.ts +16 -0
- package/dist/host/types/src/ui/components/theme-provider.d.ts +2 -1
- package/dist/host/types/src/ui/components/version-badge.d.ts +7 -0
- package/dist/host/types/src/ui/hooks/use-page-visible-interval.d.ts +10 -0
- package/dist/host/types/src/ui/lib/activity-types.d.ts +15 -0
- package/dist/host/types/src/ui/lib/app-version.d.ts +2 -0
- package/dist/host/types/src/ui/lib/apply-ui-preset.d.ts +19 -0
- package/dist/host/types/src/ui/lib/create-id.d.ts +2 -0
- package/dist/host/types/src/ui/lib/database-transfer-url.d.ts +8 -0
- package/dist/host/types/src/ui/lib/desktop-updates.d.ts +29 -0
- package/dist/host/types/src/ui/lib/host-connection-tabs.d.ts +14 -0
- package/dist/host/types/src/ui/lib/host-to-ssh-host.d.ts +4 -0
- package/dist/host/types/src/ui/lib/issue-url.d.ts +21 -0
- package/dist/host/types/src/ui/lib/local-adaptive-preferences.d.ts +9 -0
- package/dist/host/types/src/ui/lib/pane-nav-hotkey.d.ts +7 -0
- package/dist/host/types/src/ui/lib/plugin-i18n.d.ts +4 -3
- package/dist/host/types/src/ui/lib/role-label.d.ts +3 -0
- package/dist/host/types/src/ui/look/LookSettings.d.ts +8 -0
- package/dist/host/types/src/ui/look/layout-registry.d.ts +34 -0
- package/dist/host/types/src/ui/look/look-store.d.ts +29 -0
- package/dist/host/types/src/ui/look/resolve-look.d.ts +18 -0
- package/dist/host/types/src/ui/look/theme-css.d.ts +7 -0
- package/dist/host/types/src/ui/look/theme-registry.d.ts +41 -0
- package/dist/host/types/src/ui/look/use-look-theme.d.ts +12 -0
- package/dist/host/types/src/ui/main-axios.d.ts +2 -2
- package/dist/host/types/src/ui/manage/manage-requests.d.ts +34 -0
- package/dist/host/types/src/ui/onboarding/onboarding-registry.d.ts +3 -1
- package/dist/host/types/src/ui/plugin-host/loader.d.ts +2 -0
- package/dist/host/types/src/ui/plugin-host/scope.d.ts +6 -0
- package/dist/host/types/src/ui/plugin-host/sdk-ui.d.ts +101 -0
- package/dist/host/types/src/ui/plugin-host/view-ownership.d.ts +1 -1
- package/dist/host/types/src/ui/plugins/PluginConsentPrompt.d.ts +20 -0
- package/dist/host/types/src/ui/plugins/PluginDetail.d.ts +7 -0
- package/dist/host/types/src/ui/plugins/PluginReleaseNotes.d.ts +14 -0
- package/dist/host/types/src/ui/plugins/PluginsTab.d.ts +1 -0
- package/dist/host/types/src/ui/plugins/RegistriesSection.d.ts +4 -0
- package/dist/host/types/src/ui/plugins/plugin-bits.d.ts +23 -0
- package/dist/host/types/src/ui/plugins/plugin-model.d.ts +136 -0
- package/dist/host/types/src/ui/plugins/use-plugins-manager.d.ts +78 -0
- package/dist/host/types/src/ui/settings/appearance/appearance-controls.d.ts +53 -0
- package/dist/host/types/src/ui/settings/sync/LinkServerDialog.d.ts +12 -0
- package/dist/host/types/src/ui/settings/sync/ProxySettingsFields.d.ts +15 -0
- package/dist/host/types/src/ui/settings/sync/saved-servers.d.ts +3 -0
- package/dist/host/types/src/ui/shell/controller/ShellControllerProvider.d.ts +20 -0
- package/dist/host/types/src/ui/shell/controller/plugin-shell.d.ts +7 -0
- package/dist/host/types/src/ui/shell/controller/shell-store.d.ts +4 -0
- package/dist/host/types/src/ui/shell/controller/types.d.ts +156 -0
- package/dist/host/types/src/ui/shell/controller/use-hosts.d.ts +10 -0
- package/dist/host/types/src/ui/shell/controller/use-regions.d.ts +5 -0
- package/dist/host/types/src/ui/shell/controller/use-shell-keybindings.d.ts +12 -0
- package/dist/host/types/src/ui/shell/controller/use-shell-lifecycle.d.ts +17 -0
- package/dist/host/types/src/ui/shell/controller/use-split.d.ts +21 -0
- package/dist/host/types/src/ui/shell/controller/use-tabs.d.ts +70 -0
- package/dist/host/types/src/ui/shell/controller/use-workspace-layout.d.ts +4 -0
- package/dist/host/types/src/ui/shell/reconnect-tabs.d.ts +6 -0
- package/dist/host/types/src/ui/shell/shell-layout.d.ts +46 -0
- package/dist/host/types/src/ui/shell/split/EmptyPanePicker.d.ts +32 -0
- package/dist/host/types/src/ui/shell/split/SplitDropOverlay.d.ts +9 -0
- package/dist/host/types/src/ui/shell/split/SplitLayoutMenu.d.ts +16 -0
- package/dist/host/types/src/ui/shell/split/SplitView.d.ts +31 -0
- package/dist/host/types/src/ui/shell/split/split-drag.d.ts +58 -0
- package/dist/host/types/src/ui/shell/split/split-presets.d.ts +28 -0
- package/dist/host/types/src/ui/shell/split/split-tabs.d.ts +95 -0
- package/dist/host/types/src/ui/shell/split/split-targets.d.ts +30 -0
- package/dist/host/types/src/ui/shell/split/split-tree.d.ts +89 -0
- package/dist/host/types/src/ui/shell/stage/TabStage.d.ts +9 -0
- package/dist/host/types/src/ui/shell/stage/tab-stage-store.d.ts +24 -0
- package/dist/host/types/src/ui/shell/tab-close-guards.d.ts +9 -0
- package/dist/host/types/src/ui/shell/tab-handles.d.ts +8 -0
- package/dist/host/types/src/ui/shell/tabUtils.d.ts +15 -0
- package/dist/host/types/src/ui/sidebar/AuthEnrollmentSections.d.ts +5 -0
- package/dist/host/types/src/ui/sidebar/HostManagerData.d.ts +4 -0
- package/dist/host/types/src/ui/sidebar/build-host-tree.d.ts +25 -0
- package/dist/host/types/src/ui/sidebar/host-contributions.d.ts +10 -0
- package/dist/host/ui.js +6045 -4055
- package/dist/host/{use-permissions-GYCBY2AU.js → use-permissions-ISEDRY3J.js} +2 -2
- package/dist/manifest.d.ts +50 -1
- package/dist/manifest.js +104 -1
- package/dist/ssh-keys.d.ts +29 -0
- package/dist/ssh-keys.js +85 -0
- package/dist/testing/setup.js +1 -0
- package/dist/testing.d.ts +32 -15
- package/dist/testing.js +218 -1
- package/dist/theme-css.d.ts +11 -0
- package/dist/theme-css.js +142 -0
- package/dist/vitest-preset.d.ts +18 -0
- package/dist/vitest-preset.js +14 -3
- package/package.json +5 -1
- package/schema/manifest.schema.json +121 -8
- package/tsconfig.plugin.json +1 -0
- package/dist/host/chunk-YC2MAKSP.js +0 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.13
|
|
4
|
+
|
|
5
|
+
Plugin API 1.5.0.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `runtime: "desktop"` in the manifest for a plugin that only loads on the desktop app
|
|
10
|
+
- `desktop:local` and `ctx.desktop.local`: file system, local shell and machine info on the desktop app's own backend
|
|
11
|
+
- `ctx.audit.subscribe`, `ctx.audit.list` and `ctx.audit.exportBatches` under `audit:read`
|
|
12
|
+
- `ctx.hosts.importMany`, `importSshConfig`, `exportMany` and `registerImportNormalizer`, with the `hosts:export` capability
|
|
13
|
+
- `ctx.credentials.publicKey` and `resolveKeyRefs`, `ctx.ssh.exec`, and `@termix-ssh/plugin-sdk/ssh-keys`
|
|
14
|
+
- `ctx.hosts.status.provide` and `ctx.hosts.activity.provide` so a plugin can own host status and recent activity, with `ctx.network.tcpProbe`, `ctx.ssh.probe`, `ctx.hosts.probeTargets` and `ctx.hosts.sessionActive`
|
|
15
|
+
- `registerHostStatusIndicator` and `registerHostEditorToolbarItem`, and `order` for host badges
|
|
16
|
+
- Onboarding steps can sit in a `start` section, take a `position` and `replaces` a core step
|
|
17
|
+
- A plugin can provide the app name, tagline and logo through the `<id>.brandingProvider` registry key
|
|
18
|
+
|
|
19
|
+
## 1.0.12
|
|
20
|
+
|
|
21
|
+
Plugin API 1.4.0.
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- `contributes.layouts` and `app.registerLayout()` for a whole-app layout, with the `ui:layout` capability. Needs `engine.api` 1.4
|
|
26
|
+
- `contributes.themes` for CSS-only themes, with the `ui:theme` capability. `termix-plugin validate` checks theme CSS and `pack` ships it
|
|
27
|
+
- `useShell()` gives a layout the live shell: tabs, splits, hosts, docks, the command palette and the registries. The shell's home tab is left out until some plugin registers a `dashboard` tab type
|
|
28
|
+
- `TabStage` and `ShellConfirmCapture` in `@termix-ssh/plugin-sdk/ui`, for a layout to place tabs
|
|
29
|
+
- `validateThemeCss()` in `@termix-ssh/plugin-sdk/manifest`
|
|
30
|
+
- `@termix-ssh/plugin-sdk/ui` also exports what a whole layout is built from: the API client, shared types, hooks, `useShellController()` and `APP_VERSION`. These reach into core, so they can change with a core release
|
|
31
|
+
- `@termix-ssh/plugin-sdk/testing-host` exports the registries and permission seam a layout's own tests fill directly
|
|
32
|
+
- A plugin that only holds themes needs no source: `termix-plugin build` writes an empty backend and `validate` does not ask for a frontend
|
|
33
|
+
- `termix-plugin validate` flags `ui:layout` or `ui:theme` with nothing contributed, and a layout plugin with no frontend
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- The manifest JSON schema lists the `notify:hub` and `plugins:read` capabilities
|
|
38
|
+
- Plugin tests and type checks work with the SDK linked from a local Termix checkout
|
|
39
|
+
|
|
3
40
|
## 1.0.11
|
|
4
41
|
|
|
5
42
|
Plugin API 1.3.0.
|
package/PLUGIN-GUIDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Termix plugin guide
|
|
2
2
|
|
|
3
|
-
SDK 1.0.
|
|
3
|
+
SDK 1.0.13 | Plugin API 1.5.0
|
|
4
4
|
|
|
5
5
|
This guide is for anyone, person or AI coding tool, building a Termix plugin. It ships inside
|
|
6
6
|
`@termix-ssh/plugin-sdk`, so the copy in your `node_modules` always matches the SDK you have installed.
|
|
@@ -164,7 +164,7 @@ Unknown fields are refused at every level, so a typo fails validation. The schem
|
|
|
164
164
|
| `icon` | A [Lucide](https://lucide.dev/icons/) icon name, like `Hand`. |
|
|
165
165
|
| `features` | Up to 20 short lines shown on the plugin's page. |
|
|
166
166
|
| `docs` | An https link to your docs. |
|
|
167
|
-
| `engine.termix` | Termix versions you work with, like `>=26.10.0`.
|
|
167
|
+
| `engine.termix` | Termix versions you work with, like `>=26.10.0`. Older Termix builds don't list a release they can't run. |
|
|
168
168
|
| `engine.api` | Plugin API range, like `^1.2`. A major bump of the API is breaking. |
|
|
169
169
|
| `capabilities` | What your code may do. See the reference at the end. |
|
|
170
170
|
| `dependencies`, `optionalDependencies` | Other plugins, `{ "snippets": "^1.0.0" }`. You must keep working without optional ones. |
|
|
@@ -175,7 +175,7 @@ Unknown fields are refused at every level, so a typo fails validation. The schem
|
|
|
175
175
|
|
|
176
176
|
`contributes` holds `tabs`, `panels`, `dashboardCards`, `actions`, `actionSlots`, `permissions`,
|
|
177
177
|
`settings`, `protocols`, `auth`, `syncEntities`, `keybindingActions`, `http`, `guest`, `guestViews`,
|
|
178
|
-
`uiPresets` and `
|
|
178
|
+
`uiPresets`, `uses`, `layouts` and `themes`. Frontend `register*` calls need their ids declared here first.
|
|
179
179
|
|
|
180
180
|
Capabilities and permissions are different things. A capability (`hosts:read`, with a colon) is what your
|
|
181
181
|
code may reach. A permission (`hello.use`, with a dot) is what a user may do.
|
|
@@ -325,6 +325,24 @@ try {
|
|
|
325
325
|
commands. `@termix-ssh/plugin-sdk/host-commands` has helpers to detect the OS and run package manager and
|
|
326
326
|
sudo commands. Hosts from `ctx.hosts` never carry secrets. Pass the host or its id to `ctx.ssh`.
|
|
327
327
|
|
|
328
|
+
### The desktop app
|
|
329
|
+
|
|
330
|
+
Set `"runtime": "desktop"` in the manifest for a plugin that only makes sense on a desktop app. It never
|
|
331
|
+
loads on a server, ignores server sync and may declare `desktop:local`. Any plugin can declare
|
|
332
|
+
`desktop:local` and check `ctx.desktop.local.available()` at runtime.
|
|
333
|
+
|
|
334
|
+
`ctx.desktop.local` works on the desktop app's own backend only:
|
|
335
|
+
|
|
336
|
+
- `info()`: home folder, platform, user data folder, backend port and the linked server (url and token) or null
|
|
337
|
+
- `fs`: `list`, `mkdir`, `createFile`, `rename`, `ensureDir`, `exists`, `walk`, `trash`, `reveal`, `open`,
|
|
338
|
+
`openRead` and `openDownload`. Downloads stay inside the folder the user picked, links in it are refused and
|
|
339
|
+
an existing file is never replaced by accident
|
|
340
|
+
- `pty.spawn()`: a local shell, stopped when the plugin stops
|
|
341
|
+
- `pluginFetch(origin, subPath, init)`: calls this plugin's own routes on the desktop's backend ("local") or the linked server ("remote") as the acting user. Bodies can be streams, so a plugin can move big files without the renderer
|
|
342
|
+
|
|
343
|
+
Use `ctx.audit.subscribe`, `list` and `exportBatches` (`audit:read`) to read the audit trail. Subscribers
|
|
344
|
+
are called after a line is accepted and never delay the action that wrote it.
|
|
345
|
+
|
|
328
346
|
### Events, timers, alerts, services
|
|
329
347
|
|
|
330
348
|
```ts
|
|
@@ -470,22 +488,35 @@ your plugin's error boundary and Suspense.
|
|
|
470
488
|
| `registerHostEditorSection` | Your own host editor section (host settings get one for free) |
|
|
471
489
|
| `registerHostProtocol` | A connection protocol next to SSH |
|
|
472
490
|
| `registerHostBadge`, `registerHostContextMenuItem` | Badges and menu items on host rows |
|
|
491
|
+
| `registerHostStatusIndicator` | The status dot or stripe on a host row (a layout draws it) |
|
|
492
|
+
| `registerHostEditorToolbarItem` | A control in the host editor toolbar (a layout draws it) |
|
|
473
493
|
| `registerPaletteEntry`, `registerPaletteGroup` | Command palette entries |
|
|
474
494
|
| `registerKeybindingAction`, `registerKeybindingDefault` | Bindable actions |
|
|
475
495
|
| `registerSettingsComponent` | A component for a `custom` settings field |
|
|
476
496
|
| `registerAction`, `invokeAction` | Actions across plugins |
|
|
477
497
|
| `registerSlotContribution`, `declareActionSlot` | Fill or own a UI slot |
|
|
478
498
|
| `registerComponent`, `registerExtension` | Offer a component by id, add to an extension point |
|
|
479
|
-
| `registerOnboardingStep` | A first run setup step
|
|
499
|
+
| `registerOnboardingStep` | A first run setup step (`section`, `position`, `replaces`) |
|
|
480
500
|
| `registerLoginMethod`, `registerSecondFactorUI`, `registerSshAuthEditor` | Sign in UI |
|
|
501
|
+
| `registerLayout` | A whole-app layout (`contributes.layouts`, API 1.4) |
|
|
481
502
|
| `api` | Axios client for your `/plugin-api/<id>/` routes, auth included |
|
|
482
503
|
| `fetch(path)`, `wsUrl(path)` | Raw fetch for streams, socket URL and auth |
|
|
483
504
|
| `docs`, `tabs`, `confirm`, `listHosts`, `getHost`, `t`, `hasPermission`, `desktop` | Helpers |
|
|
484
505
|
| `onSettingsChanged`, `onDispose` | Hooks |
|
|
485
506
|
|
|
507
|
+
Badges, status indicators and toolbar items take `{ id, when(host), component, order? }`; lower `order`
|
|
508
|
+
comes first. An onboarding step sits in a section (`start`, `setup`, `explore`, `security`) or at an exact
|
|
509
|
+
`position` from 1 to 99. `replaces: ["preset"]` lists older step keys the step took over, so people who saw
|
|
510
|
+
those are not shown it again.
|
|
511
|
+
|
|
512
|
+
Branding: a plugin that owns the app name, tagline and logo provides
|
|
513
|
+
`ctx.registry.provide("<id>.brandingProvider", async () => ({ appName, tagline, logo }))` in its backend.
|
|
514
|
+
Core reads it for `GET /users/branding` (the login page, the browser tab and the sync server name) and falls
|
|
515
|
+
back to "Termix" with no logo when no plugin provides one. `logo` is a PNG data URL or null.
|
|
516
|
+
|
|
486
517
|
Hooks from `@termix-ssh/plugin-sdk/frontend`: `useTranslation`, `usePermission`, `useSettings`, `useHost`,
|
|
487
518
|
`useHosts`, `usePluginApi`, `useCurrentUser`, `useTheme`, `useToast`, `useDocsUrl`, `useTabs`,
|
|
488
|
-
`useSlotContributions`, `useSshAuthTypes`, `useExtensions`.
|
|
519
|
+
`useSlotContributions`, `useSshAuthTypes`, `useExtensions`, `useShell`.
|
|
489
520
|
|
|
490
521
|
Shared from Termix and never bundled: `react`, `react-dom`, `i18next`, `react-i18next`, `sonner` and the
|
|
491
522
|
SDK's `frontend` and `ui`. Anything else, like `lucide-react`, is bundled. A host your code gets only carries
|
|
@@ -496,6 +527,91 @@ Slots you can fill include `terminal.toolbar`, `terminal.toolbarStatus`, `termin
|
|
|
496
527
|
`hosts.panel`, `credentials.badges`, `shell.overlay`, `shell.rightEdge` and `tab.menu`. See
|
|
497
528
|
https://docs.termix.site/develop/more-extension-points.
|
|
498
529
|
|
|
530
|
+
## Layouts and themes
|
|
531
|
+
|
|
532
|
+
A plugin can replace the whole app layout or add themes (API 1.4). Users pick both in Settings, Appearance;
|
|
533
|
+
admins set the default for everyone.
|
|
534
|
+
|
|
535
|
+
A layout is declared in `contributes.layouts` (`id`, `label`, optional `description` and `defaultTheme`) and
|
|
536
|
+
needs the `ui:layout` capability, which admins see as high risk because a layout draws every screen. Register
|
|
537
|
+
it in `activate`:
|
|
538
|
+
|
|
539
|
+
```tsx
|
|
540
|
+
import { useShell, type TermixApp } from "@termix-ssh/plugin-sdk/frontend";
|
|
541
|
+
import {
|
|
542
|
+
ShellConfirmCapture,
|
|
543
|
+
SurfaceScope,
|
|
544
|
+
TabStage,
|
|
545
|
+
} from "@termix-ssh/plugin-sdk/ui";
|
|
546
|
+
|
|
547
|
+
function MyLayout() {
|
|
548
|
+
const shell = useShell();
|
|
549
|
+
if (!shell) return null;
|
|
550
|
+
return (
|
|
551
|
+
<div className="flex h-dvh w-screen flex-col bg-background">
|
|
552
|
+
<header>
|
|
553
|
+
{/* tabs from shell.tabs.topLevel, shell.palette.open() and so on */}
|
|
554
|
+
</header>
|
|
555
|
+
<SurfaceScope kind="tab">
|
|
556
|
+
<ShellConfirmCapture />
|
|
557
|
+
<TabStage />
|
|
558
|
+
</SurfaceScope>
|
|
559
|
+
</div>
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export function activate(app: TermixApp) {
|
|
564
|
+
app.registerLayout({
|
|
565
|
+
id: "my-layout",
|
|
566
|
+
label: "layout.name",
|
|
567
|
+
component: MyLayout,
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
- `useShell()` is the live shell with nothing drawn: tabs, splits, hosts, the left and right docks, the
|
|
573
|
+
command palette, the registries as hooks and the user. Core keeps all the state, so switching layouts keeps
|
|
574
|
+
every session.
|
|
575
|
+
- `TabStage` is where tabs show. Place it anywhere; tabs are never remounted when it moves, so a terminal
|
|
576
|
+
keeps its connection. Wrap it in `SurfaceScope kind="tab"` with a `ShellConfirmCapture` inside, so
|
|
577
|
+
close questions and inline views show over the tab area. Overlays and onboarding stay in core.
|
|
578
|
+
- The layout draws the command palette. Core keeps whether it is open (`palette.isOpen`, Ctrl+K calls
|
|
579
|
+
`palette.open()`) and what is in it (`usePaletteEntries`, `usePaletteGroups`, rail items, hosts).
|
|
580
|
+
- The shell always keeps a home tab with the id and type `dashboard`. `useShell()` leaves it out until some
|
|
581
|
+
plugin registers a `dashboard` tab type, so a layout without a home screen has no empty tab to draw.
|
|
582
|
+
- Settings, Manage and the host editor belong to Classic Layout. A layout without them should still render
|
|
583
|
+
`LookSettings` somewhere so people can switch back. `tabs.openSingleton("plugins")` opens core's own
|
|
584
|
+
Plugins tab in any layout. Switching layouts closes the tabs the old layout's plugin declared.
|
|
585
|
+
- `mobileComponent` is used on phones and narrow windows. The desktop app always gets `component`.
|
|
586
|
+
- Only the chosen layout's plugin is loaded. If it fails to load or crashes twice, Termix shows a repair
|
|
587
|
+
screen where people can switch, reset, update, turn it off or (for a bundled plugin) restore the copy that
|
|
588
|
+
shipped. `?safe-layout=1` opens it any time.
|
|
589
|
+
- Termix has no layout of its own. The default is the bundled Classic Layout plugin
|
|
590
|
+
(https://github.com/Termix-SSH/Plugin-Classic-Layout), which is also a full example. It owns the rail,
|
|
591
|
+
sidebar, tab bar, docks, Dashboard, Manage, Settings and command palette, registers the `dashboard`,
|
|
592
|
+
`host-manager` and `settings` tab types, and offers the host list and dashboard as the components
|
|
593
|
+
`classic-layout.hostTree` and `classic-layout.dashboard` for other plugins to place with `PluginComponent`.
|
|
594
|
+
It reads the shell with `useShellController()` from `@termix-ssh/plugin-sdk/ui`, which exposes more than
|
|
595
|
+
`useShell()` and may change with Termix. A third party layout should start from `useShell()`.
|
|
596
|
+
|
|
597
|
+
A theme is CSS only. Declare it in `contributes.themes` (`id`, `label`, `mode` light or dark, `css` path,
|
|
598
|
+
optional `swatch` colors for the picker) with the `ui:theme` capability. The id becomes a class on `<html>`,
|
|
599
|
+
so the file may only hold rules whose selectors start with `.<id>`, `:root.<id>` or `html.<id>`, optionally
|
|
600
|
+
inside `@media` or `@supports`. `@import`, `url()` to another host and other at-rules are refused by
|
|
601
|
+
`termix-plugin validate` and by the server. Set the same tokens core's themes set (`--background`,
|
|
602
|
+
`--foreground`, `--card`, `--border`, `--muted` and the rest).
|
|
603
|
+
|
|
604
|
+
A plugin that only holds themes needs no code. Leave out `src/`: `termix-plugin build` writes an empty backend and
|
|
605
|
+
`validate` does not ask for a frontend. Keep the CSS in `themes/` and a test that checks it with
|
|
606
|
+
`validateThemeCss()`. Notes:
|
|
607
|
+
|
|
608
|
+
- A theme with the id of a built-in (`light` or `dark`) replaces core's basic copy while the plugin runs.
|
|
609
|
+
- The first theme a bundled default-look plugin lists is the default for new installs.
|
|
610
|
+
- Themes show in the picker in the order core lists its own, then the plugin's order.
|
|
611
|
+
- A theme someone saved that no longer exists shows as System. It comes back when the plugin does.
|
|
612
|
+
- Accent color, UI font, font size and density are not part of a theme. Core applies them on top.
|
|
613
|
+
- Bundled Classic Themes (https://github.com/Termix-SSH/Plugin-Classic-Themes) is a full example.
|
|
614
|
+
|
|
499
615
|
## Looking native
|
|
500
616
|
|
|
501
617
|
Build UI from `@termix-ssh/plugin-sdk/ui` so it matches Termix and follows the theme. The full list of
|
|
@@ -742,58 +858,66 @@ To run your own registry instead, see https://docs.termix.site/develop/registrie
|
|
|
742
858
|
|
|
743
859
|
## SDK entry points
|
|
744
860
|
|
|
745
|
-
| Import | What it is
|
|
746
|
-
| -------------------------------------------------------------------- |
|
|
747
|
-
| `@termix-ssh/plugin-sdk/backend` | `PluginContext` and the backend types
|
|
748
|
-
| `@termix-ssh/plugin-sdk/frontend` | `TermixApp`, `TabProps` and the hooks
|
|
749
|
-
| `@termix-ssh/plugin-sdk/ui` | The UI kit, served by Termix at runtime
|
|
750
|
-
| `@termix-ssh/plugin-sdk/db` | `defineTable` and column builders
|
|
751
|
-
| `@termix-ssh/plugin-sdk/manifest` | `PluginManifest` type and manifest checks
|
|
752
|
-
| `@termix-ssh/plugin-sdk/capabilities` | The capability catalog
|
|
753
|
-
| `@termix-ssh/plugin-sdk/settings` | Settings field validation
|
|
754
|
-
| `@termix-ssh/plugin-sdk/testing` | `createMockCtx`, `createTestDb`, `renderWithApp`, `createFakeContext`
|
|
755
|
-
| `@termix-ssh/plugin-sdk/vitest-preset` | `pluginVitestConfig`
|
|
756
|
-
| `@termix-ssh/plugin-sdk/host-commands` | OS detection, package manager and sudo helpers over ssh2
|
|
757
|
-
| `@termix-ssh/plugin-sdk/ssh-certs` | `applyCertificateAuth()` for certificate based SSH auth
|
|
758
|
-
| `@termix-ssh/plugin-sdk/
|
|
759
|
-
| `@termix-ssh/plugin-sdk/
|
|
760
|
-
| `@termix-ssh/plugin-sdk/
|
|
761
|
-
| `@termix-ssh/plugin-sdk/
|
|
762
|
-
| `@termix-ssh/plugin-sdk/
|
|
861
|
+
| Import | What it is |
|
|
862
|
+
| -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
863
|
+
| `@termix-ssh/plugin-sdk/backend` | `PluginContext` and the backend types |
|
|
864
|
+
| `@termix-ssh/plugin-sdk/frontend` | `TermixApp`, `TabProps` and the hooks |
|
|
865
|
+
| `@termix-ssh/plugin-sdk/ui` | The UI kit, served by Termix at runtime |
|
|
866
|
+
| `@termix-ssh/plugin-sdk/db` | `defineTable` and column builders |
|
|
867
|
+
| `@termix-ssh/plugin-sdk/manifest` | `PluginManifest` type and manifest checks |
|
|
868
|
+
| `@termix-ssh/plugin-sdk/capabilities` | The capability catalog |
|
|
869
|
+
| `@termix-ssh/plugin-sdk/settings` | Settings field validation |
|
|
870
|
+
| `@termix-ssh/plugin-sdk/testing` | `createMockCtx`, `createTestDb`, `renderWithApp`, `createFakeContext` |
|
|
871
|
+
| `@termix-ssh/plugin-sdk/vitest-preset` | `pluginVitestConfig` |
|
|
872
|
+
| `@termix-ssh/plugin-sdk/host-commands` | OS detection, package manager and sudo helpers over ssh2 |
|
|
873
|
+
| `@termix-ssh/plugin-sdk/ssh-certs` | `applyCertificateAuth()` for certificate based SSH auth |
|
|
874
|
+
| `@termix-ssh/plugin-sdk/ssh-keys` | `generateSshKeyPair()`, `detectSshKeyType()`, `publicKeyFromPrivate()`, `validateSshKeyPair()` |
|
|
875
|
+
| `@termix-ssh/plugin-sdk/desktop` | The desktop app bridge, typed. Check it exists before use |
|
|
876
|
+
| `@termix-ssh/plugin-sdk/openapi` | `buildOpenApi()` |
|
|
877
|
+
| `@termix-ssh/plugin-sdk/changelog` | Changelog parsing and checks |
|
|
878
|
+
| `@termix-ssh/plugin-sdk/docs` | Docs link and env var helpers |
|
|
879
|
+
| `@termix-ssh/plugin-sdk/ddl`, `@termix-ssh/plugin-sdk/table-builder` | Table to SQL and Drizzle mapping. You rarely need these |
|
|
763
880
|
|
|
764
881
|
## Capabilities
|
|
765
882
|
|
|
766
|
-
| Capability
|
|
767
|
-
|
|
|
768
|
-
| `credentials:read`
|
|
769
|
-
| `system:tls`
|
|
770
|
-
| `ssh:connect`
|
|
771
|
-
| `process:spawn`
|
|
772
|
-
| `users:write`
|
|
773
|
-
| `users:impersonate`
|
|
774
|
-
| `db:core-refs`
|
|
775
|
-
| `auth:provide`
|
|
776
|
-
| `device:serial`
|
|
777
|
-
| `
|
|
778
|
-
| `hosts:
|
|
779
|
-
| `
|
|
780
|
-
| `
|
|
781
|
-
| `
|
|
782
|
-
| `
|
|
783
|
-
| `
|
|
784
|
-
| `
|
|
785
|
-
| `
|
|
786
|
-
| `
|
|
787
|
-
| `
|
|
788
|
-
| `
|
|
789
|
-
| `
|
|
790
|
-
| `
|
|
791
|
-
| `
|
|
792
|
-
| `
|
|
793
|
-
| `
|
|
794
|
-
| `
|
|
795
|
-
| `
|
|
796
|
-
| `
|
|
883
|
+
| Capability | Risk | What it allows |
|
|
884
|
+
| ------------------------ | -------- | ------------------------------------------------------- |
|
|
885
|
+
| `credentials:read` | critical | Read stored secrets for hosts the user can reach |
|
|
886
|
+
| `system:tls` | critical | Replace the HTTPS certificate, answer CA domain checks |
|
|
887
|
+
| `ssh:connect` | high | Run any command on hosts, with the user's access |
|
|
888
|
+
| `process:spawn` | high | Run programs on the Termix server |
|
|
889
|
+
| `users:write` | high | Add users and change their roles |
|
|
890
|
+
| `users:impersonate` | high | Act as another user, or get a session for them, audited |
|
|
891
|
+
| `db:core-refs` | high | Query core users, roles and hosts tables (read only) |
|
|
892
|
+
| `auth:provide` | high | Add login methods, second factors, SSH auth types |
|
|
893
|
+
| `device:serial` | high | Open serial and USB ports on the server |
|
|
894
|
+
| `desktop:local` | high | Use the user's own computer, desktop app only |
|
|
895
|
+
| `hosts:export` | high | Export hosts with their saved logins |
|
|
896
|
+
| `notify:hub` | high | Receive every alert (the alerts hub only) |
|
|
897
|
+
| `ui:layout` | high | Replace the whole app layout |
|
|
898
|
+
| `hosts:write` | medium | Add and edit hosts |
|
|
899
|
+
| `hosts:status-provide` | medium | Be the source of host status dots |
|
|
900
|
+
| `hosts:activity-provide` | medium | Be the source of the recent activity list |
|
|
901
|
+
| `network:probe` | medium | Check that a host answers on a TCP port |
|
|
902
|
+
| `credentials:use` | medium | Connect with saved credentials without seeing them |
|
|
903
|
+
| `credentials:write` | medium | Save new credentials |
|
|
904
|
+
| `network:outbound` | medium | Outbound HTTP through `ctx.fetch` |
|
|
905
|
+
| `network:serve` | medium | HTTP routes and sockets |
|
|
906
|
+
| `network:broadcast` | medium | Local network broadcasts, like Wake-on-LAN |
|
|
907
|
+
| `users:read` | medium | Usernames, roles and UI language |
|
|
908
|
+
| `events:core` | medium | Listen to core events |
|
|
909
|
+
| `notify:send` | medium | Send alerts |
|
|
910
|
+
| `audit:read` | medium | Read the audit log |
|
|
911
|
+
| `desktop:window` | medium | Open windows in the desktop app |
|
|
912
|
+
| `hosts:read` | low | Read hosts the user can see |
|
|
913
|
+
| `db:own` | low | Your own tables |
|
|
914
|
+
| `kv:own` | low | Small key value state |
|
|
915
|
+
| `files:own` | low | Your own folder on disk |
|
|
916
|
+
| `secrets:own` | low | Your own encrypted secrets |
|
|
917
|
+
| `settings:read-core` | low | Read Termix server settings |
|
|
918
|
+
| `plugins:read` | low | See installed plugins |
|
|
919
|
+
| `ui:surface` | low | Add tabs, panels and settings |
|
|
920
|
+
| `ui:theme` | low | Add color themes, CSS only |
|
|
797
921
|
|
|
798
922
|
An update that adds a capability waits for an admin to approve it.
|
|
799
923
|
|
package/cli/commands/build.mjs
CHANGED
|
@@ -5,7 +5,12 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
import * as esbuild from "esbuild";
|
|
6
6
|
import { BACKEND_EXTERNALS, FRONTEND_EXTERNALS } from "../lib/externals.mjs";
|
|
7
7
|
import { staticUrlImports } from "../lib/static-url-imports.mjs";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
readManifest,
|
|
10
|
+
resolveEntry,
|
|
11
|
+
copyDir,
|
|
12
|
+
isThemeOnly,
|
|
13
|
+
} from "../lib/plugin-dir.mjs";
|
|
9
14
|
import { openapi } from "./openapi.mjs";
|
|
10
15
|
import { splitByCoreClasses } from "../lib/css-classes.mjs";
|
|
11
16
|
|
|
@@ -232,32 +237,41 @@ export async function build({ cwd }) {
|
|
|
232
237
|
fs.mkdirSync(outDir, { recursive: true });
|
|
233
238
|
|
|
234
239
|
const backendEntry = resolveEntry(cwd, BACKEND_ENTRIES);
|
|
235
|
-
if (!backendEntry) {
|
|
240
|
+
if (!backendEntry && !isThemeOnly(manifest)) {
|
|
236
241
|
throw new Error(
|
|
237
242
|
`${pluginId}: no backend entry (looked for ${BACKEND_ENTRIES.join(", ")})`,
|
|
238
243
|
);
|
|
239
244
|
}
|
|
240
245
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
246
|
+
if (backendEntry) {
|
|
247
|
+
await esbuild.build({
|
|
248
|
+
entryPoints: [backendEntry],
|
|
249
|
+
outfile: path.join(outDir, "backend.js"),
|
|
250
|
+
bundle: true,
|
|
251
|
+
format: "esm",
|
|
252
|
+
platform: "node",
|
|
253
|
+
target: "node22",
|
|
254
|
+
sourcemap: true,
|
|
255
|
+
logLevel: "warning",
|
|
256
|
+
// A native dependency's compiled .node binary is resolved by the
|
|
257
|
+
// package's own relative paths, which break once esbuild inlines its JS
|
|
258
|
+
// elsewhere. Declaring it in nativeDependencies keeps it a real
|
|
259
|
+
// node_modules import instead, resolved at runtime like a host-provided
|
|
260
|
+
// package.
|
|
261
|
+
external: [...BACKEND_EXTERNALS, ...(manifest.nativeDependencies ?? [])],
|
|
262
|
+
loader: BINARY_LOADERS,
|
|
263
|
+
// Bundled CJS deps still call require() for Node builtins at runtime.
|
|
264
|
+
// ESM has no ambient require, so give esbuild's require shim a real one.
|
|
265
|
+
banner: { js: REQUIRE_BANNER },
|
|
266
|
+
});
|
|
267
|
+
} else {
|
|
268
|
+
// Core loads every plugin through a backend entry, so a theme only
|
|
269
|
+
// plugin gets an empty one.
|
|
270
|
+
fs.writeFileSync(
|
|
271
|
+
path.join(outDir, "backend.js"),
|
|
272
|
+
"export function activate() {}\n",
|
|
273
|
+
);
|
|
274
|
+
}
|
|
261
275
|
|
|
262
276
|
for (const worker of readWorkerEntries(cwd, pluginId)) {
|
|
263
277
|
await esbuild.build({
|
package/cli/commands/pack.mjs
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { readManifest } from "../lib/plugin-dir.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
collectFiles,
|
|
6
|
+
createTmxplug,
|
|
7
|
+
themeFiles,
|
|
8
|
+
TMXPLUG_CONTENTS,
|
|
9
|
+
} from "../lib/tmxplug.mjs";
|
|
5
10
|
import { sha256 } from "../lib/signing.mjs";
|
|
6
11
|
import { validate } from "./validate.mjs";
|
|
7
12
|
|
|
@@ -32,7 +37,12 @@ export async function pack({ cwd, args = [] }) {
|
|
|
32
37
|
fs.mkdirSync(outDir, { recursive: true });
|
|
33
38
|
const outfile = path.join(outDir, `${pluginId}-${version}.tmxplug`);
|
|
34
39
|
|
|
35
|
-
const archive = createTmxplug(
|
|
40
|
+
const archive = createTmxplug(
|
|
41
|
+
cwd,
|
|
42
|
+
collectFiles(cwd, [
|
|
43
|
+
...new Set([...TMXPLUG_CONTENTS, ...themeFiles(manifest)]),
|
|
44
|
+
]),
|
|
45
|
+
);
|
|
36
46
|
fs.writeFileSync(outfile, archive);
|
|
37
47
|
|
|
38
48
|
console.log(`packed ${path.basename(outfile)}`);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { readManifest } from "../lib/plugin-dir.mjs";
|
|
4
|
+
import { readManifest, isThemeOnly } from "../lib/plugin-dir.mjs";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* The manifest rules live in src/manifest.ts, which the server uses too, so
|
|
@@ -17,7 +17,7 @@ export async function loadSdkModule(name) {
|
|
|
17
17
|
|
|
18
18
|
export async function validate({ cwd }) {
|
|
19
19
|
const raw = readManifest(cwd);
|
|
20
|
-
const { parseManifest } = await loadSdkModule("manifest");
|
|
20
|
+
const { parseManifest, validateThemeCss } = await loadSdkModule("manifest");
|
|
21
21
|
const { errors } = parseManifest(raw);
|
|
22
22
|
|
|
23
23
|
const problems = [...errors];
|
|
@@ -25,7 +25,8 @@ export async function validate({ cwd }) {
|
|
|
25
25
|
// The manifest names files. They have to be there.
|
|
26
26
|
const referenced = [
|
|
27
27
|
raw.backend ?? "dist/backend.js",
|
|
28
|
-
|
|
28
|
+
// A theme only plugin has no frontend code.
|
|
29
|
+
...(isThemeOnly(raw) ? [] : [raw.frontend ?? "dist/frontend.js"]),
|
|
29
30
|
raw.locales ?? "locales",
|
|
30
31
|
];
|
|
31
32
|
for (const rel of referenced) {
|
|
@@ -34,6 +35,8 @@ export async function validate({ cwd }) {
|
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
problems.push(...validateThemeFiles(cwd, raw, validateThemeCss));
|
|
39
|
+
problems.push(...validateLookCapabilities(cwd, raw));
|
|
37
40
|
problems.push(
|
|
38
41
|
...(await validateMigrations(cwd, raw.id ?? path.basename(cwd))),
|
|
39
42
|
);
|
|
@@ -53,6 +56,63 @@ export async function validate({ cwd }) {
|
|
|
53
56
|
console.log(`ok ${raw.id ?? path.basename(cwd)}`);
|
|
54
57
|
}
|
|
55
58
|
|
|
59
|
+
/** Every declared theme file exists and holds only CSS a theme may hold. */
|
|
60
|
+
export function validateThemeFiles(cwd, raw, validateThemeCss) {
|
|
61
|
+
const problems = [];
|
|
62
|
+
const themes = raw?.contributes?.themes;
|
|
63
|
+
if (!Array.isArray(themes)) return problems;
|
|
64
|
+
for (const theme of themes) {
|
|
65
|
+
if (typeof theme?.css !== "string" || typeof theme?.id !== "string") {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const file = path.join(cwd, theme.css);
|
|
69
|
+
if (!fs.existsSync(file)) {
|
|
70
|
+
problems.push(`theme ${theme.id}: ${theme.css} does not exist`);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
for (const error of validateThemeCss(
|
|
74
|
+
fs.readFileSync(file, "utf8"),
|
|
75
|
+
theme.id,
|
|
76
|
+
)) {
|
|
77
|
+
problems.push(`theme ${theme.id}: ${error}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return problems;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* ui:layout and ui:theme must match what the manifest contributes, both
|
|
85
|
+
* ways. The manifest check already flags a contribution without its
|
|
86
|
+
* capability; this flags a capability with nothing behind it, and a layout
|
|
87
|
+
* with no frontend to register it.
|
|
88
|
+
*/
|
|
89
|
+
export function validateLookCapabilities(cwd, raw) {
|
|
90
|
+
const problems = [];
|
|
91
|
+
const capabilities = Array.isArray(raw?.capabilities) ? raw.capabilities : [];
|
|
92
|
+
const layouts = raw?.contributes?.layouts;
|
|
93
|
+
const themes = raw?.contributes?.themes;
|
|
94
|
+
const hasLayouts = Array.isArray(layouts) && layouts.length > 0;
|
|
95
|
+
const hasThemes = Array.isArray(themes) && themes.length > 0;
|
|
96
|
+
if (capabilities.includes("ui:layout") && !hasLayouts) {
|
|
97
|
+
problems.push('"ui:layout" is declared but contributes.layouts is empty');
|
|
98
|
+
}
|
|
99
|
+
if (capabilities.includes("ui:theme") && !hasThemes) {
|
|
100
|
+
problems.push('"ui:theme" is declared but contributes.themes is empty');
|
|
101
|
+
}
|
|
102
|
+
if (hasLayouts) {
|
|
103
|
+
const entries = ["index.tsx", "index.ts", "index.mjs", "index.js"];
|
|
104
|
+
const frontend = entries.some((entry) =>
|
|
105
|
+
fs.existsSync(path.join(cwd, "src", "frontend", entry)),
|
|
106
|
+
);
|
|
107
|
+
if (!frontend && raw.frontend === undefined) {
|
|
108
|
+
problems.push(
|
|
109
|
+
"contributes.layouts needs a frontend that calls app.registerLayout",
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return problems;
|
|
114
|
+
}
|
|
115
|
+
|
|
56
116
|
/** Env vars core sets for everyone, so a plugin need not list them. */
|
|
57
117
|
const CORE_ENV = new Set([
|
|
58
118
|
"DATA_DIR",
|
package/cli/lib/plugin-dir.mjs
CHANGED
|
@@ -14,6 +14,17 @@ export function readManifest(cwd) {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/** The first of these that exists, or null. */
|
|
17
|
+
/** Only themes: CSS files and no code, so no source entry is needed. */
|
|
18
|
+
export function isThemeOnly(manifest) {
|
|
19
|
+
const capabilities = manifest.capabilities ?? [];
|
|
20
|
+
return (
|
|
21
|
+
capabilities.length > 0 &&
|
|
22
|
+
capabilities.every((capability) => capability === "ui:theme") &&
|
|
23
|
+
Array.isArray(manifest.contributes?.themes) &&
|
|
24
|
+
manifest.contributes.themes.length > 0
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
17
28
|
export function resolveEntry(cwd, candidates) {
|
|
18
29
|
for (const candidate of candidates) {
|
|
19
30
|
const full = path.join(cwd, candidate);
|
package/cli/lib/tmxplug.mjs
CHANGED
|
@@ -2,6 +2,15 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import zlib from "node:zlib";
|
|
4
4
|
|
|
5
|
+
/** Theme CSS files a manifest declares, which travel with the plugin too. */
|
|
6
|
+
export function themeFiles(manifest) {
|
|
7
|
+
const themes = manifest?.contributes?.themes;
|
|
8
|
+
if (!Array.isArray(themes)) return [];
|
|
9
|
+
return themes
|
|
10
|
+
.map((theme) => theme?.css)
|
|
11
|
+
.filter((css) => typeof css === "string");
|
|
12
|
+
}
|
|
13
|
+
|
|
5
14
|
/** What a .tmxplug holds, in the order the spec lists it. */
|
|
6
15
|
export const TMXPLUG_CONTENTS = [
|
|
7
16
|
"manifest.json",
|