@happier-dev/stack 0.1.0-preview.74.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (439) hide show
  1. package/README.md +501 -0
  2. package/bin/hstack.mjs +348 -0
  3. package/docs/codex-mcp-resume.md +129 -0
  4. package/docs/edison.md +74 -0
  5. package/docs/forking-and-branding.md +189 -0
  6. package/docs/happy-development.md +22 -0
  7. package/docs/isolated-linux-vm.md +243 -0
  8. package/docs/menubar.md +244 -0
  9. package/docs/mobile-ios.md +322 -0
  10. package/docs/monorepo-migration.md +20 -0
  11. package/docs/paths-and-env.md +154 -0
  12. package/docs/remote-access.md +43 -0
  13. package/docs/server-flavors.md +147 -0
  14. package/docs/stacks.md +330 -0
  15. package/docs/tauri.md +60 -0
  16. package/docs/worktrees-and-forks.md +133 -0
  17. package/extras/swiftbar/auth-login.sh +29 -0
  18. package/extras/swiftbar/git-cache-refresh.sh +122 -0
  19. package/extras/swiftbar/hstack-term.sh +133 -0
  20. package/extras/swiftbar/hstack.5s.sh +296 -0
  21. package/extras/swiftbar/hstack.sh +35 -0
  22. package/extras/swiftbar/icons/happy-green.png +0 -0
  23. package/extras/swiftbar/icons/happy-orange.png +0 -0
  24. package/extras/swiftbar/icons/happy-red.png +0 -0
  25. package/extras/swiftbar/icons/logo-white.png +0 -0
  26. package/extras/swiftbar/install.sh +265 -0
  27. package/extras/swiftbar/lib/git.sh +629 -0
  28. package/extras/swiftbar/lib/icons.sh +92 -0
  29. package/extras/swiftbar/lib/render.sh +999 -0
  30. package/extras/swiftbar/lib/system.sh +244 -0
  31. package/extras/swiftbar/lib/utils.sh +717 -0
  32. package/extras/swiftbar/set-interval.sh +65 -0
  33. package/extras/swiftbar/set-server-flavor.sh +61 -0
  34. package/extras/swiftbar/wt-pr.sh +140 -0
  35. package/node_modules/@happier-dev/cli-common/README.md +6 -0
  36. package/node_modules/@happier-dev/cli-common/dist/index.d.ts +4 -0
  37. package/node_modules/@happier-dev/cli-common/dist/index.d.ts.map +1 -0
  38. package/node_modules/@happier-dev/cli-common/dist/index.js +4 -0
  39. package/node_modules/@happier-dev/cli-common/dist/index.js.map +1 -0
  40. package/node_modules/@happier-dev/cli-common/dist/links/index.d.ts +18 -0
  41. package/node_modules/@happier-dev/cli-common/dist/links/index.d.ts.map +1 -0
  42. package/node_modules/@happier-dev/cli-common/dist/links/index.js +25 -0
  43. package/node_modules/@happier-dev/cli-common/dist/links/index.js.map +1 -0
  44. package/node_modules/@happier-dev/cli-common/dist/links.d.ts +2 -0
  45. package/node_modules/@happier-dev/cli-common/dist/links.d.ts.map +1 -0
  46. package/node_modules/@happier-dev/cli-common/dist/links.js +2 -0
  47. package/node_modules/@happier-dev/cli-common/dist/links.js.map +1 -0
  48. package/node_modules/@happier-dev/cli-common/dist/update/index.d.ts +67 -0
  49. package/node_modules/@happier-dev/cli-common/dist/update/index.d.ts.map +1 -0
  50. package/node_modules/@happier-dev/cli-common/dist/update/index.js +259 -0
  51. package/node_modules/@happier-dev/cli-common/dist/update/index.js.map +1 -0
  52. package/node_modules/@happier-dev/cli-common/dist/workspaces/index.d.ts +17 -0
  53. package/node_modules/@happier-dev/cli-common/dist/workspaces/index.d.ts.map +1 -0
  54. package/node_modules/@happier-dev/cli-common/dist/workspaces/index.js +80 -0
  55. package/node_modules/@happier-dev/cli-common/dist/workspaces/index.js.map +1 -0
  56. package/node_modules/@happier-dev/cli-common/package.json +26 -0
  57. package/package.json +77 -0
  58. package/scripts/auth.mjs +1829 -0
  59. package/scripts/auth_copy_from_pglite_lock_in_use.integration.test.mjs +90 -0
  60. package/scripts/auth_copy_from_runCapture.integration.test.mjs +447 -0
  61. package/scripts/auth_help_cmd.test.mjs +28 -0
  62. package/scripts/auth_login_flow_in_tty.test.mjs +100 -0
  63. package/scripts/auth_login_force_default.test.mjs +66 -0
  64. package/scripts/auth_login_guided_server_no_expo.test.mjs +126 -0
  65. package/scripts/auth_login_method_override.test.mjs +67 -0
  66. package/scripts/auth_login_print_includes_configure_links.test.mjs +99 -0
  67. package/scripts/auth_status_server_validation.integration.test.mjs +140 -0
  68. package/scripts/build.mjs +266 -0
  69. package/scripts/bundleWorkspaceDeps.mjs +38 -0
  70. package/scripts/bundleWorkspaceDeps.test.mjs +77 -0
  71. package/scripts/ci.mjs +135 -0
  72. package/scripts/ci.test.mjs +50 -0
  73. package/scripts/cli-link.mjs +57 -0
  74. package/scripts/completion.mjs +395 -0
  75. package/scripts/contrib.mjs +333 -0
  76. package/scripts/daemon.mjs +1160 -0
  77. package/scripts/daemon.status_scope.test.mjs +51 -0
  78. package/scripts/daemon_cmd.mjs +26 -0
  79. package/scripts/daemon_dist_guard.test.mjs +171 -0
  80. package/scripts/daemon_invalid_auth_reseed_stack_name.integration.test.mjs +608 -0
  81. package/scripts/daemon_server_scoped_state.test.mjs +49 -0
  82. package/scripts/daemon_start_verification.integration.test.mjs +296 -0
  83. package/scripts/dev.mjs +545 -0
  84. package/scripts/doctor.mjs +340 -0
  85. package/scripts/doctor_cmd.test.mjs +22 -0
  86. package/scripts/doctor_ui_index_missing.test.mjs +37 -0
  87. package/scripts/eas.mjs +367 -0
  88. package/scripts/eas_platform_parsing.test.mjs +63 -0
  89. package/scripts/edison.mjs +1848 -0
  90. package/scripts/env.mjs +149 -0
  91. package/scripts/env_cmd.test.mjs +118 -0
  92. package/scripts/exit_cleanup_kills_detached_children_on_crash.integration.test.mjs +80 -0
  93. package/scripts/happier.mjs +82 -0
  94. package/scripts/import.mjs +1327 -0
  95. package/scripts/init.mjs +464 -0
  96. package/scripts/install.mjs +550 -0
  97. package/scripts/lint.mjs +177 -0
  98. package/scripts/menubar.mjs +202 -0
  99. package/scripts/migrate.mjs +318 -0
  100. package/scripts/mobile.mjs +353 -0
  101. package/scripts/mobile_dev_client.mjs +87 -0
  102. package/scripts/monorepo.mjs +2234 -0
  103. package/scripts/monorepo_port.apply.integration.test.mjs +680 -0
  104. package/scripts/monorepo_port.conflicts.integration.test.mjs +454 -0
  105. package/scripts/monorepo_port.validation.integration.test.mjs +486 -0
  106. package/scripts/orchestrated_stack_auth_flow.test.mjs +134 -0
  107. package/scripts/orchestrated_stack_auth_flow_resolve_port.test.mjs +98 -0
  108. package/scripts/orchestrated_stack_auth_flow_webapp_url.test.mjs +119 -0
  109. package/scripts/pack.mjs +257 -0
  110. package/scripts/pack.test.mjs +68 -0
  111. package/scripts/pglite_lock.integration.test.mjs +152 -0
  112. package/scripts/provision/linux-ubuntu-e2e.sh +132 -0
  113. package/scripts/provision/linux-ubuntu-review-pr.sh +66 -0
  114. package/scripts/provision/macos-lima-happy-vm.sh +192 -0
  115. package/scripts/provision/macos-lima-hstack-e2e.sh +100 -0
  116. package/scripts/release.mjs +53 -0
  117. package/scripts/release_binary_smoke.integration.test.mjs +138 -0
  118. package/scripts/review.mjs +1752 -0
  119. package/scripts/review_pr.mjs +435 -0
  120. package/scripts/run.mjs +561 -0
  121. package/scripts/run_script_with_stack_env.restart_port_reuse.test.mjs +30 -0
  122. package/scripts/self.mjs +465 -0
  123. package/scripts/self_host.mjs +9 -0
  124. package/scripts/self_host_binary_smoke.integration.test.mjs +74 -0
  125. package/scripts/self_host_runtime.mjs +883 -0
  126. package/scripts/self_host_runtime.test.mjs +82 -0
  127. package/scripts/self_host_systemd.real.integration.test.mjs +367 -0
  128. package/scripts/server_flavor.mjs +148 -0
  129. package/scripts/service.mjs +868 -0
  130. package/scripts/service_mode_help.test.mjs +27 -0
  131. package/scripts/setup.mjs +1324 -0
  132. package/scripts/setup_non_interactive_flag.test.mjs +60 -0
  133. package/scripts/setup_pr.mjs +605 -0
  134. package/scripts/setup_pr_orchestrated_auth_flow_util_import.test.mjs +117 -0
  135. package/scripts/stack/command_arguments.mjs +91 -0
  136. package/scripts/stack/copy_auth_from_stack.mjs +111 -0
  137. package/scripts/stack/delegated_script_commands.mjs +92 -0
  138. package/scripts/stack/help_text.mjs +110 -0
  139. package/scripts/stack/port_reservation.mjs +74 -0
  140. package/scripts/stack/repo_checkout_resolution.mjs +31 -0
  141. package/scripts/stack/run_script_with_stack_env.mjs +634 -0
  142. package/scripts/stack/stack_daemon_command.mjs +219 -0
  143. package/scripts/stack/stack_delegated_help.mjs +81 -0
  144. package/scripts/stack/stack_environment.mjs +151 -0
  145. package/scripts/stack/stack_environment.sanitization.test.mjs +75 -0
  146. package/scripts/stack/stack_happier_passthrough_command.mjs +63 -0
  147. package/scripts/stack/stack_info_snapshot.mjs +167 -0
  148. package/scripts/stack/stack_mobile_install_command.mjs +61 -0
  149. package/scripts/stack/stack_resume_command.mjs +76 -0
  150. package/scripts/stack/stack_stop_command.mjs +34 -0
  151. package/scripts/stack/stack_workspace_command.mjs +83 -0
  152. package/scripts/stack/transient_repo_overrides.mjs +29 -0
  153. package/scripts/stack.mjs +2388 -0
  154. package/scripts/stack_archive_cmd.integration.test.mjs +31 -0
  155. package/scripts/stack_audit_fix_light_env.test.mjs +129 -0
  156. package/scripts/stack_background_pinned_stack_json.test.mjs +81 -0
  157. package/scripts/stack_copy_auth_server_scoped.test.mjs +243 -0
  158. package/scripts/stack_daemon_cmd.integration.test.mjs +484 -0
  159. package/scripts/stack_eas_help.test.mjs +72 -0
  160. package/scripts/stack_editor_workspace_monorepo_root.test.mjs +102 -0
  161. package/scripts/stack_env_cmd.test.mjs +107 -0
  162. package/scripts/stack_guided_login_bundle_error_parse.test.mjs +20 -0
  163. package/scripts/stack_guided_login_inner_invocation.test.mjs +46 -0
  164. package/scripts/stack_happy_cmd.integration.test.mjs +263 -0
  165. package/scripts/stack_info_snapshot_running_status.test.mjs +186 -0
  166. package/scripts/stack_interactive_monorepo_group.test.mjs +128 -0
  167. package/scripts/stack_monorepo_defaults.test.mjs +31 -0
  168. package/scripts/stack_monorepo_repo_dev_token.test.mjs +32 -0
  169. package/scripts/stack_monorepo_server_light_from_happy_spec.test.mjs +37 -0
  170. package/scripts/stack_new_name_normalize_cmd.test.mjs +38 -0
  171. package/scripts/stack_pr_name_normalize_cmd.test.mjs +84 -0
  172. package/scripts/stack_resume_cmd.integration.test.mjs +134 -0
  173. package/scripts/stack_server_flavors_defaults.test.mjs +64 -0
  174. package/scripts/stack_shorthand_cmd.integration.test.mjs +74 -0
  175. package/scripts/stack_stop_sweeps_legacy_infra_without_kind.integration.test.mjs +44 -0
  176. package/scripts/stack_stop_sweeps_when_runtime_missing.integration.test.mjs +42 -0
  177. package/scripts/stack_stop_sweeps_when_runtime_stale.integration.test.mjs +50 -0
  178. package/scripts/stack_wt_list.test.mjs +117 -0
  179. package/scripts/start_ui_required_default.test.mjs +63 -0
  180. package/scripts/stop.mjs +190 -0
  181. package/scripts/stopStackWithEnv_no_autosweep_when_runtime_missing.integration.test.mjs +95 -0
  182. package/scripts/swiftbar_git_monorepo_cmd.test.mjs +75 -0
  183. package/scripts/swiftbar_render_monorepo_wt_actions.integration.test.mjs +116 -0
  184. package/scripts/swiftbar_utils_cmd.test.mjs +92 -0
  185. package/scripts/swiftbar_wt_pr_backcompat.test.mjs +162 -0
  186. package/scripts/systemd_unit_info.test.mjs +24 -0
  187. package/scripts/tailscale.mjs +490 -0
  188. package/scripts/test_ci.mjs +36 -0
  189. package/scripts/test_cmd.mjs +274 -0
  190. package/scripts/test_cmd.test.mjs +133 -0
  191. package/scripts/test_integration.mjs +33 -0
  192. package/scripts/testkit/auth_testkit.mjs +121 -0
  193. package/scripts/testkit/doctor_testkit.mjs +68 -0
  194. package/scripts/testkit/monorepo_port_testkit.mjs +157 -0
  195. package/scripts/testkit/stack_archive_command_testkit.mjs +55 -0
  196. package/scripts/testkit/stack_new_monorepo_testkit.mjs +83 -0
  197. package/scripts/testkit/stack_script_command_testkit.mjs +27 -0
  198. package/scripts/testkit/stack_stop_sweeps_testkit.mjs +172 -0
  199. package/scripts/testkit/worktrees_monorepo_testkit.mjs +53 -0
  200. package/scripts/tools.mjs +70 -0
  201. package/scripts/tui.mjs +914 -0
  202. package/scripts/tui_stopStackForTuiExit_no_autosweep.integration.test.mjs +95 -0
  203. package/scripts/typecheck.mjs +178 -0
  204. package/scripts/ui_gateway.mjs +247 -0
  205. package/scripts/uninstall.mjs +179 -0
  206. package/scripts/utils/auth/credentials_paths.mjs +181 -0
  207. package/scripts/utils/auth/credentials_paths.test.mjs +187 -0
  208. package/scripts/utils/auth/daemon_gate.mjs +66 -0
  209. package/scripts/utils/auth/daemon_gate.test.mjs +116 -0
  210. package/scripts/utils/auth/decode_jwt_payload_unsafe.mjs +16 -0
  211. package/scripts/utils/auth/dev_key.mjs +163 -0
  212. package/scripts/utils/auth/files.mjs +56 -0
  213. package/scripts/utils/auth/guided_pr_auth.mjs +86 -0
  214. package/scripts/utils/auth/guided_stack_web_login.mjs +56 -0
  215. package/scripts/utils/auth/handy_master_secret.mjs +42 -0
  216. package/scripts/utils/auth/interactive_stack_auth.mjs +70 -0
  217. package/scripts/utils/auth/login_ux.mjs +105 -0
  218. package/scripts/utils/auth/orchestrated_stack_auth_flow.mjs +291 -0
  219. package/scripts/utils/auth/sources.mjs +28 -0
  220. package/scripts/utils/auth/stable_scope_id.mjs +91 -0
  221. package/scripts/utils/auth/stable_scope_id.test.mjs +51 -0
  222. package/scripts/utils/auth/stack_guided_login.mjs +438 -0
  223. package/scripts/utils/cli/arg_values.mjs +23 -0
  224. package/scripts/utils/cli/arg_values.test.mjs +43 -0
  225. package/scripts/utils/cli/args.mjs +17 -0
  226. package/scripts/utils/cli/cli.mjs +24 -0
  227. package/scripts/utils/cli/cli_registry.mjs +440 -0
  228. package/scripts/utils/cli/cwd_scope.mjs +158 -0
  229. package/scripts/utils/cli/cwd_scope.test.mjs +154 -0
  230. package/scripts/utils/cli/flags.mjs +17 -0
  231. package/scripts/utils/cli/log_forwarder.mjs +157 -0
  232. package/scripts/utils/cli/normalize.mjs +16 -0
  233. package/scripts/utils/cli/prereqs.mjs +103 -0
  234. package/scripts/utils/cli/prereqs.test.mjs +33 -0
  235. package/scripts/utils/cli/progress.mjs +141 -0
  236. package/scripts/utils/cli/smoke_help.mjs +44 -0
  237. package/scripts/utils/cli/verbosity.mjs +11 -0
  238. package/scripts/utils/cli/wizard.mjs +139 -0
  239. package/scripts/utils/cli/wizard_promptSelect.test.mjs +44 -0
  240. package/scripts/utils/cli/wizard_prompt_worktree_source_lazy.test.mjs +132 -0
  241. package/scripts/utils/cli/wizard_worktree_slug.test.mjs +33 -0
  242. package/scripts/utils/crypto/tokens.mjs +14 -0
  243. package/scripts/utils/dev/daemon.mjs +232 -0
  244. package/scripts/utils/dev/daemon_watch_resilience.test.mjs +224 -0
  245. package/scripts/utils/dev/expo_dev.buildEnv.test.mjs +35 -0
  246. package/scripts/utils/dev/expo_dev.mjs +478 -0
  247. package/scripts/utils/dev/expo_dev.test.mjs +89 -0
  248. package/scripts/utils/dev/expo_dev_restart_port_reservation.test.mjs +120 -0
  249. package/scripts/utils/dev/expo_dev_verbose_logs.test.mjs +60 -0
  250. package/scripts/utils/dev/server.mjs +180 -0
  251. package/scripts/utils/dev_auth_key.mjs +7 -0
  252. package/scripts/utils/edison/git_roots.mjs +30 -0
  253. package/scripts/utils/edison/git_roots.test.mjs +49 -0
  254. package/scripts/utils/env/config.mjs +52 -0
  255. package/scripts/utils/env/dotenv.mjs +32 -0
  256. package/scripts/utils/env/dotenv.test.mjs +32 -0
  257. package/scripts/utils/env/env.mjs +130 -0
  258. package/scripts/utils/env/env_file.mjs +98 -0
  259. package/scripts/utils/env/env_file.test.mjs +49 -0
  260. package/scripts/utils/env/env_local.mjs +25 -0
  261. package/scripts/utils/env/load_env_file.mjs +34 -0
  262. package/scripts/utils/env/read.mjs +30 -0
  263. package/scripts/utils/env/sandbox.mjs +13 -0
  264. package/scripts/utils/env/scrub_env.mjs +69 -0
  265. package/scripts/utils/env/scrub_env.test.mjs +102 -0
  266. package/scripts/utils/env/values.mjs +13 -0
  267. package/scripts/utils/expo/command.mjs +65 -0
  268. package/scripts/utils/expo/expo.mjs +139 -0
  269. package/scripts/utils/expo/expo_state_running.test.mjs +48 -0
  270. package/scripts/utils/expo/metro_ports.mjs +101 -0
  271. package/scripts/utils/expo/metro_ports.test.mjs +35 -0
  272. package/scripts/utils/fs/atomic_dir_swap.mjs +55 -0
  273. package/scripts/utils/fs/atomic_dir_swap.test.mjs +54 -0
  274. package/scripts/utils/fs/file_has_content.mjs +10 -0
  275. package/scripts/utils/fs/fs.mjs +11 -0
  276. package/scripts/utils/fs/json.mjs +25 -0
  277. package/scripts/utils/fs/ops.mjs +29 -0
  278. package/scripts/utils/fs/package_json.mjs +8 -0
  279. package/scripts/utils/fs/tail.mjs +12 -0
  280. package/scripts/utils/git/dev_checkout.mjs +127 -0
  281. package/scripts/utils/git/dev_checkout.test.mjs +115 -0
  282. package/scripts/utils/git/git.mjs +67 -0
  283. package/scripts/utils/git/parse_name_status_z.mjs +21 -0
  284. package/scripts/utils/git/refs.mjs +26 -0
  285. package/scripts/utils/git/worktrees.mjs +323 -0
  286. package/scripts/utils/git/worktrees_monorepo.test.mjs +60 -0
  287. package/scripts/utils/git/worktrees_pathstyle.test.mjs +53 -0
  288. package/scripts/utils/llm/assist.mjs +260 -0
  289. package/scripts/utils/llm/codex_exec.mjs +61 -0
  290. package/scripts/utils/llm/codex_exec.test.mjs +46 -0
  291. package/scripts/utils/llm/hstack_runner.mjs +59 -0
  292. package/scripts/utils/llm/tools.mjs +56 -0
  293. package/scripts/utils/llm/tools.test.mjs +67 -0
  294. package/scripts/utils/menubar/swiftbar.mjs +121 -0
  295. package/scripts/utils/menubar/swiftbar.test.mjs +85 -0
  296. package/scripts/utils/mobile/config.mjs +35 -0
  297. package/scripts/utils/mobile/dev_client_links.mjs +59 -0
  298. package/scripts/utils/mobile/identifiers.mjs +46 -0
  299. package/scripts/utils/mobile/identifiers.test.mjs +41 -0
  300. package/scripts/utils/mobile/ios_xcodeproj_patch.mjs +128 -0
  301. package/scripts/utils/mobile/ios_xcodeproj_patch.test.mjs +131 -0
  302. package/scripts/utils/net/bind_mode.mjs +39 -0
  303. package/scripts/utils/net/dns.mjs +10 -0
  304. package/scripts/utils/net/lan_ip.mjs +24 -0
  305. package/scripts/utils/net/ports.mjs +110 -0
  306. package/scripts/utils/net/tcp_forward.mjs +162 -0
  307. package/scripts/utils/net/url.mjs +30 -0
  308. package/scripts/utils/net/url.test.mjs +29 -0
  309. package/scripts/utils/paths/canonical_home.mjs +15 -0
  310. package/scripts/utils/paths/canonical_home.test.mjs +28 -0
  311. package/scripts/utils/paths/localhost_host.mjs +112 -0
  312. package/scripts/utils/paths/localhost_host.test.mjs +58 -0
  313. package/scripts/utils/paths/paths.mjs +302 -0
  314. package/scripts/utils/paths/paths_env_win32.test.mjs +36 -0
  315. package/scripts/utils/paths/paths_monorepo.test.mjs +58 -0
  316. package/scripts/utils/paths/paths_server_flavors.test.mjs +50 -0
  317. package/scripts/utils/paths/runtime.mjs +41 -0
  318. package/scripts/utils/pglite_lock.mjs +107 -0
  319. package/scripts/utils/proc/commands.mjs +33 -0
  320. package/scripts/utils/proc/exit_cleanup.mjs +57 -0
  321. package/scripts/utils/proc/happy_monorepo_deps.mjs +37 -0
  322. package/scripts/utils/proc/happy_monorepo_deps.test.mjs +89 -0
  323. package/scripts/utils/proc/ownership.mjs +217 -0
  324. package/scripts/utils/proc/ownership_killProcessGroupOwnedByStack.test.mjs +216 -0
  325. package/scripts/utils/proc/ownership_listPidsWithEnvNeedles.test.mjs +88 -0
  326. package/scripts/utils/proc/package_scripts.mjs +38 -0
  327. package/scripts/utils/proc/package_scripts.test.mjs +58 -0
  328. package/scripts/utils/proc/parallel.mjs +25 -0
  329. package/scripts/utils/proc/pids.mjs +11 -0
  330. package/scripts/utils/proc/pm.mjs +478 -0
  331. package/scripts/utils/proc/pm_spawn.integration.test.mjs +131 -0
  332. package/scripts/utils/proc/pm_stack_cache_env.test.mjs +313 -0
  333. package/scripts/utils/proc/proc.mjs +331 -0
  334. package/scripts/utils/proc/proc.test.mjs +85 -0
  335. package/scripts/utils/proc/terminate.mjs +69 -0
  336. package/scripts/utils/proc/terminate.test.mjs +54 -0
  337. package/scripts/utils/proc/watch.mjs +63 -0
  338. package/scripts/utils/review/augment_runner_integration.test.mjs +105 -0
  339. package/scripts/utils/review/base_ref.mjs +82 -0
  340. package/scripts/utils/review/base_ref.test.mjs +89 -0
  341. package/scripts/utils/review/chunks.mjs +55 -0
  342. package/scripts/utils/review/chunks.test.mjs +107 -0
  343. package/scripts/utils/review/detached_worktree.mjs +61 -0
  344. package/scripts/utils/review/detached_worktree.test.mjs +61 -0
  345. package/scripts/utils/review/findings.mjs +278 -0
  346. package/scripts/utils/review/findings.test.mjs +203 -0
  347. package/scripts/utils/review/head_slice.mjs +132 -0
  348. package/scripts/utils/review/head_slice.test.mjs +117 -0
  349. package/scripts/utils/review/instructions/deep.md +20 -0
  350. package/scripts/utils/review/prompts.mjs +279 -0
  351. package/scripts/utils/review/prompts.test.mjs +77 -0
  352. package/scripts/utils/review/run_reviewers_safe.mjs +12 -0
  353. package/scripts/utils/review/run_reviewers_safe.test.mjs +45 -0
  354. package/scripts/utils/review/runners/augment.mjs +91 -0
  355. package/scripts/utils/review/runners/augment.test.mjs +64 -0
  356. package/scripts/utils/review/runners/claude.mjs +92 -0
  357. package/scripts/utils/review/runners/claude.test.mjs +47 -0
  358. package/scripts/utils/review/runners/coderabbit.mjs +105 -0
  359. package/scripts/utils/review/runners/coderabbit.test.mjs +32 -0
  360. package/scripts/utils/review/runners/codex.mjs +129 -0
  361. package/scripts/utils/review/runners/codex.test.mjs +115 -0
  362. package/scripts/utils/review/slice_mode.mjs +20 -0
  363. package/scripts/utils/review/slice_mode.test.mjs +69 -0
  364. package/scripts/utils/review/sliced_runner.mjs +39 -0
  365. package/scripts/utils/review/sliced_runner.test.mjs +57 -0
  366. package/scripts/utils/review/slices.mjs +140 -0
  367. package/scripts/utils/review/slices.test.mjs +41 -0
  368. package/scripts/utils/review/targets.mjs +23 -0
  369. package/scripts/utils/review/targets.test.mjs +31 -0
  370. package/scripts/utils/review/tool_home_seed.mjs +106 -0
  371. package/scripts/utils/review/tool_home_seed.test.mjs +124 -0
  372. package/scripts/utils/review/uncommitted_ops.mjs +77 -0
  373. package/scripts/utils/review/uncommitted_ops.test.mjs +117 -0
  374. package/scripts/utils/sandbox/review_pr_sandbox.mjs +105 -0
  375. package/scripts/utils/server/apply_server_light_env_defaults.mjs +14 -0
  376. package/scripts/utils/server/flavor_scripts.mjs +138 -0
  377. package/scripts/utils/server/flavor_scripts.test.mjs +115 -0
  378. package/scripts/utils/server/infra/happy_server_infra.mjs +444 -0
  379. package/scripts/utils/server/mobile_api_url.mjs +60 -0
  380. package/scripts/utils/server/mobile_api_url.test.mjs +58 -0
  381. package/scripts/utils/server/port.mjs +55 -0
  382. package/scripts/utils/server/prisma_import.mjs +36 -0
  383. package/scripts/utils/server/prisma_import.test.mjs +78 -0
  384. package/scripts/utils/server/server.mjs +109 -0
  385. package/scripts/utils/server/ui_build_check.mjs +37 -0
  386. package/scripts/utils/server/ui_build_check.test.mjs +70 -0
  387. package/scripts/utils/server/ui_env.mjs +13 -0
  388. package/scripts/utils/server/ui_env.test.mjs +57 -0
  389. package/scripts/utils/server/urls.mjs +100 -0
  390. package/scripts/utils/server/validate.mjs +60 -0
  391. package/scripts/utils/server/validate.test.mjs +76 -0
  392. package/scripts/utils/service/autostart_darwin.mjs +198 -0
  393. package/scripts/utils/service/autostart_darwin.test.mjs +49 -0
  394. package/scripts/utils/service/autostart_darwin_keepalive.test.mjs +19 -0
  395. package/scripts/utils/stack/cli_identities.mjs +29 -0
  396. package/scripts/utils/stack/context.mjs +19 -0
  397. package/scripts/utils/stack/dirs.mjs +26 -0
  398. package/scripts/utils/stack/editor_workspace.mjs +126 -0
  399. package/scripts/utils/stack/interactive_stack_config.mjs +266 -0
  400. package/scripts/utils/stack/interactive_stack_config.port_validation.test.mjs +93 -0
  401. package/scripts/utils/stack/interactive_stack_config.remote_validation.test.mjs +122 -0
  402. package/scripts/utils/stack/interactive_stack_config.stack_name_validation.test.mjs +76 -0
  403. package/scripts/utils/stack/interactive_stack_config_testkit.mjs +18 -0
  404. package/scripts/utils/stack/names.mjs +27 -0
  405. package/scripts/utils/stack/names.test.mjs +26 -0
  406. package/scripts/utils/stack/pr_stack_name.mjs +16 -0
  407. package/scripts/utils/stack/runtime_state.mjs +88 -0
  408. package/scripts/utils/stack/stacks.mjs +40 -0
  409. package/scripts/utils/stack/startup.mjs +370 -0
  410. package/scripts/utils/stack/startup_server_light_dirs.test.mjs +119 -0
  411. package/scripts/utils/stack/startup_server_light_generate.test.mjs +20 -0
  412. package/scripts/utils/stack/startup_server_light_legacy.test.mjs +79 -0
  413. package/scripts/utils/stack/startup_server_light_testkit.mjs +106 -0
  414. package/scripts/utils/stack/stop.mjs +284 -0
  415. package/scripts/utils/stack_context.mjs +1 -0
  416. package/scripts/utils/stack_runtime_state.mjs +1 -0
  417. package/scripts/utils/stacks.mjs +1 -0
  418. package/scripts/utils/tailscale/ip.mjs +116 -0
  419. package/scripts/utils/tauri/stack_overrides.mjs +22 -0
  420. package/scripts/utils/test/collect_test_files.mjs +29 -0
  421. package/scripts/utils/time/get_today_ymd.mjs +7 -0
  422. package/scripts/utils/tui/cleanup.mjs +38 -0
  423. package/scripts/utils/ui/ansi.mjs +47 -0
  424. package/scripts/utils/ui/browser.mjs +31 -0
  425. package/scripts/utils/ui/browser.test.mjs +56 -0
  426. package/scripts/utils/ui/clipboard.mjs +38 -0
  427. package/scripts/utils/ui/layout.mjs +44 -0
  428. package/scripts/utils/ui/qr.mjs +17 -0
  429. package/scripts/utils/ui/terminal_launcher.mjs +129 -0
  430. package/scripts/utils/ui/text.mjs +16 -0
  431. package/scripts/utils/update/auto_update_notice.mjs +93 -0
  432. package/scripts/utils/validate.mjs +5 -0
  433. package/scripts/where.mjs +138 -0
  434. package/scripts/worktrees.mjs +2174 -0
  435. package/scripts/worktrees_archive_cmd.integration.test.mjs +228 -0
  436. package/scripts/worktrees_cursor_monorepo_root.test.mjs +23 -0
  437. package/scripts/worktrees_list_specs_no_recurse.test.mjs +32 -0
  438. package/scripts/worktrees_monorepo_testkit.test.mjs +29 -0
  439. package/scripts/worktrees_monorepo_use_group.test.mjs +41 -0
@@ -0,0 +1,266 @@
1
+ import './utils/env/env.mjs';
2
+ import { parseArgs } from './utils/cli/args.mjs';
3
+ import { getComponentDir, getDefaultAutostartPaths, getRootDir } from './utils/paths/paths.mjs';
4
+ import { ensureDepsInstalled, pmExecBin, requireDir } from './utils/proc/pm.mjs';
5
+ import { resolveServerPortFromEnv } from './utils/server/urls.mjs';
6
+ import { dirname, join } from 'node:path';
7
+ import { readFile, rm, mkdir, writeFile } from 'node:fs/promises';
8
+ import { tailscaleServeHttpsUrl } from './tailscale.mjs';
9
+ import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
10
+ import { ensureExpoIsolationEnv, getExpoStatePaths, wantsExpoClearCache } from './utils/expo/expo.mjs';
11
+ import { expoExec } from './utils/expo/command.mjs';
12
+ import { getInvokedCwd, inferComponentFromCwd } from './utils/cli/cwd_scope.mjs';
13
+ import { applyStackTauriOverrides } from './utils/tauri/stack_overrides.mjs';
14
+ import { buildIntoTempThenReplace } from './utils/fs/atomic_dir_swap.mjs';
15
+ import { pathExists } from './utils/fs/fs.mjs';
16
+
17
+ /**
18
+ * Build a lightweight static web UI bundle (no Expo dev server).
19
+ *
20
+ * Output directory default: ~/.happier/stacks/main/ui (legacy: ~/.happier/local/ui)
21
+ * Server will serve it at / when HAPPIER_SERVER_UI_DIR is set.
22
+ * (Legacy /ui paths are redirected to /.)
23
+ */
24
+
25
+ async function main() {
26
+ const argv = process.argv.slice(2);
27
+ const { flags } = parseArgs(argv);
28
+ const json = wantsJson(argv, { flags });
29
+ if (wantsHelp(argv, { flags })) {
30
+ printResult({
31
+ json,
32
+ data: { flags: ['--tauri', '--no-tauri', '--no-ui'], json: true },
33
+ text: [
34
+ '[build] usage:',
35
+ ' hstack build [--tauri] [--json]',
36
+ ' node scripts/build.mjs [--tauri|--no-tauri] [--no-ui] [--json]',
37
+ '',
38
+ 'note:',
39
+ ' If run from inside the Happier UI checkout/worktree, the build uses that checkout.',
40
+ ].join('\n'),
41
+ });
42
+ return;
43
+ }
44
+ const rootDir = getRootDir(import.meta.url);
45
+
46
+ // If invoked from inside the Happier UI checkout/worktree, prefer that directory without requiring `hstack wt use ...`.
47
+ const inferred = inferComponentFromCwd({
48
+ rootDir,
49
+ invokedCwd: getInvokedCwd(process.env),
50
+ components: ['happier-ui', 'happy'],
51
+ });
52
+ if (inferred?.component === 'happier-ui' || inferred?.component === 'happy') {
53
+ if (!(process.env.HAPPIER_STACK_REPO_DIR ?? '').toString().trim()) {
54
+ process.env.HAPPIER_STACK_REPO_DIR = inferred.repoDir;
55
+ }
56
+ }
57
+
58
+ // Optional: skip building the web UI bundle.
59
+ //
60
+ // This is useful for evidence capture flows that validate non-UI components (e.g. `happy-cli`)
61
+ // but still require a "build" step.
62
+ const skipUi = flags.has('--no-ui');
63
+
64
+ const serverPort = resolveServerPortFromEnv({ env: process.env, defaultPort: 3005 });
65
+
66
+ // For Tauri builds we embed an explicit API base URL (tauri:// origins cannot use window.location.origin).
67
+ const internalServerUrl = `http://127.0.0.1:${serverPort}`;
68
+
69
+ const outDir = process.env.HAPPIER_STACK_UI_BUILD_DIR?.trim()
70
+ ? process.env.HAPPIER_STACK_UI_BUILD_DIR.trim()
71
+ : join(getDefaultAutostartPaths().baseDir, 'ui');
72
+
73
+ // UI is served at root; /ui redirects to /.
74
+
75
+ if (skipUi) {
76
+ // Ensure the output dir exists so server-light doesn't crash if used, but do not run Expo export.
77
+ await rm(outDir, { recursive: true, force: true });
78
+ await mkdir(outDir, { recursive: true });
79
+ await writeFile(join(outDir, '.hstack-build-skipped'), 'no-ui\n', 'utf-8');
80
+ if (json) {
81
+ printResult({ json, data: { ok: true, outDir, skippedUi: true, tauriBuilt: false } });
82
+ } else {
83
+ console.log(`[local] skipping UI export (--no-ui); created empty UI dir at ${outDir}`);
84
+ }
85
+ return;
86
+ }
87
+
88
+ const uiDir = getComponentDir(rootDir, 'happier-ui');
89
+ await requireDir('happier-ui', uiDir);
90
+
91
+ await ensureDepsInstalled(uiDir, 'happier-ui');
92
+
93
+ console.log(`[local] exporting web UI to ${outDir}...`);
94
+
95
+ // Build for root hosting (the server redirects /ui -> /).
96
+ const env = {
97
+ ...process.env,
98
+ NODE_ENV: 'production',
99
+ EXPO_PUBLIC_DEBUG: '0',
100
+ // Leave empty for web export so the app uses window.location.origin at runtime.
101
+ // (Important for Tailscale: a phone loading `http://100.x.y.z:3005` must not call `http://localhost:3005`.)
102
+ EXPO_PUBLIC_HAPPY_SERVER_URL: '',
103
+ };
104
+
105
+ // Expo CLI is available via node_modules/.bin once dependencies are installed.
106
+ await buildIntoTempThenReplace(outDir, async (tmpOutDir) => {
107
+ // Ensure the output dir exists (Expo writes into it).
108
+ await rm(tmpOutDir, { recursive: true, force: true });
109
+ await mkdir(tmpOutDir, { recursive: true });
110
+
111
+ const paths = getExpoStatePaths({
112
+ baseDir: getDefaultAutostartPaths().baseDir,
113
+ kind: 'ui-export',
114
+ projectDir: uiDir,
115
+ stateFileName: 'ui.export.state.json',
116
+ });
117
+ await ensureExpoIsolationEnv({ env, stateDir: paths.stateDir, expoHomeDir: paths.expoHomeDir, tmpDir: paths.tmpDir });
118
+ const args = ['export', '--platform', 'web', '--output-dir', tmpOutDir, ...(wantsExpoClearCache({ env }) ? ['-c'] : [])];
119
+ await expoExec({ dir: uiDir, args, env, ensureDepsLabel: 'happier-ui' });
120
+
121
+ const indexPath = join(tmpOutDir, 'index.html');
122
+ if (!(await pathExists(indexPath))) {
123
+ throw new Error(
124
+ `[local] UI export incomplete: missing ${indexPath}\nFix: re-run hstack build (or run hstack dev for dev mode)`,
125
+ );
126
+ }
127
+ });
128
+
129
+ if (json) {
130
+ printResult({ json, data: { ok: true, outDir, tauriBuilt: false } });
131
+ } else {
132
+ console.log('[local] UI build complete');
133
+ }
134
+
135
+ //
136
+ // Tauri build (optional)
137
+ //
138
+ // Default: do NOT build Tauri (it's slow and requires extra toolchain).
139
+ // Enable explicitly with:
140
+ // - `hstack build -- --tauri`, or
141
+ // - `HAPPIER_STACK_BUILD_TAURI=1`
142
+ const envBuildTauri = (process.env.HAPPIER_STACK_BUILD_TAURI ?? '').trim();
143
+ const buildTauriFromEnv = envBuildTauri !== '' ? envBuildTauri !== '0' : false;
144
+ const buildTauri = !flags.has('--no-tauri') && (flags.has('--tauri') || buildTauriFromEnv);
145
+ if (!buildTauri) {
146
+ return;
147
+ }
148
+
149
+ // Default to debug builds for local development so devtools are available.
150
+ const tauriDebug = (process.env.HAPPIER_STACK_TAURI_DEBUG ?? '1') === '1';
151
+
152
+ // Choose the API endpoint the Tauri app should use.
153
+ //
154
+ // Priority:
155
+ // 1) HAPPIER_STACK_TAURI_SERVER_URL (explicit override)
156
+ // 2) If available, a Tailscale Serve https://*.ts.net URL (portable across machines on the same tailnet)
157
+ // 3) Fallback to internal loopback (same-machine)
158
+ const tauriServerUrlOverride = process.env.HAPPIER_STACK_TAURI_SERVER_URL?.trim()
159
+ ? process.env.HAPPIER_STACK_TAURI_SERVER_URL.trim()
160
+ : '';
161
+ const preferTailscale = (process.env.HAPPIER_STACK_TAURI_PREFER_TAILSCALE ?? '1') !== '0';
162
+ const tailscaleUrl = preferTailscale ? await tailscaleServeHttpsUrl() : null;
163
+ const tauriServerUrl = tauriServerUrlOverride || tailscaleUrl || internalServerUrl;
164
+
165
+ const tauriDistDir = process.env.HAPPIER_STACK_TAURI_UI_DIR?.trim()
166
+ ? process.env.HAPPIER_STACK_TAURI_UI_DIR.trim()
167
+ : join(uiDir, 'dist');
168
+
169
+ await rm(tauriDistDir, { recursive: true, force: true });
170
+ await mkdir(tauriDistDir, { recursive: true });
171
+
172
+ console.log(`[local] exporting web UI for Tauri to ${tauriDistDir}...`);
173
+
174
+ const tauriEnv = {
175
+ ...process.env,
176
+ NODE_ENV: 'production',
177
+ EXPO_PUBLIC_DEBUG: '0',
178
+ // In Tauri, window.location.origin is a tauri:// origin, so we must hardcode the API base.
179
+ EXPO_PUBLIC_HAPPY_SERVER_URL: tauriServerUrl,
180
+ // Some parts of the app use EXPO_PUBLIC_SERVER_URL; keep them aligned.
181
+ EXPO_PUBLIC_SERVER_URL: tauriServerUrl,
182
+ // For the Tauri bundle we want root-relative assets (no /ui baseUrl), so do not set EXPO_PUBLIC_WEB_BASE_URL
183
+ };
184
+ delete tauriEnv.EXPO_PUBLIC_WEB_BASE_URL;
185
+
186
+ {
187
+ const paths = getExpoStatePaths({
188
+ baseDir: getDefaultAutostartPaths().baseDir,
189
+ kind: 'ui-export-tauri',
190
+ projectDir: uiDir,
191
+ stateFileName: 'ui.export.tauri.state.json',
192
+ });
193
+ await ensureExpoIsolationEnv({ env: tauriEnv, stateDir: paths.stateDir, expoHomeDir: paths.expoHomeDir, tmpDir: paths.tmpDir });
194
+ }
195
+
196
+ await expoExec({
197
+ dir: uiDir,
198
+ args: [
199
+ 'export',
200
+ '--platform',
201
+ 'web',
202
+ '--output-dir',
203
+ tauriDistDir,
204
+ // Important: clear bundler cache so EXPO_PUBLIC_* inlining doesn't reuse
205
+ // the previous (web) export's transform results.
206
+ '-c',
207
+ ],
208
+ env: tauriEnv,
209
+ ensureDepsLabel: 'happy',
210
+ });
211
+
212
+ // Build the Tauri app using a generated config that skips upstream beforeBuildCommand (which uses yarn).
213
+ const tauriConfigPath = join(uiDir, 'src-tauri', 'tauri.conf.json');
214
+ const tauriConfigRaw = await readFile(tauriConfigPath, 'utf-8');
215
+ const tauriConfig = JSON.parse(tauriConfigRaw);
216
+ tauriConfig.build = tauriConfig.build ?? {};
217
+ // Prefer the upstream relative dist dir when possible (less surprising for Tauri tooling).
218
+ tauriConfig.build.frontendDist = tauriDistDir === join(uiDir, 'dist') ? '../dist' : tauriDistDir;
219
+ tauriConfig.build.beforeBuildCommand = null;
220
+ tauriConfig.build.beforeDevCommand = null;
221
+
222
+ // Build a separate "local" app so it doesn't reuse previous storage (server URL, auth, etc).
223
+ // This avoids needing any changes in the Happy source code to override a previously saved server.
224
+ applyStackTauriOverrides({ tauriConfig, env: process.env });
225
+
226
+ if (tauriDebug) {
227
+ // Enable devtools in debug builds (useful for troubleshooting connectivity).
228
+ tauriConfig.app = tauriConfig.app ?? {};
229
+ tauriConfig.app.windows = Array.isArray(tauriConfig.app.windows) ? tauriConfig.app.windows : [];
230
+ if (tauriConfig.app.windows.length > 0) {
231
+ tauriConfig.app.windows = tauriConfig.app.windows.map((w) => ({ ...w, devtools: true }));
232
+ }
233
+ }
234
+
235
+ const generatedConfigPath = join(getDefaultAutostartPaths().baseDir, 'tauri.conf.happier-stack.json');
236
+ await mkdir(dirname(generatedConfigPath), { recursive: true });
237
+ await writeFile(generatedConfigPath, JSON.stringify(tauriConfig, null, 2), 'utf-8');
238
+
239
+ console.log('[local] building Tauri app...');
240
+ const cargoTargetDir = join(getDefaultAutostartPaths().baseDir, 'tauri-target');
241
+ await mkdir(cargoTargetDir, { recursive: true });
242
+
243
+ const tauriBuildEnv = {
244
+ ...process.env,
245
+ // Fixes builds after moving the repo by isolating cargo outputs from old absolute paths.
246
+ CARGO_TARGET_DIR: cargoTargetDir,
247
+ // Newer Tauri CLI parses CI as a boolean; many environments set CI=1 which fails.
248
+ CI: 'false',
249
+ };
250
+
251
+ const tauriArgs = ['build', '--config', generatedConfigPath];
252
+ if (tauriDebug) {
253
+ tauriArgs.push('--debug');
254
+ }
255
+ await pmExecBin({ dir: uiDir, bin: 'tauri', args: tauriArgs, env: tauriBuildEnv });
256
+ if (json) {
257
+ printResult({ json, data: { ok: true, outDir, tauriBuilt: true, tauriServerUrl } });
258
+ } else {
259
+ console.log('[local] Tauri build complete');
260
+ }
261
+ }
262
+
263
+ main().catch((err) => {
264
+ console.error('[local] build failed:', err);
265
+ process.exit(1);
266
+ });
@@ -0,0 +1,38 @@
1
+ import { dirname, resolve } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+
4
+ import { bundleWorkspacePackages, findRepoRoot } from '@happier-dev/cli-common/workspaces';
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+
8
+ export function bundleWorkspaceDeps(opts = {}) {
9
+ const repoRoot = opts.repoRoot ?? findRepoRoot(__dirname);
10
+ const stackDir = opts.stackDir ?? resolve(repoRoot, 'apps', 'stack');
11
+
12
+ const bundles = [
13
+ {
14
+ packageName: '@happier-dev/cli-common',
15
+ srcDir: resolve(repoRoot, 'packages', 'cli-common'),
16
+ destDir: resolve(stackDir, 'node_modules', '@happier-dev', 'cli-common'),
17
+ },
18
+ ];
19
+
20
+ bundleWorkspacePackages({ bundles });
21
+ }
22
+
23
+ const invokedAsMain = (() => {
24
+ const argv1 = process.argv[1];
25
+ if (!argv1) return false;
26
+ return resolve(argv1) === fileURLToPath(import.meta.url);
27
+ })();
28
+
29
+ if (invokedAsMain) {
30
+ try {
31
+ bundleWorkspaceDeps();
32
+ } catch (error) {
33
+ // eslint-disable-next-line no-console
34
+ console.error(error instanceof Error ? error.message : String(error));
35
+ process.exit(1);
36
+ }
37
+ }
38
+
@@ -0,0 +1,77 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, existsSync, rmSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join, resolve } from 'node:path';
6
+
7
+ import { bundleWorkspaceDeps } from './bundleWorkspaceDeps.mjs';
8
+
9
+ function writeJson(path, value) {
10
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
11
+ }
12
+
13
+ function createBundleFixture(prefix = 'happy-stack-bundle-workspace-deps-') {
14
+ const repoRoot = mkdtempSync(join(tmpdir(), prefix));
15
+ const stackDir = resolve(repoRoot, 'apps', 'stack');
16
+ const cliCommonDir = resolve(repoRoot, 'packages', 'cli-common');
17
+ writeJson(resolve(repoRoot, 'package.json'), { name: 'repo', private: true });
18
+ writeFileSync(resolve(repoRoot, 'yarn.lock'), '# lock\n', 'utf8');
19
+ mkdirSync(resolve(cliCommonDir, 'dist'), { recursive: true });
20
+ mkdirSync(stackDir, { recursive: true });
21
+ return { repoRoot, stackDir, cliCommonDir };
22
+ }
23
+
24
+ test('bundleWorkspaceDeps copies dist + writes a sanitized package.json without install scripts', () => {
25
+ const { repoRoot, stackDir, cliCommonDir } = createBundleFixture();
26
+ try {
27
+ writeJson(resolve(cliCommonDir, 'package.json'), {
28
+ name: '@happier-dev/cli-common',
29
+ version: '0.0.0',
30
+ type: 'module',
31
+ main: './dist/index.js',
32
+ types: './dist/index.d.ts',
33
+ exports: { '.': { default: './dist/index.js', types: './dist/index.d.ts' } },
34
+ scripts: { postinstall: 'echo should-not-run' },
35
+ });
36
+ writeFileSync(resolve(cliCommonDir, 'dist', 'index.js'), 'export const z = 3;\n', 'utf8');
37
+
38
+ bundleWorkspaceDeps({ repoRoot, stackDir });
39
+
40
+ const bundledPkgJson = JSON.parse(
41
+ readFileSync(resolve(stackDir, 'node_modules', '@happier-dev', 'cli-common', 'package.json'), 'utf8'),
42
+ );
43
+ assert.equal(bundledPkgJson.scripts, undefined);
44
+ assert.equal(bundledPkgJson.name, '@happier-dev/cli-common');
45
+ assert.equal(bundledPkgJson.private, true);
46
+
47
+ const bundledDistPath = resolve(stackDir, 'node_modules', '@happier-dev', 'cli-common', 'dist', 'index.js');
48
+ assert.ok(existsSync(bundledDistPath), 'dist/index.js should be copied to bundled location');
49
+ } finally {
50
+ rmSync(repoRoot, { recursive: true, force: true });
51
+ }
52
+ });
53
+
54
+ test('bundleWorkspaceDeps throws when cli-common dist/ is missing', () => {
55
+ const { repoRoot, stackDir, cliCommonDir } = createBundleFixture('happy-stack-bundle-workspace-deps-no-dist-');
56
+ try {
57
+ rmSync(resolve(cliCommonDir, 'dist'), { recursive: true, force: true });
58
+ writeJson(resolve(cliCommonDir, 'package.json'), {
59
+ name: '@happier-dev/cli-common',
60
+ version: '0.0.0',
61
+ main: './dist/index.js',
62
+ });
63
+ assert.throws(() => bundleWorkspaceDeps({ repoRoot, stackDir }), /Missing dist\/ for @happier-dev\/cli-common/);
64
+ } finally {
65
+ rmSync(repoRoot, { recursive: true, force: true });
66
+ }
67
+ });
68
+
69
+ test('bundleWorkspaceDeps throws when cli-common package.json is malformed', () => {
70
+ const { repoRoot, stackDir, cliCommonDir } = createBundleFixture('happy-stack-bundle-workspace-deps-bad-json-');
71
+ try {
72
+ writeFileSync(resolve(cliCommonDir, 'package.json'), '{"name":"@happier-dev/cli-common"', 'utf8');
73
+ assert.throws(() => bundleWorkspaceDeps({ repoRoot, stackDir }), SyntaxError);
74
+ } finally {
75
+ rmSync(repoRoot, { recursive: true, force: true });
76
+ }
77
+ });
package/scripts/ci.mjs ADDED
@@ -0,0 +1,135 @@
1
+ import './utils/env/env.mjs';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { parseArgs } from './utils/cli/args.mjs';
6
+ import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
7
+ import { getInvokedCwd } from './utils/cli/cwd_scope.mjs';
8
+ import { run } from './utils/proc/proc.mjs';
9
+
10
+ function getRootDir() {
11
+ return dirname(dirname(fileURLToPath(import.meta.url)));
12
+ }
13
+
14
+ function findHappyMonorepoRoot(startDir) {
15
+ let dir = resolve(startDir);
16
+ for (let i = 0; i < 12; i++) {
17
+ const pkg = join(dir, 'package.json');
18
+ const lock = join(dir, 'yarn.lock');
19
+ const packagesDir = join(dir, 'packages');
20
+ if (existsSync(pkg) && existsSync(lock) && existsSync(packagesDir)) {
21
+ try {
22
+ // lightweight name check (don’t parse unless needed)
23
+ const text = readFileSync(pkg, 'utf8');
24
+ if (text.includes('"name": "monorepo"')) return dir;
25
+ } catch {
26
+ return dir;
27
+ }
28
+ }
29
+ const parent = dirname(dir);
30
+ if (parent === dir) break;
31
+ dir = parent;
32
+ }
33
+ return null;
34
+ }
35
+
36
+ export function resolveDockerHostEnv() {
37
+ // Prefer Docker Desktop’s user socket on macOS.
38
+ const desktopSock = resolve(process.env.HOME ?? '', '.docker', 'run', 'docker.sock');
39
+ if (existsSync(desktopSock)) {
40
+ return `unix://${desktopSock}`;
41
+ }
42
+ // Fallback to the classic location (often present on Linux).
43
+ const defaultSock = '/var/run/docker.sock';
44
+ if (existsSync(defaultSock)) {
45
+ return `unix://${defaultSock}`;
46
+ }
47
+ return '';
48
+ }
49
+
50
+ async function cmdAct({ argv }) {
51
+ const { flags } = parseArgs(argv);
52
+ const json = wantsJson(argv, { flags });
53
+
54
+ const rootDir = getRootDir();
55
+ const invokedCwd = getInvokedCwd(process.env);
56
+ const happyRoot = findHappyMonorepoRoot(invokedCwd);
57
+ if (!happyRoot) {
58
+ throw new Error('[ci] could not find Happier monorepo root from cwd; run from inside the happier worktree (monorepo root)');
59
+ }
60
+
61
+ const runner = join(happyRoot, 'scripts', 'ci', 'run-act-tests.sh');
62
+ if (!existsSync(runner)) {
63
+ throw new Error(`[ci] missing act runner script: ${runner}`);
64
+ }
65
+
66
+ const positionals = argv.filter((a) => !a.startsWith('--'));
67
+ const jobArgs = positionals.slice(1); // after "act"
68
+
69
+ const dockerHost = resolveDockerHostEnv();
70
+ const env = {
71
+ ...process.env,
72
+ ...(dockerHost ? { ACT_DOCKER_SOCKET: dockerHost } : {}),
73
+ };
74
+
75
+ if (!json) {
76
+ // eslint-disable-next-line no-console
77
+ console.log(`[ci] act: ${runner}`);
78
+ if (dockerHost) {
79
+ // eslint-disable-next-line no-console
80
+ console.log(`[ci] ACT_DOCKER_SOCKET=${dockerHost}`);
81
+ }
82
+ if (jobArgs.length > 0) {
83
+ // eslint-disable-next-line no-console
84
+ console.log(`[ci] jobs: ${jobArgs.join(' ')}`);
85
+ }
86
+ }
87
+
88
+ await run('bash', [runner, ...jobArgs], { cwd: happyRoot, env });
89
+ printResult({
90
+ json,
91
+ data: { ok: true, runner, happyRoot, dockerHost: dockerHost || null },
92
+ text: '[ci] ✅ act ok',
93
+ });
94
+ }
95
+
96
+ async function main() {
97
+ const argv = process.argv.slice(2);
98
+ const { flags } = parseArgs(argv);
99
+ const json = wantsJson(argv, { flags });
100
+
101
+ if (wantsHelp(argv, { flags })) {
102
+ printResult({
103
+ json,
104
+ data: { commands: ['act'], flags: ['--json'] },
105
+ text: ['[ci] usage:', ' hstack ci act [job...] [--json]'].join('\n'),
106
+ });
107
+ return;
108
+ }
109
+
110
+ const positionals = argv.filter((a) => !a.startsWith('--'));
111
+ const sub = positionals[0];
112
+ if (!sub) {
113
+ throw new Error('[ci] missing subcommand (expected: act)');
114
+ }
115
+ if (sub === 'act') {
116
+ await cmdAct({ argv });
117
+ return;
118
+ }
119
+ throw new Error(`[ci] unknown subcommand: ${sub} (expected: act)`);
120
+ }
121
+
122
+ const invokedAsMain = (() => {
123
+ const argv1 = process.argv[1];
124
+ if (!argv1) return false;
125
+ return resolve(argv1) === fileURLToPath(import.meta.url);
126
+ })();
127
+
128
+ if (invokedAsMain) {
129
+ main().catch((error) => {
130
+ const msg = error instanceof Error ? error.message : String(error);
131
+ // eslint-disable-next-line no-console
132
+ console.error(msg);
133
+ process.exit(1);
134
+ });
135
+ }
@@ -0,0 +1,50 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtemp, mkdir, realpath, writeFile, rm } from 'node:fs/promises';
4
+ import { join } from 'node:path';
5
+ import { tmpdir } from 'node:os';
6
+ import { resolveDockerHostEnv } from './ci.mjs';
7
+
8
+ test('resolveDockerHostEnv prefers Docker Desktop user socket when present', async () => {
9
+ const prevHome = process.env.HOME;
10
+ const home = await mkdtemp(join(tmpdir(), 'ci-test-home-'));
11
+ try {
12
+ process.env.HOME = home;
13
+ const sockPath = join(home, '.docker', 'run', 'docker.sock');
14
+ await mkdir(join(home, '.docker', 'run'), { recursive: true });
15
+ await writeFile(sockPath, '');
16
+
17
+ const host = resolveDockerHostEnv();
18
+ const hostPath = host.replace(/^unix:\/\//, '');
19
+ const expected = await realpath(sockPath);
20
+ const actual = await realpath(hostPath);
21
+ assert.equal(actual, expected);
22
+ } finally {
23
+ process.env.HOME = prevHome;
24
+ await rm(home, { recursive: true, force: true });
25
+ }
26
+ });
27
+
28
+ test('resolveDockerHostEnv still resolves user socket when HOME is unset', async () => {
29
+ const prevHome = process.env.HOME;
30
+ const prevCwd = process.cwd();
31
+ const cwd = await mkdtemp(join(tmpdir(), 'ci-test-cwd-'));
32
+ try {
33
+ delete process.env.HOME;
34
+ process.chdir(cwd);
35
+ const sockPath = join(cwd, '.docker', 'run', 'docker.sock');
36
+ await mkdir(join(cwd, '.docker', 'run'), { recursive: true });
37
+ await writeFile(sockPath, '');
38
+
39
+ const host = resolveDockerHostEnv();
40
+ const hostPath = host.replace(/^unix:\/\//, '');
41
+ const expected = await realpath(sockPath);
42
+ const actual = await realpath(hostPath);
43
+ assert.equal(actual, expected);
44
+ } finally {
45
+ process.chdir(prevCwd);
46
+ if (typeof prevHome === 'undefined') delete process.env.HOME;
47
+ else process.env.HOME = prevHome;
48
+ await rm(cwd, { recursive: true, force: true });
49
+ }
50
+ });
@@ -0,0 +1,57 @@
1
+ import './utils/env/env.mjs';
2
+ import { parseArgs } from './utils/cli/args.mjs';
3
+ import { getComponentDir, getRootDir } from './utils/paths/paths.mjs';
4
+ import { ensureCliBuilt, ensureHappyCliLocalNpmLinked } from './utils/proc/pm.mjs';
5
+ import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
6
+
7
+ /**
8
+ * Link the local Happier CLI wrapper into your PATH.
9
+ *
10
+ * This is intentionally extracted so you can re-run linking without doing a full `hstack bootstrap`.
11
+ *
12
+ * What it does:
13
+ * - optionally builds the monorepo CLI package (apps/cli or packages/cli) (controlled by env/flags)
14
+ * - installs `happier`/`hstack` shims under `<homeDir>/bin` (default: `~/.happier-stack/bin`) (recommended over `npm link`)
15
+ *
16
+ * Env:
17
+ * - HAPPIER_STACK_CLI_BUILD=0 to skip building happier-cli
18
+ * - HAPPIER_STACK_NPM_LINK=0 to skip shim installation
19
+ *
20
+ * Flags:
21
+ * - --no-build: skip building happier-cli
22
+ * - --no-link: skip shim installation
23
+ */
24
+
25
+ async function main() {
26
+ const argv = process.argv.slice(2);
27
+ const { flags } = parseArgs(argv);
28
+ const json = wantsJson(argv, { flags });
29
+ if (wantsHelp(argv, { flags })) {
30
+ printResult({
31
+ json,
32
+ data: { flags: ['--no-build', '--no-link'], json: true },
33
+ text: [
34
+ '[cli-link] usage:',
35
+ ' hstack cli:link [--no-build] [--no-link] [--json]',
36
+ ' node scripts/cli-link.mjs [--no-build] [--no-link] [--json]',
37
+ ].join('\n'),
38
+ });
39
+ return;
40
+ }
41
+
42
+ const rootDir = getRootDir(import.meta.url);
43
+ const cliDir = getComponentDir(rootDir, 'happier-cli');
44
+
45
+ const buildCli = !flags.has('--no-build') && (process.env.HAPPIER_STACK_CLI_BUILD ?? '1') !== '0';
46
+ const npmLinkCli = !flags.has('--no-link') && (process.env.HAPPIER_STACK_NPM_LINK ?? '1') !== '0';
47
+
48
+ await ensureCliBuilt(cliDir, { buildCli });
49
+ await ensureHappyCliLocalNpmLinked(rootDir, { npmLinkCli });
50
+
51
+ printResult({ json, data: { ok: true, buildCli, npmLinkCli }, text: '[local] cli link complete' });
52
+ }
53
+
54
+ main().catch((err) => {
55
+ console.error('[local] cli link failed:', err);
56
+ process.exit(1);
57
+ });