@rxtx4816/cockpit-plugin-base-react 1.0.1 → 1.0.3

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 RXTX4816
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # @rxtx4816/cockpit-plugin-base-react
2
+
3
+ Shared foundation for building [Cockpit](https://cockpit-project.org/) plugins with React and PatternFly v6. Extracts the boilerplate that every plugin needs — bootstrapping, i18n, dark theme, async patterns, systemd integration, shared tooling config, and a full QEMU VM test harness — so each plugin only contains its own logic.
4
+
5
+ ## What's included
6
+
7
+ **Plugin runtime**
8
+ - `bootstrapPlugin` — mounts your React app into the Cockpit frame with i18n and error boundary wired up
9
+ - `dark-theme` — side-effect module that automatically syncs the `pf-v6-theme-dark` class with the Cockpit shell, responding to user preference changes and system theme
10
+ - `initCockpitI18n` — sets up i18next with Cockpit's locale loading conventions
11
+
12
+ **Hooks**
13
+ - `useAsyncAction` — wraps an async operation with `loading`, `error`, and `execute` state; ideal for buttons that trigger backend calls
14
+ - `useAutoRefresh` — runs a callback on a configurable interval, with manual refresh support
15
+ - `useAsyncStream` — consumes a Cockpit channel as a line-buffered async stream
16
+ - `useConfirmAction` — multi-step confirmation flow with typed state transitions
17
+ - `usePollingFetch` — fetch with automatic polling, refresh, and loading state
18
+
19
+ **Components**
20
+ - `ConfirmDialog` — confirmation modal driven by `useConfirmAction`, supports multi-step flows
21
+ - `ErrorBoundary` — catches render errors and shows a PatternFly alert with details
22
+ - `HelpPopover` — PatternFly popover for contextual help text
23
+ - `LogViewer` — scrollable terminal-style log display backed by an async stream
24
+ - `StatusBadge` — color-coded badge for service or resource states
25
+ - `ToastProvider` + hook — global toast notification system
26
+
27
+ **Systemd layer**
28
+ - `useServiceStatus` — reactive hook for a systemd service state (active, failed, inactive…)
29
+ - `ServiceControl` — start/stop/restart/enable control component
30
+ - `api` — typed wrappers around `cockpit.spawn` for systemctl operations
31
+
32
+ **Shared tooling config**
33
+ - `tsconfig.base.json` — TypeScript base config tuned for Cockpit plugins
34
+ - `eslint.config.base` — `createEslintConfig()` factory with TS, React, and react-hooks rules
35
+ - `vitest.config.base` — Vitest base config with jsdom and PatternFly setup
36
+
37
+ **Testing utilities**
38
+ - Vitest setup file that installs jsdom and jest-dom matchers
39
+ - `mockCockpit` — in-memory Cockpit API mock for unit tests
40
+ - `mockHttpClient` — mock for Cockpit HTTP client used in tests
41
+
42
+ **QEMU VM test harness**
43
+ - `npm run vm` — spins up real cloud VMs (Arch, Debian, Fedora) with Cockpit installed and your plugin mounted via virtfs. Used for end-to-end testing against a live Cockpit instance. See [VM Testing](docs/wiki/VM-Testing.md).
44
+
45
+ **Reusable CI/CD workflows**
46
+ - Lint, typecheck, test, and build on every push
47
+ - RPM, DEB, and Arch package build verification
48
+ - Semantic version bumping from conventional commits
49
+ - Automated release asset upload and AUR publishing
50
+ - GitHub Wiki sync from `docs/wiki/`
51
+
52
+ ## Install
53
+
54
+ ```bash
55
+ npm install @rxtx4816/cockpit-plugin-base-react
56
+ ```
57
+
58
+ Peer dependencies: `react >=19`, `react-dom >=19`, `i18next >=26`, `react-i18next >=17`
59
+
60
+ ## Quick start
61
+
62
+ ```tsx
63
+ // src/index.tsx
64
+ import "./i18n";
65
+ import "@rxtx4816/cockpit-plugin-base-react/dark-theme";
66
+ import { bootstrapPlugin } from "@rxtx4816/cockpit-plugin-base-react/bootstrap";
67
+ import App from "./App";
68
+
69
+ bootstrapPlugin(App);
70
+ ```
71
+
72
+ For full setup guidance, config sharing, and workflow integration see the [wiki](docs/wiki/Home.md).
73
+
74
+ ## Documentation
75
+
76
+ - [Getting Started](docs/wiki/Getting-Started.md)
77
+ - [Hooks](docs/wiki/Hooks.md)
78
+ - [Components](docs/wiki/Components.md)
79
+ - [Systemd Layer](docs/wiki/Systemd.md)
80
+ - [Testing](docs/wiki/Testing.md)
81
+ - [VM Testing](docs/wiki/VM-Testing.md)
82
+ - [CI/CD Workflows](docs/wiki/CI-CD.md)
83
+
84
+ ## License
85
+
86
+ MIT © 2026 RXTX4816
@@ -0,0 +1,87 @@
1
+ # CI/CD Workflows
2
+
3
+ The package ships reusable GitHub Actions workflows that plugins call with `uses:`. This keeps CI logic in one place and lets all plugins inherit fixes and improvements automatically.
4
+
5
+ ## Using the workflows
6
+
7
+ In your plugin's `.github/workflows/`:
8
+
9
+ ```yaml
10
+ # .github/workflows/ci.yml
11
+ name: CI
12
+ on:
13
+ push:
14
+ branches: [main]
15
+ pull_request:
16
+ branches: [main]
17
+
18
+ jobs:
19
+ ci:
20
+ uses: RXTX4816/cockpit-plugin-base-react/.github/workflows/ci-plugin.yml@main
21
+ with:
22
+ plugin-name: cockpit-caddy
23
+ ```
24
+
25
+ ---
26
+
27
+ ## Available workflows
28
+
29
+ ### ci-plugin.yml
30
+
31
+ Runs on every push and pull request. Steps: lint → typecheck → test → build → verify build output.
32
+
33
+ Required inputs:
34
+ - `plugin-name` — used to verify `src/main.js` and `src/main.css` exist after build
35
+
36
+ ### pkg-ci-plugin.yml
37
+
38
+ Builds and verifies RPM, DEB, and Arch packages using a placeholder `0.0.0` version. Confirms that the packaging definitions (`.spec`, `debian/`, `PKGBUILD`) are valid before a real release.
39
+
40
+ Required inputs:
41
+ - `plugin-name`
42
+ - `spec-file` — path to the RPM spec file (e.g. `cockpit-caddy.spec`)
43
+ - `aur-pkgname` — AUR package name
44
+
45
+ ### semantic-release-plugin.yml
46
+
47
+ Determines the next version from conventional commits since the last git tag and creates the tag. Does not build or publish anything — it only tags. The tag then triggers whatever release workflow you have listening on `push.tags`.
48
+
49
+ Version bump rules:
50
+ - `BREAKING CHANGE` anywhere in a commit body → major
51
+ - `feat:` prefix → minor
52
+ - anything else → patch
53
+ - no commits since last tag → skip (exits 0, no tag created)
54
+
55
+ Required secrets:
56
+ - `RELEASE_TOKEN` — a GitHub token with `contents: write` permission
57
+
58
+ Optional inputs:
59
+ - `initial_version` — version to use when no prior tag exists (default: `v1.0.0`)
60
+
61
+ ### release-plugin.yml
62
+
63
+ Builds release assets (tarball + sha256, RPM, DEB) and uploads them to a GitHub release. Also updates the `PKGBUILD` and pushes to AUR.
64
+
65
+ Required inputs:
66
+ - `plugin-name`, `spec-file`, `aur-pkgname`
67
+ - `maintainer-name`, `maintainer-email`
68
+
69
+ Required secrets:
70
+ - `AUR_SSH_KEY` — SSH private key registered with the AUR account
71
+ - `RELEASE_TOKEN` — GitHub token with `contents: write`
72
+
73
+ ### sync-wiki-plugin.yml
74
+
75
+ Copies all `.md` files from `docs/wiki/` in your plugin repo to the GitHub Wiki. Triggered by push to main.
76
+
77
+ No inputs or secrets required beyond the default `GITHUB_TOKEN`.
78
+
79
+ ---
80
+
81
+ ## Secrets reference
82
+
83
+ | Secret | Used by | Description |
84
+ |---|---|---|
85
+ | `RELEASE_TOKEN` | semantic-release, release | GitHub PAT with `contents: write` |
86
+ | `AUR_SSH_KEY` | release | SSH private key for AUR pushes |
87
+ | `NPM_TOKEN` | (base package only) | npm automation token for publishing |
@@ -0,0 +1,56 @@
1
+ # Components
2
+
3
+ All components are exported from the components entrypoint:
4
+
5
+ ```ts
6
+ import { ConfirmDialog, ErrorBoundary, HelpPopover, LogViewer, StatusBadge, ToastProvider } from "@rxtx4816/cockpit-plugin-base-react/components";
7
+ ```
8
+
9
+ `ToastProvider` is mounted automatically by `bootstrapPlugin`. The others are available for use anywhere in your plugin.
10
+
11
+ ---
12
+
13
+ ## ConfirmDialog
14
+
15
+ A PatternFly modal dialog driven by `useConfirmAction` state. Supports multi-step confirmation flows — for example, showing a warning first and requiring the user to type a resource name before proceeding.
16
+
17
+ Receives the `state` and `cancel` from `useConfirmAction` and renders the appropriate step content.
18
+
19
+ ---
20
+
21
+ ## ErrorBoundary
22
+
23
+ A React error boundary that catches render errors anywhere in the component tree and displays a PatternFly alert with the error message and stack trace. Prevents the entire plugin from going blank on an unexpected error.
24
+
25
+ Mounted automatically by `bootstrapPlugin`, but can also be used to wrap specific subtrees.
26
+
27
+ ---
28
+
29
+ ## HelpPopover
30
+
31
+ A small PatternFly popover for contextual help. Renders a help icon button that opens a popover with a title and body text. Use it next to form fields or section headings to explain non-obvious behaviour.
32
+
33
+ ---
34
+
35
+ ## LogViewer
36
+
37
+ A scrollable, terminal-style log display that accepts an array of output lines (typically from `useAsyncStream`). Automatically scrolls to the bottom on new output. Used for displaying real-time command output or service journal entries.
38
+
39
+ ---
40
+
41
+ ## StatusBadge
42
+
43
+ A color-coded label for service or resource states. Maps state strings (e.g. `"active"`, `"failed"`, `"inactive"`, `"unknown"`) to PatternFly status colors. Used by `ServiceControl` and can be used standalone wherever you need to display a state.
44
+
45
+ ---
46
+
47
+ ## ToastProvider
48
+
49
+ Global toast notification context. Wrap your app with `ToastProvider` (done automatically by `bootstrapPlugin`) and use the `useToast` hook to fire notifications from anywhere:
50
+
51
+ ```ts
52
+ const { addToast } = useToast();
53
+ addToast({ title: "Saved", variant: "success" });
54
+ ```
55
+
56
+ Toasts are displayed in the top-right corner and auto-dismiss after a configurable timeout.
@@ -0,0 +1,92 @@
1
+ # Getting Started
2
+
3
+ ## Installation
4
+
5
+ ```bash
6
+ npm install @rxtx4816/cockpit-plugin-base-react
7
+ ```
8
+
9
+ Peer dependencies required in your plugin:
10
+
11
+ ```bash
12
+ npm install react react-dom i18next react-i18next
13
+ ```
14
+
15
+ ---
16
+
17
+ ## Bootstrapping your plugin
18
+
19
+ Every Cockpit plugin needs an entry point that initialises i18n, the dark theme, and mounts React. This package handles all of it:
20
+
21
+ ```tsx
22
+ // src/index.tsx
23
+ import "./i18n";
24
+ import "@rxtx4816/cockpit-plugin-base-react/dark-theme";
25
+ import { bootstrapPlugin } from "@rxtx4816/cockpit-plugin-base-react/bootstrap";
26
+ import App from "./App";
27
+
28
+ bootstrapPlugin(App);
29
+ ```
30
+
31
+ `bootstrapPlugin` wraps your app in an `ErrorBoundary` and a `ToastProvider`, then mounts it into the `#app` element that Cockpit expects.
32
+
33
+ ---
34
+
35
+ ## i18n setup
36
+
37
+ Create `src/i18n.ts` in your plugin:
38
+
39
+ ```ts
40
+ import { initCockpitI18n } from "@rxtx4816/cockpit-plugin-base-react/i18n";
41
+
42
+ initCockpitI18n();
43
+ ```
44
+
45
+ This sets up i18next with Cockpit's locale loading conventions so `useTranslation()` works throughout your plugin.
46
+
47
+ ---
48
+
49
+ ## Shared tooling config
50
+
51
+ Extend from the base configs so all plugins stay consistent.
52
+
53
+ **tsconfig.json**
54
+ ```json
55
+ {
56
+ "extends": "@rxtx4816/cockpit-plugin-base-react/tsconfig.base.json",
57
+ "compilerOptions": {
58
+ "paths": {}
59
+ }
60
+ }
61
+ ```
62
+
63
+ **eslint.config.js**
64
+ ```js
65
+ import { createEslintConfig } from "@rxtx4816/cockpit-plugin-base-react/eslint.config.base";
66
+
67
+ export default createEslintConfig();
68
+ ```
69
+
70
+ Pass extra globals if your plugin uses custom Cockpit types:
71
+ ```js
72
+ export default createEslintConfig({ CockpitHttpClient: "readonly" });
73
+ ```
74
+
75
+ **vitest.config.ts**
76
+ ```ts
77
+ import { defineConfig } from "@rxtx4816/cockpit-plugin-base-react/vitest.config.base";
78
+
79
+ export default defineConfig();
80
+ ```
81
+
82
+ ---
83
+
84
+ ## Dark theme
85
+
86
+ Importing the `dark-theme` side-effect module is all that's needed. It listens for three signals and keeps the `pf-v6-theme-dark` class on `<html>` in sync:
87
+
88
+ - `localStorage` key `shell:style` (values: `"light"`, `"dark"`, `"auto"`)
89
+ - The custom `cockpit-style` event dispatched by the Cockpit shell switcher
90
+ - The OS-level `prefers-color-scheme` media query
91
+
92
+ No configuration required.
@@ -0,0 +1,13 @@
1
+ # cockpit-plugin-base-react Wiki
2
+
3
+ This wiki covers everything you need to build, test, and ship Cockpit plugins using `@rxtx4816/cockpit-plugin-base-react`.
4
+
5
+ ## Contents
6
+
7
+ - [Getting Started](Getting-Started.md) — install, bootstrap, and first plugin setup
8
+ - [Hooks](Hooks.md) — async state, polling, streams, confirmation flows
9
+ - [Components](Components.md) — UI building blocks: toasts, dialogs, logs, badges
10
+ - [Systemd Layer](Systemd.md) — service status, control, and typed API wrappers
11
+ - [Testing](Testing.md) — unit testing with mocked Cockpit and HTTP client
12
+ - [VM Testing](VM-Testing.md) — end-to-end testing with real QEMU VMs
13
+ - [CI/CD Workflows](CI-CD.md) — reusable GitHub Actions for builds, packages, and releases
@@ -0,0 +1,58 @@
1
+ # Hooks
2
+
3
+ All hooks are exported from the main entrypoint:
4
+
5
+ ```ts
6
+ import { useAsyncAction, useAutoRefresh, useAsyncStream, useConfirmAction, usePollingFetch } from "@rxtx4816/cockpit-plugin-base-react";
7
+ ```
8
+
9
+ ---
10
+
11
+ ## useAsyncAction
12
+
13
+ Wraps an async operation with `loading`, `error`, and `execute` state. Designed for buttons or forms that trigger backend calls.
14
+
15
+ The hook returns an object with:
16
+ - `execute(...args)` — triggers the operation
17
+ - `loading` — true while the operation is in progress
18
+ - `error` — the caught error if the operation failed, or null
19
+ - `reset()` — clears error state
20
+
21
+ Errors are caught automatically; unhandled promise rejections do not propagate to the component tree.
22
+
23
+ ---
24
+
25
+ ## useAutoRefresh
26
+
27
+ Runs a callback on a configurable interval. Returns a `refresh()` function for on-demand triggering and a `loading` flag.
28
+
29
+ Useful for periodically re-fetching data without managing `setInterval` lifecycle yourself. The interval is cleared on unmount.
30
+
31
+ ---
32
+
33
+ ## useAsyncStream
34
+
35
+ Consumes a Cockpit channel as a line-buffered async stream. Returns the accumulated output lines and an `error` state.
36
+
37
+ Used internally by `LogViewer` and useful anywhere you need to display or process real-time output from a spawned process.
38
+
39
+ ---
40
+
41
+ ## useConfirmAction
42
+
43
+ Manages a multi-step confirmation flow with typed state transitions. Returns:
44
+
45
+ - `state` — current step or `null` when idle
46
+ - `start(initialStep)` — opens the flow
47
+ - `next(step)` — advances to the next step
48
+ - `cancel()` — resets to idle
49
+
50
+ Pairs with `ConfirmDialog` to build destructive action flows (e.g. delete with a typed confirmation).
51
+
52
+ ---
53
+
54
+ ## usePollingFetch
55
+
56
+ Fetches a resource with automatic polling and returns `{ data, loading, error, refresh }`. The poll interval is configurable.
57
+
58
+ Handles the full lifecycle: initial fetch, polling, cleanup on unmount, and error recovery. Calling `refresh()` triggers an immediate re-fetch and resets the poll timer.
@@ -0,0 +1,43 @@
1
+ # Systemd Layer
2
+
3
+ The systemd layer provides typed hooks, a control component, and low-level API wrappers for interacting with systemd services through Cockpit.
4
+
5
+ ```ts
6
+ import { useServiceStatus, ServiceControl } from "@rxtx4816/cockpit-plugin-base-react/systemd";
7
+ ```
8
+
9
+ ---
10
+
11
+ ## useServiceStatus
12
+
13
+ Reactive hook that tracks the state of a systemd service. Polls via `systemctl is-active` and returns:
14
+
15
+ - `status` — one of `"active"`, `"inactive"`, `"failed"`, `"activating"`, `"deactivating"`, `"unknown"`
16
+ - `loading` — true on the initial fetch
17
+ - `error` — any error from the underlying `cockpit.spawn` call
18
+ - `refresh()` — manually re-check the service state
19
+
20
+ The hook automatically re-polls at a configurable interval, so your UI stays in sync without manual management.
21
+
22
+ ---
23
+
24
+ ## ServiceControl
25
+
26
+ A self-contained component that renders start/stop/restart/enable/disable buttons for a named service. Internally uses `useServiceStatus` for the current state and the `api` helpers to dispatch commands.
27
+
28
+ Buttons are disabled while an operation is in progress. Status changes are reflected immediately via an optimistic state update, then confirmed by the next poll.
29
+
30
+ ---
31
+
32
+ ## API helpers
33
+
34
+ Low-level typed wrappers around `cockpit.spawn` for common systemctl operations:
35
+
36
+ - `startService(name)` — `systemctl start`
37
+ - `stopService(name)` — `systemctl stop`
38
+ - `restartService(name)` — `systemctl restart`
39
+ - `enableService(name)` — `systemctl enable`
40
+ - `disableService(name)` — `systemctl disable`
41
+ - `getServiceStatus(name)` — returns the current active state string
42
+
43
+ All functions return Promises and throw on non-zero exit codes.
@@ -0,0 +1,51 @@
1
+ # Testing
2
+
3
+ ## Setup
4
+
5
+ The package ships a Vitest setup file that configures jsdom and installs jest-dom matchers. Reference it from your plugin's `vitest.config.ts` (handled automatically when you extend the base config):
6
+
7
+ ```ts
8
+ import { defineConfig } from "@rxtx4816/cockpit-plugin-base-react/vitest.config.base";
9
+ export default defineConfig();
10
+ ```
11
+
12
+ ---
13
+
14
+ ## Test utilities
15
+
16
+ ```ts
17
+ import { mockCockpit, mockHttpClient } from "@rxtx4816/cockpit-plugin-base-react/testing/helpers";
18
+ ```
19
+
20
+ ### mockCockpit
21
+
22
+ Returns an in-memory mock of the `cockpit` browser global. Stubs out `cockpit.spawn`, `cockpit.file`, `cockpit.http`, and channel creation so tests never attempt real system calls.
23
+
24
+ Set it up in your test file:
25
+
26
+ ```ts
27
+ beforeEach(() => {
28
+ vi.stubGlobal("cockpit", mockCockpit());
29
+ });
30
+ ```
31
+
32
+ Individual spawn calls can be configured to return specific output or to reject, letting you test both success and error paths.
33
+
34
+ ### mockHttpClient
35
+
36
+ Returns a mock of the Cockpit HTTP client with configurable per-path responses. Useful for testing components that call `cockpit.http().get(path)`.
37
+
38
+ ```ts
39
+ const client = mockHttpClient({ "/api/status": '{"running": true}' });
40
+ ```
41
+
42
+ `get`, `post`, and `request` are all `vi.fn()` instances, so you can assert call counts and arguments with standard Vitest matchers.
43
+
44
+ ---
45
+
46
+ ## Running tests
47
+
48
+ ```bash
49
+ npm test # single run
50
+ npm run test:watch # watch mode
51
+ ```
@@ -0,0 +1,99 @@
1
+ # VM Testing
2
+
3
+ The package ships a QEMU-based VM harness for end-to-end testing against a real Cockpit instance running on a real OS. It spins up cloud VMs (Arch, Debian, Fedora by default), installs Cockpit, and mounts your built plugin via virtfs — no packaging or installation step required.
4
+
5
+ ## Prerequisites
6
+
7
+ Arch Linux:
8
+ ```bash
9
+ sudo pacman -S qemu-full cloud-image-utils wget
10
+ ```
11
+
12
+ KVM access is strongly recommended. Without it the VMs run without hardware acceleration and will be significantly slower.
13
+
14
+ ## How it works
15
+
16
+ 1. A base cloud image is downloaded once per distro and kept on disk
17
+ 2. Each VM gets a thin overlay disk so the base image is never modified
18
+ 3. cloud-init provisions the VM on first boot: creates a `test` user, installs Cockpit and any plugin-specific packages, and mounts your plugin's `src/` directory read-only into the Cockpit install path via 9p/virtfs
19
+ 4. Changes to your built output (`src/main.js`, `src/main.css`) are immediately visible inside the VM without a restart
20
+
21
+ ## Plugin configuration
22
+
23
+ Each plugin provides `scripts/test-vm.config.sh` to customise the harness:
24
+
25
+ ```bash
26
+ PLUGIN_NAME="cockpit-caddy"
27
+ MOUNT_TAG="cockpit_caddy"
28
+ INSTALL_PATH="/usr/share/cockpit/cockpit-caddy"
29
+
30
+ extra_packages() {
31
+ echo "caddy"
32
+ }
33
+
34
+ extra_runcmd() {
35
+ echo " - systemctl enable --now caddy"
36
+ }
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ Add to your plugin's `package.json`:
42
+ ```json
43
+ "vm": "node_modules/@rxtx4816/cockpit-plugin-base-react/scripts/test-vm.sh"
44
+ ```
45
+
46
+ Then:
47
+
48
+ ```bash
49
+ npm run build # build the plugin first
50
+ npm run vm download arch # download base image (once)
51
+ npm run vm start arch # start the VM
52
+ npm run vm wait arch # block until cloud-init finishes (~2 min first boot)
53
+ # open https://localhost:9090 — login: test / test
54
+ ```
55
+
56
+ After the initial boot, subsequent starts are fast (no re-provisioning unless you `clean`).
57
+
58
+ ## All commands
59
+
60
+ | Command | Description |
61
+ |---|---|
62
+ | `download [vm\|all]` | Download base cloud images |
63
+ | `build` | Run `npm run build` |
64
+ | `start [vm ...]` | Start VM(s) in background |
65
+ | `wait <vm>` | Block until cloud-init completes |
66
+ | `stop [vm ...]` | Stop VM(s) |
67
+ | `status` | Show all VMs with ports and running state |
68
+ | `ssh <vm>` | Open an SSH session into the VM |
69
+ | `logs <vm>` | Tail the VM serial console |
70
+ | `clean [vm ...]` | Wipe disk and cloud-init state (base image kept) |
71
+ | `rebuild [vm ...]` | `clean` + `start` in one step |
72
+ | `reset [vm ...]` | Remove all VM files including base image |
73
+
74
+ ## Ports
75
+
76
+ By default the VMs are assigned sequential ports starting from:
77
+ - Cockpit: `9090`, `9091`, `9092` (arch, debian, fedora)
78
+ - SSH: `2220`, `2221`, `2222`
79
+
80
+ These can be changed in your `test-vm.config.sh` via `SSH_BASE` and `COCKPIT_BASE`.
81
+
82
+ ## Live reload workflow
83
+
84
+ Because the plugin is mounted via virtfs, you can iterate quickly:
85
+
86
+ ```bash
87
+ npm run watch # rebuild on source changes
88
+ npm run vm start arch
89
+ npm run vm wait arch
90
+ # reload the browser tab after each rebuild — no VM restart needed
91
+ ```
92
+
93
+ ## Environment overrides
94
+
95
+ | Variable | Default | Description |
96
+ |---|---|---|
97
+ | `VM_MEM` | `1024` | Memory per VM in MB |
98
+ | `VM_CPUS` | `2` | vCPU count |
99
+ | `VM_DISK_SIZE` | `12G` | Overlay disk size |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rxtx4816/cockpit-plugin-base-react",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Shared infrastructure for Cockpit plugins: i18n, dark theme, test setup, config presets, CI/CD workflows, and QEMU VM harness",
5
5
  "type": "module",
6
6
  "author": "RXTX4816",
@@ -60,20 +60,24 @@
60
60
  "default": "./eslint.config.base.js"
61
61
  },
62
62
  "./vitest.config.base": {
63
- "default": "./vitest.config.base.ts"
63
+ "default": "./vitest.config.base.js"
64
64
  }
65
65
  },
66
66
  "files": [
67
+ "LICENSE",
68
+ "README.md",
69
+ "docs/",
67
70
  "src/",
68
71
  "scripts/",
69
72
  "tsconfig.base.json",
70
73
  "eslint.config.base.js",
71
- "vitest.config.base.ts"
74
+ "vitest.config.base.js"
72
75
  ],
73
76
  "bin": {
74
77
  "cockpit-test-vm": "./scripts/test-vm.sh"
75
78
  },
76
79
  "scripts": {
80
+ "lint": "eslint src/",
77
81
  "typecheck": "tsc --noEmit",
78
82
  "test": "vitest run",
79
83
  "test:watch": "vitest"
package/src/cockpit.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  // Ambient type declarations for the Cockpit browser global.
2
2
  // Superset covering cockpit-caddy and cockpit-compose usage patterns.
3
+ // eslint-disable-next-line @typescript-eslint/triple-slash-reference
3
4
  /// <reference path="./css.d.ts" />
4
5
 
5
6
  declare interface CockpitProcess extends Promise<string> {
@@ -1,5 +1,5 @@
1
1
  import { renderHook, act, waitFor } from "@testing-library/react";
2
- import { vi, describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { vi, describe, it, expect, beforeEach } from "vitest";
3
3
  import { useServiceStatus } from "./useServiceStatus";
4
4
 
5
5
  const mockSpawn = vi.fn();
@@ -1,22 +1,13 @@
1
1
  import { defineConfig } from "vitest/config";
2
- import type { TestUserConfig } from "vitest/config";
3
2
 
4
- type TestConfig = TestUserConfig;
5
-
6
- export function createVitestConfig(overrides: TestConfig = {}) {
3
+ export function createVitestConfig(overrides = {}) {
7
4
  const { coverage: coverageOverrides, setupFiles: extraSetupFiles, ...rest } = overrides;
8
5
 
9
6
  return defineConfig({
10
7
  server: {
11
- // Allow Vite's dev server to serve files from symlinked file: packages
12
- // that live outside the consuming project's root directory.
13
8
  fs: { allow: [".."] },
14
9
  },
15
10
  resolve: {
16
- // Deduplicate packages that must be singletons when cockpit-plugin-base-react
17
- // is installed as a file: link (symlink) — without this, the linked
18
- // package resolves these from its own node_modules and React / i18next
19
- // end up with two separate instances, breaking hooks and translations.
20
11
  dedupe: ["react", "react-dom", "i18next", "react-i18next", "@patternfly/react-core", "@patternfly/react-icons"],
21
12
  },
22
13
  test: {