@rhost/testkit 0.1.0 → 1.3.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 (110) hide show
  1. package/README.md +393 -5
  2. package/ROADMAP.md +241 -0
  3. package/dist/benchmark.d.ts +44 -0
  4. package/dist/benchmark.d.ts.map +1 -0
  5. package/dist/benchmark.js +118 -0
  6. package/dist/benchmark.js.map +1 -0
  7. package/dist/cli/deploy.d.ts +2 -0
  8. package/dist/cli/deploy.d.ts.map +1 -0
  9. package/dist/cli/deploy.js +120 -0
  10. package/dist/cli/deploy.js.map +1 -0
  11. package/dist/cli/fmt.d.ts +2 -0
  12. package/dist/cli/fmt.d.ts.map +1 -0
  13. package/dist/cli/fmt.js +119 -0
  14. package/dist/cli/fmt.js.map +1 -0
  15. package/dist/cli/index.d.ts +3 -0
  16. package/dist/cli/index.d.ts.map +1 -0
  17. package/dist/cli/index.js +81 -0
  18. package/dist/cli/index.js.map +1 -0
  19. package/dist/cli/init.d.ts +2 -0
  20. package/dist/cli/init.d.ts.map +1 -0
  21. package/dist/cli/init.js +210 -0
  22. package/dist/cli/init.js.map +1 -0
  23. package/dist/cli/validate.d.ts +2 -0
  24. package/dist/cli/validate.d.ts.map +1 -0
  25. package/dist/cli/validate.js +126 -0
  26. package/dist/cli/validate.js.map +1 -0
  27. package/dist/cli/watch.d.ts +2 -0
  28. package/dist/cli/watch.d.ts.map +1 -0
  29. package/dist/cli/watch.js +136 -0
  30. package/dist/cli/watch.js.map +1 -0
  31. package/dist/client.d.ts +48 -0
  32. package/dist/client.d.ts.map +1 -1
  33. package/dist/client.js +113 -30
  34. package/dist/client.js.map +1 -1
  35. package/dist/container.d.ts.map +1 -1
  36. package/dist/container.js +1 -1
  37. package/dist/container.js.map +1 -1
  38. package/dist/deployer.d.ts +86 -0
  39. package/dist/deployer.d.ts.map +1 -0
  40. package/dist/deployer.js +154 -0
  41. package/dist/deployer.js.map +1 -0
  42. package/dist/expect.d.ts +27 -1
  43. package/dist/expect.d.ts.map +1 -1
  44. package/dist/expect.js +47 -2
  45. package/dist/expect.js.map +1 -1
  46. package/dist/index.d.ts +10 -3
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +39 -1
  49. package/dist/index.js.map +1 -1
  50. package/dist/preflight.d.ts +70 -0
  51. package/dist/preflight.d.ts.map +1 -0
  52. package/dist/preflight.js +121 -0
  53. package/dist/preflight.js.map +1 -0
  54. package/dist/reporter.d.ts +2 -0
  55. package/dist/reporter.d.ts.map +1 -1
  56. package/dist/reporter.js +24 -1
  57. package/dist/reporter.js.map +1 -1
  58. package/dist/runner.d.ts +83 -2
  59. package/dist/runner.d.ts.map +1 -1
  60. package/dist/runner.js +137 -8
  61. package/dist/runner.js.map +1 -1
  62. package/dist/snapshots.d.ts +84 -0
  63. package/dist/snapshots.d.ts.map +1 -0
  64. package/dist/snapshots.js +230 -0
  65. package/dist/snapshots.js.map +1 -0
  66. package/dist/validator/builtins.d.ts +18 -0
  67. package/dist/validator/builtins.d.ts.map +1 -0
  68. package/dist/validator/builtins.js +265 -0
  69. package/dist/validator/builtins.js.map +1 -0
  70. package/dist/validator/checker.d.ts +13 -0
  71. package/dist/validator/checker.d.ts.map +1 -0
  72. package/dist/validator/checker.js +111 -0
  73. package/dist/validator/checker.js.map +1 -0
  74. package/dist/validator/clobber.d.ts +7 -0
  75. package/dist/validator/clobber.d.ts.map +1 -0
  76. package/dist/validator/clobber.js +102 -0
  77. package/dist/validator/clobber.js.map +1 -0
  78. package/dist/validator/compat.d.ts +19 -0
  79. package/dist/validator/compat.d.ts.map +1 -0
  80. package/dist/validator/compat.js +58 -0
  81. package/dist/validator/compat.js.map +1 -0
  82. package/dist/validator/formatter.d.ts +21 -0
  83. package/dist/validator/formatter.d.ts.map +1 -0
  84. package/dist/validator/formatter.js +120 -0
  85. package/dist/validator/formatter.js.map +1 -0
  86. package/dist/validator/index.d.ts +57 -0
  87. package/dist/validator/index.d.ts.map +1 -0
  88. package/dist/validator/index.js +133 -0
  89. package/dist/validator/index.js.map +1 -0
  90. package/dist/validator/parser.d.ts +16 -0
  91. package/dist/validator/parser.d.ts.map +1 -0
  92. package/dist/validator/parser.js +260 -0
  93. package/dist/validator/parser.js.map +1 -0
  94. package/dist/validator/tokenizer.d.ts +28 -0
  95. package/dist/validator/tokenizer.d.ts.map +1 -0
  96. package/dist/validator/tokenizer.js +174 -0
  97. package/dist/validator/tokenizer.js.map +1 -0
  98. package/dist/validator/types.d.ts +55 -0
  99. package/dist/validator/types.d.ts.map +1 -0
  100. package/dist/validator/types.js +6 -0
  101. package/dist/validator/types.js.map +1 -0
  102. package/dist/watcher.d.ts +44 -0
  103. package/dist/watcher.d.ts.map +1 -0
  104. package/dist/watcher.js +297 -0
  105. package/dist/watcher.js.map +1 -0
  106. package/dist/world.d.ts +79 -0
  107. package/dist/world.d.ts.map +1 -1
  108. package/dist/world.js +167 -1
  109. package/dist/world.js.map +1 -1
  110. package/package.json +19 -3
package/ROADMAP.md ADDED
@@ -0,0 +1,241 @@
1
+ # @rhost/testkit Roadmap
2
+
3
+ Features are ranked by priority — highest value, lowest friction first.
4
+ Shipped items are marked ✅.
5
+
6
+ ---
7
+
8
+ ## ✅ v0.2.0 — Offline Validator + Watch Mode
9
+
10
+ **Offline Softcode Validator** (`validate`, `validateFile`, `rhost-testkit validate`)
11
+ - Tokenizer → Parser → Semantic checker pipeline, no server connection needed
12
+ - Catches unbalanced parens/brackets, unknown functions, wrong argument counts
13
+ - 150+ built-in function signatures with min/max arg counts
14
+ - CLI with human-readable and `--json` output; exit code 0/1
15
+
16
+ **Watch Mode** (`rhost-testkit watch`)
17
+ - Auto-discovers `*.test.ts` / `*.spec.ts` files
18
+ - Re-runs changed files on save with 300ms debounce
19
+ - Persistent terminal output with clear-between-runs
20
+ - Spawns `ts-node --transpile-only` for TypeScript test files
21
+
22
+ ---
23
+
24
+ ## ✅ v1.0.0 — Snapshot Testing + Extended World API + CI/CD Templates
25
+
26
+ **Snapshot Testing** — `await expect('iter(lnum(1,10),##)').toMatchSnapshot()`
27
+ - Stored in `__snapshots__/<testfile>.snap` JSON files, auto-located
28
+ - First run writes; subsequent runs compare with diff output on mismatch
29
+ - `RHOST_UPDATE_SNAPSHOTS=1` or `updateSnapshots: true` in options to refresh
30
+ - Obsolete snapshot detection with trimming on update
31
+
32
+ **Extended World API** — closes remaining gaps in `RhostWorld`:
33
+ `pemit` · `remit` · `force` · `parent` · `zone` · `addToChannel` · `grantQuota` · `wait` · `mail`
34
+
35
+ **CI/CD Templates** — `rhost-testkit init --ci github|gitlab`
36
+ - Generates `.github/workflows/mush-tests.yml` or `.gitlab-ci.yml`
37
+ - Lowers the barrier to "tests in CI" to near zero
38
+
39
+ ---
40
+
41
+ ## ✅ v1.1.0 — Tier 1 Features
42
+
43
+ ### ✅ 1. Server Pre-flight Assertions
44
+
45
+ Verify the server's configuration matches what your softcode requires, before deploying or running tests. Especially valuable in CI where the target server may differ from dev.
46
+
47
+ ```typescript
48
+ import { preflight } from '@rhost/testkit';
49
+
50
+ await preflight(client, [
51
+ assertFunctionExists('json'),
52
+ assertFunctionExists('lsearch'),
53
+ assertServerFlag('FUNCTION_SIDE_EFFECTS', false),
54
+ assertAttributeLimit(500),
55
+ ]);
56
+ // Throws a summary error if any check fails — stops CI before tests run
57
+ ```
58
+
59
+ - Zero new infrastructure — pure `client.eval()` calls
60
+ - Natural extension of the CI/CD templates just shipped
61
+ - Returns a structured report: passed / failed / skipped
62
+
63
+ ---
64
+
65
+ ### ✅ 2. Multi-Persona Test Matrix
66
+
67
+ The single biggest blind spot in MUSH testing: everyone tests as Wizard, so permission bugs are invisible. Define a test once and run it against multiple permission levels, asserting which outputs should match and which should differ.
68
+
69
+ ```typescript
70
+ runner.describe('Room visibility', ({ personas }) => {
71
+ personas(
72
+ ['mortal', 'builder', 'wizard'],
73
+ 'hidden room is only visible to builders+',
74
+ async ({ expect, persona }) => {
75
+ if (persona === 'mortal') {
76
+ await expect(`lsearch(me,type,room,eval,isvisible(%#,%#))`).toBe('0');
77
+ } else {
78
+ await expect(`lsearch(me,type,room,eval,isvisible(%#,%#))`).not.toBe('0');
79
+ }
80
+ }
81
+ );
82
+ });
83
+ ```
84
+
85
+ - Runner connects as each persona in sequence using separate credentials
86
+ - Clearly marks which permission level a failure occurred at
87
+ - No other MUSH tool — including PennMUSH's `kilt` — does this
88
+ - Source: documented as the #1 class of bugs missed in wizard-only testing
89
+
90
+ ---
91
+
92
+ ### ✅ 3. Side-Effect Assertion Mode
93
+
94
+ MUSHcode's "function side effects" feature means calling a function for its return value can secretly create objects, write attributes, or emit to players. Tests today only check return values. This catches the invisible class.
95
+
96
+ ```typescript
97
+ it('add() has no side effects', async ({ expect }) => {
98
+ await expect('add(2,3)')
99
+ .toBe('5')
100
+ .withNoSideEffects(); // fails if any object was created/modified
101
+ });
102
+
103
+ it('this attr must only emit to enactor', async ({ expect, world }) => {
104
+ const obj = await world.create('Greeter');
105
+ await world.set(obj, 'GREET', '@pemit %#=hello');
106
+ await expect.sideEffects(async () => {
107
+ await world.trigger(obj, 'GREET');
108
+ }).toOnlyEmitTo('me');
109
+ });
110
+ ```
111
+
112
+ - Implemented by snapshotting world state (dbrefs + attributes) before/after eval
113
+ - `world` already tracks creation — extend to also track attribute writes
114
+ - Analogous to Jest's `expect(fn).not.toHaveBeenCalled()` spy assertions
115
+ - Source: described as "Satan's spawn" in MUSH security community
116
+
117
+ ---
118
+
119
+ ## ✅ v1.2.0 — Tier 2 Features
120
+
121
+ ### ✅ 4. Register Clobber / Re-entrancy Analyzer
122
+
123
+ `%q0`–`%q9` registers are scoped per queue entry. Attributes called inside `iter()`, `@dolist`, or recursive `@trigger` chains that also write registers can silently clobber each other — the softcode equivalent of a race condition. This is a static analysis pass in the offline validator.
124
+
125
+ ```
126
+ $ rhost-testkit validate --file combat.mush
127
+
128
+ WARNING: register clobber risk
129
+ COMBAT_ROLL writes %q0 and is called inside iter() at line 14.
130
+ Concurrent invocations will overwrite each other's %q0.
131
+ Consider wrapping in localize().
132
+ ```
133
+
134
+ - Pure static analysis — no server needed
135
+ - Extends the existing semantic checker
136
+ - Source: documented in practical-mush-coding guides as a frequent silent bug
137
+
138
+ ---
139
+
140
+ ### ✅ 5. Deploy Pipeline with Rollback
141
+
142
+ The current softcode deployment workflow is manual paste-and-pray with no rollback. This addresses it directly.
143
+
144
+ ```bash
145
+ npx rhost-testkit deploy --file mycode.mush --target '#42' --test
146
+ ```
147
+
148
+ 1. **Snapshot** — `@decomp` all target objects, store attribute state
149
+ 2. **Upload** — apply the softcode file to the target object(s)
150
+ 3. **Test** — run the test suite
151
+ 4. **Rollback** — if tests fail, restore every attribute to pre-deploy state atomically
152
+
153
+ - Source: Faraday (AresMUSH creator) cited explicitly: *"when something breaks, the players are immediately affected — there's no form of revision history"*
154
+ - Highest real-world operational impact of anything on this list
155
+ - Most complex to implement correctly (ordering, partial failure, `@decomp` parsing)
156
+
157
+ ---
158
+
159
+ ### ✅ 6. Dialect Compatibility Report
160
+
161
+ PennMUSH 1.8.5 is compatible with 359 community softcode files; RhostMUSH 3.9.4 with only 119. Developers sharing code across servers have no way to know which functions are portable. Given a softcode file, emit a compatibility report.
162
+
163
+ ```bash
164
+ $ rhost-testkit validate --compat mycode.mush
165
+
166
+ DIALECT COMPATIBILITY
167
+ ✓ add, sub, mul, div — all platforms
168
+ ✓ iter, lnum, sort — all platforms
169
+ ⚠ localize() — RhostMUSH, PennMUSH only (not TinyMUX)
170
+ ✗ json(), jsonquery() — RhostMUSH only
171
+ ✗ cluster_set(), cluster_get() — RhostMUSH only
172
+ ```
173
+
174
+ - The function signature table already exists in the validator — add a `platforms` field per function
175
+ - Community-wide value: like a browser-compat table for softcode
176
+ - Source: documented cross-platform portability failures across PennMUSH/TinyMUX/Rhost
177
+
178
+ ---
179
+
180
+ ## ✅ v1.3.0 — Tier 3 Features (Batch 1)
181
+
182
+ ### ✅ 7. Benchmark Mode
183
+
184
+ Profile softcode performance against a live server:
185
+
186
+ ```typescript
187
+ runner.bench('Heavy iter', async ({ client }) => {
188
+ await client.eval('iter(lnum(1,1000),##)');
189
+ }, { iterations: 100, warmup: 10 });
190
+ ```
191
+
192
+ - Reports median, p95, p99 latency per expression
193
+ - Compares before/after across runs (regression detection)
194
+ - **Recursion depth profiler** (extension): track max call depth reached, warn when approaching server limits — gives context to opaque `#-1 FUNCTION RECURSION LIMIT` errors
195
+ - No other MUSH tooling exists for this
196
+
197
+ ---
198
+
199
+ ### ✅ 8. Softcode Formatter
200
+
201
+ ```bash
202
+ npx rhost-testkit fmt mycode.mush
203
+ npx rhost-testkit fmt --check # exit non-zero if not formatted
204
+ ```
205
+
206
+ - Normalise spacing around `(`, `,`, `)`
207
+ - Indent nested function calls for readability
208
+ - Like Prettier, but for MUSH softcode
209
+
210
+ ---
211
+
212
+ ## Planned — Tier 3 (Remaining)
213
+
214
+ ### 9. Test Coverage Tracking
215
+
216
+ - Track which attributes on which objects were exercised during a run
217
+ - Produce a coverage report: tested vs. untested softcode attributes
218
+ - Similar to Istanbul/c8 for JavaScript
219
+ - Novel in the MUSH ecosystem
220
+
221
+ ---
222
+
223
+ ### 10. Interactive REPL
224
+
225
+ ```bash
226
+ npx rhost-testkit repl [--host localhost] [--port 4201]
227
+ ```
228
+
229
+ - Persistent MUSH connection with readline history
230
+ - Tab completion for known built-in function names
231
+ - Full world API available inline (create/destroy objects, set attributes)
232
+ - Faster than raw telnet; smarter than `think` in a MUSH client
233
+
234
+ ---
235
+
236
+ ### 11. Parallel Test Execution
237
+
238
+ - Run independent `describe` blocks concurrently on separate connections
239
+ - Connection-per-suite model fits naturally with the current architecture
240
+ - Significant speedup for large codebases with independent modules
241
+ - Complex to implement correctly (world cleanup, reporter ordering)
@@ -0,0 +1,44 @@
1
+ import { RhostClient } from './client';
2
+ export interface BenchOptions {
3
+ /** Human-readable label. Defaults to the expression string. */
4
+ name?: string;
5
+ /** Number of measured iterations (after warmup). Default: 100. */
6
+ iterations?: number;
7
+ /** Number of un-measured warmup runs. Default: 10. */
8
+ warmup?: number;
9
+ }
10
+ export interface BenchmarkResult {
11
+ name: string;
12
+ iterations: number;
13
+ warmup: number;
14
+ /** All measured samples in milliseconds, in run order. */
15
+ samples: number[];
16
+ mean: number;
17
+ median: number;
18
+ p95: number;
19
+ p99: number;
20
+ min: number;
21
+ max: number;
22
+ }
23
+ /**
24
+ * Run a single softcode expression against `client` and return timing stats.
25
+ */
26
+ export declare function runBench(client: RhostClient, expression: string, options?: BenchOptions): Promise<BenchmarkResult>;
27
+ export declare class RhostBenchmark {
28
+ private readonly client;
29
+ private readonly entries;
30
+ constructor(client: RhostClient);
31
+ /**
32
+ * Register a benchmark expression. Returns `this` for chaining.
33
+ */
34
+ add(expression: string, options?: BenchOptions): this;
35
+ /**
36
+ * Run all registered benchmarks in sequence and return their results.
37
+ */
38
+ run(): Promise<BenchmarkResult[]>;
39
+ }
40
+ /**
41
+ * Format an array of BenchmarkResults into a human-readable table string.
42
+ */
43
+ export declare function formatBenchResults(results: BenchmarkResult[]): string;
44
+ //# sourceMappingURL=benchmark.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"benchmark.d.ts","sourceRoot":"","sources":["../src/benchmark.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAMvC,MAAM,WAAW,YAAY;IACzB,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACf;AAMD;;GAEG;AACH,wBAAsB,QAAQ,CAC1B,MAAM,EAAE,WAAW,EACnB,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,YAAiB,GAC3B,OAAO,CAAC,eAAe,CAAC,CA0B1B;AAWD,qBAAa,cAAc;IAGX,OAAO,CAAC,QAAQ,CAAC,MAAM;IAFnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;gBAEf,MAAM,EAAE,WAAW;IAEhD;;OAEG;IACH,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,IAAI;IAKzD;;OAEG;IACG,GAAG,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;CAQ1C;AAMD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,MAAM,CAqBrE"}
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ // ---------------------------------------------------------------------------
3
+ // Benchmark Mode — profile softcode performance against a live server
4
+ // ---------------------------------------------------------------------------
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.RhostBenchmark = void 0;
7
+ exports.runBench = runBench;
8
+ exports.formatBenchResults = formatBenchResults;
9
+ // ---------------------------------------------------------------------------
10
+ // runBench — low-level timing primitive
11
+ // ---------------------------------------------------------------------------
12
+ /**
13
+ * Run a single softcode expression against `client` and return timing stats.
14
+ */
15
+ async function runBench(client, expression, options = {}) {
16
+ const name = options.name ?? expression;
17
+ const iterations = options.iterations ?? 100;
18
+ const warmup = options.warmup ?? 10;
19
+ // Warmup runs — not measured
20
+ for (let i = 0; i < warmup; i++) {
21
+ await client.eval(expression);
22
+ }
23
+ // Measured iterations
24
+ const samples = [];
25
+ for (let i = 0; i < iterations; i++) {
26
+ const start = performance.now();
27
+ await client.eval(expression);
28
+ const elapsed = performance.now() - start;
29
+ samples.push(elapsed);
30
+ }
31
+ return {
32
+ name,
33
+ iterations,
34
+ warmup,
35
+ samples,
36
+ ...computeStats(samples),
37
+ };
38
+ }
39
+ class RhostBenchmark {
40
+ constructor(client) {
41
+ this.client = client;
42
+ this.entries = [];
43
+ }
44
+ /**
45
+ * Register a benchmark expression. Returns `this` for chaining.
46
+ */
47
+ add(expression, options = {}) {
48
+ this.entries.push({ expression, options });
49
+ return this;
50
+ }
51
+ /**
52
+ * Run all registered benchmarks in sequence and return their results.
53
+ */
54
+ async run() {
55
+ const results = [];
56
+ for (const { expression, options } of this.entries) {
57
+ const result = await runBench(this.client, expression, options);
58
+ results.push(result);
59
+ }
60
+ return results;
61
+ }
62
+ }
63
+ exports.RhostBenchmark = RhostBenchmark;
64
+ // ---------------------------------------------------------------------------
65
+ // formatBenchResults — human-readable report
66
+ // ---------------------------------------------------------------------------
67
+ /**
68
+ * Format an array of BenchmarkResults into a human-readable table string.
69
+ */
70
+ function formatBenchResults(results) {
71
+ if (results.length === 0) {
72
+ return 'No benchmark results.';
73
+ }
74
+ const lines = ['Benchmark Results', '─'.repeat(72)];
75
+ for (const r of results) {
76
+ lines.push('');
77
+ lines.push(` ${r.name}`);
78
+ lines.push(` iterations: ${r.iterations} warmup: ${r.warmup}`);
79
+ lines.push(` median: ${fmt(r.median)}ms mean: ${fmt(r.mean)}ms` +
80
+ ` p95: ${fmt(r.p95)}ms p99: ${fmt(r.p99)}ms`);
81
+ lines.push(` min: ${fmt(r.min)}ms max: ${fmt(r.max)}ms`);
82
+ }
83
+ lines.push('');
84
+ lines.push('─'.repeat(72));
85
+ return lines.join('\n');
86
+ }
87
+ // ---------------------------------------------------------------------------
88
+ // Internal stats helpers
89
+ // ---------------------------------------------------------------------------
90
+ function computeStats(samples) {
91
+ if (samples.length === 0) {
92
+ return { mean: 0, median: 0, p95: 0, p99: 0, min: 0, max: 0 };
93
+ }
94
+ const sorted = [...samples].sort((a, b) => a - b);
95
+ const mean = samples.reduce((s, v) => s + v, 0) / samples.length;
96
+ return {
97
+ mean,
98
+ median: percentile(sorted, 50),
99
+ p95: percentile(sorted, 95),
100
+ p99: percentile(sorted, 99),
101
+ min: sorted[0],
102
+ max: sorted[sorted.length - 1],
103
+ };
104
+ }
105
+ /** Compute the p-th percentile of a pre-sorted ascending array. */
106
+ function percentile(sorted, p) {
107
+ if (sorted.length === 1)
108
+ return sorted[0];
109
+ const index = (p / 100) * (sorted.length - 1);
110
+ const lower = Math.floor(index);
111
+ const upper = Math.ceil(index);
112
+ const weight = index - lower;
113
+ return sorted[lower] * (1 - weight) + sorted[upper] * weight;
114
+ }
115
+ function fmt(n) {
116
+ return n.toFixed(3);
117
+ }
118
+ //# sourceMappingURL=benchmark.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"benchmark.js","sourceRoot":"","sources":["../src/benchmark.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function runDeployCli(args: string[], cwd?: string): void;
2
+ //# sourceMappingURL=deploy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/cli/deploy.ts"],"names":[],"mappings":"AAYA,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,GAAE,MAAsB,GAAG,IAAI,CAwD9E"}
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ // ---------------------------------------------------------------------------
3
+ // rhost-testkit deploy — softcode deploy pipeline with rollback
4
+ // ---------------------------------------------------------------------------
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.runDeployCli = runDeployCli;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const deployer_1 = require("../deployer");
43
+ // ---------------------------------------------------------------------------
44
+ // Public entry point (testable with injected cwd)
45
+ // ---------------------------------------------------------------------------
46
+ function runDeployCli(args, cwd = process.cwd()) {
47
+ // --help
48
+ if (args.includes('--help') || args.includes('-h')) {
49
+ printHelp();
50
+ process.exit(0);
51
+ }
52
+ // Parse flags
53
+ const fileIndex = args.indexOf('--file');
54
+ const filePath = fileIndex !== -1 ? args[fileIndex + 1] : undefined;
55
+ const dryRun = args.includes('--dry-run');
56
+ const parseOnly = args.includes('--parse-only');
57
+ if (!filePath) {
58
+ console.error('rhost-testkit deploy: --file <path> is required\n');
59
+ printHelp();
60
+ process.exit(1);
61
+ }
62
+ const resolved = path.resolve(cwd, filePath);
63
+ if (!fs.existsSync(resolved)) {
64
+ console.error(`rhost-testkit deploy: file not found: ${resolved}`);
65
+ process.exit(1);
66
+ }
67
+ const content = fs.readFileSync(resolved, 'utf8');
68
+ const commands = (0, deployer_1.parseDeployFile)(content);
69
+ // --parse-only: validate file contents and exit
70
+ if (parseOnly) {
71
+ if (commands.length === 0) {
72
+ console.error('rhost-testkit deploy: no commands found in file');
73
+ process.exit(1);
74
+ }
75
+ console.log(`rhost-testkit deploy: ${commands.length} command(s) parsed OK`);
76
+ for (const cmd of commands) {
77
+ console.log(` &${cmd.attr} ${cmd.dbref}=<value>`);
78
+ }
79
+ process.exit(0);
80
+ }
81
+ // --dry-run: show what would be applied without connecting
82
+ if (dryRun) {
83
+ console.log(`rhost-testkit deploy (dry-run): ${commands.length} command(s) would be applied`);
84
+ for (const cmd of commands) {
85
+ console.log(` &${cmd.attr} ${cmd.dbref}=${cmd.value}`);
86
+ }
87
+ return;
88
+ }
89
+ // Live deploy requires connection flags — remind the user
90
+ console.error('rhost-testkit deploy: live deploy requires --host, --port, --user, --pass.\n' +
91
+ 'Use --dry-run to preview without connecting, or the programmatic API for full deploy.');
92
+ process.exit(1);
93
+ }
94
+ // ---------------------------------------------------------------------------
95
+ // Help text
96
+ // ---------------------------------------------------------------------------
97
+ function printHelp() {
98
+ console.log(`
99
+ USAGE
100
+ rhost-testkit deploy --file <path> [options]
101
+
102
+ OPTIONS
103
+ --file <path> Path to softcode file (required)
104
+ --dry-run Preview commands without connecting to the server
105
+ --parse-only Validate the file format and exit
106
+ -h, --help Show this help
107
+
108
+ FILE FORMAT
109
+ Lines of the form: &ATTRNAME #DBREF=value
110
+ Lines starting with # or @@ are treated as comments.
111
+
112
+ EXAMPLES
113
+ rhost-testkit deploy --file mycode.mush --dry-run
114
+ rhost-testkit deploy --file mycode.mush --parse-only
115
+
116
+ For live deployment with rollback, use the programmatic API:
117
+ import { deploy, parseDeployFile } from '@rhost/testkit';
118
+ `.trim());
119
+ }
120
+ //# sourceMappingURL=deploy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deploy.js","sourceRoot":"","sources":["../../src/cli/deploy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function runFmtCli(args: string[], cwd?: string): void;
2
+ //# sourceMappingURL=fmt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fmt.d.ts","sourceRoot":"","sources":["../../src/cli/fmt.ts"],"names":[],"mappings":"AAYA,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,GAAE,MAAsB,GAAG,IAAI,CAyD3E"}
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ // ---------------------------------------------------------------------------
3
+ // rhost-testkit fmt — softcode formatter
4
+ // ---------------------------------------------------------------------------
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.runFmtCli = runFmtCli;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const formatter_1 = require("../validator/formatter");
43
+ // ---------------------------------------------------------------------------
44
+ // Public entry point
45
+ // ---------------------------------------------------------------------------
46
+ function runFmtCli(args, cwd = process.cwd()) {
47
+ if (args.includes('--help') || args.includes('-h')) {
48
+ printHelp();
49
+ process.exit(0);
50
+ }
51
+ const checkMode = args.includes('--check');
52
+ const pretty = args.includes('--pretty');
53
+ const lowercase = args.includes('--lowercase');
54
+ // Collect file paths (all non-flag args)
55
+ const files = args.filter(a => !a.startsWith('-'));
56
+ if (files.length === 0) {
57
+ // Read from stdin
58
+ const input = fs.readFileSync('/dev/stdin', 'utf8');
59
+ const result = (0, formatter_1.format)(input.trim(), { pretty, lowercase });
60
+ if (!checkMode) {
61
+ process.stdout.write(result.formatted + '\n');
62
+ }
63
+ if (checkMode && result.changed) {
64
+ process.stderr.write('<stdin>: not formatted\n');
65
+ process.exit(1);
66
+ }
67
+ return;
68
+ }
69
+ let anyChanged = false;
70
+ for (const file of files) {
71
+ const resolved = path.resolve(cwd, file);
72
+ if (!fs.existsSync(resolved)) {
73
+ console.error(`rhost-testkit fmt: file not found: ${resolved}`);
74
+ process.exit(1);
75
+ }
76
+ const content = fs.readFileSync(resolved, 'utf8');
77
+ const result = (0, formatter_1.format)(content.trim(), { pretty, lowercase });
78
+ if (checkMode) {
79
+ if (result.changed) {
80
+ console.error(`${file}: not formatted`);
81
+ anyChanged = true;
82
+ }
83
+ }
84
+ else {
85
+ if (result.changed) {
86
+ fs.writeFileSync(resolved, result.formatted + '\n', 'utf8');
87
+ console.log(`${file}: formatted`);
88
+ }
89
+ else {
90
+ console.log(`${file}: already formatted`);
91
+ }
92
+ }
93
+ }
94
+ if (checkMode && anyChanged) {
95
+ process.exit(1);
96
+ }
97
+ }
98
+ // ---------------------------------------------------------------------------
99
+ // Help
100
+ // ---------------------------------------------------------------------------
101
+ function printHelp() {
102
+ console.log(`
103
+ USAGE
104
+ rhost-testkit fmt [options] [file...]
105
+
106
+ OPTIONS
107
+ --check Exit non-zero if any file is not formatted (no writes)
108
+ --pretty Indent nested function calls for human readability
109
+ --lowercase Normalise function names to lowercase
110
+ -h, --help Show this help
111
+
112
+ EXAMPLES
113
+ rhost-testkit fmt mycode.mush
114
+ rhost-testkit fmt --check mycode.mush
115
+ rhost-testkit fmt --pretty mycode.mush
116
+ echo "add( 2, 3 )" | rhost-testkit fmt
117
+ `.trim());
118
+ }
119
+ //# sourceMappingURL=fmt.js.map