@happier-dev/stack 0.1.0-preview.5.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 +159 -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 +94 -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,128 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import { dirname, join } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ import { interactiveNew } from './utils/stack/interactive_stack_config.mjs';
9
+
10
+ function mkRl() {
11
+ return { question: async () => '' };
12
+ }
13
+
14
+ function createInteractiveDeps({ prompted = [], selectedRepo = 'tmp/mono-wt' } = {}) {
15
+ return {
16
+ prompt: async (_rl, _question, { defaultValue } = {}) => defaultValue ?? '',
17
+ promptSelect: async (_rl, { options, defaultIndex = 0 } = {}) => options?.[defaultIndex]?.value,
18
+ promptWorktreeSource: async ({ component }) => {
19
+ prompted.push(component);
20
+ if (selectedRepo != null) return selectedRepo;
21
+ throw new Error(`unexpected promptWorktreeSource call: ${component}`);
22
+ },
23
+ };
24
+ }
25
+
26
+ test('interactive stack new in monorepo mode prompts once for shared repo source', async () => {
27
+ const scriptsDir = dirname(fileURLToPath(import.meta.url));
28
+ const rootDir = dirname(scriptsDir);
29
+ const tmp = await mkdtemp(join(tmpdir(), 'happier-stack-interactive-new-mono-'));
30
+ const prompted = [];
31
+
32
+ const prevWorkspace = process.env.HAPPIER_STACK_WORKSPACE_DIR;
33
+ const prevOwner = process.env.HAPPIER_STACK_OWNER;
34
+ try {
35
+ const workspaceDir = join(tmp, 'workspace');
36
+ process.env.HAPPIER_STACK_WORKSPACE_DIR = workspaceDir;
37
+ process.env.HAPPIER_STACK_OWNER = 'test';
38
+
39
+ const monoRoot = join(workspaceDir, 'tmp', 'test', 'mono-wt');
40
+ await mkdir(join(monoRoot, 'apps', 'ui'), { recursive: true });
41
+ await mkdir(join(monoRoot, 'apps', 'cli'), { recursive: true });
42
+ await mkdir(join(monoRoot, 'apps', 'server'), { recursive: true });
43
+ await writeFile(join(monoRoot, '.git'), 'gitdir: dummy\n', 'utf-8');
44
+ await writeFile(join(monoRoot, 'apps', 'ui', 'package.json'), '{}\n', 'utf-8');
45
+ await writeFile(join(monoRoot, 'apps', 'cli', 'package.json'), '{}\n', 'utf-8');
46
+ await writeFile(join(monoRoot, 'apps', 'server', 'package.json'), '{}\n', 'utf-8');
47
+
48
+ const out = await interactiveNew({
49
+ rootDir,
50
+ rl: mkRl(),
51
+ defaults: {
52
+ stackName: 'exp-mono-int',
53
+ port: 1,
54
+ serverComponent: 'happier-server-light',
55
+ createRemote: 'upstream',
56
+ repo: null,
57
+ },
58
+ deps: createInteractiveDeps({ prompted }),
59
+ });
60
+
61
+ assert.deepEqual(prompted, ['happier-ui']);
62
+ assert.equal(out.repo, 'tmp/mono-wt');
63
+ } finally {
64
+ if (prevWorkspace == null) {
65
+ delete process.env.HAPPIER_STACK_WORKSPACE_DIR;
66
+ } else {
67
+ process.env.HAPPIER_STACK_WORKSPACE_DIR = prevWorkspace;
68
+ }
69
+ if (prevOwner == null) {
70
+ delete process.env.HAPPIER_STACK_OWNER;
71
+ } else {
72
+ process.env.HAPPIER_STACK_OWNER = prevOwner;
73
+ }
74
+ await rm(tmp, { recursive: true, force: true });
75
+ }
76
+ });
77
+
78
+ test('interactive stack new skips worktree prompt when repo is already provided', async () => {
79
+ const scriptsDir = dirname(fileURLToPath(import.meta.url));
80
+ const rootDir = dirname(scriptsDir);
81
+ const tmp = await mkdtemp(join(tmpdir(), 'happier-stack-interactive-new-mono-preseed-'));
82
+ const prompted = [];
83
+
84
+ const prevWorkspace = process.env.HAPPIER_STACK_WORKSPACE_DIR;
85
+ const prevOwner = process.env.HAPPIER_STACK_OWNER;
86
+ try {
87
+ const workspaceDir = join(tmp, 'workspace');
88
+ process.env.HAPPIER_STACK_WORKSPACE_DIR = workspaceDir;
89
+ process.env.HAPPIER_STACK_OWNER = 'test';
90
+
91
+ const monoRoot = join(workspaceDir, 'tmp', 'test', 'mono-wt');
92
+ await mkdir(join(monoRoot, 'apps', 'ui'), { recursive: true });
93
+ await mkdir(join(monoRoot, 'apps', 'cli'), { recursive: true });
94
+ await mkdir(join(monoRoot, 'apps', 'server'), { recursive: true });
95
+ await writeFile(join(monoRoot, '.git'), 'gitdir: dummy\n', 'utf-8');
96
+ await writeFile(join(monoRoot, 'apps', 'ui', 'package.json'), '{}\n', 'utf-8');
97
+ await writeFile(join(monoRoot, 'apps', 'cli', 'package.json'), '{}\n', 'utf-8');
98
+ await writeFile(join(monoRoot, 'apps', 'server', 'package.json'), '{}\n', 'utf-8');
99
+
100
+ const out = await interactiveNew({
101
+ rootDir,
102
+ rl: mkRl(),
103
+ defaults: {
104
+ stackName: 'exp-mono-preseed',
105
+ port: 1,
106
+ serverComponent: 'happier-server-light',
107
+ createRemote: 'upstream',
108
+ repo: 'dev',
109
+ },
110
+ deps: createInteractiveDeps({ prompted, selectedRepo: null }),
111
+ });
112
+
113
+ assert.deepEqual(prompted, []);
114
+ assert.equal(out.repo, 'dev');
115
+ } finally {
116
+ if (prevWorkspace == null) {
117
+ delete process.env.HAPPIER_STACK_WORKSPACE_DIR;
118
+ } else {
119
+ process.env.HAPPIER_STACK_WORKSPACE_DIR = prevWorkspace;
120
+ }
121
+ if (prevOwner == null) {
122
+ delete process.env.HAPPIER_STACK_OWNER;
123
+ } else {
124
+ process.env.HAPPIER_STACK_OWNER = prevOwner;
125
+ }
126
+ await rm(tmp, { recursive: true, force: true });
127
+ }
128
+ });
@@ -0,0 +1,31 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { setupStackNewMonorepoFixture } from './testkit/stack_new_monorepo_testkit.mjs';
4
+
5
+ test('hstack stack new defaults repo token variants to workspace main monorepo checkout', async (t) => {
6
+ const fixture = await setupStackNewMonorepoFixture({
7
+ importMetaUrl: import.meta.url,
8
+ t,
9
+ tmpPrefix: 'happy-stacks-stack-monorepo-defaults-',
10
+ });
11
+ const mainMonorepoRoot = await fixture.createMonorepoCheckout('main');
12
+
13
+ const cases = [
14
+ { name: 'implicit default repo', stackName: 'exp-test', args: [] },
15
+ { name: '--repo=main token', stackName: 'exp-main-token', args: ['--repo=main', '--no-copy-auth'] },
16
+ { name: '--repo=default token', stackName: 'exp-default-token', args: ['--repo=default', '--no-copy-auth'] },
17
+ ];
18
+
19
+ for (const testCase of cases) {
20
+ const res = await fixture.runStackNew([testCase.stackName, ...testCase.args, '--json']);
21
+ assert.equal(
22
+ res.code,
23
+ 0,
24
+ `${testCase.name}: expected exit 0, got ${res.code}\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`
25
+ );
26
+
27
+ const contents = await fixture.readStackEnv(testCase.stackName);
28
+ assert.ok(contents.includes(`HAPPIER_STACK_REPO_DIR=${mainMonorepoRoot}\n`), `${testCase.name}\n${contents}`);
29
+ assert.ok(!contents.includes('HAPPIER_STACK_COMPONENT_DIR_'), `${testCase.name}\n${contents}`);
30
+ }
31
+ });
@@ -0,0 +1,32 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { setupStackNewMonorepoFixture } from './testkit/stack_new_monorepo_testkit.mjs';
4
+
5
+ test('hstack stack new repo selectors pin workspace dev monorepo checkout', async (t) => {
6
+ const fixture = await setupStackNewMonorepoFixture({
7
+ importMetaUrl: import.meta.url,
8
+ t,
9
+ tmpPrefix: 'hstack-stack-dev-token-',
10
+ });
11
+
12
+ await fixture.createMonorepoCheckout('main');
13
+ const devRoot = await fixture.createMonorepoCheckout('dev');
14
+
15
+ const cases = [
16
+ { name: '--repo=dev token', stackName: 'exp-dev-token', repoArg: '--repo=dev' },
17
+ { name: '--repo=<abs dev path>', stackName: 'exp-dev-abs', repoArg: `--repo=${devRoot}` },
18
+ ];
19
+
20
+ for (const testCase of cases) {
21
+ const res = await fixture.runStackNew([testCase.stackName, testCase.repoArg, '--no-copy-auth', '--json']);
22
+ assert.equal(
23
+ res.code,
24
+ 0,
25
+ `${testCase.name}: expected exit 0, got ${res.code}\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`
26
+ );
27
+
28
+ const contents = await fixture.readStackEnv(testCase.stackName);
29
+ assert.ok(contents.includes(`HAPPIER_STACK_REPO_DIR=${devRoot}\n`), `${testCase.name}\n${contents}`);
30
+ assert.ok(!contents.includes('HAPPIER_STACK_COMPONENT_DIR_'), `${testCase.name}\n${contents}`);
31
+ }
32
+ });
@@ -0,0 +1,37 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { setupStackNewMonorepoFixture } from './testkit/stack_new_monorepo_testkit.mjs';
4
+
5
+ test('hstack stack new pins HAPPIER_STACK_REPO_DIR from monorepo path specs', async (t) => {
6
+ const fixture = await setupStackNewMonorepoFixture({
7
+ importMetaUrl: import.meta.url,
8
+ t,
9
+ tmpPrefix: 'happier-stack-monorepo-spec-',
10
+ });
11
+
12
+ const monoRoot = await fixture.createMonorepoCheckout('tmp/leeroy/leeroy-wip', { includeServerPrisma: true });
13
+
14
+ const cases = [
15
+ { name: 'absolute monorepo path', stackName: 'exp-mono-spec', repoArg: `--repo=${monoRoot}` },
16
+ { name: 'workspace-relative monorepo path', stackName: 'exp-mono-relative', repoArg: '--repo=./tmp/leeroy/leeroy-wip' },
17
+ ];
18
+
19
+ for (const testCase of cases) {
20
+ const res = await fixture.runStackNew([
21
+ testCase.stackName,
22
+ testCase.repoArg,
23
+ '--server=happier-server-light',
24
+ '--no-copy-auth',
25
+ '--json',
26
+ ]);
27
+ assert.equal(
28
+ res.code,
29
+ 0,
30
+ `${testCase.name}: expected exit 0, got ${res.code}\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`
31
+ );
32
+
33
+ const contents = await fixture.readStackEnv(testCase.stackName);
34
+ assert.ok(contents.includes(`HAPPIER_STACK_REPO_DIR=${monoRoot}\n`), `${testCase.name}\n${contents}`);
35
+ assert.ok(!contents.includes('HAPPIER_STACK_COMPONENT_DIR_'), `${testCase.name}\n${contents}`);
36
+ }
37
+ });
@@ -0,0 +1,38 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { setupStackNewMonorepoFixture } from './testkit/stack_new_monorepo_testkit.mjs';
4
+
5
+ test('hstack stack new normalizes stack names across valid and punctuation-heavy inputs', async (t) => {
6
+ const fixture = await setupStackNewMonorepoFixture({
7
+ importMetaUrl: import.meta.url,
8
+ t,
9
+ tmpPrefix: 'happier-stack-new-name-',
10
+ });
11
+ await fixture.createMonorepoCheckout('main', { includeServerPrisma: true });
12
+
13
+ const cases = [
14
+ { rawName: 'My Stack', normalized: 'my-stack' },
15
+ { rawName: 'already-valid', normalized: 'already-valid' },
16
+ { rawName: ' MIXED__Case...Name ', normalized: 'mixed-case-name' },
17
+ { rawName: 'alpha---beta', normalized: 'alpha-beta' },
18
+ ];
19
+
20
+ for (const testCase of cases) {
21
+ const res = await fixture.runStackNew([testCase.rawName, '--json']);
22
+ assert.equal(
23
+ res.code,
24
+ 0,
25
+ `${testCase.rawName}: expected exit 0, got ${res.code} (signal: ${res.signal})\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`
26
+ );
27
+
28
+ const parsed = JSON.parse(res.stdout.trim());
29
+ assert.equal(parsed?.stackName, testCase.normalized, `${testCase.rawName}: unexpected stackName`);
30
+ assert.ok(
31
+ typeof parsed?.envPath === 'string' && parsed.envPath.includes(`/${testCase.normalized}/env`),
32
+ `${testCase.rawName}: unexpected envPath ${parsed?.envPath}`
33
+ );
34
+
35
+ const contents = await fixture.readStackEnv(testCase.normalized);
36
+ assert.ok(contents.includes(`HAPPIER_STACK_STACK=${testCase.normalized}\n`), `${testCase.rawName}\n${contents}`);
37
+ }
38
+ });
@@ -0,0 +1,84 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { spawn } from 'node:child_process';
4
+ import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
5
+ import { tmpdir } from 'node:os';
6
+ import { dirname, join } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ function runNode(args, { cwd, env }) {
10
+ return new Promise((resolve, reject) => {
11
+ const proc = spawn(process.execPath, args, { cwd, env, stdio: ['ignore', 'pipe', 'pipe'] });
12
+ let stdout = '';
13
+ let stderr = '';
14
+ proc.stdout.on('data', (data) => (stdout += String(data)));
15
+ proc.stderr.on('data', (data) => (stderr += String(data)));
16
+ proc.on('error', reject);
17
+ proc.on('exit', (code, signal) => resolve({ code: code ?? (signal ? 128 : 0), signal, stdout, stderr }));
18
+ });
19
+ }
20
+
21
+ test('hstack stack pr rejects stack names that normalize to reserved main', async () => {
22
+ const scriptsDir = dirname(fileURLToPath(import.meta.url));
23
+ const rootDir = dirname(scriptsDir);
24
+ const tmp = await mkdtemp(join(tmpdir(), 'happier-stack-pr-main-'));
25
+
26
+ try {
27
+ const workspaceDir = join(tmp, 'workspace');
28
+ const storageDir = join(tmp, 'storage');
29
+ const homeDir = join(tmp, 'home');
30
+ const sandboxDir = join(tmp, 'sandbox');
31
+
32
+ const env = {
33
+ ...process.env,
34
+ HAPPIER_STACK_HOME_DIR: homeDir,
35
+ HAPPIER_STACK_WORKSPACE_DIR: workspaceDir,
36
+ HAPPIER_STACK_STORAGE_DIR: storageDir,
37
+ HAPPIER_STACK_SANDBOX_DIR: sandboxDir,
38
+ };
39
+
40
+ const res = await runNode([join(rootDir, 'scripts', 'stack.mjs'), 'pr', 'Main'], { cwd: rootDir, env });
41
+ assert.notEqual(res.code, 0, `expected non-zero exit\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
42
+ assert.match(
43
+ `${res.stderr}\n${res.stdout}`,
44
+ /stack name "main" is reserved/i,
45
+ `expected reserved-name error\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`
46
+ );
47
+ } finally {
48
+ await rm(tmp, { recursive: true, force: true });
49
+ }
50
+ });
51
+
52
+ test('hstack stack pr rejects names that normalize to an existing stack', async () => {
53
+ const scriptsDir = dirname(fileURLToPath(import.meta.url));
54
+ const rootDir = dirname(scriptsDir);
55
+ const tmp = await mkdtemp(join(tmpdir(), 'happier-stack-pr-collision-'));
56
+
57
+ try {
58
+ const workspaceDir = join(tmp, 'workspace');
59
+ const storageDir = join(tmp, 'storage');
60
+ const homeDir = join(tmp, 'home');
61
+ const sandboxDir = join(tmp, 'sandbox');
62
+
63
+ await mkdir(join(storageDir, 'dev-auth'), { recursive: true });
64
+ await writeFile(join(storageDir, 'dev-auth', 'env'), 'HAPPIER_STACK_STACK=dev-auth\n', 'utf8');
65
+
66
+ const env = {
67
+ ...process.env,
68
+ HAPPIER_STACK_HOME_DIR: homeDir,
69
+ HAPPIER_STACK_WORKSPACE_DIR: workspaceDir,
70
+ HAPPIER_STACK_STORAGE_DIR: storageDir,
71
+ HAPPIER_STACK_SANDBOX_DIR: sandboxDir,
72
+ };
73
+
74
+ const res = await runNode([join(rootDir, 'scripts', 'stack.mjs'), 'pr', 'Dev-Auth'], { cwd: rootDir, env });
75
+ assert.notEqual(res.code, 0, `expected non-zero exit\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
76
+ assert.match(
77
+ `${res.stderr}\n${res.stdout}`,
78
+ /stack already exists/i,
79
+ `expected collision error\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`
80
+ );
81
+ } finally {
82
+ await rm(tmp, { recursive: true, force: true });
83
+ }
84
+ });
@@ -0,0 +1,134 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { existsSync } from 'node:fs';
4
+ import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
5
+ import { tmpdir } from 'node:os';
6
+ import { dirname, join } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ import { runNodeCapture } from './testkit/stack_script_command_testkit.mjs';
10
+
11
+ const scriptsDir = dirname(fileURLToPath(import.meta.url));
12
+ const rootDir = dirname(scriptsDir);
13
+
14
+ async function ensureMinimalHappierMonorepo({ monoRoot }) {
15
+ await mkdir(join(monoRoot, 'apps', 'ui'), { recursive: true });
16
+ await mkdir(join(monoRoot, 'apps', 'cli', 'bin'), { recursive: true });
17
+ await mkdir(join(monoRoot, 'apps', 'cli', 'dist'), { recursive: true });
18
+ await mkdir(join(monoRoot, 'apps', 'server'), { recursive: true });
19
+ await writeFile(join(monoRoot, 'apps', 'ui', 'package.json'), '{}\n', 'utf-8');
20
+ await writeFile(join(monoRoot, 'apps', 'cli', 'package.json'), '{}\n', 'utf-8');
21
+ await writeFile(join(monoRoot, 'apps', 'server', 'package.json'), '{}\n', 'utf-8');
22
+ }
23
+
24
+ async function writeStubHappyCli({ cliDir }) {
25
+ const script = [
26
+ "import { appendFileSync } from 'node:fs';",
27
+ "import { join } from 'node:path';",
28
+ '',
29
+ 'const args = process.argv.slice(2);',
30
+ "const home = process.env.HAPPIER_HOME_DIR || process.cwd();",
31
+ "const logPath = join(home, 'resume-invocations.log');",
32
+ "const supportsDaemonResume = process.env.STUB_DAEMON_RESUME_SUPPORT === '1';",
33
+ '',
34
+ 'if (args[0] === \'daemon\' && args[1] === \'--help\') {',
35
+ ' if (supportsDaemonResume) {',
36
+ " console.log('Usage:\\n happier daemon resume <sessionId...>');",
37
+ ' } else {',
38
+ " console.log('happier daemon - Daemon management');",
39
+ " console.log('Usage: happier daemon start|stop|status|list');",
40
+ ' }',
41
+ ' process.exit(0);',
42
+ '}',
43
+ '',
44
+ 'if (args[0] === \'daemon\' && args[1] === \'resume\') {',
45
+ " appendFileSync(logPath, `daemon resume ${args.slice(2).join(' ')}\\n`, 'utf-8');",
46
+ ' process.exit(0);',
47
+ '}',
48
+ '',
49
+ "appendFileSync(logPath, `unexpected ${args.join(' ')}\\n`, 'utf-8');",
50
+ 'process.exit(0);',
51
+ '',
52
+ ].join('\n');
53
+
54
+ await writeFile(join(cliDir, 'dist', 'index.mjs'), script, 'utf-8');
55
+ await writeFile(join(cliDir, 'bin', 'happier.mjs'), "import '../dist/index.mjs';\n", 'utf-8');
56
+ }
57
+
58
+ async function createResumeFixture(t, { stackName = 'exp-test', serverPort = 4101 } = {}) {
59
+ const tmp = await mkdtemp(join(tmpdir(), 'happier-stack-resume-'));
60
+ t.after(async () => {
61
+ await rm(tmp, { recursive: true, force: true });
62
+ });
63
+
64
+ const storageDir = join(tmp, 'storage');
65
+ const homeDir = join(tmp, 'home');
66
+ const workspaceDir = join(tmp, 'workspace');
67
+ const monoRoot = join(workspaceDir, 'happier');
68
+ const cliDir = join(monoRoot, 'apps', 'cli');
69
+ const stackCliHome = join(storageDir, stackName, 'cli');
70
+
71
+ await ensureMinimalHappierMonorepo({ monoRoot });
72
+ await writeStubHappyCli({ cliDir });
73
+ await mkdir(stackCliHome, { recursive: true });
74
+
75
+ const envPath = join(storageDir, stackName, 'env');
76
+ await mkdir(dirname(envPath), { recursive: true });
77
+ await writeFile(
78
+ envPath,
79
+ [
80
+ `HAPPIER_STACK_REPO_DIR=${monoRoot}`,
81
+ `HAPPIER_STACK_CLI_HOME_DIR=${stackCliHome}`,
82
+ `HAPPIER_STACK_SERVER_PORT=${serverPort}`,
83
+ '',
84
+ ].join('\n'),
85
+ 'utf-8'
86
+ );
87
+
88
+ return {
89
+ stackName,
90
+ stackCliHome,
91
+ baseEnv: {
92
+ ...process.env,
93
+ HAPPIER_STACK_HOME_DIR: homeDir,
94
+ HAPPIER_STACK_STORAGE_DIR: storageDir,
95
+ HAPPIER_STACK_WORKSPACE_DIR: workspaceDir,
96
+ HAPPIER_STACK_CLI_ROOT_DISABLE: '1',
97
+ },
98
+ };
99
+ }
100
+
101
+ test('hstack stack resume fails closed when happier daemon does not support resume', async (t) => {
102
+ const fixture = await createResumeFixture(t);
103
+ const res = await runNodeCapture(
104
+ [join(rootDir, 'bin', 'hstack.mjs'), 'stack', 'resume', fixture.stackName, 'session-1', '--json'],
105
+ { cwd: rootDir, env: fixture.baseEnv }
106
+ );
107
+
108
+ assert.equal(
109
+ res.code,
110
+ 1,
111
+ `expected exit 1 when daemon resume is unsupported\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`
112
+ );
113
+ assert.match(res.stdout, /resume_not_supported/i);
114
+
115
+ const invocationLog = join(fixture.stackCliHome, 'resume-invocations.log');
116
+ assert.equal(existsSync(invocationLog), false, 'expected daemon resume not to be invoked');
117
+ });
118
+
119
+ test('hstack stack resume invokes happier daemon resume when supported', async (t) => {
120
+ const fixture = await createResumeFixture(t);
121
+ const res = await runNodeCapture(
122
+ [join(rootDir, 'bin', 'hstack.mjs'), 'stack', 'resume', fixture.stackName, 'session-a', 'session-b', '--json'],
123
+ {
124
+ cwd: rootDir,
125
+ env: {
126
+ ...fixture.baseEnv,
127
+ STUB_DAEMON_RESUME_SUPPORT: '1',
128
+ },
129
+ }
130
+ );
131
+
132
+ assert.equal(res.code, 0, `expected exit 0\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
133
+ assert.match(res.stdout, /"resumed":\s*\[\s*"session-a",\s*"session-b"\s*\]/);
134
+ });
@@ -0,0 +1,64 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { join } from 'node:path';
4
+
5
+ import { setupStackNewMonorepoFixture } from './testkit/stack_new_monorepo_testkit.mjs';
6
+
7
+ test('hstack stack new server flavor defaults and explicit full flavor pin coherent env values', async (t) => {
8
+ const fixture = await setupStackNewMonorepoFixture({
9
+ importMetaUrl: import.meta.url,
10
+ t,
11
+ tmpPrefix: 'happier-stack-server-flavors-',
12
+ });
13
+ const monoRoot = await fixture.createMonorepoCheckout('main', { includeServerPrisma: true });
14
+
15
+ const cases = [
16
+ {
17
+ name: 'default server flavor',
18
+ stackName: 'exp-flavors-light-default',
19
+ args: ['--json'],
20
+ assertEnv(contents) {
21
+ assert.ok(contents.includes('HAPPIER_STACK_SERVER_COMPONENT=happier-server-light\n'), contents);
22
+ assert.ok(contents.includes('HAPPIER_DB_PROVIDER=sqlite\n'), contents);
23
+ assert.ok(
24
+ contents.includes(`HAPPIER_SERVER_LIGHT_DATA_DIR=${join(fixture.storageDir, 'exp-flavors-light-default', 'server-light')}\n`),
25
+ contents
26
+ );
27
+ assert.ok(
28
+ contents.includes(`HAPPIER_SERVER_LIGHT_FILES_DIR=${join(fixture.storageDir, 'exp-flavors-light-default', 'server-light', 'files')}\n`),
29
+ contents
30
+ );
31
+ assert.equal(
32
+ contents.includes(`HAPPIER_SERVER_LIGHT_DB_DIR=${join(fixture.storageDir, 'exp-flavors-light-default', 'server-light', 'pglite')}\n`),
33
+ false,
34
+ contents
35
+ );
36
+ },
37
+ },
38
+ {
39
+ name: 'explicit full server flavor',
40
+ stackName: 'exp-flavors-full-explicit',
41
+ args: ['--server=happier-server', '--no-copy-auth', '--json'],
42
+ assertEnv(contents) {
43
+ assert.ok(contents.includes('HAPPIER_STACK_SERVER_COMPONENT=happier-server\n'), contents);
44
+ assert.ok(contents.includes('HAPPIER_DB_PROVIDER=postgres\n'), contents);
45
+ assert.ok(contents.includes('HAPPIER_STACK_MANAGED_INFRA=1\n'), contents);
46
+ assert.ok(!contents.includes('HAPPIER_SERVER_LIGHT_DATA_DIR='), contents);
47
+ },
48
+ },
49
+ ];
50
+
51
+ for (const testCase of cases) {
52
+ const res = await fixture.runStackNew([testCase.stackName, ...testCase.args]);
53
+ assert.equal(
54
+ res.code,
55
+ 0,
56
+ `${testCase.name}: expected exit 0, got ${res.code}\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`
57
+ );
58
+
59
+ const contents = await fixture.readStackEnv(testCase.stackName);
60
+ assert.ok(contents.includes(`HAPPIER_STACK_REPO_DIR=${monoRoot}\n`), `${testCase.name}\n${contents}`);
61
+ assert.ok(!contents.includes('HAPPIER_STACK_COMPONENT_DIR_'), `${testCase.name}\n${contents}`);
62
+ testCase.assertEnv(contents);
63
+ }
64
+ });
@@ -0,0 +1,74 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import { dirname, join } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { runNodeCapture } from './testkit/stack_script_command_testkit.mjs';
8
+
9
+ test('hstack <stack> <cmd> ... rewrites to hstack stack <cmd> <stack> ... when stack exists', async (t) => {
10
+ const scriptsDir = dirname(fileURLToPath(import.meta.url));
11
+ const rootDir = dirname(scriptsDir);
12
+ const tmp = await mkdtemp(join(tmpdir(), 'happy-stacks-shorthand-'));
13
+ t.after(async () => {
14
+ await rm(tmp, { recursive: true, force: true }).catch(() => {});
15
+ });
16
+
17
+ const storageDir = join(tmp, 'storage');
18
+ const homeDir = join(tmp, 'home');
19
+ const stackName = 'exp-test';
20
+
21
+ const envPath = join(storageDir, stackName, 'env');
22
+ await mkdir(dirname(envPath), { recursive: true });
23
+ await mkdir(homeDir, { recursive: true });
24
+ await writeFile(envPath, 'FOO=bar\n', 'utf-8');
25
+
26
+ const baseEnv = {
27
+ ...process.env,
28
+ HAPPIER_STACK_HOME_DIR: homeDir,
29
+ HAPPIER_STACK_STORAGE_DIR: storageDir,
30
+ HAPPIER_STACK_CLI_ROOT_DISABLE: '1',
31
+ };
32
+
33
+ const res = await runNodeCapture([join(rootDir, 'bin', 'hstack.mjs'), stackName, 'env', 'path', '--json'], {
34
+ cwd: rootDir,
35
+ env: baseEnv,
36
+ });
37
+ assert.equal(res.code, 0, `expected exit 0, got ${res.code}\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
38
+
39
+ const out = JSON.parse(res.stdout || '{}');
40
+ assert.equal(out.ok, true);
41
+ assert.ok(
42
+ typeof out.envPath === 'string' && out.envPath.endsWith(`/${stackName}/env`),
43
+ `expected envPath to end with /${stackName}/env, got: ${out.envPath}`
44
+ );
45
+ });
46
+
47
+ test('hstack does not rewrite shorthand when stack does not exist', async (t) => {
48
+ const scriptsDir = dirname(fileURLToPath(import.meta.url));
49
+ const rootDir = dirname(scriptsDir);
50
+ const tmp = await mkdtemp(join(tmpdir(), 'happy-stacks-shorthand-missing-'));
51
+ t.after(async () => {
52
+ await rm(tmp, { recursive: true, force: true }).catch(() => {});
53
+ });
54
+
55
+ const storageDir = join(tmp, 'storage');
56
+ const homeDir = join(tmp, 'home');
57
+
58
+ await mkdir(storageDir, { recursive: true });
59
+ await mkdir(homeDir, { recursive: true });
60
+
61
+ const baseEnv = {
62
+ ...process.env,
63
+ HAPPIER_STACK_HOME_DIR: homeDir,
64
+ HAPPIER_STACK_STORAGE_DIR: storageDir,
65
+ HAPPIER_STACK_CLI_ROOT_DISABLE: '1',
66
+ };
67
+
68
+ const res = await runNodeCapture([join(rootDir, 'bin', 'hstack.mjs'), 'missing-stack', 'env', 'path', '--json'], {
69
+ cwd: rootDir,
70
+ env: baseEnv,
71
+ });
72
+ assert.equal(res.code, 1, `expected exit 1, got ${res.code}\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
73
+ assert.match(res.stderr, /^\[hstack\] unknown command: missing-stack/m, res.stderr);
74
+ });
@@ -0,0 +1,44 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { isAlive, setupStackStopSweepFixture, spawnOwnedSleep, waitForProcessAlive, waitForProcessExit } from './testkit/stack_stop_sweeps_testkit.mjs';
4
+
5
+ test('hstack stack stop sweeps legacy infra without HAPPIER_STACK_PROCESS_KIND=infra', async (t) => {
6
+ const fixture = await setupStackStopSweepFixture({
7
+ importMetaUrl: import.meta.url,
8
+ t,
9
+ tmpPrefix: 'hstack-stack-stop-sweep-legacy-',
10
+ });
11
+ const legacyInfra = fixture.trackChild(spawnOwnedSleep({
12
+ env: {
13
+ ...process.env,
14
+ HAPPIER_STACK_STACK: fixture.stackName,
15
+ HAPPIER_STACK_ENV_FILE: fixture.envPath,
16
+ // Simulate a yarn/npm-managed infra process from older stacks (no kind tag).
17
+ npm_lifecycle_event: 'dev:light',
18
+ npm_package_name: '@happier-dev/server',
19
+ },
20
+ }));
21
+ assert.ok(Number(legacyInfra.pid) > 1, 'expected legacy infra pid');
22
+ await waitForProcessAlive({ pid: legacyInfra.pid, timeoutMs: 2_000, intervalMs: 25, label: 'legacy infra process (pre-stop)' });
23
+ assert.ok(isAlive(legacyInfra.pid), 'expected legacy infra child to be alive');
24
+
25
+ const sessionLike = fixture.trackChild(spawnOwnedSleep({
26
+ env: {
27
+ ...process.env,
28
+ HAPPIER_STACK_STACK: fixture.stackName,
29
+ HAPPIER_STACK_ENV_FILE: fixture.envPath,
30
+ // Deliberately no npm_lifecycle_event and no process kind tag.
31
+ },
32
+ }));
33
+ assert.ok(Number(sessionLike.pid) > 1, 'expected session-like child pid');
34
+ await waitForProcessAlive({ pid: sessionLike.pid, timeoutMs: 2_000, intervalMs: 25, label: 'session-like process (pre-stop)' });
35
+ assert.ok(isAlive(sessionLike.pid), 'expected session-like child to be alive');
36
+
37
+ const res = await fixture.runStackStop(['--json']);
38
+ assert.equal(res.code, 0, `expected exit 0, got ${res.code}\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
39
+ assert.equal(res.signal, null, `expected process to exit normally, got signal ${res.signal}`);
40
+
41
+ await waitForProcessExit({ pid: legacyInfra.pid, timeoutMs: 10_000, intervalMs: 50, label: 'legacy infra process' });
42
+ assert.ok(!isAlive(legacyInfra.pid), `expected legacy infra pid ${legacyInfra.pid} to be stopped`);
43
+ assert.ok(isAlive(sessionLike.pid), `expected session-like pid ${sessionLike.pid} to still be alive`);
44
+ });