@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,163 @@
|
|
|
1
|
+
import { chmod, mkdir, readFile, unlink, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { getHappyStacksHomeDir } from '../paths/paths.mjs';
|
|
6
|
+
|
|
7
|
+
export function getDevAuthKeyPath(env = process.env) {
|
|
8
|
+
return join(getHappyStacksHomeDir(env), 'keys', 'dev-auth.json');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function base64UrlToBytes(s) {
|
|
12
|
+
try {
|
|
13
|
+
const raw = String(s ?? '').trim();
|
|
14
|
+
if (!raw) return null;
|
|
15
|
+
const b64 = raw.replace(/-/g, '+').replace(/_/g, '/');
|
|
16
|
+
const pad = b64.length % 4 === 0 ? '' : '='.repeat(4 - (b64.length % 4));
|
|
17
|
+
return new Uint8Array(Buffer.from(b64 + pad, 'base64'));
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function bytesToBase64Url(bytes) {
|
|
24
|
+
const b64 = Buffer.from(bytes).toString('base64');
|
|
25
|
+
return b64.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Base32 alphabet (RFC 4648)
|
|
29
|
+
const BASE32_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
|
30
|
+
|
|
31
|
+
function bytesToBase32(bytes) {
|
|
32
|
+
let result = '';
|
|
33
|
+
let buffer = 0;
|
|
34
|
+
let bufferLength = 0;
|
|
35
|
+
|
|
36
|
+
for (const byte of bytes) {
|
|
37
|
+
buffer = (buffer << 8) | byte;
|
|
38
|
+
bufferLength += 8;
|
|
39
|
+
while (bufferLength >= 5) {
|
|
40
|
+
bufferLength -= 5;
|
|
41
|
+
result += BASE32_ALPHABET[(buffer >> bufferLength) & 0x1f];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (bufferLength > 0) {
|
|
45
|
+
result += BASE32_ALPHABET[(buffer << (5 - bufferLength)) & 0x1f];
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function base32ToBytes(base32) {
|
|
51
|
+
let normalized = String(base32 ?? '')
|
|
52
|
+
.toUpperCase()
|
|
53
|
+
.replace(/0/g, 'O')
|
|
54
|
+
.replace(/1/g, 'I')
|
|
55
|
+
.replace(/8/g, 'B')
|
|
56
|
+
.replace(/9/g, 'G');
|
|
57
|
+
const cleaned = normalized.replace(/[^A-Z2-7]/g, '');
|
|
58
|
+
if (!cleaned) throw new Error('no valid base32 characters');
|
|
59
|
+
|
|
60
|
+
const bytes = [];
|
|
61
|
+
let buffer = 0;
|
|
62
|
+
let bufferLength = 0;
|
|
63
|
+
for (const char of cleaned) {
|
|
64
|
+
const value = BASE32_ALPHABET.indexOf(char);
|
|
65
|
+
if (value === -1) throw new Error('invalid base32 character');
|
|
66
|
+
buffer = (buffer << 5) | value;
|
|
67
|
+
bufferLength += 5;
|
|
68
|
+
if (bufferLength >= 8) {
|
|
69
|
+
bufferLength -= 8;
|
|
70
|
+
bytes.push((buffer >> bufferLength) & 0xff);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return new Uint8Array(bytes);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function normalizeDevAuthKeyInputToBytes(input) {
|
|
77
|
+
const raw = String(input ?? '').trim();
|
|
78
|
+
if (!raw) return null;
|
|
79
|
+
|
|
80
|
+
// Match Happy UI behavior:
|
|
81
|
+
// - backup format is base32 and is long (usually grouped with '-' / spaces)
|
|
82
|
+
// - base64url is short (~43 chars) and may contain '-' / '_' legitimately
|
|
83
|
+
//
|
|
84
|
+
// Key point: avoid mis-parsing backup base32 as base64.
|
|
85
|
+
if (raw.length > 50) {
|
|
86
|
+
try {
|
|
87
|
+
const b32 = base32ToBytes(raw);
|
|
88
|
+
return b32.length === 32 ? b32 : null;
|
|
89
|
+
} catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const b64 = base64UrlToBytes(raw);
|
|
95
|
+
if (b64 && b64.length === 32) return b64;
|
|
96
|
+
try {
|
|
97
|
+
const b32 = base32ToBytes(raw);
|
|
98
|
+
return b32.length === 32 ? b32 : null;
|
|
99
|
+
} catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function formatDevAuthKeyBackup(secretKeyBase64Url) {
|
|
105
|
+
const bytes = base64UrlToBytes(secretKeyBase64Url);
|
|
106
|
+
if (!bytes || bytes.length !== 32) throw new Error('invalid secret key (expected base64url 32 bytes)');
|
|
107
|
+
const base32 = bytesToBase32(bytes);
|
|
108
|
+
const groups = [];
|
|
109
|
+
for (let i = 0; i < base32.length; i += 5) groups.push(base32.slice(i, i + 5));
|
|
110
|
+
return groups.join('-');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function readDevAuthKey({ env = process.env } = {}) {
|
|
114
|
+
if ((env.HAPPIER_STACK_DEV_AUTH_SECRET_KEY ?? '').toString().trim()) {
|
|
115
|
+
const bytes = normalizeDevAuthKeyInputToBytes(env.HAPPIER_STACK_DEV_AUTH_SECRET_KEY);
|
|
116
|
+
if (!bytes) return { ok: false, error: 'invalid_env_key', source: 'env', secretKeyBase64Url: null, backup: null };
|
|
117
|
+
const base64url = bytesToBase64Url(bytes);
|
|
118
|
+
return { ok: true, source: 'env:HAPPIER_STACK_DEV_AUTH_SECRET_KEY', secretKeyBase64Url: base64url, backup: formatDevAuthKeyBackup(base64url) };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const path = getDevAuthKeyPath(env);
|
|
122
|
+
try {
|
|
123
|
+
if (!existsSync(path)) return { ok: true, source: `file:${path}`, secretKeyBase64Url: null, backup: null, path };
|
|
124
|
+
const raw = await readFile(path, 'utf-8');
|
|
125
|
+
const parsed = JSON.parse(raw);
|
|
126
|
+
const input = parsed?.secretKeyBase64Url ?? parsed?.secretKey ?? parsed?.key ?? null;
|
|
127
|
+
const bytes = normalizeDevAuthKeyInputToBytes(input);
|
|
128
|
+
if (!bytes) return { ok: false, error: 'invalid_file_key', source: `file:${path}`, secretKeyBase64Url: null, backup: null, path };
|
|
129
|
+
const base64url = bytesToBase64Url(bytes);
|
|
130
|
+
return { ok: true, source: `file:${path}`, secretKeyBase64Url: base64url, backup: formatDevAuthKeyBackup(base64url), path };
|
|
131
|
+
} catch (e) {
|
|
132
|
+
return { ok: false, error: 'failed_to_read', source: `file:${path}`, secretKeyBase64Url: null, backup: null, path, details: e instanceof Error ? e.message : String(e) };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function writeDevAuthKey({ env = process.env, input } = {}) {
|
|
137
|
+
const bytes = normalizeDevAuthKeyInputToBytes(input);
|
|
138
|
+
if (!bytes || bytes.length !== 32) {
|
|
139
|
+
throw new Error('invalid secret key (expected 32 bytes; accept base64url or backup format)');
|
|
140
|
+
}
|
|
141
|
+
const secretKeyBase64Url = bytesToBase64Url(bytes);
|
|
142
|
+
const path = getDevAuthKeyPath(env);
|
|
143
|
+
await mkdir(dirname(path), { recursive: true });
|
|
144
|
+
const payload = {
|
|
145
|
+
v: 1,
|
|
146
|
+
createdAt: new Date().toISOString(),
|
|
147
|
+
secretKeyBase64Url,
|
|
148
|
+
};
|
|
149
|
+
await writeFile(path, JSON.stringify(payload, null, 2) + '\n', { encoding: 'utf-8', mode: 0o600 });
|
|
150
|
+
await chmod(path, 0o600).catch(() => {});
|
|
151
|
+
return { ok: true, path, secretKeyBase64Url, backup: formatDevAuthKeyBackup(secretKeyBase64Url) };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function clearDevAuthKey({ env = process.env } = {}) {
|
|
155
|
+
const path = getDevAuthKeyPath(env);
|
|
156
|
+
try {
|
|
157
|
+
if (!existsSync(path)) return { ok: true, deleted: false, path };
|
|
158
|
+
await unlink(path);
|
|
159
|
+
return { ok: true, deleted: true, path };
|
|
160
|
+
} catch (e) {
|
|
161
|
+
return { ok: false, deleted: false, path, error: e instanceof Error ? e.message : String(e) };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { chmod, copyFile, lstat, symlink, unlink, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { ensureDir } from '../fs/ops.mjs';
|
|
6
|
+
|
|
7
|
+
export async function removeFileOrSymlinkIfExists(path) {
|
|
8
|
+
try {
|
|
9
|
+
const st = await lstat(path);
|
|
10
|
+
if (st.isDirectory()) {
|
|
11
|
+
throw new Error(`[auth] refusing to remove directory path: ${path}`);
|
|
12
|
+
}
|
|
13
|
+
await unlink(path);
|
|
14
|
+
return true;
|
|
15
|
+
} catch (e) {
|
|
16
|
+
if (e && typeof e === 'object' && 'code' in e && e.code === 'ENOENT') return false;
|
|
17
|
+
throw e;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function writeSecretFileIfMissing({ path, secret, force = false }) {
|
|
22
|
+
if (!force && existsSync(path)) return false;
|
|
23
|
+
if (force && existsSync(path)) {
|
|
24
|
+
await removeFileOrSymlinkIfExists(path);
|
|
25
|
+
}
|
|
26
|
+
await ensureDir(dirname(path));
|
|
27
|
+
await writeFile(path, secret, { encoding: 'utf-8', mode: 0o600 });
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function copyFileIfMissing({ from, to, mode, force = false }) {
|
|
32
|
+
if (!force && existsSync(to)) return false;
|
|
33
|
+
if (!existsSync(from)) return false;
|
|
34
|
+
await ensureDir(dirname(to));
|
|
35
|
+
// IMPORTANT: if `to` is a symlink and we "overwrite" it, do NOT write through it to the symlink target.
|
|
36
|
+
if (force && existsSync(to)) {
|
|
37
|
+
await removeFileOrSymlinkIfExists(to);
|
|
38
|
+
}
|
|
39
|
+
await copyFile(from, to);
|
|
40
|
+
if (mode) {
|
|
41
|
+
await chmod(to, mode).catch(() => {});
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function linkFileIfMissing({ from, to, force = false }) {
|
|
47
|
+
if (!force && existsSync(to)) return false;
|
|
48
|
+
if (!existsSync(from)) return false;
|
|
49
|
+
await ensureDir(dirname(to));
|
|
50
|
+
if (force && existsSync(to)) {
|
|
51
|
+
await removeFileOrSymlinkIfExists(to);
|
|
52
|
+
}
|
|
53
|
+
await symlink(from, to);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { isTty, promptSelect, withRl } from '../cli/wizard.mjs';
|
|
2
|
+
import { detectSeedableAuthSources } from './sources.mjs';
|
|
3
|
+
import { bold, cyan, dim, green } from '../ui/ansi.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Decide how a PR review stack should authenticate.
|
|
7
|
+
*
|
|
8
|
+
* This deliberately does NOT offer "legacy ~/.happy" sources:
|
|
9
|
+
* for production/remote Happy installs we cannot reliably seed local DB Account rows, so it leads to broken stacks.
|
|
10
|
+
*/
|
|
11
|
+
export async function decidePrAuthPlan({
|
|
12
|
+
interactive = isTty(),
|
|
13
|
+
seedAuthFlag = null,
|
|
14
|
+
explicitFrom = '',
|
|
15
|
+
defaultLoginNow = true,
|
|
16
|
+
} = {}) {
|
|
17
|
+
if (seedAuthFlag === false) return { mode: 'login', loginNow: defaultLoginNow };
|
|
18
|
+
if (seedAuthFlag === true) {
|
|
19
|
+
// Caller must supply from; if not, pick best available.
|
|
20
|
+
const sources = detectSeedableAuthSources();
|
|
21
|
+
const from = explicitFrom || sources[0] || 'main';
|
|
22
|
+
return { mode: 'seed', from, link: true };
|
|
23
|
+
}
|
|
24
|
+
if (explicitFrom) {
|
|
25
|
+
return { mode: 'seed', from: explicitFrom, link: true };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const sources = detectSeedableAuthSources();
|
|
29
|
+
if (!interactive) {
|
|
30
|
+
// Non-interactive default: prefer seeding only if explicitly configured elsewhere.
|
|
31
|
+
// setup-pr will handle its own defaults.
|
|
32
|
+
return { mode: 'auto', sources };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// If there's nothing to reuse, don't ask a pointless question.
|
|
36
|
+
if (!sources.length) {
|
|
37
|
+
return { mode: 'login', loginNow: defaultLoginNow, reason: 'no_seed_sources' };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Interactive prompt: keep it simple for reviewers.
|
|
41
|
+
const choice = await withRl(async (rl) => {
|
|
42
|
+
const opts = [];
|
|
43
|
+
if (sources.length) {
|
|
44
|
+
opts.push({
|
|
45
|
+
label: `reuse existing auth (${cyan(sources.join(' / '))})`,
|
|
46
|
+
value: 'seed',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
opts.push({
|
|
50
|
+
label: defaultLoginNow ? `login now (${green('recommended')})` : 'login later',
|
|
51
|
+
value: 'login',
|
|
52
|
+
});
|
|
53
|
+
return await promptSelect(rl, {
|
|
54
|
+
title: `${bold('Authentication for this PR stack')}\n${dim('Choose whether to reuse existing credentials or do a fresh guided login.')}`,
|
|
55
|
+
options: opts,
|
|
56
|
+
defaultIndex: 0,
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (choice === 'seed' && sources.length) {
|
|
61
|
+
let from = sources[0];
|
|
62
|
+
if (sources.length > 1) {
|
|
63
|
+
from = await withRl(async (rl) => {
|
|
64
|
+
return await promptSelect(rl, {
|
|
65
|
+
title: `${bold('Which auth should we reuse?')}\n${dim('Pick the stack whose credentials should be shared into this PR stack.')}`,
|
|
66
|
+
options: sources.map((s) => ({ label: s, value: s })),
|
|
67
|
+
defaultIndex: 0,
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
const link = await withRl(async (rl) => {
|
|
72
|
+
return await promptSelect(rl, {
|
|
73
|
+
title: `${bold('Reuse mode')}\n${dim('Symlink stays up to date; copy is more isolated.')}`,
|
|
74
|
+
options: [
|
|
75
|
+
{ label: `symlink (${green('recommended')}) — stays up to date`, value: true },
|
|
76
|
+
{ label: 'copy — more isolated per stack', value: false },
|
|
77
|
+
],
|
|
78
|
+
defaultIndex: 0,
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
return { mode: 'seed', from: String(from), link: Boolean(link) };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return { mode: 'login', loginNow: defaultLoginNow };
|
|
85
|
+
}
|
|
86
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { prompt, withRl } from '../cli/wizard.mjs';
|
|
2
|
+
import { openUrlInBrowser } from '../ui/browser.mjs';
|
|
3
|
+
import { preferStackLocalhostUrl } from '../paths/localhost_host.mjs';
|
|
4
|
+
import { bold, cyan, dim, green, yellow } from '../ui/ansi.mjs';
|
|
5
|
+
|
|
6
|
+
export async function guidedStackWebSignupThenLogin({ webappUrl, stackName }) {
|
|
7
|
+
const url = await preferStackLocalhostUrl(webappUrl, { stackName });
|
|
8
|
+
|
|
9
|
+
// eslint-disable-next-line no-console
|
|
10
|
+
console.log('');
|
|
11
|
+
// eslint-disable-next-line no-console
|
|
12
|
+
console.log(bold(`${cyan('Happier')} login`));
|
|
13
|
+
|
|
14
|
+
// Step 1/2
|
|
15
|
+
// eslint-disable-next-line no-console
|
|
16
|
+
console.log(dim('Step 1/2 — open the web app'));
|
|
17
|
+
// eslint-disable-next-line no-console
|
|
18
|
+
console.log(`We’ll open the Happier web app so you can ${bold('create an account')} (or ${bold('log in')}).`);
|
|
19
|
+
if (url) {
|
|
20
|
+
// eslint-disable-next-line no-console
|
|
21
|
+
console.log(`${dim('URL:')} ${cyan(url)}`);
|
|
22
|
+
}
|
|
23
|
+
// eslint-disable-next-line no-console
|
|
24
|
+
console.log('');
|
|
25
|
+
// eslint-disable-next-line no-console
|
|
26
|
+
console.log(`${bold('Press Enter')} to open it in your browser.`);
|
|
27
|
+
await withRl(async (rl) => {
|
|
28
|
+
await prompt(rl, '', { defaultValue: '' });
|
|
29
|
+
});
|
|
30
|
+
if (url) {
|
|
31
|
+
await openUrlInBrowser(url);
|
|
32
|
+
}
|
|
33
|
+
// eslint-disable-next-line no-console
|
|
34
|
+
console.log(`${green('✓')} Browser opened`);
|
|
35
|
+
|
|
36
|
+
// Step 2/2
|
|
37
|
+
// eslint-disable-next-line no-console
|
|
38
|
+
console.log('');
|
|
39
|
+
// eslint-disable-next-line no-console
|
|
40
|
+
console.log(dim('Step 2/2 — connect this terminal'));
|
|
41
|
+
// eslint-disable-next-line no-console
|
|
42
|
+
console.log(`Next, we’ll connect ${bold('this terminal')} to your Happier account.`);
|
|
43
|
+
// eslint-disable-next-line no-console
|
|
44
|
+
console.log(`When prompted, choose: ${bold('Web Browser')} ${dim('(press 2)')}.`);
|
|
45
|
+
// eslint-disable-next-line no-console
|
|
46
|
+
console.log('');
|
|
47
|
+
// eslint-disable-next-line no-console
|
|
48
|
+
console.log(`After you’ve created/logged in in the browser, ${bold('press Enter')} to continue.`);
|
|
49
|
+
// eslint-disable-next-line no-console
|
|
50
|
+
console.log(dim(`Tip: if the page is blank, wait for the first build to finish, then retry.`));
|
|
51
|
+
// eslint-disable-next-line no-console
|
|
52
|
+
console.log(dim(`Tip: you can always re-run later with: ${yellow(`hstack stack auth ${stackName || 'main'} login`)}`));
|
|
53
|
+
await withRl(async (rl) => {
|
|
54
|
+
await prompt(rl, '', { defaultValue: '' });
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
|
|
3
|
+
import { parseEnvToObject } from '../env/dotenv.mjs';
|
|
4
|
+
import { resolveStackEnvPath } from '../paths/paths.mjs';
|
|
5
|
+
import { getEnvValue } from '../env/values.mjs';
|
|
6
|
+
import { readTextIfExists } from '../fs/ops.mjs';
|
|
7
|
+
import { stackExistsSync } from '../stack/stacks.mjs';
|
|
8
|
+
|
|
9
|
+
export async function resolveHandyMasterSecretFromStack({
|
|
10
|
+
stackName,
|
|
11
|
+
requireStackExists = false,
|
|
12
|
+
} = {}) {
|
|
13
|
+
const name = String(stackName ?? '').trim() || 'main';
|
|
14
|
+
|
|
15
|
+
if (requireStackExists && !stackExistsSync(name)) {
|
|
16
|
+
throw new Error(`[auth] cannot copy auth: source stack "${name}" does not exist`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const resolved = resolveStackEnvPath(name);
|
|
20
|
+
const sourceBaseDir = resolved.baseDir;
|
|
21
|
+
const sourceEnvPath = resolved.envPath;
|
|
22
|
+
const raw = await readTextIfExists(sourceEnvPath);
|
|
23
|
+
const env = raw ? parseEnvToObject(raw) : {};
|
|
24
|
+
|
|
25
|
+
const inline = getEnvValue(env, 'HANDY_MASTER_SECRET');
|
|
26
|
+
if (inline) {
|
|
27
|
+
return { secret: inline, source: `${sourceEnvPath} (HANDY_MASTER_SECRET)` };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const secretFile = getEnvValue(env, 'HAPPIER_STACK_HANDY_MASTER_SECRET_FILE');
|
|
31
|
+
if (secretFile) {
|
|
32
|
+
const secret = await readTextIfExists(secretFile);
|
|
33
|
+
if (secret) return { secret, source: secretFile };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const dataDir = getEnvValue(env, 'HAPPIER_SERVER_LIGHT_DATA_DIR') || join(sourceBaseDir, 'server-light');
|
|
37
|
+
const secretPath = join(dataDir, 'handy-master-secret.txt');
|
|
38
|
+
const secret = await readTextIfExists(secretPath);
|
|
39
|
+
if (secret) return { secret, source: secretPath };
|
|
40
|
+
|
|
41
|
+
return { secret: null, source: null };
|
|
42
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { isTty, promptSelect, withRl } from '../cli/wizard.mjs';
|
|
2
|
+
import { detectSeedableAuthSources } from './sources.mjs';
|
|
3
|
+
import { stackAuthCopyFrom } from './stack_guided_login.mjs';
|
|
4
|
+
import { runOrchestratedGuidedAuthFlow } from './orchestrated_stack_auth_flow.mjs';
|
|
5
|
+
import { bold, cyan, dim, green } from '../ui/ansi.mjs';
|
|
6
|
+
import { findAnyCredentialPathInCliHome } from './credentials_paths.mjs';
|
|
7
|
+
|
|
8
|
+
export function needsAuthSeed({ cliHomeDir, accountCount }) {
|
|
9
|
+
const hasAccessKey = Boolean(findAnyCredentialPathInCliHome({ cliHomeDir }));
|
|
10
|
+
const hasAccounts = typeof accountCount === 'number' ? accountCount > 0 : null;
|
|
11
|
+
return !hasAccessKey || hasAccounts === false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function maybeRunInteractiveStackAuthSetup({
|
|
15
|
+
rootDir,
|
|
16
|
+
env = process.env,
|
|
17
|
+
stackName,
|
|
18
|
+
cliHomeDir,
|
|
19
|
+
accountCount,
|
|
20
|
+
isInteractive = isTty(),
|
|
21
|
+
autoSeedEnabled = false,
|
|
22
|
+
beforeLogin = null,
|
|
23
|
+
} = {}) {
|
|
24
|
+
if (!isInteractive) return { ok: true, skipped: true, reason: 'non_interactive' };
|
|
25
|
+
if (autoSeedEnabled) return { ok: true, skipped: true, reason: 'auto_seed_enabled' };
|
|
26
|
+
if (!needsAuthSeed({ cliHomeDir, accountCount })) return { ok: true, skipped: true, reason: 'already_initialized' };
|
|
27
|
+
|
|
28
|
+
const sources = detectSeedableAuthSources().filter((s) => s && s !== stackName);
|
|
29
|
+
const hasDevAuth = sources.includes('dev-auth');
|
|
30
|
+
const hasMain = sources.includes('main');
|
|
31
|
+
|
|
32
|
+
let choice = 'login';
|
|
33
|
+
if (hasDevAuth || hasMain) {
|
|
34
|
+
choice = await withRl(async (rl) => {
|
|
35
|
+
const opts = [];
|
|
36
|
+
if (hasDevAuth) {
|
|
37
|
+
opts.push({ label: `reuse ${cyan('dev-auth')} (${green('recommended')}) — no re-login`, value: 'dev-auth' });
|
|
38
|
+
}
|
|
39
|
+
if (hasMain) {
|
|
40
|
+
opts.push({ label: `reuse ${cyan('main')} — fast, but shares identity with main`, value: 'main' });
|
|
41
|
+
}
|
|
42
|
+
opts.push({ label: `login now — guided browser flow`, value: 'login' });
|
|
43
|
+
return await promptSelect(rl, {
|
|
44
|
+
title: `${bold('Authentication required')}\n${dim(
|
|
45
|
+
`Stack ${cyan(stackName)} needs auth before the daemon can register a machine.`
|
|
46
|
+
)}`,
|
|
47
|
+
options: opts,
|
|
48
|
+
defaultIndex: 0,
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (choice === 'login') {
|
|
54
|
+
if (beforeLogin && typeof beforeLogin === 'function') {
|
|
55
|
+
await beforeLogin();
|
|
56
|
+
}
|
|
57
|
+
await runOrchestratedGuidedAuthFlow({
|
|
58
|
+
rootDir,
|
|
59
|
+
stackName,
|
|
60
|
+
env,
|
|
61
|
+
verbosity: 0,
|
|
62
|
+
json: false,
|
|
63
|
+
});
|
|
64
|
+
return { ok: true, skipped: false, mode: 'login' };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const from = String(choice);
|
|
68
|
+
await stackAuthCopyFrom({ rootDir, stackName, fromStackName: from, env, link: true });
|
|
69
|
+
return { ok: true, skipped: false, mode: 'seed', from, link: true };
|
|
70
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export function normalizeAuthLoginContext(raw) {
|
|
2
|
+
const v = String(raw ?? '')
|
|
3
|
+
.trim()
|
|
4
|
+
.toLowerCase();
|
|
5
|
+
if (v === 'selfhost' || v === 'self-host' || v === 'self_host') return 'selfhost';
|
|
6
|
+
if (v === 'dev' || v === 'developer' || v === 'development') return 'dev';
|
|
7
|
+
if (v === 'stack') return 'stack';
|
|
8
|
+
return 'generic';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
import { bold, cyan, dim, green, yellow } from '../ui/ansi.mjs';
|
|
12
|
+
import { buildConfigureServerLinks } from '@happier-dev/cli-common/links';
|
|
13
|
+
|
|
14
|
+
export function printAuthLoginInstructions({
|
|
15
|
+
stackName,
|
|
16
|
+
context = 'generic',
|
|
17
|
+
webappUrl,
|
|
18
|
+
webappUrlSource,
|
|
19
|
+
internalServerUrl,
|
|
20
|
+
publicServerUrl,
|
|
21
|
+
rerunCmd,
|
|
22
|
+
}) {
|
|
23
|
+
const ctx = normalizeAuthLoginContext(context);
|
|
24
|
+
const subtitle =
|
|
25
|
+
ctx === 'selfhost'
|
|
26
|
+
? 'Self-host'
|
|
27
|
+
: ctx === 'dev'
|
|
28
|
+
? 'Dev'
|
|
29
|
+
: ctx === 'stack'
|
|
30
|
+
? `Stack: ${stackName || 'unknown'}`
|
|
31
|
+
: '';
|
|
32
|
+
|
|
33
|
+
// eslint-disable-next-line no-console
|
|
34
|
+
console.log('');
|
|
35
|
+
// eslint-disable-next-line no-console
|
|
36
|
+
console.log(bold(`${cyan('Happier')} login`));
|
|
37
|
+
if (subtitle) {
|
|
38
|
+
// eslint-disable-next-line no-console
|
|
39
|
+
console.log(dim(subtitle));
|
|
40
|
+
}
|
|
41
|
+
// eslint-disable-next-line no-console
|
|
42
|
+
console.log('');
|
|
43
|
+
// eslint-disable-next-line no-console
|
|
44
|
+
console.log(bold('What will happen:'));
|
|
45
|
+
// eslint-disable-next-line no-console
|
|
46
|
+
console.log(`- ${cyan('browser')}: we’ll open the Happier web app`);
|
|
47
|
+
// eslint-disable-next-line no-console
|
|
48
|
+
console.log(`- ${cyan('account')}: you’ll sign in (or create an account)`);
|
|
49
|
+
// eslint-disable-next-line no-console
|
|
50
|
+
console.log(`- ${cyan('connect')}: you’ll approve this terminal/machine connection`);
|
|
51
|
+
// eslint-disable-next-line no-console
|
|
52
|
+
console.log(`- ${cyan('finish')}: the CLI will complete automatically`);
|
|
53
|
+
|
|
54
|
+
if (webappUrl) {
|
|
55
|
+
// eslint-disable-next-line no-console
|
|
56
|
+
console.log('');
|
|
57
|
+
// eslint-disable-next-line no-console
|
|
58
|
+
console.log(`${dim('Web app:')} ${cyan(webappUrl)}${webappUrlSource ? dim(` (${webappUrlSource})`) : ''}`);
|
|
59
|
+
}
|
|
60
|
+
if (internalServerUrl) {
|
|
61
|
+
// eslint-disable-next-line no-console
|
|
62
|
+
console.log(`${dim('Internal:')} ${internalServerUrl}`);
|
|
63
|
+
}
|
|
64
|
+
if (publicServerUrl) {
|
|
65
|
+
// eslint-disable-next-line no-console
|
|
66
|
+
console.log(`${dim('Public:')} ${publicServerUrl}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (ctx === 'selfhost' && webappUrl && (publicServerUrl || internalServerUrl)) {
|
|
70
|
+
const serverUrl = publicServerUrl || internalServerUrl;
|
|
71
|
+
const links = buildConfigureServerLinks({ webappUrl, serverUrl });
|
|
72
|
+
// eslint-disable-next-line no-console
|
|
73
|
+
console.log('');
|
|
74
|
+
// eslint-disable-next-line no-console
|
|
75
|
+
console.log(bold('Step 0 — Configure your app/web (self-host only)'));
|
|
76
|
+
// eslint-disable-next-line no-console
|
|
77
|
+
console.log(dim('Open the link below, confirm, then sign in/create an account before approving the terminal connection.'));
|
|
78
|
+
// eslint-disable-next-line no-console
|
|
79
|
+
console.log('');
|
|
80
|
+
// eslint-disable-next-line no-console
|
|
81
|
+
console.log(`${dim('Web:')} ${cyan(links.webUrl)}`);
|
|
82
|
+
// eslint-disable-next-line no-console
|
|
83
|
+
console.log(`${dim('Mobile:')} ${cyan(links.mobileUrl)}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (ctx === 'selfhost') {
|
|
87
|
+
// eslint-disable-next-line no-console
|
|
88
|
+
console.log('');
|
|
89
|
+
// eslint-disable-next-line no-console
|
|
90
|
+
console.log(dim('Why this matters: login lets the daemon register this machine and enables sync across devices.'));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// eslint-disable-next-line no-console
|
|
94
|
+
console.log('');
|
|
95
|
+
// eslint-disable-next-line no-console
|
|
96
|
+
console.log(bold('Tips:'));
|
|
97
|
+
// eslint-disable-next-line no-console
|
|
98
|
+
console.log(`- If the page does not load, make sure the stack is running and reachable.`);
|
|
99
|
+
// eslint-disable-next-line no-console
|
|
100
|
+
console.log(`- If you see a blank page, wait for the first build (Expo/Metro) to finish.`);
|
|
101
|
+
// eslint-disable-next-line no-console
|
|
102
|
+
console.log(`- Re-run anytime: ${yellow(rerunCmd || 'hstack auth login')}`);
|
|
103
|
+
// eslint-disable-next-line no-console
|
|
104
|
+
console.log(`${green('✓')} You can safely close the browser when it finishes.`);
|
|
105
|
+
}
|