@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,98 @@
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 { join } from 'node:path';
6
+
7
+ import { resolveServerPortForPostAuthDaemonStart } from './utils/auth/orchestrated_stack_auth_flow.mjs';
8
+
9
+ test('resolveServerPortForPostAuthDaemonStart falls back to env HAPPIER_STACK_SERVER_PORT when runtime is missing it', async () => {
10
+ const tmp = await mkdtemp(join(tmpdir(), 'hstack-auth-flow-port-'));
11
+ const storageDir = join(tmp, 'storage');
12
+ const stackName = 'main';
13
+ const baseDir = join(storageDir, stackName);
14
+ await mkdir(baseDir, { recursive: true });
15
+
16
+ await writeFile(join(baseDir, 'stack.runtime.json'), JSON.stringify({ version: 1, stackName, ports: {} }) + '\n', 'utf-8');
17
+
18
+ const prev = process.env.HAPPIER_STACK_STORAGE_DIR;
19
+ process.env.HAPPIER_STACK_STORAGE_DIR = storageDir;
20
+ try {
21
+ const port = await resolveServerPortForPostAuthDaemonStart({
22
+ stackName,
23
+ env: { ...process.env, HAPPIER_STACK_SERVER_PORT: '4123' },
24
+ });
25
+ assert.equal(port, 4123);
26
+ } finally {
27
+ if (typeof prev === 'undefined') delete process.env.HAPPIER_STACK_STORAGE_DIR;
28
+ else process.env.HAPPIER_STACK_STORAGE_DIR = prev;
29
+ try {
30
+ await rm(tmp, { recursive: true, force: true });
31
+ } catch {
32
+ // ignore
33
+ }
34
+ }
35
+ });
36
+
37
+ test('resolveServerPortForPostAuthDaemonStart throws when runtime and env ports are both unusable', async () => {
38
+ const tmp = await mkdtemp(join(tmpdir(), 'hstack-auth-flow-port-invalid-'));
39
+ const storageDir = join(tmp, 'storage');
40
+ const stackName = 'main';
41
+ const baseDir = join(storageDir, stackName);
42
+ await mkdir(baseDir, { recursive: true });
43
+
44
+ await writeFile(join(baseDir, 'stack.runtime.json'), JSON.stringify({ version: 1, stackName, ports: {} }) + '\n', 'utf-8');
45
+
46
+ const prev = process.env.HAPPIER_STACK_STORAGE_DIR;
47
+ process.env.HAPPIER_STACK_STORAGE_DIR = storageDir;
48
+ try {
49
+ await assert.rejects(
50
+ () =>
51
+ resolveServerPortForPostAuthDaemonStart({
52
+ stackName,
53
+ env: { ...process.env, HAPPIER_STACK_SERVER_PORT: 'not-a-port' },
54
+ }),
55
+ /could not resolve server port/i
56
+ );
57
+ } finally {
58
+ if (typeof prev === 'undefined') delete process.env.HAPPIER_STACK_STORAGE_DIR;
59
+ else process.env.HAPPIER_STACK_STORAGE_DIR = prev;
60
+ try {
61
+ await rm(tmp, { recursive: true, force: true });
62
+ } catch {
63
+ // ignore
64
+ }
65
+ }
66
+ });
67
+
68
+ test('resolveServerPortForPostAuthDaemonStart ignores runtime port when runtime owner pid is stale', async () => {
69
+ const tmp = await mkdtemp(join(tmpdir(), 'hstack-auth-flow-port-stale-owner-'));
70
+ const storageDir = join(tmp, 'storage');
71
+ const stackName = 'main';
72
+ const baseDir = join(storageDir, stackName);
73
+ await mkdir(baseDir, { recursive: true });
74
+
75
+ await writeFile(
76
+ join(baseDir, 'stack.runtime.json'),
77
+ JSON.stringify({ version: 1, stackName, ownerPid: 999_999_999, ports: { server: 4555 } }) + '\n',
78
+ 'utf-8'
79
+ );
80
+
81
+ const prev = process.env.HAPPIER_STACK_STORAGE_DIR;
82
+ process.env.HAPPIER_STACK_STORAGE_DIR = storageDir;
83
+ try {
84
+ const port = await resolveServerPortForPostAuthDaemonStart({
85
+ stackName,
86
+ env: { ...process.env, HAPPIER_STACK_SERVER_PORT: '4222' },
87
+ });
88
+ assert.equal(port, 4222);
89
+ } finally {
90
+ if (typeof prev === 'undefined') delete process.env.HAPPIER_STACK_STORAGE_DIR;
91
+ else process.env.HAPPIER_STACK_STORAGE_DIR = prev;
92
+ try {
93
+ await rm(tmp, { recursive: true, force: true });
94
+ } catch {
95
+ // ignore
96
+ }
97
+ }
98
+ });
@@ -0,0 +1,119 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
4
+ import { spawn } from 'node:child_process';
5
+ import { tmpdir } from 'node:os';
6
+ import { dirname, join } from 'node:path';
7
+ import { fileURLToPath, pathToFileURL } 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', (d) => (stdout += String(d)));
15
+ proc.stderr.on('data', (d) => (stderr += String(d)));
16
+ proc.on('error', reject);
17
+ proc.on('exit', (code, signal) => resolve({ code: code ?? (signal ? 1 : 0), signal, stdout, stderr }));
18
+ });
19
+ }
20
+
21
+ function toDataUrl(source) {
22
+ return `data:text/javascript,${encodeURIComponent(source)}`;
23
+ }
24
+
25
+ test('startDaemonPostAuth passes webapp URL to daemon env (never internal API URL)', async () => {
26
+ const scriptsDir = dirname(fileURLToPath(import.meta.url));
27
+ const rootDir = dirname(scriptsDir);
28
+
29
+ const tmp = await mkdtemp(join(tmpdir(), 'hstack-auth-flow-webapp-url-'));
30
+ try {
31
+ const stackName = 'main';
32
+ const storageDir = join(tmp, 'storage');
33
+ const stackDir = join(storageDir, stackName);
34
+ const envPath = join(stackDir, 'env');
35
+ const markerPath = join(tmp, 'daemon-input.json');
36
+
37
+ await mkdir(stackDir, { recursive: true });
38
+ await writeFile(
39
+ envPath,
40
+ ['HAPPIER_STACK_STACK=main', 'HAPPIER_WEBAPP_URL=http://localhost:3010', ''].join('\n'),
41
+ 'utf-8'
42
+ );
43
+ await writeFile(
44
+ join(stackDir, 'stack.runtime.json'),
45
+ JSON.stringify({ version: 1, stackName, ownerPid: process.pid, ports: { server: 4102 } }, null, 2),
46
+ 'utf-8'
47
+ );
48
+
49
+ const loaderPath = join(tmp, 'loader.mjs');
50
+ const registerPath = join(tmp, 'register-loader.mjs');
51
+ const runnerPath = join(tmp, 'runner.mjs');
52
+
53
+ const stubBySpecifier = {
54
+ '../../daemon.mjs': toDataUrl(`
55
+ import { writeFileSync } from 'node:fs';
56
+ export async function startLocalDaemonWithAuth(input) {
57
+ const marker = process.env.HSTACK_AUTH_FLOW_DAEMON_MARKER;
58
+ if (marker) writeFileSync(marker, JSON.stringify(input), 'utf-8');
59
+ }
60
+ export function checkDaemonState() {
61
+ return { status: 'running', pid: 12345 };
62
+ }
63
+ `),
64
+ };
65
+
66
+ await writeFile(
67
+ loaderPath,
68
+ `
69
+ const stubBySpecifier = ${JSON.stringify(stubBySpecifier)};
70
+ export async function resolve(specifier, context, defaultResolve) {
71
+ const stub = stubBySpecifier[specifier];
72
+ if (stub) return { url: stub, shortCircuit: true };
73
+ return defaultResolve(specifier, context, defaultResolve);
74
+ }
75
+ `,
76
+ 'utf-8'
77
+ );
78
+ await writeFile(
79
+ registerPath,
80
+ `import { register } from 'node:module';
81
+ register(${JSON.stringify(pathToFileURL(loaderPath).href)}, import.meta.url);
82
+ `,
83
+ 'utf-8'
84
+ );
85
+
86
+ await writeFile(
87
+ runnerPath,
88
+ `
89
+ import { startDaemonPostAuth } from ${JSON.stringify(pathToFileURL(join(scriptsDir, 'utils', 'auth', 'orchestrated_stack_auth_flow.mjs')).href)};
90
+ await startDaemonPostAuth({
91
+ rootDir: ${JSON.stringify(rootDir)},
92
+ stackName: 'main',
93
+ env: process.env,
94
+ forceRestart: true,
95
+ });
96
+ `,
97
+ 'utf-8'
98
+ );
99
+
100
+ const env = {
101
+ ...process.env,
102
+ HAPPIER_STACK_STORAGE_DIR: storageDir,
103
+ HAPPIER_STACK_STACK: stackName,
104
+ HAPPIER_STACK_ENV_FILE: envPath,
105
+ HAPPIER_STACK_SERVER_PORT: '4102',
106
+ HSTACK_AUTH_FLOW_DAEMON_MARKER: markerPath,
107
+ };
108
+ const res = await runNode(['--import', registerPath, runnerPath], { cwd: rootDir, env });
109
+ assert.equal(res.code, 0, `expected exit 0, got ${res.code}\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
110
+
111
+ const markerRaw = await readFile(markerPath, 'utf-8');
112
+ const marker = JSON.parse(markerRaw);
113
+ assert.equal(marker.internalServerUrl, 'http://127.0.0.1:4102');
114
+ assert.equal(marker.publicServerUrl, 'http://localhost:3010');
115
+ assert.notEqual(marker.publicServerUrl, marker.internalServerUrl);
116
+ } finally {
117
+ await rm(tmp, { recursive: true, force: true });
118
+ }
119
+ });
@@ -0,0 +1,257 @@
1
+ import './utils/env/env.mjs';
2
+ import { tmpdir } from 'node:os';
3
+ import { basename, dirname, join, relative, resolve, sep } from 'node:path';
4
+ import { mkdtemp, readFile, rm, stat } from 'node:fs/promises';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { parseArgs } from './utils/cli/args.mjs';
7
+ import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
8
+ import { getComponentDir, getRootDir } from './utils/paths/paths.mjs';
9
+ import { runCapture } from './utils/proc/proc.mjs';
10
+ import { pathExists } from './utils/fs/fs.mjs';
11
+
12
+ const VALID_TARGETS = ['cli', 'server', 'ui'];
13
+
14
+ function targetFromLegacyComponent(component) {
15
+ const c = String(component ?? '').trim();
16
+ if (c === 'happy') return 'ui';
17
+ if (c === 'happy-cli') return 'cli';
18
+ if (c === 'happy-server' || c === 'happy-server-light') return 'server';
19
+ return null;
20
+ }
21
+
22
+ function legacyComponentFromTarget(target) {
23
+ const t = String(target ?? '').trim();
24
+ if (t === 'ui') return 'happy';
25
+ if (t === 'cli') return 'happy-cli';
26
+ if (t === 'server') return 'happy-server';
27
+ return null;
28
+ }
29
+
30
+ export async function findMonorepoRoot(startDir) {
31
+ let dir = startDir;
32
+ for (let i = 0; i < 12; i++) {
33
+ const pkg = join(dir, 'package.json');
34
+ const lock = join(dir, 'yarn.lock');
35
+ if ((await pathExists(pkg)) && (await pathExists(lock))) return dir;
36
+ const parent = dirname(dir);
37
+ if (parent === dir) break;
38
+ dir = parent;
39
+ }
40
+ return null;
41
+ }
42
+
43
+ async function readJson(path) {
44
+ return JSON.parse(await readFile(path, 'utf8'));
45
+ }
46
+
47
+ export async function resolvePackDirForComponent({ component, componentDir, explicitDir }) {
48
+ if (explicitDir) return explicitDir;
49
+
50
+ // In the monorepo, stacks often point the active repo dir at the monorepo root.
51
+ // For packing/publishing we want the actual workspace package dir.
52
+ const monorepoRoot = await findMonorepoRoot(componentDir);
53
+ if (monorepoRoot) {
54
+ try {
55
+ const rootPkg = await readJson(join(monorepoRoot, 'package.json'));
56
+ const name = String(rootPkg?.name ?? '').trim();
57
+ if (name === 'monorepo') {
58
+ if (component === 'happy-cli') return join(monorepoRoot, 'apps', 'cli');
59
+ if (component === 'happy-server') return join(monorepoRoot, 'apps', 'server');
60
+ if (component === 'happy') return join(monorepoRoot, 'apps', 'ui');
61
+ }
62
+ } catch {
63
+ // ignore
64
+ }
65
+ }
66
+
67
+ return componentDir;
68
+ }
69
+
70
+ async function copyDir(src, dest) {
71
+ // Node 22 supports recursive copy via `cp` in fs/promises, but this repo has its own fs utils;
72
+ // keep it simple using `cp -R` through the existing proc runner.
73
+ // Using external `cp` keeps this fast and avoids re-implementing copy logic.
74
+ await runCapture('cp', ['-R', src, dest], { cwd: '/', env: process.env });
75
+ }
76
+
77
+ async function createPackSandbox({ monorepoRoot, packageRelDir }) {
78
+ const sandboxRoot = await mkdtemp(join(tmpdir(), 'hstack-pack-'));
79
+
80
+ // Minimal monorepo layout needed for pack steps that reference workspace deps:
81
+ // - root package.json + yarn.lock (for repo root detection)
82
+ // - target package dir (e.g. apps/cli)
83
+ // - bundled deps sources (packages/*)
84
+ const filesToCopy = [
85
+ 'package.json',
86
+ 'yarn.lock',
87
+ ];
88
+ for (const f of filesToCopy) {
89
+ await copyDir(join(monorepoRoot, f), join(sandboxRoot, f));
90
+ }
91
+
92
+ const dirsToCopy = [
93
+ packageRelDir,
94
+ 'packages/agents',
95
+ 'packages/cli-common',
96
+ 'packages/protocol',
97
+ ];
98
+ for (const d of dirsToCopy) {
99
+ const src = join(monorepoRoot, d);
100
+ if (!(await pathExists(src))) {
101
+ throw new Error(`[pack] missing required directory for packing sandbox: ${src}`);
102
+ }
103
+ const destParent = dirname(join(sandboxRoot, d));
104
+ await runCapture('mkdir', ['-p', destParent], { cwd: '/', env: process.env });
105
+ await copyDir(src, join(sandboxRoot, d));
106
+ }
107
+
108
+ return sandboxRoot;
109
+ }
110
+
111
+ export function analyzeTarList(paths) {
112
+ const hasAgents = paths.some((p) => p.startsWith('package/node_modules/@happier-dev/agents/'));
113
+ const hasCliCommon = paths.some((p) => p.startsWith('package/node_modules/@happier-dev/cli-common/'));
114
+ const hasProtocol = paths.some((p) => p.startsWith('package/node_modules/@happier-dev/protocol/'));
115
+ return { hasAgents, hasCliCommon, hasProtocol };
116
+ }
117
+
118
+ async function main() {
119
+ const argv = process.argv.slice(2);
120
+ const { flags, kv } = parseArgs(argv);
121
+ const json = wantsJson(argv, { flags });
122
+
123
+ if (wantsHelp(argv, { flags })) {
124
+ printResult({
125
+ json,
126
+ data: { targets: [...VALID_TARGETS, '--dir=/abs/path'], flags: ['--json'] },
127
+ text: [
128
+ '[pack] usage:',
129
+ ' hstack pack cli [--json]',
130
+ ' hstack pack server [--json]',
131
+ ' hstack pack ui [--json]',
132
+ ' hstack pack --dir=/abs/path/to/apps/cli [--json]',
133
+ '',
134
+ 'notes:',
135
+ '- packs in a temporary sandbox to avoid dirtying the worktree',
136
+ '- can validate bundledDependencies output by inspecting the generated tarball (best-effort)',
137
+ ].join('\n'),
138
+ });
139
+ return;
140
+ }
141
+
142
+ const positionals = argv.filter((a) => !a.startsWith('--'));
143
+ const explicitDir = (kv.get('--dir') ?? '').toString().trim();
144
+ const raw =
145
+ explicitDir
146
+ ? null
147
+ : positionals.length === 1
148
+ ? positionals[0]
149
+ : null;
150
+
151
+ if (!explicitDir && !raw) {
152
+ throw new Error('[pack] missing target (expected: hstack pack cli|server|ui | --dir=...)');
153
+ }
154
+
155
+ const target = raw
156
+ ? (VALID_TARGETS.includes(String(raw).trim().toLowerCase()) ? String(raw).trim().toLowerCase() : targetFromLegacyComponent(raw))
157
+ : null;
158
+ if (raw && !target) {
159
+ throw new Error(`[pack] unknown target: ${raw} (expected one of: ${VALID_TARGETS.join(', ')})`);
160
+ }
161
+
162
+ const rootDir = getRootDir(import.meta.url);
163
+ const component = target ? legacyComponentFromTarget(target) : null;
164
+ const componentDir = component ? getComponentDir(rootDir, component) : '';
165
+ const packDir = await resolvePackDirForComponent({
166
+ component: component ?? 'happy-cli',
167
+ componentDir,
168
+ explicitDir: explicitDir ? resolve(explicitDir) : null,
169
+ });
170
+
171
+ if (!(await pathExists(packDir))) {
172
+ throw new Error(`[pack] missing pack dir: ${packDir}`);
173
+ }
174
+ const st = await stat(packDir);
175
+ if (!st.isDirectory()) {
176
+ throw new Error(`[pack] pack dir is not a directory: ${packDir}`);
177
+ }
178
+
179
+ const monorepoRoot = await findMonorepoRoot(packDir);
180
+ if (!monorepoRoot) {
181
+ throw new Error(`[pack] could not locate monorepo root (package.json + yarn.lock) from: ${packDir}`);
182
+ }
183
+ const packageRelDir = relative(monorepoRoot, packDir).split(sep).join('/');
184
+ if (!(packageRelDir.startsWith('apps/') || packageRelDir.startsWith('packages/'))) {
185
+ throw new Error(`[pack] expected pack dir to be under monorepo apps/ or packages/: ${packDir}`);
186
+ }
187
+
188
+ const sandboxRoot = await createPackSandbox({ monorepoRoot, packageRelDir });
189
+ const sandboxPackDir = join(sandboxRoot, packageRelDir);
190
+
191
+ try {
192
+ // 1) dry run: helps catch issues without producing a tarball
193
+ const dryRunOut = await runCapture('npm', ['pack', '--dry-run'], { cwd: sandboxPackDir, env: process.env });
194
+
195
+ // 2) real pack: create tarball and inspect contents
196
+ const tarballNameRaw = (await runCapture('npm', ['pack'], { cwd: sandboxPackDir, env: process.env })).trim();
197
+ const tarballName = tarballNameRaw.split('\n').filter(Boolean).slice(-1)[0] ?? '';
198
+ if (!tarballName) {
199
+ throw new Error('[pack] npm pack did not produce a tarball name');
200
+ }
201
+ const tarballPath = join(sandboxPackDir, tarballName);
202
+ const tarListRaw = await runCapture('tar', ['-tf', tarballPath], { cwd: sandboxPackDir, env: process.env });
203
+ const tarPaths = tarListRaw.split('\n').map((l) => l.trim()).filter(Boolean);
204
+ const { hasAgents, hasCliCommon, hasProtocol } = analyzeTarList(tarPaths);
205
+
206
+ // Only enforce bundled deps for CLI by default; other packages may intentionally not bundle.
207
+ const shouldEnforceBundledDeps = target === 'cli';
208
+ const ok = shouldEnforceBundledDeps ? hasAgents && hasCliCommon && hasProtocol : true;
209
+ const data = {
210
+ ok,
211
+ packDir,
212
+ sandboxRoot,
213
+ tarball: { name: basename(tarballPath) },
214
+ bundled: { agents: hasAgents, cliCommon: hasCliCommon, protocol: hasProtocol },
215
+ enforcement: { bundledDeps: shouldEnforceBundledDeps },
216
+ dryRun: { ok: true, output: json ? undefined : dryRunOut },
217
+ };
218
+
219
+ if (json) {
220
+ printResult({ json, data });
221
+ return;
222
+ }
223
+
224
+ const lines = [
225
+ `[pack] dir: ${packDir}`,
226
+ `[pack] tarball: ${basename(tarballPath)} (generated in a temp sandbox)`,
227
+ `[pack] bundledDependencies (best-effort):`,
228
+ `- @happier-dev/agents: ${hasAgents ? '✅ present' : shouldEnforceBundledDeps ? '❌ missing' : '↪ not required'}`,
229
+ `- @happier-dev/cli-common: ${hasCliCommon ? '✅ present' : shouldEnforceBundledDeps ? '❌ missing' : '↪ not required'}`,
230
+ `- @happier-dev/protocol: ${hasProtocol ? '✅ present' : shouldEnforceBundledDeps ? '❌ missing' : '↪ not required'}`,
231
+ ];
232
+ if (!ok) {
233
+ lines.push('', '[pack] NOTE: missing bundled deps in tarball; publish would likely break for npm consumers.');
234
+ }
235
+ // eslint-disable-next-line no-console
236
+ console.log(lines.join('\n'));
237
+ } finally {
238
+ // Always clean the sandbox to avoid filling tmp.
239
+ await rm(sandboxRoot, { recursive: true, force: true });
240
+ }
241
+ }
242
+
243
+ const invokedAsMain = (() => {
244
+ const argv1 = process.argv[1];
245
+ if (!argv1) return false;
246
+ // `argv[1]` can be a relative path.
247
+ return resolve(argv1) === resolve(fileURLToPath(import.meta.url));
248
+ })();
249
+
250
+ if (invokedAsMain) {
251
+ main().catch((error) => {
252
+ const msg = error instanceof Error ? error.message : String(error);
253
+ // eslint-disable-next-line no-console
254
+ console.error(msg);
255
+ process.exit(1);
256
+ });
257
+ }
@@ -0,0 +1,68 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtemp, writeFile, mkdir, rm } from 'node:fs/promises';
4
+ import { join, resolve } from 'node:path';
5
+ import { tmpdir } from 'node:os';
6
+ import { analyzeTarList, findMonorepoRoot, resolvePackDirForComponent } from './pack.mjs';
7
+
8
+ test('analyzeTarList detects bundled workspace deps in tar listing', () => {
9
+ const { hasAgents, hasCliCommon, hasProtocol } = analyzeTarList([
10
+ 'package/dist/index.mjs',
11
+ 'package/node_modules/@happier-dev/agents/package.json',
12
+ 'package/node_modules/@happier-dev/agents/dist/index.js',
13
+ 'package/node_modules/@happier-dev/cli-common/package.json',
14
+ 'package/node_modules/@happier-dev/protocol/package.json',
15
+ ]);
16
+ assert.equal(hasAgents, true);
17
+ assert.equal(hasCliCommon, true);
18
+ assert.equal(hasProtocol, true);
19
+ });
20
+
21
+ test('findMonorepoRoot finds nearest package.json + yarn.lock', async () => {
22
+ const root = await mkdtemp(join(tmpdir(), 'pack-test-'));
23
+ try {
24
+ await writeFile(join(root, 'package.json'), JSON.stringify({ name: 'monorepo' }));
25
+ await writeFile(join(root, 'yarn.lock'), '# lock');
26
+ await mkdir(join(root, 'packages', 'happy-cli'), { recursive: true });
27
+
28
+ const nested = join(root, 'packages', 'happy-cli');
29
+ const found = await findMonorepoRoot(nested);
30
+ assert.equal(found, root);
31
+ } finally {
32
+ await rm(root, { recursive: true, force: true });
33
+ }
34
+ });
35
+
36
+ test('resolvePackDirForComponent maps monorepo root to apps/cli', async () => {
37
+ const root = await mkdtemp(join(tmpdir(), 'pack-test-'));
38
+ try {
39
+ await writeFile(join(root, 'package.json'), JSON.stringify({ name: 'monorepo' }));
40
+ await writeFile(join(root, 'yarn.lock'), '# lock');
41
+ await mkdir(join(root, 'apps', 'cli'), { recursive: true });
42
+
43
+ const resolved = await resolvePackDirForComponent({
44
+ component: 'happy-cli',
45
+ componentDir: root,
46
+ explicitDir: null,
47
+ });
48
+ assert.equal(resolve(resolved), resolve(join(root, 'apps', 'cli')));
49
+ } finally {
50
+ await rm(root, { recursive: true, force: true });
51
+ }
52
+ });
53
+
54
+ test('resolvePackDirForComponent prefers explicitDir override', async () => {
55
+ const root = await mkdtemp(join(tmpdir(), 'pack-test-explicit-'));
56
+ try {
57
+ const explicit = join(root, 'custom-pack-dir');
58
+ await mkdir(explicit, { recursive: true });
59
+ const resolved = await resolvePackDirForComponent({
60
+ component: 'happy-cli',
61
+ componentDir: root,
62
+ explicitDir: explicit,
63
+ });
64
+ assert.equal(resolve(resolved), resolve(explicit));
65
+ } finally {
66
+ await rm(root, { recursive: true, force: true });
67
+ }
68
+ });
@@ -0,0 +1,152 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { spawn } from 'node:child_process';
4
+ import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
5
+ import { tmpdir } from 'node:os';
6
+ import { dirname, join } from 'node:path';
7
+ import { acquirePgliteDirLock } from './utils/pglite_lock.mjs';
8
+
9
+ function lockPathForDbDir(dbDir) {
10
+ return join(dirname(dbDir), '.happier.pglite.lock');
11
+ }
12
+
13
+ function sleep(ms) {
14
+ return new Promise((resolve) => setTimeout(resolve, ms));
15
+ }
16
+
17
+ function isPidAlive(pid) {
18
+ try {
19
+ process.kill(pid, 0);
20
+ return true;
21
+ } catch {
22
+ return false;
23
+ }
24
+ }
25
+
26
+ async function waitForPidExit(pid, { timeoutMs = 5_000, pollMs = 25 } = {}) {
27
+ const deadline = Date.now() + timeoutMs;
28
+ while (Date.now() < deadline) {
29
+ if (!isPidAlive(pid)) return true;
30
+ // eslint-disable-next-line no-await-in-loop
31
+ await sleep(pollMs);
32
+ }
33
+ return !isPidAlive(pid);
34
+ }
35
+
36
+ async function terminateChildProcessAndWait(child, { timeoutMs = 5_000 } = {}) {
37
+ const pid = Number(child?.pid);
38
+ if (!Number.isFinite(pid) || pid <= 1) return;
39
+ if (!isPidAlive(pid)) return;
40
+
41
+ try {
42
+ child.kill('SIGTERM');
43
+ } catch {
44
+ // ignore
45
+ }
46
+ if (await waitForPidExit(pid, { timeoutMs: Math.floor(timeoutMs / 2), pollMs: 25 })) return;
47
+
48
+ try {
49
+ child.kill('SIGKILL');
50
+ } catch {
51
+ // ignore
52
+ }
53
+ const exited = await waitForPidExit(pid, { timeoutMs: Math.floor(timeoutMs / 2), pollMs: 25 });
54
+ assert.equal(exited, true, `expected child pid ${pid} to exit after termination`);
55
+ }
56
+
57
+ test('acquirePgliteDirLock creates and releases lock', async (t) => {
58
+ const base = await mkdtemp(join(tmpdir(), 'happier-pglite-lock-'));
59
+ t.after(async () => {
60
+ await rm(base, { recursive: true, force: true });
61
+ });
62
+ const dbDir = join(base, 'pglite');
63
+ const lockPath = lockPathForDbDir(dbDir);
64
+
65
+ const release = await acquirePgliteDirLock(dbDir, { purpose: 'test' });
66
+ const raw = await readFile(lockPath, 'utf-8');
67
+ const json = JSON.parse(raw);
68
+ assert.equal(json.pid, process.pid);
69
+ assert.equal(json.purpose, 'test');
70
+ assert.equal(json.psEnvLine, undefined);
71
+ assert.ok(typeof json.pidStartTime === 'string' && json.pidStartTime.length > 0);
72
+
73
+ await release();
74
+ await assert.rejects(() => readFile(lockPath, 'utf-8'), /no such file|ENOENT/i);
75
+ });
76
+
77
+ test('acquirePgliteDirLock replaces stale lock (dead pid)', async (t) => {
78
+ const base = await mkdtemp(join(tmpdir(), 'happier-pglite-lock-stale-'));
79
+ t.after(async () => {
80
+ await rm(base, { recursive: true, force: true });
81
+ });
82
+ const dbDir = join(base, 'pglite');
83
+ const lockPath = lockPathForDbDir(dbDir);
84
+
85
+ await writeFile(
86
+ lockPath,
87
+ JSON.stringify({ pid: 999999, createdAt: new Date().toISOString(), purpose: 'stale', dbDir }) + '\n',
88
+ 'utf-8'
89
+ );
90
+
91
+ const release = await acquirePgliteDirLock(dbDir, { purpose: 'fresh' });
92
+ const raw = await readFile(lockPath, 'utf-8');
93
+ const json = JSON.parse(raw);
94
+ assert.equal(json.pid, process.pid);
95
+ assert.equal(json.purpose, 'fresh');
96
+ await release();
97
+ });
98
+
99
+ test('acquirePgliteDirLock fails closed when lock pid is alive', async (t) => {
100
+ const base = await mkdtemp(join(tmpdir(), 'happier-pglite-lock-live-'));
101
+ t.after(async () => {
102
+ await rm(base, { recursive: true, force: true });
103
+ });
104
+ const dbDir = join(base, 'pglite');
105
+ const lockPath = lockPathForDbDir(dbDir);
106
+
107
+ const child = spawn(process.execPath, ['-e', 'setInterval(() => {}, 1000)'], { stdio: 'ignore' });
108
+ assert.ok(child.pid && child.pid > 1);
109
+
110
+ try {
111
+ await writeFile(
112
+ lockPath,
113
+ JSON.stringify({ pid: child.pid, createdAt: new Date().toISOString(), purpose: 'live', dbDir }) + '\n',
114
+ 'utf-8'
115
+ );
116
+
117
+ await assert.rejects(() => acquirePgliteDirLock(dbDir, { purpose: 'should-fail' }), /in use by pid=/i);
118
+ } finally {
119
+ await terminateChildProcessAndWait(child);
120
+ }
121
+ });
122
+
123
+ test('acquirePgliteDirLock replaces lock when pid is alive but the start-time fingerprint mismatches', async (t) => {
124
+ const base = await mkdtemp(join(tmpdir(), 'happier-pglite-lock-mismatch-'));
125
+ t.after(async () => {
126
+ await rm(base, { recursive: true, force: true });
127
+ });
128
+ const dbDir = join(base, 'pglite');
129
+ const lockPath = lockPathForDbDir(dbDir);
130
+
131
+ const originalCreatedAt = '2000-01-01T00:00:00.000Z';
132
+ await writeFile(
133
+ lockPath,
134
+ JSON.stringify({
135
+ pid: process.pid,
136
+ createdAt: originalCreatedAt,
137
+ purpose: 'mismatch',
138
+ dbDir,
139
+ pidStartTime: 'Mon Jan 01 00:00:00 1990',
140
+ }) + '\n',
141
+ 'utf-8'
142
+ );
143
+
144
+ const release = await acquirePgliteDirLock(dbDir, { purpose: 'fresh' });
145
+ const raw = await readFile(lockPath, 'utf-8');
146
+ const json = JSON.parse(raw);
147
+ assert.equal(json.pid, process.pid);
148
+ assert.equal(json.purpose, 'fresh');
149
+ assert.notEqual(json.createdAt, originalCreatedAt);
150
+
151
+ await release();
152
+ });