@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
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { isAlive, setupStackStopSweepFixture, spawnOwnedSleep, waitForProcessAlive, waitForProcessExit } from './testkit/stack_stop_sweeps_testkit.mjs';
|
|
4
|
+
|
|
5
|
+
test('hstack stack stop sweeps owned processes when stack.runtime.json is missing', async (t) => {
|
|
6
|
+
const fixture = await setupStackStopSweepFixture({
|
|
7
|
+
importMetaUrl: import.meta.url,
|
|
8
|
+
t,
|
|
9
|
+
tmpPrefix: 'hstack-stack-stop-sweep-',
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const owned = fixture.trackChild(spawnOwnedSleep({
|
|
13
|
+
env: {
|
|
14
|
+
...process.env,
|
|
15
|
+
HAPPIER_STACK_STACK: fixture.stackName,
|
|
16
|
+
HAPPIER_STACK_ENV_FILE: fixture.envPath,
|
|
17
|
+
HAPPIER_STACK_PROCESS_KIND: 'infra',
|
|
18
|
+
},
|
|
19
|
+
}));
|
|
20
|
+
assert.ok(Number(owned.pid) > 1, 'expected child pid');
|
|
21
|
+
await waitForProcessAlive({ pid: owned.pid, timeoutMs: 2_000, intervalMs: 25, label: 'infra process (pre-stop)' });
|
|
22
|
+
assert.ok(isAlive(owned.pid), 'expected owned child to be alive');
|
|
23
|
+
|
|
24
|
+
const sessionLike = fixture.trackChild(spawnOwnedSleep({
|
|
25
|
+
env: {
|
|
26
|
+
...process.env,
|
|
27
|
+
HAPPIER_STACK_STACK: fixture.stackName,
|
|
28
|
+
HAPPIER_STACK_ENV_FILE: fixture.envPath,
|
|
29
|
+
HAPPIER_STACK_PROCESS_KIND: 'session',
|
|
30
|
+
},
|
|
31
|
+
}));
|
|
32
|
+
assert.ok(Number(sessionLike.pid) > 1, 'expected session-like child pid');
|
|
33
|
+
await waitForProcessAlive({ pid: sessionLike.pid, timeoutMs: 2_000, intervalMs: 25, label: 'session-like process (pre-stop)' });
|
|
34
|
+
assert.ok(isAlive(sessionLike.pid), 'expected session-like child to be alive');
|
|
35
|
+
|
|
36
|
+
const res = await fixture.runStackStop(['--json']);
|
|
37
|
+
assert.equal(res.code, 0, `expected exit 0, got ${res.code}\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
38
|
+
|
|
39
|
+
await waitForProcessExit({ pid: owned.pid, timeoutMs: 10_000, intervalMs: 50, label: 'infra process' });
|
|
40
|
+
assert.ok(!isAlive(owned.pid), `expected owned pid ${owned.pid} to be stopped`);
|
|
41
|
+
assert.ok(isAlive(sessionLike.pid), `expected session-like pid ${sessionLike.pid} to still be alive`);
|
|
42
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { isAlive, setupStackStopSweepFixture, spawnOwnedSleep, waitForProcessAlive, waitForProcessExit } from './testkit/stack_stop_sweeps_testkit.mjs';
|
|
4
|
+
import { writeFile } from 'node:fs/promises';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
test('hstack stack stop sweeps infra when stack.runtime.json exists but ownerPid is stale', async (t) => {
|
|
8
|
+
const fixture = await setupStackStopSweepFixture({
|
|
9
|
+
importMetaUrl: import.meta.url,
|
|
10
|
+
t,
|
|
11
|
+
tmpPrefix: 'hstack-stack-stop-sweep-stale-',
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
await writeFile(
|
|
15
|
+
join(fixture.baseDir, 'stack.runtime.json'),
|
|
16
|
+
JSON.stringify({ version: 1, stackName: fixture.stackName, ownerPid: 999999, processes: {} }),
|
|
17
|
+
'utf-8'
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const infra = fixture.trackChild(spawnOwnedSleep({
|
|
21
|
+
env: {
|
|
22
|
+
...process.env,
|
|
23
|
+
HAPPIER_STACK_STACK: fixture.stackName,
|
|
24
|
+
HAPPIER_STACK_ENV_FILE: fixture.envPath,
|
|
25
|
+
HAPPIER_STACK_PROCESS_KIND: 'infra',
|
|
26
|
+
},
|
|
27
|
+
}));
|
|
28
|
+
assert.ok(Number(infra.pid) > 1, 'expected infra pid');
|
|
29
|
+
await waitForProcessAlive({ pid: infra.pid, timeoutMs: 2_000, intervalMs: 25, label: 'infra process (pre-stop)' });
|
|
30
|
+
assert.ok(isAlive(infra.pid), 'expected infra child to be alive');
|
|
31
|
+
|
|
32
|
+
const sessionLike = fixture.trackChild(spawnOwnedSleep({
|
|
33
|
+
env: {
|
|
34
|
+
...process.env,
|
|
35
|
+
HAPPIER_STACK_STACK: fixture.stackName,
|
|
36
|
+
HAPPIER_STACK_ENV_FILE: fixture.envPath,
|
|
37
|
+
HAPPIER_STACK_PROCESS_KIND: 'session',
|
|
38
|
+
},
|
|
39
|
+
}));
|
|
40
|
+
assert.ok(Number(sessionLike.pid) > 1, 'expected session-like child pid');
|
|
41
|
+
await waitForProcessAlive({ pid: sessionLike.pid, timeoutMs: 2_000, intervalMs: 25, label: 'session-like process (pre-stop)' });
|
|
42
|
+
assert.ok(isAlive(sessionLike.pid), 'expected session-like child to be alive');
|
|
43
|
+
|
|
44
|
+
const res = await fixture.runStackStop(['--json']);
|
|
45
|
+
assert.equal(res.code, 0, `expected exit 0, got ${res.code}\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
46
|
+
|
|
47
|
+
await waitForProcessExit({ pid: infra.pid, timeoutMs: 10_000, intervalMs: 50, label: 'infra process (stale runtime)' });
|
|
48
|
+
assert.ok(!isAlive(infra.pid), `expected infra pid ${infra.pid} to be stopped`);
|
|
49
|
+
assert.ok(isAlive(sessionLike.pid), `expected session-like pid ${sessionLike.pid} to still be alive`);
|
|
50
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
import { runNodeCapture } from './testkit/stack_script_command_testkit.mjs';
|
|
9
|
+
|
|
10
|
+
async function touchWorktree(dir) {
|
|
11
|
+
await mkdir(dir, { recursive: true });
|
|
12
|
+
// In a git worktree, ".git" is often a file; our detection treats either file or dir as truthy.
|
|
13
|
+
await writeFile(join(dir, '.git'), 'gitdir: /dev/null\n', 'utf-8');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function setupStackWtListFixture({ importMetaUrl, t, tmpPrefix }) {
|
|
17
|
+
const scriptsDir = dirname(fileURLToPath(importMetaUrl));
|
|
18
|
+
const rootDir = dirname(scriptsDir);
|
|
19
|
+
const tmp = await mkdtemp(join(tmpdir(), tmpPrefix));
|
|
20
|
+
|
|
21
|
+
t.after(async () => {
|
|
22
|
+
await rm(tmp, { recursive: true, force: true }).catch(() => {});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const storageDir = join(tmp, 'storage');
|
|
26
|
+
const homeDir = join(tmp, 'home');
|
|
27
|
+
const workspaceDir = join(tmp, 'workspace');
|
|
28
|
+
const stackName = 'exp-test';
|
|
29
|
+
|
|
30
|
+
const wtRoot = join(workspaceDir, 'pr');
|
|
31
|
+
const monoActive = join(wtRoot, 'active-branch');
|
|
32
|
+
const monoOther = join(wtRoot, 'other-branch');
|
|
33
|
+
await touchWorktree(monoActive);
|
|
34
|
+
await touchWorktree(monoOther);
|
|
35
|
+
|
|
36
|
+
for (const monorepoRoot of [monoActive, monoOther]) {
|
|
37
|
+
await mkdir(join(monorepoRoot, 'apps', 'ui'), { recursive: true });
|
|
38
|
+
await mkdir(join(monorepoRoot, 'apps', 'cli'), { recursive: true });
|
|
39
|
+
await mkdir(join(monorepoRoot, 'apps', 'server'), { recursive: true });
|
|
40
|
+
await writeFile(join(monorepoRoot, 'apps', 'ui', 'package.json'), '{}\n', 'utf-8');
|
|
41
|
+
await writeFile(join(monorepoRoot, 'apps', 'cli', 'package.json'), '{}\n', 'utf-8');
|
|
42
|
+
await writeFile(join(monorepoRoot, 'apps', 'server', 'package.json'), '{}\n', 'utf-8');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const envDir = join(storageDir, stackName);
|
|
46
|
+
const envPath = join(envDir, 'env');
|
|
47
|
+
await mkdir(envDir, { recursive: true });
|
|
48
|
+
await writeFile(
|
|
49
|
+
envPath,
|
|
50
|
+
[
|
|
51
|
+
`HAPPIER_STACK_STACK=${stackName}`,
|
|
52
|
+
`HAPPIER_STACK_REPO_DIR=${monoActive}`,
|
|
53
|
+
'',
|
|
54
|
+
].join('\n'),
|
|
55
|
+
'utf-8'
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const baseEnv = {
|
|
59
|
+
...process.env,
|
|
60
|
+
// Prevent loading the user's real ~/.happier-stack/.env via canonical discovery.
|
|
61
|
+
HAPPIER_STACK_HOME_DIR: homeDir,
|
|
62
|
+
HAPPIER_STACK_STORAGE_DIR: storageDir,
|
|
63
|
+
HAPPIER_STACK_WORKSPACE_DIR: workspaceDir,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
async function runStackWtList(extraArgs = []) {
|
|
67
|
+
return await runNodeCapture([join(rootDir, 'scripts', 'stack.mjs'), 'wt', stackName, '--', 'list', ...extraArgs], {
|
|
68
|
+
cwd: rootDir,
|
|
69
|
+
env: baseEnv,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
monoActive,
|
|
75
|
+
monoOther,
|
|
76
|
+
runStackWtList,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
test('hstack stack wt <stack> -- list defaults to active-only (no exhaustive enumeration)', async (t) => {
|
|
81
|
+
const fixture = await setupStackWtListFixture({
|
|
82
|
+
importMetaUrl: import.meta.url,
|
|
83
|
+
t,
|
|
84
|
+
tmpPrefix: 'happy-stacks-stack-wt-list-',
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const res = await fixture.runStackWtList();
|
|
88
|
+
assert.equal(res.code, 0, `expected exit 0, got ${res.code}\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
89
|
+
|
|
90
|
+
assert.ok(
|
|
91
|
+
res.stdout.includes(`- active: ${fixture.monoActive}`),
|
|
92
|
+
`expected happy active in output\n${res.stdout}`
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
// Should NOT enumerate other worktrees unless --all was passed.
|
|
96
|
+
assert.ok(!res.stdout.includes(`- ${fixture.monoOther}`), `expected other to be omitted\n${res.stdout}`);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('hstack stack wt <stack> -- list --all shows all worktrees (opt-in)', async (t) => {
|
|
100
|
+
const fixture = await setupStackWtListFixture({
|
|
101
|
+
importMetaUrl: import.meta.url,
|
|
102
|
+
t,
|
|
103
|
+
tmpPrefix: 'happy-stacks-stack-wt-list-all-',
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const res = await fixture.runStackWtList(['--all']);
|
|
107
|
+
assert.equal(res.code, 0, `expected exit 0, got ${res.code}\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
108
|
+
|
|
109
|
+
assert.ok(
|
|
110
|
+
res.stdout.includes(`- active: ${fixture.monoActive}`),
|
|
111
|
+
`expected happy active in output\n${res.stdout}`
|
|
112
|
+
);
|
|
113
|
+
assert.ok(
|
|
114
|
+
res.stdout.includes(`- ${fixture.monoOther}`),
|
|
115
|
+
`expected happy other to be listed with --all\n${res.stdout}`
|
|
116
|
+
);
|
|
117
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { dirname, join } from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
|
|
9
|
+
function runNode(args, { cwd, env }) {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
const proc = spawn(process.execPath, args, { cwd, env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
12
|
+
let stdout = '';
|
|
13
|
+
let stderr = '';
|
|
14
|
+
proc.stdout.on('data', (d) => (stdout += String(d)));
|
|
15
|
+
proc.stderr.on('data', (d) => (stderr += String(d)));
|
|
16
|
+
proc.on('error', reject);
|
|
17
|
+
proc.on('exit', (code, signal) => resolve({ code, signal, stdout, stderr }));
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
test('hstack start fails closed when UI serving is enabled and index.html is missing (default uiRequired)', async () => {
|
|
22
|
+
const scriptsDir = dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const rootDir = dirname(scriptsDir);
|
|
24
|
+
|
|
25
|
+
const tmp = await mkdtemp(join(tmpdir(), 'hstack-start-ui-required-'));
|
|
26
|
+
try {
|
|
27
|
+
const monoRoot = join(tmp, 'happier');
|
|
28
|
+
|
|
29
|
+
await mkdir(join(monoRoot, 'apps', 'ui'), { recursive: true });
|
|
30
|
+
await mkdir(join(monoRoot, 'apps', 'cli'), { recursive: true });
|
|
31
|
+
await mkdir(join(monoRoot, 'apps', 'server'), { recursive: true });
|
|
32
|
+
await writeFile(join(monoRoot, 'apps', 'ui', 'package.json'), '{}\n', 'utf-8');
|
|
33
|
+
await writeFile(join(monoRoot, 'apps', 'cli', 'package.json'), '{}\n', 'utf-8');
|
|
34
|
+
await writeFile(join(monoRoot, 'apps', 'server', 'package.json'), '{}\n', 'utf-8');
|
|
35
|
+
|
|
36
|
+
const uiBuildDir = join(tmp, 'ui');
|
|
37
|
+
await mkdir(uiBuildDir, { recursive: true });
|
|
38
|
+
await writeFile(join(uiBuildDir, 'canvaskit.wasm'), 'stub\n', 'utf-8');
|
|
39
|
+
|
|
40
|
+
const env = {
|
|
41
|
+
...process.env,
|
|
42
|
+
HAPPIER_STACK_REPO_DIR: monoRoot,
|
|
43
|
+
HAPPIER_STACK_SERVE_UI: '1',
|
|
44
|
+
HAPPIER_STACK_UI_BUILD_DIR: uiBuildDir,
|
|
45
|
+
// Ensure we do not spawn real services during this test.
|
|
46
|
+
HAPPIER_STACK_CLI_ROOT_DISABLE: '1',
|
|
47
|
+
HAPPIER_STACK_TAILSCALE_SERVE: '0',
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const res = await runNode([join(rootDir, 'scripts', 'run.mjs'), '--json'], { cwd: rootDir, env });
|
|
51
|
+
assert.ok(
|
|
52
|
+
res.code === 0 && !res.signal,
|
|
53
|
+
`sanity: --json should exit cleanly\ncode: ${res.code}\nsignal: ${res.signal}\nstderr:\n${res.stderr}`
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const res2 = await runNode([join(rootDir, 'scripts', 'run.mjs')], { cwd: rootDir, env });
|
|
57
|
+
assert.ok(res2.code !== 0 || Boolean(res2.signal), `expected non-zero exit\ncode: ${res2.code}\nsignal: ${res2.signal}`);
|
|
58
|
+
assert.match(res2.stderr + res2.stdout, /index\.html/i);
|
|
59
|
+
assert.match(res2.stderr + res2.stdout, /hstack build/i);
|
|
60
|
+
} finally {
|
|
61
|
+
await rm(tmp, { recursive: true, force: true });
|
|
62
|
+
}
|
|
63
|
+
});
|
package/scripts/stop.mjs
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import './utils/env/env.mjs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
|
|
5
|
+
import { parseArgs } from './utils/cli/args.mjs';
|
|
6
|
+
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
7
|
+
import { run, runCapture } from './utils/proc/proc.mjs';
|
|
8
|
+
import { getRootDir, resolveStackEnvPath } from './utils/paths/paths.mjs';
|
|
9
|
+
import { banner, bullets, cmd, kv, sectionTitle } from './utils/ui/layout.mjs';
|
|
10
|
+
import { dim, green, yellow } from './utils/ui/ansi.mjs';
|
|
11
|
+
|
|
12
|
+
function usage() {
|
|
13
|
+
return [
|
|
14
|
+
'',
|
|
15
|
+
banner('stop', { subtitle: 'Stop stacks safely (bulk).' }),
|
|
16
|
+
'',
|
|
17
|
+
sectionTitle('Usage'),
|
|
18
|
+
bullets([
|
|
19
|
+
`${dim('stop all (non-interactive):')} ${cmd('hstack stop --yes')}`,
|
|
20
|
+
`${dim('exclude some stacks:')} ${cmd('hstack stop --except-stacks=main,exp1 --yes')}`,
|
|
21
|
+
`${dim('aggressive:')} ${cmd('hstack stop --yes --aggressive')} ${dim('(also stops daemon-tracked sessions)')}`,
|
|
22
|
+
`${dim('sweep-owned:')} ${cmd('hstack stop --yes --sweep-owned')} ${dim('(final owned-process sweep)')}`,
|
|
23
|
+
`${dim('no docker:')} ${cmd('hstack stop --yes --no-docker')}`,
|
|
24
|
+
`${dim('no service:')} ${cmd('hstack stop --yes --no-service')}`,
|
|
25
|
+
`${dim('json:')} ${cmd('hstack stop --yes --json')}`,
|
|
26
|
+
]),
|
|
27
|
+
'',
|
|
28
|
+
sectionTitle('What it does'),
|
|
29
|
+
bullets([dim('Stops stacks and related local processes (server, daemon, Expo, managed infra) using stack-scoped commands.')]),
|
|
30
|
+
].join('\n');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseCsv(raw) {
|
|
34
|
+
const s = String(raw ?? '').trim();
|
|
35
|
+
if (!s) return [];
|
|
36
|
+
return s
|
|
37
|
+
.split(',')
|
|
38
|
+
.map((p) => p.trim())
|
|
39
|
+
.filter(Boolean);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function listAllStackNames() {
|
|
43
|
+
try {
|
|
44
|
+
// Reuse stack.mjs for enumeration (avoids duplicating legacy/new stack dir logic).
|
|
45
|
+
// Note: `stack list` intentionally omits `main`, so we add it back.
|
|
46
|
+
const rootDir = getRootDir(import.meta.url);
|
|
47
|
+
const out = await runCapture(process.execPath, [join(rootDir, 'scripts', 'stack.mjs'), 'list', '--json'], { cwd: rootDir });
|
|
48
|
+
const parsed = JSON.parse(out);
|
|
49
|
+
const stacks = Array.isArray(parsed?.stacks) ? parsed.stacks : [];
|
|
50
|
+
const all = ['main', ...stacks.filter((s) => s !== 'main')];
|
|
51
|
+
return all.sort();
|
|
52
|
+
} catch {
|
|
53
|
+
return ['main'];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function main() {
|
|
58
|
+
const rootDir = getRootDir(import.meta.url);
|
|
59
|
+
const argv = process.argv.slice(2);
|
|
60
|
+
const { flags, kv } = parseArgs(argv);
|
|
61
|
+
const json = wantsJson(argv, { flags });
|
|
62
|
+
|
|
63
|
+
if (wantsHelp(argv, { flags })) {
|
|
64
|
+
printResult({ json, data: { ok: true }, text: usage() });
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const exceptStacks = new Set(parseCsv(kv.get('--except-stacks')));
|
|
69
|
+
const yes = flags.has('--yes');
|
|
70
|
+
const aggressive = flags.has('--aggressive');
|
|
71
|
+
const sweepOwned = flags.has('--sweep-owned');
|
|
72
|
+
const noDocker = flags.has('--no-docker');
|
|
73
|
+
const noService = flags.has('--no-service');
|
|
74
|
+
|
|
75
|
+
const stacks = await listAllStackNames();
|
|
76
|
+
const targets = stacks.filter((n) => !exceptStacks.has(n));
|
|
77
|
+
|
|
78
|
+
if (!targets.length) {
|
|
79
|
+
printResult({ json, data: { ok: true, stopped: [], skipped: stacks }, text: '[stop] nothing to do (all stacks excluded)' });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!yes && !(process.stdin.isTTY && process.stdout.isTTY)) {
|
|
84
|
+
throw new Error('[stop] refusing to stop stacks without --yes in non-interactive mode');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!yes) {
|
|
88
|
+
// Simple confirm prompt (avoid importing wizard/rl here).
|
|
89
|
+
// eslint-disable-next-line no-console
|
|
90
|
+
console.log('');
|
|
91
|
+
console.log(banner('stop', { subtitle: 'Confirmation required (TTY).' }));
|
|
92
|
+
console.log('');
|
|
93
|
+
console.log(sectionTitle('Will stop'));
|
|
94
|
+
console.log(bullets([kv('stacks:', targets.join(', '))]));
|
|
95
|
+
console.log('');
|
|
96
|
+
console.log(sectionTitle('Proceed'));
|
|
97
|
+
// eslint-disable-next-line no-console
|
|
98
|
+
console.log(bullets([`${dim('re-run:')} ${cmd('hstack stop --yes')}`]));
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const results = [];
|
|
103
|
+
const errors = [];
|
|
104
|
+
const skipped = [];
|
|
105
|
+
|
|
106
|
+
for (const stackName of targets) {
|
|
107
|
+
if (stackName !== 'main') {
|
|
108
|
+
const { envPath } = resolveStackEnvPath(stackName);
|
|
109
|
+
// Stack name might appear in directory listings, but if it has no env file, treat it as non-existent.
|
|
110
|
+
if (!existsSync(envPath)) {
|
|
111
|
+
skipped.push({ stackName, reason: 'missing_env', envPath });
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
if (!noService) {
|
|
117
|
+
// Best-effort: stop autostart service for the stack so it doesn't restart what we just stopped.
|
|
118
|
+
// eslint-disable-next-line no-await-in-loop
|
|
119
|
+
await run(process.execPath, [join(rootDir, 'scripts', 'stack.mjs'), 'service', stackName, 'stop'], { cwd: rootDir }).catch(() => {});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const args = [
|
|
123
|
+
join(rootDir, 'scripts', 'stack.mjs'),
|
|
124
|
+
'stop',
|
|
125
|
+
stackName,
|
|
126
|
+
...(aggressive ? ['--aggressive'] : []),
|
|
127
|
+
...(sweepOwned ? ['--sweep-owned'] : []),
|
|
128
|
+
...(noDocker ? ['--no-docker'] : []),
|
|
129
|
+
];
|
|
130
|
+
if (json) {
|
|
131
|
+
// eslint-disable-next-line no-await-in-loop
|
|
132
|
+
const out = await runCapture(process.execPath, [...args, '--json'], { cwd: rootDir });
|
|
133
|
+
results.push({ stackName, out: JSON.parse(out) });
|
|
134
|
+
} else {
|
|
135
|
+
// eslint-disable-next-line no-await-in-loop
|
|
136
|
+
await run(process.execPath, args, { cwd: rootDir });
|
|
137
|
+
results.push({ stackName, ok: true });
|
|
138
|
+
}
|
|
139
|
+
} catch (e) {
|
|
140
|
+
errors.push({ stackName, error: e instanceof Error ? e.message : String(e) });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (json) {
|
|
145
|
+
printResult({
|
|
146
|
+
json,
|
|
147
|
+
data: { ok: errors.length === 0, stopped: results, skipped, errors, exceptStacks: Array.from(exceptStacks) },
|
|
148
|
+
});
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// eslint-disable-next-line no-console
|
|
153
|
+
console.log('');
|
|
154
|
+
// eslint-disable-next-line no-console
|
|
155
|
+
console.log(banner('stop', { subtitle: 'Complete.' }));
|
|
156
|
+
// eslint-disable-next-line no-console
|
|
157
|
+
console.log(
|
|
158
|
+
bullets([
|
|
159
|
+
kv('stopped:', String(results.length)),
|
|
160
|
+
kv('skipped:', String(skipped.length)),
|
|
161
|
+
kv('errors:', String(errors.length)),
|
|
162
|
+
])
|
|
163
|
+
);
|
|
164
|
+
if (skipped.length) {
|
|
165
|
+
// eslint-disable-next-line no-console
|
|
166
|
+
console.log('');
|
|
167
|
+
// eslint-disable-next-line no-console
|
|
168
|
+
console.log(sectionTitle('Skipped'));
|
|
169
|
+
for (const s of skipped) {
|
|
170
|
+
// eslint-disable-next-line no-console
|
|
171
|
+
console.log(`- ${s.stackName}: ${s.reason}${s.envPath ? ` ${dim(`(${s.envPath})`)}` : ''}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (errors.length) {
|
|
175
|
+
// eslint-disable-next-line no-console
|
|
176
|
+
console.log('');
|
|
177
|
+
// eslint-disable-next-line no-console
|
|
178
|
+
console.log(sectionTitle('Errors'));
|
|
179
|
+
for (const e of errors) {
|
|
180
|
+
// eslint-disable-next-line no-console
|
|
181
|
+
console.warn(`- ${e.stackName}: ${e.error}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
main().catch((err) => {
|
|
187
|
+
console.error('[stop] failed:', err);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
});
|
|
190
|
+
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
import { stopStackWithEnv } from './utils/stack/stop.mjs';
|
|
9
|
+
import { isAlive, spawnOwnedSleep, waitForProcessAlive } from './testkit/stack_stop_sweeps_testkit.mjs';
|
|
10
|
+
|
|
11
|
+
test('stopStackWithEnv does not auto-sweep when autoSweep=false and stack.runtime.json is missing', async (t) => {
|
|
12
|
+
const scriptsDir = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const rootDir = dirname(scriptsDir);
|
|
14
|
+
|
|
15
|
+
const tmp = await mkdtemp(join(tmpdir(), 'hstack-stop-no-autosweep-'));
|
|
16
|
+
const homeDir = join(tmp, 'home');
|
|
17
|
+
const storageDir = join(tmp, 'storage');
|
|
18
|
+
const workspaceDir = join(tmp, 'workspace');
|
|
19
|
+
const repoDir = dirname(rootDir);
|
|
20
|
+
|
|
21
|
+
await mkdir(homeDir, { recursive: true });
|
|
22
|
+
await mkdir(storageDir, { recursive: true });
|
|
23
|
+
await mkdir(workspaceDir, { recursive: true });
|
|
24
|
+
|
|
25
|
+
const stackName = 'exp1';
|
|
26
|
+
const baseDir = join(storageDir, stackName);
|
|
27
|
+
const envPath = join(baseDir, 'env');
|
|
28
|
+
await mkdir(baseDir, { recursive: true });
|
|
29
|
+
|
|
30
|
+
await writeFile(
|
|
31
|
+
envPath,
|
|
32
|
+
[
|
|
33
|
+
`HAPPIER_STACK_STACK=${stackName}`,
|
|
34
|
+
`HAPPIER_STACK_SERVER_COMPONENT=happier-server-light`,
|
|
35
|
+
`HAPPIER_STACK_UI_BUILD_DIR=${join(baseDir, 'ui')}`,
|
|
36
|
+
`HAPPIER_STACK_CLI_HOME_DIR=${join(baseDir, 'cli')}`,
|
|
37
|
+
`HAPPIER_STACK_REPO_DIR=${repoDir}`,
|
|
38
|
+
'',
|
|
39
|
+
].join('\n'),
|
|
40
|
+
'utf-8'
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
/** @type {ReturnType<typeof spawnOwnedSleep> | null} */
|
|
44
|
+
let owned = null;
|
|
45
|
+
t.after(async () => {
|
|
46
|
+
const pid = owned?.pid;
|
|
47
|
+
if (pid) {
|
|
48
|
+
try {
|
|
49
|
+
process.kill(-pid, 'SIGKILL');
|
|
50
|
+
} catch {
|
|
51
|
+
// ignore
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
await rm(tmp, { recursive: true, force: true });
|
|
57
|
+
} catch {
|
|
58
|
+
// ignore
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
owned = spawnOwnedSleep({
|
|
63
|
+
env: {
|
|
64
|
+
...process.env,
|
|
65
|
+
HAPPIER_STACK_STACK: stackName,
|
|
66
|
+
HAPPIER_STACK_ENV_FILE: envPath,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
assert.ok(Number(owned.pid) > 1, 'expected child pid');
|
|
70
|
+
assert.ok(isAlive(owned.pid), 'expected owned child to be alive');
|
|
71
|
+
|
|
72
|
+
const actions = await stopStackWithEnv({
|
|
73
|
+
rootDir,
|
|
74
|
+
stackName,
|
|
75
|
+
baseDir,
|
|
76
|
+
env: {
|
|
77
|
+
...process.env,
|
|
78
|
+
HAPPIER_STACK_STACK: stackName,
|
|
79
|
+
HAPPIER_STACK_ENV_FILE: envPath,
|
|
80
|
+
HAPPIER_STACK_REPO_DIR: repoDir,
|
|
81
|
+
HAPPIER_STACK_HOME_DIR: homeDir,
|
|
82
|
+
HAPPIER_STACK_STORAGE_DIR: storageDir,
|
|
83
|
+
HAPPIER_STACK_WORKSPACE_DIR: workspaceDir,
|
|
84
|
+
},
|
|
85
|
+
json: true,
|
|
86
|
+
noDocker: true,
|
|
87
|
+
aggressive: false,
|
|
88
|
+
sweepOwned: false,
|
|
89
|
+
autoSweep: false,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
assert.ok(!actions?.sweep, 'expected no sweep field when autoSweep=false');
|
|
93
|
+
await waitForProcessAlive({ pid: owned.pid, timeoutMs: 2_000, intervalMs: 25, label: 'owned process (no autosweep)' });
|
|
94
|
+
assert.ok(isAlive(owned.pid), `expected owned pid ${owned.pid} to still be alive`);
|
|
95
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { dirname, join } from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
|
|
9
|
+
function run(cmd, args, { cwd, env } = {}) {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
const proc = spawn(cmd, args, { cwd, env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
12
|
+
let stdout = '';
|
|
13
|
+
let stderr = '';
|
|
14
|
+
proc.stdout.on('data', (d) => (stdout += String(d)));
|
|
15
|
+
proc.stderr.on('data', (d) => (stderr += String(d)));
|
|
16
|
+
proc.on('error', reject);
|
|
17
|
+
proc.on('exit', (code, signal) => resolve({ code: code ?? (signal ? 1 : 0), signal: signal ?? null, stdout, stderr }));
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function runOrThrow(cmd, args, { cwd, env } = {}) {
|
|
22
|
+
const res = await run(cmd, args, { cwd, env });
|
|
23
|
+
assert.equal(res.code, 0, `${cmd} ${args.join(' ')} failed\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
24
|
+
return res;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
test('swiftbar git cache treats monorepo package dirs as git repos', async (t) => {
|
|
28
|
+
const scriptsDir = dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
const rootDir = dirname(scriptsDir);
|
|
30
|
+
const tmp = await mkdtemp(join(tmpdir(), 'happier-stack-swiftbar-git-'));
|
|
31
|
+
t.after(async () => {
|
|
32
|
+
await rm(tmp, { recursive: true, force: true }).catch(() => {});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const repoRoot = join(tmp, 'repo');
|
|
36
|
+
await mkdir(repoRoot, { recursive: true });
|
|
37
|
+
await mkdir(join(repoRoot, 'apps', 'ui'), { recursive: true });
|
|
38
|
+
await writeFile(join(repoRoot, 'apps', 'ui', 'README.md'), 'hello\n', 'utf-8');
|
|
39
|
+
|
|
40
|
+
// Create a minimal git repo with one commit.
|
|
41
|
+
await runOrThrow('git', ['init'], { cwd: repoRoot });
|
|
42
|
+
await runOrThrow('git', ['add', '.'], { cwd: repoRoot });
|
|
43
|
+
await runOrThrow(
|
|
44
|
+
'git',
|
|
45
|
+
['-c', 'user.name=test', '-c', 'user.email=test@example.com', 'commit', '-m', 'init'],
|
|
46
|
+
{ cwd: repoRoot }
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const stacksHome = join(tmp, 'happier-stack-home');
|
|
50
|
+
const env = {
|
|
51
|
+
...process.env,
|
|
52
|
+
HAPPIER_STACK_HOME_DIR: stacksHome,
|
|
53
|
+
HAPPIER_STACK_CANONICAL_HOME_DIR: stacksHome,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const bashScript = [
|
|
57
|
+
`set -euo pipefail`,
|
|
58
|
+
`source "${rootDir}/extras/swiftbar/lib/utils.sh"`,
|
|
59
|
+
`source "${rootDir}/extras/swiftbar/lib/git.sh"`,
|
|
60
|
+
`active_dir="${repoRoot}/apps/ui"`,
|
|
61
|
+
`git_cache_refresh_one main main happier "$active_dir" >/dev/null 2>&1 || true`,
|
|
62
|
+
`key="$(git_cache_key main main happier "$active_dir")"`,
|
|
63
|
+
`IFS=$'\\t' read -r meta info wts <<<"$(git_cache_paths "$key")"`,
|
|
64
|
+
`if [[ ! -f "$info" ]]; then echo "missing-info"; exit 2; fi`,
|
|
65
|
+
`head -n 1 "$info"`,
|
|
66
|
+
].join('\n');
|
|
67
|
+
|
|
68
|
+
const res = await run('bash', ['-lc', bashScript], { env });
|
|
69
|
+
assert.equal(
|
|
70
|
+
res.code,
|
|
71
|
+
0,
|
|
72
|
+
`expected bash exit 0, got ${res.code}\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`
|
|
73
|
+
);
|
|
74
|
+
assert.match(res.stdout, /^ok\t/, `expected info.tsv to start with ok\\t, got:\n${res.stdout}`);
|
|
75
|
+
});
|