@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,88 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { unlink } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+
5
+ import { resolveStackEnvPath } from '../paths/paths.mjs';
6
+ import { readJsonIfExists, writeJsonAtomic } from '../fs/json.mjs';
7
+ import { isPidAlive } from '../proc/pids.mjs';
8
+
9
+ export { isPidAlive };
10
+
11
+ export function getStackRuntimeStatePath(stackName) {
12
+ const { baseDir } = resolveStackEnvPath(stackName);
13
+ return join(baseDir, 'stack.runtime.json');
14
+ }
15
+
16
+ export async function readStackRuntimeStateFile(statePath) {
17
+ const parsed = await readJsonIfExists(statePath, { defaultValue: null });
18
+ return parsed && typeof parsed === 'object' ? parsed : null;
19
+ }
20
+
21
+ export async function writeStackRuntimeStateFile(statePath, state) {
22
+ if (!statePath) {
23
+ throw new Error('[stack] missing runtime state path');
24
+ }
25
+ await writeJsonAtomic(statePath, state);
26
+ }
27
+
28
+ function isPlainObject(v) {
29
+ return Boolean(v) && typeof v === 'object' && !Array.isArray(v);
30
+ }
31
+
32
+ function deepMerge(a, b) {
33
+ if (!isPlainObject(a) || !isPlainObject(b)) {
34
+ return b;
35
+ }
36
+ const out = { ...a };
37
+ for (const [k, v] of Object.entries(b)) {
38
+ if (isPlainObject(out[k]) && isPlainObject(v)) {
39
+ out[k] = deepMerge(out[k], v);
40
+ } else {
41
+ out[k] = v;
42
+ }
43
+ }
44
+ return out;
45
+ }
46
+
47
+ export async function updateStackRuntimeStateFile(statePath, patch) {
48
+ const existing = (await readStackRuntimeStateFile(statePath)) ?? {};
49
+ const next = deepMerge(existing, patch ?? {});
50
+ await writeStackRuntimeStateFile(statePath, next);
51
+ return next;
52
+ }
53
+
54
+ export async function recordStackRuntimeStart(statePath, { stackName, script, ephemeral, ownerPid, ports, ...rest } = {}) {
55
+ const now = new Date().toISOString();
56
+ const existing = (await readStackRuntimeStateFile(statePath)) ?? {};
57
+ const startedAt = typeof existing.startedAt === 'string' && existing.startedAt.trim() ? existing.startedAt : now;
58
+ const next = deepMerge(existing, {
59
+ version: 1,
60
+ stackName,
61
+ script,
62
+ ephemeral: Boolean(ephemeral),
63
+ ownerPid,
64
+ ports: ports ?? {},
65
+ startedAt,
66
+ updatedAt: now,
67
+ ...(rest ?? {}),
68
+ });
69
+ await writeStackRuntimeStateFile(statePath, next);
70
+ return next;
71
+ }
72
+
73
+ export async function recordStackRuntimeUpdate(statePath, patch = {}) {
74
+ return await updateStackRuntimeStateFile(statePath, {
75
+ ...(patch ?? {}),
76
+ updatedAt: new Date().toISOString(),
77
+ });
78
+ }
79
+
80
+ export async function deleteStackRuntimeStateFile(statePath) {
81
+ try {
82
+ if (!statePath || !existsSync(statePath)) return;
83
+ await unlink(statePath);
84
+ } catch {
85
+ // ignore
86
+ }
87
+ }
88
+
@@ -0,0 +1,40 @@
1
+ import { readdir } from 'node:fs/promises';
2
+ import { existsSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+
5
+ import { getStacksStorageRoot } from '../paths/paths.mjs';
6
+ import { resolveStackEnvPath } from '../paths/paths.mjs';
7
+
8
+ export async function listAllStackNames() {
9
+ const names = new Set(['main']);
10
+ const roots = [
11
+ getStacksStorageRoot(),
12
+ ];
13
+
14
+ for (const root of roots) {
15
+ let entries = [];
16
+ try {
17
+ // eslint-disable-next-line no-await-in-loop
18
+ entries = await readdir(root, { withFileTypes: true });
19
+ } catch {
20
+ entries = [];
21
+ }
22
+ for (const ent of entries) {
23
+ if (!ent.isDirectory()) continue;
24
+ const name = ent.name;
25
+ if (!name || name.startsWith('.')) continue;
26
+ const envPath = join(root, name, 'env');
27
+ if (existsSync(envPath)) {
28
+ names.add(name);
29
+ }
30
+ }
31
+ }
32
+
33
+ return Array.from(names).sort();
34
+ }
35
+
36
+ export function stackExistsSync(stackName) {
37
+ const name = String(stackName ?? '').trim() || 'main';
38
+ if (name === 'main') return true;
39
+ return existsSync(resolveStackEnvPath(name).envPath);
40
+ }
@@ -0,0 +1,370 @@
1
+ import { run, runCapture } from '../proc/proc.mjs';
2
+ import { ensureDepsInstalled, pmExecBin } from '../proc/pm.mjs';
3
+ import { isSandboxed, sandboxAllowsGlobalSideEffects } from '../env/sandbox.mjs';
4
+ import { existsSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+ import { mkdir } from 'node:fs/promises';
7
+ import { resolvePrismaClientImportForDbProvider, resolvePrismaClientImportForServerComponent } from '../server/flavor_scripts.mjs';
8
+ import { findAnyCredentialPathInCliHome } from '../auth/credentials_paths.mjs';
9
+
10
+ function looksLikeMissingTableError(msg) {
11
+ const s = String(msg ?? '').toLowerCase();
12
+ return s.includes('does not exist') || s.includes('no such table');
13
+ }
14
+
15
+ function firstNonEmptyEnv(...values) {
16
+ for (const value of values) {
17
+ const normalized = String(value ?? '').trim();
18
+ if (normalized) return normalized;
19
+ }
20
+ return '';
21
+ }
22
+
23
+ function resolveLightDbProviderFromEnv(env) {
24
+ const raw = (env?.HAPPIER_DB_PROVIDER ?? env?.HAPPY_DB_PROVIDER ?? '').toString().trim().toLowerCase();
25
+ if (raw === 'pglite') return 'pglite';
26
+ return 'sqlite';
27
+ }
28
+
29
+ async function probeAccountCount({ serverComponentName, serverDir, env, lightDbProvider = 'sqlite' }) {
30
+ const probe =
31
+ serverComponentName === 'happier-server-light' && lightDbProvider === 'pglite'
32
+ ? `
33
+ let db;
34
+ let pglite;
35
+ let server;
36
+ try {
37
+ const { PGlite } = await import('@electric-sql/pglite');
38
+ const { PGLiteSocketServer } = await import('@electric-sql/pglite-socket');
39
+ const { PrismaClient } = await import('@prisma/client');
40
+ const dbDirPrimary = (process.env.HAPPIER_SERVER_LIGHT_DB_DIR ?? '').toString().trim();
41
+ const dbDirLegacy = (process.env.HAPPY_SERVER_LIGHT_DB_DIR ?? '').toString().trim();
42
+ const dbDir = dbDirPrimary || dbDirLegacy;
43
+ if (!dbDir) throw new Error('Missing HAPPIER_SERVER_LIGHT_DB_DIR or HAPPY_SERVER_LIGHT_DB_DIR for pglite probe');
44
+ pglite = new PGlite(dbDir);
45
+ await pglite.waitReady;
46
+ server = new PGLiteSocketServer({ db: pglite, host: '127.0.0.1', port: 0 });
47
+ await server.start();
48
+ const raw = server.getServerConn();
49
+ const url = (() => {
50
+ try {
51
+ return new URL(raw);
52
+ } catch {
53
+ return new URL(\`postgresql://postgres@\${raw}/postgres?sslmode=disable\`);
54
+ }
55
+ })();
56
+ url.searchParams.set('connection_limit', '1');
57
+ process.env.DATABASE_URL = url.toString();
58
+ db = new PrismaClient();
59
+ const accountCount = await db.account.count();
60
+ console.log(JSON.stringify({ accountCount }));
61
+ } catch (e) {
62
+ console.log(
63
+ JSON.stringify({
64
+ error: {
65
+ name: e?.name,
66
+ message: e?.message,
67
+ code: e?.code,
68
+ },
69
+ })
70
+ );
71
+ } finally {
72
+ try {
73
+ await db?.$disconnect();
74
+ } catch {
75
+ // ignore
76
+ }
77
+ try {
78
+ await server?.stop();
79
+ } catch {}
80
+ try {
81
+ await pglite?.close();
82
+ } catch {}
83
+ }
84
+ `.trim()
85
+ : serverComponentName === 'happier-server-light'
86
+ ? `
87
+ let db;
88
+ try {
89
+ const { PrismaClient } = await import(${JSON.stringify(
90
+ resolvePrismaClientImportForDbProvider({ serverDir, provider: 'sqlite' })
91
+ )});
92
+ const dataDirPrimary = (process.env.HAPPIER_SERVER_LIGHT_DATA_DIR ?? '').toString().trim();
93
+ const dataDirLegacy = (process.env.HAPPY_SERVER_LIGHT_DATA_DIR ?? '').toString().trim();
94
+ const dataDir = dataDirPrimary || dataDirLegacy;
95
+ const fromEnv = (process.env.DATABASE_URL ?? '').toString().trim();
96
+ const url = fromEnv || (dataDir ? \`file:\${dataDir}/happier-server-light.sqlite\` : '');
97
+ if (!url) throw new Error('Missing DATABASE_URL and HAPPIER_SERVER_LIGHT_DATA_DIR or HAPPY_SERVER_LIGHT_DATA_DIR for sqlite probe');
98
+ process.env.DATABASE_URL = url;
99
+ db = new PrismaClient();
100
+ const accountCount = await db.account.count();
101
+ console.log(JSON.stringify({ accountCount }));
102
+ } catch (e) {
103
+ console.log(
104
+ JSON.stringify({
105
+ error: {
106
+ name: e?.name,
107
+ message: e?.message,
108
+ code: e?.code,
109
+ },
110
+ })
111
+ );
112
+ } finally {
113
+ try {
114
+ await db?.$disconnect();
115
+ } catch {
116
+ // ignore
117
+ }
118
+ }
119
+ `.trim()
120
+ : `
121
+ let db;
122
+ try {
123
+ const { PrismaClient } = await import(${JSON.stringify(
124
+ resolvePrismaClientImportForServerComponent({ serverComponentName, serverDir })
125
+ )});
126
+ db = new PrismaClient();
127
+ const accountCount = await db.account.count();
128
+ console.log(JSON.stringify({ accountCount }));
129
+ } catch (e) {
130
+ console.log(
131
+ JSON.stringify({
132
+ error: {
133
+ name: e?.name,
134
+ message: e?.message,
135
+ code: e?.code,
136
+ },
137
+ })
138
+ );
139
+ } finally {
140
+ try {
141
+ await db?.$disconnect();
142
+ } catch {
143
+ // ignore
144
+ }
145
+ }
146
+ `.trim();
147
+
148
+ const out = await runCapture(process.execPath, ['--input-type=module', '-e', probe], { cwd: serverDir, env, timeoutMs: 15_000 });
149
+ const parsed = out.trim() ? JSON.parse(out.trim()) : {};
150
+ if (parsed?.error) {
151
+ const e = new Error(parsed.error.message || 'unknown prisma probe error');
152
+ if (typeof parsed.error.name === 'string' && parsed.error.name) e.name = parsed.error.name;
153
+ if (typeof parsed.error.code === 'string' && parsed.error.code) e.code = parsed.error.code;
154
+ throw e;
155
+ }
156
+ return Number(parsed.accountCount ?? 0);
157
+ }
158
+
159
+ export function resolveAutoCopyFromMainEnabled({ env, stackName, isInteractive }) {
160
+ // Sandboxes should be isolated by default.
161
+ // Auto auth seeding can copy credentials/account rows from another stack (global state),
162
+ // which breaks isolation and can confuse guided auth flows (setup-pr/review-pr).
163
+ if (isSandboxed() && !sandboxAllowsGlobalSideEffects()) {
164
+ return false;
165
+ }
166
+ const raw = (env.HAPPIER_STACK_AUTO_AUTH_SEED ?? '').toString().trim();
167
+ if (raw) return raw !== '0';
168
+
169
+ if (stackName === 'main') return false;
170
+
171
+ // Default:
172
+ // - always auto-seed in non-interactive contexts (agents/services)
173
+ // - in interactive shells, auto-seed only when the user explicitly configured a non-main seed stack
174
+ // (this avoids silently spreading main identity for users who haven't opted in yet).
175
+ if (!isInteractive) return true;
176
+ const seed = (env.HAPPIER_STACK_AUTH_SEED_FROM ?? '').toString().trim();
177
+ return Boolean(seed && seed !== 'main');
178
+ }
179
+
180
+ export function resolveAuthSeedFromEnv(env) {
181
+ const seed = (env.HAPPIER_STACK_AUTH_SEED_FROM ?? '').toString().trim();
182
+ return seed || 'main';
183
+ }
184
+
185
+ export async function ensureServerLightSchemaReady({ serverDir, env, bestEffort = false }) {
186
+ await ensureDepsInstalled(serverDir, 'happier-server-light', { env });
187
+
188
+ const lightDbProvider = resolveLightDbProviderFromEnv(env);
189
+ const dataDir = firstNonEmptyEnv(env?.HAPPIER_SERVER_LIGHT_DATA_DIR, env?.HAPPY_SERVER_LIGHT_DATA_DIR);
190
+ const filesDir = firstNonEmptyEnv(env?.HAPPIER_SERVER_LIGHT_FILES_DIR, dataDir ? join(dataDir, 'files') : '');
191
+ const dbDir = firstNonEmptyEnv(env?.HAPPIER_SERVER_LIGHT_DB_DIR, env?.HAPPY_SERVER_LIGHT_DB_DIR, dataDir ? join(dataDir, 'pglite') : '');
192
+ if (dataDir) {
193
+ try {
194
+ await mkdir(dataDir, { recursive: true });
195
+ } catch {
196
+ // best-effort
197
+ }
198
+ }
199
+ if (filesDir) {
200
+ try {
201
+ await mkdir(filesDir, { recursive: true });
202
+ } catch {
203
+ // best-effort
204
+ }
205
+ }
206
+ if (dbDir) {
207
+ try {
208
+ await mkdir(dbDir, { recursive: true });
209
+ env.HAPPIER_SERVER_LIGHT_DB_DIR = env.HAPPIER_SERVER_LIGHT_DB_DIR ?? dbDir;
210
+ env.HAPPY_SERVER_LIGHT_DB_DIR = env.HAPPY_SERVER_LIGHT_DB_DIR ?? dbDir;
211
+ } catch {
212
+ // best-effort
213
+ }
214
+ }
215
+
216
+ if (
217
+ lightDbProvider === 'sqlite' &&
218
+ dataDir &&
219
+ !(env?.DATABASE_URL ?? '').toString().trim()
220
+ ) {
221
+ env.DATABASE_URL = `file:${join(dataDir, 'happier-server-light.sqlite')}`;
222
+ }
223
+
224
+ const probe = async () =>
225
+ await probeAccountCount({ serverComponentName: 'happier-server-light', serverDir, env, lightDbProvider });
226
+ // Apply provider-specific light migrations:
227
+ // - sqlite: prisma/sqlite/schema.prisma
228
+ // - pglite: embedded Postgres + pglite socket
229
+ //
230
+ // IMPORTANT:
231
+ // If the server is already running with pglite, it may hold the DB open (single-connection).
232
+ // When bestEffort=true (used for heuristics), skip migrations and only probe.
233
+ const migrateScript = lightDbProvider === 'pglite' ? 'migrate:light:deploy' : 'migrate:sqlite:deploy';
234
+ if (!bestEffort) {
235
+ await run('yarn', ['-s', migrateScript], { cwd: serverDir, env });
236
+ }
237
+
238
+ // 2) Probe account count (used for auth seeding heuristics).
239
+ try {
240
+ const accountCount = await probe();
241
+ return { ok: true, migrated: true, accountCount };
242
+ } catch (e) {
243
+ const msg = e instanceof Error ? e.message : String(e);
244
+ if (looksLikeMissingTableError(msg)) {
245
+ if (bestEffort) {
246
+ return { ok: false, migrated: true, accountCount: null, error: 'server-light schema not ready (missing tables)' };
247
+ }
248
+ throw new Error(`[server-light] schema not ready after ${migrateScript} (missing tables).`);
249
+ }
250
+ if (bestEffort) {
251
+ return { ok: false, migrated: true, accountCount: null, error: msg };
252
+ }
253
+ throw e;
254
+ }
255
+ }
256
+
257
+ export async function ensureHappyServerSchemaReady({ serverDir, env }) {
258
+ await ensureDepsInstalled(serverDir, 'happier-server', { env });
259
+
260
+ try {
261
+ const accountCount = await probeAccountCount({ serverComponentName: 'happier-server', serverDir, env });
262
+ return { ok: true, migrated: false, accountCount };
263
+ } catch (e) {
264
+ const msg = e instanceof Error ? e.message : String(e);
265
+ if (!looksLikeMissingTableError(msg)) {
266
+ throw e;
267
+ }
268
+ // If tables are missing, try migrations (safe for postgres). Then re-probe.
269
+ await pmExecBin({ dir: serverDir, bin: 'prisma', args: ['migrate', 'deploy'], env });
270
+ const accountCount = await probeAccountCount({ serverComponentName: 'happier-server', serverDir, env });
271
+ return { ok: true, migrated: true, accountCount };
272
+ }
273
+ }
274
+
275
+ export async function getAccountCountForServerComponent({ serverComponentName, serverDir, env, bestEffort = false }) {
276
+ if (serverComponentName === 'happier-server-light') {
277
+ try {
278
+ const ready = await ensureServerLightSchemaReady({ serverDir, env, bestEffort });
279
+ if (!ready?.ok) {
280
+ return { ok: false, accountCount: null, error: String(ready?.error ?? 'server-light schema probe failed') };
281
+ }
282
+ return { ok: true, accountCount: Number.isFinite(ready.accountCount) ? ready.accountCount : 0 };
283
+ } catch (e) {
284
+ if (!bestEffort) throw e;
285
+ return { ok: false, accountCount: null, error: e instanceof Error ? e.message : String(e) };
286
+ }
287
+ }
288
+ if (serverComponentName === 'happier-server') {
289
+ try {
290
+ const ready = await ensureHappyServerSchemaReady({ serverDir, env });
291
+ return { ok: true, accountCount: Number.isFinite(ready.accountCount) ? ready.accountCount : 0 };
292
+ } catch (e) {
293
+ if (!bestEffort) throw e;
294
+ return { ok: false, accountCount: null, error: e instanceof Error ? e.message : String(e) };
295
+ }
296
+ }
297
+ return { ok: false, accountCount: null, error: `unknown server component: ${serverComponentName}` };
298
+ }
299
+
300
+ export async function maybeAutoCopyAuthFromMainIfNeeded({
301
+ rootDir,
302
+ env,
303
+ enabled,
304
+ stackName,
305
+ cliHomeDir,
306
+ accountCount,
307
+ quiet = false,
308
+ authEnv = null,
309
+ }) {
310
+ const hasAccessKey = Boolean(findAnyCredentialPathInCliHome({ cliHomeDir }));
311
+
312
+ // "Initialized" heuristic:
313
+ // - if we have credentials AND (when known) at least one Account row, we don't need to seed from main.
314
+ const hasAccounts = typeof accountCount === 'number' ? accountCount > 0 : null;
315
+ const needsSeed = !hasAccessKey || hasAccounts === false;
316
+
317
+ if (!enabled || !needsSeed) {
318
+ return { ok: true, skipped: true, reason: !enabled ? 'disabled' : 'already_initialized' };
319
+ }
320
+
321
+ const reason = !hasAccessKey ? 'missing_credentials' : 'no_accounts';
322
+ const fromStackName = resolveAuthSeedFromEnv(env);
323
+ const linkAuth =
324
+ (env.HAPPIER_STACK_AUTH_LINK ?? '').toString().trim() === '1' ||
325
+ (env.HAPPIER_STACK_AUTH_MODE ?? '').toString().trim() === 'link';
326
+ if (!quiet) {
327
+ console.log(`[local] auth: auto seed from ${fromStackName} for ${stackName} (${reason})`);
328
+ }
329
+
330
+ // Best-effort: copy credentials/master secret + seed accounts from the configured seed stack.
331
+ // Keep this non-fatal; the daemon will emit actionable errors if it still can't authenticate.
332
+ try {
333
+ const out = await runCapture(
334
+ process.execPath,
335
+ [`${rootDir}/scripts/auth.mjs`, 'copy-from', fromStackName, '--json', ...(linkAuth ? ['--link'] : [])],
336
+ {
337
+ cwd: rootDir,
338
+ env: authEnv && typeof authEnv === 'object' ? authEnv : env,
339
+ }
340
+ );
341
+ return { ok: true, skipped: false, reason, out: out.trim() ? JSON.parse(out) : null };
342
+ } catch (e) {
343
+ return { ok: false, skipped: false, reason, error: e instanceof Error ? e.message : String(e) };
344
+ }
345
+ }
346
+
347
+ export async function prepareDaemonAuthSeedIfNeeded({
348
+ rootDir,
349
+ env,
350
+ stackName,
351
+ cliHomeDir,
352
+ startDaemon,
353
+ isInteractive,
354
+ accountCount,
355
+ quiet = false,
356
+ authEnv = null,
357
+ }) {
358
+ if (!startDaemon) return { ok: true, skipped: true, reason: 'no_daemon' };
359
+ const enabled = resolveAutoCopyFromMainEnabled({ env, stackName, isInteractive });
360
+ return await maybeAutoCopyAuthFromMainIfNeeded({
361
+ rootDir,
362
+ env,
363
+ enabled,
364
+ stackName,
365
+ cliHomeDir,
366
+ accountCount,
367
+ quiet,
368
+ authEnv,
369
+ });
370
+ }
@@ -0,0 +1,119 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { chmod, mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { existsSync } from 'node:fs';
7
+
8
+ import { ensureServerLightSchemaReady } from './startup.mjs';
9
+
10
+ async function writeJson(path, obj) {
11
+ await writeFile(path, JSON.stringify(obj, null, 2) + '\n', 'utf-8');
12
+ }
13
+
14
+ async function writeEsmPkg({ dir, name, body }) {
15
+ await mkdir(dir, { recursive: true });
16
+ await writeJson(join(dir, 'package.json'), { name, type: 'module', main: './index.js' });
17
+ await writeFile(join(dir, 'index.js'), body.trim() + '\n', 'utf-8');
18
+ }
19
+
20
+ async function writeServerLightProbeStubs(serverDir) {
21
+ const packages = [
22
+ {
23
+ path: join(serverDir, 'node_modules', '@electric-sql', 'pglite'),
24
+ name: '@electric-sql/pglite',
25
+ body: `
26
+ export class PGlite {
27
+ constructor(_dir) { this.waitReady = Promise.resolve(); }
28
+ async close() {}
29
+ }
30
+ `,
31
+ },
32
+ {
33
+ path: join(serverDir, 'node_modules', '@electric-sql', 'pglite-socket'),
34
+ name: '@electric-sql/pglite-socket',
35
+ body: `
36
+ export class PGLiteSocketServer {
37
+ constructor(_opts) {}
38
+ async start() {}
39
+ getServerConn() { return '127.0.0.1:54321'; }
40
+ async stop() {}
41
+ }
42
+ `,
43
+ },
44
+ {
45
+ path: join(serverDir, 'node_modules', '@prisma', 'client'),
46
+ name: '@prisma/client',
47
+ body: `
48
+ export class PrismaClient {
49
+ constructor() { this.account = { count: async () => 0 }; }
50
+ async $disconnect() {}
51
+ }
52
+ `,
53
+ },
54
+ ];
55
+
56
+ for (const pkg of packages) {
57
+ await writeEsmPkg({ dir: pkg.path, name: pkg.name, body: pkg.body });
58
+ }
59
+ }
60
+
61
+ async function writeYarnVersionShim(binDir) {
62
+ const yarnPath = join(binDir, 'yarn');
63
+ await mkdir(binDir, { recursive: true });
64
+ await writeFile(
65
+ yarnPath,
66
+ [
67
+ '#!/usr/bin/env node',
68
+ "const args = process.argv.slice(2);",
69
+ "if (args.includes('--version')) { console.log('1.22.22'); process.exit(0); }",
70
+ 'process.exit(0);',
71
+ ].join('\n') + '\n',
72
+ 'utf-8'
73
+ );
74
+ await chmod(yarnPath, 0o755);
75
+ }
76
+
77
+ async function createServerLightProbeFixture(root) {
78
+ const serverDir = join(root, 'server');
79
+ await mkdir(serverDir, { recursive: true });
80
+ await writeJson(join(serverDir, 'package.json'), { name: 'server', version: '0.0.0', type: 'module' });
81
+ await writeFile(join(serverDir, 'yarn.lock'), '# yarn\n', 'utf-8');
82
+ await mkdir(join(serverDir, 'node_modules'), { recursive: true });
83
+ await writeFile(join(serverDir, 'node_modules', '.yarn-integrity'), 'ok\n', 'utf-8');
84
+ await writeServerLightProbeStubs(serverDir);
85
+
86
+ const binDir = join(root, 'bin');
87
+ await writeYarnVersionShim(binDir);
88
+ return { serverDir, binDir };
89
+ }
90
+
91
+ test('ensureServerLightSchemaReady creates light data dirs before probing', async (t) => {
92
+ const root = await mkdtemp(join(tmpdir(), 'hs-startup-light-dirs-'));
93
+ t.after(async () => {
94
+ await rm(root, { recursive: true, force: true });
95
+ });
96
+
97
+ const { serverDir, binDir } = await createServerLightProbeFixture(root);
98
+
99
+ const dataDir = join(root, 'data');
100
+ const filesDir = join(dataDir, 'files');
101
+ const dbDir = join(dataDir, 'pglite');
102
+ const env = {
103
+ ...process.env,
104
+ PATH: `${binDir}:${process.env.PATH ?? ''}`,
105
+ HAPPIER_SERVER_LIGHT_DATA_DIR: dataDir,
106
+ HAPPIER_SERVER_LIGHT_FILES_DIR: filesDir,
107
+ HAPPIER_SERVER_LIGHT_DB_DIR: dbDir,
108
+ };
109
+
110
+ assert.equal(existsSync(dataDir), false);
111
+ assert.equal(existsSync(filesDir), false);
112
+ assert.equal(existsSync(dbDir), false);
113
+
114
+ await ensureServerLightSchemaReady({ serverDir, env });
115
+
116
+ assert.equal(existsSync(dataDir), true);
117
+ assert.equal(existsSync(filesDir), true);
118
+ assert.equal(existsSync(dbDir), true);
119
+ });
@@ -0,0 +1,20 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { existsSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+
6
+ import { ensureServerLightSchemaReady } from './startup.mjs';
7
+ import { buildServerLightEnv, createServerLightFixture } from './startup_server_light_testkit.mjs';
8
+
9
+ test('ensureServerLightSchemaReady runs migrate:sqlite:deploy by default when not best-effort', async (t) => {
10
+ const { binDir, markerPath, root, serverDir } = await createServerLightFixture(t, {
11
+ prefix: 'hs-startup-light-migrate-',
12
+ socketPort: 54322,
13
+ });
14
+ const env = buildServerLightEnv({ binDir, root });
15
+ const res = await ensureServerLightSchemaReady({ serverDir, env });
16
+ assert.equal(res.ok, true);
17
+ assert.equal(res.migrated, true);
18
+ assert.equal(res.accountCount, 0);
19
+ assert.equal(existsSync(markerPath), true, `expected migrate:sqlite:deploy to be invoked (${markerPath})`);
20
+ });