@happier-dev/stack 0.1.0-preview.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +501 -0
- package/bin/hstack.mjs +348 -0
- package/docs/codex-mcp-resume.md +129 -0
- package/docs/edison.md +74 -0
- package/docs/forking-and-branding.md +189 -0
- package/docs/happy-development.md +22 -0
- package/docs/isolated-linux-vm.md +243 -0
- package/docs/menubar.md +244 -0
- package/docs/mobile-ios.md +322 -0
- package/docs/monorepo-migration.md +20 -0
- package/docs/paths-and-env.md +154 -0
- package/docs/remote-access.md +43 -0
- package/docs/server-flavors.md +147 -0
- package/docs/stacks.md +330 -0
- package/docs/tauri.md +60 -0
- package/docs/worktrees-and-forks.md +133 -0
- package/extras/swiftbar/auth-login.sh +29 -0
- package/extras/swiftbar/git-cache-refresh.sh +122 -0
- package/extras/swiftbar/hstack-term.sh +133 -0
- package/extras/swiftbar/hstack.5s.sh +296 -0
- package/extras/swiftbar/hstack.sh +35 -0
- package/extras/swiftbar/icons/happy-green.png +0 -0
- package/extras/swiftbar/icons/happy-orange.png +0 -0
- package/extras/swiftbar/icons/happy-red.png +0 -0
- package/extras/swiftbar/icons/logo-white.png +0 -0
- package/extras/swiftbar/install.sh +265 -0
- package/extras/swiftbar/lib/git.sh +629 -0
- package/extras/swiftbar/lib/icons.sh +92 -0
- package/extras/swiftbar/lib/render.sh +999 -0
- package/extras/swiftbar/lib/system.sh +244 -0
- package/extras/swiftbar/lib/utils.sh +717 -0
- package/extras/swiftbar/set-interval.sh +65 -0
- package/extras/swiftbar/set-server-flavor.sh +61 -0
- package/extras/swiftbar/wt-pr.sh +140 -0
- package/node_modules/@happier-dev/cli-common/README.md +6 -0
- package/node_modules/@happier-dev/cli-common/dist/index.d.ts +4 -0
- package/node_modules/@happier-dev/cli-common/dist/index.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/index.js +4 -0
- package/node_modules/@happier-dev/cli-common/dist/index.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/links/index.d.ts +18 -0
- package/node_modules/@happier-dev/cli-common/dist/links/index.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/links/index.js +25 -0
- package/node_modules/@happier-dev/cli-common/dist/links/index.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/links.d.ts +2 -0
- package/node_modules/@happier-dev/cli-common/dist/links.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/links.js +2 -0
- package/node_modules/@happier-dev/cli-common/dist/links.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/update/index.d.ts +67 -0
- package/node_modules/@happier-dev/cli-common/dist/update/index.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/update/index.js +259 -0
- package/node_modules/@happier-dev/cli-common/dist/update/index.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/workspaces/index.d.ts +17 -0
- package/node_modules/@happier-dev/cli-common/dist/workspaces/index.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/workspaces/index.js +80 -0
- package/node_modules/@happier-dev/cli-common/dist/workspaces/index.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/package.json +26 -0
- package/package.json +77 -0
- package/scripts/auth.mjs +1829 -0
- package/scripts/auth_copy_from_pglite_lock_in_use.integration.test.mjs +90 -0
- package/scripts/auth_copy_from_runCapture.integration.test.mjs +447 -0
- package/scripts/auth_help_cmd.test.mjs +28 -0
- package/scripts/auth_login_flow_in_tty.test.mjs +100 -0
- package/scripts/auth_login_force_default.test.mjs +66 -0
- package/scripts/auth_login_guided_server_no_expo.test.mjs +126 -0
- package/scripts/auth_login_method_override.test.mjs +67 -0
- package/scripts/auth_login_print_includes_configure_links.test.mjs +99 -0
- package/scripts/auth_status_server_validation.integration.test.mjs +140 -0
- package/scripts/build.mjs +266 -0
- package/scripts/bundleWorkspaceDeps.mjs +38 -0
- package/scripts/bundleWorkspaceDeps.test.mjs +77 -0
- package/scripts/ci.mjs +135 -0
- package/scripts/ci.test.mjs +50 -0
- package/scripts/cli-link.mjs +57 -0
- package/scripts/completion.mjs +395 -0
- package/scripts/contrib.mjs +333 -0
- package/scripts/daemon.mjs +1160 -0
- package/scripts/daemon.status_scope.test.mjs +51 -0
- package/scripts/daemon_cmd.mjs +26 -0
- package/scripts/daemon_dist_guard.test.mjs +171 -0
- package/scripts/daemon_invalid_auth_reseed_stack_name.integration.test.mjs +608 -0
- package/scripts/daemon_server_scoped_state.test.mjs +49 -0
- package/scripts/daemon_start_verification.integration.test.mjs +296 -0
- package/scripts/dev.mjs +545 -0
- package/scripts/doctor.mjs +340 -0
- package/scripts/doctor_cmd.test.mjs +22 -0
- package/scripts/doctor_ui_index_missing.test.mjs +37 -0
- package/scripts/eas.mjs +367 -0
- package/scripts/eas_platform_parsing.test.mjs +63 -0
- package/scripts/edison.mjs +1848 -0
- package/scripts/env.mjs +149 -0
- package/scripts/env_cmd.test.mjs +118 -0
- package/scripts/exit_cleanup_kills_detached_children_on_crash.integration.test.mjs +80 -0
- package/scripts/happier.mjs +82 -0
- package/scripts/import.mjs +1327 -0
- package/scripts/init.mjs +464 -0
- package/scripts/install.mjs +550 -0
- package/scripts/lint.mjs +177 -0
- package/scripts/menubar.mjs +202 -0
- package/scripts/migrate.mjs +318 -0
- package/scripts/mobile.mjs +353 -0
- package/scripts/mobile_dev_client.mjs +87 -0
- package/scripts/monorepo.mjs +2234 -0
- package/scripts/monorepo_port.apply.integration.test.mjs +680 -0
- package/scripts/monorepo_port.conflicts.integration.test.mjs +454 -0
- package/scripts/monorepo_port.validation.integration.test.mjs +486 -0
- package/scripts/orchestrated_stack_auth_flow.test.mjs +134 -0
- package/scripts/orchestrated_stack_auth_flow_resolve_port.test.mjs +98 -0
- package/scripts/orchestrated_stack_auth_flow_webapp_url.test.mjs +119 -0
- package/scripts/pack.mjs +257 -0
- package/scripts/pack.test.mjs +68 -0
- package/scripts/pglite_lock.integration.test.mjs +152 -0
- package/scripts/provision/linux-ubuntu-e2e.sh +132 -0
- package/scripts/provision/linux-ubuntu-review-pr.sh +66 -0
- package/scripts/provision/macos-lima-happy-vm.sh +192 -0
- package/scripts/provision/macos-lima-hstack-e2e.sh +100 -0
- package/scripts/release.mjs +53 -0
- package/scripts/release_binary_smoke.integration.test.mjs +159 -0
- package/scripts/review.mjs +1752 -0
- package/scripts/review_pr.mjs +435 -0
- package/scripts/run.mjs +561 -0
- package/scripts/run_script_with_stack_env.restart_port_reuse.test.mjs +30 -0
- package/scripts/self.mjs +465 -0
- package/scripts/self_host.mjs +9 -0
- package/scripts/self_host_binary_smoke.integration.test.mjs +94 -0
- package/scripts/self_host_runtime.mjs +883 -0
- package/scripts/self_host_runtime.test.mjs +82 -0
- package/scripts/self_host_systemd.real.integration.test.mjs +367 -0
- package/scripts/server_flavor.mjs +148 -0
- package/scripts/service.mjs +868 -0
- package/scripts/service_mode_help.test.mjs +27 -0
- package/scripts/setup.mjs +1324 -0
- package/scripts/setup_non_interactive_flag.test.mjs +60 -0
- package/scripts/setup_pr.mjs +605 -0
- package/scripts/setup_pr_orchestrated_auth_flow_util_import.test.mjs +117 -0
- package/scripts/stack/command_arguments.mjs +91 -0
- package/scripts/stack/copy_auth_from_stack.mjs +111 -0
- package/scripts/stack/delegated_script_commands.mjs +92 -0
- package/scripts/stack/help_text.mjs +110 -0
- package/scripts/stack/port_reservation.mjs +74 -0
- package/scripts/stack/repo_checkout_resolution.mjs +31 -0
- package/scripts/stack/run_script_with_stack_env.mjs +634 -0
- package/scripts/stack/stack_daemon_command.mjs +219 -0
- package/scripts/stack/stack_delegated_help.mjs +81 -0
- package/scripts/stack/stack_environment.mjs +151 -0
- package/scripts/stack/stack_environment.sanitization.test.mjs +75 -0
- package/scripts/stack/stack_happier_passthrough_command.mjs +63 -0
- package/scripts/stack/stack_info_snapshot.mjs +167 -0
- package/scripts/stack/stack_mobile_install_command.mjs +61 -0
- package/scripts/stack/stack_resume_command.mjs +76 -0
- package/scripts/stack/stack_stop_command.mjs +34 -0
- package/scripts/stack/stack_workspace_command.mjs +83 -0
- package/scripts/stack/transient_repo_overrides.mjs +29 -0
- package/scripts/stack.mjs +2388 -0
- package/scripts/stack_archive_cmd.integration.test.mjs +31 -0
- package/scripts/stack_audit_fix_light_env.test.mjs +129 -0
- package/scripts/stack_background_pinned_stack_json.test.mjs +81 -0
- package/scripts/stack_copy_auth_server_scoped.test.mjs +243 -0
- package/scripts/stack_daemon_cmd.integration.test.mjs +484 -0
- package/scripts/stack_eas_help.test.mjs +72 -0
- package/scripts/stack_editor_workspace_monorepo_root.test.mjs +102 -0
- package/scripts/stack_env_cmd.test.mjs +107 -0
- package/scripts/stack_guided_login_bundle_error_parse.test.mjs +20 -0
- package/scripts/stack_guided_login_inner_invocation.test.mjs +46 -0
- package/scripts/stack_happy_cmd.integration.test.mjs +263 -0
- package/scripts/stack_info_snapshot_running_status.test.mjs +186 -0
- package/scripts/stack_interactive_monorepo_group.test.mjs +128 -0
- package/scripts/stack_monorepo_defaults.test.mjs +31 -0
- package/scripts/stack_monorepo_repo_dev_token.test.mjs +32 -0
- package/scripts/stack_monorepo_server_light_from_happy_spec.test.mjs +37 -0
- package/scripts/stack_new_name_normalize_cmd.test.mjs +38 -0
- package/scripts/stack_pr_name_normalize_cmd.test.mjs +84 -0
- package/scripts/stack_resume_cmd.integration.test.mjs +134 -0
- package/scripts/stack_server_flavors_defaults.test.mjs +64 -0
- package/scripts/stack_shorthand_cmd.integration.test.mjs +74 -0
- package/scripts/stack_stop_sweeps_legacy_infra_without_kind.integration.test.mjs +44 -0
- package/scripts/stack_stop_sweeps_when_runtime_missing.integration.test.mjs +42 -0
- package/scripts/stack_stop_sweeps_when_runtime_stale.integration.test.mjs +50 -0
- package/scripts/stack_wt_list.test.mjs +117 -0
- package/scripts/start_ui_required_default.test.mjs +63 -0
- package/scripts/stop.mjs +190 -0
- package/scripts/stopStackWithEnv_no_autosweep_when_runtime_missing.integration.test.mjs +95 -0
- package/scripts/swiftbar_git_monorepo_cmd.test.mjs +75 -0
- package/scripts/swiftbar_render_monorepo_wt_actions.integration.test.mjs +116 -0
- package/scripts/swiftbar_utils_cmd.test.mjs +92 -0
- package/scripts/swiftbar_wt_pr_backcompat.test.mjs +162 -0
- package/scripts/systemd_unit_info.test.mjs +24 -0
- package/scripts/tailscale.mjs +490 -0
- package/scripts/test_ci.mjs +36 -0
- package/scripts/test_cmd.mjs +274 -0
- package/scripts/test_cmd.test.mjs +133 -0
- package/scripts/test_integration.mjs +33 -0
- package/scripts/testkit/auth_testkit.mjs +121 -0
- package/scripts/testkit/doctor_testkit.mjs +68 -0
- package/scripts/testkit/monorepo_port_testkit.mjs +157 -0
- package/scripts/testkit/stack_archive_command_testkit.mjs +55 -0
- package/scripts/testkit/stack_new_monorepo_testkit.mjs +83 -0
- package/scripts/testkit/stack_script_command_testkit.mjs +27 -0
- package/scripts/testkit/stack_stop_sweeps_testkit.mjs +172 -0
- package/scripts/testkit/worktrees_monorepo_testkit.mjs +53 -0
- package/scripts/tools.mjs +70 -0
- package/scripts/tui.mjs +914 -0
- package/scripts/tui_stopStackForTuiExit_no_autosweep.integration.test.mjs +95 -0
- package/scripts/typecheck.mjs +178 -0
- package/scripts/ui_gateway.mjs +247 -0
- package/scripts/uninstall.mjs +179 -0
- package/scripts/utils/auth/credentials_paths.mjs +181 -0
- package/scripts/utils/auth/credentials_paths.test.mjs +187 -0
- package/scripts/utils/auth/daemon_gate.mjs +66 -0
- package/scripts/utils/auth/daemon_gate.test.mjs +116 -0
- package/scripts/utils/auth/decode_jwt_payload_unsafe.mjs +16 -0
- package/scripts/utils/auth/dev_key.mjs +163 -0
- package/scripts/utils/auth/files.mjs +56 -0
- package/scripts/utils/auth/guided_pr_auth.mjs +86 -0
- package/scripts/utils/auth/guided_stack_web_login.mjs +56 -0
- package/scripts/utils/auth/handy_master_secret.mjs +42 -0
- package/scripts/utils/auth/interactive_stack_auth.mjs +70 -0
- package/scripts/utils/auth/login_ux.mjs +105 -0
- package/scripts/utils/auth/orchestrated_stack_auth_flow.mjs +291 -0
- package/scripts/utils/auth/sources.mjs +28 -0
- package/scripts/utils/auth/stable_scope_id.mjs +91 -0
- package/scripts/utils/auth/stable_scope_id.test.mjs +51 -0
- package/scripts/utils/auth/stack_guided_login.mjs +438 -0
- package/scripts/utils/cli/arg_values.mjs +23 -0
- package/scripts/utils/cli/arg_values.test.mjs +43 -0
- package/scripts/utils/cli/args.mjs +17 -0
- package/scripts/utils/cli/cli.mjs +24 -0
- package/scripts/utils/cli/cli_registry.mjs +440 -0
- package/scripts/utils/cli/cwd_scope.mjs +158 -0
- package/scripts/utils/cli/cwd_scope.test.mjs +154 -0
- package/scripts/utils/cli/flags.mjs +17 -0
- package/scripts/utils/cli/log_forwarder.mjs +157 -0
- package/scripts/utils/cli/normalize.mjs +16 -0
- package/scripts/utils/cli/prereqs.mjs +103 -0
- package/scripts/utils/cli/prereqs.test.mjs +33 -0
- package/scripts/utils/cli/progress.mjs +141 -0
- package/scripts/utils/cli/smoke_help.mjs +44 -0
- package/scripts/utils/cli/verbosity.mjs +11 -0
- package/scripts/utils/cli/wizard.mjs +139 -0
- package/scripts/utils/cli/wizard_promptSelect.test.mjs +44 -0
- package/scripts/utils/cli/wizard_prompt_worktree_source_lazy.test.mjs +132 -0
- package/scripts/utils/cli/wizard_worktree_slug.test.mjs +33 -0
- package/scripts/utils/crypto/tokens.mjs +14 -0
- package/scripts/utils/dev/daemon.mjs +232 -0
- package/scripts/utils/dev/daemon_watch_resilience.test.mjs +224 -0
- package/scripts/utils/dev/expo_dev.buildEnv.test.mjs +35 -0
- package/scripts/utils/dev/expo_dev.mjs +478 -0
- package/scripts/utils/dev/expo_dev.test.mjs +89 -0
- package/scripts/utils/dev/expo_dev_restart_port_reservation.test.mjs +120 -0
- package/scripts/utils/dev/expo_dev_verbose_logs.test.mjs +60 -0
- package/scripts/utils/dev/server.mjs +180 -0
- package/scripts/utils/dev_auth_key.mjs +7 -0
- package/scripts/utils/edison/git_roots.mjs +30 -0
- package/scripts/utils/edison/git_roots.test.mjs +49 -0
- package/scripts/utils/env/config.mjs +52 -0
- package/scripts/utils/env/dotenv.mjs +32 -0
- package/scripts/utils/env/dotenv.test.mjs +32 -0
- package/scripts/utils/env/env.mjs +130 -0
- package/scripts/utils/env/env_file.mjs +98 -0
- package/scripts/utils/env/env_file.test.mjs +49 -0
- package/scripts/utils/env/env_local.mjs +25 -0
- package/scripts/utils/env/load_env_file.mjs +34 -0
- package/scripts/utils/env/read.mjs +30 -0
- package/scripts/utils/env/sandbox.mjs +13 -0
- package/scripts/utils/env/scrub_env.mjs +69 -0
- package/scripts/utils/env/scrub_env.test.mjs +102 -0
- package/scripts/utils/env/values.mjs +13 -0
- package/scripts/utils/expo/command.mjs +65 -0
- package/scripts/utils/expo/expo.mjs +139 -0
- package/scripts/utils/expo/expo_state_running.test.mjs +48 -0
- package/scripts/utils/expo/metro_ports.mjs +101 -0
- package/scripts/utils/expo/metro_ports.test.mjs +35 -0
- package/scripts/utils/fs/atomic_dir_swap.mjs +55 -0
- package/scripts/utils/fs/atomic_dir_swap.test.mjs +54 -0
- package/scripts/utils/fs/file_has_content.mjs +10 -0
- package/scripts/utils/fs/fs.mjs +11 -0
- package/scripts/utils/fs/json.mjs +25 -0
- package/scripts/utils/fs/ops.mjs +29 -0
- package/scripts/utils/fs/package_json.mjs +8 -0
- package/scripts/utils/fs/tail.mjs +12 -0
- package/scripts/utils/git/dev_checkout.mjs +127 -0
- package/scripts/utils/git/dev_checkout.test.mjs +115 -0
- package/scripts/utils/git/git.mjs +67 -0
- package/scripts/utils/git/parse_name_status_z.mjs +21 -0
- package/scripts/utils/git/refs.mjs +26 -0
- package/scripts/utils/git/worktrees.mjs +323 -0
- package/scripts/utils/git/worktrees_monorepo.test.mjs +60 -0
- package/scripts/utils/git/worktrees_pathstyle.test.mjs +53 -0
- package/scripts/utils/llm/assist.mjs +260 -0
- package/scripts/utils/llm/codex_exec.mjs +61 -0
- package/scripts/utils/llm/codex_exec.test.mjs +46 -0
- package/scripts/utils/llm/hstack_runner.mjs +59 -0
- package/scripts/utils/llm/tools.mjs +56 -0
- package/scripts/utils/llm/tools.test.mjs +67 -0
- package/scripts/utils/menubar/swiftbar.mjs +121 -0
- package/scripts/utils/menubar/swiftbar.test.mjs +85 -0
- package/scripts/utils/mobile/config.mjs +35 -0
- package/scripts/utils/mobile/dev_client_links.mjs +59 -0
- package/scripts/utils/mobile/identifiers.mjs +46 -0
- package/scripts/utils/mobile/identifiers.test.mjs +41 -0
- package/scripts/utils/mobile/ios_xcodeproj_patch.mjs +128 -0
- package/scripts/utils/mobile/ios_xcodeproj_patch.test.mjs +131 -0
- package/scripts/utils/net/bind_mode.mjs +39 -0
- package/scripts/utils/net/dns.mjs +10 -0
- package/scripts/utils/net/lan_ip.mjs +24 -0
- package/scripts/utils/net/ports.mjs +110 -0
- package/scripts/utils/net/tcp_forward.mjs +162 -0
- package/scripts/utils/net/url.mjs +30 -0
- package/scripts/utils/net/url.test.mjs +29 -0
- package/scripts/utils/paths/canonical_home.mjs +15 -0
- package/scripts/utils/paths/canonical_home.test.mjs +28 -0
- package/scripts/utils/paths/localhost_host.mjs +112 -0
- package/scripts/utils/paths/localhost_host.test.mjs +58 -0
- package/scripts/utils/paths/paths.mjs +302 -0
- package/scripts/utils/paths/paths_env_win32.test.mjs +36 -0
- package/scripts/utils/paths/paths_monorepo.test.mjs +58 -0
- package/scripts/utils/paths/paths_server_flavors.test.mjs +50 -0
- package/scripts/utils/paths/runtime.mjs +41 -0
- package/scripts/utils/pglite_lock.mjs +107 -0
- package/scripts/utils/proc/commands.mjs +33 -0
- package/scripts/utils/proc/exit_cleanup.mjs +57 -0
- package/scripts/utils/proc/happy_monorepo_deps.mjs +37 -0
- package/scripts/utils/proc/happy_monorepo_deps.test.mjs +89 -0
- package/scripts/utils/proc/ownership.mjs +217 -0
- package/scripts/utils/proc/ownership_killProcessGroupOwnedByStack.test.mjs +216 -0
- package/scripts/utils/proc/ownership_listPidsWithEnvNeedles.test.mjs +88 -0
- package/scripts/utils/proc/package_scripts.mjs +38 -0
- package/scripts/utils/proc/package_scripts.test.mjs +58 -0
- package/scripts/utils/proc/parallel.mjs +25 -0
- package/scripts/utils/proc/pids.mjs +11 -0
- package/scripts/utils/proc/pm.mjs +478 -0
- package/scripts/utils/proc/pm_spawn.integration.test.mjs +131 -0
- package/scripts/utils/proc/pm_stack_cache_env.test.mjs +313 -0
- package/scripts/utils/proc/proc.mjs +331 -0
- package/scripts/utils/proc/proc.test.mjs +85 -0
- package/scripts/utils/proc/terminate.mjs +69 -0
- package/scripts/utils/proc/terminate.test.mjs +54 -0
- package/scripts/utils/proc/watch.mjs +63 -0
- package/scripts/utils/review/augment_runner_integration.test.mjs +105 -0
- package/scripts/utils/review/base_ref.mjs +82 -0
- package/scripts/utils/review/base_ref.test.mjs +89 -0
- package/scripts/utils/review/chunks.mjs +55 -0
- package/scripts/utils/review/chunks.test.mjs +107 -0
- package/scripts/utils/review/detached_worktree.mjs +61 -0
- package/scripts/utils/review/detached_worktree.test.mjs +61 -0
- package/scripts/utils/review/findings.mjs +278 -0
- package/scripts/utils/review/findings.test.mjs +203 -0
- package/scripts/utils/review/head_slice.mjs +132 -0
- package/scripts/utils/review/head_slice.test.mjs +117 -0
- package/scripts/utils/review/instructions/deep.md +20 -0
- package/scripts/utils/review/prompts.mjs +279 -0
- package/scripts/utils/review/prompts.test.mjs +77 -0
- package/scripts/utils/review/run_reviewers_safe.mjs +12 -0
- package/scripts/utils/review/run_reviewers_safe.test.mjs +45 -0
- package/scripts/utils/review/runners/augment.mjs +91 -0
- package/scripts/utils/review/runners/augment.test.mjs +64 -0
- package/scripts/utils/review/runners/claude.mjs +92 -0
- package/scripts/utils/review/runners/claude.test.mjs +47 -0
- package/scripts/utils/review/runners/coderabbit.mjs +105 -0
- package/scripts/utils/review/runners/coderabbit.test.mjs +32 -0
- package/scripts/utils/review/runners/codex.mjs +129 -0
- package/scripts/utils/review/runners/codex.test.mjs +115 -0
- package/scripts/utils/review/slice_mode.mjs +20 -0
- package/scripts/utils/review/slice_mode.test.mjs +69 -0
- package/scripts/utils/review/sliced_runner.mjs +39 -0
- package/scripts/utils/review/sliced_runner.test.mjs +57 -0
- package/scripts/utils/review/slices.mjs +140 -0
- package/scripts/utils/review/slices.test.mjs +41 -0
- package/scripts/utils/review/targets.mjs +23 -0
- package/scripts/utils/review/targets.test.mjs +31 -0
- package/scripts/utils/review/tool_home_seed.mjs +106 -0
- package/scripts/utils/review/tool_home_seed.test.mjs +124 -0
- package/scripts/utils/review/uncommitted_ops.mjs +77 -0
- package/scripts/utils/review/uncommitted_ops.test.mjs +117 -0
- package/scripts/utils/sandbox/review_pr_sandbox.mjs +105 -0
- package/scripts/utils/server/apply_server_light_env_defaults.mjs +14 -0
- package/scripts/utils/server/flavor_scripts.mjs +138 -0
- package/scripts/utils/server/flavor_scripts.test.mjs +115 -0
- package/scripts/utils/server/infra/happy_server_infra.mjs +444 -0
- package/scripts/utils/server/mobile_api_url.mjs +60 -0
- package/scripts/utils/server/mobile_api_url.test.mjs +58 -0
- package/scripts/utils/server/port.mjs +55 -0
- package/scripts/utils/server/prisma_import.mjs +36 -0
- package/scripts/utils/server/prisma_import.test.mjs +78 -0
- package/scripts/utils/server/server.mjs +109 -0
- package/scripts/utils/server/ui_build_check.mjs +37 -0
- package/scripts/utils/server/ui_build_check.test.mjs +70 -0
- package/scripts/utils/server/ui_env.mjs +13 -0
- package/scripts/utils/server/ui_env.test.mjs +57 -0
- package/scripts/utils/server/urls.mjs +100 -0
- package/scripts/utils/server/validate.mjs +60 -0
- package/scripts/utils/server/validate.test.mjs +76 -0
- package/scripts/utils/service/autostart_darwin.mjs +198 -0
- package/scripts/utils/service/autostart_darwin.test.mjs +49 -0
- package/scripts/utils/service/autostart_darwin_keepalive.test.mjs +19 -0
- package/scripts/utils/stack/cli_identities.mjs +29 -0
- package/scripts/utils/stack/context.mjs +19 -0
- package/scripts/utils/stack/dirs.mjs +26 -0
- package/scripts/utils/stack/editor_workspace.mjs +126 -0
- package/scripts/utils/stack/interactive_stack_config.mjs +266 -0
- package/scripts/utils/stack/interactive_stack_config.port_validation.test.mjs +93 -0
- package/scripts/utils/stack/interactive_stack_config.remote_validation.test.mjs +122 -0
- package/scripts/utils/stack/interactive_stack_config.stack_name_validation.test.mjs +76 -0
- package/scripts/utils/stack/interactive_stack_config_testkit.mjs +18 -0
- package/scripts/utils/stack/names.mjs +27 -0
- package/scripts/utils/stack/names.test.mjs +26 -0
- package/scripts/utils/stack/pr_stack_name.mjs +16 -0
- package/scripts/utils/stack/runtime_state.mjs +88 -0
- package/scripts/utils/stack/stacks.mjs +40 -0
- package/scripts/utils/stack/startup.mjs +370 -0
- package/scripts/utils/stack/startup_server_light_dirs.test.mjs +119 -0
- package/scripts/utils/stack/startup_server_light_generate.test.mjs +20 -0
- package/scripts/utils/stack/startup_server_light_legacy.test.mjs +79 -0
- package/scripts/utils/stack/startup_server_light_testkit.mjs +106 -0
- package/scripts/utils/stack/stop.mjs +284 -0
- package/scripts/utils/stack_context.mjs +1 -0
- package/scripts/utils/stack_runtime_state.mjs +1 -0
- package/scripts/utils/stacks.mjs +1 -0
- package/scripts/utils/tailscale/ip.mjs +116 -0
- package/scripts/utils/tauri/stack_overrides.mjs +22 -0
- package/scripts/utils/test/collect_test_files.mjs +29 -0
- package/scripts/utils/time/get_today_ymd.mjs +7 -0
- package/scripts/utils/tui/cleanup.mjs +38 -0
- package/scripts/utils/ui/ansi.mjs +47 -0
- package/scripts/utils/ui/browser.mjs +31 -0
- package/scripts/utils/ui/browser.test.mjs +56 -0
- package/scripts/utils/ui/clipboard.mjs +38 -0
- package/scripts/utils/ui/layout.mjs +44 -0
- package/scripts/utils/ui/qr.mjs +17 -0
- package/scripts/utils/ui/terminal_launcher.mjs +129 -0
- package/scripts/utils/ui/text.mjs +16 -0
- package/scripts/utils/update/auto_update_notice.mjs +93 -0
- package/scripts/utils/validate.mjs +5 -0
- package/scripts/where.mjs +138 -0
- package/scripts/worktrees.mjs +2174 -0
- package/scripts/worktrees_archive_cmd.integration.test.mjs +228 -0
- package/scripts/worktrees_cursor_monorepo_root.test.mjs +23 -0
- package/scripts/worktrees_list_specs_no_recurse.test.mjs +32 -0
- package/scripts/worktrees_monorepo_testkit.test.mjs +29 -0
- package/scripts/worktrees_monorepo_use_group.test.mjs +41 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { readdir, stat } from 'node:fs/promises';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
5
|
+
|
|
6
|
+
import { run, runCapture } from '../proc/proc.mjs';
|
|
7
|
+
import { preferStackLocalhostUrl } from '../paths/localhost_host.mjs';
|
|
8
|
+
import { guidedStackWebSignupThenLogin } from './guided_stack_web_login.mjs';
|
|
9
|
+
import { getComponentDir, resolveStackEnvPath } from '../paths/paths.mjs';
|
|
10
|
+
import { getExpoStatePaths, isStateProcessRunning, looksLikeExpoMetro } from '../expo/expo.mjs';
|
|
11
|
+
import { resolveLocalhostHost } from '../paths/localhost_host.mjs';
|
|
12
|
+
import { getStackRuntimeStatePath, isPidAlive, readStackRuntimeStateFile } from '../stack/runtime_state.mjs';
|
|
13
|
+
import { readEnvObjectFromFile } from '../env/read.mjs';
|
|
14
|
+
import { resolveServerUrls } from '../server/urls.mjs';
|
|
15
|
+
|
|
16
|
+
function extractEnvVar(cmd, key) {
|
|
17
|
+
const re = new RegExp(`${key}="([^"]+)"`);
|
|
18
|
+
const m = String(cmd ?? '').match(re);
|
|
19
|
+
return m?.[1] ? String(m[1]) : '';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function resolveRuntimeExpoWebappUrlForAuth({ stackName }) {
|
|
23
|
+
try {
|
|
24
|
+
const runtimeStatePath = getStackRuntimeStatePath(stackName);
|
|
25
|
+
const st = await readStackRuntimeStateFile(runtimeStatePath);
|
|
26
|
+
const ownerPid = Number(st?.ownerPid);
|
|
27
|
+
if (!isPidAlive(ownerPid)) return '';
|
|
28
|
+
const expoPid = Number(st?.processes?.expoPid);
|
|
29
|
+
if (Number.isFinite(expoPid) && expoPid > 1 && !isPidAlive(expoPid)) return '';
|
|
30
|
+
const port = Number(st?.expo?.port ?? st?.expo?.webPort ?? st?.expo?.mobilePort);
|
|
31
|
+
if (!Number.isFinite(port) || port <= 0) return '';
|
|
32
|
+
const live = await looksLikeExpoMetro({ port, timeoutMs: 900 });
|
|
33
|
+
if (!live) return '';
|
|
34
|
+
const host = resolveLocalhostHost({ stackMode: true, stackName });
|
|
35
|
+
return `http://${host}:${port}`;
|
|
36
|
+
} catch {
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function resolveExpoWebappUrlForAuth({ rootDir, stackName, timeoutMs }) {
|
|
42
|
+
const baseDir = resolveStackEnvPath(stackName).baseDir;
|
|
43
|
+
void rootDir; // kept for API stability; url resolution is stack-dir based
|
|
44
|
+
|
|
45
|
+
// IMPORTANT:
|
|
46
|
+
// In PR stacks (and especially in sandbox), the UI directory is typically a worktree path.
|
|
47
|
+
// Expo state paths include a hash derived from projectDir, so we cannot assume a stable uiDir
|
|
48
|
+
// here (e.g. the default checkout). Instead, scan the stack's expo-dev state directory and pick
|
|
49
|
+
// the running Expo instance.
|
|
50
|
+
const expoDevRoot = join(baseDir, 'expo-dev');
|
|
51
|
+
|
|
52
|
+
async function resolveExpectedUiDir() {
|
|
53
|
+
try {
|
|
54
|
+
const { envPath } = resolveStackEnvPath(stackName);
|
|
55
|
+
const stackEnv = await readEnvObjectFromFile(envPath);
|
|
56
|
+
const merged = { ...process.env, ...stackEnv };
|
|
57
|
+
return resolve(getComponentDir(rootDir, 'happier-ui', merged));
|
|
58
|
+
} catch {
|
|
59
|
+
return '';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function findRunningExpoStateUrl() {
|
|
64
|
+
if (!existsSync(expoDevRoot)) return '';
|
|
65
|
+
let entries = [];
|
|
66
|
+
try {
|
|
67
|
+
entries = await readdir(expoDevRoot, { withFileTypes: true });
|
|
68
|
+
} catch {
|
|
69
|
+
return '';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const expectedUiDir = await resolveExpectedUiDir();
|
|
73
|
+
const expectedUiDirResolved = expectedUiDir ? resolve(expectedUiDir) : '';
|
|
74
|
+
|
|
75
|
+
let best = null;
|
|
76
|
+
for (const ent of entries) {
|
|
77
|
+
if (!ent.isDirectory()) continue;
|
|
78
|
+
const statePath = join(expoDevRoot, ent.name, 'expo.state.json');
|
|
79
|
+
if (!existsSync(statePath)) continue;
|
|
80
|
+
// eslint-disable-next-line no-await-in-loop
|
|
81
|
+
const running = await isStateProcessRunning(statePath);
|
|
82
|
+
if (!running.running) continue;
|
|
83
|
+
|
|
84
|
+
// If the state includes capabilities, require web for auth (dev-client-only isn't enough).
|
|
85
|
+
const hasCaps = running.state && typeof running.state === 'object' && 'webEnabled' in running.state;
|
|
86
|
+
const webEnabled = hasCaps ? Boolean(running.state?.webEnabled) : true;
|
|
87
|
+
if (!webEnabled) continue;
|
|
88
|
+
|
|
89
|
+
// Tighten: if the stack env specifies an explicit UI directory, only accept Expo state that
|
|
90
|
+
// matches it. This avoids accidentally selecting stale Expo state left under this stack dir.
|
|
91
|
+
if (expectedUiDirResolved) {
|
|
92
|
+
const uiDirRaw = String(running.state?.uiDir ?? '').trim();
|
|
93
|
+
if (!uiDirRaw) continue;
|
|
94
|
+
if (resolve(uiDirRaw) !== expectedUiDirResolved) continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const port = Number(running.state?.port);
|
|
98
|
+
if (!Number.isFinite(port) || port <= 0) continue;
|
|
99
|
+
|
|
100
|
+
// If we're only considering this "running" because the port is occupied (pid not alive),
|
|
101
|
+
// do a quick Metro probe so we don't accept an unrelated process reusing the port.
|
|
102
|
+
// Note: `isStateProcessRunning` already verifies Metro /status for port-only cases.
|
|
103
|
+
|
|
104
|
+
// Prefer newest (startedAt) and prefer real pid-verified instances.
|
|
105
|
+
const startedAtMs = Date.parse(String(running.state?.startedAt ?? '')) || 0;
|
|
106
|
+
const score = (running.reason === 'pid' ? 1_000_000_000 : 0) + startedAtMs;
|
|
107
|
+
if (!best || score > best.score) {
|
|
108
|
+
best = { port, score };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!best) return '';
|
|
113
|
+
const host = resolveLocalhostHost({ stackMode: stackName !== 'main', stackName });
|
|
114
|
+
return `http://${host}:${best.port}`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const deadline = Date.now() + timeoutMs;
|
|
118
|
+
while (Date.now() < deadline) {
|
|
119
|
+
// eslint-disable-next-line no-await-in-loop
|
|
120
|
+
const url = await findRunningExpoStateUrl();
|
|
121
|
+
if (url) return url;
|
|
122
|
+
// eslint-disable-next-line no-await-in-loop
|
|
123
|
+
await delay(200);
|
|
124
|
+
}
|
|
125
|
+
return '';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function fetchText(url, { timeoutMs = 2000 } = {}) {
|
|
129
|
+
const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
|
|
130
|
+
const timeout = setTimeout(() => controller?.abort(), timeoutMs);
|
|
131
|
+
try {
|
|
132
|
+
const res = await fetch(url, { signal: controller?.signal });
|
|
133
|
+
const text = await res.text().catch(() => '');
|
|
134
|
+
return { ok: res.ok, status: res.status, text, headers: res.headers };
|
|
135
|
+
} catch (e) {
|
|
136
|
+
return { ok: false, status: 0, text: String(e?.message ?? e), headers: null };
|
|
137
|
+
} finally {
|
|
138
|
+
clearTimeout(timeout);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function pickHtmlBundlePath(html) {
|
|
143
|
+
const m = String(html ?? '').match(/<script[^>]+src="([^"]+)"[^>]*><\/script>/i);
|
|
144
|
+
return m?.[1] ? String(m[1]) : '';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function parseExpoBundleErrorPayload(payload) {
|
|
148
|
+
try {
|
|
149
|
+
const parsed = JSON.parse(String(payload ?? ''));
|
|
150
|
+
const type = String(parsed?.type ?? '').trim();
|
|
151
|
+
const message = String(parsed?.message ?? '').trim();
|
|
152
|
+
if (!type && !message) return null;
|
|
153
|
+
const isResolverError = type === 'UnableToResolveError' || message.includes('Unable to resolve module');
|
|
154
|
+
return { type, message, isResolverError };
|
|
155
|
+
} catch {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function detectSymlinkedNodeModules({ worktreeDir }) {
|
|
161
|
+
try {
|
|
162
|
+
const p = join(worktreeDir, 'node_modules');
|
|
163
|
+
const st = await stat(p);
|
|
164
|
+
return Boolean(st.isSymbolicLink && st.isSymbolicLink());
|
|
165
|
+
} catch {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export async function assertExpoWebappBundlesOrThrow({ rootDir, stackName, webappUrl }) {
|
|
171
|
+
const u = new URL(webappUrl);
|
|
172
|
+
const port = u.port ? Number(u.port) : null;
|
|
173
|
+
const probeHost = Number.isFinite(port) ? '127.0.0.1' : u.hostname;
|
|
174
|
+
const base = `${u.protocol}//${probeHost}${u.port ? `:${u.port}` : ''}`;
|
|
175
|
+
|
|
176
|
+
// Retry briefly: Metro can be up while the first bundle compile is still warming.
|
|
177
|
+
const deadline = Date.now() + 60_000;
|
|
178
|
+
let lastError = '';
|
|
179
|
+
while (Date.now() < deadline) {
|
|
180
|
+
// eslint-disable-next-line no-await-in-loop
|
|
181
|
+
const htmlRes = await fetchText(`${base}/`, { timeoutMs: 2500 });
|
|
182
|
+
if (!htmlRes.ok) {
|
|
183
|
+
lastError = `HTTP ${htmlRes.status} loading ${base}/`;
|
|
184
|
+
// eslint-disable-next-line no-await-in-loop
|
|
185
|
+
await delay(500);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const bundlePath = pickHtmlBundlePath(htmlRes.text);
|
|
190
|
+
if (!bundlePath) {
|
|
191
|
+
lastError = `could not find bundle <script src> in ${base}/`;
|
|
192
|
+
// eslint-disable-next-line no-await-in-loop
|
|
193
|
+
await delay(500);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// eslint-disable-next-line no-await-in-loop
|
|
198
|
+
const bundleRes = await fetchText(`${base}${bundlePath.startsWith('/') ? '' : '/'}${bundlePath}`, { timeoutMs: 8000 });
|
|
199
|
+
if (bundleRes.ok) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Metro resolver errors are deterministic: surface immediately with actionable hints.
|
|
204
|
+
const bundleError = parseExpoBundleErrorPayload(bundleRes.text);
|
|
205
|
+
if (bundleError?.isResolverError) {
|
|
206
|
+
let hint = '';
|
|
207
|
+
try {
|
|
208
|
+
const { envPath } = resolveStackEnvPath(stackName);
|
|
209
|
+
const stackEnv = await readEnvObjectFromFile(envPath);
|
|
210
|
+
const uiDir = getComponentDir(rootDir, 'happier-ui', { ...process.env, ...stackEnv });
|
|
211
|
+
const symlinked = uiDir ? await detectSymlinkedNodeModules({ worktreeDir: uiDir }) : false;
|
|
212
|
+
if (symlinked) {
|
|
213
|
+
hint =
|
|
214
|
+
'\n' +
|
|
215
|
+
'[auth] Hint: this looks like an Expo/Metro resolution failure with symlinked node_modules.\n' +
|
|
216
|
+
'[auth] Fix: re-run review-pr/setup-pr with `--deps=install` (avoid linking node_modules for happy).\n';
|
|
217
|
+
}
|
|
218
|
+
} catch {
|
|
219
|
+
// ignore
|
|
220
|
+
}
|
|
221
|
+
throw new Error(
|
|
222
|
+
'[auth] Expo web UI is running, but the web bundle failed to build.\n' +
|
|
223
|
+
`[auth] URL: ${webappUrl}\n` +
|
|
224
|
+
`[auth] Error: ${bundleError.message || bundleError.type || `HTTP ${bundleRes.status}`}\n` +
|
|
225
|
+
hint
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
lastError = `HTTP ${bundleRes.status} loading bundle ${bundlePath}`;
|
|
230
|
+
// eslint-disable-next-line no-await-in-loop
|
|
231
|
+
await delay(500);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (lastError) {
|
|
235
|
+
throw new Error(
|
|
236
|
+
'[auth] Expo web UI did not become ready for guided login (bundle not loadable).\n' +
|
|
237
|
+
`[auth] URL: ${webappUrl}\n` +
|
|
238
|
+
`[auth] Last error: ${lastError}\n` +
|
|
239
|
+
'[auth] Tip: re-run with --verbose to see Expo logs (or open the stack runner log file).'
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export async function resolveStackWebappUrlForAuth({ rootDir, stackName, env = process.env }) {
|
|
245
|
+
// Fast path: runtime Expo metadata is a hint, but only accepted when liveness checks pass.
|
|
246
|
+
const runtimeExpoUrl = await resolveRuntimeExpoWebappUrlForAuth({ stackName });
|
|
247
|
+
if (runtimeExpoUrl) {
|
|
248
|
+
return await preferStackLocalhostUrl(runtimeExpoUrl, { stackName });
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const authFlow =
|
|
252
|
+
(env.HAPPIER_STACK_AUTH_FLOW ?? '').toString().trim() === '1' ||
|
|
253
|
+
(env.HAPPIER_STACK_DAEMON_WAIT_FOR_AUTH ?? '').toString().trim() === '1';
|
|
254
|
+
|
|
255
|
+
// Prefer the Expo web UI URL when running in dev mode.
|
|
256
|
+
// This is crucial for guided login: the browser needs the UI origin, not the server port.
|
|
257
|
+
const timeoutMsRaw =
|
|
258
|
+
(env.HAPPIER_STACK_AUTH_UI_READY_TIMEOUT_MS ?? '180000').toString().trim();
|
|
259
|
+
const timeoutMs = timeoutMsRaw ? Number(timeoutMsRaw) : 180_000;
|
|
260
|
+
const expoUrl = await resolveExpoWebappUrlForAuth({
|
|
261
|
+
rootDir,
|
|
262
|
+
stackName,
|
|
263
|
+
timeoutMs: Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 180_000,
|
|
264
|
+
});
|
|
265
|
+
if (expoUrl) {
|
|
266
|
+
return await preferStackLocalhostUrl(expoUrl, { stackName });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Fail closed for guided auth flows: falling back to server URLs opens the wrong origin.
|
|
270
|
+
if (authFlow) {
|
|
271
|
+
throw new Error(
|
|
272
|
+
`[auth] failed to resolve Expo web UI URL for guided login.\n` +
|
|
273
|
+
`[auth] Reason: Expo web UI did not become ready within ${Number.isFinite(timeoutMs) ? timeoutMs : 180_000}ms.\n` +
|
|
274
|
+
`[auth] Fix: re-run and wait for Expo to start, or run in prod mode (--start) if you want server-served UI.`
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
const raw = await runCapture(
|
|
280
|
+
process.execPath,
|
|
281
|
+
[join(rootDir, 'scripts', 'stack.mjs'), 'auth', stackName, '--', 'login', '--print', '--json'],
|
|
282
|
+
{
|
|
283
|
+
cwd: rootDir,
|
|
284
|
+
env,
|
|
285
|
+
}
|
|
286
|
+
);
|
|
287
|
+
const parsed = JSON.parse(String(raw ?? '').trim());
|
|
288
|
+
const cmd = typeof parsed?.cmd === 'string' ? parsed.cmd : '';
|
|
289
|
+
const url = extractEnvVar(cmd, 'HAPPIER_WEBAPP_URL');
|
|
290
|
+
return url ? await preferStackLocalhostUrl(url, { stackName }) : '';
|
|
291
|
+
} catch {
|
|
292
|
+
return '';
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function resolvePortFromUrl(urlRaw) {
|
|
297
|
+
const raw = String(urlRaw ?? '').trim();
|
|
298
|
+
if (!raw) return null;
|
|
299
|
+
try {
|
|
300
|
+
const parsed = new URL(raw);
|
|
301
|
+
if (!parsed.port) return null;
|
|
302
|
+
const n = Number(parsed.port);
|
|
303
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
304
|
+
} catch {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function resolveServerPortForCoreAuth({ stackName, env = process.env }) {
|
|
310
|
+
const direct = Number((env.HAPPIER_STACK_SERVER_PORT ?? '').toString().trim());
|
|
311
|
+
if (Number.isFinite(direct) && direct > 0) return direct;
|
|
312
|
+
|
|
313
|
+
const fromInternal = resolvePortFromUrl(env.HAPPIER_SERVER_URL);
|
|
314
|
+
if (fromInternal) return fromInternal;
|
|
315
|
+
|
|
316
|
+
const fromPublic = resolvePortFromUrl(env.HAPPIER_PUBLIC_SERVER_URL);
|
|
317
|
+
if (fromPublic) return fromPublic;
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
const runtimeStatePath = getStackRuntimeStatePath(stackName);
|
|
321
|
+
const st = await readStackRuntimeStateFile(runtimeStatePath);
|
|
322
|
+
const runtimePort = Number(st?.ports?.server);
|
|
323
|
+
if (Number.isFinite(runtimePort) && runtimePort > 0) return runtimePort;
|
|
324
|
+
} catch {
|
|
325
|
+
// ignore
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async function prepareCoreAuthEnv({ stackName, webappUrl, env = process.env } = {}) {
|
|
332
|
+
const name = String(stackName ?? '').trim() || 'main';
|
|
333
|
+
const merged = { ...process.env, ...(env ?? {}) };
|
|
334
|
+
const { baseDir } = resolveStackEnvPath(name, merged);
|
|
335
|
+
|
|
336
|
+
const serverPort = await resolveServerPortForCoreAuth({ stackName: name, env: merged });
|
|
337
|
+
if (!serverPort) {
|
|
338
|
+
throw new Error('[auth] cannot run stack login: unable to resolve stack server port');
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const internalServerUrl = String(merged.HAPPIER_SERVER_URL ?? '').trim() || `http://127.0.0.1:${serverPort}`;
|
|
342
|
+
const resolvedPublic = await resolveServerUrls({
|
|
343
|
+
env: merged,
|
|
344
|
+
serverPort,
|
|
345
|
+
allowEnable: false,
|
|
346
|
+
});
|
|
347
|
+
const publicServerUrl =
|
|
348
|
+
String(merged.HAPPIER_PUBLIC_SERVER_URL ?? '').trim() || String(resolvedPublic.publicServerUrl ?? '').trim();
|
|
349
|
+
if (!publicServerUrl) {
|
|
350
|
+
throw new Error('[auth] cannot run stack login: unable to resolve public server URL');
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const cliHomeDir =
|
|
354
|
+
String(merged.HAPPIER_HOME_DIR ?? '').trim() ||
|
|
355
|
+
String(merged.HAPPIER_STACK_CLI_HOME_DIR ?? '').trim() ||
|
|
356
|
+
join(baseDir, 'cli');
|
|
357
|
+
|
|
358
|
+
return {
|
|
359
|
+
...merged,
|
|
360
|
+
HAPPIER_HOME_DIR: cliHomeDir,
|
|
361
|
+
HAPPIER_SERVER_URL: internalServerUrl,
|
|
362
|
+
HAPPIER_PUBLIC_SERVER_URL: publicServerUrl,
|
|
363
|
+
HAPPIER_WEBAPP_URL: webappUrl,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function buildStackAuthLoginInvocation({ rootDir, stackName, webappUrl, env = process.env } = {}) {
|
|
368
|
+
const root = String(rootDir ?? '').trim();
|
|
369
|
+
if (!root) {
|
|
370
|
+
throw new Error('[auth] buildStackAuthLoginInvocation requires rootDir');
|
|
371
|
+
}
|
|
372
|
+
const url = String(webappUrl ?? '').trim();
|
|
373
|
+
if (!url) {
|
|
374
|
+
throw new Error('[auth] buildStackAuthLoginInvocation requires a webappUrl');
|
|
375
|
+
}
|
|
376
|
+
const cliBin = join(getComponentDir(root, 'happier-cli', env), 'bin', 'happier.mjs');
|
|
377
|
+
const merged = { ...(env ?? process.env), HAPPIER_WEBAPP_URL: url };
|
|
378
|
+
const method = String(merged.HAPPIER_AUTH_METHOD ?? '').trim().toLowerCase();
|
|
379
|
+
if (method && method !== 'web' && method !== 'browser' && method !== 'mobile') {
|
|
380
|
+
throw new Error(`[auth] invalid HAPPIER_AUTH_METHOD=${method} (expected: web|browser|mobile)`);
|
|
381
|
+
}
|
|
382
|
+
const normalizedMethod = method === 'browser' ? 'web' : method;
|
|
383
|
+
|
|
384
|
+
const args = [cliBin, 'auth', 'login'];
|
|
385
|
+
if (String(merged.HAPPIER_AUTH_FORCE ?? '').trim() === '1') {
|
|
386
|
+
args.push('--force');
|
|
387
|
+
}
|
|
388
|
+
if (String(merged.HAPPIER_NO_BROWSER_OPEN ?? '').trim() === '1') {
|
|
389
|
+
args.push('--no-open');
|
|
390
|
+
}
|
|
391
|
+
if (normalizedMethod) {
|
|
392
|
+
args.push('--method', normalizedMethod);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
args,
|
|
397
|
+
env: merged,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export async function guidedStackAuthLoginNow({ rootDir, stackName, env = process.env, webappUrl = null }) {
|
|
402
|
+
const name = String(stackName ?? '').trim() || 'main';
|
|
403
|
+
const resolved = (webappUrl ?? '').toString().trim() || (await resolveStackWebappUrlForAuth({ rootDir, stackName: name, env }));
|
|
404
|
+
if (!resolved) {
|
|
405
|
+
throw new Error('[auth] cannot start guided login: web UI URL is empty');
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const skipBundleCheck = (env.HAPPIER_STACK_AUTH_SKIP_BUNDLE_CHECK ?? '').toString().trim() === '1';
|
|
409
|
+
// Surface common "blank page" issues (Metro resolver errors) even in quiet mode.
|
|
410
|
+
if (!skipBundleCheck) {
|
|
411
|
+
await assertExpoWebappBundlesOrThrow({ rootDir, stackName: name, webappUrl: resolved });
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
415
|
+
if (interactive) {
|
|
416
|
+
await guidedStackWebSignupThenLogin({ webappUrl: resolved, stackName: name });
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const preparedEnv = await prepareCoreAuthEnv({ stackName: name, webappUrl: resolved, env });
|
|
420
|
+
const inv = buildStackAuthLoginInvocation({ rootDir, stackName: name, webappUrl: resolved, env: preparedEnv });
|
|
421
|
+
await run(process.execPath, inv.args, { cwd: rootDir, env: inv.env });
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export async function stackAuthCopyFrom({ rootDir, stackName, fromStackName, env = process.env, link = true }) {
|
|
425
|
+
await run(
|
|
426
|
+
process.execPath,
|
|
427
|
+
[
|
|
428
|
+
join(rootDir, 'scripts', 'stack.mjs'),
|
|
429
|
+
'auth',
|
|
430
|
+
stackName,
|
|
431
|
+
'--',
|
|
432
|
+
'copy-from',
|
|
433
|
+
fromStackName,
|
|
434
|
+
...(link ? ['--link'] : []),
|
|
435
|
+
],
|
|
436
|
+
{ cwd: rootDir, env }
|
|
437
|
+
);
|
|
438
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function getFlagValue({ argv, kv, flag }) {
|
|
2
|
+
const k = String(flag ?? '').trim();
|
|
3
|
+
if (!k) return undefined;
|
|
4
|
+
|
|
5
|
+
const fromKv = kv?.get?.(k);
|
|
6
|
+
if (fromKv !== undefined) {
|
|
7
|
+
return fromKv;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Support space-separated args like: --platform android
|
|
11
|
+
// Prefer the last occurrence (typical CLI behavior).
|
|
12
|
+
const args = Array.isArray(argv) ? argv : [];
|
|
13
|
+
for (let i = args.length - 2; i >= 0; i -= 1) {
|
|
14
|
+
if (args[i] !== k) continue;
|
|
15
|
+
const next = args[i + 1];
|
|
16
|
+
if (typeof next !== 'string') return undefined;
|
|
17
|
+
if (next.startsWith('--')) return undefined;
|
|
18
|
+
return next;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
|
|
4
|
+
import { getFlagValue } from './arg_values.mjs';
|
|
5
|
+
|
|
6
|
+
test('getFlagValue prefers kv values over argv', () => {
|
|
7
|
+
const argv = ['build', '--platform', 'android'];
|
|
8
|
+
const kv = new Map([['--platform', 'ios']]);
|
|
9
|
+
assert.equal(getFlagValue({ argv, kv, flag: '--platform' }), 'ios');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('getFlagValue reads space-separated flag values', () => {
|
|
13
|
+
const argv = ['build', '--platform', 'android'];
|
|
14
|
+
const kv = new Map();
|
|
15
|
+
assert.equal(getFlagValue({ argv, kv, flag: '--platform' }), 'android');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('getFlagValue ignores space-separated values when the next token is another flag', () => {
|
|
19
|
+
const argv = ['build', '--platform', '--profile', 'production'];
|
|
20
|
+
const kv = new Map();
|
|
21
|
+
assert.equal(getFlagValue({ argv, kv, flag: '--platform' }), undefined);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('getFlagValue uses the last occurrence in argv', () => {
|
|
25
|
+
const argv = ['build', '--platform', 'ios', '--platform', 'android'];
|
|
26
|
+
const kv = new Map();
|
|
27
|
+
assert.equal(getFlagValue({ argv, kv, flag: '--platform' }), 'android');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('getFlagValue falls back to argv when kv entry is undefined', () => {
|
|
31
|
+
const argv = ['build', '--platform', 'android'];
|
|
32
|
+
const kv = new Map([['--platform', undefined]]);
|
|
33
|
+
assert.equal(getFlagValue({ argv, kv, flag: '--platform' }), 'android');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('getFlagValue returns undefined when flag token is empty', () => {
|
|
37
|
+
assert.equal(getFlagValue({ argv: ['--platform', 'android'], kv: new Map(), flag: ' ' }), undefined);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('getFlagValue ignores equals-form flags and missing string values', () => {
|
|
41
|
+
assert.equal(getFlagValue({ argv: ['--platform=android'], kv: new Map(), flag: '--platform' }), undefined);
|
|
42
|
+
assert.equal(getFlagValue({ argv: ['--platform', 123], kv: new Map(), flag: '--platform' }), undefined);
|
|
43
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function parseArgs(argv) {
|
|
2
|
+
const flags = new Set();
|
|
3
|
+
const kv = new Map();
|
|
4
|
+
for (const raw of argv) {
|
|
5
|
+
if (!raw.startsWith('--')) {
|
|
6
|
+
continue;
|
|
7
|
+
}
|
|
8
|
+
const [k, v] = raw.split('=', 2);
|
|
9
|
+
if (v === undefined) {
|
|
10
|
+
flags.add(k);
|
|
11
|
+
} else {
|
|
12
|
+
kv.set(k, v);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return { flags, kv };
|
|
16
|
+
}
|
|
17
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function wantsJson(argv, { flags = null } = {}) {
|
|
2
|
+
if (flags?.has('--json')) {
|
|
3
|
+
return true;
|
|
4
|
+
}
|
|
5
|
+
return argv.includes('--json');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function wantsHelp(argv, { flags = null } = {}) {
|
|
9
|
+
if (flags?.has('--help')) {
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
return argv.includes('--help') || argv.includes('-h');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function printResult({ json, data, text }) {
|
|
16
|
+
if (json) {
|
|
17
|
+
process.stdout.write(JSON.stringify(data ?? null, null, 2) + '\n');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (text) {
|
|
21
|
+
process.stdout.write(text.endsWith('\n') ? text : text + '\n');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|