@rhost/testkit 1.6.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +187 -7
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![npm version](https://img.shields.io/npm/v/@rhost/testkit.svg)](https://www.npmjs.com/package/@rhost/testkit)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/@rhost/testkit.svg)](https://www.npmjs.com/package/@rhost/testkit)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
- [![CI](https://github.com/RhostMUSH/rhostmush-docker/actions/workflows/security-tests.yml/badge.svg)](https://github.com/RhostMUSH/rhostmush-docker/actions/workflows/security-tests.yml)
6
+ [![CI](https://github.com/lcanady/rhost-testkit/actions/workflows/ci.yml/badge.svg)](https://github.com/lcanady/rhost-testkit/actions/workflows/ci.yml)
7
7
  [![Security](https://img.shields.io/badge/Security-Audited-brightgreen.svg)](./SECURITY.md)
8
8
 
9
9
  A Jest-like testing framework for [RhostMUSH](https://github.com/RhostMUSH/trunk) softcode.
@@ -21,12 +21,14 @@ npm install @rhost/testkit
21
21
  - [Installation](#installation)
22
22
  - [How the runner works](#how-the-runner-works)
23
23
  - [Quick start](#quick-start)
24
+ - [Starting a server](#starting-a-server)
24
25
  - [API reference — RhostRunner](#api-reference--rhostrunner)
25
26
  - [API reference — RhostExpect](#api-reference--rhostexpect)
26
27
  - [Snapshot testing](#snapshot-testing)
27
28
  - [API reference — RhostWorld](#api-reference--rhostworld)
28
29
  - [API reference — RhostClient](#api-reference--rhostclient)
29
30
  - [API reference — RhostContainer](#api-reference--rhostcontainer)
31
+ - [Pueblo protocol](#pueblo-protocol)
30
32
  - [Offline validator](#offline-validator)
31
33
  - [Softcode formatter](#softcode-formatter)
32
34
  - [Benchmark mode](#benchmark-mode)
@@ -184,6 +186,74 @@ runner.describe('attributes', ({ it, beforeEach, afterEach }) => {
184
186
 
185
187
  ---
186
188
 
189
+ ## Starting a server
190
+
191
+ The `rhost-server` CLI (also available as `npx rhost-testkit server`) spins up a RhostMUSH Docker container and keeps it running until you press Ctrl+C. It is the quickest way to get a live MUSH for development or ad-hoc testing without writing any code.
192
+
193
+ ### Zero-config usage
194
+
195
+ ```bash
196
+ npx rhost-testkit server
197
+ # or, if you install globally:
198
+ rhost-server
199
+ ```
200
+
201
+ This pulls `lcanady/rhostmush:latest` from Docker Hub, binds it to port 4201, and prints a startup banner when the server is ready.
202
+
203
+ ### All flags
204
+
205
+ **Server options**
206
+
207
+ | Flag | Default | Description |
208
+ |------|---------|-------------|
209
+ | `-p, --port <n>` | `4201` | Host port to publish the MUSH on. |
210
+ | `--image <name>` | `lcanady/rhostmush:latest` | Docker image to use. |
211
+ | `--build-from-source` | — | Build the image from source instead of pulling. |
212
+ | `--project-root <path>` | `cwd` | Path to the rhostmush-docker repo (used with `--build-from-source`). |
213
+ | `-c, --config <path>` | auto-discover | Path to `rhost.config.json` (or its directory). |
214
+ | `--startup-timeout <ms>` | `120000` | Maximum milliseconds to wait for the server to be ready. |
215
+
216
+ **Compile-time flags** _(require `--build-from-source`)_
217
+
218
+ | Flag | Description |
219
+ |------|-------------|
220
+ | `--enable-websockets` | Enable WebSocket (RFC 6455) support at compile time. |
221
+ | `--disable-websockets` | Disable WebSocket support. |
222
+ | `--enable-reality` | Enable the REALMS/Reality Levels system. |
223
+ | `--extra-cflags <flags>` | Additional raw CFLAGS passed to the compiler (e.g. `"-DFOO -DBAR"`). |
224
+
225
+ **Stunnel (TLS) options**
226
+
227
+ | Flag | Default | Description |
228
+ |------|---------|-------------|
229
+ | `--stunnel` | — | Wrap the MUSH port in TLS via stunnel. |
230
+ | `--stunnel-port <n>` | `4203` | Port stunnel listens on for incoming TLS connections. |
231
+ | `--stunnel-connect-port <n>` | `4201` | Internal port stunnel forwards decrypted traffic to. |
232
+ | `--stunnel-cert <path>` | auto-generated | PEM certificate file (a self-signed cert is generated when omitted). |
233
+ | `--stunnel-key <path>` | same as cert | PEM private key file (defaults to `--stunnel-cert` for combined PEM). |
234
+
235
+ ### Example startup banner
236
+
237
+ ```
238
+ Starting RhostMUSH — image: lcanady/rhostmush:latest
239
+ Pulling/building image and booting container… (first build may take several minutes)
240
+
241
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
242
+ RhostMUSH is running
243
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
244
+ Host: localhost
245
+ Port: 4201
246
+ Image: lcanady/rhostmush:latest
247
+ Wizard: Wizard / Nyctasia (default credentials)
248
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
249
+
250
+ Press Ctrl+C to stop.
251
+ ```
252
+
253
+ When `--build-from-source --enable-websockets --stunnel` are combined, the banner also prints the WebSocket URL and the stunnel TLS port.
254
+
255
+ ---
256
+
187
257
  ## API reference — RhostRunner
188
258
 
189
259
  ### Constructor
@@ -555,6 +625,9 @@ const client = new RhostClient(options?: RhostClientOptions);
555
625
  | `stripAnsi` | `boolean` | `true` | Strip ANSI/VT100 codes from results |
556
626
  | `paceMs` | `number` | `0` | Delay between commands (flood control) |
557
627
  | `connectTimeout` | `number` | `10000` | TCP connection establishment timeout (ms) |
628
+ | `useWebSocket` | `boolean` | `false` | Connect via WebSocket instead of raw TCP. Requires a server built with `ENABLE_WEBSOCKETS`. |
629
+ | `websocketPath` | `string` | `'/'` | WebSocket request path sent in the HTTP upgrade. |
630
+ | `websocketSecure` | `boolean` | `false` | Use `wss://` instead of `ws://`. Requires stunnel or another TLS terminator in front of the server. |
558
631
 
559
632
  ### Methods
560
633
 
@@ -643,26 +716,63 @@ Spins up a RhostMUSH Docker container for isolated test runs. Uses [testcontaine
643
716
  ### Factory methods
644
717
 
645
718
  ```typescript
646
- RhostContainer.fromImage(image?: string): RhostContainer
719
+ RhostContainer.fromImage(image?: string, config?: RhostConfig): RhostContainer
647
720
  ```
648
- Use a pre-built Docker image. Default image: `'rhostmush:latest'`.
649
- Build it first: `docker build -t rhostmush:latest .` (from the rhostmush-docker repo root).
721
+ Use a pre-built Docker image. Default image: `'lcanady/rhostmush:latest'`.
722
+ The optional `config` argument lets you inject a `RhostConfig` directly rather than relying on `rhost.config.json` auto-discovery. Build flags in `config.build` are ignored for pre-built images, but `stunnel` and file-copy options still apply.
650
723
 
651
724
  ```typescript
652
- RhostContainer.fromSource(projectRoot?: string): RhostContainer
725
+ RhostContainer.fromSource(projectRoot?: string, config?: RhostConfig): RhostContainer
653
726
  ```
654
727
  Build the image from the `Dockerfile` in the rhostmush-docker project root.
655
728
  First run: ~5–10 minutes (clones and compiles RhostMUSH from source). Subsequent runs use Docker layer cache.
656
729
  `projectRoot` defaults to `'../'` relative to the installed package location.
657
730
 
731
+ **`RhostConfig` shape** (see `src/config.ts` for full TypeScript types):
732
+
733
+ ```typescript
734
+ interface RhostConfig {
735
+ scriptsDir?: string; // local directory copied to /home/rhost/game/scripts
736
+ mushConfig?: string; // local file copied as mush.config
737
+
738
+ /** Compile-time features — fromSource() only; ignored for pre-built images. */
739
+ build?: {
740
+ enableWebSockets?: boolean; // Enable WebSocket (RFC 6455) support
741
+ enableReality?: boolean; // Enable REALMS/Reality Levels
742
+ extraCflags?: string; // Raw extra CFLAGS, e.g. "-DFOO -DBAR"
743
+ };
744
+
745
+ /** stunnel TLS wrapper — applies to both fromImage() and fromSource(). */
746
+ stunnel?: {
747
+ enable?: boolean; // Launch stunnel inside the container
748
+ acceptPort?: number; // TLS listen port (default: 4203)
749
+ connectPort?: number; // Internal forward port (default: 4201)
750
+ certFile?: string; // PEM cert — auto-generated self-signed when omitted
751
+ keyFile?: string; // PEM key — defaults to certFile for combined PEM
752
+ };
753
+ }
754
+ ```
755
+
756
+ All paths in `RhostConfig` are resolved relative to the directory containing `rhost.config.json` when loaded from disk, or relative to `process.cwd()` when passed programmatically.
757
+
658
758
  ### Instance methods
659
759
 
660
760
  ```typescript
661
761
  await container.start(startupTimeout?: number): Promise<ContainerConnectionInfo>
662
762
  ```
663
- Starts the container and waits for port 4201 to accept connections.
763
+ Starts the container and waits for listening ports to be ready.
664
764
  `startupTimeout` defaults to `120000` ms (2 minutes).
665
- Returns `{ host: string; port: number }` — the dynamically assigned host/port.
765
+ Returns a `ContainerConnectionInfo` object with the dynamically assigned host/port.
766
+
767
+ **`ContainerConnectionInfo`:**
768
+
769
+ ```typescript
770
+ interface ContainerConnectionInfo {
771
+ host: string;
772
+ port: number;
773
+ stunnelPort?: number; // present when stunnel is enabled; the mapped TLS port
774
+ }
775
+ ```
666
776
 
667
777
  ```typescript
668
778
  await container.stop(): Promise<void>
@@ -705,6 +815,75 @@ process.exit(result.failed > 0 ? 1 : 0);
705
815
 
706
816
  ---
707
817
 
818
+ ## Pueblo protocol
819
+
820
+ [Pueblo](http://www.legacymud.com/pueblo/pueblo_ext.html) is a protocol extension that allows a MUSH server to send HTML-enriched output to capable clients. RhostMUSH has native Pueblo support — no compile-time flag is required.
821
+
822
+ ### Handshake
823
+
824
+ After connecting and before logging in, send the Pueblo handshake string. The server responds with a line beginning with `PUEBLOCLIENT`, signalling that it will now include HTML markup in its output.
825
+
826
+ ```typescript
827
+ import { RhostClient, generatePuebloHandshake, parsePuebloHandshake, convertPueblo } from '@rhost/testkit';
828
+
829
+ const client = new RhostClient({ host: 'localhost', port: 4201, stripAnsi: false });
830
+ await client.connect();
831
+
832
+ // 1. Announce Pueblo capability
833
+ await client.send(generatePuebloHandshake());
834
+
835
+ // 2. Detect the server's acknowledgement
836
+ let puebloActive = false;
837
+ client.onLine((line) => {
838
+ if (!puebloActive && parsePuebloHandshake(line)) {
839
+ puebloActive = true;
840
+ }
841
+ });
842
+
843
+ await client.login('Wizard', process.env.RHOST_PASS!);
844
+
845
+ // 3. Convert mixed Pueblo/text output to safe semantic HTML
846
+ const raw = await client.eval('look here');
847
+ const html = convertPueblo(raw);
848
+ // html is sanitized, safe to inject into a browser DOM
849
+ ```
850
+
851
+ ### `generatePuebloHandshake()`
852
+
853
+ Returns the canonical `PUEBLOCLIENT 1.0.1\r\n` string to send to the server.
854
+
855
+ ### `parsePuebloHandshake(line)`
856
+
857
+ ```typescript
858
+ parsePuebloHandshake(input: string): boolean
859
+ ```
860
+
861
+ Returns `true` when `input` begins with the `PUEBLOCLIENT` keyword (case-insensitive). Use this to detect the server's acknowledgement line.
862
+
863
+ ### `convertPueblo(input)`
864
+
865
+ ```typescript
866
+ convertPueblo(input: string): string
867
+ ```
868
+
869
+ Converts a mixed Pueblo/text stream received from a Pueblo-enabled server into sanitized, semantic HTML. Safe to inject directly into a browser DOM.
870
+
871
+ **Key conversion behaviors:**
872
+
873
+ | Input | Output |
874
+ |-------|--------|
875
+ | `<a xch_cmd="look here">here</a>` | `<a href="#" data-xch-cmd="look here">here</a>` |
876
+ | `<bgsound src="theme.mid">` | `<audio src="theme.mid" controls loop>` |
877
+ | `<center>...</center>` | `<div style="text-align:center">...</div>` |
878
+ | `<script>...</script>` | _(entire subtree stripped)_ |
879
+ | `<iframe>...</iframe>` | _(entire subtree stripped)_ |
880
+ | Any `on*` attribute (e.g. `onclick`) | _(attribute removed)_ |
881
+ | `javascript:` / `vbscript:` in `href` | _(attribute removed)_ |
882
+
883
+ Tags not on the allowlist are silently dropped. Text content outside tags is HTML-escaped.
884
+
885
+ ---
886
+
708
887
  ## Offline validator
709
888
 
710
889
  Validate softcode expressions without a server connection. Catches structural errors (unbalanced parens/brackets), wrong argument counts, and unknown built-in functions.
@@ -1232,6 +1411,7 @@ See [ROADMAP.md](./ROADMAP.md) for full details and implementation notes.
1232
1411
  | v1.2.0 | Register clobber analyzer · Deploy pipeline with rollback · Dialect compatibility report |
1233
1412
  | v1.3.0 | **Softcode formatter** (`rhost-testkit fmt`) · **Benchmark mode** (`RhostBenchmark`) |
1234
1413
  | v1.4.0 | **PostgreSQL sidecar** (`docker-compose.yml`) · **`execscript` Jobs bridge** (`scripts/jobs_db.py`) · **`rhost.config.json`** custom scripts dir + mush config · `softcode/` directory |
1414
+ | v1.6.0 | **`rhost-server` CLI** — standalone server launcher · **WebSocket client support** (`useWebSocket`, `websocketPath`, `websocketSecure`) · **Build flags + stunnel** in `RhostContainer` and `rhost.config.json` · **Pueblo converter** (`convertPueblo`, `generatePuebloHandshake`, `parsePuebloHandshake`) |
1235
1415
 
1236
1416
  ### Planned
1237
1417
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rhost/testkit",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "description": "SDK for programmatically testing MUSHcode against a RhostMUSH server",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",