@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,58 @@
|
|
|
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 { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
import { detectPackageManagerCmd, pickFirstScript, readPackageJsonScripts } from './package_scripts.mjs';
|
|
8
|
+
|
|
9
|
+
test('detectPackageManagerCmd prefers yarn when run from a Happy monorepo package dir (packages/ layout)', async (t) => {
|
|
10
|
+
const root = await mkdtemp(join(tmpdir(), 'hs-package-scripts-happy-monorepo-'));
|
|
11
|
+
t.after(async () => {
|
|
12
|
+
await rm(root, { recursive: true, force: true });
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// Minimal monorepo markers + yarn.lock at the monorepo root.
|
|
16
|
+
await mkdir(join(root, 'apps', 'ui'), { recursive: true });
|
|
17
|
+
await mkdir(join(root, 'apps', 'cli'), { recursive: true });
|
|
18
|
+
await mkdir(join(root, 'apps', 'server'), { recursive: true });
|
|
19
|
+
await writeFile(join(root, 'apps', 'ui', 'package.json'), '{}\n', 'utf-8');
|
|
20
|
+
await writeFile(join(root, 'apps', 'cli', 'package.json'), '{}\n', 'utf-8');
|
|
21
|
+
await writeFile(join(root, 'apps', 'server', 'package.json'), '{}\n', 'utf-8');
|
|
22
|
+
await writeFile(join(root, 'package.json'), '{ "name": "monorepo", "private": true }\n', 'utf-8');
|
|
23
|
+
await writeFile(join(root, 'yarn.lock'), '# yarn\n', 'utf-8');
|
|
24
|
+
|
|
25
|
+
const pm = await detectPackageManagerCmd(join(root, 'apps', 'server'));
|
|
26
|
+
assert.equal(pm.name, 'yarn');
|
|
27
|
+
assert.deepEqual(pm.argsForScript('test'), ['-s', 'test']);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('detectPackageManagerCmd defaults to yarn when no lockfile/monorepo markers are present', async (t) => {
|
|
31
|
+
const root = await mkdtemp(join(tmpdir(), 'hs-package-scripts-default-'));
|
|
32
|
+
t.after(async () => {
|
|
33
|
+
await rm(root, { recursive: true, force: true });
|
|
34
|
+
});
|
|
35
|
+
await writeFile(join(root, 'package.json'), '{ "name": "single-package" }\n', 'utf-8');
|
|
36
|
+
|
|
37
|
+
const pm = await detectPackageManagerCmd(root);
|
|
38
|
+
assert.equal(pm.name, 'yarn');
|
|
39
|
+
assert.deepEqual(pm.argsForScript('lint'), ['-s', 'lint']);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('readPackageJsonScripts returns scripts object or null for missing package.json', async (t) => {
|
|
43
|
+
const root = await mkdtemp(join(tmpdir(), 'hs-package-scripts-read-'));
|
|
44
|
+
t.after(async () => {
|
|
45
|
+
await rm(root, { recursive: true, force: true });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
assert.equal(await readPackageJsonScripts(root), null);
|
|
49
|
+
await writeFile(join(root, 'package.json'), JSON.stringify({ scripts: { test: 'vitest', lint: 'eslint .' } }), 'utf-8');
|
|
50
|
+
assert.deepEqual(await readPackageJsonScripts(root), { test: 'vitest', lint: 'eslint .' });
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('pickFirstScript returns the first non-empty script in candidate order', () => {
|
|
54
|
+
const scripts = { test: ' ', check: 'vitest', lint: 'eslint .' };
|
|
55
|
+
assert.equal(pickFirstScript(scripts, ['test', 'check', 'lint']), 'check');
|
|
56
|
+
assert.equal(pickFirstScript(scripts, ['missing', 'lint']), 'lint');
|
|
57
|
+
assert.equal(pickFirstScript(scripts, ['missing']), null);
|
|
58
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export async function runWithConcurrencyLimit({ items, limit, fn }) {
|
|
2
|
+
const list = Array.isArray(items) ? items : [];
|
|
3
|
+
const max = Number(limit);
|
|
4
|
+
const concurrency = Number.isFinite(max) && max > 0 ? Math.floor(max) : 4;
|
|
5
|
+
|
|
6
|
+
const results = new Array(list.length);
|
|
7
|
+
let nextIndex = 0;
|
|
8
|
+
|
|
9
|
+
const worker = async () => {
|
|
10
|
+
while (true) {
|
|
11
|
+
const i = nextIndex;
|
|
12
|
+
nextIndex += 1;
|
|
13
|
+
if (i >= list.length) return;
|
|
14
|
+
results[i] = await fn(list[i], i);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const workers = [];
|
|
19
|
+
for (let i = 0; i < Math.min(concurrency, list.length); i++) {
|
|
20
|
+
workers.push(worker());
|
|
21
|
+
}
|
|
22
|
+
await Promise.all(workers);
|
|
23
|
+
return results;
|
|
24
|
+
}
|
|
25
|
+
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { dirname, join, resolve, sep } from 'node:path';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { chmod, mkdir, readFile, readdir, rename, rm, stat, unlink, writeFile } from 'node:fs/promises';
|
|
5
|
+
import { createHash } from 'node:crypto';
|
|
6
|
+
|
|
7
|
+
import { pathExists } from '../fs/fs.mjs';
|
|
8
|
+
import { readJsonIfExists, writeJsonAtomic } from '../fs/json.mjs';
|
|
9
|
+
import { run, runCapture, spawnProc } from './proc.mjs';
|
|
10
|
+
import { commandExists } from './commands.mjs';
|
|
11
|
+
import { coerceHappyMonorepoRootFromPath, getDefaultAutostartPaths, getHappyStacksHomeDir } from '../paths/paths.mjs';
|
|
12
|
+
import { resolveInstalledPath, resolveInstalledCliRoot } from '../paths/runtime.mjs';
|
|
13
|
+
|
|
14
|
+
function sha256Hex(s) {
|
|
15
|
+
return createHash('sha256').update(String(s ?? ''), 'utf-8').digest('hex');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function resolveBuildStatePath({ label, dir }) {
|
|
19
|
+
const homeDir = getHappyStacksHomeDir();
|
|
20
|
+
const key = sha256Hex(resolve(dir));
|
|
21
|
+
return join(homeDir, 'cache', 'build', label, `${key}.json`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function computeGitWorktreeSignature(dir) {
|
|
25
|
+
try {
|
|
26
|
+
// Fast path: only if this is a git worktree.
|
|
27
|
+
const inside = (await runCapture('git', ['-C', dir, 'rev-parse', '--is-inside-work-tree'])).trim();
|
|
28
|
+
if (inside !== 'true') return null;
|
|
29
|
+
const head = (await runCapture('git', ['-C', dir, 'rev-parse', 'HEAD'])).trim();
|
|
30
|
+
// Includes staged + unstaged + untracked changes; captures “dirty” vs “clean”.
|
|
31
|
+
const status = await runCapture('git', ['-C', dir, 'status', '--porcelain=v1']);
|
|
32
|
+
return {
|
|
33
|
+
kind: 'git',
|
|
34
|
+
head,
|
|
35
|
+
statusHash: sha256Hex(status),
|
|
36
|
+
signature: sha256Hex(`${head}\n${status}`),
|
|
37
|
+
};
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function getComponentPm(dir, env = process.env) {
|
|
44
|
+
const happyMonorepoRoot = await (async () => {
|
|
45
|
+
try {
|
|
46
|
+
return coerceHappyMonorepoRootFromPath(dir);
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
})();
|
|
51
|
+
void happyMonorepoRoot;
|
|
52
|
+
|
|
53
|
+
// IMPORTANT: probe yarn with cwd=componentDir; yarn can be blocked depending on Corepack context.
|
|
54
|
+
if (await commandExists('yarn', { cwd: dir, env })) {
|
|
55
|
+
return { name: 'yarn', cmd: 'yarn' };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const binaryMode = String(env.HAPPIER_STACK_BINARY_MODE ?? '').trim() === '1'
|
|
59
|
+
|| String(env.HAPPIER_STACK_INSTALL_SOURCE ?? '').trim() === 'binary';
|
|
60
|
+
if (binaryMode && (await commandExists('npm', { cwd: dir, env }))) {
|
|
61
|
+
return { name: 'npm', cmd: 'npm' };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
throw new Error(`[local] yarn is required for component at ${dir}. Install it via Corepack: \`corepack enable\``);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const _yarnReadyKeys = new Set();
|
|
68
|
+
|
|
69
|
+
async function ensureYarnReady({ dir, env, quiet = false }) {
|
|
70
|
+
const e = env && typeof env === 'object' ? env : process.env;
|
|
71
|
+
// In stack mode we isolate HOME/cache; key by effective HOME+XDG cache so we only do this once.
|
|
72
|
+
const key = `${resolve(dir)}|${String(e.HOME ?? '')}|${String(e.XDG_CACHE_HOME ?? '')}`;
|
|
73
|
+
if (_yarnReadyKeys.has(key)) return;
|
|
74
|
+
|
|
75
|
+
// If stdin isn't a TTY (e.g. `hstack tui ...` uses stdio:ignore for child stdin),
|
|
76
|
+
// Corepack prompts can deadlock. Provide a single "yes" to unblock initial downloads.
|
|
77
|
+
const isTui = (e.HAPPIER_STACK_TUI ?? '').toString().trim() === '1';
|
|
78
|
+
// Also auto-yes in quiet mode so guided flows don't get stuck on:
|
|
79
|
+
// "Corepack is about to download ... Do you want to continue? [Y/n]"
|
|
80
|
+
const autoYes = isTui || !process.stdin.isTTY || quiet;
|
|
81
|
+
const stdio = quiet ? 'ignore' : 'inherit';
|
|
82
|
+
await run('yarn', ['--version'], { cwd: dir, env: e, stdio, ...(autoYes ? { input: 'y\n' } : {}) });
|
|
83
|
+
_yarnReadyKeys.add(key);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function requireDir(label, dir) {
|
|
87
|
+
if (await pathExists(dir)) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
throw new Error(
|
|
91
|
+
`[local] missing ${label} at ${dir}\n` +
|
|
92
|
+
`Run: hstack setup (or hstack bootstrap) to clone the Happier monorepo into your workspace.`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function resolveStackCacheBaseDirFromEnv(env) {
|
|
97
|
+
const envFile = (env.HAPPIER_STACK_ENV_FILE ?? '').toString().trim();
|
|
98
|
+
if (!envFile) return null;
|
|
99
|
+
try {
|
|
100
|
+
return join(dirname(envFile), 'cache');
|
|
101
|
+
} catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function applyStackCacheEnv(baseEnv) {
|
|
107
|
+
const env = { ...(baseEnv && typeof baseEnv === 'object' ? baseEnv : process.env) };
|
|
108
|
+
const envFile = (env.HAPPIER_STACK_ENV_FILE ?? '').toString().trim();
|
|
109
|
+
const stackCacheBase = resolveStackCacheBaseDirFromEnv(env);
|
|
110
|
+
if (!stackCacheBase) return env;
|
|
111
|
+
|
|
112
|
+
// Prisma engines currently default to ~/.cache/prisma (via os.homedir()).
|
|
113
|
+
// In stack mode, isolate HOME for package-manager driven commands so Prisma/Yarn/NPM don't
|
|
114
|
+
// depend on global home caches (and so sandboxed runs can succeed).
|
|
115
|
+
const isolateHomeRaw = (env.HAPPIER_STACK_PM_ISOLATE_HOME ?? '').toString().trim();
|
|
116
|
+
const isolateHome = isolateHomeRaw ? isolateHomeRaw !== '0' : true;
|
|
117
|
+
if (isolateHome && envFile) {
|
|
118
|
+
const stackHome = join(dirname(envFile), 'home');
|
|
119
|
+
env.HOME = stackHome;
|
|
120
|
+
env.USERPROFILE = stackHome;
|
|
121
|
+
try {
|
|
122
|
+
await mkdir(stackHome, { recursive: true });
|
|
123
|
+
} catch {
|
|
124
|
+
// best-effort
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!(env.XDG_CACHE_HOME ?? '').toString().trim()) {
|
|
129
|
+
env.XDG_CACHE_HOME = join(stackCacheBase, 'xdg');
|
|
130
|
+
}
|
|
131
|
+
if (!(env.YARN_CACHE_FOLDER ?? '').toString().trim()) {
|
|
132
|
+
env.YARN_CACHE_FOLDER = join(stackCacheBase, 'yarn');
|
|
133
|
+
}
|
|
134
|
+
if (!(env.npm_config_cache ?? '').toString().trim()) {
|
|
135
|
+
env.npm_config_cache = join(stackCacheBase, 'npm');
|
|
136
|
+
}
|
|
137
|
+
// Corepack caches downloaded package managers (like Yarn) under COREPACK_HOME.
|
|
138
|
+
// In stack mode we want this to be stable and writable so first-run downloads don't prompt/hang in TUI.
|
|
139
|
+
if (!(env.COREPACK_HOME ?? '').toString().trim()) {
|
|
140
|
+
env.COREPACK_HOME = join(stackCacheBase, 'corepack');
|
|
141
|
+
}
|
|
142
|
+
// Avoid Corepack mutating package.json by auto-adding a packageManager field.
|
|
143
|
+
// (This is safe and reduces noise when Corepack is used implicitly.)
|
|
144
|
+
if (!(env.COREPACK_ENABLE_AUTO_PIN ?? '').toString().trim()) {
|
|
145
|
+
env.COREPACK_ENABLE_AUTO_PIN = '0';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
await mkdir(env.XDG_CACHE_HOME, { recursive: true });
|
|
150
|
+
await mkdir(env.YARN_CACHE_FOLDER, { recursive: true });
|
|
151
|
+
await mkdir(env.npm_config_cache, { recursive: true });
|
|
152
|
+
await mkdir(env.COREPACK_HOME, { recursive: true });
|
|
153
|
+
} catch {
|
|
154
|
+
// best-effort
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return env;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export async function ensureDepsInstalled(dir, label, { quiet = false, env: envIn = process.env } = {}) {
|
|
161
|
+
const pkgJson = join(dir, 'package.json');
|
|
162
|
+
if (!(await pathExists(pkgJson))) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const nodeModules = join(dir, 'node_modules');
|
|
167
|
+
const stdio = quiet ? 'ignore' : 'inherit';
|
|
168
|
+
const env = await applyStackCacheEnv(envIn);
|
|
169
|
+
const pm = await getComponentPm(dir, env);
|
|
170
|
+
if (pm.name === 'yarn') {
|
|
171
|
+
await ensureYarnReady({ dir, env, quiet });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (await pathExists(nodeModules)) {
|
|
175
|
+
const yarnLock = join(dir, 'yarn.lock');
|
|
176
|
+
const yarnIntegrity = join(nodeModules, '.yarn-integrity');
|
|
177
|
+
|
|
178
|
+
// If dependencies changed since the last install, re-run install even if node_modules exists.
|
|
179
|
+
const mtimeMs = async (p) => {
|
|
180
|
+
try {
|
|
181
|
+
const s = await stat(p);
|
|
182
|
+
return s.mtimeMs ?? 0;
|
|
183
|
+
} catch {
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const patchesMtimeMs = async () => {
|
|
189
|
+
// Happy's mobile app (and some other repos) use patch-package and keep patches under `patches/`.
|
|
190
|
+
// If a patch file changes but yarn.lock/package.json do not, Yarn won't reinstall and
|
|
191
|
+
// patch-package won't re-apply the patch, leading to confusing "why isn't my patch wired?"
|
|
192
|
+
// failures later (e.g. during iOS pod install).
|
|
193
|
+
const patchesDir = join(dir, 'patches');
|
|
194
|
+
if (!(await pathExists(patchesDir))) return 0;
|
|
195
|
+
try {
|
|
196
|
+
const entries = await readdir(patchesDir, { withFileTypes: true });
|
|
197
|
+
let max = 0;
|
|
198
|
+
for (const e of entries) {
|
|
199
|
+
if (!e.isFile()) continue;
|
|
200
|
+
if (!e.name.endsWith('.patch')) continue;
|
|
201
|
+
const m = await mtimeMs(join(patchesDir, e.name));
|
|
202
|
+
if (m > max) max = m;
|
|
203
|
+
}
|
|
204
|
+
return max;
|
|
205
|
+
} catch {
|
|
206
|
+
return 0;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
if (pm.name === 'yarn' && (await pathExists(yarnLock))) {
|
|
211
|
+
const lockM = await mtimeMs(yarnLock);
|
|
212
|
+
const pkgM = await mtimeMs(pkgJson);
|
|
213
|
+
const intM = await mtimeMs(yarnIntegrity);
|
|
214
|
+
const patchM = await patchesMtimeMs();
|
|
215
|
+
if (!intM || lockM > intM || pkgM > intM || patchM > intM) {
|
|
216
|
+
if (!quiet) {
|
|
217
|
+
// eslint-disable-next-line no-console
|
|
218
|
+
console.log(`[local] refreshing ${label} dependencies (yarn.lock/package.json/patches changed)...`);
|
|
219
|
+
}
|
|
220
|
+
await run(pm.cmd, ['install'], { cwd: dir, stdio, env });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (!quiet) {
|
|
228
|
+
// eslint-disable-next-line no-console
|
|
229
|
+
console.log(`[local] installing ${label} dependencies (first run)...`);
|
|
230
|
+
}
|
|
231
|
+
await run(pm.cmd, ['install'], { cwd: dir, stdio, env });
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export async function ensureCliBuilt(cliDir, { buildCli, quiet = false, env: envIn = process.env } = {}) {
|
|
235
|
+
await ensureDepsInstalled(cliDir, 'happier-cli', { quiet, env: envIn });
|
|
236
|
+
if (!buildCli) {
|
|
237
|
+
return { built: false, reason: 'disabled' };
|
|
238
|
+
}
|
|
239
|
+
// Default: build only when needed (fast + reliable for worktrees that haven't been built yet).
|
|
240
|
+
//
|
|
241
|
+
// You can force always-build by setting:
|
|
242
|
+
// - HAPPIER_STACK_CLI_BUILD_MODE=always
|
|
243
|
+
// Or disable via:
|
|
244
|
+
// - HAPPIER_STACK_CLI_BUILD=0
|
|
245
|
+
const modeRaw = (process.env.HAPPIER_STACK_CLI_BUILD_MODE ?? 'auto').trim().toLowerCase();
|
|
246
|
+
const mode = modeRaw === 'always' || modeRaw === 'auto' || modeRaw === 'never' ? modeRaw : 'auto';
|
|
247
|
+
const distEntrypoint = join(cliDir, 'dist', 'index.mjs');
|
|
248
|
+
const distDir = join(cliDir, 'dist');
|
|
249
|
+
const distBackupDir = join(cliDir, '.dist.hstack-backup');
|
|
250
|
+
const buildStatePath = resolveBuildStatePath({ label: 'happier-cli', dir: cliDir });
|
|
251
|
+
const gitSig = await computeGitWorktreeSignature(cliDir);
|
|
252
|
+
const prev = await readJsonIfExists(buildStatePath);
|
|
253
|
+
|
|
254
|
+
// Recovery: if a previous build was interrupted after moving dist/ aside, we can be left with
|
|
255
|
+
// dist/ missing but .dist.hstack-backup/ present. Restore it so the stack remains runnable
|
|
256
|
+
// (and so subsequent "auto" mode checks can correctly treat the CLI as already built).
|
|
257
|
+
if (!(await pathExists(distDir)) && (await pathExists(distBackupDir))) {
|
|
258
|
+
await rename(distBackupDir, distDir);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// "never" should prevent rebuild churn, but it must not make the stack unrunnable.
|
|
262
|
+
// If the dist entrypoint is missing, build once even in "never" mode.
|
|
263
|
+
if (mode === 'never') {
|
|
264
|
+
if (await pathExists(distEntrypoint)) {
|
|
265
|
+
return { built: false, reason: 'mode_never' };
|
|
266
|
+
}
|
|
267
|
+
// fallthrough to build
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (mode === 'auto') {
|
|
271
|
+
// If dist doesn't exist, we must build.
|
|
272
|
+
if (!(await pathExists(distEntrypoint))) {
|
|
273
|
+
// fallthrough to build
|
|
274
|
+
} else if (gitSig && prev?.signature && prev.signature === gitSig.signature) {
|
|
275
|
+
return { built: false, reason: 'up_to_date' };
|
|
276
|
+
} else if (!gitSig) {
|
|
277
|
+
// No git info: best-effort skip if dist exists (keeps this fast outside git worktrees).
|
|
278
|
+
return { built: false, reason: 'no_git_info' };
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (!quiet) {
|
|
283
|
+
// eslint-disable-next-line no-console
|
|
284
|
+
console.log('[local] building happier-cli...');
|
|
285
|
+
}
|
|
286
|
+
const pm = await getComponentPm(cliDir, envIn);
|
|
287
|
+
const hadDistBeforeBuild = await pathExists(distDir);
|
|
288
|
+
if (hadDistBeforeBuild) {
|
|
289
|
+
await rm(distBackupDir, { recursive: true, force: true });
|
|
290
|
+
await rename(distDir, distBackupDir);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
await run(pm.cmd, ['build'], { cwd: cliDir, env: envIn, stdio: quiet ? 'ignore' : 'inherit' });
|
|
295
|
+
|
|
296
|
+
// Sanity check: happier-cli daemon entrypoint must exist after a successful build.
|
|
297
|
+
// Without this, watch-based rebuilds can restart the daemon into a MODULE_NOT_FOUND crash,
|
|
298
|
+
// which looks like the UI "dies out of nowhere" even though the root cause is missing build output.
|
|
299
|
+
if (!(await pathExists(distEntrypoint))) {
|
|
300
|
+
throw new Error(
|
|
301
|
+
`[local] happier-cli build finished but did not produce expected entrypoint.\n` +
|
|
302
|
+
`Expected: ${distEntrypoint}\n` +
|
|
303
|
+
`Fix: run the component build directly and inspect its output:\n` +
|
|
304
|
+
` cd "${cliDir}" && ${pm.cmd} build`
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
if (hadDistBeforeBuild) {
|
|
308
|
+
await rm(distBackupDir, { recursive: true, force: true });
|
|
309
|
+
}
|
|
310
|
+
} catch (error) {
|
|
311
|
+
if (hadDistBeforeBuild && (await pathExists(distBackupDir))) {
|
|
312
|
+
await rm(distDir, { recursive: true, force: true });
|
|
313
|
+
await rename(distBackupDir, distDir);
|
|
314
|
+
}
|
|
315
|
+
throw error;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Persist new build state (best-effort).
|
|
319
|
+
const nowSig = gitSig ?? (await computeGitWorktreeSignature(cliDir));
|
|
320
|
+
if (nowSig) {
|
|
321
|
+
await writeJsonAtomic(buildStatePath, {
|
|
322
|
+
label: 'happier-cli',
|
|
323
|
+
dir: resolve(cliDir),
|
|
324
|
+
signature: nowSig.signature,
|
|
325
|
+
head: nowSig.head,
|
|
326
|
+
statusHash: nowSig.statusHash,
|
|
327
|
+
builtAt: new Date().toISOString(),
|
|
328
|
+
}).catch(() => {});
|
|
329
|
+
}
|
|
330
|
+
return { built: true, reason: mode === 'always' ? 'mode_always' : 'changed' };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function getPathEntries() {
|
|
334
|
+
const raw = process.env.PATH ?? '';
|
|
335
|
+
const delimiter = process.platform === 'win32' ? ';' : ':';
|
|
336
|
+
return raw.split(delimiter).filter(Boolean);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function isPathInside(path, dir) {
|
|
340
|
+
const p = resolve(path);
|
|
341
|
+
const d = resolve(dir);
|
|
342
|
+
return p === d || p.startsWith(d.endsWith(sep) ? d : d + sep);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export async function ensureHappyCliLocalNpmLinked(rootDir, { npmLinkCli, quiet = false } = {}) {
|
|
346
|
+
if (!npmLinkCli) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const homeDir = getHappyStacksHomeDir();
|
|
351
|
+
const binDir = join(homeDir, 'bin');
|
|
352
|
+
await mkdir(binDir, { recursive: true });
|
|
353
|
+
|
|
354
|
+
const legacyHappyShim = join(binDir, 'happy');
|
|
355
|
+
const happierShim = join(binDir, 'happier');
|
|
356
|
+
|
|
357
|
+
const shim = `#!/bin/bash
|
|
358
|
+
set -euo pipefail
|
|
359
|
+
# Prefer the sibling hstack shim (works for sandbox installs too).
|
|
360
|
+
BIN_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
361
|
+
hstack="$BIN_DIR/hstack"
|
|
362
|
+
if [[ -x "$hstack" ]]; then
|
|
363
|
+
exec "$hstack" happier "$@"
|
|
364
|
+
fi
|
|
365
|
+
|
|
366
|
+
# Fallback: run hstack from runtime install if present.
|
|
367
|
+
HOME_DIR="\${HAPPIER_STACK_HOME_DIR:-$HOME/.happier-stack}"
|
|
368
|
+
RUNTIME="$HOME_DIR/runtime/node_modules/@happier-dev/stack/bin/hstack.mjs"
|
|
369
|
+
if [[ -f "$RUNTIME" ]]; then
|
|
370
|
+
exec node "$RUNTIME" happier "$@"
|
|
371
|
+
fi
|
|
372
|
+
|
|
373
|
+
echo "error: cannot find hstack shim or runtime install" >&2
|
|
374
|
+
exit 1
|
|
375
|
+
`;
|
|
376
|
+
|
|
377
|
+
const writeIfChanged = async (path, text) => {
|
|
378
|
+
let existing = '';
|
|
379
|
+
try {
|
|
380
|
+
existing = await readFile(path, 'utf-8');
|
|
381
|
+
} catch {
|
|
382
|
+
existing = '';
|
|
383
|
+
}
|
|
384
|
+
if (existing === text) return false;
|
|
385
|
+
await writeFile(path, text, 'utf-8');
|
|
386
|
+
return true;
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
// Install the Happier CLI shim under `happier` (avoid clashing with Happy's `happy` shim).
|
|
390
|
+
await writeIfChanged(happierShim, shim);
|
|
391
|
+
await chmod(happierShim, 0o755).catch(() => {});
|
|
392
|
+
|
|
393
|
+
// Remove legacy `happy` shim (it conflicts with Happy stacks installs).
|
|
394
|
+
try {
|
|
395
|
+
await unlink(legacyHappyShim);
|
|
396
|
+
} catch {
|
|
397
|
+
// ignore
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// If user’s PATH points at a legacy install path, try to make it sane (best-effort).
|
|
401
|
+
const entries = getPathEntries();
|
|
402
|
+
const legacyBin = join(homedir(), '.happier-stack', 'bin');
|
|
403
|
+
const newBin = join(homeDir, 'bin');
|
|
404
|
+
if (entries.some((p) => isPathInside(p, legacyBin)) && !entries.some((p) => isPathInside(p, newBin))) {
|
|
405
|
+
if (!quiet) {
|
|
406
|
+
// eslint-disable-next-line no-console
|
|
407
|
+
console.log(`[local] note: your PATH includes ${legacyBin}; recommended path is ${newBin}`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const cliRoot = resolveInstalledCliRoot(rootDir);
|
|
412
|
+
return { ok: true, cliRoot, binDir, happierShim, removedLegacyHappyShim: true };
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export async function pmExecBin(dirOrOpts, binArg, argsArg, optsArg) {
|
|
416
|
+
const usesObjectStyle = typeof dirOrOpts === 'object' && dirOrOpts !== null;
|
|
417
|
+
|
|
418
|
+
const dir = usesObjectStyle ? dirOrOpts.dir : dirOrOpts;
|
|
419
|
+
const bin = usesObjectStyle ? dirOrOpts.bin : binArg;
|
|
420
|
+
const args = usesObjectStyle ? (dirOrOpts.args ?? []) : (argsArg ?? []);
|
|
421
|
+
|
|
422
|
+
const envIn = usesObjectStyle ? (dirOrOpts.env ?? process.env) : (optsArg?.env ?? process.env);
|
|
423
|
+
const env = await applyStackCacheEnv(envIn);
|
|
424
|
+
const quiet = usesObjectStyle ? Boolean(dirOrOpts.quiet) : Boolean(optsArg?.quiet);
|
|
425
|
+
const stdio = quiet ? 'ignore' : 'inherit';
|
|
426
|
+
|
|
427
|
+
const pm = await getComponentPm(dir, env);
|
|
428
|
+
if (pm.name === 'yarn') {
|
|
429
|
+
await ensureYarnReady({ dir, env, quiet });
|
|
430
|
+
}
|
|
431
|
+
await run(pm.cmd, ['run', bin, ...args], { cwd: dir, env, stdio });
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export async function pmSpawnBin(dir, label, bin, args, { env = process.env } = {}) {
|
|
435
|
+
const usesObjectStyle = typeof dir === 'object' && dir !== null;
|
|
436
|
+
const componentDir = usesObjectStyle ? dir.dir : dir;
|
|
437
|
+
const componentLabel = usesObjectStyle ? dir.label : label;
|
|
438
|
+
const componentBin = usesObjectStyle ? dir.bin : bin;
|
|
439
|
+
const componentArgs = usesObjectStyle ? (dir.args ?? []) : (args ?? []);
|
|
440
|
+
const componentEnv = usesObjectStyle ? (dir.env ?? process.env) : (env ?? process.env);
|
|
441
|
+
const options = usesObjectStyle ? (dir.options ?? {}) : {};
|
|
442
|
+
const quiet = usesObjectStyle ? Boolean(dir.quiet) : false;
|
|
443
|
+
|
|
444
|
+
const effectiveEnv = await applyStackCacheEnv(componentEnv);
|
|
445
|
+
const pm = await getComponentPm(componentDir, effectiveEnv);
|
|
446
|
+
if (pm.name === 'yarn') {
|
|
447
|
+
await ensureYarnReady({ dir: componentDir, env: effectiveEnv, quiet });
|
|
448
|
+
}
|
|
449
|
+
const kind = (effectiveEnv.HAPPIER_STACK_PROCESS_KIND ?? '').toString().trim();
|
|
450
|
+
const envForChild =
|
|
451
|
+
kind || !(effectiveEnv.HAPPIER_STACK_ENV_FILE ?? '').toString().trim()
|
|
452
|
+
? effectiveEnv
|
|
453
|
+
: { ...effectiveEnv, HAPPIER_STACK_PROCESS_KIND: 'infra' };
|
|
454
|
+
return spawnProc(componentLabel, pm.cmd, ['run', componentBin, ...componentArgs], envForChild, { cwd: componentDir, ...options });
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export async function pmSpawnScript(dir, label, script, args, { env = process.env } = {}) {
|
|
458
|
+
const usesObjectStyle = typeof dir === 'object' && dir !== null;
|
|
459
|
+
const componentDir = usesObjectStyle ? dir.dir : dir;
|
|
460
|
+
const componentLabel = usesObjectStyle ? dir.label : label;
|
|
461
|
+
const componentScript = usesObjectStyle ? dir.script : script;
|
|
462
|
+
const componentArgs = usesObjectStyle ? (dir.args ?? []) : (args ?? []);
|
|
463
|
+
const componentEnv = usesObjectStyle ? (dir.env ?? process.env) : (env ?? process.env);
|
|
464
|
+
const options = usesObjectStyle ? (dir.options ?? {}) : {};
|
|
465
|
+
const quiet = usesObjectStyle ? Boolean(dir.quiet) : false;
|
|
466
|
+
|
|
467
|
+
const effectiveEnv = await applyStackCacheEnv(componentEnv);
|
|
468
|
+
const pm = await getComponentPm(componentDir, effectiveEnv);
|
|
469
|
+
if (pm.name === 'yarn') {
|
|
470
|
+
await ensureYarnReady({ dir: componentDir, env: effectiveEnv, quiet });
|
|
471
|
+
}
|
|
472
|
+
const kind = (effectiveEnv.HAPPIER_STACK_PROCESS_KIND ?? '').toString().trim();
|
|
473
|
+
const envForChild =
|
|
474
|
+
kind || !(effectiveEnv.HAPPIER_STACK_ENV_FILE ?? '').toString().trim()
|
|
475
|
+
? effectiveEnv
|
|
476
|
+
: { ...effectiveEnv, HAPPIER_STACK_PROCESS_KIND: 'infra' };
|
|
477
|
+
return spawnProc(componentLabel, pm.cmd, ['run', componentScript, ...componentArgs], envForChild, { cwd: componentDir, ...options });
|
|
478
|
+
}
|