@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,90 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { chmod, mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { dirname, join } from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { authScriptPath, runNodeCapture, terminateChildProcess } from './testkit/auth_testkit.mjs';
|
|
9
|
+
|
|
10
|
+
test('hstack stack auth copy-from skips pglite DB seed when lock is held by a live pid', async () => {
|
|
11
|
+
const scriptsDir = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const rootDir = dirname(scriptsDir);
|
|
13
|
+
|
|
14
|
+
const tmp = await mkdtemp(join(tmpdir(), 'hstack-auth-copy-from-pglite-lock-in-use-'));
|
|
15
|
+
const homeDir = join(tmp, 'home');
|
|
16
|
+
const storageDir = join(tmp, 'storage');
|
|
17
|
+
const workspaceDir = join(tmp, 'workspace');
|
|
18
|
+
await mkdir(homeDir, { recursive: true });
|
|
19
|
+
await mkdir(storageDir, { recursive: true });
|
|
20
|
+
await mkdir(workspaceDir, { recursive: true });
|
|
21
|
+
|
|
22
|
+
// Stub yarn to keep this test fast/deterministic. This is an external boundary.
|
|
23
|
+
const binDir = join(tmp, 'bin');
|
|
24
|
+
await mkdir(binDir, { recursive: true });
|
|
25
|
+
const yarnPath = join(binDir, 'yarn');
|
|
26
|
+
await writeFile(yarnPath, '#!/bin/bash\nexit 0\n', 'utf-8');
|
|
27
|
+
await chmod(yarnPath, 0o755);
|
|
28
|
+
|
|
29
|
+
const repoRoot = dirname(rootDir); // .../apps/stack -> .../ (monorepo root)
|
|
30
|
+
|
|
31
|
+
const mkStackEnv = async (name) => {
|
|
32
|
+
const baseDir = join(storageDir, name);
|
|
33
|
+
const dataDir = join(baseDir, 'server-light');
|
|
34
|
+
await mkdir(dataDir, { recursive: true });
|
|
35
|
+
await writeFile(
|
|
36
|
+
join(baseDir, 'env'),
|
|
37
|
+
[
|
|
38
|
+
`HAPPIER_STACK_STACK=${name}`,
|
|
39
|
+
`HAPPIER_STACK_SERVER_COMPONENT=happier-server-light`,
|
|
40
|
+
`HAPPIER_STACK_REPO_DIR=${repoRoot}`,
|
|
41
|
+
`HAPPIER_SERVER_LIGHT_DATA_DIR=${dataDir}`,
|
|
42
|
+
`HAPPIER_SERVER_LIGHT_FILES_DIR=${join(dataDir, 'files')}`,
|
|
43
|
+
`HAPPIER_SERVER_LIGHT_DB_DIR=${join(dataDir, 'pglite')}`,
|
|
44
|
+
'',
|
|
45
|
+
].join('\n'),
|
|
46
|
+
'utf-8'
|
|
47
|
+
);
|
|
48
|
+
return { baseDir, dataDir };
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const source = await mkStackEnv('dev-auth');
|
|
52
|
+
await mkStackEnv('dev');
|
|
53
|
+
|
|
54
|
+
const lockPath = join(source.dataDir, '.happier.pglite.lock');
|
|
55
|
+
const holder = spawn(process.execPath, ['-e', 'setInterval(() => {}, 1000)'], { stdio: 'ignore' });
|
|
56
|
+
assert.ok(holder.pid && holder.pid > 1);
|
|
57
|
+
try {
|
|
58
|
+
await writeFile(
|
|
59
|
+
lockPath,
|
|
60
|
+
JSON.stringify({ pid: holder.pid, createdAt: new Date().toISOString(), purpose: 'test', dbDir: join(source.dataDir, 'pglite') }) + '\n',
|
|
61
|
+
'utf-8'
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const env = {
|
|
65
|
+
...process.env,
|
|
66
|
+
PATH: `${binDir}:${process.env.PATH ?? ''}`,
|
|
67
|
+
HAPPIER_STACK_HOME_DIR: homeDir,
|
|
68
|
+
HAPPIER_STACK_STORAGE_DIR: storageDir,
|
|
69
|
+
HAPPIER_STACK_WORKSPACE_DIR: workspaceDir,
|
|
70
|
+
HAPPIER_STACK_STACK: 'dev',
|
|
71
|
+
HAPPIER_STACK_ENV_FILE: join(storageDir, 'dev', 'env'),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const res = await runNodeCapture([authScriptPath(rootDir), 'copy-from', 'dev-auth'], { cwd: rootDir, env });
|
|
75
|
+
assert.equal(res.code, 0, `expected exit 0, got ${res.code}\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
76
|
+
const combinedOutput = `${res.stdout}\n${res.stderr}`;
|
|
77
|
+
assert.match(combinedOutput, /\bdb seed skipped\b/i, `expected db seed to be skipped\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
78
|
+
assert.match(
|
|
79
|
+
combinedOutput,
|
|
80
|
+
/\bpglite db dir is in use by pid(?:=|\b)/i,
|
|
81
|
+
`expected message about live pglite lock\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`
|
|
82
|
+
);
|
|
83
|
+
} finally {
|
|
84
|
+
try {
|
|
85
|
+
await terminateChildProcess(holder, { signal: 'SIGTERM', timeoutMs: 1200 });
|
|
86
|
+
} finally {
|
|
87
|
+
await rm(tmp, { recursive: true, force: true });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createServer } from 'node:http';
|
|
4
|
+
import { chmod, mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { dirname, join } from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { resolveStackCredentialPaths } from './utils/auth/credentials_paths.mjs';
|
|
9
|
+
import { buildStackStableScopeId } from './utils/auth/stable_scope_id.mjs';
|
|
10
|
+
import { authScriptPath, runNodeCapture } from './testkit/auth_testkit.mjs';
|
|
11
|
+
|
|
12
|
+
test('hstack stack auth copy-from does not hit ReferenceError: runCapture is not defined', async (t) => {
|
|
13
|
+
const scriptsDir = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const rootDir = dirname(scriptsDir);
|
|
15
|
+
|
|
16
|
+
const tmp = await mkdtemp(join(tmpdir(), 'hstack-auth-copy-from-'));
|
|
17
|
+
t.after(async () => {
|
|
18
|
+
await rm(tmp, { recursive: true, force: true });
|
|
19
|
+
});
|
|
20
|
+
const homeDir = join(tmp, 'home');
|
|
21
|
+
const storageDir = join(tmp, 'storage');
|
|
22
|
+
const workspaceDir = join(tmp, 'workspace');
|
|
23
|
+
await mkdir(homeDir, { recursive: true });
|
|
24
|
+
await mkdir(storageDir, { recursive: true });
|
|
25
|
+
await mkdir(workspaceDir, { recursive: true });
|
|
26
|
+
|
|
27
|
+
// Stub yarn to keep this test fast/deterministic. This is an external boundary.
|
|
28
|
+
const binDir = join(tmp, 'bin');
|
|
29
|
+
await mkdir(binDir, { recursive: true });
|
|
30
|
+
const yarnPath = join(binDir, 'yarn');
|
|
31
|
+
await writeFile(yarnPath, '#!/bin/bash\nexit 0\n', 'utf-8');
|
|
32
|
+
await chmod(yarnPath, 0o755);
|
|
33
|
+
|
|
34
|
+
const repoRoot = dirname(rootDir); // .../apps/stack -> .../ (monorepo root)
|
|
35
|
+
|
|
36
|
+
const mkStackEnv = async (name) => {
|
|
37
|
+
const baseDir = join(storageDir, name);
|
|
38
|
+
const dataDir = join(baseDir, 'server-light');
|
|
39
|
+
await mkdir(dataDir, { recursive: true });
|
|
40
|
+
await writeFile(
|
|
41
|
+
join(baseDir, 'env'),
|
|
42
|
+
[
|
|
43
|
+
`HAPPIER_STACK_STACK=${name}`,
|
|
44
|
+
`HAPPIER_STACK_SERVER_COMPONENT=happier-server-light`,
|
|
45
|
+
`HAPPIER_STACK_REPO_DIR=${repoRoot}`,
|
|
46
|
+
`HAPPIER_SERVER_LIGHT_DATA_DIR=${dataDir}`,
|
|
47
|
+
`HAPPIER_SERVER_LIGHT_FILES_DIR=${join(dataDir, 'files')}`,
|
|
48
|
+
`HAPPIER_SERVER_LIGHT_DB_DIR=${join(dataDir, 'pglite')}`,
|
|
49
|
+
'',
|
|
50
|
+
].join('\n'),
|
|
51
|
+
'utf-8'
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
await mkStackEnv('dev-auth');
|
|
56
|
+
await mkStackEnv('dev');
|
|
57
|
+
|
|
58
|
+
const env = {
|
|
59
|
+
...process.env,
|
|
60
|
+
PATH: `${binDir}:${process.env.PATH ?? ''}`,
|
|
61
|
+
HAPPIER_STACK_HOME_DIR: homeDir,
|
|
62
|
+
HAPPIER_STACK_STORAGE_DIR: storageDir,
|
|
63
|
+
HAPPIER_STACK_WORKSPACE_DIR: workspaceDir,
|
|
64
|
+
HAPPIER_STACK_STACK: 'dev',
|
|
65
|
+
HAPPIER_STACK_ENV_FILE: join(storageDir, 'dev', 'env'),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const res = await runNodeCapture([authScriptPath(rootDir), 'copy-from', 'dev-auth'], { cwd: rootDir, env });
|
|
69
|
+
assert.equal(res.code, 0, `expected exit 0, got ${res.code}\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
70
|
+
assert.ok(
|
|
71
|
+
!res.stdout.includes('runCapture is not defined') && !res.stderr.includes('runCapture is not defined'),
|
|
72
|
+
`expected no ReferenceError about runCapture\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`
|
|
73
|
+
);
|
|
74
|
+
assert.ok(
|
|
75
|
+
!res.stdout.includes('spawn yarn ENOENT') && !res.stderr.includes('spawn yarn ENOENT'),
|
|
76
|
+
`expected yarn to be resolvable in light migrations step\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('hstack stack auth copy-from prefers source server-scoped credential over unrelated legacy key', async (t) => {
|
|
81
|
+
const scriptsDir = dirname(fileURLToPath(import.meta.url));
|
|
82
|
+
const rootDir = dirname(scriptsDir);
|
|
83
|
+
|
|
84
|
+
const tmp = await mkdtemp(join(tmpdir(), 'hstack-auth-copy-from-prefer-server-scoped-'));
|
|
85
|
+
t.after(async () => {
|
|
86
|
+
await rm(tmp, { recursive: true, force: true });
|
|
87
|
+
});
|
|
88
|
+
const homeDir = join(tmp, 'home');
|
|
89
|
+
const storageDir = join(tmp, 'storage');
|
|
90
|
+
const workspaceDir = join(tmp, 'workspace');
|
|
91
|
+
await mkdir(homeDir, { recursive: true });
|
|
92
|
+
await mkdir(storageDir, { recursive: true });
|
|
93
|
+
await mkdir(workspaceDir, { recursive: true });
|
|
94
|
+
|
|
95
|
+
const binDir = join(tmp, 'bin');
|
|
96
|
+
await mkdir(binDir, { recursive: true });
|
|
97
|
+
const yarnPath = join(binDir, 'yarn');
|
|
98
|
+
await writeFile(yarnPath, '#!/bin/bash\nexit 0\n', 'utf-8');
|
|
99
|
+
await chmod(yarnPath, 0o755);
|
|
100
|
+
|
|
101
|
+
const repoRoot = dirname(rootDir);
|
|
102
|
+
const sourceStack = 'dev-auth';
|
|
103
|
+
const targetStack = 'dev';
|
|
104
|
+
const serverPort = 4201;
|
|
105
|
+
const serverUrl = `http://127.0.0.1:${serverPort}`;
|
|
106
|
+
const sourceCliHome = join(storageDir, sourceStack, 'cli');
|
|
107
|
+
const targetCliHome = join(storageDir, targetStack, 'cli');
|
|
108
|
+
|
|
109
|
+
const mkStackEnv = async (name, cliHomeDir) => {
|
|
110
|
+
const baseDir = join(storageDir, name);
|
|
111
|
+
const dataDir = join(baseDir, 'server-light');
|
|
112
|
+
await mkdir(dataDir, { recursive: true });
|
|
113
|
+
await mkdir(cliHomeDir, { recursive: true });
|
|
114
|
+
await writeFile(
|
|
115
|
+
join(baseDir, 'env'),
|
|
116
|
+
[
|
|
117
|
+
`HAPPIER_STACK_STACK=${name}`,
|
|
118
|
+
`HAPPIER_STACK_SERVER_COMPONENT=happier-server-light`,
|
|
119
|
+
`HAPPIER_STACK_REPO_DIR=${repoRoot}`,
|
|
120
|
+
`HAPPIER_STACK_CLI_HOME_DIR=${cliHomeDir}`,
|
|
121
|
+
`HAPPIER_STACK_SERVER_PORT=${serverPort}`,
|
|
122
|
+
`HAPPIER_SERVER_LIGHT_DATA_DIR=${dataDir}`,
|
|
123
|
+
`HAPPIER_SERVER_LIGHT_FILES_DIR=${join(dataDir, 'files')}`,
|
|
124
|
+
`HAPPIER_SERVER_LIGHT_DB_DIR=${join(dataDir, 'pglite')}`,
|
|
125
|
+
'',
|
|
126
|
+
].join('\n'),
|
|
127
|
+
'utf-8'
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
await mkStackEnv(sourceStack, sourceCliHome);
|
|
132
|
+
await mkStackEnv(targetStack, targetCliHome);
|
|
133
|
+
|
|
134
|
+
const sourceCred = resolveStackCredentialPaths({ cliHomeDir: sourceCliHome, serverUrl });
|
|
135
|
+
await mkdir(dirname(sourceCred.serverScopedPath), { recursive: true });
|
|
136
|
+
await writeFile(join(sourceCliHome, 'access.key'), 'legacy-wrong\n', 'utf-8');
|
|
137
|
+
await writeFile(sourceCred.serverScopedPath, 'server-scoped-correct\n', 'utf-8');
|
|
138
|
+
|
|
139
|
+
const env = {
|
|
140
|
+
...process.env,
|
|
141
|
+
PATH: `${binDir}:${process.env.PATH ?? ''}`,
|
|
142
|
+
HAPPIER_STACK_HOME_DIR: homeDir,
|
|
143
|
+
HAPPIER_STACK_STORAGE_DIR: storageDir,
|
|
144
|
+
HAPPIER_STACK_WORKSPACE_DIR: workspaceDir,
|
|
145
|
+
HAPPIER_STACK_STACK: targetStack,
|
|
146
|
+
HAPPIER_STACK_ENV_FILE: join(storageDir, targetStack, 'env'),
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const res = await runNodeCapture([authScriptPath(rootDir), 'copy-from', sourceStack], { cwd: rootDir, env });
|
|
150
|
+
assert.equal(res.code, 0, `expected exit 0, got ${res.code}\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
151
|
+
|
|
152
|
+
const expectedTargetId = buildStackStableScopeId({ stackName: targetStack, cliIdentity: 'default' });
|
|
153
|
+
const copied = (await readFile(join(targetCliHome, 'servers', expectedTargetId, 'access.key'), 'utf-8')).trim();
|
|
154
|
+
assert.equal(copied, 'server-scoped-correct');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('hstack stack auth copy-from prefers source stable-scope credential when source stack env has no pinned port (even if stable scope env is disabled)', async (t) => {
|
|
158
|
+
const scriptsDir = dirname(fileURLToPath(import.meta.url));
|
|
159
|
+
const rootDir = dirname(scriptsDir);
|
|
160
|
+
|
|
161
|
+
const tmp = await mkdtemp(join(tmpdir(), 'hstack-auth-copy-from-stable-scope-'));
|
|
162
|
+
t.after(async () => {
|
|
163
|
+
await rm(tmp, { recursive: true, force: true });
|
|
164
|
+
});
|
|
165
|
+
const homeDir = join(tmp, 'home');
|
|
166
|
+
const storageDir = join(tmp, 'storage');
|
|
167
|
+
const workspaceDir = join(tmp, 'workspace');
|
|
168
|
+
await mkdir(homeDir, { recursive: true });
|
|
169
|
+
await mkdir(storageDir, { recursive: true });
|
|
170
|
+
await mkdir(workspaceDir, { recursive: true });
|
|
171
|
+
|
|
172
|
+
const binDir = join(tmp, 'bin');
|
|
173
|
+
await mkdir(binDir, { recursive: true });
|
|
174
|
+
const yarnPath = join(binDir, 'yarn');
|
|
175
|
+
await writeFile(yarnPath, '#!/bin/bash\nexit 0\n', 'utf-8');
|
|
176
|
+
await chmod(yarnPath, 0o755);
|
|
177
|
+
|
|
178
|
+
const repoRoot = dirname(rootDir);
|
|
179
|
+
const sourceStack = 'dev-auth';
|
|
180
|
+
const targetStack = 'dev';
|
|
181
|
+
const sourceCliHome = join(storageDir, sourceStack, 'cli');
|
|
182
|
+
const targetCliHome = join(storageDir, targetStack, 'cli');
|
|
183
|
+
|
|
184
|
+
const mkStackEnv = async (name, cliHomeDir) => {
|
|
185
|
+
const baseDir = join(storageDir, name);
|
|
186
|
+
const dataDir = join(baseDir, 'server-light');
|
|
187
|
+
await mkdir(dataDir, { recursive: true });
|
|
188
|
+
await mkdir(cliHomeDir, { recursive: true });
|
|
189
|
+
await writeFile(
|
|
190
|
+
join(baseDir, 'env'),
|
|
191
|
+
[
|
|
192
|
+
`HAPPIER_STACK_STACK=${name}`,
|
|
193
|
+
`HAPPIER_STACK_SERVER_COMPONENT=happier-server-light`,
|
|
194
|
+
`HAPPIER_STACK_REPO_DIR=${repoRoot}`,
|
|
195
|
+
`HAPPIER_STACK_CLI_HOME_DIR=${cliHomeDir}`,
|
|
196
|
+
// IMPORTANT: no HAPPIER_STACK_SERVER_PORT here (ephemeral stack behavior)
|
|
197
|
+
`HAPPIER_SERVER_LIGHT_DATA_DIR=${dataDir}`,
|
|
198
|
+
`HAPPIER_SERVER_LIGHT_FILES_DIR=${join(dataDir, 'files')}`,
|
|
199
|
+
`HAPPIER_SERVER_LIGHT_DB_DIR=${join(dataDir, 'pglite')}`,
|
|
200
|
+
'',
|
|
201
|
+
].join('\n'),
|
|
202
|
+
'utf-8'
|
|
203
|
+
);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
await mkStackEnv(sourceStack, sourceCliHome);
|
|
207
|
+
await mkStackEnv(targetStack, targetCliHome);
|
|
208
|
+
|
|
209
|
+
const stableId = buildStackStableScopeId({ stackName: sourceStack, cliIdentity: 'default' });
|
|
210
|
+
const stableCredPath = join(sourceCliHome, 'servers', stableId, 'access.key');
|
|
211
|
+
await mkdir(dirname(stableCredPath), { recursive: true });
|
|
212
|
+
|
|
213
|
+
// Legacy key contains a JWT-like payload with a subject; this would normally trigger stale-check failure
|
|
214
|
+
// (source DB is empty in this test). We expect copy-from to pick the stable-scope credential instead.
|
|
215
|
+
const makeToken = (sub) => {
|
|
216
|
+
const b64 = (v) =>
|
|
217
|
+
Buffer.from(v, 'utf-8')
|
|
218
|
+
.toString('base64')
|
|
219
|
+
.replace(/\+/g, '-')
|
|
220
|
+
.replace(/\//g, '_')
|
|
221
|
+
.replace(/=+$/g, '');
|
|
222
|
+
return `${b64(JSON.stringify({ alg: 'none', typ: 'JWT' }))}.${b64(JSON.stringify({ sub }))}.sig`;
|
|
223
|
+
};
|
|
224
|
+
await writeFile(
|
|
225
|
+
join(sourceCliHome, 'access.key'),
|
|
226
|
+
JSON.stringify({ token: makeToken('stale-account-id') }) + '\n',
|
|
227
|
+
'utf-8'
|
|
228
|
+
);
|
|
229
|
+
await writeFile(stableCredPath, 'server-scoped-stable\n', 'utf-8');
|
|
230
|
+
|
|
231
|
+
const env = {
|
|
232
|
+
...process.env,
|
|
233
|
+
PATH: `${binDir}:${process.env.PATH ?? ''}`,
|
|
234
|
+
// Simulate leaked rollback env in caller shell; copy-from should still prefer source stack stable credential.
|
|
235
|
+
HAPPIER_STACK_DISABLE_STABLE_SCOPE: '1',
|
|
236
|
+
HAPPIER_STACK_HOME_DIR: homeDir,
|
|
237
|
+
HAPPIER_STACK_STORAGE_DIR: storageDir,
|
|
238
|
+
HAPPIER_STACK_WORKSPACE_DIR: workspaceDir,
|
|
239
|
+
HAPPIER_STACK_STACK: targetStack,
|
|
240
|
+
HAPPIER_STACK_ENV_FILE: join(storageDir, targetStack, 'env'),
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const res = await runNodeCapture([authScriptPath(rootDir), 'copy-from', sourceStack], { cwd: rootDir, env });
|
|
244
|
+
assert.equal(res.code, 0, `expected exit 0, got ${res.code}\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
245
|
+
|
|
246
|
+
const expectedTargetId = buildStackStableScopeId({ stackName: targetStack, cliIdentity: 'default' });
|
|
247
|
+
const copied = await readFile(join(targetCliHome, 'servers', expectedTargetId, 'access.key'), 'utf-8');
|
|
248
|
+
assert.equal(copied.trim(), 'server-scoped-stable');
|
|
249
|
+
|
|
250
|
+
// Ensure we did not fail due to stale legacy token subject.
|
|
251
|
+
assert.ok(
|
|
252
|
+
!`${res.stdout}\n${res.stderr}`.includes('source auth appears stale'),
|
|
253
|
+
`expected stable-scope source to bypass legacy stale check\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`
|
|
254
|
+
);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test('hstack stack auth copy-from fails closed when source token subject is missing in source Account rows', async (t) => {
|
|
258
|
+
const scriptsDir = dirname(fileURLToPath(import.meta.url));
|
|
259
|
+
const rootDir = dirname(scriptsDir);
|
|
260
|
+
|
|
261
|
+
const tmp = await mkdtemp(join(tmpdir(), 'hstack-auth-copy-from-stale-source-'));
|
|
262
|
+
t.after(async () => {
|
|
263
|
+
await rm(tmp, { recursive: true, force: true });
|
|
264
|
+
});
|
|
265
|
+
const homeDir = join(tmp, 'home');
|
|
266
|
+
const storageDir = join(tmp, 'storage');
|
|
267
|
+
const workspaceDir = join(tmp, 'workspace');
|
|
268
|
+
await mkdir(homeDir, { recursive: true });
|
|
269
|
+
await mkdir(storageDir, { recursive: true });
|
|
270
|
+
await mkdir(workspaceDir, { recursive: true });
|
|
271
|
+
|
|
272
|
+
const binDir = join(tmp, 'bin');
|
|
273
|
+
await mkdir(binDir, { recursive: true });
|
|
274
|
+
const yarnPath = join(binDir, 'yarn');
|
|
275
|
+
await writeFile(yarnPath, '#!/bin/bash\nexit 0\n', 'utf-8');
|
|
276
|
+
await chmod(yarnPath, 0o755);
|
|
277
|
+
|
|
278
|
+
const repoRoot = dirname(rootDir);
|
|
279
|
+
const sourceStack = 'dev-auth';
|
|
280
|
+
const targetStack = 'dev';
|
|
281
|
+
const sourceCliHome = join(storageDir, sourceStack, 'cli');
|
|
282
|
+
const targetCliHome = join(storageDir, targetStack, 'cli');
|
|
283
|
+
const makeToken = (sub) => {
|
|
284
|
+
const b64 = (v) => Buffer.from(v, 'utf-8').toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
|
|
285
|
+
return `${b64(JSON.stringify({ alg: 'none', typ: 'JWT' }))}.${b64(JSON.stringify({ sub }))}.sig`;
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const mkStackEnv = async (name, cliHomeDir) => {
|
|
289
|
+
const baseDir = join(storageDir, name);
|
|
290
|
+
const dataDir = join(baseDir, 'server-light');
|
|
291
|
+
await mkdir(dataDir, { recursive: true });
|
|
292
|
+
await mkdir(cliHomeDir, { recursive: true });
|
|
293
|
+
await writeFile(
|
|
294
|
+
join(baseDir, 'env'),
|
|
295
|
+
[
|
|
296
|
+
`HAPPIER_STACK_STACK=${name}`,
|
|
297
|
+
`HAPPIER_STACK_SERVER_COMPONENT=happier-server-light`,
|
|
298
|
+
`HAPPIER_STACK_REPO_DIR=${repoRoot}`,
|
|
299
|
+
`HAPPIER_STACK_CLI_HOME_DIR=${cliHomeDir}`,
|
|
300
|
+
`HAPPIER_SERVER_LIGHT_DATA_DIR=${dataDir}`,
|
|
301
|
+
`HAPPIER_SERVER_LIGHT_FILES_DIR=${join(dataDir, 'files')}`,
|
|
302
|
+
`HAPPIER_SERVER_LIGHT_DB_DIR=${join(dataDir, 'pglite')}`,
|
|
303
|
+
'',
|
|
304
|
+
].join('\n'),
|
|
305
|
+
'utf-8'
|
|
306
|
+
);
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
await mkStackEnv(sourceStack, sourceCliHome);
|
|
310
|
+
await mkStackEnv(targetStack, targetCliHome);
|
|
311
|
+
|
|
312
|
+
// Source token subject has no matching Account row in source DB (source DB starts empty).
|
|
313
|
+
await writeFile(
|
|
314
|
+
join(sourceCliHome, 'access.key'),
|
|
315
|
+
JSON.stringify({
|
|
316
|
+
token: makeToken('stale-account-id'),
|
|
317
|
+
secret: Buffer.from('secret', 'utf-8').toString('base64'),
|
|
318
|
+
}) + '\n',
|
|
319
|
+
'utf-8'
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
const env = {
|
|
323
|
+
...process.env,
|
|
324
|
+
PATH: `${binDir}:${process.env.PATH ?? ''}`,
|
|
325
|
+
HAPPIER_STACK_HOME_DIR: homeDir,
|
|
326
|
+
HAPPIER_STACK_STORAGE_DIR: storageDir,
|
|
327
|
+
HAPPIER_STACK_WORKSPACE_DIR: workspaceDir,
|
|
328
|
+
HAPPIER_STACK_STACK: targetStack,
|
|
329
|
+
HAPPIER_STACK_ENV_FILE: join(storageDir, targetStack, 'env'),
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
const res = await runNodeCapture([authScriptPath(rootDir), 'copy-from', sourceStack], { cwd: rootDir, env });
|
|
333
|
+
assert.notEqual(res.code, 0, `expected non-zero exit for stale source auth\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
334
|
+
assert.match(
|
|
335
|
+
`${res.stdout}\n${res.stderr}`,
|
|
336
|
+
/source auth appears stale|token subject .* is not present in source Account rows/i,
|
|
337
|
+
`expected stale source auth guidance\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`
|
|
338
|
+
);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test('hstack stack auth copy-from accepts source auth when source server validates token', async (t) => {
|
|
342
|
+
const scriptsDir = dirname(fileURLToPath(import.meta.url));
|
|
343
|
+
const rootDir = dirname(scriptsDir);
|
|
344
|
+
|
|
345
|
+
const tmp = await mkdtemp(join(tmpdir(), 'hstack-auth-copy-from-server-validated-'));
|
|
346
|
+
t.after(async () => {
|
|
347
|
+
await rm(tmp, { recursive: true, force: true });
|
|
348
|
+
});
|
|
349
|
+
const homeDir = join(tmp, 'home');
|
|
350
|
+
const storageDir = join(tmp, 'storage');
|
|
351
|
+
const workspaceDir = join(tmp, 'workspace');
|
|
352
|
+
await mkdir(homeDir, { recursive: true });
|
|
353
|
+
await mkdir(storageDir, { recursive: true });
|
|
354
|
+
await mkdir(workspaceDir, { recursive: true });
|
|
355
|
+
|
|
356
|
+
const binDir = join(tmp, 'bin');
|
|
357
|
+
await mkdir(binDir, { recursive: true });
|
|
358
|
+
const yarnPath = join(binDir, 'yarn');
|
|
359
|
+
await writeFile(yarnPath, '#!/bin/bash\nexit 0\n', 'utf-8');
|
|
360
|
+
await chmod(yarnPath, 0o755);
|
|
361
|
+
|
|
362
|
+
const repoRoot = dirname(rootDir);
|
|
363
|
+
const sourceStack = 'dev-auth';
|
|
364
|
+
const targetStack = 'dev';
|
|
365
|
+
const sourceCliHome = join(storageDir, sourceStack, 'cli');
|
|
366
|
+
const targetCliHome = join(storageDir, targetStack, 'cli');
|
|
367
|
+
const makeToken = (sub) => {
|
|
368
|
+
const b64 = (v) => Buffer.from(v, 'utf-8').toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
|
|
369
|
+
return `${b64(JSON.stringify({ alg: 'none', typ: 'JWT' }))}.${b64(JSON.stringify({ sub }))}.sig`;
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const mkStackEnv = async (name, cliHomeDir) => {
|
|
373
|
+
const baseDir = join(storageDir, name);
|
|
374
|
+
const dataDir = join(baseDir, 'server-light');
|
|
375
|
+
await mkdir(dataDir, { recursive: true });
|
|
376
|
+
await mkdir(cliHomeDir, { recursive: true });
|
|
377
|
+
await writeFile(
|
|
378
|
+
join(baseDir, 'env'),
|
|
379
|
+
[
|
|
380
|
+
`HAPPIER_STACK_STACK=${name}`,
|
|
381
|
+
`HAPPIER_STACK_SERVER_COMPONENT=happier-server-light`,
|
|
382
|
+
`HAPPIER_STACK_REPO_DIR=${repoRoot}`,
|
|
383
|
+
`HAPPIER_STACK_CLI_HOME_DIR=${cliHomeDir}`,
|
|
384
|
+
`HAPPIER_SERVER_LIGHT_DATA_DIR=${dataDir}`,
|
|
385
|
+
`HAPPIER_SERVER_LIGHT_FILES_DIR=${join(dataDir, 'files')}`,
|
|
386
|
+
`HAPPIER_SERVER_LIGHT_DB_DIR=${join(dataDir, 'pglite')}`,
|
|
387
|
+
'',
|
|
388
|
+
].join('\n'),
|
|
389
|
+
'utf-8'
|
|
390
|
+
);
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
await mkStackEnv(sourceStack, sourceCliHome);
|
|
394
|
+
await mkStackEnv(targetStack, targetCliHome);
|
|
395
|
+
|
|
396
|
+
const sourceToken = makeToken('subject-not-present-in-db');
|
|
397
|
+
await writeFile(
|
|
398
|
+
join(sourceCliHome, 'access.key'),
|
|
399
|
+
JSON.stringify({ token: sourceToken, secret: Buffer.from('secret', 'utf-8').toString('base64') }) + '\n',
|
|
400
|
+
'utf-8'
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
const sourceServer = createServer((req, res) => {
|
|
404
|
+
if (req.method === 'GET' && req.url === '/v1/account/profile') {
|
|
405
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
406
|
+
res.end(JSON.stringify({ id: 'any-account' }));
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
res.writeHead(404);
|
|
410
|
+
res.end();
|
|
411
|
+
});
|
|
412
|
+
await new Promise((resolve) => sourceServer.listen(0, '127.0.0.1', resolve));
|
|
413
|
+
t.after(async () => {
|
|
414
|
+
await new Promise((resolve) => sourceServer.close(resolve));
|
|
415
|
+
});
|
|
416
|
+
const sourceAddress = sourceServer.address();
|
|
417
|
+
const sourcePort =
|
|
418
|
+
sourceAddress && typeof sourceAddress === 'object' && Number.isFinite(sourceAddress.port) ? Number(sourceAddress.port) : 0;
|
|
419
|
+
assert.ok(sourcePort > 0, `expected source server port, got ${JSON.stringify(sourceAddress)}`);
|
|
420
|
+
|
|
421
|
+
await writeFile(
|
|
422
|
+
join(storageDir, sourceStack, 'stack.runtime.json'),
|
|
423
|
+
JSON.stringify({ version: 1, stackName: sourceStack, ownerPid: process.pid, ports: { server: sourcePort } }) + '\n',
|
|
424
|
+
'utf-8'
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
const env = {
|
|
428
|
+
...process.env,
|
|
429
|
+
PATH: `${binDir}:${process.env.PATH ?? ''}`,
|
|
430
|
+
HAPPIER_STACK_HOME_DIR: homeDir,
|
|
431
|
+
HAPPIER_STACK_STORAGE_DIR: storageDir,
|
|
432
|
+
HAPPIER_STACK_WORKSPACE_DIR: workspaceDir,
|
|
433
|
+
HAPPIER_STACK_STACK: targetStack,
|
|
434
|
+
HAPPIER_STACK_ENV_FILE: join(storageDir, targetStack, 'env'),
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
const res = await runNodeCapture([authScriptPath(rootDir), 'copy-from', sourceStack], { cwd: rootDir, env });
|
|
438
|
+
assert.equal(res.code, 0, `expected exit 0, got ${res.code}\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
439
|
+
assert.ok(
|
|
440
|
+
!`${res.stdout}\n${res.stderr}`.includes('source auth appears stale'),
|
|
441
|
+
`expected live source server validation to bypass stale subject guard\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
const targetStableId = buildStackStableScopeId({ stackName: targetStack, cliIdentity: 'default' });
|
|
445
|
+
const copied = await readFile(join(targetCliHome, 'servers', targetStableId, 'access.key'), 'utf-8');
|
|
446
|
+
assert.ok(copied.includes(sourceToken), `expected copied credential to include source token\ncopied:\n${copied}`);
|
|
447
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { hstackBinPath, runNodeCapture } from './testkit/auth_testkit.mjs';
|
|
6
|
+
|
|
7
|
+
test('hstack auth --help surfaces dev-auth seed stack command', async () => {
|
|
8
|
+
const scriptsDir = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const rootDir = dirname(scriptsDir);
|
|
10
|
+
|
|
11
|
+
const env = {
|
|
12
|
+
...process.env,
|
|
13
|
+
// Prevent env.mjs from auto-loading a real machine stack env file (keeps the test hermetic).
|
|
14
|
+
HAPPIER_STACK_STACK: 'test-stack',
|
|
15
|
+
HAPPIER_STACK_ENV_FILE: join(rootDir, 'scripts', 'nonexistent-env'),
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const res = await runNodeCapture([hstackBinPath(rootDir), 'auth', '--help'], { cwd: rootDir, env });
|
|
19
|
+
assert.equal(res.code, 0, `expected exit 0, got ${res.code}\nstderr:\n${res.stderr}\nstdout:\n${res.stdout}`);
|
|
20
|
+
assert.match(res.stdout, /\bhstack auth seed\b/, `expected help to include seed command\nstdout:\n${res.stdout}`);
|
|
21
|
+
assert.match(res.stdout, /\bdev-auth\b/, `expected help to mention dev-auth\nstdout:\n${res.stdout}`);
|
|
22
|
+
|
|
23
|
+
// Auth login targeting flags (local-first UX)
|
|
24
|
+
assert.match(res.stdout, /--webapp(?:=|\b)/, `expected help to mention --webapp\nstdout:\n${res.stdout}`);
|
|
25
|
+
assert.match(res.stdout, /--webapp-url(?:=|\b)/, `expected help to mention --webapp-url\nstdout:\n${res.stdout}`);
|
|
26
|
+
assert.match(res.stdout, /--method(?:=|\b)/, `expected help to mention --method\nstdout:\n${res.stdout}`);
|
|
27
|
+
assert.match(res.stdout, /--start-if-needed(?:\b|$)/, `expected help to mention --start-if-needed\nstdout:\n${res.stdout}`);
|
|
28
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createAuthStackFixture, getStackRootFromMeta, hstackBinPath, runNodeCapture } from './testkit/auth_testkit.mjs';
|
|
4
|
+
|
|
5
|
+
async function createTtyFixture(prefix) {
|
|
6
|
+
return await createAuthStackFixture({
|
|
7
|
+
prefix,
|
|
8
|
+
stackEnvLines: [
|
|
9
|
+
'HAPPIER_STACK_STACK=main',
|
|
10
|
+
'HAPPIER_STACK_SERVER_PORT=4101',
|
|
11
|
+
'HAPPIER_STACK_TAILSCALE_PREFER_PUBLIC_URL=0',
|
|
12
|
+
'HAPPIER_STACK_TAILSCALE_SERVE=0',
|
|
13
|
+
],
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function runAuthLoginPrintJson(rootDir, env, extraArgs = []) {
|
|
18
|
+
return await runNodeCapture(
|
|
19
|
+
[hstackBinPath(rootDir), 'auth', 'login', '--print', '--no-open', '--json', ...extraArgs],
|
|
20
|
+
{ cwd: rootDir, env }
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
test('hstack auth login reports guided flow by default in tty contexts (via --print --json)', async () => {
|
|
25
|
+
const rootDir = getStackRootFromMeta(import.meta.url);
|
|
26
|
+
|
|
27
|
+
const fixture = await createTtyFixture('hstack-auth-flow-tty-');
|
|
28
|
+
try {
|
|
29
|
+
const env = fixture.buildEnv({
|
|
30
|
+
HAPPIER_STACK_TEST_TTY: '1',
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const res = await runAuthLoginPrintJson(rootDir, env);
|
|
34
|
+
assert.equal(
|
|
35
|
+
res.code,
|
|
36
|
+
0,
|
|
37
|
+
`expected exit 0, got ${res.code}${res.signal ? ` (signal=${res.signal})` : ''}\nstderr:\n${res.stderr}\nstdout:\n${res.stdout}`
|
|
38
|
+
);
|
|
39
|
+
const parsed = JSON.parse(res.stdout.trim());
|
|
40
|
+
assert.equal(parsed?.flow, 'guided');
|
|
41
|
+
} finally {
|
|
42
|
+
await fixture.cleanup();
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('hstack auth login keeps guided flow in tty when --method=mobile is provided', async () => {
|
|
47
|
+
const rootDir = getStackRootFromMeta(import.meta.url);
|
|
48
|
+
|
|
49
|
+
const fixture = await createTtyFixture('hstack-auth-flow-tty-method-');
|
|
50
|
+
try {
|
|
51
|
+
const env = fixture.buildEnv({
|
|
52
|
+
HAPPIER_STACK_TEST_TTY: '1',
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const res = await runAuthLoginPrintJson(rootDir, env, ['--method=mobile']);
|
|
56
|
+
assert.equal(
|
|
57
|
+
res.code,
|
|
58
|
+
0,
|
|
59
|
+
`expected exit 0, got ${res.code}${res.signal ? ` (signal=${res.signal})` : ''}\nstderr:\n${res.stderr}\nstdout:\n${res.stdout}`
|
|
60
|
+
);
|
|
61
|
+
const parsed = JSON.parse(res.stdout.trim());
|
|
62
|
+
assert.equal(parsed?.flow, 'guided');
|
|
63
|
+
} finally {
|
|
64
|
+
await fixture.cleanup();
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('hstack auth login rejects --flow override (guided is always used)', async () => {
|
|
69
|
+
const rootDir = getStackRootFromMeta(import.meta.url);
|
|
70
|
+
|
|
71
|
+
const fixture = await createTtyFixture('hstack-auth-flow-tty-raw-');
|
|
72
|
+
try {
|
|
73
|
+
const env = fixture.buildEnv({
|
|
74
|
+
HAPPIER_STACK_TEST_TTY: '1',
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const res = await runAuthLoginPrintJson(rootDir, env, ['--flow=raw']);
|
|
78
|
+
assert.notEqual(res.code, 0, `expected non-zero exit\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
79
|
+
assert.match(res.stderr, /--flow is no longer supported/i);
|
|
80
|
+
} finally {
|
|
81
|
+
await fixture.cleanup();
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('hstack auth login rejects invalid --flow values', async () => {
|
|
86
|
+
const rootDir = getStackRootFromMeta(import.meta.url);
|
|
87
|
+
|
|
88
|
+
const fixture = await createTtyFixture('hstack-auth-flow-tty-invalid-');
|
|
89
|
+
try {
|
|
90
|
+
const env = fixture.buildEnv({
|
|
91
|
+
HAPPIER_STACK_TEST_TTY: '1',
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const res = await runAuthLoginPrintJson(rootDir, env, ['--flow=weird']);
|
|
95
|
+
assert.notEqual(res.code, 0, `expected non-zero exit\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
96
|
+
assert.match(res.stderr, /--flow is no longer supported/i);
|
|
97
|
+
} finally {
|
|
98
|
+
await fixture.cleanup();
|
|
99
|
+
}
|
|
100
|
+
});
|