@rhost/testkit 0.1.1 → 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.
- package/README.md +393 -5
- package/ROADMAP.md +241 -0
- package/dist/benchmark.d.ts +44 -0
- package/dist/benchmark.d.ts.map +1 -0
- package/dist/benchmark.js +118 -0
- package/dist/benchmark.js.map +1 -0
- package/dist/cli/deploy.d.ts +2 -0
- package/dist/cli/deploy.d.ts.map +1 -0
- package/dist/cli/deploy.js +120 -0
- package/dist/cli/deploy.js.map +1 -0
- package/dist/cli/fmt.d.ts +2 -0
- package/dist/cli/fmt.d.ts.map +1 -0
- package/dist/cli/fmt.js +119 -0
- package/dist/cli/fmt.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +81 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +2 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +210 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/validate.d.ts +2 -0
- package/dist/cli/validate.d.ts.map +1 -0
- package/dist/cli/validate.js +126 -0
- package/dist/cli/validate.js.map +1 -0
- package/dist/cli/watch.d.ts +2 -0
- package/dist/cli/watch.d.ts.map +1 -0
- package/dist/cli/watch.js +136 -0
- package/dist/cli/watch.js.map +1 -0
- package/dist/client.d.ts +48 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +113 -30
- package/dist/client.js.map +1 -1
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +1 -1
- package/dist/container.js.map +1 -1
- package/dist/deployer.d.ts +86 -0
- package/dist/deployer.d.ts.map +1 -0
- package/dist/deployer.js +154 -0
- package/dist/deployer.js.map +1 -0
- package/dist/expect.d.ts +27 -1
- package/dist/expect.d.ts.map +1 -1
- package/dist/expect.js +47 -2
- package/dist/expect.js.map +1 -1
- package/dist/index.d.ts +10 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +39 -1
- package/dist/index.js.map +1 -1
- package/dist/preflight.d.ts +70 -0
- package/dist/preflight.d.ts.map +1 -0
- package/dist/preflight.js +121 -0
- package/dist/preflight.js.map +1 -0
- package/dist/reporter.d.ts +2 -0
- package/dist/reporter.d.ts.map +1 -1
- package/dist/reporter.js +24 -1
- package/dist/reporter.js.map +1 -1
- package/dist/runner.d.ts +83 -2
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +137 -8
- package/dist/runner.js.map +1 -1
- package/dist/snapshots.d.ts +84 -0
- package/dist/snapshots.d.ts.map +1 -0
- package/dist/snapshots.js +230 -0
- package/dist/snapshots.js.map +1 -0
- package/dist/validator/builtins.d.ts +18 -0
- package/dist/validator/builtins.d.ts.map +1 -0
- package/dist/validator/builtins.js +265 -0
- package/dist/validator/builtins.js.map +1 -0
- package/dist/validator/checker.d.ts +13 -0
- package/dist/validator/checker.d.ts.map +1 -0
- package/dist/validator/checker.js +111 -0
- package/dist/validator/checker.js.map +1 -0
- package/dist/validator/clobber.d.ts +7 -0
- package/dist/validator/clobber.d.ts.map +1 -0
- package/dist/validator/clobber.js +102 -0
- package/dist/validator/clobber.js.map +1 -0
- package/dist/validator/compat.d.ts +19 -0
- package/dist/validator/compat.d.ts.map +1 -0
- package/dist/validator/compat.js +58 -0
- package/dist/validator/compat.js.map +1 -0
- package/dist/validator/formatter.d.ts +21 -0
- package/dist/validator/formatter.d.ts.map +1 -0
- package/dist/validator/formatter.js +120 -0
- package/dist/validator/formatter.js.map +1 -0
- package/dist/validator/index.d.ts +57 -0
- package/dist/validator/index.d.ts.map +1 -0
- package/dist/validator/index.js +133 -0
- package/dist/validator/index.js.map +1 -0
- package/dist/validator/parser.d.ts +16 -0
- package/dist/validator/parser.d.ts.map +1 -0
- package/dist/validator/parser.js +260 -0
- package/dist/validator/parser.js.map +1 -0
- package/dist/validator/tokenizer.d.ts +28 -0
- package/dist/validator/tokenizer.d.ts.map +1 -0
- package/dist/validator/tokenizer.js +174 -0
- package/dist/validator/tokenizer.js.map +1 -0
- package/dist/validator/types.d.ts +55 -0
- package/dist/validator/types.d.ts.map +1 -0
- package/dist/validator/types.js +6 -0
- package/dist/validator/types.js.map +1 -0
- package/dist/watcher.d.ts +44 -0
- package/dist/watcher.d.ts.map +1 -0
- package/dist/watcher.js +297 -0
- package/dist/watcher.js.map +1 -0
- package/dist/world.d.ts +79 -0
- package/dist/world.d.ts.map +1 -1
- package/dist/world.js +167 -1
- package/dist/world.js.map +1 -1
- 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":";AAAA,8EAA8E;AAC9E,sEAAsE;AACtE,8EAA8E;;;AAsC9E,4BA8BC;AA4CD,gDAqBC;AAtGD,8EAA8E;AAC9E,wCAAwC;AACxC,8EAA8E;AAE9E;;GAEG;AACI,KAAK,UAAU,QAAQ,CAC1B,MAAmB,EACnB,UAAkB,EAClB,UAAwB,EAAE;IAE1B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,UAAU,CAAC;IACxC,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,GAAG,CAAC;IAC7C,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;IAEpC,6BAA6B;IAC7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9B,MAAM,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAClC,CAAC;IAED,sBAAsB;IACtB,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QAChC,MAAM,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC9B,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;QAC1C,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC1B,CAAC;IAED,OAAO;QACH,IAAI;QACJ,UAAU;QACV,MAAM;QACN,OAAO;QACP,GAAG,YAAY,CAAC,OAAO,CAAC;KAC3B,CAAC;AACN,CAAC;AAWD,MAAa,cAAc;IAGvB,YAA6B,MAAmB;QAAnB,WAAM,GAAN,MAAM,CAAa;QAF/B,YAAO,GAAiB,EAAE,CAAC;IAEO,CAAC;IAEpD;;OAEG;IACH,GAAG,CAAC,UAAkB,EAAE,UAAwB,EAAE;QAC9C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC;QAC3C,OAAO,IAAI,CAAC;IAChB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,GAAG;QACL,MAAM,OAAO,GAAsB,EAAE,CAAC;QACtC,KAAK,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;YAChE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACzB,CAAC;QACD,OAAO,OAAO,CAAC;IACnB,CAAC;CACJ;AAxBD,wCAwBC;AAED,8EAA8E;AAC9E,6CAA6C;AAC7C,8EAA8E;AAE9E;;GAEG;AACH,SAAgB,kBAAkB,CAAC,OAA0B;IACzD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,uBAAuB,CAAC;IACnC,CAAC;IAED,MAAM,KAAK,GAAa,CAAC,mBAAmB,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;IAE9D,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACtB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC1B,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,UAAU,aAAa,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;QACjE,KAAK,CAAC,IAAI,CACN,aAAa,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI;YACtD,UAAU,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CACjD,CAAC;QACF,KAAK,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC/D,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;IAC3B,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC;AAED,8EAA8E;AAC9E,yBAAyB;AACzB,8EAA8E;AAE9E,SAAS,YAAY,CAAC,OAAiB;IACnC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;IAClE,CAAC;IAED,MAAM,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAClD,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAEjE,OAAO;QACH,IAAI;QACJ,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC;QAC9B,GAAG,EAAE,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC;QAC3B,GAAG,EAAE,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC;QAC3B,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;QACd,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;KACjC,CAAC;AACN,CAAC;AAED,mEAAmE;AACnE,SAAS,UAAU,CAAC,MAAgB,EAAE,CAAS;IAC3C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAChC,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAG,KAAK,GAAG,KAAK,CAAC;IAC7B,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC;AACjE,CAAC;AAED,SAAS,GAAG,CAAC,CAAS;IAClB,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AACxB,CAAC"}
|
|
@@ -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 @@
|
|
|
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"}
|
package/dist/cli/fmt.js
ADDED
|
@@ -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
|