@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,117 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { dirname, join } from 'node:path';
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
8
|
+
|
|
9
|
+
function runNode(args, { cwd, env }) {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
const proc = spawn(process.execPath, args, { cwd, env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
12
|
+
let stdout = '';
|
|
13
|
+
let stderr = '';
|
|
14
|
+
proc.stdout.on('data', (d) => (stdout += String(d)));
|
|
15
|
+
proc.stderr.on('data', (d) => (stderr += String(d)));
|
|
16
|
+
proc.on('error', reject);
|
|
17
|
+
proc.on('exit', (code, signal) => resolve({ code: code ?? (signal ? 1 : 0), signal, stdout, stderr }));
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function toDataUrl(source) {
|
|
22
|
+
return `data:text/javascript,${encodeURIComponent(source)}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
test('setup_pr guided-login path delegates to orchestrated auth flow helpers at runtime', async () => {
|
|
26
|
+
const scriptsDir = dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
const rootDir = dirname(scriptsDir);
|
|
28
|
+
const tmp = await mkdtemp(join(tmpdir(), 'hstack-setup-pr-orchestrated-'));
|
|
29
|
+
try {
|
|
30
|
+
const markerPath = join(tmp, 'orchestrated.markers.log');
|
|
31
|
+
const loaderPath = join(tmp, 'loader.mjs');
|
|
32
|
+
const registerPath = join(tmp, 'register-loader.mjs');
|
|
33
|
+
await writeFile(markerPath, '', 'utf-8');
|
|
34
|
+
|
|
35
|
+
const stubBySpecifier = {
|
|
36
|
+
'./utils/cli/prereqs.mjs': toDataUrl(`
|
|
37
|
+
export async function assertCliPrereqs() {}
|
|
38
|
+
`),
|
|
39
|
+
'./utils/proc/proc.mjs': toDataUrl(`
|
|
40
|
+
export async function run() {}
|
|
41
|
+
`),
|
|
42
|
+
'./utils/auth/guided_pr_auth.mjs': toDataUrl(`
|
|
43
|
+
export async function decidePrAuthPlan() {
|
|
44
|
+
return { mode: 'login', loginNow: true };
|
|
45
|
+
}
|
|
46
|
+
`),
|
|
47
|
+
'./utils/auth/orchestrated_stack_auth_flow.mjs': toDataUrl(`
|
|
48
|
+
import { appendFileSync } from 'node:fs';
|
|
49
|
+
const markerPath = process.env.HSTACK_SETUP_PR_MARKER;
|
|
50
|
+
function mark(name) {
|
|
51
|
+
if (!markerPath) return;
|
|
52
|
+
appendFileSync(markerPath, String(name) + '\\n', 'utf-8');
|
|
53
|
+
}
|
|
54
|
+
export async function runOrchestratedGuidedAuthFlow() {
|
|
55
|
+
mark('runOrchestratedGuidedAuthFlow');
|
|
56
|
+
return { ok: true, webappUrl: 'http://127.0.0.1:3010' };
|
|
57
|
+
}
|
|
58
|
+
export async function startDaemonPostAuth() {
|
|
59
|
+
mark('startDaemonPostAuth');
|
|
60
|
+
return { ok: true };
|
|
61
|
+
}
|
|
62
|
+
`),
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const loaderSource = `
|
|
66
|
+
const stubBySpecifier = ${JSON.stringify(stubBySpecifier)};
|
|
67
|
+
|
|
68
|
+
export async function resolve(specifier, context, defaultResolve) {
|
|
69
|
+
const stub = stubBySpecifier[specifier];
|
|
70
|
+
if (stub) {
|
|
71
|
+
return { url: stub, shortCircuit: true };
|
|
72
|
+
}
|
|
73
|
+
return defaultResolve(specifier, context, defaultResolve);
|
|
74
|
+
}
|
|
75
|
+
`;
|
|
76
|
+
await writeFile(loaderPath, loaderSource, 'utf-8');
|
|
77
|
+
await writeFile(
|
|
78
|
+
registerPath,
|
|
79
|
+
[
|
|
80
|
+
`import { register } from 'node:module';`,
|
|
81
|
+
`register(${JSON.stringify(pathToFileURL(loaderPath).href)}, import.meta.url);`,
|
|
82
|
+
'',
|
|
83
|
+
].join('\n'),
|
|
84
|
+
'utf-8'
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const env = {
|
|
88
|
+
...process.env,
|
|
89
|
+
HAPPIER_STACK_TEST_TTY: '1',
|
|
90
|
+
HAPPIER_STACK_VERBOSE: '1',
|
|
91
|
+
HAPPIER_STACK_HOME_DIR: join(tmp, 'home'),
|
|
92
|
+
HAPPIER_STACK_STORAGE_DIR: join(tmp, 'storage'),
|
|
93
|
+
HAPPIER_STACK_WORKSPACE_DIR: join(tmp, 'workspace'),
|
|
94
|
+
HSTACK_SETUP_PR_MARKER: markerPath,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const res = await runNode(
|
|
98
|
+
[
|
|
99
|
+
'--import',
|
|
100
|
+
registerPath,
|
|
101
|
+
join(rootDir, 'scripts', 'setup_pr.mjs'),
|
|
102
|
+
'--repo=123',
|
|
103
|
+
'--name=pr123',
|
|
104
|
+
'--dev',
|
|
105
|
+
'--no-seed-auth',
|
|
106
|
+
],
|
|
107
|
+
{ cwd: rootDir, env }
|
|
108
|
+
);
|
|
109
|
+
assert.equal(res.code, 0, `expected exit 0, got ${res.code}\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
110
|
+
|
|
111
|
+
const markerLog = await readFile(markerPath, 'utf-8');
|
|
112
|
+
assert.match(markerLog, /\brunOrchestratedGuidedAuthFlow\b/, `expected setup_pr to call runOrchestratedGuidedAuthFlow\n${markerLog}`);
|
|
113
|
+
assert.match(markerLog, /\bstartDaemonPostAuth\b/, `expected setup_pr to call startDaemonPostAuth\n${markerLog}`);
|
|
114
|
+
} finally {
|
|
115
|
+
await rm(tmp, { recursive: true, force: true });
|
|
116
|
+
}
|
|
117
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { resolvehstackCommand } from '../utils/cli/cli_registry.mjs';
|
|
2
|
+
|
|
3
|
+
const STACK_NAME_FIRST_SUPPORTED_COMMANDS = new Set([
|
|
4
|
+
'new',
|
|
5
|
+
'edit',
|
|
6
|
+
'list',
|
|
7
|
+
'migrate',
|
|
8
|
+
'audit',
|
|
9
|
+
'archive',
|
|
10
|
+
'duplicate',
|
|
11
|
+
'info',
|
|
12
|
+
'pr',
|
|
13
|
+
'create-dev-auth-seed',
|
|
14
|
+
'daemon',
|
|
15
|
+
'happier',
|
|
16
|
+
'env',
|
|
17
|
+
'auth',
|
|
18
|
+
'dev',
|
|
19
|
+
'start',
|
|
20
|
+
'build',
|
|
21
|
+
'review',
|
|
22
|
+
'typecheck',
|
|
23
|
+
'lint',
|
|
24
|
+
'test',
|
|
25
|
+
'doctor',
|
|
26
|
+
'mobile',
|
|
27
|
+
'mobile:install',
|
|
28
|
+
'mobile-dev-client',
|
|
29
|
+
'resume',
|
|
30
|
+
'stop',
|
|
31
|
+
'code',
|
|
32
|
+
'cursor',
|
|
33
|
+
'open',
|
|
34
|
+
'srv',
|
|
35
|
+
'wt',
|
|
36
|
+
'service',
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
export function resolveTopLevelNodeScriptFile(cmd) {
|
|
40
|
+
const command = resolvehstackCommand(cmd);
|
|
41
|
+
if (!command || command.kind !== 'node') return null;
|
|
42
|
+
const rel = String(command.scriptRelPath ?? '').trim();
|
|
43
|
+
if (!rel) return null;
|
|
44
|
+
return rel.startsWith('scripts/') ? rel.slice('scripts/'.length) : rel;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function stackNameFromArg(positionals, idx) {
|
|
48
|
+
const name = positionals[idx]?.trim() ? positionals[idx].trim() : '';
|
|
49
|
+
return name;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function isKnownStackCommandToken(token) {
|
|
53
|
+
const value = (token ?? '').toString().trim();
|
|
54
|
+
if (!value) return false;
|
|
55
|
+
if (value.startsWith('service:')) return true;
|
|
56
|
+
if (value.startsWith('tailscale:')) return true;
|
|
57
|
+
return STACK_NAME_FIRST_SUPPORTED_COMMANDS.has(value);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function normalizeStackNameFirstArgs(argv, { stackExists }) {
|
|
61
|
+
// Back-compat UX:
|
|
62
|
+
// Allow `hstack stack <name> <command> ...` (stack name first) as a shortcut for:
|
|
63
|
+
// `hstack stack <command> <name> ...`
|
|
64
|
+
//
|
|
65
|
+
// We only apply this rewrite when the first positional is *not* a known stack subcommand,
|
|
66
|
+
// but *is* an existing stack name.
|
|
67
|
+
const args = Array.isArray(argv) ? argv : [];
|
|
68
|
+
const positionalIdx = [];
|
|
69
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
70
|
+
const a = args[i];
|
|
71
|
+
if (!a) continue;
|
|
72
|
+
if (a === '--') continue;
|
|
73
|
+
if (a.startsWith('-')) continue;
|
|
74
|
+
positionalIdx.push(i);
|
|
75
|
+
if (positionalIdx.length >= 2) break;
|
|
76
|
+
}
|
|
77
|
+
if (positionalIdx.length < 2) return args;
|
|
78
|
+
|
|
79
|
+
const [i0, i1] = positionalIdx;
|
|
80
|
+
const first = args[i0];
|
|
81
|
+
const second = args[i1];
|
|
82
|
+
|
|
83
|
+
if (isKnownStackCommandToken(first)) return args;
|
|
84
|
+
if (!isKnownStackCommandToken(second)) return args;
|
|
85
|
+
if (typeof stackExists !== 'function' || !stackExists(first)) return args;
|
|
86
|
+
|
|
87
|
+
const next = [...args];
|
|
88
|
+
next[i0] = second;
|
|
89
|
+
next[i1] = first;
|
|
90
|
+
return next;
|
|
91
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { parseEnvToObject } from '../utils/env/dotenv.mjs';
|
|
4
|
+
import { readTextOrEmpty } from '../utils/fs/ops.mjs';
|
|
5
|
+
import { resolveStackEnvPath } from '../utils/paths/paths.mjs';
|
|
6
|
+
import { resolveServerPortFromEnv } from '../utils/server/urls.mjs';
|
|
7
|
+
import { getCliHomeDirFromEnvOrDefault } from '../utils/stack/dirs.mjs';
|
|
8
|
+
import { applyStackActiveServerScopeEnv } from '../utils/auth/stable_scope_id.mjs';
|
|
9
|
+
import { resolveHandyMasterSecretFromStack } from '../utils/auth/handy_master_secret.mjs';
|
|
10
|
+
import { copyFileIfMissing, linkFileIfMissing, writeSecretFileIfMissing } from '../utils/auth/files.mjs';
|
|
11
|
+
import { findAnyCredentialPathInCliHome, findExistingStackCredentialPath, resolveStackCredentialPaths } from '../utils/auth/credentials_paths.mjs';
|
|
12
|
+
|
|
13
|
+
const readExistingEnv = readTextOrEmpty;
|
|
14
|
+
|
|
15
|
+
export async function copyAuthFromStackIntoNewStack({
|
|
16
|
+
fromStackName,
|
|
17
|
+
stackName,
|
|
18
|
+
stackEnv,
|
|
19
|
+
serverComponent,
|
|
20
|
+
json,
|
|
21
|
+
requireSourceStackExists,
|
|
22
|
+
linkMode = false,
|
|
23
|
+
}) {
|
|
24
|
+
const { secret, source } = await resolveHandyMasterSecretFromStack({
|
|
25
|
+
stackName: fromStackName,
|
|
26
|
+
requireStackExists: requireSourceStackExists,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const copied = { secret: false, accessKey: false, settings: false, sourceStack: fromStackName };
|
|
30
|
+
|
|
31
|
+
if (secret) {
|
|
32
|
+
if (serverComponent === 'happier-server-light') {
|
|
33
|
+
const dataDir = stackEnv.HAPPIER_SERVER_LIGHT_DATA_DIR;
|
|
34
|
+
const target = join(dataDir, 'handy-master-secret.txt');
|
|
35
|
+
const sourcePath = source && !String(source).includes('(HANDY_MASTER_SECRET)') ? String(source) : '';
|
|
36
|
+
copied.secret =
|
|
37
|
+
linkMode && sourcePath && existsSync(sourcePath)
|
|
38
|
+
? await linkFileIfMissing({ from: sourcePath, to: target })
|
|
39
|
+
: await writeSecretFileIfMissing({ path: target, secret });
|
|
40
|
+
} else if (serverComponent === 'happier-server') {
|
|
41
|
+
const target = stackEnv.HAPPIER_STACK_HANDY_MASTER_SECRET_FILE;
|
|
42
|
+
if (target) {
|
|
43
|
+
const sourcePath = source && !String(source).includes('(HANDY_MASTER_SECRET)') ? String(source) : '';
|
|
44
|
+
copied.secret =
|
|
45
|
+
linkMode && sourcePath && existsSync(sourcePath)
|
|
46
|
+
? await linkFileIfMissing({ from: sourcePath, to: target })
|
|
47
|
+
: await writeSecretFileIfMissing({ path: target, secret });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const { baseDir: sourceBaseDir, envPath: sourceEnvPath } = resolveStackEnvPath(fromStackName);
|
|
53
|
+
const sourceEnvRaw = await readExistingEnv(sourceEnvPath);
|
|
54
|
+
const sourceEnv = parseEnvToObject(sourceEnvRaw);
|
|
55
|
+
const sourceCli = getCliHomeDirFromEnvOrDefault({ stackBaseDir: sourceBaseDir, env: sourceEnv });
|
|
56
|
+
const targetCli = stackEnv.HAPPIER_STACK_CLI_HOME_DIR;
|
|
57
|
+
const sourceInternalServerUrl = `http://127.0.0.1:${resolveServerPortFromEnv({ env: sourceEnv, defaultPort: 3005 })}`;
|
|
58
|
+
const targetInternalServerUrl = `http://127.0.0.1:${resolveServerPortFromEnv({ env: stackEnv, defaultPort: 3005 })}`;
|
|
59
|
+
const sourceEnvScoped = applyStackActiveServerScopeEnv({ env: sourceEnv, stackName: fromStackName, cliIdentity: 'default' });
|
|
60
|
+
const targetEnvScoped = applyStackActiveServerScopeEnv({ env: stackEnv, stackName, cliIdentity: 'default' });
|
|
61
|
+
const sourceCredentialPaths = resolveStackCredentialPaths({
|
|
62
|
+
cliHomeDir: sourceCli,
|
|
63
|
+
serverUrl: sourceInternalServerUrl,
|
|
64
|
+
env: sourceEnvScoped,
|
|
65
|
+
});
|
|
66
|
+
const targetCredentialPaths = resolveStackCredentialPaths({
|
|
67
|
+
cliHomeDir: targetCli,
|
|
68
|
+
serverUrl: targetInternalServerUrl,
|
|
69
|
+
env: targetEnvScoped,
|
|
70
|
+
});
|
|
71
|
+
const sourceAccessKeyPath =
|
|
72
|
+
findExistingStackCredentialPath({ cliHomeDir: sourceCli, serverUrl: sourceInternalServerUrl, env: sourceEnvScoped }) ||
|
|
73
|
+
[sourceCredentialPaths.serverScopedPath, sourceCredentialPaths.urlHashServerScopedPath, sourceCredentialPaths.legacyPath]
|
|
74
|
+
.filter(Boolean)
|
|
75
|
+
.find((candidate) => existsSync(candidate)) ||
|
|
76
|
+
findAnyCredentialPathInCliHome({ cliHomeDir: sourceCli });
|
|
77
|
+
|
|
78
|
+
if (linkMode) {
|
|
79
|
+
copied.accessKey =
|
|
80
|
+
sourceAccessKeyPath
|
|
81
|
+
? await linkFileIfMissing({ from: sourceAccessKeyPath, to: targetCredentialPaths.serverScopedPath })
|
|
82
|
+
: false;
|
|
83
|
+
copied.settings = await linkFileIfMissing({ from: join(sourceCli, 'settings.json'), to: join(targetCli, 'settings.json') });
|
|
84
|
+
} else {
|
|
85
|
+
copied.accessKey =
|
|
86
|
+
sourceAccessKeyPath
|
|
87
|
+
? await copyFileIfMissing({
|
|
88
|
+
from: sourceAccessKeyPath,
|
|
89
|
+
to: targetCredentialPaths.serverScopedPath,
|
|
90
|
+
mode: 0o600,
|
|
91
|
+
})
|
|
92
|
+
: false;
|
|
93
|
+
copied.settings = await copyFileIfMissing({
|
|
94
|
+
from: join(sourceCli, 'settings.json'),
|
|
95
|
+
to: join(targetCli, 'settings.json'),
|
|
96
|
+
mode: 0o600,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!json) {
|
|
101
|
+
const any = copied.secret || copied.accessKey || copied.settings;
|
|
102
|
+
if (any) {
|
|
103
|
+
console.log(`[stack] copied auth from "${fromStackName}" into "${stackName}" (no re-login needed)`);
|
|
104
|
+
if (copied.secret) console.log(` - master secret: copied (${source || 'unknown source'})`);
|
|
105
|
+
if (copied.accessKey) console.log(` - cli: copied access.key`);
|
|
106
|
+
if (copied.settings) console.log(` - cli: copied settings.json`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return copied;
|
|
111
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { run } from '../utils/proc/proc.mjs';
|
|
3
|
+
import { listAllStackNames } from '../utils/stack/stacks.mjs';
|
|
4
|
+
import { getRuntimePortExtraEnv, withStackEnv } from './stack_environment.mjs';
|
|
5
|
+
|
|
6
|
+
export async function cmdService({ rootDir, stackName, svcCmd }) {
|
|
7
|
+
await withStackEnv({
|
|
8
|
+
stackName,
|
|
9
|
+
fn: async ({ env }) => {
|
|
10
|
+
await run(process.execPath, [join(rootDir, 'scripts', 'service.mjs'), svcCmd], { cwd: rootDir, env });
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function cmdTailscale({ rootDir, stackName, subcmd, args }) {
|
|
16
|
+
const extraEnv = await getRuntimePortExtraEnv(stackName);
|
|
17
|
+
await withStackEnv({
|
|
18
|
+
stackName,
|
|
19
|
+
...(extraEnv ? { extraEnv } : {}),
|
|
20
|
+
fn: async ({ env }) => {
|
|
21
|
+
await run(process.execPath, [join(rootDir, 'scripts', 'tailscale.mjs'), subcmd, ...args], { cwd: rootDir, env });
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function cmdSrv({ rootDir, stackName, args }) {
|
|
27
|
+
// Forward to scripts/server_flavor.mjs under the stack env.
|
|
28
|
+
const forwarded = args[0] === '--' ? args.slice(1) : args;
|
|
29
|
+
await withStackEnv({
|
|
30
|
+
stackName,
|
|
31
|
+
fn: async ({ env }) => {
|
|
32
|
+
await run(process.execPath, [join(rootDir, 'scripts', 'server_flavor.mjs'), ...forwarded], { cwd: rootDir, env });
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function cmdWt({ rootDir, stackName, args }) {
|
|
38
|
+
// Forward to scripts/worktrees.mjs under the stack env.
|
|
39
|
+
// This makes `hstack stack wt <name> -- ...` behave exactly like `hstack wt ...`,
|
|
40
|
+
// but read/write the stack env file (HAPPIER_STACK_ENV_FILE) instead of repo env.local.
|
|
41
|
+
let forwarded = args[0] === '--' ? args.slice(1) : args;
|
|
42
|
+
|
|
43
|
+
// Stack users usually want to see what *this stack* is using (active checkout),
|
|
44
|
+
// not an exhaustive enumeration of every worktree on disk.
|
|
45
|
+
//
|
|
46
|
+
// `hstack wt list` defaults to showing all worktrees. In stack mode, default to
|
|
47
|
+
// an active-only view unless the caller opts into `--all`.
|
|
48
|
+
if (forwarded[0] === 'list') {
|
|
49
|
+
const wantsAll = forwarded.includes('--all') || forwarded.includes('--all-worktrees');
|
|
50
|
+
const wantsActive = forwarded.includes('--active') || forwarded.includes('--active-only');
|
|
51
|
+
if (!wantsAll && !wantsActive) {
|
|
52
|
+
forwarded = [...forwarded, '--active'];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
await withStackEnv({
|
|
57
|
+
stackName,
|
|
58
|
+
fn: async ({ env }) => {
|
|
59
|
+
await run(process.execPath, [join(rootDir, 'scripts', 'worktrees.mjs'), ...forwarded], { cwd: rootDir, env });
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function cmdAuth({ rootDir, stackName, args }) {
|
|
65
|
+
// Forward to scripts/auth.mjs under the stack env.
|
|
66
|
+
// This makes `hstack stack auth <name> ...` resolve CLI home/urls for that stack.
|
|
67
|
+
const forwarded = args[0] === '--' ? args.slice(1) : args;
|
|
68
|
+
const extraEnv = await getRuntimePortExtraEnv(stackName);
|
|
69
|
+
await withStackEnv({
|
|
70
|
+
stackName,
|
|
71
|
+
...(extraEnv ? { extraEnv } : {}),
|
|
72
|
+
fn: async ({ env }) => {
|
|
73
|
+
await run(process.execPath, [join(rootDir, 'scripts', 'auth.mjs'), ...forwarded], { cwd: rootDir, env });
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function cmdListStacks() {
|
|
79
|
+
try {
|
|
80
|
+
const names = (await listAllStackNames()).filter((n) => n !== 'main');
|
|
81
|
+
if (!names.length) {
|
|
82
|
+
console.log('[stack] no stacks found');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
console.log('[stack] stacks:');
|
|
86
|
+
for (const n of names) {
|
|
87
|
+
console.log(`- ${n}`);
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
console.log('[stack] no stacks found');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
const STACK_HELP_USAGE_LINES = [
|
|
2
|
+
'hstack stack new <name> [--port=NNN] [--server=happier-server|happier-server-light] [--repo=default|dev|<owner/...>|<path>] [--db-provider=pglite|sqlite|postgres|mysql] [--database-url=<url>] [--interactive] [--non-interactive] [--copy-auth-from=<stack>] [--no-copy-auth] [--force-port] [--json]',
|
|
3
|
+
'hstack stack edit <name> --interactive [--json]',
|
|
4
|
+
'hstack stack list [--json]',
|
|
5
|
+
'hstack stack audit [--fix] [--fix-main] [--fix-ports] [--fix-workspace] [--fix-paths] [--unpin-ports] [--unpin-ports-except=stack1,stack2] [--json]',
|
|
6
|
+
'hstack stack archive <name> [--dry-run] [--date=YYYY-MM-DD] [--json]',
|
|
7
|
+
'hstack stack duplicate <from> <to> [--duplicate-worktrees] [--deps=none|link|install|link-or-install] [--json]',
|
|
8
|
+
'hstack stack info <name> [--json]',
|
|
9
|
+
'hstack stack pr <name> --repo=<pr-url|number> [--server-flavor=light|full] [--dev|--start] [--json] [-- ...]',
|
|
10
|
+
'hstack stack create-dev-auth-seed [name] [--server=happier-server|happier-server-light] [--login|--no-login] [--skip-default-seed] [--non-interactive] [--json]',
|
|
11
|
+
'hstack stack daemon <name> start|stop|restart|status [--json]',
|
|
12
|
+
'hstack stack happier <name> [-- ...]',
|
|
13
|
+
'hstack stack env <name> set KEY=VALUE [KEY2=VALUE2...] | unset KEY [KEY2...] | get KEY | list | path [--json]',
|
|
14
|
+
'hstack stack auth <name> status|login|copy-from [--json]',
|
|
15
|
+
'hstack stack dev <name> [-- ...]',
|
|
16
|
+
'hstack stack start <name> [-- ...]',
|
|
17
|
+
'hstack stack build <name> [-- ...]',
|
|
18
|
+
'hstack stack review <name> [component...] [--reviewers=coderabbit,codex] [--base-remote=<remote>] [--base-branch=<branch>] [--base-ref=<ref>] [--chunks|--no-chunks] [--chunking=auto|head-slice|commit-window] [--chunk-max-files=N] [--json]',
|
|
19
|
+
'hstack stack typecheck <name> [component...] [--json]',
|
|
20
|
+
'hstack stack lint <name> [component...] [--json]',
|
|
21
|
+
'hstack stack test <name> [component...] [--json]',
|
|
22
|
+
'hstack stack doctor <name> [-- ...]',
|
|
23
|
+
'hstack stack mobile <name> [-- ...]',
|
|
24
|
+
'hstack stack mobile:install <name> [--name="Happier (exp1)"] [--device=...] [--json]',
|
|
25
|
+
'hstack stack mobile-dev-client <name> --install [--device=...] [--clean] [--configuration=Debug|Release] [--json]',
|
|
26
|
+
'hstack stack resume <name> <sessionId...> [--json]',
|
|
27
|
+
'hstack stack stop <name> [--aggressive] [--sweep-owned] [--no-docker] [--json]',
|
|
28
|
+
'hstack stack code <name> [--no-stack-dir] [--include-all-components] [--include-cli-home] [--json]',
|
|
29
|
+
'hstack stack cursor <name> [--no-stack-dir] [--include-all-components] [--include-cli-home] [--json]',
|
|
30
|
+
'hstack stack open <name> [--no-stack-dir] [--include-all-components] [--include-cli-home] [--json] # prefer Cursor, else VS Code',
|
|
31
|
+
'hstack stack srv <name> -- status|use ...',
|
|
32
|
+
'hstack stack wt <name> -- <wt args...>',
|
|
33
|
+
'hstack stack tailscale:status|enable|disable|url <name> [-- ...]',
|
|
34
|
+
'hstack stack service <name> <install|uninstall|status|start|stop|restart|enable|disable|logs|tail>',
|
|
35
|
+
'hstack stack service:* <name> # legacy alias',
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
export const STACK_HELP_COMMANDS = [
|
|
39
|
+
'new',
|
|
40
|
+
'edit',
|
|
41
|
+
'list',
|
|
42
|
+
'audit',
|
|
43
|
+
'archive',
|
|
44
|
+
'duplicate',
|
|
45
|
+
'info',
|
|
46
|
+
'pr',
|
|
47
|
+
'create-dev-auth-seed',
|
|
48
|
+
'daemon',
|
|
49
|
+
'eas',
|
|
50
|
+
'happier',
|
|
51
|
+
'env',
|
|
52
|
+
'auth',
|
|
53
|
+
'dev',
|
|
54
|
+
'start',
|
|
55
|
+
'build',
|
|
56
|
+
'review',
|
|
57
|
+
'typecheck',
|
|
58
|
+
'lint',
|
|
59
|
+
'test',
|
|
60
|
+
'doctor',
|
|
61
|
+
'mobile',
|
|
62
|
+
'mobile:install',
|
|
63
|
+
'mobile-dev-client',
|
|
64
|
+
'resume',
|
|
65
|
+
'stop',
|
|
66
|
+
'code',
|
|
67
|
+
'cursor',
|
|
68
|
+
'open',
|
|
69
|
+
'srv',
|
|
70
|
+
'wt',
|
|
71
|
+
'tailscale:*',
|
|
72
|
+
'service:*',
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
const STACK_HELP_USAGE_BY_CMD = (() => {
|
|
76
|
+
const map = new Map();
|
|
77
|
+
for (const line of STACK_HELP_USAGE_LINES) {
|
|
78
|
+
const parts = line.trim().split(/\s+/);
|
|
79
|
+
if (parts[0] !== 'hstack' || parts[1] !== 'stack') continue;
|
|
80
|
+
const command = parts[2] ?? '';
|
|
81
|
+
if (command) map.set(command, line);
|
|
82
|
+
}
|
|
83
|
+
return map;
|
|
84
|
+
})();
|
|
85
|
+
|
|
86
|
+
export function getStackHelpUsageLine(cmd) {
|
|
87
|
+
const command = (cmd ?? '').toString().trim();
|
|
88
|
+
if (!command) return null;
|
|
89
|
+
return STACK_HELP_USAGE_BY_CMD.get(command) ?? null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function renderStackRootHelpText() {
|
|
93
|
+
return ['[stack] usage:', ...STACK_HELP_USAGE_LINES.map((line) => ` ${line}`)].join('\n');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function renderStackSubcommandHelpText(cmd) {
|
|
97
|
+
const command = (cmd ?? '').toString().trim();
|
|
98
|
+
if (!command) return null;
|
|
99
|
+
|
|
100
|
+
const lines = [];
|
|
101
|
+
const direct = STACK_HELP_USAGE_BY_CMD.get(command);
|
|
102
|
+
if (direct) lines.push(direct);
|
|
103
|
+
else if (command.startsWith('tailscale:')) lines.push(STACK_HELP_USAGE_BY_CMD.get('tailscale:status|enable|disable|url'));
|
|
104
|
+
else if (command.startsWith('service:')) lines.push(STACK_HELP_USAGE_BY_CMD.get('service:*') || STACK_HELP_USAGE_BY_CMD.get('service'));
|
|
105
|
+
|
|
106
|
+
const filtered = lines.filter(Boolean);
|
|
107
|
+
if (!filtered.length) return null;
|
|
108
|
+
|
|
109
|
+
return [`[stack ${command}] usage:`, ...filtered.map((line) => ` ${line}`), '', 'see also:', ' hstack stack --help'].join('\n');
|
|
110
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { readdir } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { readTextOrEmpty } from '../utils/fs/ops.mjs';
|
|
5
|
+
import { isTcpPortFree, pickNextFreeTcpPort } from '../utils/net/ports.mjs';
|
|
6
|
+
import { getStacksStorageRoot } from '../utils/paths/paths.mjs';
|
|
7
|
+
import { parseEnvToObject } from '../utils/env/dotenv.mjs';
|
|
8
|
+
import { coercePort, listPortsFromEnvObject, readPinnedServerPortFromEnvFile, STACK_RESERVED_PORT_KEYS } from '../utils/server/port.mjs';
|
|
9
|
+
|
|
10
|
+
const readExistingEnv = readTextOrEmpty;
|
|
11
|
+
|
|
12
|
+
export function getDefaultPortStart(stackName = null) {
|
|
13
|
+
const raw = process.env.HAPPIER_STACK_STACK_PORT_START?.trim() ? process.env.HAPPIER_STACK_STACK_PORT_START.trim() : '';
|
|
14
|
+
// Default port strategy:
|
|
15
|
+
// - main historically lives at 3005
|
|
16
|
+
// - non-main stacks should avoid 3005 to reduce accidental collisions/confusion
|
|
17
|
+
const target = (stackName ?? '').toString().trim() || (process.env.HAPPIER_STACK_STACK ?? '').trim() || 'main';
|
|
18
|
+
const fallback = target === 'main' ? 3005 : 3009;
|
|
19
|
+
const n = raw ? Number(raw) : fallback;
|
|
20
|
+
return Number.isFinite(n) ? n : fallback;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function isPortFree(port, { host = '127.0.0.1' } = {}) {
|
|
24
|
+
return await isTcpPortFree(port, { host });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function pickNextFreePort(startPort, { reservedPorts = new Set(), host = '127.0.0.1' } = {}) {
|
|
28
|
+
try {
|
|
29
|
+
return await pickNextFreeTcpPort(startPort, { reservedPorts, host });
|
|
30
|
+
} catch (e) {
|
|
31
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
32
|
+
throw new Error(msg.replace(/^\[local\]/, '[stack]'));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function readPortFromEnvFile(envPath) {
|
|
37
|
+
return await readPinnedServerPortFromEnvFile(envPath);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function readPortsFromEnvFile(envPath) {
|
|
41
|
+
const raw = await readExistingEnv(envPath);
|
|
42
|
+
if (!raw.trim()) return [];
|
|
43
|
+
const parsed = parseEnvToObject(raw);
|
|
44
|
+
return listPortsFromEnvObject(parsed, STACK_RESERVED_PORT_KEYS);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function collectReservedStackPorts({ excludeStackName = null } = {}) {
|
|
48
|
+
const reserved = new Set();
|
|
49
|
+
const roots = [getStacksStorageRoot()];
|
|
50
|
+
|
|
51
|
+
for (const root of roots) {
|
|
52
|
+
let entries = [];
|
|
53
|
+
try {
|
|
54
|
+
// eslint-disable-next-line no-await-in-loop
|
|
55
|
+
entries = await readdir(root, { withFileTypes: true });
|
|
56
|
+
} catch {
|
|
57
|
+
entries = [];
|
|
58
|
+
}
|
|
59
|
+
for (const entry of entries) {
|
|
60
|
+
if (!entry.isDirectory()) continue;
|
|
61
|
+
const name = entry.name;
|
|
62
|
+
if (excludeStackName && name === excludeStackName) continue;
|
|
63
|
+
const envPath = join(root, name, 'env');
|
|
64
|
+
// eslint-disable-next-line no-await-in-loop
|
|
65
|
+
const ports = await readPortsFromEnvFile(envPath);
|
|
66
|
+
for (const port of ports) {
|
|
67
|
+
const coerced = coercePort(port);
|
|
68
|
+
if (coerced) reserved.add(coerced);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return reserved;
|
|
74
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { isAbsolute, resolve } from 'node:path';
|
|
2
|
+
import { createWorktree, resolveComponentSpecToDir } from '../utils/git/worktrees.mjs';
|
|
3
|
+
import { coerceHappyMonorepoRootFromPath, getRepoDir, getWorkspaceDir } from '../utils/paths/paths.mjs';
|
|
4
|
+
|
|
5
|
+
export async function resolveRequestedRepoCheckoutDir({ rootDir, repoSelection, remoteName, defaultRepoDir = '' }) {
|
|
6
|
+
if (!repoSelection) return '';
|
|
7
|
+
|
|
8
|
+
let resolved = '';
|
|
9
|
+
if (typeof repoSelection === 'object' && repoSelection.create) {
|
|
10
|
+
const uiPkgDir = await createWorktree({
|
|
11
|
+
rootDir,
|
|
12
|
+
component: 'happier-ui',
|
|
13
|
+
slug: repoSelection.slug,
|
|
14
|
+
remoteName,
|
|
15
|
+
});
|
|
16
|
+
resolved = uiPkgDir ? coerceHappyMonorepoRootFromPath(uiPkgDir) || uiPkgDir : '';
|
|
17
|
+
} else {
|
|
18
|
+
const spec = String(repoSelection ?? '').trim();
|
|
19
|
+
if (spec === 'default' || spec === 'main') {
|
|
20
|
+
resolved = defaultRepoDir || getRepoDir(rootDir, { ...process.env, HAPPIER_STACK_REPO_DIR: '' });
|
|
21
|
+
} else if (spec === 'active') {
|
|
22
|
+
resolved = getRepoDir(rootDir, process.env);
|
|
23
|
+
} else {
|
|
24
|
+
const dir = resolveComponentSpecToDir({ rootDir, component: 'happier-ui', spec });
|
|
25
|
+
const abs = dir ? resolve(rootDir, dir) : isAbsolute(spec) ? spec : resolve(getWorkspaceDir(rootDir), spec);
|
|
26
|
+
resolved = coerceHappyMonorepoRootFromPath(abs) || abs;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return resolved;
|
|
31
|
+
}
|