@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,60 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { runNodeCapture as runNode } from './testkit/stack_script_command_testkit.mjs';
|
|
6
|
+
|
|
7
|
+
function runSetupJson({ rootDir, extraArgs = [], env }) {
|
|
8
|
+
return runNode(
|
|
9
|
+
[
|
|
10
|
+
join(rootDir, 'bin', 'hstack.mjs'),
|
|
11
|
+
'setup',
|
|
12
|
+
'--json',
|
|
13
|
+
'--profile=selfhost',
|
|
14
|
+
'--server=happier-server-light',
|
|
15
|
+
'--no-auth',
|
|
16
|
+
'--no-tailscale',
|
|
17
|
+
'--no-autostart',
|
|
18
|
+
'--no-menubar',
|
|
19
|
+
'--no-start-now',
|
|
20
|
+
...extraArgs,
|
|
21
|
+
],
|
|
22
|
+
{ cwd: rootDir, env }
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
test('hstack setup --non-interactive forces interactive=false', async () => {
|
|
27
|
+
const scriptsDir = dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
const rootDir = dirname(scriptsDir);
|
|
29
|
+
|
|
30
|
+
const env = { ...process.env, HAPPIER_STACK_TEST_TTY: '1' };
|
|
31
|
+
const res = await runSetupJson({ rootDir, env, extraArgs: ['--non-interactive'] });
|
|
32
|
+
assert.equal(res.code, 0, `expected exit 0, got ${res.code}\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
33
|
+
|
|
34
|
+
const data = JSON.parse(res.stdout);
|
|
35
|
+
assert.equal(data.interactive, false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('HAPPIER_STACK_NON_INTERACTIVE=1 forces interactive=false', async () => {
|
|
39
|
+
const scriptsDir = dirname(fileURLToPath(import.meta.url));
|
|
40
|
+
const rootDir = dirname(scriptsDir);
|
|
41
|
+
|
|
42
|
+
const env = { ...process.env, HAPPIER_STACK_TEST_TTY: '1', HAPPIER_STACK_NON_INTERACTIVE: '1' };
|
|
43
|
+
const res = await runSetupJson({ rootDir, env });
|
|
44
|
+
assert.equal(res.code, 0, `expected exit 0, got ${res.code}\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
45
|
+
|
|
46
|
+
const data = JSON.parse(res.stdout);
|
|
47
|
+
assert.equal(data.interactive, false);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('--interactive does not override HAPPIER_STACK_NON_INTERACTIVE=1', async () => {
|
|
51
|
+
const scriptsDir = dirname(fileURLToPath(import.meta.url));
|
|
52
|
+
const rootDir = dirname(scriptsDir);
|
|
53
|
+
|
|
54
|
+
const env = { ...process.env, HAPPIER_STACK_TEST_TTY: '1', HAPPIER_STACK_NON_INTERACTIVE: '1' };
|
|
55
|
+
const res = await runSetupJson({ rootDir, env, extraArgs: ['--interactive'] });
|
|
56
|
+
assert.equal(res.code, 0, `expected exit 0, got ${res.code}\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
57
|
+
|
|
58
|
+
const data = JSON.parse(res.stdout);
|
|
59
|
+
assert.equal(data.interactive, false);
|
|
60
|
+
});
|
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
import './utils/env/env.mjs';
|
|
2
|
+
import { parseArgs } from './utils/cli/args.mjs';
|
|
3
|
+
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
4
|
+
import { isTty } from './utils/cli/wizard.mjs';
|
|
5
|
+
import { getVerbosityLevel } from './utils/cli/verbosity.mjs';
|
|
6
|
+
import { createStepPrinter, runCommandLogged } from './utils/cli/progress.mjs';
|
|
7
|
+
import { assertCliPrereqs } from './utils/cli/prereqs.mjs';
|
|
8
|
+
import { decidePrAuthPlan } from './utils/auth/guided_pr_auth.mjs';
|
|
9
|
+
import { findAnyCredentialPathInCliHome } from './utils/auth/credentials_paths.mjs';
|
|
10
|
+
import {
|
|
11
|
+
runOrchestratedGuidedAuthFlow,
|
|
12
|
+
startDaemonPostAuth,
|
|
13
|
+
} from './utils/auth/orchestrated_stack_auth_flow.mjs';
|
|
14
|
+
import { applyStackActiveServerScopeEnv } from './utils/auth/stable_scope_id.mjs';
|
|
15
|
+
import { preferStackLocalhostUrl } from './utils/paths/localhost_host.mjs';
|
|
16
|
+
import { getComponentDir, getRootDir, resolveStackEnvPath } from './utils/paths/paths.mjs';
|
|
17
|
+
import { run } from './utils/proc/proc.mjs';
|
|
18
|
+
import { isSandboxed, sandboxAllowsGlobalSideEffects } from './utils/env/sandbox.mjs';
|
|
19
|
+
import { sanitizeStackName } from './utils/stack/names.mjs';
|
|
20
|
+
import { getStackRuntimeStatePath, readStackRuntimeStateFile } from './utils/stack/runtime_state.mjs';
|
|
21
|
+
import { readEnvObjectFromFile } from './utils/env/read.mjs';
|
|
22
|
+
import { existsSync } from 'node:fs';
|
|
23
|
+
import { join } from 'node:path';
|
|
24
|
+
import { homedir } from 'node:os';
|
|
25
|
+
import { resolveMobileQrPayload } from './utils/mobile/dev_client_links.mjs';
|
|
26
|
+
import { renderQrAscii } from './utils/ui/qr.mjs';
|
|
27
|
+
import { inferPrStackBaseName } from './utils/stack/pr_stack_name.mjs';
|
|
28
|
+
import { bold, cyan, dim, green } from './utils/ui/ansi.mjs';
|
|
29
|
+
|
|
30
|
+
function pickReviewerMobileSchemeEnv(env) {
|
|
31
|
+
// For review-pr flows, reviewers typically have the standard Happier dev build on their phone,
|
|
32
|
+
// so default to the canonical `happier://` scheme unless the user explicitly configured one.
|
|
33
|
+
// If the user explicitly set a review-specific override, honor it.
|
|
34
|
+
const reviewOverride = (env.HAPPIER_STACK_REVIEW_MOBILE_SCHEME ?? '').toString().trim();
|
|
35
|
+
if (reviewOverride) {
|
|
36
|
+
return { ...env, HAPPIER_STACK_MOBILE_SCHEME: reviewOverride };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// In sandbox review flows, keep things predictable and avoid relying on any global per-machine overrides.
|
|
40
|
+
if (isSandboxed()) {
|
|
41
|
+
return { ...env, HAPPIER_STACK_MOBILE_SCHEME: 'happier' };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Non-sandbox: keep existing behavior unless nothing is configured at all.
|
|
45
|
+
const explicit =
|
|
46
|
+
(env.HAPPIER_STACK_MOBILE_SCHEME ?? env.HAPPIER_STACK_DEV_CLIENT_SCHEME ?? '')
|
|
47
|
+
.toString()
|
|
48
|
+
.trim();
|
|
49
|
+
if (explicit) return env;
|
|
50
|
+
return { ...env, HAPPIER_STACK_MOBILE_SCHEME: 'happier' };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function printReviewerStackSummary({ rootDir, stackName, env, wantsMobile }) {
|
|
54
|
+
try {
|
|
55
|
+
const runtimeStatePath = getStackRuntimeStatePath(stackName);
|
|
56
|
+
// Wait briefly for Expo metadata to land in stack.runtime.json (it can be published slightly
|
|
57
|
+
// after the server /health check passes, especially after a restart).
|
|
58
|
+
const deadline = Date.now() + 20_000;
|
|
59
|
+
let st = await readStackRuntimeStateFile(runtimeStatePath);
|
|
60
|
+
while (Date.now() < deadline) {
|
|
61
|
+
const hasExpo = Boolean(st?.expo && typeof st.expo === 'object' && Number(st.expo.port) > 0);
|
|
62
|
+
if (hasExpo) break;
|
|
63
|
+
// eslint-disable-next-line no-await-in-loop
|
|
64
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
65
|
+
// eslint-disable-next-line no-await-in-loop
|
|
66
|
+
st = await readStackRuntimeStateFile(runtimeStatePath);
|
|
67
|
+
}
|
|
68
|
+
const baseDir = resolveStackEnvPath(stackName, env).baseDir;
|
|
69
|
+
const envPath = resolveStackEnvPath(stackName, env).envPath;
|
|
70
|
+
|
|
71
|
+
const serverPort = Number(st?.ports?.server);
|
|
72
|
+
const backendPort = Number(st?.ports?.backend);
|
|
73
|
+
const uiPort = Number(st?.expo?.webPort ?? st?.expo?.port);
|
|
74
|
+
const mobilePort = Number(st?.expo?.mobilePort ?? st?.expo?.port);
|
|
75
|
+
const runnerLog = String(st?.logs?.runner ?? '').trim();
|
|
76
|
+
const runnerPid = Number(st?.ownerPid);
|
|
77
|
+
const serverPid = Number(st?.processes?.serverPid);
|
|
78
|
+
const expoPid = Number(st?.processes?.expoPid);
|
|
79
|
+
|
|
80
|
+
const internalServerUrl = Number.isFinite(serverPort) && serverPort > 0 ? `http://127.0.0.1:${serverPort}` : '';
|
|
81
|
+
const uiUrlRaw = Number.isFinite(uiPort) && uiPort > 0 ? `http://localhost:${uiPort}` : '';
|
|
82
|
+
const uiUrl = uiUrlRaw ? await preferStackLocalhostUrl(uiUrlRaw, { stackName, env }) : '';
|
|
83
|
+
|
|
84
|
+
// eslint-disable-next-line no-console
|
|
85
|
+
console.log('');
|
|
86
|
+
// eslint-disable-next-line no-console
|
|
87
|
+
console.log(bold('Review details'));
|
|
88
|
+
// eslint-disable-next-line no-console
|
|
89
|
+
console.log(`${dim('Stack:')} ${cyan(stackName)}`);
|
|
90
|
+
// eslint-disable-next-line no-console
|
|
91
|
+
console.log(`${dim('Env:')} ${envPath}`);
|
|
92
|
+
// eslint-disable-next-line no-console
|
|
93
|
+
console.log(`${dim('Dir:')} ${baseDir}`);
|
|
94
|
+
if (Number.isFinite(runnerPid) && runnerPid > 1) {
|
|
95
|
+
// eslint-disable-next-line no-console
|
|
96
|
+
console.log(`${dim('Runner:')} pid=${runnerPid}${Number.isFinite(serverPid) && serverPid > 1 ? ` serverPid=${serverPid}` : ''}${Number.isFinite(expoPid) && expoPid > 1 ? ` expoPid=${expoPid}` : ''}`);
|
|
97
|
+
}
|
|
98
|
+
if (runnerLog) {
|
|
99
|
+
// eslint-disable-next-line no-console
|
|
100
|
+
console.log(`${dim('Logs:')} ${runnerLog}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// eslint-disable-next-line no-console
|
|
104
|
+
console.log('');
|
|
105
|
+
// eslint-disable-next-line no-console
|
|
106
|
+
console.log(bold('Ports'));
|
|
107
|
+
if (Number.isFinite(serverPort) && serverPort > 0) {
|
|
108
|
+
// eslint-disable-next-line no-console
|
|
109
|
+
console.log(`- ${dim('server')}: ${serverPort}${internalServerUrl ? ` (${internalServerUrl})` : ''}`);
|
|
110
|
+
}
|
|
111
|
+
if (Number.isFinite(backendPort) && backendPort > 0) {
|
|
112
|
+
// eslint-disable-next-line no-console
|
|
113
|
+
console.log(`- ${dim('backend')}: ${backendPort}`);
|
|
114
|
+
}
|
|
115
|
+
if (Number.isFinite(uiPort) && uiPort > 0) {
|
|
116
|
+
// eslint-disable-next-line no-console
|
|
117
|
+
console.log(`- ${dim('web UI')}: ${uiPort}${uiUrl ? ` (${uiUrl})` : ''}`);
|
|
118
|
+
}
|
|
119
|
+
if (wantsMobile && Number.isFinite(mobilePort) && mobilePort > 0) {
|
|
120
|
+
// eslint-disable-next-line no-console
|
|
121
|
+
console.log(`- ${dim('mobile')}: ${mobilePort} (Metro)`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Prefer the Metro port recorded by Expo; fall back to the web UI port if needed.
|
|
125
|
+
const metroPort = Number.isFinite(mobilePort) && mobilePort > 0 ? mobilePort : Number.isFinite(uiPort) && uiPort > 0 ? uiPort : null;
|
|
126
|
+
|
|
127
|
+
if (wantsMobile && Number.isFinite(metroPort) && metroPort > 0) {
|
|
128
|
+
const payload = resolveMobileQrPayload({ env, port: metroPort });
|
|
129
|
+
const qr = await renderQrAscii(payload.payload, { small: true });
|
|
130
|
+
|
|
131
|
+
// eslint-disable-next-line no-console
|
|
132
|
+
console.log('');
|
|
133
|
+
// eslint-disable-next-line no-console
|
|
134
|
+
console.log(bold('Mobile (Expo dev-client)'));
|
|
135
|
+
if (payload.metroUrl) {
|
|
136
|
+
// eslint-disable-next-line no-console
|
|
137
|
+
console.log(`- ${dim('Metro')}: ${payload.metroUrl}`);
|
|
138
|
+
}
|
|
139
|
+
if (payload.scheme) {
|
|
140
|
+
// eslint-disable-next-line no-console
|
|
141
|
+
console.log(`- ${dim('Scheme')}: ${payload.scheme}://`);
|
|
142
|
+
}
|
|
143
|
+
if (payload.deepLink) {
|
|
144
|
+
// eslint-disable-next-line no-console
|
|
145
|
+
console.log(`- ${dim('Link')}: ${payload.deepLink}`);
|
|
146
|
+
}
|
|
147
|
+
if (qr.ok && qr.lines.length) {
|
|
148
|
+
// eslint-disable-next-line no-console
|
|
149
|
+
console.log('');
|
|
150
|
+
// eslint-disable-next-line no-console
|
|
151
|
+
console.log(bold('Scan this QR code with your Happier dev build:'));
|
|
152
|
+
// eslint-disable-next-line no-console
|
|
153
|
+
console.log(qr.lines.join('\n'));
|
|
154
|
+
} else if (!qr.ok) {
|
|
155
|
+
// eslint-disable-next-line no-console
|
|
156
|
+
console.log(dim(`(QR unavailable: ${qr.error || 'unknown error'})`));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// eslint-disable-next-line no-console
|
|
161
|
+
console.log('');
|
|
162
|
+
// eslint-disable-next-line no-console
|
|
163
|
+
console.log(green('✓ Ready'));
|
|
164
|
+
// eslint-disable-next-line no-console
|
|
165
|
+
console.log(dim('Tip: press Ctrl+C when you’re done to stop the stack and clean up the sandbox.'));
|
|
166
|
+
} catch {
|
|
167
|
+
// best-effort
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function detectBestAuthSource() {
|
|
172
|
+
const devAuthEnvExists = existsSync(resolveStackEnvPath('dev-auth').envPath);
|
|
173
|
+
const hasDevAuth =
|
|
174
|
+
devAuthEnvExists &&
|
|
175
|
+
Boolean(findAnyCredentialPathInCliHome({ cliHomeDir: join(resolveStackEnvPath('dev-auth').baseDir, 'cli') }));
|
|
176
|
+
const hasMain = Boolean(findAnyCredentialPathInCliHome({ cliHomeDir: join(resolveStackEnvPath('main').baseDir, 'cli') }));
|
|
177
|
+
|
|
178
|
+
if (hasDevAuth) return { from: 'dev-auth', hasAny: true };
|
|
179
|
+
if (hasMain) return { from: 'main', hasAny: true };
|
|
180
|
+
return { from: 'main', hasAny: false };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function detectLinkDefault() {
|
|
184
|
+
const rawLink = (process.env.HAPPIER_STACK_AUTH_LINK ?? '').toString().trim();
|
|
185
|
+
if (rawLink) return rawLink !== '0';
|
|
186
|
+
const rawMode = (process.env.HAPPIER_STACK_AUTH_MODE ?? '').toString().trim().toLowerCase();
|
|
187
|
+
if (rawMode) return rawMode === 'link';
|
|
188
|
+
// Default for setup-pr: prefer reuse/symlink to avoid stale creds and reduce re-login friction.
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function runNodeScript({ rootDir, rel, args = [], env = process.env }) {
|
|
193
|
+
await run(process.execPath, [join(rootDir, rel), ...args], { cwd: rootDir, env });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function main() {
|
|
197
|
+
const rootDir = getRootDir(import.meta.url);
|
|
198
|
+
const argvRaw = process.argv.slice(2);
|
|
199
|
+
const sep = argvRaw.indexOf('--');
|
|
200
|
+
const argv = sep >= 0 ? argvRaw.slice(0, sep) : argvRaw;
|
|
201
|
+
const forwarded = sep >= 0 ? argvRaw.slice(sep + 1) : [];
|
|
202
|
+
|
|
203
|
+
const { flags, kv } = parseArgs(argv);
|
|
204
|
+
const json = wantsJson(argv, { flags });
|
|
205
|
+
const interactive = isTty() && !json;
|
|
206
|
+
const verbosity = getVerbosityLevel(process.env);
|
|
207
|
+
const quietUi = interactive && verbosity === 0;
|
|
208
|
+
|
|
209
|
+
if (wantsHelp(argv, { flags })) {
|
|
210
|
+
printResult({
|
|
211
|
+
json,
|
|
212
|
+
data: {
|
|
213
|
+
usage:
|
|
214
|
+
'hstack tools setup-pr --repo=<pr-url|number> [--name=<stack>] [--server-flavor=light|full] [--dev|--start] [--mobile] [--deps=none|link|install|link-or-install] [--forks|--upstream] [--seed-auth|--no-seed-auth] [--copy-auth-from=<stack>] [--link-auth|--copy-auth] [--update] [--force] [--json] [-- <stack dev/start args...>]',
|
|
215
|
+
},
|
|
216
|
+
text: [
|
|
217
|
+
'[setup-pr] usage:',
|
|
218
|
+
' hstack tools setup-pr --repo=<pr-url|number> [--dev]',
|
|
219
|
+
'',
|
|
220
|
+
'What it does (idempotent):',
|
|
221
|
+
'- ensures hstack home exists (init)',
|
|
222
|
+
'- bootstraps/clones missing repos (upstream by default)',
|
|
223
|
+
'- creates or reuses a PR stack and checks out the PR worktree',
|
|
224
|
+
'- optionally seeds auth (best available source: dev-auth → main)',
|
|
225
|
+
'- starts the stack (dev by default)',
|
|
226
|
+
'',
|
|
227
|
+
'Updating when the PR changes:',
|
|
228
|
+
'- re-run the same command; it will fast-forward PR worktrees when possible',
|
|
229
|
+
'- if the PR was force-pushed, add --force',
|
|
230
|
+
'',
|
|
231
|
+
'example:',
|
|
232
|
+
' hstack tools setup-pr \\',
|
|
233
|
+
' --repo=https://github.com/happier-dev/happier/pull/123 \\',
|
|
234
|
+
' --dev',
|
|
235
|
+
].join('\n'),
|
|
236
|
+
});
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
await assertCliPrereqs({ git: true, yarn: true });
|
|
241
|
+
|
|
242
|
+
const prRepo = (kv.get('--repo') ?? kv.get('--pr') ?? '').trim();
|
|
243
|
+
const legacyHappy = (kv.get('--happy') ?? '').trim();
|
|
244
|
+
if (legacyHappy) {
|
|
245
|
+
throw new Error('[setup-pr] use --repo=<pr-url|number> (the old --happy flag has been removed)');
|
|
246
|
+
}
|
|
247
|
+
if (!prRepo) {
|
|
248
|
+
throw new Error('[setup-pr] missing PR input. Provide --repo=<pr-url|number>.');
|
|
249
|
+
}
|
|
250
|
+
for (const legacy of ['--happy-cli', '--happy-server', '--happy-server-light']) {
|
|
251
|
+
const v = (kv.get(legacy) ?? '').trim();
|
|
252
|
+
if (v) {
|
|
253
|
+
throw new Error(`[setup-pr] legacy split-repo flag is not supported anymore: ${legacy}\nFix: use --repo=<pr-url|number>`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const wantsDev = flags.has('--dev') || (!flags.has('--start') && !flags.has('--prod'));
|
|
258
|
+
const wantsStart = flags.has('--start') || flags.has('--prod');
|
|
259
|
+
if (wantsDev && wantsStart) {
|
|
260
|
+
throw new Error('[setup-pr] choose either --dev or --start (not both)');
|
|
261
|
+
}
|
|
262
|
+
const repoSourceFlag = flags.has('--upstream') ? '--upstream' : flags.has('--forks') ? '--forks' : null;
|
|
263
|
+
const wantsMobile = (flags.has('--mobile') || flags.has('--with-mobile')) && !flags.has('--no-mobile');
|
|
264
|
+
// Worktree dependency strategy:
|
|
265
|
+
// - For dev flows (review-pr/setup-pr), prefer reusing base checkout node_modules to avoid reinstalling in worktrees.
|
|
266
|
+
// - Allow override via --deps=none|link|install|link-or-install.
|
|
267
|
+
const depsModeArg = (kv.get('--deps') ?? '').trim();
|
|
268
|
+
const depsMode = depsModeArg || (wantsDev ? 'link-or-install' : 'none');
|
|
269
|
+
|
|
270
|
+
const stackNameRaw = (kv.get('--name') ?? '').trim();
|
|
271
|
+
const stackName = stackNameRaw
|
|
272
|
+
? sanitizeStackName(stackNameRaw)
|
|
273
|
+
: inferPrStackBaseName({ happy: prRepo, happyCli: '', server: '', serverLight: '', fallback: 'pr' });
|
|
274
|
+
|
|
275
|
+
// Determine server flavor for bootstrap and stack creation.
|
|
276
|
+
const serverFlavorFromArg = (kv.get('--server-flavor') ?? '').trim().toLowerCase();
|
|
277
|
+
const serverFromArg = (kv.get('--server') ?? '').trim();
|
|
278
|
+
const serverComponent =
|
|
279
|
+
serverFlavorFromArg === 'full'
|
|
280
|
+
? 'happy-server'
|
|
281
|
+
: serverFlavorFromArg === 'light'
|
|
282
|
+
? 'happy-server-light'
|
|
283
|
+
: serverFromArg || 'happy-server-light';
|
|
284
|
+
const bootstrapServer = serverComponent === 'happy-server' ? 'both' : 'happy-server-light';
|
|
285
|
+
|
|
286
|
+
// Auth defaults (avoid prompts; setup-pr should be low-friction).
|
|
287
|
+
// Note: these may be updated below (sandbox prompt), so keep them mutable.
|
|
288
|
+
let seedAuthFlag = flags.has('--seed-auth') ? true : flags.has('--no-seed-auth') ? false : null;
|
|
289
|
+
let authFrom = (kv.get('--copy-auth-from') ?? '').trim();
|
|
290
|
+
let linkAuth = flags.has('--link-auth') ? true : flags.has('--copy-auth') ? false : null;
|
|
291
|
+
|
|
292
|
+
// Disallow "legacy" auth seeding in setup-pr flows:
|
|
293
|
+
// We can't reliably seed local DB Account rows from a remote/production Happy install,
|
|
294
|
+
// so this leads to broken stacks. Use guided login instead.
|
|
295
|
+
if (authFrom && authFrom.toLowerCase() === 'legacy') {
|
|
296
|
+
throw new Error('[setup-pr] --copy-auth-from=legacy is not supported. Use guided login (no seeding) instead.');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Re-read flags after optional prompt mutation.
|
|
300
|
+
seedAuthFlag = flags.has('--seed-auth') ? true : flags.has('--no-seed-auth') ? false : null;
|
|
301
|
+
authFrom = (kv.get('--copy-auth-from') ?? '').trim();
|
|
302
|
+
linkAuth = flags.has('--link-auth') ? true : flags.has('--copy-auth') ? false : null;
|
|
303
|
+
|
|
304
|
+
// If this PR stack already has credentials, do not prompt or override it.
|
|
305
|
+
const stackAlreadyAuthed = (() => {
|
|
306
|
+
try {
|
|
307
|
+
const { baseDir, envPath } = resolveStackEnvPath(stackName);
|
|
308
|
+
if (!existsSync(envPath)) return false;
|
|
309
|
+
return Boolean(findAnyCredentialPathInCliHome({ cliHomeDir: join(baseDir, 'cli') }));
|
|
310
|
+
} catch {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
})();
|
|
314
|
+
|
|
315
|
+
// Centralized guided auth decision (prompt early, before noisy install logs).
|
|
316
|
+
// In non-sandbox mode we still guide: offer reusing dev-auth/main first, otherwise guided login.
|
|
317
|
+
const sandboxNoGlobal = isSandboxed() && !sandboxAllowsGlobalSideEffects();
|
|
318
|
+
if (sandboxNoGlobal && (seedAuthFlag === true || authFrom)) {
|
|
319
|
+
throw new Error(
|
|
320
|
+
'[setup-pr] auth seeding is disabled in sandbox mode.\n' +
|
|
321
|
+
'Reason: it reuses global machine state (other stacks) and breaks sandbox isolation.\n' +
|
|
322
|
+
'Use guided login instead, or set: HAPPIER_STACK_SANDBOX_ALLOW_GLOBAL=1'
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
let plan = stackAlreadyAuthed
|
|
327
|
+
? { mode: 'existing' }
|
|
328
|
+
: await decidePrAuthPlan({
|
|
329
|
+
interactive,
|
|
330
|
+
seedAuthFlag,
|
|
331
|
+
explicitFrom: authFrom,
|
|
332
|
+
defaultLoginNow: true,
|
|
333
|
+
});
|
|
334
|
+
if (sandboxNoGlobal && plan?.mode === 'seed') {
|
|
335
|
+
// Keep sandbox runs isolated by default.
|
|
336
|
+
plan = { mode: 'login', loginNow: true, reason: 'sandbox_no_global' };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const best = detectBestAuthSource();
|
|
340
|
+
const effectiveSeedAuth =
|
|
341
|
+
plan.mode === 'existing'
|
|
342
|
+
? false
|
|
343
|
+
: plan.mode === 'seed'
|
|
344
|
+
? true
|
|
345
|
+
: plan.mode === 'login'
|
|
346
|
+
? false
|
|
347
|
+
: seedAuthFlag != null
|
|
348
|
+
? seedAuthFlag
|
|
349
|
+
: best.hasAny;
|
|
350
|
+
const effectiveAuthFrom = plan.mode === 'seed' ? plan.from : authFrom || best.from;
|
|
351
|
+
const effectiveLinkAuth = plan.mode === 'seed' ? Boolean(plan.link) : linkAuth != null ? linkAuth : detectLinkDefault();
|
|
352
|
+
|
|
353
|
+
// Sandbox default: no cross-stack auth reuse unless explicitly allowed.
|
|
354
|
+
const sandboxEffectiveSeedAuth = sandboxNoGlobal ? false : effectiveSeedAuth;
|
|
355
|
+
|
|
356
|
+
// If we're going to guide the user through login, start in background first (even in verbose mode)
|
|
357
|
+
// so auth prompts aren't buried in runner logs.
|
|
358
|
+
const needsAuthFlow = interactive && !stackAlreadyAuthed && !sandboxEffectiveSeedAuth && plan.mode === 'login' && plan.loginNow;
|
|
359
|
+
let stackStartEnv = needsAuthFlow
|
|
360
|
+
? {
|
|
361
|
+
...process.env,
|
|
362
|
+
// Hint to the dev runner that it should start the Expo web UI early (before daemon auth),
|
|
363
|
+
// so guided login can open the correct UI origin (not the server port).
|
|
364
|
+
HAPPIER_STACK_AUTH_FLOW: '1',
|
|
365
|
+
}
|
|
366
|
+
: process.env;
|
|
367
|
+
if (wantsMobile) {
|
|
368
|
+
stackStartEnv = pickReviewerMobileSchemeEnv(stackStartEnv);
|
|
369
|
+
}
|
|
370
|
+
stackStartEnv = applyStackActiveServerScopeEnv({ env: stackStartEnv, stackName, cliIdentity: 'default' });
|
|
371
|
+
// (No extra messaging here; review-pr prints the up-front explanation + enter-to-proceed gate.)
|
|
372
|
+
|
|
373
|
+
// 1) Ensure happy-stacks home is initialized (idempotent).
|
|
374
|
+
// 2) Bootstrap component repos and deps (idempotent; clones only if missing).
|
|
375
|
+
if (quietUi) {
|
|
376
|
+
const baseLogDir = join(process.env.HAPPIER_STACK_HOME_DIR ?? join(homedir(), '.happier-stack'), 'logs', 'setup-pr');
|
|
377
|
+
const initLog = join(baseLogDir, `init.${Date.now()}.log`);
|
|
378
|
+
const installLog = join(baseLogDir, `install.${Date.now()}.log`);
|
|
379
|
+
try {
|
|
380
|
+
await runCommandLogged({
|
|
381
|
+
label: `init hstack home${isSandboxed() ? ' (sandbox)' : ''}`,
|
|
382
|
+
cmd: process.execPath,
|
|
383
|
+
args: [join(rootDir, 'scripts', 'init.mjs'), '--no-bootstrap'],
|
|
384
|
+
cwd: rootDir,
|
|
385
|
+
env: process.env,
|
|
386
|
+
logPath: initLog,
|
|
387
|
+
quiet: true,
|
|
388
|
+
showSteps: true,
|
|
389
|
+
});
|
|
390
|
+
await runCommandLogged({
|
|
391
|
+
label: `install/clone components${isSandboxed() ? ' (sandbox)' : ''}`,
|
|
392
|
+
cmd: process.execPath,
|
|
393
|
+
args: [
|
|
394
|
+
join(rootDir, 'scripts', 'install.mjs'),
|
|
395
|
+
...(repoSourceFlag ? [repoSourceFlag] : []),
|
|
396
|
+
'--clone',
|
|
397
|
+
`--server=${bootstrapServer}`,
|
|
398
|
+
...(wantsDev ? ['--no-ui-build'] : []),
|
|
399
|
+
// Sandbox dev: avoid wasting time installing base deps we won't run directly.
|
|
400
|
+
...(isSandboxed() && wantsDev ? ['--no-ui-deps'] : []),
|
|
401
|
+
],
|
|
402
|
+
cwd: rootDir,
|
|
403
|
+
env: process.env,
|
|
404
|
+
logPath: installLog,
|
|
405
|
+
quiet: true,
|
|
406
|
+
showSteps: true,
|
|
407
|
+
});
|
|
408
|
+
} catch (e) {
|
|
409
|
+
const logPath = e?.logPath ? String(e.logPath) : null;
|
|
410
|
+
console.error('[setup-pr] failed during setup.');
|
|
411
|
+
if (logPath) {
|
|
412
|
+
console.error(`[setup-pr] log: ${logPath}`);
|
|
413
|
+
}
|
|
414
|
+
if (e?.stderr) {
|
|
415
|
+
console.error(String(e.stderr).trim());
|
|
416
|
+
} else if (e instanceof Error) {
|
|
417
|
+
console.error(e.message);
|
|
418
|
+
} else {
|
|
419
|
+
console.error(String(e));
|
|
420
|
+
}
|
|
421
|
+
process.exit(1);
|
|
422
|
+
}
|
|
423
|
+
} else {
|
|
424
|
+
await runNodeScript({ rootDir, rel: 'scripts/init.mjs', args: ['--no-bootstrap'] });
|
|
425
|
+
await runNodeScript({
|
|
426
|
+
rootDir,
|
|
427
|
+
rel: 'scripts/install.mjs',
|
|
428
|
+
args: [
|
|
429
|
+
...(repoSourceFlag ? [repoSourceFlag] : []),
|
|
430
|
+
'--clone',
|
|
431
|
+
`--server=${bootstrapServer}`,
|
|
432
|
+
...(wantsDev ? ['--no-ui-build'] : []),
|
|
433
|
+
...(isSandboxed() && wantsDev ? ['--no-ui-deps'] : []),
|
|
434
|
+
],
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// 3) Create/reuse the PR stack and wire worktrees.
|
|
439
|
+
// Start Expo with all requested capabilities from the beginning to avoid stop/restart churn.
|
|
440
|
+
const startMobileNow = wantsMobile;
|
|
441
|
+
const userDisabledDaemon = forwarded.includes('--no-daemon');
|
|
442
|
+
const forwardedEffective =
|
|
443
|
+
needsAuthFlow && !userDisabledDaemon && !forwarded.includes('--no-daemon')
|
|
444
|
+
? [...forwarded, '--no-daemon']
|
|
445
|
+
: forwarded;
|
|
446
|
+
const injectedNoDaemon = needsAuthFlow && !userDisabledDaemon && forwardedEffective.includes('--no-daemon');
|
|
447
|
+
const stackArgs = [
|
|
448
|
+
'pr',
|
|
449
|
+
stackName,
|
|
450
|
+
`--repo=${prRepo}`,
|
|
451
|
+
`--server=${serverComponent}`,
|
|
452
|
+
'--reuse',
|
|
453
|
+
...(depsMode ? [`--deps=${depsMode}`] : []),
|
|
454
|
+
...(flags.has('--update') ? ['--update'] : []),
|
|
455
|
+
...(flags.has('--force') ? ['--force'] : []),
|
|
456
|
+
...(sandboxEffectiveSeedAuth
|
|
457
|
+
? ['--seed-auth', `--copy-auth-from=${effectiveAuthFrom}`, ...(effectiveLinkAuth ? ['--link-auth'] : [])]
|
|
458
|
+
: ['--no-seed-auth']),
|
|
459
|
+
...(wantsDev ? ['--dev'] : ['--start']),
|
|
460
|
+
...(startMobileNow ? ['--mobile'] : []),
|
|
461
|
+
...(((quietUi && !json) || needsAuthFlow) ? ['--background'] : []),
|
|
462
|
+
...(json ? ['--json'] : []),
|
|
463
|
+
];
|
|
464
|
+
if (forwardedEffective.length) {
|
|
465
|
+
stackArgs.push('--', ...forwardedEffective);
|
|
466
|
+
}
|
|
467
|
+
if (quietUi) {
|
|
468
|
+
const baseLogDir = join(process.env.HAPPIER_STACK_HOME_DIR ?? join(homedir(), '.happier-stack'), 'logs', 'setup-pr');
|
|
469
|
+
const stackLog = join(baseLogDir, `stack-pr.${Date.now()}.log`);
|
|
470
|
+
await runCommandLogged({
|
|
471
|
+
label: `start PR stack${isSandboxed() ? ' (sandbox)' : ''}`,
|
|
472
|
+
cmd: process.execPath,
|
|
473
|
+
args: [join(rootDir, 'scripts', 'stack.mjs'), ...stackArgs],
|
|
474
|
+
cwd: rootDir,
|
|
475
|
+
env: stackStartEnv,
|
|
476
|
+
logPath: stackLog,
|
|
477
|
+
quiet: true,
|
|
478
|
+
showSteps: true,
|
|
479
|
+
}).catch((e) => {
|
|
480
|
+
const logPath = e?.logPath ? String(e.logPath) : stackLog;
|
|
481
|
+
console.error('[setup-pr] failed to start PR stack.');
|
|
482
|
+
console.error(`[setup-pr] log: ${logPath}`);
|
|
483
|
+
process.exit(1);
|
|
484
|
+
});
|
|
485
|
+
} else {
|
|
486
|
+
await runNodeScript({ rootDir, rel: 'scripts/stack.mjs', args: stackArgs, env: stackStartEnv });
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Sandbox UX: if we won't run the guided login flow, explicitly tell the user we're now in "keepalive"
|
|
490
|
+
// mode and how to exit/cleanup. Otherwise it can look like the command "hung".
|
|
491
|
+
if (isSandboxed() && interactive && !json && !needsAuthFlow) {
|
|
492
|
+
// eslint-disable-next-line no-console
|
|
493
|
+
console.log('');
|
|
494
|
+
// eslint-disable-next-line no-console
|
|
495
|
+
console.log('[setup-pr] Stack is running in the sandbox.');
|
|
496
|
+
// eslint-disable-next-line no-console
|
|
497
|
+
console.log('[setup-pr] Press Ctrl+C when you’re done to stop and delete the sandbox.');
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Guided auth flow:
|
|
501
|
+
// If the user chose "login now", we start in background (quiet mode) then perform login in the foreground.
|
|
502
|
+
// Sandbox: keep this process alive so review-pr can clean up on exit.
|
|
503
|
+
// Non-sandbox: after login, restart dev/start in the foreground so logs follow as usual.
|
|
504
|
+
if (needsAuthFlow) {
|
|
505
|
+
// eslint-disable-next-line no-console
|
|
506
|
+
console.log('');
|
|
507
|
+
const guided = await runOrchestratedGuidedAuthFlow({
|
|
508
|
+
rootDir,
|
|
509
|
+
stackName,
|
|
510
|
+
env: stackStartEnv,
|
|
511
|
+
verbosity,
|
|
512
|
+
json: false,
|
|
513
|
+
});
|
|
514
|
+
const postAuthWebappUrl = String(guided?.webappUrl ?? '').trim();
|
|
515
|
+
|
|
516
|
+
// After guided login, start daemon now (unless the user explicitly disabled it).
|
|
517
|
+
// This ensures the machine is registered and appears in the UI.
|
|
518
|
+
if (injectedNoDaemon && !userDisabledDaemon) {
|
|
519
|
+
const steps = createStepPrinter({ enabled: Boolean(process.stdout.isTTY && !json) });
|
|
520
|
+
const label = 'start daemon (post-auth)';
|
|
521
|
+
steps.start(label);
|
|
522
|
+
try {
|
|
523
|
+
const daemonStart = await startDaemonPostAuth({
|
|
524
|
+
rootDir,
|
|
525
|
+
stackName,
|
|
526
|
+
env: stackStartEnv,
|
|
527
|
+
forceRestart: true,
|
|
528
|
+
webappUrl: postAuthWebappUrl,
|
|
529
|
+
});
|
|
530
|
+
if (daemonStart?.ok === false) {
|
|
531
|
+
steps.stop('!', label);
|
|
532
|
+
if (!json) {
|
|
533
|
+
// eslint-disable-next-line no-console
|
|
534
|
+
console.error(daemonStart.error ?? `[setup-pr] ${stackName}: post-auth daemon start verification timed out`);
|
|
535
|
+
}
|
|
536
|
+
} else {
|
|
537
|
+
steps.stop('✓', label);
|
|
538
|
+
}
|
|
539
|
+
} catch (e) {
|
|
540
|
+
if (e instanceof Error && e.message.includes('could not resolve server port')) {
|
|
541
|
+
steps.stop('x', label);
|
|
542
|
+
throw new Error('[setup-pr] post-auth daemon start failed: could not resolve server port from stack.runtime.json');
|
|
543
|
+
}
|
|
544
|
+
steps.stop('x', label);
|
|
545
|
+
throw e;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (isSandboxed()) {
|
|
550
|
+
// Fall through to sandbox keepalive below.
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Re-attach logs in the foreground for the chosen mode.
|
|
554
|
+
const restartArgs = [
|
|
555
|
+
wantsDev ? 'dev' : 'start',
|
|
556
|
+
stackName,
|
|
557
|
+
'--restart',
|
|
558
|
+
...(wantsMobile ? ['--mobile'] : []),
|
|
559
|
+
...(forwarded.length ? ['--', ...forwarded] : []),
|
|
560
|
+
];
|
|
561
|
+
// If the user explicitly asked for verbose, reattach; otherwise keep things quiet.
|
|
562
|
+
if (verbosity > 0) {
|
|
563
|
+
await runNodeScript({ rootDir, rel: 'scripts/stack.mjs', args: restartArgs });
|
|
564
|
+
}
|
|
565
|
+
// Mobile is started up-front (in the initial stack pr start) so we don't need to restart here.
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// After login (and after the optional mobile Metro start), print a clear summary so reviewers
|
|
569
|
+
// have everything they need (URLs/ports/logs + QR) without needing verbose logs.
|
|
570
|
+
if (interactive && !json) {
|
|
571
|
+
await printReviewerStackSummary({ rootDir, stackName, env: stackStartEnv, wantsMobile });
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Sandbox: keep this process alive so review-pr stays running and can clean up on exit.
|
|
575
|
+
// The stack runner continues in the background; `review-pr` will stop it on Ctrl+C.
|
|
576
|
+
//
|
|
577
|
+
// IMPORTANT:
|
|
578
|
+
// Waiting on a Promise that only resolves on signals is NOT enough to keep Node alive; pending
|
|
579
|
+
// Promises and signal handlers do not keep the event loop open. We must keep a ref'd handle.
|
|
580
|
+
if (isSandboxed() && interactive && !json) {
|
|
581
|
+
// eslint-disable-next-line no-console
|
|
582
|
+
console.log('');
|
|
583
|
+
// eslint-disable-next-line no-console
|
|
584
|
+
console.log('[setup-pr] Stack is running in the sandbox.');
|
|
585
|
+
// eslint-disable-next-line no-console
|
|
586
|
+
console.log('[setup-pr] Press Ctrl+C when you’re done to stop and delete the sandbox.');
|
|
587
|
+
|
|
588
|
+
await new Promise((resolvePromise) => {
|
|
589
|
+
const interval = setInterval(() => {}, 1_000);
|
|
590
|
+
const done = () => {
|
|
591
|
+
clearInterval(interval);
|
|
592
|
+
process.off('SIGINT', done);
|
|
593
|
+
process.off('SIGTERM', done);
|
|
594
|
+
resolvePromise();
|
|
595
|
+
};
|
|
596
|
+
process.on('SIGINT', done);
|
|
597
|
+
process.on('SIGTERM', done);
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
main().catch((err) => {
|
|
603
|
+
console.error('[setup-pr] failed:', err);
|
|
604
|
+
process.exit(1);
|
|
605
|
+
});
|