@tantawowa/hosanna-tools 2.26.0 → 3.1.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 (92) hide show
  1. package/dist/build-info.json +3 -3
  2. package/dist/cli.js +393 -63
  3. package/dist/cli.js.map +1 -1
  4. package/dist/device/device-cli.js +2 -0
  5. package/dist/device/device-cli.js.map +1 -1
  6. package/dist/device/drivers/roku-driver.d.ts +2 -0
  7. package/dist/device/drivers/roku-driver.js +11 -0
  8. package/dist/device/drivers/roku-driver.js.map +1 -1
  9. package/dist/device/log-stream.d.ts +1 -0
  10. package/dist/device/log-stream.js +11 -0
  11. package/dist/device/log-stream.js.map +1 -1
  12. package/dist/device/types.d.ts +2 -0
  13. package/dist/index.d.ts +11 -15
  14. package/dist/index.js +16 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/native/app-entry-generation.d.ts +9 -0
  17. package/dist/native/app-entry-generation.js +100 -0
  18. package/dist/native/app-entry-generation.js.map +1 -0
  19. package/dist/native/native-project.d.ts +103 -0
  20. package/dist/native/native-project.js +848 -0
  21. package/dist/native/native-project.js.map +1 -0
  22. package/dist/native/templates/android/AndroidManifest.xml +20 -0
  23. package/dist/native/templates/android/app-build.gradle.kts +186 -0
  24. package/dist/native/templates/android/build.gradle.kts +4 -0
  25. package/dist/native/templates/android/colors.xml +3 -0
  26. package/dist/native/templates/android/gradle-wrapper.properties +5 -0
  27. package/dist/native/templates/android/gradle.properties +2 -0
  28. package/dist/native/templates/android/gradlew +11 -0
  29. package/dist/native/templates/android/gradlew.bat +12 -0
  30. package/dist/native/templates/android/launcher.xml +12 -0
  31. package/dist/native/templates/android/settings.gradle.kts +18 -0
  32. package/dist/native/templates/android/strings.xml +3 -0
  33. package/dist/native/templates/android/styles.xml +8 -0
  34. package/dist/native/templates/apple/AppIconContents.json +7 -0
  35. package/dist/native/templates/apple/Info.plist +24 -0
  36. package/dist/native/templates/apple/Podfile.example +7 -0
  37. package/dist/native/templates/apple/project.yml +95 -0
  38. package/dist/run/android-emulator.d.ts +8 -2
  39. package/dist/run/android-emulator.js +206 -39
  40. package/dist/run/android-emulator.js.map +1 -1
  41. package/dist/run/apple-tv.d.ts +5 -2
  42. package/dist/run/apple-tv.js +33 -32
  43. package/dist/run/apple-tv.js.map +1 -1
  44. package/dist/run/executor.d.ts +18 -0
  45. package/dist/run/executor.js +134 -0
  46. package/dist/run/executor.js.map +1 -0
  47. package/dist/run/ios-device.d.ts +4 -1
  48. package/dist/run/ios-device.js +19 -24
  49. package/dist/run/ios-device.js.map +1 -1
  50. package/dist/run/ios-simulator.d.ts +1 -1
  51. package/dist/run/ios-simulator.js +15 -23
  52. package/dist/run/ios-simulator.js.map +1 -1
  53. package/dist/run/native-build.d.ts +18 -0
  54. package/dist/run/native-build.js +154 -0
  55. package/dist/run/native-build.js.map +1 -0
  56. package/dist/run/planner.d.ts +100 -0
  57. package/dist/run/planner.js +898 -0
  58. package/dist/run/planner.js.map +1 -0
  59. package/dist/run/run-config.d.ts +13 -0
  60. package/dist/run/run-config.js +115 -32
  61. package/dist/run/run-config.js.map +1 -1
  62. package/dist/run/run-controller.d.ts +26 -1
  63. package/dist/run/run-controller.js +372 -32
  64. package/dist/run/run-controller.js.map +1 -1
  65. package/dist/run/targets.d.ts +4 -1
  66. package/dist/run/targets.js +5 -2
  67. package/dist/run/targets.js.map +1 -1
  68. package/dist/run/types.d.ts +1 -1
  69. package/dist/run/types.js +3 -1
  70. package/dist/run/types.js.map +1 -1
  71. package/dist/support-tools/android-dev-server.d.ts +2 -0
  72. package/dist/support-tools/android-dev-server.js +122 -0
  73. package/dist/support-tools/android-dev-server.js.map +1 -0
  74. package/dist/support-tools/android-metro-build.d.ts +2 -0
  75. package/dist/support-tools/android-metro-build.js +265 -0
  76. package/dist/support-tools/android-metro-build.js.map +1 -0
  77. package/dist/support-tools/apple-bundler.d.ts +7 -0
  78. package/dist/support-tools/apple-bundler.js +331 -0
  79. package/dist/support-tools/apple-bundler.js.map +1 -0
  80. package/dist/support-tools/build-config-resolver.d.ts +6 -0
  81. package/dist/support-tools/build-config-resolver.js +66 -1
  82. package/dist/support-tools/build-config-resolver.js.map +1 -1
  83. package/dist/support-tools/run-roku.d.ts +2 -0
  84. package/dist/support-tools/run-roku.js +28 -8
  85. package/dist/support-tools/run-roku.js.map +1 -1
  86. package/dist/support-tools/tsc-filter.d.ts +2 -0
  87. package/dist/support-tools/tsc-filter.js +62 -0
  88. package/dist/support-tools/tsc-filter.js.map +1 -0
  89. package/dist/vite.d.ts +53 -0
  90. package/dist/vite.js +566 -0
  91. package/dist/vite.js.map +1 -0
  92. package/package.json +8 -2
@@ -1,6 +1,6 @@
1
1
  {
2
- "buildDate": "2026-06-21T00:24:40+01:00",
3
- "buildDateISO": "2026-06-20T23:24:40.984Z",
2
+ "buildDate": "2026-06-22T22:01:33+01:00",
3
+ "buildDateISO": "2026-06-22T21:01:33.521Z",
4
4
  "timeZone": "Europe/London",
5
- "gitHash": "11f0011"
5
+ "gitHash": "03dcf25"
6
6
  }
package/dist/cli.js CHANGED
@@ -60,6 +60,152 @@ const env_info_js_1 = require("./lib/env-info.js");
60
60
  const hosanna_tools_utils_js_1 = require("./lib/hosanna-tools-utils.js");
61
61
  const cli_app_config_resolve_js_1 = require("./lib/cli.app-config-resolve.js");
62
62
  const hosanna_config_js_1 = require("./lib/hosanna-config.js");
63
+ function formatRunPlanText(plan, explain, command = 'run') {
64
+ const lines = [
65
+ `hst ${command} plan: ${plan.resolved.platform} ${plan.resolved.env} ${plan.resolved.target}`,
66
+ '',
67
+ ...plan.shellPreview.map(line => ` ${line}`),
68
+ ];
69
+ if (explain) {
70
+ lines.push('', 'Resolution sources:');
71
+ for (const [key, value] of Object.entries(plan.sources)) {
72
+ lines.push(` ${key}: ${value}`);
73
+ }
74
+ }
75
+ return lines.join('\n');
76
+ }
77
+ function rawCommandPositionals(command) {
78
+ const raw = (0, helpers_1.hideBin)(process.argv);
79
+ const index = raw.indexOf(command);
80
+ if (index === -1)
81
+ return [];
82
+ const result = [];
83
+ for (const token of raw.slice(index + 1)) {
84
+ if (token === '--' || token.startsWith('-'))
85
+ break;
86
+ result.push(token);
87
+ }
88
+ return result;
89
+ }
90
+ function rawHasFlag(name) {
91
+ const dashName = `--${name}`;
92
+ return (0, helpers_1.hideBin)(process.argv).some(token => token === dashName || token.startsWith(`${dashName}=`));
93
+ }
94
+ function runPlanFlagsFromCliArgs(args, format) {
95
+ return {
96
+ platform: rawHasFlag('platform') ? args.platform : undefined,
97
+ env: rawHasFlag('env') ? args.env : undefined,
98
+ app: rawHasFlag('app') ? args.app : undefined,
99
+ target: rawHasFlag('target') ? args.target : undefined,
100
+ device: rawHasFlag('device') ? args.device : undefined,
101
+ profile: rawHasFlag('profile') ? args.profile : undefined,
102
+ appConfig: rawHasFlag('app-config') ? args['app-config'] : undefined,
103
+ hotReload: rawHasFlag('hot-reload') ? Boolean(args['hot-reload']) : undefined,
104
+ bundled: rawHasFlag('bundled') ? Boolean(args.bundled) : undefined,
105
+ noBuild: rawHasFlag('no-build') ? Boolean(args['no-build']) : undefined,
106
+ buildOnly: rawHasFlag('build-only') ? Boolean(args['build-only']) : undefined,
107
+ noLogs: rawHasFlag('no-logs') ? Boolean(args['no-logs']) : undefined,
108
+ logs: rawHasFlag('logs') ? Boolean(args.logs) : undefined,
109
+ sourceRoot: rawHasFlag('source-root') ? args['source-root'] : undefined,
110
+ appPackage: rawHasFlag('app-package') ? args['app-package'] : undefined,
111
+ files: rawHasFlag('files') ? args.files : undefined,
112
+ deployTimeoutMs: rawHasFlag('deploy-timeout-ms') ? args['deploy-timeout-ms'] : undefined,
113
+ config: rawHasFlag('config') ? args.config : undefined,
114
+ hscProject: rawHasFlag('hsc-project') ? args.hscProject : undefined,
115
+ vitePort: rawHasFlag('vitePort') ? args.vitePort : undefined,
116
+ webAppPort: rawHasFlag('webAppPort') ? args.webAppPort : undefined,
117
+ deviceAppPort: rawHasFlag('deviceAppPort') ? args.deviceAppPort : undefined,
118
+ extensionPort: rawHasFlag('extensionPort') ? args.extensionPort : undefined,
119
+ managementPort: rawHasFlag('managementPort') ? args.managementPort : undefined,
120
+ appName: rawHasFlag('appName') ? args.appName : undefined,
121
+ replace: rawHasFlag('replace') ? Boolean(args.replace) : undefined,
122
+ nonInteractive: rawHasFlag('non-interactive') ? Boolean(args['non-interactive']) : undefined,
123
+ dryRun: Boolean(args['dry-run']),
124
+ format,
125
+ explain: rawHasFlag('explain') ? Boolean(args.explain) : undefined,
126
+ allowProfileInProd: rawHasFlag('allow-profile-in-prod') ? Boolean(args['allow-profile-in-prod']) : undefined,
127
+ passthroughArgs: args['--'],
128
+ };
129
+ }
130
+ async function runRunPlanCommand(command, args) {
131
+ var _a, _b, _c;
132
+ try {
133
+ const planner = await Promise.resolve().then(() => __importStar(require('./run/planner.js')));
134
+ const executor = await Promise.resolve().then(() => __importStar(require('./run/executor.js')));
135
+ const positionalArgv = rawCommandPositionals(command);
136
+ const format = (args.json || args.format === 'json') ? 'json' : 'text';
137
+ const planInput = {
138
+ cwd: process.cwd(),
139
+ argv: positionalArgv,
140
+ flags: runPlanFlagsFromCliArgs(args, format),
141
+ env: process.env,
142
+ };
143
+ const plan = command === 'build'
144
+ ? planner.createBuildPlan(planInput)
145
+ : planner.createRunPlan(planInput);
146
+ if (args['dry-run']) {
147
+ if (format === 'json') {
148
+ console.info(JSON.stringify(plan, null, 2));
149
+ }
150
+ else {
151
+ console.info(formatRunPlanText(plan, Boolean(args.explain), command));
152
+ }
153
+ process.exit(0);
154
+ }
155
+ const result = await executor.executeRunPlan(plan);
156
+ if (format === 'json') {
157
+ console.info(JSON.stringify(result, null, 2));
158
+ }
159
+ else if (result.status === 'reused') {
160
+ console.info(`hst ${command}: reusing ${(_a = result.appName) !== null && _a !== void 0 ? _a : 'app'} at ${(_b = result.url) !== null && _b !== void 0 ? _b : '(unknown URL)'}`);
161
+ }
162
+ else if (result.url) {
163
+ console.info(`hst ${command}: ${(_c = result.appName) !== null && _c !== void 0 ? _c : 'app'} available at ${result.url}`);
164
+ }
165
+ }
166
+ catch (err) {
167
+ console.error(`❌ ${command} failed`);
168
+ console.error(err instanceof Error ? err.message : String(err));
169
+ process.exit(1);
170
+ }
171
+ }
172
+ const NATIVE_PLATFORM_CHOICES = ['apple', 'ios', 'apple-tv', 'android', 'android-tv', 'all'];
173
+ function nativeInputFromArgs(args) {
174
+ return {
175
+ cwd: process.cwd(),
176
+ platform: nativePlatformArg(args.platform),
177
+ app: stringArg(args.app),
178
+ displayName: stringArg(args['display-name']),
179
+ bundleId: stringArg(args['bundle-id']),
180
+ iosBundleId: stringArg(args['ios-bundle-id']),
181
+ appleTvBundleId: stringArg(args['apple-tv-bundle-id']),
182
+ androidPackage: stringArg(args['android-package']),
183
+ androidTvPackage: stringArg(args['android-tv-package']),
184
+ teamId: stringArg(args['team-id']),
185
+ versionName: stringArg(args['version-name']),
186
+ versionCode: numberOrStringArg(args['version-code']),
187
+ referenceRoot: stringArg(args['reference-root']),
188
+ replace: Boolean(args.replace),
189
+ dryRun: Boolean(args['dry-run']),
190
+ nonInteractive: Boolean(args['non-interactive']),
191
+ };
192
+ }
193
+ function nativePlatformArg(value) {
194
+ return typeof value === 'string' && value.trim()
195
+ ? value.trim()
196
+ : undefined;
197
+ }
198
+ function nativeFormatArg(args) {
199
+ return args.format === 'json' ? 'json' : 'text';
200
+ }
201
+ function stringArg(value) {
202
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
203
+ }
204
+ function numberOrStringArg(value) {
205
+ if (typeof value === 'number' && Number.isFinite(value))
206
+ return value;
207
+ return stringArg(value);
208
+ }
63
209
  // Read build date, version, and git hash embedded at build time
64
210
  let buildDate = 'unknown';
65
211
  let buildVersion = 'unknown';
@@ -86,7 +232,7 @@ catch (err) {
86
232
  // Let yargs handle all commands including env
87
233
  (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
88
234
  .version(`${buildVersion}\n git: ${buildGitHash}\n built: ${buildDate}`)
89
- .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 run Cross-platform launch orchestration\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`)
235
+ .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`)
90
236
  .option('verbose', {
91
237
  type: 'boolean',
92
238
  default: false,
@@ -237,6 +383,112 @@ catch (err) {
237
383
  console.error(`Failed to resolve app config: ${message}`);
238
384
  process.exit(1);
239
385
  }
386
+ })
387
+ .command('native:init [platform]', 'Initialize app-owned Hosanna native thin projects and config', yargs => yargs
388
+ .positional('platform', { type: 'string', choices: NATIVE_PLATFORM_CHOICES, default: 'all', describe: 'Native platform family to initialize' })
389
+ .option('app', { type: 'string', describe: 'App key/name used when inferring defaults' })
390
+ .option('display-name', { type: 'string', describe: 'Native display name' })
391
+ .option('bundle-id', { type: 'string', describe: 'Base bundle/package identifier' })
392
+ .option('ios-bundle-id', { type: 'string', describe: 'iOS bundle identifier' })
393
+ .option('apple-tv-bundle-id', { type: 'string', describe: 'tvOS bundle identifier' })
394
+ .option('android-package', { type: 'string', describe: 'Android namespace and phone package base' })
395
+ .option('android-tv-package', { type: 'string', describe: 'Android TV application id' })
396
+ .option('team-id', { type: 'string', describe: 'Apple development team id for generated xcconfig files' })
397
+ .option('version-name', { type: 'string', describe: 'Native marketing/versionName value' })
398
+ .option('version-code', { type: 'number', describe: 'Native build/versionCode value' })
399
+ .option('reference-root', { type: 'string', default: 'hosanna-ui', describe: 'Installed framework/runtime root inside the app repo' })
400
+ .option('replace', { type: 'boolean', default: false, describe: 'Replace unchanged hst-managed native template files' })
401
+ .option('dry-run', { type: 'boolean', default: false, describe: 'Print the native init plan without writing files' })
402
+ .option('format', { type: 'string', choices: ['text', 'json'], default: 'text', describe: 'Output format' })
403
+ .option('non-interactive', { type: 'boolean', default: false, describe: 'Fail instead of prompting when a decision is needed' })
404
+ .example('$0 native:init all --display-name "Hope Stream"', 'Initialize Apple and Android app-owned native projects')
405
+ .example('$0 native:init android-tv --dry-run --format json', 'Preview Android TV native setup as JSON'), async (args) => {
406
+ try {
407
+ const mod = await Promise.resolve().then(() => __importStar(require('./native/native-project.js')));
408
+ const input = nativeInputFromArgs(args);
409
+ const format = nativeFormatArg(args);
410
+ const plan = mod.createNativeInitPlan(input);
411
+ if (input.dryRun) {
412
+ console.info(format === 'json' ? JSON.stringify(plan, null, 2) : mod.formatNativeInitPlanText(plan));
413
+ process.exit(plan.errors.length > 0 ? 1 : 0);
414
+ }
415
+ const result = await mod.executeNativeInitPlan(plan);
416
+ if (format === 'json') {
417
+ console.info(JSON.stringify(result, null, 2));
418
+ }
419
+ else {
420
+ console.info(`hst native:init applied for ${plan.requestedPlatforms.join(', ')}`);
421
+ console.info(mod.formatNativeInitPlanText(plan));
422
+ }
423
+ process.exit(0);
424
+ }
425
+ catch (err) {
426
+ console.error('❌ native:init failed');
427
+ console.error(err instanceof Error ? err.message : String(err));
428
+ process.exit(1);
429
+ }
430
+ })
431
+ .command('native:doctor [platform]', 'Validate Hosanna native platform setup and print actionable fixes', yargs => yargs
432
+ .positional('platform', { type: 'string', choices: NATIVE_PLATFORM_CHOICES, default: 'all', describe: 'Native platform family to validate' })
433
+ .option('app', { type: 'string', describe: 'App key/name used when inferring defaults' })
434
+ .option('display-name', { type: 'string', describe: 'Native display name' })
435
+ .option('bundle-id', { type: 'string', describe: 'Base bundle/package identifier' })
436
+ .option('ios-bundle-id', { type: 'string', describe: 'iOS bundle identifier' })
437
+ .option('apple-tv-bundle-id', { type: 'string', describe: 'tvOS bundle identifier' })
438
+ .option('android-package', { type: 'string', describe: 'Android namespace and phone package base' })
439
+ .option('android-tv-package', { type: 'string', describe: 'Android TV application id' })
440
+ .option('team-id', { type: 'string', describe: 'Apple development team id for generated xcconfig files' })
441
+ .option('version-name', { type: 'string', describe: 'Native marketing/versionName value' })
442
+ .option('version-code', { type: 'number', describe: 'Native build/versionCode value' })
443
+ .option('reference-root', { type: 'string', default: 'hosanna-ui', describe: 'Installed framework/runtime root inside the app repo' })
444
+ .option('replace', { type: 'boolean', default: false, describe: 'Plan replacement checks for unchanged hst-managed native files' })
445
+ .option('dry-run', { type: 'boolean', default: false, describe: 'Accepted for script parity; doctor never writes files' })
446
+ .option('format', { type: 'string', choices: ['text', 'json'], default: 'text', describe: 'Output format' })
447
+ .option('non-interactive', { type: 'boolean', default: false, describe: 'Fail instead of prompting when a decision is needed' })
448
+ .example('$0 native:doctor all --format json', 'Validate native setup as JSON'), async (args) => {
449
+ try {
450
+ const mod = await Promise.resolve().then(() => __importStar(require('./native/native-project.js')));
451
+ const report = mod.createNativeDoctorReport(nativeInputFromArgs(args));
452
+ const format = nativeFormatArg(args);
453
+ console.info(format === 'json' ? JSON.stringify(report, null, 2) : mod.formatNativeDoctorReportText(report));
454
+ process.exit(0);
455
+ }
456
+ catch (err) {
457
+ console.error('❌ native:doctor failed');
458
+ console.error(err instanceof Error ? err.message : String(err));
459
+ process.exit(1);
460
+ }
461
+ })
462
+ .command('native:prepare [platform]', 'Run lightweight Hosanna native prebuild validation', yargs => yargs
463
+ .positional('platform', { type: 'string', choices: NATIVE_PLATFORM_CHOICES, default: 'all', describe: 'Native platform family to prepare' })
464
+ .option('app', { type: 'string', describe: 'App key/name used when inferring defaults' })
465
+ .option('display-name', { type: 'string', describe: 'Native display name' })
466
+ .option('bundle-id', { type: 'string', describe: 'Base bundle/package identifier' })
467
+ .option('ios-bundle-id', { type: 'string', describe: 'iOS bundle identifier' })
468
+ .option('apple-tv-bundle-id', { type: 'string', describe: 'tvOS bundle identifier' })
469
+ .option('android-package', { type: 'string', describe: 'Android namespace and phone package base' })
470
+ .option('android-tv-package', { type: 'string', describe: 'Android TV application id' })
471
+ .option('team-id', { type: 'string', describe: 'Apple development team id for generated xcconfig files' })
472
+ .option('version-name', { type: 'string', describe: 'Native marketing/versionName value' })
473
+ .option('version-code', { type: 'number', describe: 'Native build/versionCode value' })
474
+ .option('reference-root', { type: 'string', default: 'hosanna-ui', describe: 'Installed framework/runtime root inside the app repo' })
475
+ .option('replace', { type: 'boolean', default: false, describe: 'Accepted for script parity; prepare never writes files' })
476
+ .option('dry-run', { type: 'boolean', default: false, describe: 'Accepted for script parity; prepare never writes files' })
477
+ .option('format', { type: 'string', choices: ['text', 'json'], default: 'text', describe: 'Output format' })
478
+ .option('non-interactive', { type: 'boolean', default: false, describe: 'Fail instead of prompting when a decision is needed' })
479
+ .example('$0 native:prepare ios', 'Validate native iOS setup before building'), async (args) => {
480
+ try {
481
+ const mod = await Promise.resolve().then(() => __importStar(require('./native/native-project.js')));
482
+ const result = await mod.prepareNativeProject(nativeInputFromArgs(args));
483
+ const format = nativeFormatArg(args);
484
+ console.info(format === 'json' ? JSON.stringify(result, null, 2) : mod.formatNativeDoctorReportText(result.report));
485
+ process.exit(0);
486
+ }
487
+ catch (err) {
488
+ console.error('❌ native:prepare failed');
489
+ console.error(err instanceof Error ? err.message : String(err));
490
+ process.exit(1);
491
+ }
240
492
  })
241
493
  .command('roku:package', 'Package and sign a Roku channel using roku-deploy. Flags fall back to env vars.', yargs => yargs
242
494
  .option('ip', { type: 'string', describe: 'Roku device IP (env: ROKU_IP)' })
@@ -1148,7 +1400,12 @@ catch (err) {
1148
1400
  process.exit(1);
1149
1401
  }
1150
1402
  })
1151
- .command('target:list', 'List run targets discovered by hst, including web previews, simulators, and physical devices', yargs => yargs
1403
+ .command('target:list [platform]', 'List run targets discovered by hst, including web previews, simulators, and physical devices', yargs => yargs
1404
+ .positional('platform', {
1405
+ type: 'string',
1406
+ choices: ['web', 'roku', 'ios', 'apple-tv', 'android', 'android-tv'],
1407
+ describe: 'Filter by launch platform',
1408
+ })
1152
1409
  .option('platform', {
1153
1410
  type: 'string',
1154
1411
  choices: ['web', 'roku', 'ios', 'apple-tv', 'android', 'android-tv'],
@@ -1156,7 +1413,7 @@ catch (err) {
1156
1413
  })
1157
1414
  .option('target', {
1158
1415
  type: 'string',
1159
- choices: ['web', 'device', 'simulator', 'emulator', 'simulated', 'emulated'],
1416
+ choices: ['browser', 'web', 'device', 'simulator', 'emulator', 'simulated', 'emulated'],
1160
1417
  describe: 'Filter by target kind',
1161
1418
  })
1162
1419
  .option('form-factor', {
@@ -1168,9 +1425,15 @@ catch (err) {
1168
1425
  type: 'boolean',
1169
1426
  default: false,
1170
1427
  describe: 'Print machine-readable JSON',
1428
+ })
1429
+ .option('format', {
1430
+ type: 'string',
1431
+ choices: ['text', 'json'],
1432
+ default: 'text',
1433
+ describe: 'Output format',
1171
1434
  })
1172
1435
  .example('$0 target:list --platform roku', 'List Roku web/device targets')
1173
- .example('$0 target:list --platform ios --target web --json', 'List the iOS web-runtime target as JSON')
1436
+ .example('$0 target:list --platform ios --target browser --json', 'List the iOS browser target as JSON')
1174
1437
  .example('$0 target:list --platform ios --target simulator --json', 'List iOS simulator targets as JSON')
1175
1438
  .example('$0 target:list --platform ios --target device --json', 'List physical iOS device targets as JSON')
1176
1439
  .example('$0 target:list --platform android --target emulator --json', 'List Android phone emulator targets as JSON')
@@ -1186,7 +1449,7 @@ catch (err) {
1186
1449
  formFactor: args['form-factor'],
1187
1450
  });
1188
1451
  const targets = await mod.collectRunTargets(Object.assign(Object.assign({ cwd: process.cwd() }, filters), { env: process.env }));
1189
- if (args.json) {
1452
+ if (args.json || args.format === 'json') {
1190
1453
  console.info(JSON.stringify(mod.formatRunTargetsJson(targets), null, 2));
1191
1454
  }
1192
1455
  else {
@@ -1200,27 +1463,117 @@ catch (err) {
1200
1463
  process.exit(1);
1201
1464
  }
1202
1465
  })
1203
- .command('run', 'Launch a Hosanna app on a platform target (web, Roku, iOS, Apple TV, Android, and Android TV targets are supported)', yargs => yargs
1204
- .parserConfiguration({ 'boolean-negation': false })
1466
+ .command('build [platform] [env] [target] [deviceSelector]', 'Build a Hosanna app for a platform target without launching, deploying, opening browsers, or streaming logs', yargs => yargs
1467
+ .parserConfiguration({ 'boolean-negation': false, 'populate--': true })
1468
+ .positional('platform', {
1469
+ type: 'string',
1470
+ describe: 'Build platform. Defaults from flags, env, .hosanna-tools/run.json, then web.',
1471
+ })
1472
+ .positional('env', {
1473
+ type: 'string',
1474
+ describe: 'Build environment, e.g. dev, qa, or prod.',
1475
+ })
1476
+ .positional('target', {
1477
+ type: 'string',
1478
+ describe: 'Target kind: browser, device, simulator, or emulator.',
1479
+ })
1480
+ .positional('deviceSelector', {
1481
+ type: 'string',
1482
+ describe: 'Device IP, serial/UDID, AVD name, simulator name, or case-insensitive name substring.',
1483
+ })
1205
1484
  .option('platform', {
1206
1485
  type: 'string',
1207
1486
  choices: ['web', 'roku', 'ios', 'apple-tv', 'android', 'android-tv'],
1208
- demandOption: true,
1209
- describe: 'Launch platform. Required to keep agent runs deterministic.',
1487
+ describe: 'Build platform.',
1488
+ })
1489
+ .option('env', {
1490
+ type: 'string',
1491
+ describe: 'Build environment, e.g. dev, qa, or prod.',
1210
1492
  })
1211
1493
  .option('target', {
1212
1494
  type: 'string',
1213
- choices: ['web', 'device', 'simulator', 'emulator', 'simulated', 'emulated'],
1214
- describe: 'Target kind. Defaults to web for web/roku and native simulator/emulator targets for mobile/TV platforms.',
1495
+ choices: ['browser', 'web', 'device', 'simulator', 'emulator', 'simulated', 'emulated'],
1496
+ describe: 'Target kind. Defaults by platform.',
1215
1497
  })
1216
1498
  .option('device', {
1217
1499
  type: 'string',
1218
- describe: 'Device IP, serial/UDID, case-insensitive name substring, or web preset id for --target web',
1500
+ describe: 'Device IP, serial/UDID, case-insensitive name substring, or web preset id for --target browser',
1219
1501
  })
1220
1502
  .option('appName', {
1221
1503
  type: 'string',
1222
1504
  describe: 'App name used for debugger/session identity',
1223
1505
  })
1506
+ .option('app', { type: 'string', describe: 'Run config app/flavor selector (env: HOSANNA_RUN_APP)' })
1507
+ .option('profile', { type: 'string', describe: 'Build config profile (env: HS_BUILD_PROFILE)' })
1508
+ .option('allow-profile-in-prod', { type: 'boolean', default: false, describe: 'Allow applying --profile with --env prod' })
1509
+ .option('app-config', { type: 'string', describe: 'Runtime app config selector passed to web/native launch inputs' })
1510
+ .option('hot-reload', { type: 'boolean', default: false, describe: 'Use native JS dev server where supported' })
1511
+ .option('bundled', { type: 'boolean', default: false, describe: 'Use bundled native JS/assets where supported' })
1512
+ .option('vitePort', { type: 'number', description: 'Port for Vite dev server', default: 5173 })
1513
+ .option('webAppPort', { type: 'number', description: 'Port for web app debugger client', default: 59151 })
1514
+ .option('deviceAppPort', { type: 'number', description: 'Port for device app debugger client', default: 59152 })
1515
+ .option('extensionPort', { type: 'number', description: 'Debugger extension WebSocket port', default: 59153 })
1516
+ .option('managementPort', { type: 'number', description: 'Debugger management HTTP port', default: 59150 })
1517
+ .option('non-interactive', { type: 'boolean', default: false, describe: 'Fail instead of prompting when a decision is needed' })
1518
+ .option('json', { type: 'boolean', default: false, describe: 'Print build result as JSON when the command returns' })
1519
+ .option('dry-run', { type: 'boolean', default: false, describe: 'Print the resolved build RunPlan without executing it' })
1520
+ .option('format', { type: 'string', choices: ['text', 'json'], default: 'text', describe: 'Output format for --dry-run and build result' })
1521
+ .option('explain', { type: 'boolean', default: false, describe: 'Include resolution source details in text dry-run output' })
1522
+ .option('hsc-project', { type: 'string', describe: 'Explicit Roku HSC project file for hst build planning' })
1523
+ .option('config', { type: 'string', describe: 'Alias for --hsc-project' })
1524
+ .option('source-root', { type: 'string', describe: 'Roku source root to preserve in build planning output' })
1525
+ .example('$0 build roku dev device', 'Build the dev Roku output without deploying or streaming logs')
1526
+ .example('$0 build roku prod device --hsc-project platforms/hsconfig-roku.prod.json', 'Build Roku with an explicit HSC project file')
1527
+ .example('$0 build ios dev simulator --device "iPhone 17 Pro"', 'Build an iOS simulator target without install, launch, or logs')
1528
+ .example('$0 build web dev browser', 'Resolve web runtime build config without opening a browser'), async (args) => {
1529
+ await runRunPlanCommand('build', args);
1530
+ })
1531
+ .command('run [platform] [env] [target] [deviceSelector]', 'Launch a Hosanna app on a platform target (web, Roku, iOS, Apple TV, Android, and Android TV targets are supported)', yargs => yargs
1532
+ .parserConfiguration({ 'boolean-negation': false, 'populate--': true })
1533
+ .positional('platform', {
1534
+ type: 'string',
1535
+ describe: 'Launch platform. Defaults from flags, env, .hosanna-tools/run.json, then web.',
1536
+ })
1537
+ .positional('env', {
1538
+ type: 'string',
1539
+ describe: 'Build environment, e.g. dev, qa, or prod.',
1540
+ })
1541
+ .positional('target', {
1542
+ type: 'string',
1543
+ describe: 'Target kind: browser, device, simulator, or emulator.',
1544
+ })
1545
+ .positional('deviceSelector', {
1546
+ type: 'string',
1547
+ describe: 'Device IP, serial/UDID, AVD name, simulator name, or case-insensitive name substring.',
1548
+ })
1549
+ .option('platform', {
1550
+ type: 'string',
1551
+ choices: ['web', 'roku', 'ios', 'apple-tv', 'android', 'android-tv'],
1552
+ describe: 'Launch platform.',
1553
+ })
1554
+ .option('env', {
1555
+ type: 'string',
1556
+ describe: 'Build environment, e.g. dev, qa, or prod.',
1557
+ })
1558
+ .option('target', {
1559
+ type: 'string',
1560
+ choices: ['browser', 'web', 'device', 'simulator', 'emulator', 'simulated', 'emulated'],
1561
+ describe: 'Target kind. Defaults by platform.',
1562
+ })
1563
+ .option('device', {
1564
+ type: 'string',
1565
+ describe: 'Device IP, serial/UDID, case-insensitive name substring, or web preset id for --target browser',
1566
+ })
1567
+ .option('appName', {
1568
+ type: 'string',
1569
+ describe: 'App name used for debugger/session identity',
1570
+ })
1571
+ .option('app', { type: 'string', describe: 'Run config app/flavor selector (env: HOSANNA_RUN_APP)' })
1572
+ .option('profile', { type: 'string', describe: 'Build config profile (env: HS_BUILD_PROFILE)' })
1573
+ .option('allow-profile-in-prod', { type: 'boolean', default: false, describe: 'Allow applying --profile with --env prod' })
1574
+ .option('app-config', { type: 'string', describe: 'Runtime app config selector passed to web/native launch inputs' })
1575
+ .option('hot-reload', { type: 'boolean', default: false, describe: 'Use native JS dev server where supported' })
1576
+ .option('bundled', { type: 'boolean', default: false, describe: 'Use bundled native JS/assets where supported' })
1224
1577
  .option('vitePort', { type: 'number', description: 'Port for Vite dev server', default: 5173 })
1225
1578
  .option('webAppPort', { type: 'number', description: 'Port for web app debugger client', default: 59151 })
1226
1579
  .option('deviceAppPort', { type: 'number', description: 'Port for device app debugger client', default: 59152 })
@@ -1232,74 +1585,39 @@ catch (err) {
1232
1585
  .option('no-open', { type: 'boolean', default: false, describe: 'Do not open the default browser for web runtime launches' })
1233
1586
  .option('non-interactive', { type: 'boolean', default: false, describe: 'Fail instead of prompting when a decision is needed' })
1234
1587
  .option('json', { type: 'boolean', default: false, describe: 'Print run result as JSON when the command returns' })
1588
+ .option('dry-run', { type: 'boolean', default: false, describe: 'Print the resolved RunPlan without executing it' })
1589
+ .option('format', { type: 'string', choices: ['text', 'json'], default: 'text', describe: 'Output format for --dry-run and run result' })
1590
+ .option('explain', { type: 'boolean', default: false, describe: 'Include resolution source details in text dry-run output' })
1235
1591
  .option('no-build', { type: 'boolean', default: false, describe: 'Skip the platform build step for device runs' })
1592
+ .option('build-only', { type: 'boolean', default: false, describe: 'Plan or run build steps without opening browser/log streams where supported' })
1236
1593
  .option('no-logs', { type: 'boolean', default: false, describe: 'Do not stream device logs after launch where supported' })
1237
1594
  .option('build-cmd', { type: 'string', describe: 'Override the platform build command for device runs' })
1595
+ .option('hsc-project', { type: 'string', describe: 'Explicit Roku HSC project file for hst run planning' })
1596
+ .option('config', { type: 'string', describe: 'Alias for --hsc-project' })
1238
1597
  .option('app-package', { type: 'string', describe: 'Roku app .zip or folder to deploy for device runs' })
1239
1598
  .option('source-root', { type: 'string', describe: 'Roku source root to deploy for device runs' })
1240
1599
  .option('source-map-root', { type: 'string', describe: 'Roku source map root for log resolution' })
1241
1600
  .option('files', { type: 'array', describe: 'Roku file globs when deploying a folder' })
1601
+ .option('deploy-timeout-ms', { type: 'number', describe: 'Roku deploy request timeout in milliseconds' })
1242
1602
  .option('installer-password', { type: 'string', describe: 'Roku installer password (env: ROKU_DEVPASSWORD)' })
1243
1603
  .option('logs', { type: 'boolean', default: false, describe: 'Enable verbose deploy logging where supported' })
1244
1604
  .option('applySourceMapToPaths', { type: 'boolean', default: true, describe: 'Resolve Roku stack trace paths with source maps during log streaming' })
1245
1605
  .example('$0 run --platform web', 'Start/reuse web dev services for this app')
1246
- .example('$0 run --platform roku --target web', 'Start/reuse the Roku TV web preview')
1247
- .example('$0 run --platform ios --target web', 'Start/reuse the iPhone-shaped web runtime profile')
1248
- .example('$0 run --platform android --target web --device pixel-8', 'Start/reuse the Android-shaped web runtime profile')
1249
- .example('$0 run --platform apple-tv --target web --no-open', 'Start/reuse the Apple TV web runtime profile without opening a browser')
1250
- .example('$0 run --platform android-tv --target web', 'Start/reuse the Android TV web runtime profile')
1606
+ .example('$0 run --platform roku --target browser', 'Start/reuse the Roku TV browser preview')
1607
+ .example('$0 run --platform ios --target browser', 'Start/reuse the iPhone-shaped browser runtime profile')
1608
+ .example('$0 run --platform android --target browser --device pixel-8', 'Start/reuse the Android-shaped browser runtime profile')
1609
+ .example('$0 run --platform apple-tv --target browser --no-open', 'Start/reuse the Apple TV browser runtime profile without opening a browser')
1610
+ .example('$0 run --platform android-tv --target browser', 'Start/reuse the Android TV browser runtime profile')
1251
1611
  .example('$0 run --platform ios --target simulator --device "iPhone 17 Pro"', 'Build, install, and launch on an iOS simulator')
1252
1612
  .example('$0 run --platform ios --target device --device "George iPhone" --no-logs', 'Build, install, and launch on a physical iPhone')
1253
1613
  .example('$0 run --platform android --target emulator --device "Pixel_8" --no-logs', 'Build, install, and launch on an Android phone emulator')
1254
1614
  .example('$0 run --platform android --target device --device "RF8M62694QT" --no-logs', 'Build, install, and launch on a physical Android device')
1255
1615
  .example('$0 run --platform android-tv --target emulator --device "Television_1080p" --no-logs', 'Build, install, and launch on an Android TV emulator')
1256
1616
  .example('$0 run --platform apple-tv --target simulator --device "Apple TV 4K"', 'Build, install, and launch on an Apple TV simulator')
1257
- .example('$0 run --platform roku --target device --device "living room"', 'Build, deploy, launch, and stream logs on a Roku device'), async (args) => {
1258
- var _a, _b, _c;
1259
- try {
1260
- const mod = await Promise.resolve().then(() => __importStar(require('./run/run-controller.js')));
1261
- const result = await mod.runHosanna({
1262
- cwd: process.cwd(),
1263
- platform: args.platform,
1264
- target: args.target,
1265
- device: args.device,
1266
- appName: args.appName,
1267
- vitePort: args.vitePort,
1268
- webAppPort: args.webAppPort,
1269
- deviceAppPort: args.deviceAppPort,
1270
- extensionPort: args.extensionPort,
1271
- managementPort: args.managementPort,
1272
- reuse: Boolean(args.reuse),
1273
- replace: Boolean(args.replace),
1274
- open: args['no-open'] ? false : args.open === true ? true : undefined,
1275
- nonInteractive: Boolean(args['non-interactive']),
1276
- noBuild: Boolean(args['no-build']),
1277
- noLogs: Boolean(args['no-logs']),
1278
- buildCmd: args['build-cmd'],
1279
- appPackage: args['app-package'],
1280
- sourceRoot: args['source-root'],
1281
- sourceMapRoot: args['source-map-root'],
1282
- files: args.files,
1283
- installerPassword: args['installer-password'],
1284
- logs: Boolean(args.logs),
1285
- applySourceMapToPaths: Boolean(args.applySourceMapToPaths),
1286
- env: process.env,
1287
- });
1288
- if (args.json) {
1289
- console.info(JSON.stringify(result, null, 2));
1290
- }
1291
- else if (result.status === 'reused') {
1292
- console.info(`hst run: reusing ${(_a = result.appName) !== null && _a !== void 0 ? _a : 'app'} at ${(_b = result.url) !== null && _b !== void 0 ? _b : '(unknown URL)'}`);
1293
- }
1294
- else if (result.url) {
1295
- console.info(`hst run: ${(_c = result.appName) !== null && _c !== void 0 ? _c : 'app'} available at ${result.url}`);
1296
- }
1297
- }
1298
- catch (err) {
1299
- console.error('❌ run failed');
1300
- console.error(err instanceof Error ? err.message : String(err));
1301
- process.exit(1);
1302
- }
1617
+ .example('$0 run --platform roku --target device --device "living room"', 'Build, deploy, launch, and stream logs on a Roku device')
1618
+ .example('$0 run roku dev device --app criterion', 'Run a configured monorepo app/flavor')
1619
+ .example('$0 run roku prod device --hsc-project platforms/hsconfig.criterion.prod.json', 'Override the Roku HSC project file'), async (args) => {
1620
+ await runRunPlanCommand('run', args);
1303
1621
  })
1304
1622
  .command('device:run', 'Build, deploy, launch, and stream logs for a device', yargs => yargs
1305
1623
  .parserConfiguration({ 'boolean-negation': false })
@@ -1409,6 +1727,16 @@ catch (err) {
1409
1727
  type: 'boolean',
1410
1728
  default: false,
1411
1729
  describe: 'Connect to telnet and pipe Roku device log output to terminal. In log mode, press r to redeploy (use --log-output, not --log-output true)'
1730
+ })
1731
+ .option('deploy-timeout-ms', {
1732
+ type: 'number',
1733
+ default: 30000,
1734
+ describe: 'Roku deploy request timeout in milliseconds'
1735
+ })
1736
+ .option('log-connect-timeout-ms', {
1737
+ type: 'number',
1738
+ default: 10000,
1739
+ describe: 'Roku telnet log connection timeout in milliseconds'
1412
1740
  })
1413
1741
  .option('applySourceMapToPaths', {
1414
1742
  type: 'boolean',
@@ -1450,6 +1778,8 @@ catch (err) {
1450
1778
  logs: Boolean(args.logs),
1451
1779
  logOutput: Boolean(args['log-output']),
1452
1780
  applySourceMapToPaths: Boolean(args['applySourceMapToPaths']),
1781
+ deployTimeoutMs: args['deploy-timeout-ms'],
1782
+ logConnectTimeoutMs: args['log-connect-timeout-ms'],
1453
1783
  dotenv: true,
1454
1784
  });
1455
1785
  process.exit(0);