@tantawowa/hosanna-tools 4.0.0 → 5.0.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.
Files changed (59) hide show
  1. package/README.md +85 -76
  2. package/dist/build-info.json +3 -3
  3. package/dist/cli.js +27 -191
  4. package/dist/cli.js.map +1 -1
  5. package/dist/generation/emitters/RuntimeEmitter.js +1 -12
  6. package/dist/generation/emitters/RuntimeEmitter.js.map +1 -1
  7. package/dist/generation/import-utils.js +2 -11
  8. package/dist/generation/import-utils.js.map +1 -1
  9. package/dist/index.d.ts +4 -12
  10. package/dist/index.js +5 -13
  11. package/dist/index.js.map +1 -1
  12. package/dist/lib/env-info.d.ts +9 -16
  13. package/dist/lib/env-info.js +117 -386
  14. package/dist/lib/env-info.js.map +1 -1
  15. package/dist/lib/hosanna-config.d.ts +1 -31
  16. package/dist/lib/hosanna-config.js +8 -367
  17. package/dist/lib/hosanna-config.js.map +1 -1
  18. package/dist/support-tools/mcp-server/rasp-exporter.js +1 -1
  19. package/dist/testing/client.js +2 -3
  20. package/dist/testing/client.js.map +1 -1
  21. package/dist/testing/config.d.ts +1 -0
  22. package/dist/testing/config.js +8 -0
  23. package/dist/testing/config.js.map +1 -1
  24. package/dist/testing/launcher.js +26 -29
  25. package/dist/testing/launcher.js.map +1 -1
  26. package/dist/testing/types.d.ts +5 -0
  27. package/dist/updater/framework-script-migration.d.ts +6 -0
  28. package/dist/updater/framework-script-migration.js +55 -0
  29. package/dist/updater/framework-script-migration.js.map +1 -0
  30. package/dist/updater/license-manager.d.ts +1 -80
  31. package/dist/updater/license-manager.js +4 -329
  32. package/dist/updater/license-manager.js.map +1 -1
  33. package/dist/updater/npm-framework.d.ts +19 -0
  34. package/dist/updater/npm-framework.js +214 -0
  35. package/dist/updater/npm-framework.js.map +1 -0
  36. package/dist/updater/sdk-manager.d.ts +2 -31
  37. package/dist/updater/sdk-manager.js +53 -804
  38. package/dist/updater/sdk-manager.js.map +1 -1
  39. package/dist/updater/symlink-manager.js +2 -21
  40. package/dist/updater/symlink-manager.js.map +1 -1
  41. package/dist/vite.d.ts +8 -0
  42. package/dist/vite.js +22 -6
  43. package/dist/vite.js.map +1 -1
  44. package/package.json +4 -2
  45. package/dist/lib/cli.configure-hosanna-url.d.ts +0 -11
  46. package/dist/lib/cli.configure-hosanna-url.js +0 -51
  47. package/dist/lib/cli.configure-hosanna-url.js.map +0 -1
  48. package/dist/lib/local-source-override.d.ts +0 -22
  49. package/dist/lib/local-source-override.js +0 -141
  50. package/dist/lib/local-source-override.js.map +0 -1
  51. package/dist/support-tools/framework-sync.d.ts +0 -75
  52. package/dist/support-tools/framework-sync.js +0 -348
  53. package/dist/support-tools/framework-sync.js.map +0 -1
  54. package/dist/updater/framework-installer.d.ts +0 -57
  55. package/dist/updater/framework-installer.js +0 -492
  56. package/dist/updater/framework-installer.js.map +0 -1
  57. package/dist/updater/local-sdk-manager.d.ts +0 -16
  58. package/dist/updater/local-sdk-manager.js +0 -225
  59. package/dist/updater/local-sdk-manager.js.map +0 -1
package/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # hosanna-tools
2
2
 
3
+ This package lives in `packages/tools` in the Hosanna UI monorepo. It retains its
4
+ own package name, version, and lockfile. Run its development commands from this
5
+ directory; framework source remains at the repository root.
6
+
3
7
  ## Agent skills
4
8
 
5
9
  The repository instructions use TantaWowa's ClickUp workflow skills, Matt
@@ -7,7 +11,7 @@ Pocock's engineering skills, and pstack's `unslop` skill. Install them once on
7
11
  each developer machine:
8
12
 
9
13
  ```sh
10
- npx -y skills add TantaWowa/hosanna-tools --global --skill work-on-ticket hope-platform-planning hope-platform-architecture-ownership --agent '*' --full-depth --yes
14
+ npx -y skills add TantaWowa/hosanna-tools --global --skill work-on-ticket --agent '*' --full-depth --yes
11
15
  npx -y skills add mattpocock/skills --global --all
12
16
  npx -y skills add cursor/plugins --global --skill unslop --agent '*' --full-depth --yes
13
17
  ```
@@ -24,7 +28,7 @@ Run `npx -y skills update --global --yes` to update the installed skills later.
24
28
  - **[Video capture](docs/video-capture.md)** — standalone, agent-driven, and test-run recording for Roku capture cards and browser emulator windows.
25
29
  - **[Roku and browser emulation](docs/roku-browser-emulation.md)** — supported launch commands, device selection, and emulator configuration.
26
30
  - **[docs/README.md](docs/README.md)** — index of docs in this repo (MCP + source-map note).
27
- - **hosanna-ui** (separate repo): `docs/README.md` for agentic debugging and links back to this README.
31
+ - **[Hosanna UI framework](../../docs/README.md)** for agentic debugging and framework documentation.
28
32
 
29
33
  ## To use the tool
30
34
 
@@ -38,46 +42,68 @@ Note, if you are developing hosanna-tools, you will install the package locally.
38
42
  npm install . -g # path to the hosanna-tools directory
39
43
  ```
40
44
 
41
- ## Hosanna source install modes
45
+ ## Install the framework
46
+
47
+ The framework is an npm dependency. Install it with the tools in each app:
48
+
49
+ ```bash
50
+ npm install --save-dev --save-exact @tantawowa/hosanna-tools@5 @tantawowa/hosanna-ui@2 @tantawowa/hosanna-eslint-plugin@2
51
+ npx hst sdk:install
52
+ ```
53
+
54
+ Commit `package.json` and `package-lock.json`. A fresh checkout runs `npm ci`
55
+ followed by `npx hst sdk:install`. The command validates the installed package
56
+ and maintains these app paths:
57
+
58
+ ```text
59
+ hosanna-ui -> node_modules/@tantawowa/hosanna-ui
60
+ src/hosanna-ui -> ../hosanna-ui/src/hosanna-ui
61
+ ```
62
+
63
+ The other framework source folders use the same links. Existing `@hs-src`
64
+ imports, generated app files and compiler paths continue to work. Update the
65
+ framework with npm, then run `hst sdk:install` again. The command also reconciles
66
+ the framework's declared app dependencies. Use `--skip-consumer-dependencies`
67
+ when the app package manager has already installed them. Tools 5 requires
68
+ Vitest `>=3.2.0 <5`; update an older app test dependency during the npm upgrade.
69
+
70
+ For an app using the previous framework installer, install the npm packages
71
+ with `--ignore-scripts` first so old install hooks cannot run removed commands,
72
+ then run:
73
+
74
+ ```bash
75
+ npm install --ignore-scripts --save-dev --save-exact @tantawowa/hosanna-tools@5 @tantawowa/hosanna-ui@2 @tantawowa/hosanna-eslint-plugin@2
76
+ npx hst sdk:migrate --replace-existing
77
+ ```
78
+
79
+ Migration removes the old framework version/source settings and private source
80
+ override, preserves compiler and app settings, and replaces the old SDK folder
81
+ with the npm compatibility link. The previous real SDK folder and its Git
82
+ history remain in `hosanna-ui.before-npm`. It requires explicit replacement
83
+ and refuses to discard changes in a Git checkout. Move or commit those changes first.
84
+ Migration also updates exact known install, update, generation and cleanup
85
+ scripts. Custom scripts that select a framework source or generate inside the
86
+ SDK must be updated manually before migration; the command names them before
87
+ changing files. A failed migration restores app configuration, dependency
88
+ manifests, links and the previous SDK folder. After migration, run `npm rebuild`
89
+ if dependencies need their install scripts.
42
90
 
43
- Client projects can switch between private Keygen source tarballs, an embedded
44
- copy, a shared local checkout, and the legacy git/local modes:
91
+ For local framework development, use npm's normal local package support:
45
92
 
46
93
  ```bash
47
- hst config source # show current mode and immutable install identity
48
- hst config source keygen --sdk-version latest --install # newest valid private publication across all lanes
49
- hst config source keygen --sdk-version v1.29.1 --install # pin an exact immutable production release
50
- hst config source embedded --source-path ../hosanna-ui --install # filtered, reproducible copy
51
- hst config source shared --source-path ../hosanna-ui --install # portable relative link for local development
52
- hst config source git --install # legacy git checkout
53
- hst config source local # caller-provided LOCAL_SDK checkout
54
- ```
55
-
56
- Keygen mode writes `distribution: "keygen"`, `flavour: "fullSource"`, and no
57
- platform list to `hosanna.json`. Bare `latest` means the newest valid published
58
- full-source release by Keygen publication time, whether it came from a nightly,
59
- manual, or tag run. `@latest` remains the stable-lane alias and `@nightly`
60
- remains the development-lane alias. Exact versions are immutable pins.
61
- The same `HSC_LICENSE_KEY` used by the compiler authorizes source installation;
62
- there is no separate Hosanna source-license environment variable.
63
-
64
- Local `shared`, `embedded`, and `local` selections are private developer
65
- overrides. They do not rewrite `hosanna.json`: Git projects store the selection
66
- in worktree-specific Git metadata, so activating a local checkout cannot dirty
67
- the project or leak a machine path into a commit. `hst config source` and
68
- `hst env check` prominently report the active override, resolved checkout, and
69
- the committed distribution/version it temporarily supersedes. Running
70
- `hst sdk:install` or `hst framework:update` while an override is active preserves
71
- the local checkout. Select `keygen` or `git` with `--install
72
- --replace-existing` to clear the override and return to the committed source.
73
-
74
- Use `--replace-existing` with `--install` when switching an existing project
75
- between modes. Replacement is refused for an unrecognized or modified source
76
- root; `--force` is required for a dirty git checkout. Embedded installs exclude
77
- repository/build/secret state, write a content-hash manifest, and never modify
78
- the source checkout. Shared installs validate the source and create a relative
79
- symlink. Granular flavours and platform subsets are deliberately rejected with
80
- `NOT SUPPORTED YET — use flavour "fullSource"`.
94
+ npm install --save-dev ../hosanna-ui
95
+ npx hst sdk:install
96
+ ```
97
+
98
+ A declared dependency can also be redirected with `npm link`; the installer
99
+ resolves the linked package through the same npm path. To return to a release,
100
+ install the desired npm version and rerun `hst sdk:install`.
101
+
102
+ `hosanna.json` retains `transpiler-version` and app integration settings such as
103
+ `symlink-extra-folders`. Compiler installation and licensing are unchanged.
104
+ `config source`, `framework:update`, `framework:sync` and `framework:doctor`
105
+ were removed in tools 5. Framework delivery no longer uses Git clones, Keygen
106
+ source artifacts or private source overrides.
81
107
 
82
108
  ## Embedding as a library
83
109
 
@@ -295,25 +321,15 @@ See [RELEASE.md](RELEASE.md) for the full release process.
295
321
 
296
322
  Quick reference:
297
323
  ```bash
298
- npm run release -- patch # 2.14.0 → 2.14.1
299
- npm run release -- minor # 2.14.0 → 2.15.0
300
- npm run release -- major # 2.14.0 → 3.0.0
324
+ npm run release -- patch --dry-run
325
+ npm run release -- minor --dry-run
326
+ npm run release -- major --dry-run
301
327
  ```
302
328
 
303
- For tokenless agent releases, dispatch **Release Patch** or **Release Minor**
304
- in GitHub Actions. Both wrappers invoke the OIDC-trusted `release.yml`
305
- workflow on `main`; agents do not need npm credentials.
306
-
307
- ## CLI: configure-hosanna-url
308
-
309
- - **purpose**: Updates `hosanna.json` with a new Hosanna UI Git URL under the `git-url` key.
310
- - **usage**:
311
- - `hst configure-hosanna-url https://github.com/YourOrg/hosanna-ui.git`
312
- - `HOSANNA_UI_GIT_URL=https://github.com/YourOrg/hosanna-ui.git hst configure-hosanna-url`
313
- - **behavior**:
314
- - If no positional `gitUrl` is provided, the command falls back to the `HOSANNA_UI_GIT_URL` environment variable.
315
- - If neither is provided, it prints a message and leaves `hosanna.json` unchanged.
316
- - On success, it prints a sanitized URL (protocol + host + path) for logs.
329
+ The wrapper delegates to the root monorepo release command. Publication uses the
330
+ central `monorepo-release.yml` workflow after this package's npm trusted publisher
331
+ has been migrated. See [RELEASE.md](RELEASE.md) for the configuration and validation
332
+ requirements.
317
333
 
318
334
  # Hosanna Tools CLI
319
335
 
@@ -337,12 +353,12 @@ Hosanna launches native Roku or Roku emulated in the browser. Other platform lau
337
353
  ```bash
338
354
  hst run
339
355
  hst run --last
340
- hst run roku dev device --device 192.168.1.229
356
+ hst run roku dev device --device 192.0.2.50
341
357
  hst run roku dev emulator
342
358
  hst run browser dev emulator
343
359
  hst build roku dev device
344
360
  hst target:list --json
345
- hst launch roku --app-id dev --device 192.168.1.229
361
+ hst launch roku --app-id dev --device 192.0.2.50
346
362
  ```
347
363
 
348
364
  `web` aliases `browser`. Browser launches default to the `rokufhd` device preset. Existing development sessions are reused only when the requested app and port match. Configured run defaults live in `.hosanna-tools/run.json`.
@@ -430,10 +446,9 @@ npm run roku:build
430
446
  ### CI Commands (`ci:*`) - Continuous Integration
431
447
  - `ci:project-actions --base <ref> [--head <ref>]` - Select `hst.json` `prScopeRules` actions for changed files; add `--run` to execute matched npm scripts
432
448
  - `ci:extract-pkg-key` - Extract signing key from an existing signed Roku package as base64
433
- - `config set --git-url` - Configure hosanna.json git-url from argument or environment
434
449
 
435
- Project-specific PR rules belong in `hst.json`; SDK source/version settings
436
- remain in `hosanna.json`:
450
+ Project-specific PR rules belong in `hst.json`. The framework dependency and
451
+ version belong in `package.json` and `package-lock.json`:
437
452
 
438
453
  ```json
439
454
  {
@@ -490,8 +505,8 @@ The installed SDK owns its app dependency contract in
490
505
  Both maps are required. `hst doctor` and `hst env check` validate the exact
491
506
  manifest, the app's direct `package.json` declarations, and the versions
492
507
  actually available through the app's Node resolution path (including a
493
- workspace-hoisted install). An older SDK without the manifest is reported as a
494
- supported legacy install with no requirements.
508
+ workspace-hoisted install). The npm SDK must include this manifest; an
509
+ incomplete package fails the environment check.
495
510
 
496
511
  `hst doctor --fix` and `hst env fix` add missing requirements and run one
497
512
  app-root `npm install`. The repair is additive to the app's dependency set: it
@@ -508,10 +523,8 @@ rollback snapshot; run the workspace's package manager from its root instead.
508
523
  If `npm install` fails, or the installed
509
524
  versions still fail the audit afterward, `package.json` and npm lockfiles
510
525
  (`package-lock.json` and `npm-shrinkwrap.json`) are restored to their original
511
- contents. SDK installs and updates—including
512
- Git, Keygen, local, embedded, and shared source modes—run the same reconciliation
513
- after the new SDK is ready, so a framework upgrade cannot leave new app
514
- requirements undeclared.
526
+ contents. `sdk:install` and `sdk:migrate` run the same reconciliation after
527
+ resolving the npm package, so framework requirements remain declared in the app.
515
528
 
516
529
  Library callers can inspect or run the same focused operation through
517
530
  `env.dependencies.audit({ projectRoot })` and
@@ -520,13 +533,12 @@ repair results without exiting the process.
520
533
 
521
534
  ### Framework Source Symlinks (semver-keyed registry)
522
535
 
523
- `hst` symlinks framework source folders from the `hosanna-ui/` checkout into the
536
+ `hst` symlinks framework source folders through the `hosanna-ui/` npm compatibility link into the
524
537
  project (`src/hosanna-ui -> ../hosanna-ui/src/hosanna-ui`, etc.). Which folders
525
538
  get linked is decided by a **semver-keyed registry**: an entry applies when the
526
539
  installed framework version is **>=** its key, and the highest applicable key
527
540
  wins. The installed version is read from `hosanna-ui/package.json` (prerelease
528
- suffixes are ignored, so `1.31.0-next` counts as `1.31.0`) — this works for
529
- tags, branches, `@latest`, and `LOCAL_SDK` alike.
541
+ suffixes are ignored, so `2.0.0-next` counts as `2.0.0`).
530
542
 
531
543
  **Built-in defaults** (`src/updater/symlink-registry.ts`):
532
544
 
@@ -534,10 +546,7 @@ tags, branches, `@latest`, and `LOCAL_SDK` alike.
534
546
  |---|---|
535
547
  | >= 1.0.0 | `hosanna-bridge-core`, `hosanna-bridge-http`, `hosanna-bridge-lib`, `hosanna-bridge-targets`, `hosanna-list`, `hosanna-ui` |
536
548
 
537
- Prefer folder existence over version gates when adding registry entries: a
538
- checkout's `package.json` version can lag the tag it was cut from (hosanna-ui
539
- `v1.28.3` reads `1.28.1-next`), and folders absent from a checkout are already
540
- skipped safely at sync time.
549
+ Folders absent from the installed package are skipped when syncing links.
541
550
 
542
551
  `hst env check` always prints the active set, the matched registry version, and the
543
552
  source (`built-in`, `hosanna.json override`, `+ N extra`). `hst env fix`
@@ -551,8 +560,6 @@ to the resolved defaults; cannot break them. Folder names are forgiving
551
560
 
552
561
  ```json
553
562
  {
554
- "git-url": "git@github.com:TantaWowa/hosanna-ui.git",
555
- "sdk-version": "v1.31.0",
556
563
  "symlink-extra-folders": ["hosanna-game/", "hosanna-game-examples/"]
557
564
  }
558
565
  ```
@@ -599,6 +606,8 @@ silently break an install.
599
606
 
600
607
  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.
601
608
 
609
+ `timeoutMs` sets the limit for each launch/readiness stage and browser navigation during initial launch, runtime relaunch, and `hs.app.deepLink(...)`. Navigation still waits for the page's `load` event. The default is 60 seconds; a positive, finite `HS_TEST_TIMEOUT_MS` overrides the config. This is a per-stage limit, not a deadline for the whole test. Vitest's `testTimeout` and `hookTimeout`, and timeouts passed to individual assertion waits, remain separate.
610
+
602
611
  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.
603
612
 
604
613
  Tests can record a complete browser emulator or Roku device run:
@@ -1,6 +1,6 @@
1
1
  {
2
- "buildDate": "2026-11-03T23:23:08+00:00",
3
- "buildDateISO": "2026-11-03T23:23:08.559Z",
2
+ "buildDate": "2026-11-12T23:40:49+00:00",
3
+ "buildDateISO": "2026-11-12T23:40:49.615Z",
4
4
  "timeZone": "UTC",
5
- "gitHash": "a1fefb6"
5
+ "gitHash": "08a7d43e7"
6
6
  }
package/dist/cli.js CHANGED
@@ -243,14 +243,6 @@ const RUN_PLATFORM_CHOICES = ['browser', 'web', 'roku'];
243
243
  function stringArg(value) {
244
244
  return typeof value === 'string' && value.trim() ? value.trim() : undefined;
245
245
  }
246
- function stringArrayArg(value) {
247
- if (typeof value === 'string')
248
- return [value];
249
- if (Array.isArray(value)) {
250
- return value.filter((item) => typeof item === 'string');
251
- }
252
- return undefined;
253
- }
254
246
  function parseVideoCaptureRect(value) {
255
247
  if (value === undefined)
256
248
  return undefined;
@@ -391,7 +383,7 @@ async function runEnvironmentCommand(action, verbose) {
391
383
  // Let yargs handle all commands including env
392
384
  (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
393
385
  .version(`${buildVersion}\n git: ${buildGitHash}\n built: ${buildDate}`)
394
- .usage(`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 doctor Friendly environment/package check (use --fix to repair)\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 Roku and Roku browser emulator builds\n run Roku and Roku browser emulator launches\n capture Standalone scenario video capture\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`)
386
+ .usage(`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 doctor Friendly environment/package check (use --fix to repair)\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 Roku and Roku browser emulator builds\n run Roku and Roku browser emulator launches\n capture Standalone scenario video capture\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 license:* Hosanna compiler licensing\n secrets:* Portable .secrets files\n rasp:* RASP cert scripts`)
395
387
  .option('verbose', {
396
388
  type: 'boolean',
397
389
  default: false,
@@ -962,84 +954,23 @@ async function runEnvironmentCommand(action, verbose) {
962
954
  }
963
955
  })
964
956
  // remove old --env flag; env is now a subcommand
965
- .command('config [action] [mode]', 'Show or update hosanna.json configuration from a curated catalog', yargs => yargs
957
+ .command('config [action]', 'Show or update hosanna.json configuration from a curated catalog', yargs => yargs
966
958
  .positional('action', {
967
959
  type: 'string',
968
- choices: ['show', 'set', 'wizard', 'source'],
969
- describe: 'show prints current config, set writes values from flags, wizard runs the interactive setup, source switches install mode',
960
+ choices: ['show', 'set', 'wizard'],
961
+ describe: 'show prints compiler config, set writes values from flags, wizard runs compiler setup',
970
962
  })
971
- .positional('mode', {
972
- type: 'string',
973
- choices: ['show', 'keygen', 'git', 'local', 'embedded', 'shared'],
974
- describe: 'Source install mode when action is source',
975
- })
976
- .option('git-url', { type: 'string', describe: 'Set hosanna.json git-url' })
977
- .option('sdk-version', { type: 'string', describe: 'Set hosanna.json sdk-version' })
978
- .option('branch', { type: 'string', describe: 'Set hosanna.json branch; use an empty string to prefer sdk-version' })
979
963
  .option('transpiler-version', { type: 'string', describe: 'Set hosanna.json transpiler-version' })
980
964
  .option('config-catalog-url', { type: 'string', describe: 'Persist a remote catalog URL into hosanna.json' })
981
965
  .option('catalog-url', { type: 'string', describe: 'Use a remote catalog URL for this run without writing it' })
982
- .option('flavour', { type: 'string', choices: ['fullSource', 'core', 'game', 'full'], describe: 'Keygen source flavour when action is source (supported: fullSource)' })
983
- .option('platforms', { type: 'string', array: true, describe: 'Future split-platform selection; omit for fullSource' })
984
- .option('source-path', { type: 'string', describe: 'Hosanna UI checkout to copy for embedded mode or link for shared mode' })
985
- .option('install', { type: 'boolean', default: false, describe: 'Install the selected source mode atomically after updating config' })
986
- .option('replace-existing', { type: 'boolean', default: false, describe: 'Allow --install to replace an existing hosanna-ui source root when switching modes' })
987
- .option('force', { type: 'boolean', default: false, describe: 'Allow --install to replace a dirty git checkout when used with --replace-existing' })
988
966
  .option('fix', { type: 'boolean', default: false, describe: 'Run hst env fix after updating config' }), async (args) => {
989
967
  if (args.verbose) {
990
968
  process.env.HOSANNA_VERBOSE = '1';
991
969
  process.env.HOSANNA_UPDATER_VERBOSE = '1';
992
970
  }
993
971
  const actionArg = typeof args.action === 'string' ? args.action : undefined;
994
- if (actionArg === 'source') {
995
- try {
996
- const result = await (0, hosanna_config_js_1.runHosannaSourceConfig)({
997
- cwd: process.cwd(),
998
- mode: (typeof args.mode === 'string' ? args.mode : 'show'),
999
- flavour: args.flavour,
1000
- platforms: (0, hosanna_config_js_1.parsePlatformList)(stringArrayArg(args.platforms)),
1001
- sourcePath: typeof args.sourcePath === 'string' ? args.sourcePath : undefined,
1002
- sdkVersion: typeof args.sdkVersion === 'string' ? args.sdkVersion : undefined,
1003
- gitUrl: typeof args.gitUrl === 'string' ? args.gitUrl : undefined,
1004
- branch: typeof args.branch === 'string' ? args.branch : undefined,
1005
- install: Boolean(args.install),
1006
- replaceExisting: Boolean(args.replaceExisting),
1007
- force: Boolean(args.force),
1008
- writeStdout: text => console.info(text),
1009
- });
1010
- if (args.fix && result.updated) {
1011
- const { fixAllEnvironmentIssues } = await Promise.resolve().then(() => __importStar(require('./lib/env-info.js')));
1012
- const fixResult = await fixAllEnvironmentIssues(process.cwd());
1013
- console.info(`\n${fixResult.message}`);
1014
- if (fixResult.remainingIssues.length > 0) {
1015
- console.info('\n❌ Remaining issues:');
1016
- for (const issue of fixResult.remainingIssues) {
1017
- console.info(` • ${issue}`);
1018
- }
1019
- }
1020
- if (!process.env.VITEST)
1021
- process.exit(fixResult.success ? 0 : 1);
1022
- return;
1023
- }
1024
- if (!process.env.VITEST)
1025
- process.exit(0);
1026
- return;
1027
- }
1028
- catch (err) {
1029
- console.error('❌ Failed to configure source install mode:', err instanceof Error ? err.message : err);
1030
- if (!process.env.VITEST)
1031
- process.exit(1);
1032
- return;
1033
- }
1034
- }
1035
972
  const action = actionArg === 'show' || actionArg === 'set' ? actionArg : 'wizard';
1036
973
  const updates = {};
1037
- if (typeof args.gitUrl === 'string')
1038
- updates['git-url'] = args.gitUrl;
1039
- if (typeof args.sdkVersion === 'string')
1040
- updates['sdk-version'] = args.sdkVersion;
1041
- if (typeof args.branch === 'string')
1042
- updates.branch = args.branch;
1043
974
  if (typeof args.transpilerVersion === 'string')
1044
975
  updates['transpiler-version'] = args.transpilerVersion;
1045
976
  if (typeof args.configCatalogUrl === 'string')
@@ -1447,124 +1378,33 @@ async function runEnvironmentCommand(action, verbose) {
1447
1378
  process.exit(1);
1448
1379
  }
1449
1380
  })
1450
- .command('sdk:install', 'Install the SDK. Uses git (hosanna.json git-url) or license-gated keygen artifacts (hosanna.json "distribution": "keygen")', yargs => yargs
1451
- .option('force', { type: 'boolean', default: false, describe: 'Reinstall even if the requested version is already installed (keygen mode)' })
1452
- .option('skip-consumer-dependencies', { type: 'boolean', default: false, describe: 'Install the SDK without changing the app dependency manifest or installing app dependencies' }), async (args) => {
1453
- if (args.verbose) {
1454
- process.env.HOSANNA_VERBOSE = '1';
1455
- process.env.HOSANNA_UPDATER_VERBOSE = '1';
1456
- }
1457
- console.info('Running sdk:install command...');
1458
- try {
1459
- const skipConsumerDependencyReconciliation = Boolean(args['skip-consumer-dependencies']);
1460
- const { assertLocalSourceOverrideInstalled, readLocalSourceOverride } = await Promise.resolve().then(() => __importStar(require('./lib/local-source-override.js')));
1461
- const localOverride = readLocalSourceOverride(process.cwd());
1462
- if (localOverride) {
1463
- assertLocalSourceOverrideInstalled(process.cwd(), localOverride);
1464
- const { prepareLocalSdkIntegration } = await Promise.resolve().then(() => __importStar(require('./updater/local-sdk-manager.js')));
1465
- prepareLocalSdkIntegration(process.cwd(), { skipConsumerDependencyReconciliation });
1466
- console.info(`Private ${localOverride.mode} Hosanna source override is active; preserved the local SDK and skipped the committed distribution install.`);
1467
- console.info('sdk:install command completed.');
1468
- process.exit(0);
1469
- return;
1470
- }
1471
- // Keygen-distributed projects bypass the git flow entirely.
1472
- const hosannaJsonPath = path.resolve(process.cwd(), 'hosanna.json');
1473
- let distribution;
1474
- if (fs.existsSync(hosannaJsonPath)) {
1475
- try {
1476
- distribution = JSON.parse(fs.readFileSync(hosannaJsonPath, 'utf-8')).distribution;
1477
- }
1478
- catch ( /* fall through to git flow which reports config errors */_a) { /* fall through to git flow which reports config errors */ }
1479
- }
1480
- if (distribution === 'keygen') {
1481
- const mod = await Promise.resolve().then(() => __importStar(require('./updater/framework-installer.js')));
1482
- await mod.installFrameworkFromKeygen({ force: Boolean(args.force), skipConsumerDependencyReconciliation });
1483
- }
1484
- else {
1485
- await (0, sdk_manager_js_1.installSdk)(false, false, true, { skipConsumerDependencyReconciliation });
1486
- }
1487
- console.info('sdk:install command completed.');
1488
- process.exit(0); // Exit after successful completion
1489
- }
1490
- catch (err) {
1491
- console.error('An error occurred during the install process:', err);
1492
- process.exit(1); // Exit with error code on failure
1493
- }
1494
- })
1495
- .command('framework:update', 'Update the keygen-distributed hosanna-ui folder to the version in hosanna.json (no-op when already current)', yargs => yargs
1496
- .option('force', { type: 'boolean', default: false, describe: 'Reinstall even if the requested version is already installed' }), async (args) => {
1497
- if (args.verbose) {
1498
- process.env.HOSANNA_VERBOSE = '1';
1499
- process.env.HOSANNA_UPDATER_VERBOSE = '1';
1500
- }
1381
+ .command('sdk:install', 'Integrate the npm-installed @tantawowa/hosanna-ui package and repair its source links', yargs => yargs.option('skip-consumer-dependencies', { type: 'boolean', default: false, describe: 'Repair links without changing app dependency declarations' }), async (args) => {
1501
1382
  try {
1502
- const { assertLocalSourceOverrideInstalled, readLocalSourceOverride } = await Promise.resolve().then(() => __importStar(require('./lib/local-source-override.js')));
1503
- const localOverride = readLocalSourceOverride(process.cwd());
1504
- if (localOverride) {
1505
- assertLocalSourceOverrideInstalled(process.cwd(), localOverride);
1506
- const { prepareLocalSdkIntegration } = await Promise.resolve().then(() => __importStar(require('./updater/local-sdk-manager.js')));
1507
- prepareLocalSdkIntegration(process.cwd());
1508
- console.info(`Private ${localOverride.mode} Hosanna source override is active; preserved the local SDK and skipped framework:update.`);
1383
+ await (0, sdk_manager_js_1.installSdk)({ skipConsumerDependencyReconciliation: Boolean(args['skip-consumer-dependencies']) });
1384
+ if (!process.env.VITEST)
1509
1385
  process.exit(0);
1510
- return;
1511
- }
1512
- const mod = await Promise.resolve().then(() => __importStar(require('./updater/framework-installer.js')));
1513
- await mod.installFrameworkFromKeygen({ force: Boolean(args.force) });
1514
- process.exit(0);
1515
1386
  }
1516
- catch (err) {
1517
- console.error('❌ framework:update failed:', err instanceof Error ? err.message : err);
1518
- process.exit(1);
1387
+ catch (error) {
1388
+ console.error(error instanceof Error ? error.message : error);
1389
+ if (!process.env.VITEST)
1390
+ process.exit(1);
1519
1391
  }
1520
1392
  })
1521
- .command('framework:sync', 'Maintainer: fast-forward tier-1 client forks (branches + tags) from upstream, driven by ~/.hosanna/sync-clients.json', yargs => yargs
1522
- .option('client', { type: 'string', describe: 'Sync a single named client from the registry (default: all clients)' })
1523
- .option('registry', { type: 'string', describe: 'Path to the sync registry (default: ~/.hosanna/sync-clients.json)' })
1524
- .option('dry-run', { type: 'boolean', default: false, describe: 'Show what would be pushed without pushing' })
1525
- .option('init', { type: 'boolean', default: false, describe: 'Create a template registry file and exit' })
1526
- .example('$0 framework:sync', 'Sync all registered client forks')
1527
- .example('$0 framework:sync --client ewtn --dry-run', 'Preview a sync for one client'), async (args) => {
1393
+ .command('sdk:migrate', 'Migrate an existing app to its npm-installed framework, preserving compiler configuration', yargs => yargs
1394
+ .option('replace-existing', { type: 'boolean', default: false, describe: 'Replace the old SDK folder or link; dirty Git checkouts are always protected' })
1395
+ .option('skip-consumer-dependencies', { type: 'boolean', default: false, describe: 'Migrate config and links without changing app dependency declarations' }), async (args) => {
1528
1396
  try {
1529
- const mod = await Promise.resolve().then(() => __importStar(require('./support-tools/framework-sync.js')));
1530
- if (args.init) {
1531
- const filePath = mod.initRegistry(args.registry);
1532
- console.info(`✅ Created sync registry template: ${filePath}`);
1533
- console.info('Edit it to add your clients, then run: hst framework:sync');
1397
+ await (0, sdk_manager_js_1.migrateSdk)({ replaceExisting: Boolean(args['replace-existing']), skipConsumerDependencyReconciliation: Boolean(args['skip-consumer-dependencies']) });
1398
+ if (!process.env.VITEST)
1534
1399
  process.exit(0);
1535
- }
1536
- const results = await mod.syncFrameworkForks({
1537
- registryPath: args.registry,
1538
- client: args.client,
1539
- dryRun: Boolean(args['dry-run']),
1540
- });
1541
- const ok = mod.printSyncSummary(results, Boolean(args['dry-run']));
1542
- process.exit(ok ? 0 : 1);
1543
- }
1544
- catch (err) {
1545
- console.error('❌ framework:sync failed:', err instanceof Error ? err.message : err);
1546
- process.exit(1);
1547
- }
1548
- })
1549
- .command('framework:doctor', 'Maintainer: validate tier-1 client forks (reachability, hosanna.json git-url/schema, sdk-version tags)', yargs => yargs
1550
- .option('client', { type: 'string', describe: 'Check a single named client from the registry (default: all clients)' })
1551
- .option('registry', { type: 'string', describe: 'Path to the sync registry (default: ~/.hosanna/sync-clients.json)' })
1552
- .example('$0 framework:doctor', 'Validate all registered client forks'), async (args) => {
1553
- try {
1554
- const mod = await Promise.resolve().then(() => __importStar(require('./support-tools/framework-sync.js')));
1555
- const issues = await mod.doctorFrameworkForks({
1556
- registryPath: args.registry,
1557
- client: args.client,
1558
- });
1559
- const ok = mod.printDoctorSummary(issues);
1560
- process.exit(ok ? 0 : 1);
1561
1400
  }
1562
- catch (err) {
1563
- console.error('❌ framework:doctor failed:', err instanceof Error ? err.message : err);
1564
- process.exit(1);
1401
+ catch (error) {
1402
+ console.error(error instanceof Error ? error.message : error);
1403
+ if (!process.env.VITEST)
1404
+ process.exit(1);
1565
1405
  }
1566
1406
  })
1567
- .command('license:activate <key>', 'Store your Hosanna framework license key (~/.hosanna/license.key) after validating it', yargs => yargs
1407
+ .command('license:activate <key>', 'Store your Hosanna compiler license key (~/.hosanna/license.key) after validating it', yargs => yargs
1568
1408
  .positional('key', { type: 'string', demandOption: true, describe: 'License key issued for your organization' }), async (args) => {
1569
1409
  try {
1570
1410
  const mod = await Promise.resolve().then(() => __importStar(require('./updater/license-manager.js')));
@@ -1591,7 +1431,7 @@ async function runEnvironmentCommand(action, verbose) {
1591
1431
  process.exit(1);
1592
1432
  }
1593
1433
  })
1594
- .command('license:status', 'Validate the stored license and print its entitlements (flavour, platforms, tier)', yargs => yargs, async () => {
1434
+ .command('license:status', 'Validate the stored compiler license and print its entitlements', yargs => yargs, async () => {
1595
1435
  try {
1596
1436
  const mod = await Promise.resolve().then(() => __importStar(require('./updater/license-manager.js')));
1597
1437
  const credential = mod.requireLicenseCredential();
@@ -1607,10 +1447,6 @@ async function runEnvironmentCommand(action, verbose) {
1607
1447
  if (info.validation.expiry)
1608
1448
  console.info(` Expires: ${info.validation.expiry}`);
1609
1449
  console.info(` Credential: ${credential.source === 'environment' ? 'HSC_LICENSE_KEY' : credential.filePath}`);
1610
- const flavours = mod.entitledFlavours(info.entitlements);
1611
- const platforms = mod.entitledPlatforms(info.entitlements);
1612
- console.info(` Flavour: ${flavours.length ? flavours[flavours.length - 1] : '(none)'}`);
1613
- console.info(` Platforms: ${platforms.length ? platforms.join(', ') : '(none)'}`);
1614
1450
  console.info(` Entitlements: ${info.entitlements.join(', ') || '(none)'}`);
1615
1451
  for (const warning of mod.getLicenseConfigurationWarnings()) {
1616
1452
  console.warn(` ⚠ ${warning}`);
@@ -1943,8 +1779,8 @@ async function runEnvironmentCommand(action, verbose) {
1943
1779
  .example('$0 run --last', 'Immediately run on the last selected destination')
1944
1780
  .example('$0 run --platform roku --target emulator', 'Start/reuse the Roku TV web emulator')
1945
1781
  .example('$0 run --platform roku --target device --device "living room"', 'Build, deploy, launch, and stream logs on a Roku device')
1946
- .example('$0 run roku dev device --app criterion', 'Run a configured monorepo app/flavor')
1947
- .example('$0 run roku prod device --hsc-project platforms/hsconfig.criterion.prod.json', 'Override the Roku HSC project file'), async (args) => {
1782
+ .example('$0 run roku dev device --app client2', 'Run a configured monorepo app/flavor')
1783
+ .example('$0 run roku prod device --hsc-project platforms/hsconfig.client2.prod.json', 'Override the Roku HSC project file'), async (args) => {
1948
1784
  await runRunPlanCommand('run', args);
1949
1785
  })
1950
1786
  .command('device:run', 'Build, deploy, launch, and stream logs for a device', yargs => yargs
@@ -2519,9 +2355,9 @@ async function runEnvironmentCommand(action, verbose) {
2519
2355
  default: false,
2520
2356
  describe: 'Keep the temporary RASP capture JSON/screenshots after the script is written.',
2521
2357
  })
2522
- .example('$0 rasp:capture login --channel-name "Criterion"', 'Record and export to rasp-scripts/login.rasp')
2523
- .example('$0 rasp:capture login --channel-name "Criterion" --login-text "user@example.com" --password-text "MyPass1!"', 'Default credential flow with custom text')
2524
- .example('$0 rasp:capture login --channel-name "Criterion" --credential-flow ./flow.rasp --login-text U --password-text P', 'Override flow via YAML file'), async (args) => {
2358
+ .example('$0 rasp:capture login --channel-name "Client2"', 'Record and export to rasp-scripts/login.rasp')
2359
+ .example('$0 rasp:capture login --channel-name "Client2" --login-text "user@example.com" --password-text "MyPass1!"', 'Default credential flow with custom text')
2360
+ .example('$0 rasp:capture login --channel-name "Client2" --credential-flow ./flow.rasp --login-text U --password-text P', 'Override flow via YAML file'), async (args) => {
2525
2361
  var _a, _b;
2526
2362
  const path = await Promise.resolve().then(() => __importStar(require('path')));
2527
2363
  const fs = await Promise.resolve().then(() => __importStar(require('fs')));