@tantawowa/hosanna-tools 3.6.1 → 3.7.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 CHANGED
@@ -20,19 +20,32 @@ npm install . -g # path to the hosanna-tools directory
20
20
 
21
21
  ## Hosanna source install modes
22
22
 
23
- Client projects can switch between Keygen source tarballs, git checkouts, and a
24
- local SDK checkout:
23
+ Client projects can switch between private Keygen source tarballs, an embedded
24
+ copy, a shared local checkout, and the legacy git/local modes:
25
25
 
26
26
  ```bash
27
- hst config source # show current mode
28
- hst config source keygen --fix # default: full + roku,web from @nightly
29
- hst config source git --fix # use the configured git checkout again
30
- hst config source local --fix # use LOCAL_SDK for framework development
27
+ hst config source # show current mode and immutable install identity
28
+ hst config source keygen --sdk-version latest --install # newest valid private publication across all lanes
29
+ hst config source keygen --sdk-version v1.29.1 --install # pin an exact immutable production release
30
+ hst config source embedded --source-path ../hosanna-ui --install # filtered, reproducible copy
31
+ hst config source shared --source-path ../hosanna-ui --install # portable relative link for local development
32
+ hst config source git --install # legacy git checkout
33
+ hst config source local # caller-provided LOCAL_SDK checkout
31
34
  ```
32
35
 
33
- Keygen mode writes `distribution: "keygen"` to `hosanna.json` and installs
34
- license-gated artifacts with `hst env fix` / `hst sdk:install`. Git mode keeps
35
- the legacy clone/update path for developers who need it.
36
+ Keygen mode writes `distribution: "keygen"`, `flavour: "fullSource"`, and no
37
+ platform list to `hosanna.json`. Bare `latest` means the newest valid published
38
+ full-source release by Keygen publication time, whether it came from a nightly,
39
+ manual, or tag run. `@latest` remains the stable-lane alias and `@nightly`
40
+ remains the development-lane alias. Exact versions are immutable pins.
41
+
42
+ Use `--replace-existing` with `--install` when switching an existing project
43
+ between modes. Replacement is refused for an unrecognized or modified source
44
+ root; `--force` is required for a dirty git checkout. Embedded installs exclude
45
+ repository/build/secret state, write a content-hash manifest, and never modify
46
+ the source checkout. Shared installs validate the source and create a relative
47
+ symlink. Granular flavours and platform subsets are deliberately rejected with
48
+ `NOT SUPPORTED YET — use flavour "fullSource"`.
36
49
 
37
50
  ## Embedding as a library
38
51
 
@@ -197,9 +210,23 @@ hst target:list --form-factor tv --json
197
210
 
198
211
  ### Roku Commands (`roku:*`) - Roku Deployment & Packaging
199
212
  - `roku:run` - Deploy a Roku app to a device (supports .zip file or folder)
213
+ - `roku:command` - Send a Hosanna command to an installed channel through Roku ECP
200
214
  - `roku:package` - Package and sign a Roku channel using roku-deploy
201
215
  - `roku:map-stack` - Resolve Roku `.brs` stack traces, compile errors, and crash snippets to original TypeScript locations using `.brs.map` files
202
216
 
217
+ Send a command to the sideloaded `dev` channel (the default app ID):
218
+
219
+ ```bash
220
+ hst roku:command clearRegistry --device 192.168.1.50
221
+ hst roku:command clearRegistry --device "Living Room" --yes
222
+ ```
223
+
224
+ The device can also come from `ROKU_IP` or the preferred/default Roku in `.hosanna-tools/devices.json`. Destructive commands such as `clearRegistry` prompt for confirmation unless `--yes` is passed. Use `--app-id` for another installed channel and `--dry-run` to print the encoded ECP request without sending it:
225
+
226
+ ```bash
227
+ hst roku:command clearRegistry --app-id 8518 --dry-run
228
+ ```
229
+
203
230
  Resolve pasted Roku output:
204
231
 
205
232
  ```bash
@@ -1,6 +1,6 @@
1
1
  {
2
- "buildDate": "2026-07-11T20:48:11+00:00",
3
- "buildDateISO": "2026-07-11T20:48:11.158Z",
2
+ "buildDate": "2026-07-14T09:12:47+00:00",
3
+ "buildDateISO": "2026-07-14T09:12:47.324Z",
4
4
  "timeZone": "UTC",
5
- "gitHash": "aee354a"
5
+ "gitHash": "915e992"
6
6
  }
package/dist/cli.js CHANGED
@@ -250,7 +250,7 @@ catch (err) {
250
250
  // Let yargs handle all commands including env
251
251
  (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
252
252
  .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 & packaging (run, 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 test:* UI testing\n rasp:* RASP cert scripts`)
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 test:* UI testing\n rasp:* RASP cert scripts`)
254
254
  .option('verbose', {
255
255
  type: 'boolean',
256
256
  default: false,
@@ -551,6 +551,44 @@ catch (err) {
551
551
  console.error('❌ Failed to publish Roku package:', err);
552
552
  process.exit(1);
553
553
  }
554
+ })
555
+ .command('roku:command <command>', 'Send a Hosanna command to an installed Roku channel through ECP', yargs => yargs
556
+ .positional('command', { type: 'string', demandOption: true, describe: 'Hosanna command name, e.g. clearRegistry' })
557
+ .option('device', { alias: 'ip', type: 'string', describe: 'Roku IP or configured device name (env: ROKU_IP)' })
558
+ .option('app-id', { type: 'string', default: 'dev', describe: 'Installed Roku channel ID (default: dev)' })
559
+ .option('timeout-ms', { type: 'number', default: 10000, describe: 'ECP request timeout in milliseconds' })
560
+ .option('yes', { alias: 'y', type: 'boolean', default: false, describe: 'Confirm destructive commands without prompting' })
561
+ .option('dry-run', { type: 'boolean', default: false, describe: 'Resolve the device and print the ECP request without sending it' })
562
+ .option('non-interactive', { type: 'boolean', default: false, describe: 'Fail instead of prompting for destructive command confirmation' })
563
+ .example('$0 roku:command clearRegistry --device 192.168.1.50', 'Clear the sideloaded dev channel registry after confirmation')
564
+ .example('$0 roku:command clearRegistry --device "living room" --yes', 'Clear the configured Roku registry non-interactively')
565
+ .example('$0 roku:command clearRegistry --app-id 8518 --dry-run', 'Preview a command for an installed production channel'), async (args) => {
566
+ try {
567
+ const mod = await Promise.resolve().then(() => __importStar(require('./support-tools/roku-command.js')));
568
+ const result = await mod.runRokuCommand({
569
+ command: args.command,
570
+ cwd: process.cwd(),
571
+ device: args.device,
572
+ appId: args['app-id'],
573
+ timeoutMs: args['timeout-ms'],
574
+ yes: Boolean(args.yes),
575
+ dryRun: Boolean(args['dry-run']),
576
+ nonInteractive: Boolean(args['non-interactive']),
577
+ env: process.env,
578
+ });
579
+ if (result.dryRun) {
580
+ console.info(`Dry run: POST ${result.url}`);
581
+ }
582
+ else {
583
+ console.info(`✅ Sent Hosanna command "${result.command}" to Roku ${result.deviceIp} (app ${result.appId}).`);
584
+ }
585
+ process.exit(0);
586
+ }
587
+ catch (err) {
588
+ console.error('❌ roku:command failed');
589
+ console.error(err instanceof Error ? err.message : String(err));
590
+ process.exit(1);
591
+ }
554
592
  })
555
593
  .command('roku:map-stack', 'Resolve Roku BrightScript stack traces or compile errors to original TypeScript locations using .brs.map files', yargs => yargs
556
594
  .option('source-map-root', {
@@ -747,7 +785,7 @@ catch (err) {
747
785
  })
748
786
  .positional('mode', {
749
787
  type: 'string',
750
- choices: ['show', 'keygen', 'git', 'local'],
788
+ choices: ['show', 'keygen', 'git', 'local', 'embedded', 'shared'],
751
789
  describe: 'Source install mode when action is source',
752
790
  })
753
791
  .option('git-url', { type: 'string', describe: 'Set hosanna.json git-url' })
@@ -756,8 +794,9 @@ catch (err) {
756
794
  .option('transpiler-version', { type: 'string', describe: 'Set hosanna.json transpiler-version' })
757
795
  .option('config-catalog-url', { type: 'string', describe: 'Persist a remote catalog URL into hosanna.json' })
758
796
  .option('catalog-url', { type: 'string', describe: 'Use a remote catalog URL for this run without writing it' })
759
- .option('flavour', { type: 'string', choices: ['core', 'game', 'full'], describe: 'Keygen source flavour when action is source (default: full)' })
760
- .option('platforms', { type: 'string', array: true, describe: 'Keygen platforms when action is source, comma-separated or repeatable (default: roku,web)' })
797
+ .option('flavour', { type: 'string', choices: ['fullSource', 'core', 'game', 'full'], describe: 'Keygen source flavour when action is source (supported: fullSource)' })
798
+ .option('platforms', { type: 'string', array: true, describe: 'Future split-platform selection; omit for fullSource' })
799
+ .option('source-path', { type: 'string', describe: 'Hosanna UI checkout to copy for embedded mode or link for shared mode' })
761
800
  .option('install', { type: 'boolean', default: false, describe: 'Install the selected source mode atomically after updating config' })
762
801
  .option('replace-existing', { type: 'boolean', default: false, describe: 'Allow --install to replace an existing hosanna-ui source root when switching modes' })
763
802
  .option('force', { type: 'boolean', default: false, describe: 'Allow --install to replace a dirty git checkout when used with --replace-existing' })
@@ -774,6 +813,7 @@ catch (err) {
774
813
  mode: (typeof args.mode === 'string' ? args.mode : 'show'),
775
814
  flavour: args.flavour,
776
815
  platforms: (0, hosanna_config_js_1.parsePlatformList)(stringArrayArg(args.platforms)),
816
+ sourcePath: typeof args.sourcePath === 'string' ? args.sourcePath : undefined,
777
817
  sdkVersion: typeof args.sdkVersion === 'string' ? args.sdkVersion : undefined,
778
818
  gitUrl: typeof args.gitUrl === 'string' ? args.gitUrl : undefined,
779
819
  branch: typeof args.branch === 'string' ? args.branch : undefined,