@tantawowa/hosanna-tools 3.8.0 → 3.10.0
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 +108 -20
- package/dist/build-info.json +3 -3
- package/dist/cli.js +87 -142
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +3 -9
- package/dist/index.js +3 -12
- package/dist/index.js.map +1 -1
- package/dist/run/apple-tv.js +14 -12
- package/dist/run/apple-tv.js.map +1 -1
- package/dist/run/destination-picker.d.ts +24 -0
- package/dist/run/destination-picker.js +180 -0
- package/dist/run/destination-picker.js.map +1 -0
- package/dist/run/ios-device.js +7 -6
- package/dist/run/ios-device.js.map +1 -1
- package/dist/run/ios-simulator.js +7 -6
- package/dist/run/ios-simulator.js.map +1 -1
- package/dist/run/native-build.d.ts +3 -0
- package/dist/run/native-build.js +8 -0
- package/dist/run/native-build.js.map +1 -1
- package/dist/run/planner.js +10 -8
- package/dist/run/planner.js.map +1 -1
- package/dist/run/run-config.d.ts +9 -0
- package/dist/run/run-config.js +43 -3
- package/dist/run/run-config.js.map +1 -1
- package/dist/run/run-controller.js +10 -1
- package/dist/run/run-controller.js.map +1 -1
- package/dist/support-tools/android-metro-build.d.ts +3 -0
- package/dist/support-tools/android-metro-build.js +57 -2
- package/dist/support-tools/android-metro-build.js.map +1 -1
- package/dist/support-tools/apple-bundler.js +20 -8
- package/dist/support-tools/apple-bundler.js.map +1 -1
- package/dist/support-tools/build-config-resolver.d.ts +1 -0
- package/dist/support-tools/build-config-resolver.js +38 -9
- package/dist/support-tools/build-config-resolver.js.map +1 -1
- package/dist/support-tools/command-debugger.d.ts +1 -0
- package/dist/support-tools/command-debugger.js +6 -0
- package/dist/support-tools/command-debugger.js.map +1 -1
- package/dist/support-tools/dev-runner.d.ts +1 -0
- package/dist/support-tools/dev-runner.js +3 -3
- package/dist/support-tools/dev-runner.js.map +1 -1
- package/dist/support-tools/mcp-server/{cli-test-recorder.d.ts → rasp-capture.d.ts} +7 -7
- package/dist/support-tools/mcp-server/{cli-test-recorder.js → rasp-capture.js} +58 -58
- package/dist/support-tools/mcp-server/rasp-capture.js.map +1 -0
- package/dist/support-tools/mcp-server/rasp-exporter.d.ts +12 -12
- package/dist/support-tools/mcp-server/rasp-exporter.js +23 -23
- package/dist/support-tools/mcp-server/rasp-exporter.js.map +1 -1
- package/dist/support-tools/mcp-server/rasp-input-capture.d.ts +60 -0
- package/dist/support-tools/mcp-server/rasp-input-capture.js +100 -0
- package/dist/support-tools/mcp-server/rasp-input-capture.js.map +1 -0
- package/dist/support-tools/mcp-server/roku-device-client.d.ts +9 -0
- package/dist/support-tools/mcp-server/roku-device-client.js +17 -2
- package/dist/support-tools/mcp-server/roku-device-client.js.map +1 -1
- package/dist/support-tools/mcp-server/roku-log-client.d.ts +2 -0
- package/dist/support-tools/mcp-server/roku-log-client.js +16 -2
- package/dist/support-tools/mcp-server/roku-log-client.js.map +1 -1
- package/dist/support-tools/mcp-server/tools.js +30 -679
- package/dist/support-tools/mcp-server/tools.js.map +1 -1
- package/dist/support-tools/mcp-server/types.d.ts +4 -1
- package/dist/support-tools/mcp-server/types.js +3 -1
- package/dist/support-tools/mcp-server/types.js.map +1 -1
- package/dist/testing/client.d.ts +215 -0
- package/dist/testing/client.js +1029 -0
- package/dist/testing/client.js.map +1 -0
- package/dist/testing/config.d.ts +5 -0
- package/dist/testing/config.js +55 -0
- package/dist/testing/config.js.map +1 -0
- package/dist/testing/global-setup.d.ts +10 -0
- package/dist/testing/global-setup.js +39 -0
- package/dist/testing/global-setup.js.map +1 -0
- package/dist/testing/index.d.ts +7 -0
- package/dist/testing/index.js +26 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/launcher.d.ts +23 -0
- package/dist/testing/launcher.js +675 -0
- package/dist/testing/launcher.js.map +1 -0
- package/dist/testing/target.d.ts +16 -0
- package/dist/testing/target.js +40 -0
- package/dist/testing/target.js.map +1 -0
- package/dist/testing/types.d.ts +266 -0
- package/dist/testing/types.js +3 -0
- package/dist/testing/types.js.map +1 -0
- package/dist/testing/vitest-fixture.d.mts +28 -0
- package/dist/testing/vitest-fixture.mjs +87 -0
- package/dist/testing/vitest-fixture.mjs.map +1 -0
- package/dist/testing/vitest-reporter.d.mts +35 -0
- package/dist/testing/vitest-reporter.mjs +176 -0
- package/dist/testing/vitest-reporter.mjs.map +1 -0
- package/dist/testing/vitest.d.ts +1 -0
- package/dist/testing/vitest.js +8 -0
- package/dist/testing/vitest.js.map +1 -0
- package/dist/vite.d.ts +19 -0
- package/dist/vite.js +98 -5
- package/dist/vite.js.map +1 -1
- package/package.json +26 -1
- package/dist/support-tools/mcp-server/cli-test-recorder.js.map +0 -1
- package/dist/support-tools/mcp-server/cli-test-runner.d.ts +0 -37
- package/dist/support-tools/mcp-server/cli-test-runner.js +0 -332
- package/dist/support-tools/mcp-server/cli-test-runner.js.map +0 -1
- package/dist/support-tools/mcp-server/test-reporter.d.ts +0 -19
- package/dist/support-tools/mcp-server/test-reporter.js +0 -248
- package/dist/support-tools/mcp-server/test-reporter.js.map +0 -1
- package/dist/support-tools/mcp-server/test-runner.d.ts +0 -222
- package/dist/support-tools/mcp-server/test-runner.js +0 -686
- package/dist/support-tools/mcp-server/test-runner.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Documentation
|
|
4
4
|
|
|
5
|
-
- **[MCP debugger
|
|
5
|
+
- **[MCP debugger](src/support-tools/mcp-server/README.md)** — `hst mcp:start`, debugger tools, commands by use case, examples, and RASP certification capture.
|
|
6
6
|
- **[docs/README.md](docs/README.md)** — index of docs in this repo (MCP + source-map note).
|
|
7
7
|
- **hosanna-ui** (separate repo): `docs/README.md` for agentic debugging and links back to this README.
|
|
8
8
|
|
|
@@ -94,7 +94,7 @@ Common build-script replacements:
|
|
|
94
94
|
| `npx hst secrets:check` | `await secrets.check({ cwd: process.cwd() })` |
|
|
95
95
|
| `npx hst env check` | `await env.check({ cwd: process.cwd() })` |
|
|
96
96
|
|
|
97
|
-
Long-running APIs such as `dev.run`, `debugger.start`, `mcp.start`,
|
|
97
|
+
Long-running APIs such as `dev.run`, `debugger.start`, `mcp.start`, and RASP-owned certification capture workflows keep the same operational behavior as the corresponding CLI commands: they start services, attach to debuggers, or wait for user/session activity. `mcp.start` and `dev.run` are wired for programmatic no-exit behavior through the top-level API.
|
|
98
98
|
|
|
99
99
|
# Getting started
|
|
100
100
|
|
|
@@ -189,7 +189,7 @@ Hosanna Tools is a comprehensive CLI toolset for the Hosanna framework that prov
|
|
|
189
189
|
The CLI commands are organized into logical groups using colon-separated namespacing:
|
|
190
190
|
|
|
191
191
|
### Development Commands
|
|
192
|
-
- `run` - Canonical cross-platform app launcher.
|
|
192
|
+
- `run` - Canonical cross-platform app launcher. Bare interactive runs show one destination picker; explicit platform options support web, Roku, iOS, Apple TV, Android, and Android TV launch targets.
|
|
193
193
|
- `target:list` - List launch targets for humans or agents, including web emulators, sims, and physical devices.
|
|
194
194
|
- `dev:start` - Run dev processes: vite, generator watch, and optional debugger
|
|
195
195
|
- `debugger:start` - Start the command debugger WebSocket proxy (only one debugger is needed for multiple apps; if the port is already in use, a friendly message is shown instead of crashing)
|
|
@@ -197,6 +197,8 @@ The CLI commands are organized into logical groups using colon-separated namespa
|
|
|
197
197
|
Examples:
|
|
198
198
|
|
|
199
199
|
```bash
|
|
200
|
+
hst run
|
|
201
|
+
hst run --last
|
|
200
202
|
hst run --platform web
|
|
201
203
|
hst run --platform roku --target emulator
|
|
202
204
|
hst run --platform ios --target emulator
|
|
@@ -218,7 +220,9 @@ hst target:list --platform apple-tv --target sim --json
|
|
|
218
220
|
hst target:list --form-factor tv --json
|
|
219
221
|
```
|
|
220
222
|
|
|
221
|
-
`hst run`
|
|
223
|
+
Bare `hst run` in an interactive terminal discovers runnable web previews, vendor simulators, and online physical devices, then presents one flat destination picker. The last selection is placed first, so pressing Enter repeats it; `hst run --last` skips discovery and immediately uses that remembered destination. The selection is stored in the user-level `~/.hosanna-tools/run.json`, preserving existing defaults. Explicit commands remain deterministic, and non-interactive runs never open the picker.
|
|
224
|
+
|
|
225
|
+
`--target emulator` starts or reuses the shared Vite/debugger dev services for any platform and opens a profiled DOM runtime URL for interactive runs; use `--device <preset-id>` to override defaults such as `iphone-15`, `pixel-8`, `apple-tv-4k`, `android-tv-1080p`, or `rokufhd`, and use `--no-open` for server-only launches. `--target sim` launches the vendor virtual device for platforms that have one: Apple Simulator for iOS and Apple TV, Android Emulator for Android and Android TV. `--target device` launches or deploys to physical local hardware. `web` and `browser` remain accepted aliases for the web emulator; `simulator` and `simulated` remain accepted aliases for `sim`; `emulated` remains an accepted alias for the web emulator. Device/sim runs record the target and refuse to overwrite a recorded session or safe-to-kill occupied resource in non-interactive mode unless `--replace` is passed. Preferred run defaults live in `.hosanna-tools/run.json`, and preferred devices live in `.hosanna-tools/devices.json` or `~/.hosanna-tools/devices.json`. Legacy `.hs-devices.json` files are still read for compatibility.
|
|
222
226
|
|
|
223
227
|
### Roku Commands (`roku:*`) - Roku Deployment & Packaging
|
|
224
228
|
- `roku:run` - Deploy a Roku app to a device (supports .zip file or folder)
|
|
@@ -269,6 +273,8 @@ Supported inputs include `file/line: pkg:/components/source_0.brs(7475)`, `at ..
|
|
|
269
273
|
### Build Config Commands (`build-config`) - Runtime Configuration
|
|
270
274
|
- `build-config:resolve` - Resolve `build-config/base.json`, env/platform overlays, secrets, and optional developer profiles into the canonical runtime `build-config.json`
|
|
271
275
|
|
|
276
|
+
Production JavaScript bundles strip `console.*` calls by default. Configure this through the normal overlay chain with `build.stripLogs`: set it to `false` in `build-config/prod.json` to retain production logs, or in `build-config/prod.apple.json` / `build-config/prod.web.json` for a platform-only exception. Non-production environments keep logs unless their overlay explicitly enables stripping. Apple `prod` builds use Xcode `Release`; other environments continue to use `Debug`.
|
|
277
|
+
|
|
272
278
|
Native builds can resolve config immediately before the platform build:
|
|
273
279
|
|
|
274
280
|
```bash
|
|
@@ -380,9 +386,95 @@ silently break an install.
|
|
|
380
386
|
|
|
381
387
|
**Cursor and MCP:** Cursor does not magically attach to a terminal `hst mcp:start`. Add Hosanna as a Command MCP server (project **`.cursor/mcp.json`** or **Cursor Settings → MCP → Add Custom MCP**) so Cursor spawns **`hosanna-mcp`** over stdio. Start the command debugger/app explicitly with `hst run --platform web --target web`, `hst run --platform ios --target web`, `hst run --platform android-tv --target web`, or `hst run --platform roku --target device`.
|
|
382
388
|
|
|
383
|
-
###
|
|
384
|
-
|
|
385
|
-
-
|
|
389
|
+
### Deterministic UI testing
|
|
390
|
+
|
|
391
|
+
Applications own regular Vitest suites. Global setup comes from `@tantawowa/hosanna-tools/testing/vitest`; worker fixtures and matchers come from `@tantawowa/hosanna-tools/testing/vitest/fixture`; shared lifecycle, types, and helpers come from `@tantawowa/hosanna-tools/testing`. There is no HST test CLI or recording/replay format.
|
|
392
|
+
|
|
393
|
+
Add `createHosannaReporter(config)` from `@tantawowa/hosanna-tools/testing/vitest/reporter` to the application's Vitest reporters. Every run then writes a self-contained JSON and HTML report under `test-results/<testRunId>/`, updates `test-results/latest.json` and `test-results/latest.html`, and includes every Vitest result plus any named visual proofs captured by the tests.
|
|
394
|
+
|
|
395
|
+
Browser targets can also record the complete owned browser session as WebM:
|
|
396
|
+
|
|
397
|
+
```ts
|
|
398
|
+
defineHosannaTestConfig({
|
|
399
|
+
// ...
|
|
400
|
+
video: true,
|
|
401
|
+
// Or: video: { size: { width: 1280, height: 720 } },
|
|
402
|
+
});
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
The finalized recording is written to
|
|
406
|
+
`test-results/<testRunId>/<platform>-web/run.webm` and embedded in the HTML
|
|
407
|
+
report. Recording is opt-in in the shared tools and ignored for device and
|
|
408
|
+
simulator targets. It records browser video only, without audio. This is a
|
|
409
|
+
review artifact, not a replay format or executable test source.
|
|
410
|
+
|
|
411
|
+
```ts
|
|
412
|
+
import { defineConfig } from 'vitest/config';
|
|
413
|
+
import { createHosannaReporter } from '@tantawowa/hosanna-tools/testing/vitest/reporter';
|
|
414
|
+
import hosannaTestConfig from './integration/hosanna-test.config';
|
|
415
|
+
|
|
416
|
+
export default defineConfig({
|
|
417
|
+
reporters: ['default', createHosannaReporter(hosannaTestConfig)],
|
|
418
|
+
});
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
Use `hs.proof.capture(...)` at meaningful acceptance moments. A proof contains the target screenshot and the inspected screen/focus/control state used to understand it; hierarchy and scoped logs are optional. Proofs are evidence in the report, not an assertion by themselves:
|
|
422
|
+
|
|
423
|
+
```ts
|
|
424
|
+
const poster = hs.image.inCollectionCell({
|
|
425
|
+
collectionId: 'collectionView',
|
|
426
|
+
rowSettingsKey: 'rows.regular',
|
|
427
|
+
cellIndex: 0,
|
|
428
|
+
imageId: 'poster',
|
|
429
|
+
});
|
|
430
|
+
await poster.waitForLoaded({ requireBitmapSize: true });
|
|
431
|
+
await expect(poster).toBeLoadedImage({ requireBitmapSize: true });
|
|
432
|
+
|
|
433
|
+
await hs.proof.capture('programme-focused', {
|
|
434
|
+
controls: {
|
|
435
|
+
programmeCard: hs.control.byId('programmeCard'),
|
|
436
|
+
},
|
|
437
|
+
images: { poster },
|
|
438
|
+
includeLogs: true,
|
|
439
|
+
});
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Image readiness requires a non-empty URI, `loadStatus: "ready"`, positive rendered dimensions, and positive decoded bitmap dimensions by default. Explicit `loadWidth`/`loadHeight` assertions remain available for controls that request a fixed decode size; zero is valid when the runtime decodes at the source size. `hs.image.byId(...)`, `hs.image.within(...)`, and semantic `hs.image.inCollectionCell(...)` locators work across browser, Roku, and later native drivers.
|
|
443
|
+
|
|
444
|
+
Screenshots and proofs wait for the command-plane hierarchy and focus to remain stable for 300ms by default, then allow a short platform render-settle window. Consecutive screenshots are also spaced automatically. Override this only for known platform behaviour through the test config's `screenshot` settings or per-capture options; failure diagnostics bypass the stable-frame wait so they can still capture broken states.
|
|
445
|
+
|
|
446
|
+
Custom test ports must be unique. Keep the Vite and browser CDP ports outside the debugger's dynamic app allocation range; the launcher validates this before starting any processes and reports the conflicting range.
|
|
447
|
+
|
|
448
|
+
Keep structural and state assertions as the default correctness checks. Use `hs.screenshot.match(...)` only for an explicit visual regression whose target-specific baseline and tolerance are intentionally maintained. Failure diagnostics and proof artifacts are separated below `test-results/<testRunId>/<platform>-<target>/`.
|
|
449
|
+
|
|
450
|
+
`buildConfig` supplies a non-secret overlay that is merged last for the owned test launch. It may be a target-aware factory, so browser emulation can enable deterministic mocks while a physical Roku keeps its real platform services. The launcher and resolver both reject test overlays for `prod`:
|
|
451
|
+
|
|
452
|
+
```ts
|
|
453
|
+
defineHosannaTestConfig({
|
|
454
|
+
// ...
|
|
455
|
+
environment: 'test',
|
|
456
|
+
buildConfig: ({ platform, target }) => ({
|
|
457
|
+
remoteDebug: { isEnabled: true },
|
|
458
|
+
...(target === 'web'
|
|
459
|
+
? { channelStore: { testMode: { enabled: true } } }
|
|
460
|
+
: {}),
|
|
461
|
+
}),
|
|
462
|
+
});
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
Use `createHosannaTestTarget(config)` with Vitest's standard conditional APIs. Selectors accept one platform or a set, and distinguish browser, simulator, and physical-device targets:
|
|
466
|
+
|
|
467
|
+
```ts
|
|
468
|
+
const target = createHosannaTestTarget(config);
|
|
469
|
+
|
|
470
|
+
test.runIf(target.matches({ platform: 'roku', target: 'device' }))('Roku only', async ({ hs }) => {});
|
|
471
|
+
test.runIf(target.matches({ platform: ['ios', 'android'], target: 'sim' }))('mobile simulators', async ({ hs }) => {});
|
|
472
|
+
test.runIf(target.matches({ target: 'web' }))('all browser flavours', async ({ hs }) => {});
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
Browser Channel Store emulation supports per-test `success`, `cancelled`, and `failed` outcomes when `channelStore.testMode.enabled` is present in the test overlay. Set it from a named precondition with `hs.mocks.channelStore.setPurchaseOutcome(...)`. This API refuses physical-device targets. Roku purchase tests use Roku Pay [billing testing](https://developer.roku.com/dev/docs/billing-testing), a linked [Roku test user](https://developer.roku.com/dev/docs/test-users), and real Channel Store/OS purchase screens; keep them opt-in and serial.
|
|
476
|
+
|
|
477
|
+
For Roku device suites, the launcher builds and deploys before the test launch, then relaunches through ECP with the exact test run ID and a debugger address reachable from the device. Configure the device IP and developer password in the application test config/environment. `HS_TEST_DEBUG_HOST` overrides automatic LAN address selection when needed. Physical Roku input, held keys, deep links, screenshots, and logs use their device transports; the committed Vitest source remains the same API used by browser tests.
|
|
386
478
|
|
|
387
479
|
## Usage Examples
|
|
388
480
|
|
|
@@ -439,16 +531,15 @@ hst env prepare-gitignore # Update .gitignore
|
|
|
439
531
|
hst debugger:start # Start debug proxy (required)
|
|
440
532
|
hst mcp:start # Start MCP server for Cursor/Claude
|
|
441
533
|
|
|
442
|
-
# UI
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
hst test:ui --json --report-dir ./reports # CI mode with reports
|
|
534
|
+
# Application-owned UI tests
|
|
535
|
+
npm run test:ui
|
|
536
|
+
npm run test:ui -- auth/login.test.ts
|
|
537
|
+
HS_TEST_PLATFORM=roku HS_TEST_TARGET=device npm run test:ui
|
|
447
538
|
```
|
|
448
539
|
|
|
449
|
-
## MCP Debugging & UI
|
|
540
|
+
## MCP Debugging & Deterministic UI Tests
|
|
450
541
|
|
|
451
|
-
Hosanna Tools includes
|
|
542
|
+
Hosanna Tools includes an MCP server for interactive AI-assisted debugging. Deterministic UI tests run in an application's Vitest process and use the separate `@tantawowa/hosanna-tools/testing` export; MCP is only an authoring aid.
|
|
452
543
|
|
|
453
544
|
### Cursor Settings → MCP (Hosanna debugger)
|
|
454
545
|
|
|
@@ -501,7 +592,7 @@ After saving, enable the server in the MCP list. **`hst mcp:start`** is still us
|
|
|
501
592
|
### Quick Start
|
|
502
593
|
|
|
503
594
|
```bash
|
|
504
|
-
# 1. Start the debug proxy
|
|
595
|
+
# 1. Start the debug proxy for an interactive MCP debugging session
|
|
505
596
|
hst debugger:start
|
|
506
597
|
|
|
507
598
|
# 2. Start your Hosanna app with remote debugging enabled
|
|
@@ -509,11 +600,8 @@ hst debugger:start
|
|
|
509
600
|
# 3. For AI debugging (Cursor, Claude Code):
|
|
510
601
|
# Add MCP server (see "Cursor Settings → MCP" above) or hst mcp:start in a terminal
|
|
511
602
|
|
|
512
|
-
# 4.
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
# 5. For UI test replay:
|
|
516
|
-
hst test:ui
|
|
603
|
+
# 4. Run committed deterministic tests from the application repository:
|
|
604
|
+
npm run test:ui
|
|
517
605
|
```
|
|
518
606
|
|
|
519
607
|
### See also
|
package/dist/build-info.json
CHANGED
package/dist/cli.js
CHANGED
|
@@ -144,10 +144,33 @@ async function runRunPlanCommand(command, args) {
|
|
|
144
144
|
const executor = await Promise.resolve().then(() => __importStar(require('./run/executor.js')));
|
|
145
145
|
const positionalArgv = rawCommandPositionals(command);
|
|
146
146
|
const format = (args.json || args.format === 'json') ? 'json' : 'text';
|
|
147
|
+
let flags = runPlanFlagsFromCliArgs(args, format);
|
|
148
|
+
let chosenDestination;
|
|
149
|
+
const hasExplicitDestination = positionalArgv.length > 0 ||
|
|
150
|
+
rawHasFlag('platform') || rawHasFlag('target') || rawHasFlag('device');
|
|
151
|
+
if (command === 'run' && args.last) {
|
|
152
|
+
if (hasExplicitDestination) {
|
|
153
|
+
throw new Error('--last cannot be combined with an explicit platform, target, or device.');
|
|
154
|
+
}
|
|
155
|
+
const picker = await Promise.resolve().then(() => __importStar(require('./run/destination-picker.js')));
|
|
156
|
+
chosenDestination = picker.requireLastRunDestination(process.cwd());
|
|
157
|
+
flags = Object.assign(Object.assign({}, flags), runDestinationFlags(chosenDestination));
|
|
158
|
+
}
|
|
159
|
+
else if (command === 'run' &&
|
|
160
|
+
!hasExplicitDestination &&
|
|
161
|
+
!flags.nonInteractive &&
|
|
162
|
+
!args['dry-run'] &&
|
|
163
|
+
format === 'text' &&
|
|
164
|
+
process.stdin.isTTY &&
|
|
165
|
+
process.stdout.isTTY) {
|
|
166
|
+
const picker = await Promise.resolve().then(() => __importStar(require('./run/destination-picker.js')));
|
|
167
|
+
chosenDestination = await picker.chooseRunDestination({ cwd: process.cwd(), env: process.env });
|
|
168
|
+
flags = Object.assign(Object.assign({}, flags), runDestinationFlags(chosenDestination));
|
|
169
|
+
}
|
|
147
170
|
const planInput = {
|
|
148
171
|
cwd: process.cwd(),
|
|
149
172
|
argv: positionalArgv,
|
|
150
|
-
flags
|
|
173
|
+
flags,
|
|
151
174
|
env: process.env,
|
|
152
175
|
};
|
|
153
176
|
const plan = command === 'build'
|
|
@@ -162,6 +185,18 @@ async function runRunPlanCommand(command, args) {
|
|
|
162
185
|
}
|
|
163
186
|
process.exit(0);
|
|
164
187
|
}
|
|
188
|
+
if (command === 'run' && !flags.nonInteractive && process.stdin.isTTY) {
|
|
189
|
+
const destination = chosenDestination !== null && chosenDestination !== void 0 ? chosenDestination : rememberedExplicitDestination(plan, hasExplicitDestination);
|
|
190
|
+
if (destination) {
|
|
191
|
+
try {
|
|
192
|
+
const config = await Promise.resolve().then(() => __importStar(require('./run/run-config.js')));
|
|
193
|
+
config.saveLastRunDestination({ cwd: process.cwd(), destination });
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
console.warn(`Unable to remember the run destination: ${err instanceof Error ? err.message : String(err)}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
165
200
|
const result = await executor.executeRunPlan(plan);
|
|
166
201
|
if (format === 'json') {
|
|
167
202
|
console.info(JSON.stringify(result, null, 2));
|
|
@@ -179,6 +214,21 @@ async function runRunPlanCommand(command, args) {
|
|
|
179
214
|
process.exit(1);
|
|
180
215
|
}
|
|
181
216
|
}
|
|
217
|
+
function runDestinationFlags(destination) {
|
|
218
|
+
return {
|
|
219
|
+
platform: destination.platform,
|
|
220
|
+
target: destination.target,
|
|
221
|
+
device: destination.device,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function rememberedExplicitDestination(plan, hasExplicitDestination) {
|
|
225
|
+
if (!hasExplicitDestination)
|
|
226
|
+
return undefined;
|
|
227
|
+
const target = plan.resolved.target === 'emulator' ? 'web' : plan.resolved.target;
|
|
228
|
+
if (target !== 'web' && !plan.resolved.deviceSelector)
|
|
229
|
+
return undefined;
|
|
230
|
+
return Object.assign({ platform: plan.resolved.platform, target }, (plan.resolved.deviceSelector ? { device: plan.resolved.deviceSelector } : {}));
|
|
231
|
+
}
|
|
182
232
|
const NATIVE_PLATFORM_CHOICES = ['apple', 'ios', 'apple-tv', 'android', 'android-tv', 'all'];
|
|
183
233
|
function nativeInputFromArgs(args) {
|
|
184
234
|
return {
|
|
@@ -250,7 +300,7 @@ catch (err) {
|
|
|
250
300
|
// Let yargs handle all commands including env
|
|
251
301
|
(0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
|
|
252
302
|
.version(`${buildVersion}\n git: ${buildGitHash}\n built: ${buildDate}`)
|
|
253
|
-
.usage('$0 [command] [options]', `Hosanna Tools CLI\nVersion: ${buildVersion}\nBuild Date: ${buildDate}\n\nCommand Groups:\n compiler:* Compiler lifecycle (install, status, list)\n config hosanna.json management (wizard, show, set)\n env Environment checks and repair (check, fix, prepare-gitignore)\n dev:* Local development execution\n debugger:* Command debugger control\n mcp:* Hosanna MCP server control\n build Cross-platform build orchestration\n run Cross-platform launch orchestration\n native:* Native platform bootstrap and validation\n target:* Run target discovery and inventory\n roku:* Roku deployment, commands & packaging (run, command, package, map-stack)\n device:* Device discovery and run workflows\n generate:* Code and asset generation\n sdk:* Hosanna SDK/framework installation\n build-config:* Build/runtime configuration resolution\n ci:* Continuous integration helpers\n framework:* Framework distribution helpers\n license:* Hosanna framework licensing\n secrets:* Portable .secrets files\n
|
|
303
|
+
.usage('$0 [command] [options]', `Hosanna Tools CLI\nVersion: ${buildVersion}\nBuild Date: ${buildDate}\n\nCommand Groups:\n compiler:* Compiler lifecycle (install, status, list)\n config hosanna.json management (wizard, show, set)\n env Environment checks and repair (check, fix, prepare-gitignore)\n dev:* Local development execution\n debugger:* Command debugger control\n mcp:* Hosanna MCP server control\n build Cross-platform build orchestration\n run Cross-platform launch orchestration\n native:* Native platform bootstrap and validation\n target:* Run target discovery and inventory\n roku:* Roku deployment, commands & packaging (run, command, package, map-stack)\n device:* Device discovery and run workflows\n generate:* Code and asset generation\n sdk:* Hosanna SDK/framework installation\n build-config:* Build/runtime configuration resolution\n ci:* Continuous integration helpers\n framework:* Framework distribution helpers\n license:* Hosanna framework licensing\n secrets:* Portable .secrets files\n rasp:* RASP cert scripts`)
|
|
254
304
|
.option('verbose', {
|
|
255
305
|
type: 'boolean',
|
|
256
306
|
default: false,
|
|
@@ -1662,7 +1712,7 @@ catch (err) {
|
|
|
1662
1712
|
.parserConfiguration({ 'boolean-negation': false, 'populate--': true })
|
|
1663
1713
|
.positional('platform', {
|
|
1664
1714
|
type: 'string',
|
|
1665
|
-
describe: 'Launch platform.
|
|
1715
|
+
describe: 'Launch platform. Bare interactive runs show a destination picker; non-interactive runs default to web.',
|
|
1666
1716
|
})
|
|
1667
1717
|
.positional('env', {
|
|
1668
1718
|
type: 'string',
|
|
@@ -1714,6 +1764,7 @@ catch (err) {
|
|
|
1714
1764
|
.option('open', { type: 'boolean', describe: 'Open the web runtime URL in the default browser; defaults to interactive runs only' })
|
|
1715
1765
|
.option('no-open', { type: 'boolean', default: false, describe: 'Do not open the default browser for web runtime launches' })
|
|
1716
1766
|
.option('non-interactive', { type: 'boolean', default: false, describe: 'Fail instead of prompting when a decision is needed' })
|
|
1767
|
+
.option('last', { type: 'boolean', default: false, describe: 'Immediately reuse the last destination selected by an interactive run' })
|
|
1717
1768
|
.option('json', { type: 'boolean', default: false, describe: 'Print run result as JSON when the command returns' })
|
|
1718
1769
|
.option('dry-run', { type: 'boolean', default: false, describe: 'Print the resolved RunPlan without executing it' })
|
|
1719
1770
|
.option('format', { type: 'string', choices: ['text', 'json'], default: 'text', describe: 'Output format for --dry-run and run result' })
|
|
@@ -1734,6 +1785,8 @@ catch (err) {
|
|
|
1734
1785
|
.option('logs', { type: 'boolean', default: false, describe: 'Enable verbose deploy logging where supported' })
|
|
1735
1786
|
.option('applySourceMapToPaths', { type: 'boolean', default: true, describe: 'Resolve Roku stack trace paths with source maps during log streaming' })
|
|
1736
1787
|
.example('$0 run --platform web', 'Start/reuse web dev services for this app')
|
|
1788
|
+
.example('$0 run', 'Choose once from all available web, simulator, and physical-device destinations')
|
|
1789
|
+
.example('$0 run --last', 'Immediately run on the last selected destination')
|
|
1737
1790
|
.example('$0 run --platform roku --target emulator', 'Start/reuse the Roku TV web emulator')
|
|
1738
1791
|
.example('$0 run --platform ios --target emulator', 'Start/reuse the iPhone-shaped web emulator profile')
|
|
1739
1792
|
.example('$0 run --platform android --target emulator --device pixel-8', 'Start/reuse the Android-shaped web emulator profile')
|
|
@@ -2003,121 +2056,13 @@ catch (err) {
|
|
|
2003
2056
|
appName: args.appName,
|
|
2004
2057
|
});
|
|
2005
2058
|
})
|
|
2006
|
-
.command('
|
|
2007
|
-
'Requires a running debug proxy (hst debugger:start) and a connected app.', yargs => yargs
|
|
2008
|
-
.positional('recordings', {
|
|
2009
|
-
describe: 'Recording IDs to replay (omit to run all)',
|
|
2010
|
-
type: 'string',
|
|
2011
|
-
array: true,
|
|
2012
|
-
})
|
|
2013
|
-
.option('suite', {
|
|
2014
|
-
type: 'string',
|
|
2015
|
-
describe: 'Path to a test-suite.json file (relative to recordings dir or absolute)',
|
|
2016
|
-
})
|
|
2017
|
-
.option('appId', {
|
|
2018
|
-
type: 'string',
|
|
2019
|
-
describe: 'Target app ID (defaults to the one stored in each recording)',
|
|
2020
|
-
})
|
|
2021
|
-
.option('managementPort', { type: 'number', default: 59150, describe: 'Debug proxy management port' })
|
|
2022
|
-
.option('managementHost', { type: 'string', default: 'localhost', describe: 'Debug proxy host' })
|
|
2023
|
-
.option('extensionPort', { type: 'number', default: 59153, describe: 'Debug proxy extension WebSocket port' })
|
|
2024
|
-
.option('stop-on-failure', { type: 'boolean', default: true, describe: 'Stop each recording on first step failure' })
|
|
2025
|
-
.option('step-delay-ms', { type: 'number', default: 500, describe: 'Delay after each action (ms)' })
|
|
2026
|
-
.option('focus-assert-retry-ms', {
|
|
2027
|
-
type: 'number',
|
|
2028
|
-
default: 300,
|
|
2029
|
-
describe: 'Delay (ms) between focus polls after a mismatch (0 = no polling)',
|
|
2030
|
-
})
|
|
2031
|
-
.option('focus-assert-max-retries', {
|
|
2032
|
-
type: 'number',
|
|
2033
|
-
default: 12,
|
|
2034
|
-
describe: 'Max extra GetFocusedView polls per step (skeleton/load); 0 = single read only',
|
|
2035
|
-
})
|
|
2036
|
-
.option('screenshot-settle-ms', { type: 'number', default: 400, describe: 'Extra delay before screenshot capture (ms)' })
|
|
2037
|
-
.option('compare-screenshots', { type: 'boolean', default: true, describe: 'Enable visual regression comparison' })
|
|
2038
|
-
.option('screenshot-threshold', { type: 'number', default: 0.15, describe: 'Pixel diff tolerance (0-1)' })
|
|
2039
|
-
.option('screenshot-fail-percent', { type: 'number', default: 2.0, describe: 'Max allowed diff % before failure' })
|
|
2040
|
-
.option('roku-ip', { type: 'string', describe: 'Roku device IP (env: HOSANNA_ROKU_DEVICE_IP)' })
|
|
2041
|
-
.option('roku-password', { type: 'string', describe: 'Roku dev password (env: HOSANNA_ROKU_DEV_PASSWORD)' })
|
|
2042
|
-
.option('json', { type: 'boolean', default: false, describe: 'Output results as JSON only' })
|
|
2043
|
-
.option('report-dir', { type: 'string', describe: 'Directory for HTML + JUnit XML reports' })
|
|
2044
|
-
.option('update-baselines', { type: 'boolean', default: false, describe: 'Re-capture baseline screenshots without asserting (replays actions only)' })
|
|
2045
|
-
.option('coverage', { type: 'boolean', default: false, describe: 'Analyze recordings and print navigation coverage (no running app needed)' }), async (args) => {
|
|
2046
|
-
if (args.verbose) {
|
|
2047
|
-
process.env.HOSANNA_VERBOSE = '1';
|
|
2048
|
-
}
|
|
2049
|
-
const { runTestUI } = await Promise.resolve().then(() => __importStar(require('./support-tools/mcp-server/cli-test-runner.js')));
|
|
2050
|
-
await runTestUI({
|
|
2051
|
-
recordings: args.recordings,
|
|
2052
|
-
suite: args.suite,
|
|
2053
|
-
appId: args.appId,
|
|
2054
|
-
managementPort: args.managementPort,
|
|
2055
|
-
managementHost: args.managementHost,
|
|
2056
|
-
extensionPort: args.extensionPort,
|
|
2057
|
-
stopOnFailure: args.stopOnFailure,
|
|
2058
|
-
stepDelayMs: args.stepDelayMs,
|
|
2059
|
-
focusAssertRetryMs: args.focusAssertRetryMs,
|
|
2060
|
-
focusAssertMaxRetries: args.focusAssertMaxRetries,
|
|
2061
|
-
screenshotSettleMs: args.screenshotSettleMs,
|
|
2062
|
-
compareScreenshots: args.compareScreenshots,
|
|
2063
|
-
screenshotThreshold: args.screenshotThreshold,
|
|
2064
|
-
screenshotFailPercent: args.screenshotFailPercent,
|
|
2065
|
-
rokuIp: args.rokuIp,
|
|
2066
|
-
rokuPassword: args.rokuPassword,
|
|
2067
|
-
json: args.json,
|
|
2068
|
-
verbose: args.verbose,
|
|
2069
|
-
reportDir: args.reportDir,
|
|
2070
|
-
updateBaselines: args.updateBaselines,
|
|
2071
|
-
coverage: args.coverage,
|
|
2072
|
-
});
|
|
2073
|
-
})
|
|
2074
|
-
.command('test:record', 'Interactively record a UI test by navigating the app. ' +
|
|
2075
|
-
'Key events from the running app are captured via the debug proxy.', yargs => yargs
|
|
2076
|
-
.option('name', {
|
|
2077
|
-
type: 'string',
|
|
2078
|
-
demandOption: true,
|
|
2079
|
-
describe: 'Name for this test recording',
|
|
2080
|
-
})
|
|
2081
|
-
.option('description', {
|
|
2082
|
-
type: 'string',
|
|
2083
|
-
describe: 'Optional description of what this test covers',
|
|
2084
|
-
})
|
|
2085
|
-
.option('appId', {
|
|
2086
|
-
type: 'string',
|
|
2087
|
-
describe: 'Target app ID (defaults to first connected app)',
|
|
2088
|
-
})
|
|
2089
|
-
.option('managementPort', { type: 'number', default: 59150, describe: 'Debug proxy management port' })
|
|
2090
|
-
.option('managementHost', { type: 'string', default: 'localhost', describe: 'Debug proxy host' })
|
|
2091
|
-
.option('extensionPort', { type: 'number', default: 59153, describe: 'Debug proxy extension WebSocket port' })
|
|
2092
|
-
.option('roku-ip', { type: 'string', describe: 'Roku device IP for screenshots (env: HOSANNA_ROKU_DEVICE_IP)' })
|
|
2093
|
-
.option('roku-password', { type: 'string', describe: 'Roku dev password for screenshots (env: HOSANNA_ROKU_DEV_PASSWORD)' })
|
|
2094
|
-
.option('screenshot-on-select', { type: 'boolean', default: true, describe: 'Auto-capture screenshot on Select key press' })
|
|
2095
|
-
.example('$0 test:record --name "Home screen navigation"', 'Record key presses while navigating')
|
|
2096
|
-
.example('$0 test:record --name "Details test" --roku-ip 192.168.1.10', 'Record with Roku screenshot support'), async (args) => {
|
|
2097
|
-
if (args.verbose) {
|
|
2098
|
-
process.env.HOSANNA_VERBOSE = '1';
|
|
2099
|
-
}
|
|
2100
|
-
const { runTestRecord } = await Promise.resolve().then(() => __importStar(require('./support-tools/mcp-server/cli-test-recorder.js')));
|
|
2101
|
-
await runTestRecord({
|
|
2102
|
-
name: args.name,
|
|
2103
|
-
description: args.description,
|
|
2104
|
-
appId: args.appId,
|
|
2105
|
-
managementPort: args.managementPort,
|
|
2106
|
-
managementHost: args.managementHost,
|
|
2107
|
-
extensionPort: args.extensionPort,
|
|
2108
|
-
rokuIp: args.rokuIp,
|
|
2109
|
-
rokuPassword: args.rokuPassword,
|
|
2110
|
-
screenshotOnSelect: args.screenshotOnSelect,
|
|
2111
|
-
verbose: args.verbose,
|
|
2112
|
-
});
|
|
2113
|
-
})
|
|
2114
|
-
.command('rasp:export <recordingId> <name>', 'Convert a saved test recording into a Roku Automation Scripting Platform (RASP) ' +
|
|
2059
|
+
.command('rasp:export <captureId> <name>', 'Convert a saved RASP input capture into a Roku Automation Scripting Platform (RASP) ' +
|
|
2115
2060
|
'YAML script suitable for Roku certification testing. Output goes to ' +
|
|
2116
2061
|
'rasp-scripts/<name>.rasp in the current project by default.', yargs => yargs
|
|
2117
|
-
.positional('
|
|
2062
|
+
.positional('captureId', {
|
|
2118
2063
|
type: 'string',
|
|
2119
2064
|
demandOption: true,
|
|
2120
|
-
describe: 'ID of the saved
|
|
2065
|
+
describe: 'ID of the saved capture (filename without .json), e.g. "signin_1736888000000"',
|
|
2121
2066
|
})
|
|
2122
2067
|
.positional('name', {
|
|
2123
2068
|
type: 'string',
|
|
@@ -2127,11 +2072,11 @@ catch (err) {
|
|
|
2127
2072
|
.option('out', {
|
|
2128
2073
|
type: 'string',
|
|
2129
2074
|
alias: 'o',
|
|
2130
|
-
describe: 'Override the output path. Default: rasp-scripts/<name>.rasp next to
|
|
2075
|
+
describe: 'Override the output path. Default: rasp-scripts/<name>.rasp next to the capture directory.',
|
|
2131
2076
|
})
|
|
2132
2077
|
.option('channel-name', {
|
|
2133
2078
|
type: 'string',
|
|
2134
|
-
describe: 'Channel name for the launch step (overrides
|
|
2079
|
+
describe: 'Channel name for the launch step (overrides capture.debugContext.channel.name).',
|
|
2135
2080
|
})
|
|
2136
2081
|
.option('channel-mode', {
|
|
2137
2082
|
type: 'string',
|
|
@@ -2189,7 +2134,7 @@ catch (err) {
|
|
|
2189
2134
|
describe: 'Master gate for credential auto-injection. When true (default), the exporter ' +
|
|
2190
2135
|
'watches for OK presses landing on a TextInputView classified as email or password ' +
|
|
2191
2136
|
'(via inputType / secureMode or field id) and injects the matching half of the RFI credential flow. ' +
|
|
2192
|
-
'Use --no-useWithRfi to disable injection entirely (
|
|
2137
|
+
'Use --no-useWithRfi to disable injection entirely (capture flows through verbatim).',
|
|
2193
2138
|
})
|
|
2194
2139
|
.option('login-text', {
|
|
2195
2140
|
type: 'string',
|
|
@@ -2224,8 +2169,8 @@ catch (err) {
|
|
|
2224
2169
|
}
|
|
2225
2170
|
const path = await Promise.resolve().then(() => __importStar(require('path')));
|
|
2226
2171
|
const fs = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
2227
|
-
const {
|
|
2228
|
-
const {
|
|
2172
|
+
const { getRaspCapturesDir } = await Promise.resolve().then(() => __importStar(require('./support-tools/mcp-server/rasp-input-capture.js')));
|
|
2173
|
+
const { exportRaspFromCapture, validateRaspYaml } = await Promise.resolve().then(() => __importStar(require('./support-tools/mcp-server/rasp-exporter.js')));
|
|
2229
2174
|
// Parses a minimal RASP YAML fragment containing only `- press: <key>` and
|
|
2230
2175
|
// `- text: <value>` ops (with `LOGIN` / `PASSWORD` reserved tokens). Comments
|
|
2231
2176
|
// (#…) and blank lines are allowed. Anything else throws.
|
|
@@ -2252,16 +2197,16 @@ catch (err) {
|
|
|
2252
2197
|
}
|
|
2253
2198
|
return ops;
|
|
2254
2199
|
};
|
|
2255
|
-
const
|
|
2200
|
+
const captureId = args.captureId;
|
|
2256
2201
|
const scriptName = args.name.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
2257
|
-
const
|
|
2258
|
-
const
|
|
2259
|
-
let
|
|
2202
|
+
const capturesDir = getRaspCapturesDir();
|
|
2203
|
+
const capturePath = path.join(capturesDir, `${captureId}.json`);
|
|
2204
|
+
let capture;
|
|
2260
2205
|
try {
|
|
2261
|
-
|
|
2206
|
+
capture = JSON.parse(fs.readFileSync(capturePath, 'utf-8'));
|
|
2262
2207
|
}
|
|
2263
2208
|
catch (_c) {
|
|
2264
|
-
console.error(`
|
|
2209
|
+
console.error(`RASP capture "${captureId}" not found in ${capturesDir}.`);
|
|
2265
2210
|
process.exit(1);
|
|
2266
2211
|
}
|
|
2267
2212
|
// Parse repeatable --text-field flags into a map.
|
|
@@ -2279,7 +2224,7 @@ catch (err) {
|
|
|
2279
2224
|
const credentialFlow = credentialFlowPath
|
|
2280
2225
|
? parseCredentialFlowFile(credentialFlowPath)
|
|
2281
2226
|
: undefined;
|
|
2282
|
-
const result =
|
|
2227
|
+
const result = exportRaspFromCapture(capture, {
|
|
2283
2228
|
channelName: args.channelName,
|
|
2284
2229
|
channelMode: args.channelMode,
|
|
2285
2230
|
contentId: args.contentId,
|
|
@@ -2304,7 +2249,7 @@ catch (err) {
|
|
|
2304
2249
|
process.stdout.write(result.yaml);
|
|
2305
2250
|
}
|
|
2306
2251
|
console.error(`✅ Wrote ${outPath}`);
|
|
2307
|
-
console.error(` ${result.stats.
|
|
2252
|
+
console.error(` ${result.stats.capturedSteps} captured steps → ${result.stats.raspOps} RASP ops` +
|
|
2308
2253
|
` (auto-pauses: ${result.stats.autoPausesInserted}, text injections: ${result.stats.textInjections}` +
|
|
2309
2254
|
`, keyboard steps dropped: ${result.stats.keyboardStepsDropped}, verify ops: ${result.stats.verifyOpsEmitted})`);
|
|
2310
2255
|
for (const d of result.rfiDetections) {
|
|
@@ -2327,7 +2272,7 @@ catch (err) {
|
|
|
2327
2272
|
}
|
|
2328
2273
|
})
|
|
2329
2274
|
.command('rasp:capture <name>', 'Record a UI session and export it directly to a RASP script in one step. ' +
|
|
2330
|
-
'Navigate the app, press Ctrl+C to stop — the
|
|
2275
|
+
'Navigate the app, press Ctrl+C to stop — the capture is saved and the RASP script is written automatically.', yargs => yargs
|
|
2331
2276
|
.positional('name', {
|
|
2332
2277
|
type: 'string',
|
|
2333
2278
|
demandOption: true,
|
|
@@ -2412,10 +2357,10 @@ catch (err) {
|
|
|
2412
2357
|
default: false,
|
|
2413
2358
|
describe: 'Print the YAML to stdout after writing.',
|
|
2414
2359
|
})
|
|
2415
|
-
.option('keep-
|
|
2360
|
+
.option('keep-capture', {
|
|
2416
2361
|
type: 'boolean',
|
|
2417
2362
|
default: false,
|
|
2418
|
-
describe: 'Keep the temporary
|
|
2363
|
+
describe: 'Keep the temporary RASP capture JSON/screenshots after the script is written.',
|
|
2419
2364
|
})
|
|
2420
2365
|
.example('$0 rasp:capture login --channel-name "Criterion"', 'Record and export to rasp-scripts/login.rasp')
|
|
2421
2366
|
.example('$0 rasp:capture login --channel-name "Criterion" --login-text "user@example.com" --password-text "MyPass1!"', 'Default credential flow with custom text')
|
|
@@ -2423,13 +2368,13 @@ catch (err) {
|
|
|
2423
2368
|
var _a, _b;
|
|
2424
2369
|
const path = await Promise.resolve().then(() => __importStar(require('path')));
|
|
2425
2370
|
const fs = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
2426
|
-
const {
|
|
2427
|
-
const {
|
|
2428
|
-
const {
|
|
2371
|
+
const { cleanupRaspCaptureArtifacts, runRaspCapture } = await Promise.resolve().then(() => __importStar(require('./support-tools/mcp-server/rasp-capture.js')));
|
|
2372
|
+
const { getRaspCapturesDir } = await Promise.resolve().then(() => __importStar(require('./support-tools/mcp-server/rasp-input-capture.js')));
|
|
2373
|
+
const { exportRaspFromCapture, validateRaspYaml } = await Promise.resolve().then(() => __importStar(require('./support-tools/mcp-server/rasp-exporter.js')));
|
|
2429
2374
|
const scriptName = args.name.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
2430
2375
|
const raspScriptsDir = path.join(process.cwd(), 'rasp-scripts');
|
|
2431
2376
|
const outPath = (_a = args.out) !== null && _a !== void 0 ? _a : path.join(raspScriptsDir, `${scriptName}.rasp`);
|
|
2432
|
-
const
|
|
2377
|
+
const capturesDir = getRaspCapturesDir();
|
|
2433
2378
|
const textFields = {};
|
|
2434
2379
|
const tfArgs = (_b = args['text-field']) !== null && _b !== void 0 ? _b : [];
|
|
2435
2380
|
for (const tf of tfArgs) {
|
|
@@ -2467,7 +2412,7 @@ catch (err) {
|
|
|
2467
2412
|
const credentialFlow = credentialFlowPath
|
|
2468
2413
|
? parseCredentialFlowFile(credentialFlowPath)
|
|
2469
2414
|
: undefined;
|
|
2470
|
-
await
|
|
2415
|
+
await runRaspCapture({
|
|
2471
2416
|
name: args.name,
|
|
2472
2417
|
appId: args.appId,
|
|
2473
2418
|
managementPort: args.managementPort,
|
|
@@ -2475,8 +2420,8 @@ catch (err) {
|
|
|
2475
2420
|
extensionPort: args.extensionPort,
|
|
2476
2421
|
rokuIp: args.rokuIp,
|
|
2477
2422
|
rokuPassword: args.rokuPassword,
|
|
2478
|
-
onSaved: async (
|
|
2479
|
-
const result =
|
|
2423
|
+
onSaved: async (captureId, capture) => {
|
|
2424
|
+
const result = exportRaspFromCapture(capture, {
|
|
2480
2425
|
channelName: args.channelName,
|
|
2481
2426
|
channelMode: args.channelMode,
|
|
2482
2427
|
contentId: args.contentId,
|
|
@@ -2495,7 +2440,7 @@ catch (err) {
|
|
|
2495
2440
|
if (args.print)
|
|
2496
2441
|
process.stdout.write(result.yaml);
|
|
2497
2442
|
console.log(`✅ RASP script written: ${outPath}`);
|
|
2498
|
-
console.log(` ${result.stats.
|
|
2443
|
+
console.log(` ${result.stats.capturedSteps} steps → ${result.stats.raspOps} RASP ops` +
|
|
2499
2444
|
` (pauses: ${result.stats.autoPausesInserted}, text injections: ${result.stats.textInjections})`);
|
|
2500
2445
|
for (const d of result.rfiDetections) {
|
|
2501
2446
|
const icon = d.type === 'login' ? '📧' : '🔑';
|
|
@@ -2514,15 +2459,15 @@ catch (err) {
|
|
|
2514
2459
|
console.error(` Script has validation errors — review before submitting to Roku cert.`);
|
|
2515
2460
|
process.exitCode = 1;
|
|
2516
2461
|
}
|
|
2517
|
-
if (!args.
|
|
2462
|
+
if (!args.keepCapture) {
|
|
2518
2463
|
try {
|
|
2519
|
-
const cleanup =
|
|
2464
|
+
const cleanup = cleanupRaspCaptureArtifacts(capturesDir, captureId);
|
|
2520
2465
|
if (cleanup.jsonDeleted || cleanup.artifactDirDeleted) {
|
|
2521
|
-
console.log(`🧹 Cleaned up temporary
|
|
2466
|
+
console.log(`🧹 Cleaned up temporary capture: ${captureId}`);
|
|
2522
2467
|
}
|
|
2523
2468
|
}
|
|
2524
2469
|
catch (err) {
|
|
2525
|
-
console.warn(`⚠ RASP script was written, but temporary
|
|
2470
|
+
console.warn(`⚠ RASP script was written, but temporary capture cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2526
2471
|
}
|
|
2527
2472
|
}
|
|
2528
2473
|
},
|