@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,154 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdtemp, mkdir, realpath, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
import { getInvokedCwd, inferComponentFromCwd } from './cwd_scope.mjs';
|
|
8
|
+
|
|
9
|
+
async function withTempRoot(t) {
|
|
10
|
+
const dir = await mkdtemp(join(tmpdir(), 'happier-stacks-cwd-scope-'));
|
|
11
|
+
t.after(async () => {
|
|
12
|
+
await rm(dir, { recursive: true, force: true });
|
|
13
|
+
});
|
|
14
|
+
return dir;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function createMonorepoCheckout({ rootDir, checkoutPath }) {
|
|
18
|
+
const repoRoot = join(rootDir, checkoutPath);
|
|
19
|
+
await mkdir(join(repoRoot, 'apps', 'ui'), { recursive: true });
|
|
20
|
+
await mkdir(join(repoRoot, 'apps', 'cli', 'src'), { recursive: true });
|
|
21
|
+
await mkdir(join(repoRoot, 'apps', 'server'), { recursive: true });
|
|
22
|
+
await writeFile(join(repoRoot, 'apps', 'ui', 'package.json'), '{}\n', 'utf-8');
|
|
23
|
+
await writeFile(join(repoRoot, 'apps', 'cli', 'package.json'), '{}\n', 'utf-8');
|
|
24
|
+
await writeFile(join(repoRoot, 'apps', 'server', 'package.json'), '{}\n', 'utf-8');
|
|
25
|
+
await writeFile(join(repoRoot, '.git'), 'gitdir: /tmp/fake\n', 'utf-8');
|
|
26
|
+
return repoRoot;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function workspaceEnv(rootDir) {
|
|
30
|
+
return { ...process.env, HAPPIER_STACK_WORKSPACE_DIR: rootDir };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function withMockedProcessCwd(t, value) {
|
|
34
|
+
if (t.mock?.method) {
|
|
35
|
+
t.mock.method(process, 'cwd', () => value);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const prevCwd = process.cwd;
|
|
39
|
+
process.cwd = () => value;
|
|
40
|
+
t.after(() => {
|
|
41
|
+
process.cwd = prevCwd;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
test('inferComponentFromCwd resolves the stable monorepo checkout under <workspace>/main', async (t) => {
|
|
46
|
+
const rootDir = await withTempRoot(t);
|
|
47
|
+
const repoRoot = await createMonorepoCheckout({ rootDir, checkoutPath: 'main' });
|
|
48
|
+
const inferred = inferComponentFromCwd({
|
|
49
|
+
rootDir,
|
|
50
|
+
invokedCwd: join(repoRoot, 'apps', 'ui'),
|
|
51
|
+
components: ['happier-ui', 'happier-cli'],
|
|
52
|
+
env: workspaceEnv(rootDir),
|
|
53
|
+
});
|
|
54
|
+
assert.deepEqual(inferred, { component: 'happier-ui', repoDir: repoRoot });
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('inferComponentFromCwd resolves happier monorepo subpackages under <workspace>/main', async (t) => {
|
|
58
|
+
const rootDir = await withTempRoot(t);
|
|
59
|
+
const repoRoot = await createMonorepoCheckout({ rootDir, checkoutPath: 'main' });
|
|
60
|
+
const inferred = inferComponentFromCwd({
|
|
61
|
+
rootDir,
|
|
62
|
+
invokedCwd: join(repoRoot, 'apps', 'cli', 'src'),
|
|
63
|
+
components: ['happier-ui', 'happier-cli', 'happier-server'],
|
|
64
|
+
env: workspaceEnv(rootDir),
|
|
65
|
+
});
|
|
66
|
+
assert.deepEqual(inferred, { component: 'happier-cli', repoDir: repoRoot });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('inferComponentFromCwd resolves happier monorepo worktree roots under <workspace>/pr', async (t) => {
|
|
70
|
+
const rootDir = await withTempRoot(t);
|
|
71
|
+
const repoRoot = await createMonorepoCheckout({ rootDir, checkoutPath: join('pr', '123-fix') });
|
|
72
|
+
await mkdir(join(repoRoot, 'apps', 'cli', 'nested'), { recursive: true });
|
|
73
|
+
const inferred = inferComponentFromCwd({
|
|
74
|
+
rootDir,
|
|
75
|
+
invokedCwd: join(repoRoot, 'apps', 'cli', 'nested'),
|
|
76
|
+
components: ['happier-ui', 'happier-cli', 'happier-server'],
|
|
77
|
+
env: workspaceEnv(rootDir),
|
|
78
|
+
});
|
|
79
|
+
assert.deepEqual(inferred, { component: 'happier-cli', repoDir: repoRoot });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('inferComponentFromCwd returns null outside known component roots', async (t) => {
|
|
83
|
+
const rootDir = await withTempRoot(t);
|
|
84
|
+
const invokedCwd = join(rootDir, 'somewhere', 'else');
|
|
85
|
+
await mkdir(invokedCwd, { recursive: true });
|
|
86
|
+
const inferred = inferComponentFromCwd({
|
|
87
|
+
rootDir,
|
|
88
|
+
invokedCwd,
|
|
89
|
+
components: ['happier-ui'],
|
|
90
|
+
env: workspaceEnv(rootDir),
|
|
91
|
+
});
|
|
92
|
+
assert.equal(inferred, null);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('inferComponentFromCwd uses the provided env (does not depend on process.env)', async (t) => {
|
|
96
|
+
const rootDir = await withTempRoot(t);
|
|
97
|
+
const repoRoot = await createMonorepoCheckout({ rootDir, checkoutPath: 'main' });
|
|
98
|
+
const inferred = inferComponentFromCwd({
|
|
99
|
+
rootDir,
|
|
100
|
+
invokedCwd: join(repoRoot, 'apps', 'ui'),
|
|
101
|
+
components: ['happier-ui'],
|
|
102
|
+
env: workspaceEnv(rootDir),
|
|
103
|
+
});
|
|
104
|
+
assert.deepEqual(inferred, { component: 'happier-ui', repoDir: repoRoot });
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('getInvokedCwd falls back to process.cwd() when PWD is not set (Windows)', async (t) => {
|
|
108
|
+
const dir = await withTempRoot(t);
|
|
109
|
+
const expected = await realpath(dir).catch(() => dir);
|
|
110
|
+
withMockedProcessCwd(t, dir);
|
|
111
|
+
|
|
112
|
+
const actual = await realpath(getInvokedCwd({})).catch(() => getInvokedCwd({}));
|
|
113
|
+
assert.equal(actual, expected);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('getInvokedCwd prefers OLDPWD when it looks like the real repo/worktree root', async (t) => {
|
|
117
|
+
const rootDir = await withTempRoot(t);
|
|
118
|
+
const oldPwd = join(rootDir, 'dev');
|
|
119
|
+
const pwd = join(rootDir, 'main');
|
|
120
|
+
await mkdir(oldPwd, { recursive: true });
|
|
121
|
+
await mkdir(pwd, { recursive: true });
|
|
122
|
+
await writeFile(join(oldPwd, '.git'), 'gitdir: /tmp/fake\n', 'utf-8');
|
|
123
|
+
|
|
124
|
+
withMockedProcessCwd(t, pwd);
|
|
125
|
+
const actual = getInvokedCwd({ PWD: pwd, OLDPWD: oldPwd });
|
|
126
|
+
assert.equal(actual, oldPwd);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('getInvokedCwd prefers PWD when both PWD and OLDPWD look like repo/worktree roots', async (t) => {
|
|
130
|
+
const rootDir = await withTempRoot(t);
|
|
131
|
+
const oldPwd = join(rootDir, 'dev');
|
|
132
|
+
const pwd = join(rootDir, 'main');
|
|
133
|
+
await mkdir(oldPwd, { recursive: true });
|
|
134
|
+
await mkdir(pwd, { recursive: true });
|
|
135
|
+
await writeFile(join(oldPwd, '.git'), 'gitdir: /tmp/fake\n', 'utf-8');
|
|
136
|
+
await writeFile(join(pwd, '.git'), 'gitdir: /tmp/fake\n', 'utf-8');
|
|
137
|
+
|
|
138
|
+
withMockedProcessCwd(t, pwd);
|
|
139
|
+
const actual = getInvokedCwd({ PWD: pwd, OLDPWD: oldPwd });
|
|
140
|
+
assert.equal(actual, pwd);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('getInvokedCwd falls back to OLDPWD when PWD does not look like a checkout/worktree root', async (t) => {
|
|
144
|
+
const rootDir = await withTempRoot(t);
|
|
145
|
+
const oldPwd = join(rootDir, 'dev');
|
|
146
|
+
const pwd = join(rootDir, 'not-a-worktree');
|
|
147
|
+
await mkdir(oldPwd, { recursive: true });
|
|
148
|
+
await mkdir(pwd, { recursive: true });
|
|
149
|
+
await writeFile(join(oldPwd, '.git'), 'gitdir: /tmp/fake\n', 'utf-8');
|
|
150
|
+
|
|
151
|
+
withMockedProcessCwd(t, pwd);
|
|
152
|
+
const actual = getInvokedCwd({ PWD: pwd, OLDPWD: oldPwd });
|
|
153
|
+
assert.equal(actual, oldPwd);
|
|
154
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function boolFromFlags({ flags, onFlag, offFlag, defaultValue }) {
|
|
2
|
+
if (flags.has(offFlag)) return false;
|
|
3
|
+
if (flags.has(onFlag)) return true;
|
|
4
|
+
return defaultValue;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function boolFromFlagsOrKv({ flags, kv, onFlag, offFlag, key, defaultValue }) {
|
|
8
|
+
if (flags.has(offFlag)) return false;
|
|
9
|
+
if (flags.has(onFlag)) return true;
|
|
10
|
+
if (key && kv.has(key)) {
|
|
11
|
+
const raw = String(kv.get(key) ?? '').trim().toLowerCase();
|
|
12
|
+
if (raw === '1' || raw === 'true' || raw === 'yes' || raw === 'y') return true;
|
|
13
|
+
if (raw === '0' || raw === 'false' || raw === 'no' || raw === 'n') return false;
|
|
14
|
+
}
|
|
15
|
+
return defaultValue;
|
|
16
|
+
}
|
|
17
|
+
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { createReadStream, existsSync } from 'node:fs';
|
|
2
|
+
import { stat } from 'node:fs/promises';
|
|
3
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
4
|
+
|
|
5
|
+
function splitLines(s) {
|
|
6
|
+
return String(s ?? '').split(/\r?\n/);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function supportsAnsi() {
|
|
10
|
+
if (!process.stdout.isTTY) return false;
|
|
11
|
+
if (process.env.NO_COLOR) return false;
|
|
12
|
+
if ((process.env.TERM ?? '').toLowerCase() === 'dumb') return false;
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function dim(s) {
|
|
17
|
+
return supportsAnsi() ? `\x1b[2m${s}\x1b[0m` : String(s);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Lightweight file log forwarder (tail-like) with pause/resume.
|
|
22
|
+
*
|
|
23
|
+
* - Always advances the file offset (prevents backpressure issues).
|
|
24
|
+
* - While paused, it buffers the last N lines and prints them once resumed.
|
|
25
|
+
*/
|
|
26
|
+
export function createFileLogForwarder({
|
|
27
|
+
path,
|
|
28
|
+
enabled = true,
|
|
29
|
+
pollMs = 200,
|
|
30
|
+
maxBytesPerTick = 256 * 1024,
|
|
31
|
+
bufferedLinesWhilePaused = 120,
|
|
32
|
+
startFromEnd = true,
|
|
33
|
+
label = 'logs',
|
|
34
|
+
} = {}) {
|
|
35
|
+
const p = String(path ?? '').trim();
|
|
36
|
+
if (!enabled || !p) {
|
|
37
|
+
return {
|
|
38
|
+
ok: false,
|
|
39
|
+
start: async () => {},
|
|
40
|
+
stop: async () => {},
|
|
41
|
+
pause: () => {},
|
|
42
|
+
resume: () => {},
|
|
43
|
+
isPaused: () => false,
|
|
44
|
+
path: p,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let running = false;
|
|
49
|
+
let paused = false;
|
|
50
|
+
let offset = 0;
|
|
51
|
+
let partial = '';
|
|
52
|
+
let buffered = [];
|
|
53
|
+
|
|
54
|
+
const pushBufferedLine = (line) => {
|
|
55
|
+
if (!line) return;
|
|
56
|
+
buffered.push(line);
|
|
57
|
+
if (buffered.length > bufferedLinesWhilePaused) {
|
|
58
|
+
buffered = buffered.slice(buffered.length - bufferedLinesWhilePaused);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const flushBuffered = () => {
|
|
63
|
+
if (!buffered.length) return;
|
|
64
|
+
// eslint-disable-next-line no-console
|
|
65
|
+
console.log(dim(`[${label}] (showing last ${buffered.length} lines while paused)`));
|
|
66
|
+
for (const l of buffered) {
|
|
67
|
+
// eslint-disable-next-line no-console
|
|
68
|
+
console.log(l);
|
|
69
|
+
}
|
|
70
|
+
buffered = [];
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const readNewBytes = async () => {
|
|
74
|
+
if (!existsSync(p)) return;
|
|
75
|
+
let st = null;
|
|
76
|
+
try {
|
|
77
|
+
st = await stat(p);
|
|
78
|
+
} catch {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const size = Number(st?.size ?? 0);
|
|
82
|
+
if (!Number.isFinite(size) || size <= 0) return;
|
|
83
|
+
if (size < offset) {
|
|
84
|
+
// truncated/rotated
|
|
85
|
+
offset = 0;
|
|
86
|
+
}
|
|
87
|
+
if (size === offset) return;
|
|
88
|
+
|
|
89
|
+
const end = Math.min(size, offset + maxBytesPerTick);
|
|
90
|
+
const start = offset;
|
|
91
|
+
offset = end;
|
|
92
|
+
|
|
93
|
+
await new Promise((resolvePromise) => {
|
|
94
|
+
const chunks = [];
|
|
95
|
+
const stream = createReadStream(p, { start, end: end - 1 });
|
|
96
|
+
stream.on('data', (d) => chunks.push(Buffer.from(d)));
|
|
97
|
+
stream.on('error', () => resolvePromise());
|
|
98
|
+
stream.on('close', () => {
|
|
99
|
+
const text = partial + Buffer.concat(chunks).toString('utf-8');
|
|
100
|
+
const lines = splitLines(text);
|
|
101
|
+
partial = lines.pop() ?? '';
|
|
102
|
+
for (const line of lines) {
|
|
103
|
+
if (paused) {
|
|
104
|
+
pushBufferedLine(line);
|
|
105
|
+
} else {
|
|
106
|
+
// eslint-disable-next-line no-console
|
|
107
|
+
console.log(line);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
resolvePromise();
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const loop = async () => {
|
|
116
|
+
while (running) {
|
|
117
|
+
// eslint-disable-next-line no-await-in-loop
|
|
118
|
+
await readNewBytes();
|
|
119
|
+
// eslint-disable-next-line no-await-in-loop
|
|
120
|
+
await delay(pollMs);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
ok: true,
|
|
126
|
+
path: p,
|
|
127
|
+
start: async () => {
|
|
128
|
+
if (running) return;
|
|
129
|
+
running = true;
|
|
130
|
+
// By default, start at end (don't replay historical logs).
|
|
131
|
+
if (startFromEnd) {
|
|
132
|
+
try {
|
|
133
|
+
const st = await stat(p);
|
|
134
|
+
offset = Number(st?.size ?? 0) || 0;
|
|
135
|
+
} catch {
|
|
136
|
+
offset = 0;
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
offset = 0;
|
|
140
|
+
}
|
|
141
|
+
void loop();
|
|
142
|
+
},
|
|
143
|
+
stop: async () => {
|
|
144
|
+
running = false;
|
|
145
|
+
},
|
|
146
|
+
pause: () => {
|
|
147
|
+
paused = true;
|
|
148
|
+
buffered = [];
|
|
149
|
+
},
|
|
150
|
+
resume: () => {
|
|
151
|
+
paused = false;
|
|
152
|
+
flushBuffered();
|
|
153
|
+
},
|
|
154
|
+
isPaused: () => paused,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function normalizeProfile(raw) {
|
|
2
|
+
const v = (raw ?? '').trim().toLowerCase();
|
|
3
|
+
if (!v) return '';
|
|
4
|
+
if (v === 'selfhost' || v === 'self-host' || v === 'self_host' || v === 'host') return 'selfhost';
|
|
5
|
+
if (v === 'dev' || v === 'developer' || v === 'develop' || v === 'development') return 'dev';
|
|
6
|
+
return '';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function normalizeServerComponent(raw) {
|
|
10
|
+
const v = (raw ?? '').trim().toLowerCase();
|
|
11
|
+
if (!v) return '';
|
|
12
|
+
// Prefer Happier component IDs; accept legacy Happy IDs for backward compatibility.
|
|
13
|
+
if (v === 'light' || v === 'server-light' || v === 'happier-server-light' || v === 'happy-server-light') return 'happier-server-light';
|
|
14
|
+
if (v === 'server' || v === 'full' || v === 'happier-server' || v === 'happy-server') return 'happier-server';
|
|
15
|
+
return '';
|
|
16
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { commandExists } from '../proc/commands.mjs';
|
|
2
|
+
|
|
3
|
+
function formatMissingTool({ name, why, install }) {
|
|
4
|
+
return [`- ${name}: ${why}`, ...(install?.length ? install.map((l) => ` ${l}`) : [])].join('\n');
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export async function assertCliPrereqs({
|
|
8
|
+
git = false,
|
|
9
|
+
yarn = false,
|
|
10
|
+
codex = false,
|
|
11
|
+
coderabbit = false,
|
|
12
|
+
augment = false,
|
|
13
|
+
claude = false,
|
|
14
|
+
} = {}) {
|
|
15
|
+
const missing = [];
|
|
16
|
+
|
|
17
|
+
if (git) {
|
|
18
|
+
const hasGit = await commandExists('git');
|
|
19
|
+
if (!hasGit) {
|
|
20
|
+
const install =
|
|
21
|
+
process.platform === 'darwin'
|
|
22
|
+
? ['Install Xcode Command Line Tools: `xcode-select --install`', 'Or install Git via Homebrew: `brew install git`']
|
|
23
|
+
: ['Install Git using your package manager (e.g. `apt install git`, `dnf install git`)'];
|
|
24
|
+
missing.push({
|
|
25
|
+
name: 'git',
|
|
26
|
+
why: 'required for cloning + updating PR worktrees',
|
|
27
|
+
install,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (yarn) {
|
|
33
|
+
const hasYarn = await commandExists('yarn');
|
|
34
|
+
if (!hasYarn) {
|
|
35
|
+
missing.push({
|
|
36
|
+
name: 'yarn',
|
|
37
|
+
why: 'required to install dependencies',
|
|
38
|
+
install: [
|
|
39
|
+
'Enable Corepack (recommended): `corepack enable`',
|
|
40
|
+
'Or install Yarn: `npm install -g yarn`',
|
|
41
|
+
],
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (codex) {
|
|
47
|
+
const hasCodex = await commandExists('codex');
|
|
48
|
+
if (!hasCodex) {
|
|
49
|
+
missing.push({
|
|
50
|
+
name: 'codex',
|
|
51
|
+
why: 'required to run Codex review',
|
|
52
|
+
install: [
|
|
53
|
+
'Install Codex CLI and ensure `codex` is on PATH',
|
|
54
|
+
'If using a managed install, ensure your PATH includes the Codex binary',
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (coderabbit) {
|
|
61
|
+
const hasCodeRabbit = await commandExists('coderabbit');
|
|
62
|
+
if (!hasCodeRabbit) {
|
|
63
|
+
missing.push({
|
|
64
|
+
name: 'coderabbit',
|
|
65
|
+
why: 'required to run CodeRabbit CLI review',
|
|
66
|
+
install: [
|
|
67
|
+
'Install CodeRabbit CLI: `curl -fsSL https://cli.coderabbit.ai/install.sh | sh`',
|
|
68
|
+
'Then authenticate: `coderabbit auth login`',
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (augment) {
|
|
75
|
+
const hasAuggie = await commandExists('auggie');
|
|
76
|
+
if (!hasAuggie) {
|
|
77
|
+
missing.push({
|
|
78
|
+
name: 'auggie',
|
|
79
|
+
why: 'required to run Augment (Auggie) review',
|
|
80
|
+
install: ['Install Auggie CLI: `npm install -g @augmentcode/auggie`', 'Then authenticate: `auggie login`'],
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (claude) {
|
|
86
|
+
const hasClaude = await commandExists('claude');
|
|
87
|
+
if (!hasClaude) {
|
|
88
|
+
missing.push({
|
|
89
|
+
name: 'claude',
|
|
90
|
+
why: 'required to run Claude Code review',
|
|
91
|
+
install: ['Install Claude Code CLI and ensure `claude` is on PATH', 'Then authenticate (if needed) with your Claude setup'],
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!missing.length) return;
|
|
97
|
+
|
|
98
|
+
throw new Error(
|
|
99
|
+
`[prereqs] missing required tools:\n` +
|
|
100
|
+
`${missing.map(formatMissingTool).join('\n')}\n\n` +
|
|
101
|
+
`[prereqs] After installing, re-run the command.`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { chmod, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
import { assertCliPrereqs } from './prereqs.mjs';
|
|
8
|
+
|
|
9
|
+
test('assertCliPrereqs({yarn:true}) accepts yarn', async () => {
|
|
10
|
+
const root = await mkdtemp(join(tmpdir(), 'hs-prereqs-yarn-'));
|
|
11
|
+
const oldPath = process.env.PATH;
|
|
12
|
+
try {
|
|
13
|
+
const yarnBin = join(root, 'yarn');
|
|
14
|
+
await writeFile(yarnBin, '#!/bin/sh\nexit 0\n', 'utf-8');
|
|
15
|
+
await chmod(yarnBin, 0o755);
|
|
16
|
+
process.env.PATH = `/bin:${root}`;
|
|
17
|
+
|
|
18
|
+
await assertCliPrereqs({ yarn: true });
|
|
19
|
+
} finally {
|
|
20
|
+
process.env.PATH = oldPath;
|
|
21
|
+
await rm(root, { recursive: true, force: true });
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('assertCliPrereqs({yarn:true}) throws when yarn is unavailable', async () => {
|
|
26
|
+
const oldPath = process.env.PATH;
|
|
27
|
+
try {
|
|
28
|
+
process.env.PATH = '/bin';
|
|
29
|
+
await assert.rejects(() => assertCliPrereqs({ yarn: true }), /yarn/i);
|
|
30
|
+
} finally {
|
|
31
|
+
process.env.PATH = oldPath;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { createWriteStream } from 'node:fs';
|
|
3
|
+
import { mkdir } from 'node:fs/promises';
|
|
4
|
+
import { dirname } from 'node:path';
|
|
5
|
+
import { ansiEnabled, cyan, dim, green, red, yellow } from '../ui/ansi.mjs';
|
|
6
|
+
|
|
7
|
+
function isTty() {
|
|
8
|
+
return Boolean(process.stdout.isTTY && process.stderr.isTTY);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function spinnerFrames() {
|
|
12
|
+
return ['|', '/', '-', '\\'];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function colorResult(result) {
|
|
16
|
+
if (!ansiEnabled()) return String(result);
|
|
17
|
+
const r = String(result);
|
|
18
|
+
if (r === '✓') return green(r);
|
|
19
|
+
if (r === 'x' || r === '✗') return red(r);
|
|
20
|
+
if (r === '!') return yellow(r);
|
|
21
|
+
return r;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function colorSpinner(frame) {
|
|
25
|
+
if (!ansiEnabled()) return String(frame);
|
|
26
|
+
return cyan(String(frame));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function createStepPrinter({ enabled = true } = {}) {
|
|
30
|
+
const tty = enabled && isTty();
|
|
31
|
+
const frames = spinnerFrames();
|
|
32
|
+
let timer = null;
|
|
33
|
+
let idx = 0;
|
|
34
|
+
let currentLine = '';
|
|
35
|
+
|
|
36
|
+
const write = (s) => process.stdout.write(s);
|
|
37
|
+
|
|
38
|
+
const start = (label) => {
|
|
39
|
+
if (!tty) {
|
|
40
|
+
write(`- [${dim('..')}] ${label}\n`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
currentLine = `- [${colorSpinner(frames[idx % frames.length])}] ${label}`;
|
|
44
|
+
write(currentLine);
|
|
45
|
+
timer = setInterval(() => {
|
|
46
|
+
idx++;
|
|
47
|
+
const next = `- [${colorSpinner(frames[idx % frames.length])}] ${label}`;
|
|
48
|
+
const pad = currentLine.length > next.length ? ' '.repeat(currentLine.length - next.length) : '';
|
|
49
|
+
currentLine = next;
|
|
50
|
+
write(`\r${next}${pad}`);
|
|
51
|
+
}, 120);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const stop = (result, label) => {
|
|
55
|
+
if (timer) clearInterval(timer);
|
|
56
|
+
timer = null;
|
|
57
|
+
if (!tty) {
|
|
58
|
+
write(`- [${colorResult(result)}] ${label}\n`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const out = `- [${colorResult(result)}] ${label}`;
|
|
62
|
+
const pad = currentLine.length > out.length ? ' '.repeat(currentLine.length - out.length) : '';
|
|
63
|
+
currentLine = '';
|
|
64
|
+
write(`\r${out}${pad}\n`);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const info = (line) => {
|
|
68
|
+
write(`${line}\n`);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return { start, stop, info };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function runCommandLogged({
|
|
75
|
+
label,
|
|
76
|
+
cmd,
|
|
77
|
+
args,
|
|
78
|
+
cwd,
|
|
79
|
+
env,
|
|
80
|
+
logPath,
|
|
81
|
+
showSteps = true,
|
|
82
|
+
quiet = true,
|
|
83
|
+
}) {
|
|
84
|
+
const steps = createStepPrinter({ enabled: showSteps });
|
|
85
|
+
if (quiet) {
|
|
86
|
+
await mkdir(dirname(logPath), { recursive: true }).catch(() => {});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
steps.start(label);
|
|
90
|
+
|
|
91
|
+
const child = spawn(cmd, args, {
|
|
92
|
+
cwd,
|
|
93
|
+
env,
|
|
94
|
+
stdio: quiet ? ['ignore', 'pipe', 'pipe'] : 'inherit',
|
|
95
|
+
shell: false,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
let stdout = '';
|
|
99
|
+
let stderr = '';
|
|
100
|
+
let logStream = null;
|
|
101
|
+
if (quiet) {
|
|
102
|
+
logStream = createWriteStream(logPath, { flags: 'a' });
|
|
103
|
+
child.stdout?.on('data', (d) => {
|
|
104
|
+
const s = d.toString();
|
|
105
|
+
stdout += s;
|
|
106
|
+
logStream?.write(s);
|
|
107
|
+
});
|
|
108
|
+
child.stderr?.on('data', (d) => {
|
|
109
|
+
const s = d.toString();
|
|
110
|
+
stderr += s;
|
|
111
|
+
logStream?.write(s);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const res = await new Promise((resolvePromise, rejectPromise) => {
|
|
116
|
+
child.on('error', rejectPromise);
|
|
117
|
+
child.on('close', (code, signal) => resolvePromise({ code: code ?? 1, signal: signal ?? null }));
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
logStream?.end();
|
|
122
|
+
} catch {
|
|
123
|
+
// ignore
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (res.code === 0) {
|
|
127
|
+
steps.stop('✓', label);
|
|
128
|
+
return { ok: true, code: 0, stdout, stderr, logPath };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
steps.stop('x', label);
|
|
132
|
+
const err = new Error(`${cmd} failed (code=${res.code}${res.signal ? `, sig=${res.signal}` : ''})`);
|
|
133
|
+
err.code = 'EEXIT';
|
|
134
|
+
err.exitCode = res.code;
|
|
135
|
+
err.signal = res.signal;
|
|
136
|
+
err.stdout = stdout;
|
|
137
|
+
err.stderr = stderr;
|
|
138
|
+
err.logPath = logPath;
|
|
139
|
+
throw err;
|
|
140
|
+
}
|
|
141
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { dirname } from 'node:path';
|
|
5
|
+
|
|
6
|
+
import { gethstackRegistry } from './cli_registry.mjs';
|
|
7
|
+
|
|
8
|
+
function cliRootDir() {
|
|
9
|
+
// scripts/utils/cli/* -> scripts/utils -> scripts -> repo root
|
|
10
|
+
return dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url)))));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function runOrThrow(label, args) {
|
|
14
|
+
const root = cliRootDir();
|
|
15
|
+
const bin = join(root, 'bin', 'hstack.mjs');
|
|
16
|
+
const res = spawnSync(process.execPath, [bin, ...args], { stdio: 'inherit', cwd: root, env: process.env });
|
|
17
|
+
if (res.status !== 0) {
|
|
18
|
+
throw new Error(`[smoke_help] failed (${label}): node bin/hstack.mjs ${args.join(' ')}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function visibleCommands() {
|
|
23
|
+
const { commands } = gethstackRegistry();
|
|
24
|
+
return commands.filter((c) => !c.hidden).map((c) => c.name);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function main() {
|
|
28
|
+
const cmds = visibleCommands();
|
|
29
|
+
for (const c of cmds) {
|
|
30
|
+
runOrThrow(`${c} --help`, [c, '--help']);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Also validate delegation path for a few key groups.
|
|
34
|
+
for (const c of ['wt', 'stack', 'srv', 'service', 'tailscale', 'self', 'menubar', 'completion', 'where', 'init', 'uninstall']) {
|
|
35
|
+
runOrThrow(`help ${c}`, ['help', c]);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
process.stdout.write('[smoke_help] ok\n');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
main().catch((err) => {
|
|
42
|
+
process.stderr.write(String(err?.message ?? err) + '\n');
|
|
43
|
+
process.exit(1);
|
|
44
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function getVerbosityLevel(env = process.env) {
|
|
2
|
+
const raw = (env.HAPPIER_STACK_VERBOSE ?? '').toString().trim();
|
|
3
|
+
if (!raw) return 0;
|
|
4
|
+
const n = Number(raw);
|
|
5
|
+
if (!Number.isFinite(n)) return 1;
|
|
6
|
+
return Math.max(0, Math.min(3, Math.floor(n)));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function isVerbose(env = process.env) {
|
|
10
|
+
return getVerbosityLevel(env) > 0;
|
|
11
|
+
}
|