@happier-dev/stack 0.1.0-preview.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +159 -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 +94 -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,484 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
6
|
+
import { tmpdir } from 'node:os';
|
|
7
|
+
import { dirname, join } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { resolveStackCredentialPaths } from './utils/auth/credentials_paths.mjs';
|
|
10
|
+
|
|
11
|
+
const scriptsDir = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const rootDir = dirname(scriptsDir);
|
|
13
|
+
|
|
14
|
+
function runNode(args, { cwd, env }) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
const proc = spawn(process.execPath, args, { cwd, env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
17
|
+
let stdout = '';
|
|
18
|
+
let stderr = '';
|
|
19
|
+
proc.stdout.on('data', (d) => (stdout += String(d)));
|
|
20
|
+
proc.stderr.on('data', (d) => (stderr += String(d)));
|
|
21
|
+
proc.on('error', reject);
|
|
22
|
+
proc.on('exit', (code, signal) => resolve({ code: code ?? (signal ? 1 : 0), signal: signal ?? null, stdout, stderr }));
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function runHstack(args, { env }) {
|
|
27
|
+
return runNode([join(rootDir, 'bin', 'hstack.mjs'), ...args], { cwd: rootDir, env });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function assertExitOk(res, context) {
|
|
31
|
+
assert.equal(res.code, 0, `expected exit 0 for ${context}, got ${res.code}\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function buildBaseEnv({ homeDir, storageDir, workspaceDir }) {
|
|
35
|
+
return {
|
|
36
|
+
...process.env,
|
|
37
|
+
HAPPIER_STACK_HOME_DIR: homeDir,
|
|
38
|
+
HAPPIER_STACK_STORAGE_DIR: storageDir,
|
|
39
|
+
HAPPIER_STACK_WORKSPACE_DIR: workspaceDir,
|
|
40
|
+
HAPPIER_STACK_CLI_ROOT_DISABLE: '1',
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function registerDaemonCleanup(t, { env, stackName, identity = '', includeNameFirst = false }) {
|
|
45
|
+
const identityArgs = identity ? [`--identity=${identity}`] : [];
|
|
46
|
+
const commandMatrix = [
|
|
47
|
+
['stack', 'daemon', stackName, 'stop', ...identityArgs, '--json'],
|
|
48
|
+
...(includeNameFirst ? [['stack', stackName, 'daemon', 'stop', ...identityArgs, '--json']] : []),
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
t.after(async () => {
|
|
52
|
+
for (const args of commandMatrix) {
|
|
53
|
+
await runHstack(args, { env });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function writeDummyAuth({ cliHomeDir }) {
|
|
59
|
+
await mkdir(cliHomeDir, { recursive: true });
|
|
60
|
+
await writeFile(join(cliHomeDir, 'access.key'), 'dummy\n', 'utf-8');
|
|
61
|
+
await writeFile(join(cliHomeDir, 'settings.json'), JSON.stringify({ machineId: 'test-machine' }) + '\n', 'utf-8');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function writeServerScopedAuth({ cliHomeDir, serverUrl }) {
|
|
65
|
+
const paths = resolveStackCredentialPaths({ cliHomeDir, serverUrl });
|
|
66
|
+
await mkdir(dirname(paths.serverScopedPath), { recursive: true });
|
|
67
|
+
await writeFile(paths.serverScopedPath, 'dummy\n', 'utf-8');
|
|
68
|
+
await writeFile(join(cliHomeDir, 'settings.json'), JSON.stringify({ machineId: 'test-machine' }) + '\n', 'utf-8');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function writeStubHappyCli({ cliDir }) {
|
|
72
|
+
await mkdir(join(cliDir, 'bin'), { recursive: true });
|
|
73
|
+
await mkdir(join(cliDir, 'dist'), { recursive: true });
|
|
74
|
+
|
|
75
|
+
const distScript = `
|
|
76
|
+
import { spawn } from 'node:child_process';
|
|
77
|
+
import { existsSync, readFileSync, writeFileSync, rmSync } from 'node:fs';
|
|
78
|
+
import { join } from 'node:path';
|
|
79
|
+
|
|
80
|
+
const args = process.argv.slice(2);
|
|
81
|
+
const home = process.env.HAPPIER_HOME_DIR || process.env.HAPPIER_STACK_CLI_HOME_DIR;
|
|
82
|
+
if (!home) {
|
|
83
|
+
console.error('missing HAPPIER_HOME_DIR');
|
|
84
|
+
process.exit(2);
|
|
85
|
+
}
|
|
86
|
+
const log = join(home, 'stub-daemon.log');
|
|
87
|
+
const state = join(home, 'daemon.state.json');
|
|
88
|
+
|
|
89
|
+
function append(line) {
|
|
90
|
+
try { writeFileSync(log, line + '\\n', { flag: 'a' }); } catch {}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (args[0] !== 'daemon') {
|
|
94
|
+
append('unknown:' + args.join(' '));
|
|
95
|
+
process.exit(0);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const sub = args[1] || '';
|
|
99
|
+
if (sub === 'stop') {
|
|
100
|
+
append('stop');
|
|
101
|
+
if (existsSync(state)) {
|
|
102
|
+
try {
|
|
103
|
+
const pid = Number(JSON.parse(readFileSync(state, 'utf-8')).pid);
|
|
104
|
+
if (Number.isFinite(pid) && pid > 1) {
|
|
105
|
+
try { process.kill(pid, 'SIGTERM'); } catch {}
|
|
106
|
+
}
|
|
107
|
+
} catch {}
|
|
108
|
+
try { rmSync(state); } catch {}
|
|
109
|
+
}
|
|
110
|
+
process.exit(0);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (sub === 'start') {
|
|
114
|
+
append('start');
|
|
115
|
+
// Capture resolved target server so integration tests can assert correct stack port selection.
|
|
116
|
+
append('server_url=' + String(process.env.HAPPIER_SERVER_URL || ''));
|
|
117
|
+
append('webapp_url=' + String(process.env.HAPPIER_WEBAPP_URL || ''));
|
|
118
|
+
const child = spawn(process.execPath, ['-e', 'setInterval(() => {}, 1000)'], { detached: true, stdio: 'ignore' });
|
|
119
|
+
child.unref();
|
|
120
|
+
writeFileSync(state, JSON.stringify({ pid: child.pid, httpPort: 0, startTime: new Date().toISOString() }) + '\\n', 'utf-8');
|
|
121
|
+
process.exit(0);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (sub === 'status') {
|
|
125
|
+
append('status');
|
|
126
|
+
let ok = false;
|
|
127
|
+
if (existsSync(state)) {
|
|
128
|
+
try {
|
|
129
|
+
const pid = Number(JSON.parse(readFileSync(state, 'utf-8')).pid);
|
|
130
|
+
if (Number.isFinite(pid) && pid > 1) {
|
|
131
|
+
try { process.kill(pid, 0); ok = true; } catch {}
|
|
132
|
+
}
|
|
133
|
+
} catch {}
|
|
134
|
+
}
|
|
135
|
+
console.log(ok ? 'daemon: running' : 'daemon: stopped');
|
|
136
|
+
process.exit(0);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
append('other:' + sub);
|
|
140
|
+
process.exit(0);
|
|
141
|
+
`;
|
|
142
|
+
|
|
143
|
+
await writeFile(join(cliDir, 'dist', 'index.mjs'), distScript.trimStart(), 'utf-8');
|
|
144
|
+
await writeFile(join(cliDir, 'bin', 'happier.mjs'), "import '../dist/index.mjs';\n", 'utf-8');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function ensureMinimalHappierMonorepo({ monoRoot }) {
|
|
148
|
+
await mkdir(join(monoRoot, 'apps', 'ui'), { recursive: true });
|
|
149
|
+
await mkdir(join(monoRoot, 'apps', 'cli'), { recursive: true });
|
|
150
|
+
await mkdir(join(monoRoot, 'apps', 'server'), { recursive: true });
|
|
151
|
+
await writeFile(join(monoRoot, 'apps', 'ui', 'package.json'), '{}\n', 'utf-8');
|
|
152
|
+
await writeFile(join(monoRoot, 'apps', 'cli', 'package.json'), '{}\n', 'utf-8');
|
|
153
|
+
await writeFile(join(monoRoot, 'apps', 'server', 'package.json'), '{}\n', 'utf-8');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function createDaemonFixture(t, { prefix, stackName = 'exp-test', serverPort = 4101 } = {}) {
|
|
157
|
+
const tmp = await mkdtemp(join(tmpdir(), prefix));
|
|
158
|
+
t.after(async () => {
|
|
159
|
+
await rm(tmp, { recursive: true, force: true });
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const storageDir = join(tmp, 'storage');
|
|
163
|
+
const homeDir = join(tmp, 'home');
|
|
164
|
+
const workspaceDir = join(tmp, 'workspace');
|
|
165
|
+
const monoRoot = join(workspaceDir, 'happier');
|
|
166
|
+
|
|
167
|
+
await ensureMinimalHappierMonorepo({ monoRoot });
|
|
168
|
+
await writeStubHappyCli({ cliDir: join(monoRoot, 'apps', 'cli') });
|
|
169
|
+
|
|
170
|
+
const stackCliHome = join(storageDir, stackName, 'cli');
|
|
171
|
+
await mkdir(stackCliHome, { recursive: true });
|
|
172
|
+
|
|
173
|
+
async function writeStackEnv({ name = stackName, cliHomeDir = stackCliHome, port = serverPort, repoDir = monoRoot } = {}) {
|
|
174
|
+
const envPath = join(storageDir, name, 'env');
|
|
175
|
+
await mkdir(dirname(envPath), { recursive: true });
|
|
176
|
+
await writeFile(
|
|
177
|
+
envPath,
|
|
178
|
+
[
|
|
179
|
+
`HAPPIER_STACK_REPO_DIR=${repoDir}`,
|
|
180
|
+
`HAPPIER_STACK_CLI_HOME_DIR=${cliHomeDir}`,
|
|
181
|
+
`HAPPIER_STACK_SERVER_PORT=${port}`,
|
|
182
|
+
'',
|
|
183
|
+
].join('\n'),
|
|
184
|
+
'utf-8'
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
storageDir,
|
|
190
|
+
stackName,
|
|
191
|
+
serverPort,
|
|
192
|
+
stackCliHome,
|
|
193
|
+
baseEnv: buildBaseEnv({ homeDir, storageDir, workspaceDir }),
|
|
194
|
+
writeStackEnv,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function readLogText(logPath) {
|
|
199
|
+
return await readFile(logPath, 'utf-8').then(String);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
test('hstack stack daemon <name> restart restarts only the daemon', async (t) => {
|
|
203
|
+
const fixture = await createDaemonFixture(t, {
|
|
204
|
+
prefix: 'happier-stack-daemon-',
|
|
205
|
+
stackName: 'exp-test',
|
|
206
|
+
serverPort: 4101,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
await writeDummyAuth({ cliHomeDir: fixture.stackCliHome });
|
|
210
|
+
await fixture.writeStackEnv();
|
|
211
|
+
registerDaemonCleanup(t, { env: fixture.baseEnv, stackName: fixture.stackName, includeNameFirst: true });
|
|
212
|
+
|
|
213
|
+
const startRes = await runHstack(['stack', 'daemon', fixture.stackName, 'start', '--json'], { env: fixture.baseEnv });
|
|
214
|
+
assertExitOk(startRes, 'stack daemon start');
|
|
215
|
+
|
|
216
|
+
const restartRes = await runHstack(['stack', 'daemon', fixture.stackName, 'restart', '--json'], { env: fixture.baseEnv });
|
|
217
|
+
assertExitOk(restartRes, 'stack daemon restart');
|
|
218
|
+
|
|
219
|
+
const logPath = join(fixture.stackCliHome, 'stub-daemon.log');
|
|
220
|
+
const logText = await readLogText(logPath);
|
|
221
|
+
assert.ok(logText.includes('stop'), `expected stub daemon stop to be called\n${logText}`);
|
|
222
|
+
assert.ok(logText.includes('start'), `expected stub daemon start to be called\n${logText}`);
|
|
223
|
+
assert.ok(logText.includes('status'), `expected stub daemon status to be called\n${logText}`);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test('hstack stack <name> daemon start works (stack name first)', async (t) => {
|
|
227
|
+
const fixture = await createDaemonFixture(t, {
|
|
228
|
+
prefix: 'happy-stacks-stack-daemon-name-first-',
|
|
229
|
+
stackName: 'exp-test',
|
|
230
|
+
serverPort: 4101,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
await writeDummyAuth({ cliHomeDir: fixture.stackCliHome });
|
|
234
|
+
await fixture.writeStackEnv();
|
|
235
|
+
registerDaemonCleanup(t, { env: fixture.baseEnv, stackName: fixture.stackName, includeNameFirst: true });
|
|
236
|
+
|
|
237
|
+
const startRes = await runHstack(['stack', fixture.stackName, 'daemon', 'start', '--json'], { env: fixture.baseEnv });
|
|
238
|
+
assertExitOk(startRes, 'stack <name> daemon start');
|
|
239
|
+
assert.ok(!startRes.stdout.includes('[stack] unknown command'), `unexpected unknown command output\n${startRes.stdout}`);
|
|
240
|
+
|
|
241
|
+
const logPath = join(fixture.stackCliHome, 'stub-daemon.log');
|
|
242
|
+
const logText = await readLogText(logPath);
|
|
243
|
+
assert.ok(logText.includes('start'), `expected stub daemon start to be called\n${logText}`);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('hstack stack daemon start accepts server-scoped credentials without legacy access.key', async (t) => {
|
|
247
|
+
const fixture = await createDaemonFixture(t, {
|
|
248
|
+
prefix: 'happy-stacks-stack-daemon-server-scoped-auth-',
|
|
249
|
+
stackName: 'exp-test',
|
|
250
|
+
serverPort: 4101,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
await writeServerScopedAuth({ cliHomeDir: fixture.stackCliHome, serverUrl: `http://127.0.0.1:${fixture.serverPort}` });
|
|
254
|
+
await fixture.writeStackEnv();
|
|
255
|
+
registerDaemonCleanup(t, { env: fixture.baseEnv, stackName: fixture.stackName });
|
|
256
|
+
|
|
257
|
+
const startRes = await runHstack(['stack', 'daemon', fixture.stackName, 'start', '--json'], { env: fixture.baseEnv });
|
|
258
|
+
assertExitOk(startRes, 'stack daemon start with server-scoped credentials');
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test('hstack stack daemon start rejects credentials scoped to a different server url', async (t) => {
|
|
262
|
+
const fixture = await createDaemonFixture(t, {
|
|
263
|
+
prefix: 'happy-stacks-stack-daemon-wrong-server-auth-',
|
|
264
|
+
stackName: 'exp-test',
|
|
265
|
+
serverPort: 4101,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
await writeServerScopedAuth({ cliHomeDir: fixture.stackCliHome, serverUrl: 'http://127.0.0.1:4999' });
|
|
269
|
+
await fixture.writeStackEnv();
|
|
270
|
+
registerDaemonCleanup(t, { env: fixture.baseEnv, stackName: fixture.stackName });
|
|
271
|
+
|
|
272
|
+
const startRes = await runHstack(['stack', 'daemon', fixture.stackName, 'start', '--json'], { env: fixture.baseEnv });
|
|
273
|
+
const logPath = join(fixture.stackCliHome, 'stub-daemon.log');
|
|
274
|
+
const logText = existsSync(logPath) ? await readLogText(logPath) : '';
|
|
275
|
+
|
|
276
|
+
assert.ok(
|
|
277
|
+
startRes.stdout.includes('"error": "auth_required"') || startRes.stdout.includes('"error":"auth_required"'),
|
|
278
|
+
`expected auth_required response for mismatched server-scoped credentials\nstdout:\n${startRes.stdout}\nstderr:\n${startRes.stderr}\nlog:\n${logText}`
|
|
279
|
+
);
|
|
280
|
+
assert.ok(
|
|
281
|
+
!logText.includes('start'),
|
|
282
|
+
`expected daemon start to be blocked for mismatched server-scoped credentials\nstdout:\n${startRes.stdout}\nstderr:\n${startRes.stderr}\nlog:\n${logText}`
|
|
283
|
+
);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test('hstack stack daemon <name> start/stop with --identity uses an isolated cli home dir', async (t) => {
|
|
287
|
+
const fixture = await createDaemonFixture(t, {
|
|
288
|
+
prefix: 'happy-stacks-stack-daemon-identity-',
|
|
289
|
+
stackName: 'exp-test',
|
|
290
|
+
serverPort: 4101,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const identity = 'account-b';
|
|
294
|
+
const identityHome = join(fixture.storageDir, fixture.stackName, 'cli-identities', identity);
|
|
295
|
+
await writeDummyAuth({ cliHomeDir: identityHome });
|
|
296
|
+
await fixture.writeStackEnv();
|
|
297
|
+
registerDaemonCleanup(t, { env: fixture.baseEnv, stackName: fixture.stackName, identity });
|
|
298
|
+
|
|
299
|
+
const startRes = await runHstack(
|
|
300
|
+
['stack', 'daemon', fixture.stackName, 'start', `--identity=${identity}`, '--json'],
|
|
301
|
+
{ env: fixture.baseEnv }
|
|
302
|
+
);
|
|
303
|
+
assertExitOk(startRes, 'stack daemon start with identity');
|
|
304
|
+
|
|
305
|
+
const logPath = join(identityHome, 'stub-daemon.log');
|
|
306
|
+
const logText = await readLogText(logPath);
|
|
307
|
+
assert.ok(logText.includes('start'), `expected stub daemon start to be called in identity home\n${logText}`);
|
|
308
|
+
|
|
309
|
+
const stopRes = await runHstack(
|
|
310
|
+
['stack', 'daemon', fixture.stackName, 'stop', `--identity=${identity}`, '--json'],
|
|
311
|
+
{ env: fixture.baseEnv }
|
|
312
|
+
);
|
|
313
|
+
assertExitOk(stopRes, 'stack daemon stop with identity');
|
|
314
|
+
|
|
315
|
+
const logTextAfter = await readLogText(logPath);
|
|
316
|
+
assert.ok(logTextAfter.includes('stop'), `expected stub daemon stop to be called for identity\n${logTextAfter}`);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test('hstack daemon status targets main stack', async (t) => {
|
|
320
|
+
const fixture = await createDaemonFixture(t, {
|
|
321
|
+
prefix: 'happy-stacks-main-daemon-shortcut-',
|
|
322
|
+
stackName: 'main',
|
|
323
|
+
serverPort: 4101,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
await writeDummyAuth({ cliHomeDir: fixture.stackCliHome });
|
|
327
|
+
await fixture.writeStackEnv();
|
|
328
|
+
registerDaemonCleanup(t, { env: fixture.baseEnv, stackName: fixture.stackName });
|
|
329
|
+
|
|
330
|
+
const startRes = await runHstack(['stack', 'daemon', fixture.stackName, 'start', '--json'], { env: fixture.baseEnv });
|
|
331
|
+
assertExitOk(startRes, 'stack daemon start (main)');
|
|
332
|
+
|
|
333
|
+
const statusRes = await runHstack(['daemon', 'status', '--json'], { env: fixture.baseEnv });
|
|
334
|
+
assertExitOk(statusRes, 'daemon status shortcut');
|
|
335
|
+
|
|
336
|
+
const logPath = join(fixture.stackCliHome, 'stub-daemon.log');
|
|
337
|
+
const logText = await readLogText(logPath);
|
|
338
|
+
assert.ok(logText.includes('status'), `expected stub daemon status to be called\n${logText}`);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test('hstack stack daemon <name> status does not include global process inventory', async (t) => {
|
|
342
|
+
const fixture = await createDaemonFixture(t, {
|
|
343
|
+
prefix: 'happy-stacks-daemon-status-scope-',
|
|
344
|
+
stackName: 'exp-test',
|
|
345
|
+
serverPort: 4101,
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
await writeDummyAuth({ cliHomeDir: fixture.stackCliHome });
|
|
349
|
+
await fixture.writeStackEnv();
|
|
350
|
+
registerDaemonCleanup(t, { env: fixture.baseEnv, stackName: fixture.stackName });
|
|
351
|
+
|
|
352
|
+
const startRes = await runHstack(['stack', 'daemon', fixture.stackName, 'start', '--json'], { env: fixture.baseEnv });
|
|
353
|
+
assertExitOk(startRes, 'stack daemon start for scoped status');
|
|
354
|
+
|
|
355
|
+
const statusRes = await runHstack(['stack', 'daemon', fixture.stackName, 'status', '--json'], { env: fixture.baseEnv });
|
|
356
|
+
assertExitOk(statusRes, 'stack daemon status');
|
|
357
|
+
|
|
358
|
+
const parsed = JSON.parse(statusRes.stdout.trim());
|
|
359
|
+
const statusText = String(parsed?.status ?? '');
|
|
360
|
+
assert.equal(
|
|
361
|
+
statusText.includes('🔍 All Happier CLI Processes'),
|
|
362
|
+
false,
|
|
363
|
+
`expected stack-scoped daemon status to omit global process inventory\n${statusText}`
|
|
364
|
+
);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
test('hstack stack daemon <name> status falls back when cli dist entrypoint is missing', async (t) => {
|
|
368
|
+
const fixture = await createDaemonFixture(t, {
|
|
369
|
+
prefix: 'happy-stacks-daemon-status-fallback-',
|
|
370
|
+
stackName: 'exp-test',
|
|
371
|
+
serverPort: 4101,
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
await writeDummyAuth({ cliHomeDir: fixture.stackCliHome });
|
|
375
|
+
await fixture.writeStackEnv();
|
|
376
|
+
registerDaemonCleanup(t, { env: fixture.baseEnv, stackName: fixture.stackName });
|
|
377
|
+
|
|
378
|
+
const startRes = await runHstack(['stack', 'daemon', fixture.stackName, 'start', '--json'], { env: fixture.baseEnv });
|
|
379
|
+
assertExitOk(startRes, 'stack daemon start for fallback status');
|
|
380
|
+
|
|
381
|
+
const distEntrypoint = join(fixture.baseEnv.HAPPIER_STACK_WORKSPACE_DIR, 'happier', 'apps', 'cli', 'dist', 'index.mjs');
|
|
382
|
+
await rm(distEntrypoint, { force: true });
|
|
383
|
+
|
|
384
|
+
const statusRes = await runHstack(['stack', 'daemon', fixture.stackName, 'status', '--json'], { env: fixture.baseEnv });
|
|
385
|
+
assertExitOk(statusRes, 'stack daemon status fallback');
|
|
386
|
+
|
|
387
|
+
const parsed = JSON.parse(statusRes.stdout.trim());
|
|
388
|
+
const statusText = String(parsed?.status ?? '');
|
|
389
|
+
assert.ok(
|
|
390
|
+
statusText.includes('Fallback status used because CLI dist entrypoint is missing'),
|
|
391
|
+
`expected fallback marker in daemon status output\n${statusText}`
|
|
392
|
+
);
|
|
393
|
+
assert.ok(
|
|
394
|
+
statusText.includes('Daemon Status'),
|
|
395
|
+
`expected daemon status section in fallback output\n${statusText}`
|
|
396
|
+
);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test('hstack stack daemon <name> start uses runtime server port when env port is missing', async (t) => {
|
|
400
|
+
const fixture = await createDaemonFixture(t, {
|
|
401
|
+
prefix: 'happy-stacks-stack-daemon-runtime-port-',
|
|
402
|
+
stackName: 'exp-test',
|
|
403
|
+
serverPort: 4101,
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
await writeDummyAuth({ cliHomeDir: fixture.stackCliHome });
|
|
407
|
+
|
|
408
|
+
// Write a stack env *without* HAPPIER_STACK_SERVER_PORT so the command must fall back to runtime state.
|
|
409
|
+
await fixture.writeStackEnv({ port: '' });
|
|
410
|
+
|
|
411
|
+
// Create a runtime state file that indicates the stack server is running on fixture.serverPort.
|
|
412
|
+
const serverStub = spawn(process.execPath, ['-e', 'setInterval(() => {}, 1000)'], { stdio: 'ignore' });
|
|
413
|
+
t.after(() => {
|
|
414
|
+
try {
|
|
415
|
+
serverStub.kill('SIGTERM');
|
|
416
|
+
} catch {
|
|
417
|
+
// ignore
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
const runtimePath = join(fixture.storageDir, fixture.stackName, 'stack.runtime.json');
|
|
422
|
+
await writeFile(
|
|
423
|
+
runtimePath,
|
|
424
|
+
JSON.stringify(
|
|
425
|
+
{
|
|
426
|
+
version: 1,
|
|
427
|
+
stackName: fixture.stackName,
|
|
428
|
+
ephemeral: true,
|
|
429
|
+
ports: { server: fixture.serverPort },
|
|
430
|
+
processes: { serverPid: serverStub.pid },
|
|
431
|
+
},
|
|
432
|
+
null,
|
|
433
|
+
2
|
|
434
|
+
) + '\n',
|
|
435
|
+
'utf-8'
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
registerDaemonCleanup(t, { env: fixture.baseEnv, stackName: fixture.stackName });
|
|
439
|
+
|
|
440
|
+
const startRes = await runHstack(['stack', 'daemon', fixture.stackName, 'start', '--json'], { env: fixture.baseEnv });
|
|
441
|
+
assertExitOk(startRes, 'stack daemon start uses runtime port');
|
|
442
|
+
|
|
443
|
+
const logPath = join(fixture.stackCliHome, 'stub-daemon.log');
|
|
444
|
+
const logText = await readLogText(logPath);
|
|
445
|
+
assert.ok(
|
|
446
|
+
logText.includes(`server_url=http://127.0.0.1:${fixture.serverPort}`),
|
|
447
|
+
`expected daemon env to target runtime port ${fixture.serverPort}\n${logText}`
|
|
448
|
+
);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
test('hstack stack auth <name> login --identity=<name> --print prints identity-scoped HAPPIER_HOME_DIR', async (t) => {
|
|
452
|
+
const fixture = await createDaemonFixture(t, {
|
|
453
|
+
prefix: 'happier-stack-auth-identity-',
|
|
454
|
+
stackName: 'exp-test',
|
|
455
|
+
serverPort: 4101,
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
const identity = 'account-b';
|
|
459
|
+
await writeDummyAuth({ cliHomeDir: fixture.stackCliHome });
|
|
460
|
+
await fixture.writeStackEnv();
|
|
461
|
+
|
|
462
|
+
const res = await runHstack(
|
|
463
|
+
[
|
|
464
|
+
'stack',
|
|
465
|
+
'auth',
|
|
466
|
+
fixture.stackName,
|
|
467
|
+
'login',
|
|
468
|
+
`--identity=${identity}`,
|
|
469
|
+
'--no-open',
|
|
470
|
+
'--print',
|
|
471
|
+
'--json',
|
|
472
|
+
],
|
|
473
|
+
{ env: fixture.baseEnv }
|
|
474
|
+
);
|
|
475
|
+
assertExitOk(res, 'stack auth login --identity --print');
|
|
476
|
+
|
|
477
|
+
const parsed = JSON.parse(res.stdout.trim());
|
|
478
|
+
assert.equal(parsed?.cliIdentity, identity);
|
|
479
|
+
assert.ok(
|
|
480
|
+
parsed?.cmd?.includes(`HAPPIER_HOME_DIR="${join(fixture.storageDir, fixture.stackName, 'cli-identities', identity)}"`),
|
|
481
|
+
`expected printed cmd to include identity home dir\n${parsed?.cmd}`
|
|
482
|
+
);
|
|
483
|
+
assert.ok(parsed?.cmd?.includes('--no-open'), `expected printed cmd to include --no-open\n${parsed?.cmd}`);
|
|
484
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
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
|
+
import { runNodeCapture } from './testkit/stack_script_command_testkit.mjs';
|
|
8
|
+
|
|
9
|
+
test('hstack stack eas help routing covers explicit, implicit, and missing-stack branches', async (t) => {
|
|
10
|
+
const scriptsDir = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const rootDir = dirname(scriptsDir);
|
|
12
|
+
const tmp = await mkdtemp(join(tmpdir(), 'hstack-stack-eas-help-'));
|
|
13
|
+
const storageDir = join(tmp, 'storage');
|
|
14
|
+
const homeDir = join(tmp, 'home');
|
|
15
|
+
const stackName = 'exp-eas-help';
|
|
16
|
+
const envPath = join(storageDir, stackName, 'env');
|
|
17
|
+
await mkdir(dirname(envPath), { recursive: true });
|
|
18
|
+
await writeFile(envPath, 'HAPPIER_STACK_SERVER_COMPONENT=happier-server-light\n', 'utf-8');
|
|
19
|
+
|
|
20
|
+
t.after(async () => {
|
|
21
|
+
await rm(tmp, { recursive: true, force: true }).catch(() => {});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const env = {
|
|
25
|
+
...process.env,
|
|
26
|
+
HAPPIER_STACK_HOME_DIR: homeDir,
|
|
27
|
+
HAPPIER_STACK_STORAGE_DIR: storageDir,
|
|
28
|
+
HSTACK_EAS_TEST_STUB: '1',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const cases = [
|
|
32
|
+
{
|
|
33
|
+
name: 'explicit --help',
|
|
34
|
+
args: ['eas', stackName, '--help'],
|
|
35
|
+
expectedCode: 0,
|
|
36
|
+
expectStdout: (stdout) => {
|
|
37
|
+
assert.match(stdout, /^\[eas\] usage:/m, stdout);
|
|
38
|
+
assert.ok(!stdout.includes('[stack] usage:'), stdout);
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'implicit help with no subcommand',
|
|
43
|
+
args: ['eas', stackName],
|
|
44
|
+
expectedCode: 0,
|
|
45
|
+
expectStdout: (stdout) => {
|
|
46
|
+
assert.match(stdout, /^\[eas\] usage:/m, stdout);
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'unknown stack returns stack-scoped env error',
|
|
51
|
+
args: ['eas', 'missing-stack', 'whoami'],
|
|
52
|
+
expectedCode: 1,
|
|
53
|
+
expectStdout: (stdout) => {
|
|
54
|
+
assert.equal(stdout, '', stdout);
|
|
55
|
+
},
|
|
56
|
+
expectStderr: (stderr) => {
|
|
57
|
+
assert.match(stderr, /^\[stack\] failed: \[stack\] stack "missing-stack" does not exist yet\./m, stderr);
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
for (const testCase of cases) {
|
|
63
|
+
const res = await runNodeCapture([join(rootDir, 'scripts', 'stack.mjs'), ...testCase.args], { cwd: rootDir, env });
|
|
64
|
+
assert.equal(
|
|
65
|
+
res.code,
|
|
66
|
+
testCase.expectedCode,
|
|
67
|
+
`${testCase.name}: expected exit ${testCase.expectedCode}, got ${res.code}\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`
|
|
68
|
+
);
|
|
69
|
+
testCase.expectStdout(res.stdout);
|
|
70
|
+
testCase.expectStderr?.(res.stderr);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
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 { writeStackCodeWorkspace } from './utils/stack/editor_workspace.mjs';
|
|
9
|
+
|
|
10
|
+
async function createMonorepoCheckout(rootDir) {
|
|
11
|
+
await mkdir(join(rootDir, 'apps', 'ui'), { recursive: true });
|
|
12
|
+
await mkdir(join(rootDir, 'apps', 'cli'), { recursive: true });
|
|
13
|
+
await mkdir(join(rootDir, 'apps', 'server'), { recursive: true });
|
|
14
|
+
await writeFile(join(rootDir, 'apps', 'ui', 'package.json'), '{}\n', 'utf-8');
|
|
15
|
+
await writeFile(join(rootDir, 'apps', 'cli', 'package.json'), '{}\n', 'utf-8');
|
|
16
|
+
await writeFile(join(rootDir, 'apps', 'server', 'package.json'), '{}\n', 'utf-8');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function withStackEnvDirectories(tmp, callback) {
|
|
20
|
+
const scriptsDir = dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const rootDir = dirname(scriptsDir);
|
|
22
|
+
const storageDir = join(tmp, 'storage');
|
|
23
|
+
const homeDir = join(tmp, 'home');
|
|
24
|
+
|
|
25
|
+
const prevStorage = process.env.HAPPIER_STACK_STORAGE_DIR;
|
|
26
|
+
const prevHome = process.env.HAPPIER_STACK_HOME_DIR;
|
|
27
|
+
process.env.HAPPIER_STACK_STORAGE_DIR = storageDir;
|
|
28
|
+
process.env.HAPPIER_STACK_HOME_DIR = homeDir;
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
await callback({ rootDir, storageDir });
|
|
32
|
+
} finally {
|
|
33
|
+
if (prevStorage == null) delete process.env.HAPPIER_STACK_STORAGE_DIR;
|
|
34
|
+
else process.env.HAPPIER_STACK_STORAGE_DIR = prevStorage;
|
|
35
|
+
if (prevHome == null) delete process.env.HAPPIER_STACK_HOME_DIR;
|
|
36
|
+
else process.env.HAPPIER_STACK_HOME_DIR = prevHome;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
test('stack code workspace groups monorepo components to the monorepo root', async (t) => {
|
|
41
|
+
const tmp = await mkdtemp(join(tmpdir(), 'happier-stack-workspace-mono-'));
|
|
42
|
+
t.after(async () => {
|
|
43
|
+
await rm(tmp, { recursive: true, force: true }).catch(() => {});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
await withStackEnvDirectories(tmp, async ({ rootDir, storageDir }) => {
|
|
47
|
+
const stackName = 'exp-test';
|
|
48
|
+
const monoRoot = join(tmp, 'mono');
|
|
49
|
+
await createMonorepoCheckout(monoRoot);
|
|
50
|
+
|
|
51
|
+
const envPath = join(storageDir, stackName, 'env');
|
|
52
|
+
await mkdir(dirname(envPath), { recursive: true });
|
|
53
|
+
await writeFile(
|
|
54
|
+
envPath,
|
|
55
|
+
['HAPPIER_STACK_SERVER_COMPONENT=happier-server', `HAPPIER_STACK_REPO_DIR=${monoRoot}`, ''].join('\n'),
|
|
56
|
+
'utf-8'
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const ws = await writeStackCodeWorkspace({
|
|
60
|
+
rootDir,
|
|
61
|
+
stackName,
|
|
62
|
+
includeStackDir: false,
|
|
63
|
+
includeAllComponents: false,
|
|
64
|
+
includeCliHome: false,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
assert.equal(ws.folders.length, 1);
|
|
68
|
+
assert.equal(ws.folders[0].path, monoRoot);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('stack code workspace normalizes nested monorepo package path to monorepo root', async (t) => {
|
|
73
|
+
const tmp = await mkdtemp(join(tmpdir(), 'happier-stack-workspace-mono-subdir-'));
|
|
74
|
+
t.after(async () => {
|
|
75
|
+
await rm(tmp, { recursive: true, force: true }).catch(() => {});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
await withStackEnvDirectories(tmp, async ({ rootDir, storageDir }) => {
|
|
79
|
+
const stackName = 'exp-subdir';
|
|
80
|
+
const monoRoot = join(tmp, 'mono');
|
|
81
|
+
await createMonorepoCheckout(monoRoot);
|
|
82
|
+
|
|
83
|
+
const envPath = join(storageDir, stackName, 'env');
|
|
84
|
+
await mkdir(dirname(envPath), { recursive: true });
|
|
85
|
+
await writeFile(
|
|
86
|
+
envPath,
|
|
87
|
+
['HAPPIER_STACK_SERVER_COMPONENT=happier-server-light', `HAPPIER_STACK_REPO_DIR=${join(monoRoot, 'apps', 'ui')}`, ''].join('\n'),
|
|
88
|
+
'utf-8'
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const ws = await writeStackCodeWorkspace({
|
|
92
|
+
rootDir,
|
|
93
|
+
stackName,
|
|
94
|
+
includeStackDir: false,
|
|
95
|
+
includeAllComponents: false,
|
|
96
|
+
includeCliHome: false,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
assert.equal(ws.folders.length, 1);
|
|
100
|
+
assert.equal(ws.folders[0].path, monoRoot);
|
|
101
|
+
});
|
|
102
|
+
});
|