@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.
- package/README.md +501 -0
- package/bin/hstack.mjs +348 -0
- package/docs/codex-mcp-resume.md +129 -0
- package/docs/edison.md +74 -0
- package/docs/forking-and-branding.md +189 -0
- package/docs/happy-development.md +22 -0
- package/docs/isolated-linux-vm.md +243 -0
- package/docs/menubar.md +244 -0
- package/docs/mobile-ios.md +322 -0
- package/docs/monorepo-migration.md +20 -0
- package/docs/paths-and-env.md +154 -0
- package/docs/remote-access.md +43 -0
- package/docs/server-flavors.md +147 -0
- package/docs/stacks.md +330 -0
- package/docs/tauri.md +60 -0
- package/docs/worktrees-and-forks.md +133 -0
- package/extras/swiftbar/auth-login.sh +29 -0
- package/extras/swiftbar/git-cache-refresh.sh +122 -0
- package/extras/swiftbar/hstack-term.sh +133 -0
- package/extras/swiftbar/hstack.5s.sh +296 -0
- package/extras/swiftbar/hstack.sh +35 -0
- package/extras/swiftbar/icons/happy-green.png +0 -0
- package/extras/swiftbar/icons/happy-orange.png +0 -0
- package/extras/swiftbar/icons/happy-red.png +0 -0
- package/extras/swiftbar/icons/logo-white.png +0 -0
- package/extras/swiftbar/install.sh +265 -0
- package/extras/swiftbar/lib/git.sh +629 -0
- package/extras/swiftbar/lib/icons.sh +92 -0
- package/extras/swiftbar/lib/render.sh +999 -0
- package/extras/swiftbar/lib/system.sh +244 -0
- package/extras/swiftbar/lib/utils.sh +717 -0
- package/extras/swiftbar/set-interval.sh +65 -0
- package/extras/swiftbar/set-server-flavor.sh +61 -0
- package/extras/swiftbar/wt-pr.sh +140 -0
- package/node_modules/@happier-dev/cli-common/README.md +6 -0
- package/node_modules/@happier-dev/cli-common/dist/index.d.ts +4 -0
- package/node_modules/@happier-dev/cli-common/dist/index.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/index.js +4 -0
- package/node_modules/@happier-dev/cli-common/dist/index.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/links/index.d.ts +18 -0
- package/node_modules/@happier-dev/cli-common/dist/links/index.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/links/index.js +25 -0
- package/node_modules/@happier-dev/cli-common/dist/links/index.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/links.d.ts +2 -0
- package/node_modules/@happier-dev/cli-common/dist/links.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/links.js +2 -0
- package/node_modules/@happier-dev/cli-common/dist/links.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/update/index.d.ts +67 -0
- package/node_modules/@happier-dev/cli-common/dist/update/index.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/update/index.js +259 -0
- package/node_modules/@happier-dev/cli-common/dist/update/index.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/workspaces/index.d.ts +17 -0
- package/node_modules/@happier-dev/cli-common/dist/workspaces/index.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/workspaces/index.js +80 -0
- package/node_modules/@happier-dev/cli-common/dist/workspaces/index.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/package.json +26 -0
- package/package.json +77 -0
- package/scripts/auth.mjs +1829 -0
- package/scripts/auth_copy_from_pglite_lock_in_use.integration.test.mjs +90 -0
- package/scripts/auth_copy_from_runCapture.integration.test.mjs +447 -0
- package/scripts/auth_help_cmd.test.mjs +28 -0
- package/scripts/auth_login_flow_in_tty.test.mjs +100 -0
- package/scripts/auth_login_force_default.test.mjs +66 -0
- package/scripts/auth_login_guided_server_no_expo.test.mjs +126 -0
- package/scripts/auth_login_method_override.test.mjs +67 -0
- package/scripts/auth_login_print_includes_configure_links.test.mjs +99 -0
- package/scripts/auth_status_server_validation.integration.test.mjs +140 -0
- package/scripts/build.mjs +266 -0
- package/scripts/bundleWorkspaceDeps.mjs +38 -0
- package/scripts/bundleWorkspaceDeps.test.mjs +77 -0
- package/scripts/ci.mjs +135 -0
- package/scripts/ci.test.mjs +50 -0
- package/scripts/cli-link.mjs +57 -0
- package/scripts/completion.mjs +395 -0
- package/scripts/contrib.mjs +333 -0
- package/scripts/daemon.mjs +1160 -0
- package/scripts/daemon.status_scope.test.mjs +51 -0
- package/scripts/daemon_cmd.mjs +26 -0
- package/scripts/daemon_dist_guard.test.mjs +171 -0
- package/scripts/daemon_invalid_auth_reseed_stack_name.integration.test.mjs +608 -0
- package/scripts/daemon_server_scoped_state.test.mjs +49 -0
- package/scripts/daemon_start_verification.integration.test.mjs +296 -0
- package/scripts/dev.mjs +545 -0
- package/scripts/doctor.mjs +340 -0
- package/scripts/doctor_cmd.test.mjs +22 -0
- package/scripts/doctor_ui_index_missing.test.mjs +37 -0
- package/scripts/eas.mjs +367 -0
- package/scripts/eas_platform_parsing.test.mjs +63 -0
- package/scripts/edison.mjs +1848 -0
- package/scripts/env.mjs +149 -0
- package/scripts/env_cmd.test.mjs +118 -0
- package/scripts/exit_cleanup_kills_detached_children_on_crash.integration.test.mjs +80 -0
- package/scripts/happier.mjs +82 -0
- package/scripts/import.mjs +1327 -0
- package/scripts/init.mjs +464 -0
- package/scripts/install.mjs +550 -0
- package/scripts/lint.mjs +177 -0
- package/scripts/menubar.mjs +202 -0
- package/scripts/migrate.mjs +318 -0
- package/scripts/mobile.mjs +353 -0
- package/scripts/mobile_dev_client.mjs +87 -0
- package/scripts/monorepo.mjs +2234 -0
- package/scripts/monorepo_port.apply.integration.test.mjs +680 -0
- package/scripts/monorepo_port.conflicts.integration.test.mjs +454 -0
- package/scripts/monorepo_port.validation.integration.test.mjs +486 -0
- package/scripts/orchestrated_stack_auth_flow.test.mjs +134 -0
- package/scripts/orchestrated_stack_auth_flow_resolve_port.test.mjs +98 -0
- package/scripts/orchestrated_stack_auth_flow_webapp_url.test.mjs +119 -0
- package/scripts/pack.mjs +257 -0
- package/scripts/pack.test.mjs +68 -0
- package/scripts/pglite_lock.integration.test.mjs +152 -0
- package/scripts/provision/linux-ubuntu-e2e.sh +132 -0
- package/scripts/provision/linux-ubuntu-review-pr.sh +66 -0
- package/scripts/provision/macos-lima-happy-vm.sh +192 -0
- package/scripts/provision/macos-lima-hstack-e2e.sh +100 -0
- package/scripts/release.mjs +53 -0
- package/scripts/release_binary_smoke.integration.test.mjs +138 -0
- package/scripts/review.mjs +1752 -0
- package/scripts/review_pr.mjs +435 -0
- package/scripts/run.mjs +561 -0
- package/scripts/run_script_with_stack_env.restart_port_reuse.test.mjs +30 -0
- package/scripts/self.mjs +465 -0
- package/scripts/self_host.mjs +9 -0
- package/scripts/self_host_binary_smoke.integration.test.mjs +74 -0
- package/scripts/self_host_runtime.mjs +883 -0
- package/scripts/self_host_runtime.test.mjs +82 -0
- package/scripts/self_host_systemd.real.integration.test.mjs +367 -0
- package/scripts/server_flavor.mjs +148 -0
- package/scripts/service.mjs +868 -0
- package/scripts/service_mode_help.test.mjs +27 -0
- package/scripts/setup.mjs +1324 -0
- package/scripts/setup_non_interactive_flag.test.mjs +60 -0
- package/scripts/setup_pr.mjs +605 -0
- package/scripts/setup_pr_orchestrated_auth_flow_util_import.test.mjs +117 -0
- package/scripts/stack/command_arguments.mjs +91 -0
- package/scripts/stack/copy_auth_from_stack.mjs +111 -0
- package/scripts/stack/delegated_script_commands.mjs +92 -0
- package/scripts/stack/help_text.mjs +110 -0
- package/scripts/stack/port_reservation.mjs +74 -0
- package/scripts/stack/repo_checkout_resolution.mjs +31 -0
- package/scripts/stack/run_script_with_stack_env.mjs +634 -0
- package/scripts/stack/stack_daemon_command.mjs +219 -0
- package/scripts/stack/stack_delegated_help.mjs +81 -0
- package/scripts/stack/stack_environment.mjs +151 -0
- package/scripts/stack/stack_environment.sanitization.test.mjs +75 -0
- package/scripts/stack/stack_happier_passthrough_command.mjs +63 -0
- package/scripts/stack/stack_info_snapshot.mjs +167 -0
- package/scripts/stack/stack_mobile_install_command.mjs +61 -0
- package/scripts/stack/stack_resume_command.mjs +76 -0
- package/scripts/stack/stack_stop_command.mjs +34 -0
- package/scripts/stack/stack_workspace_command.mjs +83 -0
- package/scripts/stack/transient_repo_overrides.mjs +29 -0
- package/scripts/stack.mjs +2388 -0
- package/scripts/stack_archive_cmd.integration.test.mjs +31 -0
- package/scripts/stack_audit_fix_light_env.test.mjs +129 -0
- package/scripts/stack_background_pinned_stack_json.test.mjs +81 -0
- package/scripts/stack_copy_auth_server_scoped.test.mjs +243 -0
- package/scripts/stack_daemon_cmd.integration.test.mjs +484 -0
- package/scripts/stack_eas_help.test.mjs +72 -0
- package/scripts/stack_editor_workspace_monorepo_root.test.mjs +102 -0
- package/scripts/stack_env_cmd.test.mjs +107 -0
- package/scripts/stack_guided_login_bundle_error_parse.test.mjs +20 -0
- package/scripts/stack_guided_login_inner_invocation.test.mjs +46 -0
- package/scripts/stack_happy_cmd.integration.test.mjs +263 -0
- package/scripts/stack_info_snapshot_running_status.test.mjs +186 -0
- package/scripts/stack_interactive_monorepo_group.test.mjs +128 -0
- package/scripts/stack_monorepo_defaults.test.mjs +31 -0
- package/scripts/stack_monorepo_repo_dev_token.test.mjs +32 -0
- package/scripts/stack_monorepo_server_light_from_happy_spec.test.mjs +37 -0
- package/scripts/stack_new_name_normalize_cmd.test.mjs +38 -0
- package/scripts/stack_pr_name_normalize_cmd.test.mjs +84 -0
- package/scripts/stack_resume_cmd.integration.test.mjs +134 -0
- package/scripts/stack_server_flavors_defaults.test.mjs +64 -0
- package/scripts/stack_shorthand_cmd.integration.test.mjs +74 -0
- package/scripts/stack_stop_sweeps_legacy_infra_without_kind.integration.test.mjs +44 -0
- package/scripts/stack_stop_sweeps_when_runtime_missing.integration.test.mjs +42 -0
- package/scripts/stack_stop_sweeps_when_runtime_stale.integration.test.mjs +50 -0
- package/scripts/stack_wt_list.test.mjs +117 -0
- package/scripts/start_ui_required_default.test.mjs +63 -0
- package/scripts/stop.mjs +190 -0
- package/scripts/stopStackWithEnv_no_autosweep_when_runtime_missing.integration.test.mjs +95 -0
- package/scripts/swiftbar_git_monorepo_cmd.test.mjs +75 -0
- package/scripts/swiftbar_render_monorepo_wt_actions.integration.test.mjs +116 -0
- package/scripts/swiftbar_utils_cmd.test.mjs +92 -0
- package/scripts/swiftbar_wt_pr_backcompat.test.mjs +162 -0
- package/scripts/systemd_unit_info.test.mjs +24 -0
- package/scripts/tailscale.mjs +490 -0
- package/scripts/test_ci.mjs +36 -0
- package/scripts/test_cmd.mjs +274 -0
- package/scripts/test_cmd.test.mjs +133 -0
- package/scripts/test_integration.mjs +33 -0
- package/scripts/testkit/auth_testkit.mjs +121 -0
- package/scripts/testkit/doctor_testkit.mjs +68 -0
- package/scripts/testkit/monorepo_port_testkit.mjs +157 -0
- package/scripts/testkit/stack_archive_command_testkit.mjs +55 -0
- package/scripts/testkit/stack_new_monorepo_testkit.mjs +83 -0
- package/scripts/testkit/stack_script_command_testkit.mjs +27 -0
- package/scripts/testkit/stack_stop_sweeps_testkit.mjs +172 -0
- package/scripts/testkit/worktrees_monorepo_testkit.mjs +53 -0
- package/scripts/tools.mjs +70 -0
- package/scripts/tui.mjs +914 -0
- package/scripts/tui_stopStackForTuiExit_no_autosweep.integration.test.mjs +95 -0
- package/scripts/typecheck.mjs +178 -0
- package/scripts/ui_gateway.mjs +247 -0
- package/scripts/uninstall.mjs +179 -0
- package/scripts/utils/auth/credentials_paths.mjs +181 -0
- package/scripts/utils/auth/credentials_paths.test.mjs +187 -0
- package/scripts/utils/auth/daemon_gate.mjs +66 -0
- package/scripts/utils/auth/daemon_gate.test.mjs +116 -0
- package/scripts/utils/auth/decode_jwt_payload_unsafe.mjs +16 -0
- package/scripts/utils/auth/dev_key.mjs +163 -0
- package/scripts/utils/auth/files.mjs +56 -0
- package/scripts/utils/auth/guided_pr_auth.mjs +86 -0
- package/scripts/utils/auth/guided_stack_web_login.mjs +56 -0
- package/scripts/utils/auth/handy_master_secret.mjs +42 -0
- package/scripts/utils/auth/interactive_stack_auth.mjs +70 -0
- package/scripts/utils/auth/login_ux.mjs +105 -0
- package/scripts/utils/auth/orchestrated_stack_auth_flow.mjs +291 -0
- package/scripts/utils/auth/sources.mjs +28 -0
- package/scripts/utils/auth/stable_scope_id.mjs +91 -0
- package/scripts/utils/auth/stable_scope_id.test.mjs +51 -0
- package/scripts/utils/auth/stack_guided_login.mjs +438 -0
- package/scripts/utils/cli/arg_values.mjs +23 -0
- package/scripts/utils/cli/arg_values.test.mjs +43 -0
- package/scripts/utils/cli/args.mjs +17 -0
- package/scripts/utils/cli/cli.mjs +24 -0
- package/scripts/utils/cli/cli_registry.mjs +440 -0
- package/scripts/utils/cli/cwd_scope.mjs +158 -0
- package/scripts/utils/cli/cwd_scope.test.mjs +154 -0
- package/scripts/utils/cli/flags.mjs +17 -0
- package/scripts/utils/cli/log_forwarder.mjs +157 -0
- package/scripts/utils/cli/normalize.mjs +16 -0
- package/scripts/utils/cli/prereqs.mjs +103 -0
- package/scripts/utils/cli/prereqs.test.mjs +33 -0
- package/scripts/utils/cli/progress.mjs +141 -0
- package/scripts/utils/cli/smoke_help.mjs +44 -0
- package/scripts/utils/cli/verbosity.mjs +11 -0
- package/scripts/utils/cli/wizard.mjs +139 -0
- package/scripts/utils/cli/wizard_promptSelect.test.mjs +44 -0
- package/scripts/utils/cli/wizard_prompt_worktree_source_lazy.test.mjs +132 -0
- package/scripts/utils/cli/wizard_worktree_slug.test.mjs +33 -0
- package/scripts/utils/crypto/tokens.mjs +14 -0
- package/scripts/utils/dev/daemon.mjs +232 -0
- package/scripts/utils/dev/daemon_watch_resilience.test.mjs +224 -0
- package/scripts/utils/dev/expo_dev.buildEnv.test.mjs +35 -0
- package/scripts/utils/dev/expo_dev.mjs +478 -0
- package/scripts/utils/dev/expo_dev.test.mjs +89 -0
- package/scripts/utils/dev/expo_dev_restart_port_reservation.test.mjs +120 -0
- package/scripts/utils/dev/expo_dev_verbose_logs.test.mjs +60 -0
- package/scripts/utils/dev/server.mjs +180 -0
- package/scripts/utils/dev_auth_key.mjs +7 -0
- package/scripts/utils/edison/git_roots.mjs +30 -0
- package/scripts/utils/edison/git_roots.test.mjs +49 -0
- package/scripts/utils/env/config.mjs +52 -0
- package/scripts/utils/env/dotenv.mjs +32 -0
- package/scripts/utils/env/dotenv.test.mjs +32 -0
- package/scripts/utils/env/env.mjs +130 -0
- package/scripts/utils/env/env_file.mjs +98 -0
- package/scripts/utils/env/env_file.test.mjs +49 -0
- package/scripts/utils/env/env_local.mjs +25 -0
- package/scripts/utils/env/load_env_file.mjs +34 -0
- package/scripts/utils/env/read.mjs +30 -0
- package/scripts/utils/env/sandbox.mjs +13 -0
- package/scripts/utils/env/scrub_env.mjs +69 -0
- package/scripts/utils/env/scrub_env.test.mjs +102 -0
- package/scripts/utils/env/values.mjs +13 -0
- package/scripts/utils/expo/command.mjs +65 -0
- package/scripts/utils/expo/expo.mjs +139 -0
- package/scripts/utils/expo/expo_state_running.test.mjs +48 -0
- package/scripts/utils/expo/metro_ports.mjs +101 -0
- package/scripts/utils/expo/metro_ports.test.mjs +35 -0
- package/scripts/utils/fs/atomic_dir_swap.mjs +55 -0
- package/scripts/utils/fs/atomic_dir_swap.test.mjs +54 -0
- package/scripts/utils/fs/file_has_content.mjs +10 -0
- package/scripts/utils/fs/fs.mjs +11 -0
- package/scripts/utils/fs/json.mjs +25 -0
- package/scripts/utils/fs/ops.mjs +29 -0
- package/scripts/utils/fs/package_json.mjs +8 -0
- package/scripts/utils/fs/tail.mjs +12 -0
- package/scripts/utils/git/dev_checkout.mjs +127 -0
- package/scripts/utils/git/dev_checkout.test.mjs +115 -0
- package/scripts/utils/git/git.mjs +67 -0
- package/scripts/utils/git/parse_name_status_z.mjs +21 -0
- package/scripts/utils/git/refs.mjs +26 -0
- package/scripts/utils/git/worktrees.mjs +323 -0
- package/scripts/utils/git/worktrees_monorepo.test.mjs +60 -0
- package/scripts/utils/git/worktrees_pathstyle.test.mjs +53 -0
- package/scripts/utils/llm/assist.mjs +260 -0
- package/scripts/utils/llm/codex_exec.mjs +61 -0
- package/scripts/utils/llm/codex_exec.test.mjs +46 -0
- package/scripts/utils/llm/hstack_runner.mjs +59 -0
- package/scripts/utils/llm/tools.mjs +56 -0
- package/scripts/utils/llm/tools.test.mjs +67 -0
- package/scripts/utils/menubar/swiftbar.mjs +121 -0
- package/scripts/utils/menubar/swiftbar.test.mjs +85 -0
- package/scripts/utils/mobile/config.mjs +35 -0
- package/scripts/utils/mobile/dev_client_links.mjs +59 -0
- package/scripts/utils/mobile/identifiers.mjs +46 -0
- package/scripts/utils/mobile/identifiers.test.mjs +41 -0
- package/scripts/utils/mobile/ios_xcodeproj_patch.mjs +128 -0
- package/scripts/utils/mobile/ios_xcodeproj_patch.test.mjs +131 -0
- package/scripts/utils/net/bind_mode.mjs +39 -0
- package/scripts/utils/net/dns.mjs +10 -0
- package/scripts/utils/net/lan_ip.mjs +24 -0
- package/scripts/utils/net/ports.mjs +110 -0
- package/scripts/utils/net/tcp_forward.mjs +162 -0
- package/scripts/utils/net/url.mjs +30 -0
- package/scripts/utils/net/url.test.mjs +29 -0
- package/scripts/utils/paths/canonical_home.mjs +15 -0
- package/scripts/utils/paths/canonical_home.test.mjs +28 -0
- package/scripts/utils/paths/localhost_host.mjs +112 -0
- package/scripts/utils/paths/localhost_host.test.mjs +58 -0
- package/scripts/utils/paths/paths.mjs +302 -0
- package/scripts/utils/paths/paths_env_win32.test.mjs +36 -0
- package/scripts/utils/paths/paths_monorepo.test.mjs +58 -0
- package/scripts/utils/paths/paths_server_flavors.test.mjs +50 -0
- package/scripts/utils/paths/runtime.mjs +41 -0
- package/scripts/utils/pglite_lock.mjs +107 -0
- package/scripts/utils/proc/commands.mjs +33 -0
- package/scripts/utils/proc/exit_cleanup.mjs +57 -0
- package/scripts/utils/proc/happy_monorepo_deps.mjs +37 -0
- package/scripts/utils/proc/happy_monorepo_deps.test.mjs +89 -0
- package/scripts/utils/proc/ownership.mjs +217 -0
- package/scripts/utils/proc/ownership_killProcessGroupOwnedByStack.test.mjs +216 -0
- package/scripts/utils/proc/ownership_listPidsWithEnvNeedles.test.mjs +88 -0
- package/scripts/utils/proc/package_scripts.mjs +38 -0
- package/scripts/utils/proc/package_scripts.test.mjs +58 -0
- package/scripts/utils/proc/parallel.mjs +25 -0
- package/scripts/utils/proc/pids.mjs +11 -0
- package/scripts/utils/proc/pm.mjs +478 -0
- package/scripts/utils/proc/pm_spawn.integration.test.mjs +131 -0
- package/scripts/utils/proc/pm_stack_cache_env.test.mjs +313 -0
- package/scripts/utils/proc/proc.mjs +331 -0
- package/scripts/utils/proc/proc.test.mjs +85 -0
- package/scripts/utils/proc/terminate.mjs +69 -0
- package/scripts/utils/proc/terminate.test.mjs +54 -0
- package/scripts/utils/proc/watch.mjs +63 -0
- package/scripts/utils/review/augment_runner_integration.test.mjs +105 -0
- package/scripts/utils/review/base_ref.mjs +82 -0
- package/scripts/utils/review/base_ref.test.mjs +89 -0
- package/scripts/utils/review/chunks.mjs +55 -0
- package/scripts/utils/review/chunks.test.mjs +107 -0
- package/scripts/utils/review/detached_worktree.mjs +61 -0
- package/scripts/utils/review/detached_worktree.test.mjs +61 -0
- package/scripts/utils/review/findings.mjs +278 -0
- package/scripts/utils/review/findings.test.mjs +203 -0
- package/scripts/utils/review/head_slice.mjs +132 -0
- package/scripts/utils/review/head_slice.test.mjs +117 -0
- package/scripts/utils/review/instructions/deep.md +20 -0
- package/scripts/utils/review/prompts.mjs +279 -0
- package/scripts/utils/review/prompts.test.mjs +77 -0
- package/scripts/utils/review/run_reviewers_safe.mjs +12 -0
- package/scripts/utils/review/run_reviewers_safe.test.mjs +45 -0
- package/scripts/utils/review/runners/augment.mjs +91 -0
- package/scripts/utils/review/runners/augment.test.mjs +64 -0
- package/scripts/utils/review/runners/claude.mjs +92 -0
- package/scripts/utils/review/runners/claude.test.mjs +47 -0
- package/scripts/utils/review/runners/coderabbit.mjs +105 -0
- package/scripts/utils/review/runners/coderabbit.test.mjs +32 -0
- package/scripts/utils/review/runners/codex.mjs +129 -0
- package/scripts/utils/review/runners/codex.test.mjs +115 -0
- package/scripts/utils/review/slice_mode.mjs +20 -0
- package/scripts/utils/review/slice_mode.test.mjs +69 -0
- package/scripts/utils/review/sliced_runner.mjs +39 -0
- package/scripts/utils/review/sliced_runner.test.mjs +57 -0
- package/scripts/utils/review/slices.mjs +140 -0
- package/scripts/utils/review/slices.test.mjs +41 -0
- package/scripts/utils/review/targets.mjs +23 -0
- package/scripts/utils/review/targets.test.mjs +31 -0
- package/scripts/utils/review/tool_home_seed.mjs +106 -0
- package/scripts/utils/review/tool_home_seed.test.mjs +124 -0
- package/scripts/utils/review/uncommitted_ops.mjs +77 -0
- package/scripts/utils/review/uncommitted_ops.test.mjs +117 -0
- package/scripts/utils/sandbox/review_pr_sandbox.mjs +105 -0
- package/scripts/utils/server/apply_server_light_env_defaults.mjs +14 -0
- package/scripts/utils/server/flavor_scripts.mjs +138 -0
- package/scripts/utils/server/flavor_scripts.test.mjs +115 -0
- package/scripts/utils/server/infra/happy_server_infra.mjs +444 -0
- package/scripts/utils/server/mobile_api_url.mjs +60 -0
- package/scripts/utils/server/mobile_api_url.test.mjs +58 -0
- package/scripts/utils/server/port.mjs +55 -0
- package/scripts/utils/server/prisma_import.mjs +36 -0
- package/scripts/utils/server/prisma_import.test.mjs +78 -0
- package/scripts/utils/server/server.mjs +109 -0
- package/scripts/utils/server/ui_build_check.mjs +37 -0
- package/scripts/utils/server/ui_build_check.test.mjs +70 -0
- package/scripts/utils/server/ui_env.mjs +13 -0
- package/scripts/utils/server/ui_env.test.mjs +57 -0
- package/scripts/utils/server/urls.mjs +100 -0
- package/scripts/utils/server/validate.mjs +60 -0
- package/scripts/utils/server/validate.test.mjs +76 -0
- package/scripts/utils/service/autostart_darwin.mjs +198 -0
- package/scripts/utils/service/autostart_darwin.test.mjs +49 -0
- package/scripts/utils/service/autostart_darwin_keepalive.test.mjs +19 -0
- package/scripts/utils/stack/cli_identities.mjs +29 -0
- package/scripts/utils/stack/context.mjs +19 -0
- package/scripts/utils/stack/dirs.mjs +26 -0
- package/scripts/utils/stack/editor_workspace.mjs +126 -0
- package/scripts/utils/stack/interactive_stack_config.mjs +266 -0
- package/scripts/utils/stack/interactive_stack_config.port_validation.test.mjs +93 -0
- package/scripts/utils/stack/interactive_stack_config.remote_validation.test.mjs +122 -0
- package/scripts/utils/stack/interactive_stack_config.stack_name_validation.test.mjs +76 -0
- package/scripts/utils/stack/interactive_stack_config_testkit.mjs +18 -0
- package/scripts/utils/stack/names.mjs +27 -0
- package/scripts/utils/stack/names.test.mjs +26 -0
- package/scripts/utils/stack/pr_stack_name.mjs +16 -0
- package/scripts/utils/stack/runtime_state.mjs +88 -0
- package/scripts/utils/stack/stacks.mjs +40 -0
- package/scripts/utils/stack/startup.mjs +370 -0
- package/scripts/utils/stack/startup_server_light_dirs.test.mjs +119 -0
- package/scripts/utils/stack/startup_server_light_generate.test.mjs +20 -0
- package/scripts/utils/stack/startup_server_light_legacy.test.mjs +79 -0
- package/scripts/utils/stack/startup_server_light_testkit.mjs +106 -0
- package/scripts/utils/stack/stop.mjs +284 -0
- package/scripts/utils/stack_context.mjs +1 -0
- package/scripts/utils/stack_runtime_state.mjs +1 -0
- package/scripts/utils/stacks.mjs +1 -0
- package/scripts/utils/tailscale/ip.mjs +116 -0
- package/scripts/utils/tauri/stack_overrides.mjs +22 -0
- package/scripts/utils/test/collect_test_files.mjs +29 -0
- package/scripts/utils/time/get_today_ymd.mjs +7 -0
- package/scripts/utils/tui/cleanup.mjs +38 -0
- package/scripts/utils/ui/ansi.mjs +47 -0
- package/scripts/utils/ui/browser.mjs +31 -0
- package/scripts/utils/ui/browser.test.mjs +56 -0
- package/scripts/utils/ui/clipboard.mjs +38 -0
- package/scripts/utils/ui/layout.mjs +44 -0
- package/scripts/utils/ui/qr.mjs +17 -0
- package/scripts/utils/ui/terminal_launcher.mjs +129 -0
- package/scripts/utils/ui/text.mjs +16 -0
- package/scripts/utils/update/auto_update_notice.mjs +93 -0
- package/scripts/utils/validate.mjs +5 -0
- package/scripts/where.mjs +138 -0
- package/scripts/worktrees.mjs +2174 -0
- package/scripts/worktrees_archive_cmd.integration.test.mjs +228 -0
- package/scripts/worktrees_cursor_monorepo_root.test.mjs +23 -0
- package/scripts/worktrees_list_specs_no_recurse.test.mjs +32 -0
- package/scripts/worktrees_monorepo_testkit.test.mjs +29 -0
- package/scripts/worktrees_monorepo_use_group.test.mjs +41 -0
package/scripts/dev.mjs
ADDED
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
import './utils/env/env.mjs';
|
|
2
|
+
import { parseArgs } from './utils/cli/args.mjs';
|
|
3
|
+
import { killProcessTree } from './utils/proc/proc.mjs';
|
|
4
|
+
import { getComponentDir, getDefaultAutostartPaths, getRootDir } from './utils/paths/paths.mjs';
|
|
5
|
+
import { killPortListeners } from './utils/net/ports.mjs';
|
|
6
|
+
import { getServerComponentName, isHappierServerRunning } from './utils/server/server.mjs';
|
|
7
|
+
import { requireDir } from './utils/proc/pm.mjs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
10
|
+
import { isDaemonRunning, stopLocalDaemon } from './daemon.mjs';
|
|
11
|
+
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
12
|
+
import { assertServerComponentDirMatches, assertServerPrismaProviderMatches } from './utils/server/validate.mjs';
|
|
13
|
+
import { getExpoStatePaths, isStateProcessRunning } from './utils/expo/expo.mjs';
|
|
14
|
+
import { isPidAlive, readStackRuntimeStateFile, recordStackRuntimeStart } from './utils/stack/runtime_state.mjs';
|
|
15
|
+
import { resolveStackContext } from './utils/stack/context.mjs';
|
|
16
|
+
import { resolveServerPortFromEnv, resolveServerUrls } from './utils/server/urls.mjs';
|
|
17
|
+
import { ensureDevCliReady, prepareDaemonAuthSeed, startDevDaemon, watchHappyCliAndRestartDaemon } from './utils/dev/daemon.mjs';
|
|
18
|
+
import { startDevServer, watchDevServerAndRestart } from './utils/dev/server.mjs';
|
|
19
|
+
import { ensureDevExpoServer, resolveExpoTailscaleEnabled } from './utils/dev/expo_dev.mjs';
|
|
20
|
+
import { preferStackLocalhostUrl } from './utils/paths/localhost_host.mjs';
|
|
21
|
+
import { openUrlInBrowser } from './utils/ui/browser.mjs';
|
|
22
|
+
import { waitForHttpOk } from './utils/server/server.mjs';
|
|
23
|
+
import { sanitizeDnsLabel } from './utils/net/dns.mjs';
|
|
24
|
+
import { getAccountCountForServerComponent, resolveAutoCopyFromMainEnabled } from './utils/stack/startup.mjs';
|
|
25
|
+
import { maybeRunInteractiveStackAuthSetup } from './utils/auth/interactive_stack_auth.mjs';
|
|
26
|
+
import { getInvokedCwd, inferComponentFromCwd } from './utils/cli/cwd_scope.mjs';
|
|
27
|
+
import { daemonStartGate, formatDaemonAuthRequiredError } from './utils/auth/daemon_gate.mjs';
|
|
28
|
+
import { applyBindModeToEnv, resolveBindModeFromArgs } from './utils/net/bind_mode.mjs';
|
|
29
|
+
import { cmd, sectionTitle } from './utils/ui/layout.mjs';
|
|
30
|
+
import { cyan, dim, green } from './utils/ui/ansi.mjs';
|
|
31
|
+
import { isSandboxed } from './utils/env/sandbox.mjs';
|
|
32
|
+
import { installExitCleanup } from './utils/proc/exit_cleanup.mjs';
|
|
33
|
+
import { expandHome } from './utils/paths/canonical_home.mjs';
|
|
34
|
+
import { buildConfigureServerLinks } from '@happier-dev/cli-common/links';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Dev mode stack:
|
|
38
|
+
* - happier-server-light
|
|
39
|
+
* - happier-cli daemon
|
|
40
|
+
* - Expo web dev server (watch/reload)
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
async function main() {
|
|
44
|
+
const argv = process.argv.slice(2);
|
|
45
|
+
const { flags, kv } = parseArgs(argv);
|
|
46
|
+
const json = wantsJson(argv, { flags });
|
|
47
|
+
if (wantsHelp(argv, { flags })) {
|
|
48
|
+
printResult({
|
|
49
|
+
json,
|
|
50
|
+
data: {
|
|
51
|
+
flags: [
|
|
52
|
+
'--server=happier-server|happier-server-light',
|
|
53
|
+
'--server-flavor=light|full',
|
|
54
|
+
'--no-ui',
|
|
55
|
+
'--no-daemon',
|
|
56
|
+
'--restart',
|
|
57
|
+
'--watch',
|
|
58
|
+
'--no-watch',
|
|
59
|
+
'--no-browser',
|
|
60
|
+
'--mobile',
|
|
61
|
+
'--expo-tailscale',
|
|
62
|
+
'--bind=loopback|lan',
|
|
63
|
+
'--loopback',
|
|
64
|
+
'--lan',
|
|
65
|
+
],
|
|
66
|
+
json: true,
|
|
67
|
+
},
|
|
68
|
+
text: [
|
|
69
|
+
'[dev] usage:',
|
|
70
|
+
' hstack dev [--server=happier-server|happier-server-light] [--server-flavor=light|full] [--restart] [--json]',
|
|
71
|
+
' hstack dev --watch # rebuild/restart happier-cli daemon on file changes (TTY default)',
|
|
72
|
+
' hstack dev --no-watch # disable watch mode (always disabled in non-interactive mode)',
|
|
73
|
+
' hstack dev --no-browser # do not open the UI in your browser automatically',
|
|
74
|
+
' hstack dev --mobile # also start Expo dev-client Metro for mobile',
|
|
75
|
+
' hstack dev --expo-tailscale # forward Expo to Tailscale interface for remote access',
|
|
76
|
+
' hstack dev --bind=loopback # prefer localhost-only URLs (not reachable from phones)',
|
|
77
|
+
' note: --json prints the resolved config (dry-run) and exits.',
|
|
78
|
+
'',
|
|
79
|
+
'note:',
|
|
80
|
+
' If run from inside a repo checkout/worktree, that checkout is used for this run (without requiring `hstack wt use`).',
|
|
81
|
+
'',
|
|
82
|
+
'env:',
|
|
83
|
+
' HAPPIER_STACK_EXPO_TAILSCALE=1 # enable Expo Tailscale forwarding via env var',
|
|
84
|
+
].join('\n'),
|
|
85
|
+
});
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const rootDir = getRootDir(import.meta.url);
|
|
89
|
+
|
|
90
|
+
// Optional bind-mode override (affects Expo host/origins; best-effort sets HOST too).
|
|
91
|
+
const bindMode = resolveBindModeFromArgs({ flags, kv });
|
|
92
|
+
if (bindMode) {
|
|
93
|
+
applyBindModeToEnv(process.env, bindMode);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Outside sandbox mode we allow a convenience: if you run `hstack dev` from inside a repo checkout/worktree,
|
|
97
|
+
// we use that checkout even if you never ran `hstack wt use`.
|
|
98
|
+
//
|
|
99
|
+
// In sandbox mode this would break isolation by pointing at your "real" checkout, so we disable it.
|
|
100
|
+
if (!isSandboxed()) {
|
|
101
|
+
const inferred = inferComponentFromCwd({
|
|
102
|
+
rootDir,
|
|
103
|
+
invokedCwd: getInvokedCwd(process.env),
|
|
104
|
+
components: ['happier-ui', 'happier-cli', 'happier-server-light', 'happier-server'],
|
|
105
|
+
});
|
|
106
|
+
if (inferred) {
|
|
107
|
+
// Stack env should win. Only infer from CWD when the repo dir isn't already configured.
|
|
108
|
+
if (!(process.env.HAPPIER_STACK_REPO_DIR ?? '').toString().trim()) {
|
|
109
|
+
process.env.HAPPIER_STACK_REPO_DIR = inferred.repoDir;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Convenience alias: allow `--server-flavor=light|full` for parity with `stack pr` and `tools setup-pr`.
|
|
115
|
+
// `--server=...` always wins when both are specified.
|
|
116
|
+
const serverFlavorFromArg = (kv.get('--server-flavor') ?? '').trim().toLowerCase();
|
|
117
|
+
if (!kv.get('--server') && serverFlavorFromArg) {
|
|
118
|
+
if (serverFlavorFromArg === 'light') kv.set('--server', 'happier-server-light');
|
|
119
|
+
else if (serverFlavorFromArg === 'full') kv.set('--server', 'happier-server');
|
|
120
|
+
else throw new Error(`[dev] invalid --server-flavor=${serverFlavorFromArg} (expected: light|full)`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const serverComponentName = getServerComponentName({ kv });
|
|
124
|
+
if (serverComponentName === 'both') {
|
|
125
|
+
throw new Error(`[local] --server=both is not supported for dev (pick one: happier-server-light or happier-server)`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const startUi = !flags.has('--no-ui');
|
|
129
|
+
const startDaemon = !flags.has('--no-daemon');
|
|
130
|
+
const startMobile = flags.has('--mobile') || flags.has('--with-mobile');
|
|
131
|
+
const noBrowser = flags.has('--no-browser') || (process.env.HAPPIER_STACK_NO_BROWSER ?? '').toString().trim() === '1';
|
|
132
|
+
const expoTailscale = flags.has('--expo-tailscale') || resolveExpoTailscaleEnabled({ env: process.env });
|
|
133
|
+
|
|
134
|
+
const serverDir = getComponentDir(rootDir, serverComponentName);
|
|
135
|
+
const uiDir = getComponentDir(rootDir, 'happier-ui');
|
|
136
|
+
const cliDir = getComponentDir(rootDir, 'happier-cli');
|
|
137
|
+
|
|
138
|
+
const cliBin = join(cliDir, 'bin', 'happier.mjs');
|
|
139
|
+
const autostart = getDefaultAutostartPaths();
|
|
140
|
+
const baseEnv = { ...process.env };
|
|
141
|
+
const stackCtx = resolveStackContext({ env: baseEnv, autostart });
|
|
142
|
+
const { stackMode, runtimeStatePath, stackName, envPath, ephemeral } = stackCtx;
|
|
143
|
+
|
|
144
|
+
const serverPort = resolveServerPortFromEnv({ env: baseEnv, defaultPort: 3005 });
|
|
145
|
+
// IMPORTANT:
|
|
146
|
+
// - Only the main stack should ever auto-enable (or prefer) Tailscale Serve by default.
|
|
147
|
+
// - Non-main stacks should default to localhost URLs unless the user explicitly configured a public URL
|
|
148
|
+
// OR Tailscale Serve is already configured for this stack's internal URL (status matches).
|
|
149
|
+
const allowEnableTailscale =
|
|
150
|
+
!stackMode ||
|
|
151
|
+
stackName === 'main' ||
|
|
152
|
+
(baseEnv.HAPPIER_STACK_TAILSCALE_SERVE ?? '0').toString().trim() === '1';
|
|
153
|
+
const resolvedUrls = await resolveServerUrls({ env: baseEnv, serverPort, allowEnable: allowEnableTailscale });
|
|
154
|
+
const internalServerUrl = resolvedUrls.internalServerUrl;
|
|
155
|
+
let publicServerUrl = resolvedUrls.publicServerUrl;
|
|
156
|
+
if (stackMode && stackName !== 'main' && !resolvedUrls.envPublicUrl) {
|
|
157
|
+
const src = String(resolvedUrls.publicServerUrlSource ?? '');
|
|
158
|
+
const hasStackScopedTailscale = src.startsWith('tailscale-');
|
|
159
|
+
if (!hasStackScopedTailscale) {
|
|
160
|
+
publicServerUrl = resolvedUrls.defaultPublicUrl;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Expo app config: this is what both web + native app use to reach the Happy server.
|
|
164
|
+
// LAN rewrite (for dev-client) is centralized in ensureDevExpoServer.
|
|
165
|
+
const uiApiUrl = resolvedUrls.defaultPublicUrl;
|
|
166
|
+
const restart = flags.has('--restart');
|
|
167
|
+
const cliHomeDir = process.env.HAPPIER_STACK_CLI_HOME_DIR?.trim()
|
|
168
|
+
? expandHome(process.env.HAPPIER_STACK_CLI_HOME_DIR.trim())
|
|
169
|
+
: join(autostart.baseDir, 'cli');
|
|
170
|
+
|
|
171
|
+
if (json) {
|
|
172
|
+
printResult({
|
|
173
|
+
json,
|
|
174
|
+
data: {
|
|
175
|
+
mode: 'dev',
|
|
176
|
+
serverComponentName,
|
|
177
|
+
serverDir,
|
|
178
|
+
uiDir,
|
|
179
|
+
cliDir,
|
|
180
|
+
serverPort,
|
|
181
|
+
internalServerUrl,
|
|
182
|
+
publicServerUrl,
|
|
183
|
+
startUi,
|
|
184
|
+
startMobile,
|
|
185
|
+
startDaemon,
|
|
186
|
+
cliHomeDir,
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
assertServerComponentDirMatches({ rootDir, serverComponentName, serverDir });
|
|
193
|
+
assertServerPrismaProviderMatches({ serverComponentName, serverDir });
|
|
194
|
+
|
|
195
|
+
await requireDir(serverComponentName, serverDir);
|
|
196
|
+
await requireDir('happier-ui', uiDir);
|
|
197
|
+
await requireDir('happier-cli', cliDir);
|
|
198
|
+
|
|
199
|
+
const children = [];
|
|
200
|
+
let shuttingDown = false;
|
|
201
|
+
installExitCleanup({ label: 'local', children });
|
|
202
|
+
|
|
203
|
+
// Ensure happier-cli is install+build ready before starting the daemon.
|
|
204
|
+
// Worktrees often don't have dist/ built yet, which causes MODULE_NOT_FOUND on dist/index.mjs.
|
|
205
|
+
const buildCli = (baseEnv.HAPPIER_STACK_CLI_BUILD ?? '1').toString().trim() !== '0';
|
|
206
|
+
await ensureDevCliReady({ cliDir, buildCli });
|
|
207
|
+
|
|
208
|
+
// Watch mode (interactive only by default): rebuild happier-cli and restart daemon when code changes.
|
|
209
|
+
const watchEnabled =
|
|
210
|
+
flags.has('--watch') || (!flags.has('--no-watch') && Boolean(process.stdin.isTTY && process.stdout.isTTY));
|
|
211
|
+
const watchers = [];
|
|
212
|
+
|
|
213
|
+
const serverAlreadyRunning = await isHappierServerRunning(internalServerUrl);
|
|
214
|
+
const daemonAlreadyRunning = startDaemon ? isDaemonRunning(cliHomeDir) : false;
|
|
215
|
+
|
|
216
|
+
// Expo dev server state (worktree-scoped): single Expo process per stack/worktree.
|
|
217
|
+
const startExpo = startUi || startMobile;
|
|
218
|
+
const expoPaths = getExpoStatePaths({
|
|
219
|
+
baseDir: autostart.baseDir,
|
|
220
|
+
kind: 'expo-dev',
|
|
221
|
+
projectDir: uiDir,
|
|
222
|
+
stateFileName: 'expo.state.json',
|
|
223
|
+
});
|
|
224
|
+
const expoRunning = startExpo ? await isStateProcessRunning(expoPaths.statePath) : { running: false, state: null };
|
|
225
|
+
let expoAlreadyRunning = Boolean(expoRunning.running);
|
|
226
|
+
|
|
227
|
+
if (!restart && serverAlreadyRunning && (!startDaemon || daemonAlreadyRunning) && (!startExpo || expoAlreadyRunning)) {
|
|
228
|
+
console.log(
|
|
229
|
+
`${green('✓')} dev: already running ${dim('(')}` +
|
|
230
|
+
`${dim('server=')}${cyan(internalServerUrl)}` +
|
|
231
|
+
`${startDaemon ? ` ${dim('daemon=')}${daemonAlreadyRunning ? green('running') : dim('stopped')}` : ''}` +
|
|
232
|
+
`${startUi ? ` ${dim('ui=')}${expoAlreadyRunning ? green('running') : dim('stopped')}` : ''}` +
|
|
233
|
+
`${startMobile ? ` ${dim('mobile=')}${expoAlreadyRunning ? green('running') : dim('stopped')}` : ''}` +
|
|
234
|
+
`${dim(')')}`
|
|
235
|
+
);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (stackMode && runtimeStatePath) {
|
|
240
|
+
await recordStackRuntimeStart(runtimeStatePath, {
|
|
241
|
+
stackName,
|
|
242
|
+
script: 'dev.mjs',
|
|
243
|
+
ephemeral,
|
|
244
|
+
ownerPid: process.pid,
|
|
245
|
+
ports: { server: serverPort },
|
|
246
|
+
}).catch(() => {});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Start server (only if not already healthy)
|
|
250
|
+
// NOTE: In stack mode we avoid killing arbitrary port listeners (fail-closed instead).
|
|
251
|
+
if ((!serverAlreadyRunning || restart) && !stackMode) {
|
|
252
|
+
await killPortListeners(serverPort, { label: 'server' });
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const { serverEnv, serverScript, serverProc } = await startDevServer({
|
|
256
|
+
serverComponentName,
|
|
257
|
+
serverDir,
|
|
258
|
+
autostart,
|
|
259
|
+
baseEnv,
|
|
260
|
+
serverPort,
|
|
261
|
+
internalServerUrl,
|
|
262
|
+
publicServerUrl,
|
|
263
|
+
envPath,
|
|
264
|
+
stackMode,
|
|
265
|
+
runtimeStatePath,
|
|
266
|
+
serverAlreadyRunning,
|
|
267
|
+
restart,
|
|
268
|
+
children,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
if (!serverAlreadyRunning || restart) {
|
|
272
|
+
console.log(`${green('✓')} server: ready at ${cyan(internalServerUrl)}`);
|
|
273
|
+
} else {
|
|
274
|
+
console.log(`${green('✓')} server: already running at ${cyan(internalServerUrl)}`);
|
|
275
|
+
}
|
|
276
|
+
console.log('');
|
|
277
|
+
console.log(sectionTitle('Terminal usage'));
|
|
278
|
+
console.log(dim(`To run ${cyan('happier')} against this stack (and have sessions appear in the UI), export:`));
|
|
279
|
+
console.log(cmd(`export HAPPIER_SERVER_URL="${internalServerUrl}"`));
|
|
280
|
+
console.log(cmd(`export HAPPIER_HOME_DIR="${cliHomeDir}"`));
|
|
281
|
+
console.log(cmd(`export HAPPIER_WEBAPP_URL="${publicServerUrl}"`));
|
|
282
|
+
|
|
283
|
+
// Reliability before daemon start:
|
|
284
|
+
// - Ensure schema exists (server-light: prisma migrate deploy; happier-server: migrate deploy if tables missing)
|
|
285
|
+
// - Auto-seed from main only when needed (non-main + non-interactive default, and only if missing creds or 0 accounts)
|
|
286
|
+
const isInteractive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
287
|
+
const accountProbe = await getAccountCountForServerComponent({
|
|
288
|
+
serverComponentName,
|
|
289
|
+
serverDir,
|
|
290
|
+
env: serverEnv,
|
|
291
|
+
bestEffort: true,
|
|
292
|
+
});
|
|
293
|
+
const accountCount = typeof accountProbe.accountCount === 'number' ? accountProbe.accountCount : null;
|
|
294
|
+
const autoSeedEnabled = resolveAutoCopyFromMainEnabled({ env: baseEnv, stackName, isInteractive });
|
|
295
|
+
|
|
296
|
+
let expoResEarly = null;
|
|
297
|
+
const wantsAuthFlow =
|
|
298
|
+
(baseEnv.HAPPIER_STACK_AUTH_FLOW ?? '').toString().trim() === '1' ||
|
|
299
|
+
(baseEnv.HAPPIER_STACK_DAEMON_WAIT_FOR_AUTH ?? '').toString().trim() === '1';
|
|
300
|
+
|
|
301
|
+
// CRITICAL (review-pr / setup-pr guided login):
|
|
302
|
+
// In background/non-interactive runs, the daemon may block on auth. If we wait to start Expo web
|
|
303
|
+
// until after the daemon is authenticated, guided login will have no UI origin and will fall back
|
|
304
|
+
// to the server port (wrong). Start Expo web UI early when running an auth flow.
|
|
305
|
+
if (wantsAuthFlow && startUi && !expoResEarly) {
|
|
306
|
+
expoResEarly = await ensureDevExpoServer({
|
|
307
|
+
startUi,
|
|
308
|
+
startMobile,
|
|
309
|
+
uiDir,
|
|
310
|
+
autostart,
|
|
311
|
+
baseEnv,
|
|
312
|
+
apiServerUrl: uiApiUrl,
|
|
313
|
+
restart,
|
|
314
|
+
stackMode,
|
|
315
|
+
runtimeStatePath,
|
|
316
|
+
stackName,
|
|
317
|
+
envPath,
|
|
318
|
+
children,
|
|
319
|
+
spawnOptions: { stdio: ['ignore', 'ignore', 'ignore'] },
|
|
320
|
+
expoTailscale,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
await maybeRunInteractiveStackAuthSetup({
|
|
324
|
+
rootDir,
|
|
325
|
+
// In dev mode, guided login must target the Expo web UI origin (not the server port).
|
|
326
|
+
// Mark this as an auth-flow so URL resolution fails closed if Expo isn't ready.
|
|
327
|
+
env: startUi ? { ...baseEnv, HAPPIER_STACK_AUTH_FLOW: '1' } : baseEnv,
|
|
328
|
+
stackName,
|
|
329
|
+
cliHomeDir,
|
|
330
|
+
accountCount,
|
|
331
|
+
isInteractive,
|
|
332
|
+
autoSeedEnabled,
|
|
333
|
+
beforeLogin: async () => {
|
|
334
|
+
if (!startUi) {
|
|
335
|
+
throw new Error(
|
|
336
|
+
`[local] auth: interactive login requires the web UI.\n` +
|
|
337
|
+
`Re-run without --no-ui, or set HAPPIER_WEBAPP_URL to a reachable Happier UI for this stack.`
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
if (expoResEarly) return;
|
|
341
|
+
expoResEarly = await ensureDevExpoServer({
|
|
342
|
+
startUi,
|
|
343
|
+
startMobile,
|
|
344
|
+
uiDir,
|
|
345
|
+
autostart,
|
|
346
|
+
baseEnv,
|
|
347
|
+
apiServerUrl: uiApiUrl,
|
|
348
|
+
restart,
|
|
349
|
+
stackMode,
|
|
350
|
+
runtimeStatePath,
|
|
351
|
+
stackName,
|
|
352
|
+
envPath,
|
|
353
|
+
children,
|
|
354
|
+
expoTailscale,
|
|
355
|
+
});
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
await prepareDaemonAuthSeed({
|
|
359
|
+
rootDir,
|
|
360
|
+
env: baseEnv,
|
|
361
|
+
stackName,
|
|
362
|
+
cliHomeDir,
|
|
363
|
+
startDaemon,
|
|
364
|
+
isInteractive,
|
|
365
|
+
serverComponentName,
|
|
366
|
+
serverDir,
|
|
367
|
+
serverEnv,
|
|
368
|
+
quiet: false,
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
if (startDaemon) {
|
|
372
|
+
const gate = daemonStartGate({ env: baseEnv, cliHomeDir, serverUrl: internalServerUrl });
|
|
373
|
+
if (!gate.ok) {
|
|
374
|
+
// In orchestrated auth flows (setup-pr/review-pr), we intentionally keep server/UI up
|
|
375
|
+
// for guided login and start daemon post-auth from the orchestrator.
|
|
376
|
+
if (gate.reason === 'auth_flow_missing_credentials') {
|
|
377
|
+
console.log('[local] auth flow: skipping daemon start until credentials exist');
|
|
378
|
+
} else {
|
|
379
|
+
const isInteractive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
380
|
+
if (!isInteractive) {
|
|
381
|
+
throw new Error(formatDaemonAuthRequiredError({ stackName, cliHomeDir, serverUrl: internalServerUrl }));
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
await startDevDaemon({
|
|
386
|
+
startDaemon,
|
|
387
|
+
cliBin,
|
|
388
|
+
cliHomeDir,
|
|
389
|
+
internalServerUrl,
|
|
390
|
+
publicServerUrl,
|
|
391
|
+
restart,
|
|
392
|
+
isShuttingDown: () => shuttingDown,
|
|
393
|
+
env: baseEnv,
|
|
394
|
+
stackName,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const cliWatcher = watchHappyCliAndRestartDaemon({
|
|
400
|
+
enabled: watchEnabled,
|
|
401
|
+
startDaemon: startDaemon && daemonStartGate({ env: baseEnv, cliHomeDir, serverUrl: internalServerUrl }).ok,
|
|
402
|
+
buildCli,
|
|
403
|
+
cliDir,
|
|
404
|
+
cliBin,
|
|
405
|
+
cliHomeDir,
|
|
406
|
+
internalServerUrl,
|
|
407
|
+
publicServerUrl,
|
|
408
|
+
isShuttingDown: () => shuttingDown,
|
|
409
|
+
env: baseEnv,
|
|
410
|
+
stackName,
|
|
411
|
+
});
|
|
412
|
+
if (cliWatcher) watchers.push(cliWatcher);
|
|
413
|
+
|
|
414
|
+
const serverProcRef = { current: serverProc };
|
|
415
|
+
if (stackMode && runtimeStatePath && !serverProcRef.current?.pid) {
|
|
416
|
+
// If the server was already running when we started dev, `startDevServer` won't spawn a new process
|
|
417
|
+
// (and therefore we don't have a ChildProcess handle). For safe watch/restart we need a PID.
|
|
418
|
+
const state = await readStackRuntimeStateFile(runtimeStatePath);
|
|
419
|
+
const pid = state?.processes?.serverPid;
|
|
420
|
+
if (isPidAlive(pid)) {
|
|
421
|
+
serverProcRef.current = { pid: Number(pid), exitCode: null };
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
const serverWatcher = watchDevServerAndRestart({
|
|
425
|
+
enabled: watchEnabled && Boolean(serverProcRef.current?.pid),
|
|
426
|
+
stackMode,
|
|
427
|
+
serverComponentName,
|
|
428
|
+
serverDir,
|
|
429
|
+
serverPort,
|
|
430
|
+
internalServerUrl,
|
|
431
|
+
serverScript,
|
|
432
|
+
serverEnv,
|
|
433
|
+
runtimeStatePath,
|
|
434
|
+
stackName,
|
|
435
|
+
envPath,
|
|
436
|
+
children,
|
|
437
|
+
serverProcRef,
|
|
438
|
+
isShuttingDown: () => shuttingDown,
|
|
439
|
+
});
|
|
440
|
+
if (serverWatcher) watchers.push(serverWatcher);
|
|
441
|
+
if (watchEnabled && stackMode && serverComponentName === 'happier-server' && !serverWatcher) {
|
|
442
|
+
console.warn(
|
|
443
|
+
`[local] watch: server restart is disabled because the running server PID is unknown.\n` +
|
|
444
|
+
`[local] watch: fix: re-run with --restart so hstack can (re)spawn the server and track its PID.`
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const expoRes =
|
|
449
|
+
expoResEarly ??
|
|
450
|
+
(await ensureDevExpoServer({
|
|
451
|
+
startUi,
|
|
452
|
+
startMobile,
|
|
453
|
+
uiDir,
|
|
454
|
+
autostart,
|
|
455
|
+
baseEnv,
|
|
456
|
+
apiServerUrl: uiApiUrl,
|
|
457
|
+
restart,
|
|
458
|
+
stackMode,
|
|
459
|
+
runtimeStatePath,
|
|
460
|
+
stackName,
|
|
461
|
+
envPath,
|
|
462
|
+
children,
|
|
463
|
+
expoTailscale,
|
|
464
|
+
}));
|
|
465
|
+
if (startUi) {
|
|
466
|
+
const uiPort = expoRes?.port;
|
|
467
|
+
const uiUrlRaw = uiPort ? `http://localhost:${uiPort}` : '';
|
|
468
|
+
const uiUrl = uiUrlRaw ? await preferStackLocalhostUrl(uiUrlRaw, { stackName }) : '';
|
|
469
|
+
const uiOpenUrl = uiUrl
|
|
470
|
+
? buildConfigureServerLinks({ webappUrl: uiUrl, serverUrl: publicServerUrl }).webUrl
|
|
471
|
+
: '';
|
|
472
|
+
if (expoRes?.reason === 'already_running' && expoRes.port) {
|
|
473
|
+
console.log(`[local] ui already running (pid=${expoRes.pid}, port=${expoRes.port})`);
|
|
474
|
+
if (uiOpenUrl) console.log(`[local] ui: open ${uiOpenUrl}`);
|
|
475
|
+
} else if (expoRes?.skipped === false && expoRes.port) {
|
|
476
|
+
if (uiOpenUrl) console.log(`[local] ui: open ${uiOpenUrl}`);
|
|
477
|
+
} else if (expoRes?.skipped && expoRes?.reason === 'already_running') {
|
|
478
|
+
console.log('[local] ui already running (skipping Expo start)');
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const isInteractive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
482
|
+
const shouldOpen = isInteractive && !noBrowser && Boolean(expoRes?.port);
|
|
483
|
+
if (shouldOpen) {
|
|
484
|
+
// Prefer localhost for readiness checks (faster/more reliable), but open the stack-scoped hostname.
|
|
485
|
+
await waitForHttpOk(`http://localhost:${expoRes.port}`, { timeoutMs: 30_000 }).catch(() => {});
|
|
486
|
+
const res = await openUrlInBrowser(uiOpenUrl || uiUrl);
|
|
487
|
+
if (!res.ok) {
|
|
488
|
+
console.warn(`[local] ui: failed to open browser automatically (${res.error}).`);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (startMobile && expoRes?.port) {
|
|
494
|
+
const metroUrl = await preferStackLocalhostUrl(`http://localhost:${expoRes.port}`, { stackName });
|
|
495
|
+
console.log(`[local] mobile: metro ${metroUrl}`);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Show Tailscale URL if forwarder is running
|
|
499
|
+
if (expoRes?.tailscale?.ok && expoRes.tailscale.tailscaleIp && expoRes.port) {
|
|
500
|
+
console.log(`[local] expo tailscale: http://${expoRes.tailscale.tailscaleIp}:${expoRes.port}`);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const shutdown = async () => {
|
|
504
|
+
if (shuttingDown) {
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
shuttingDown = true;
|
|
508
|
+
console.log('\n[local] shutting down...');
|
|
509
|
+
|
|
510
|
+
for (const w of watchers) {
|
|
511
|
+
try {
|
|
512
|
+
w.close();
|
|
513
|
+
} catch {
|
|
514
|
+
// ignore
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (startDaemon) {
|
|
519
|
+
await stopLocalDaemon({ cliBin, internalServerUrl, cliHomeDir });
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
for (const child of children) {
|
|
523
|
+
if (child.exitCode == null) {
|
|
524
|
+
killProcessTree(child, 'SIGINT');
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
await delay(1500);
|
|
529
|
+
for (const child of children) {
|
|
530
|
+
if (child.exitCode == null) {
|
|
531
|
+
killProcessTree(child, 'SIGKILL');
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
process.on('SIGINT', () => shutdown().then(() => process.exit(0)));
|
|
537
|
+
process.on('SIGTERM', () => shutdown().then(() => process.exit(0)));
|
|
538
|
+
|
|
539
|
+
await new Promise(() => {});
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
main().catch((err) => {
|
|
543
|
+
console.error('[local] failed:', err);
|
|
544
|
+
process.exit(1);
|
|
545
|
+
});
|