@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,291 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
|
|
3
|
+
import { createStepPrinter } from '../cli/progress.mjs';
|
|
4
|
+
import { createFileLogForwarder } from '../cli/log_forwarder.mjs';
|
|
5
|
+
import { getComponentDir, resolveStackEnvPath } from '../paths/paths.mjs';
|
|
6
|
+
import { getStackRuntimeStatePath, isPidAlive, readStackRuntimeStateFile } from '../stack/runtime_state.mjs';
|
|
7
|
+
import { readEnvObjectFromFile } from '../env/read.mjs';
|
|
8
|
+
import { getWebappUrlEnvOverride, resolveServerUrls } from '../server/urls.mjs';
|
|
9
|
+
import { readLastLines } from '../fs/tail.mjs';
|
|
10
|
+
import { run } from '../proc/proc.mjs';
|
|
11
|
+
|
|
12
|
+
import { guidedStackAuthLoginNow, assertExpoWebappBundlesOrThrow, resolveStackWebappUrlForAuth } from './stack_guided_login.mjs';
|
|
13
|
+
import { checkDaemonState, startLocalDaemonWithAuth } from '../../daemon.mjs';
|
|
14
|
+
|
|
15
|
+
function appendCauseText(baseMessage, cause) {
|
|
16
|
+
const msg = String(baseMessage ?? '').trim();
|
|
17
|
+
const c = String(cause ?? '').trim();
|
|
18
|
+
if (!c) return msg;
|
|
19
|
+
return `${msg}\n\n[auth] Cause: ${c}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function appendRunnerLogTailDiagnostics({ message, stackName, lines = 140 }) {
|
|
23
|
+
const base = String(message ?? '').trim();
|
|
24
|
+
const logPath = await resolveRunnerLogPathFromRuntime({ stackName, waitMs: 1000, pollMs: 100 }).catch(() => '');
|
|
25
|
+
if (!logPath) return base;
|
|
26
|
+
const tail = await readLastLines(logPath, lines).catch(() => null);
|
|
27
|
+
if (!tail || !String(tail).trim()) {
|
|
28
|
+
return `${base}\n\n[auth] Stack runner log: ${logPath}`;
|
|
29
|
+
}
|
|
30
|
+
return `${base}\n\n[auth] Stack runner log: ${logPath}\n\n[auth] Last runner log lines:\n${String(tail).trimEnd()}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function tryStartStackUiInBackgroundForAuth({ rootDir, stackName, env = process.env } = {}) {
|
|
34
|
+
const name = String(stackName ?? '').trim() || 'main';
|
|
35
|
+
try {
|
|
36
|
+
await run(
|
|
37
|
+
process.execPath,
|
|
38
|
+
[join(rootDir, 'scripts', 'stack.mjs'), 'dev', name, '--background', '--no-daemon', '--no-browser'],
|
|
39
|
+
{
|
|
40
|
+
cwd: rootDir,
|
|
41
|
+
env: {
|
|
42
|
+
...process.env,
|
|
43
|
+
...(env ?? {}),
|
|
44
|
+
HAPPIER_STACK_AUTH_FLOW: '1',
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
return { ok: true };
|
|
49
|
+
} catch (e) {
|
|
50
|
+
return {
|
|
51
|
+
ok: false,
|
|
52
|
+
error: e instanceof Error ? e.message : String(e),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function resolveRunnerLogPathFromRuntime({ stackName, waitMs = 10_000, pollMs = 200 } = {}) {
|
|
58
|
+
const name = String(stackName ?? '').trim() || 'main';
|
|
59
|
+
const runtimeStatePath = getStackRuntimeStatePath(name);
|
|
60
|
+
const deadline = Date.now() + (Number.isFinite(Number(waitMs)) ? Number(waitMs) : 10_000);
|
|
61
|
+
|
|
62
|
+
while (Date.now() < deadline) {
|
|
63
|
+
// eslint-disable-next-line no-await-in-loop
|
|
64
|
+
const st = await readStackRuntimeStateFile(runtimeStatePath);
|
|
65
|
+
// Returning '' here is intentional: log forwarding is optional best-effort telemetry.
|
|
66
|
+
// Callers must treat this as "no runner log available", not as a hard failure.
|
|
67
|
+
const ownerPid = Number(st?.ownerPid);
|
|
68
|
+
if (Number.isFinite(ownerPid) && ownerPid > 1 && !isPidAlive(ownerPid)) return '';
|
|
69
|
+
const logPath = String(st?.logs?.runner ?? '').trim();
|
|
70
|
+
if (logPath) return logPath;
|
|
71
|
+
// eslint-disable-next-line no-await-in-loop
|
|
72
|
+
await new Promise((r) => setTimeout(r, pollMs));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return '';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function prepareGuidedLoginWebapp({ rootDir, stackName, env, steps } = {}) {
|
|
79
|
+
const name = String(stackName ?? '').trim() || 'main';
|
|
80
|
+
const label = 'prepare login (waiting for web UI)';
|
|
81
|
+
const printer = steps && typeof steps.start === 'function' && typeof steps.stop === 'function' ? steps : null;
|
|
82
|
+
|
|
83
|
+
if (printer) printer.start(label);
|
|
84
|
+
try {
|
|
85
|
+
const resolveAndAssert = async () => {
|
|
86
|
+
const webappUrl = await resolveStackWebappUrlForAuth({ rootDir, stackName: name, env });
|
|
87
|
+
await assertExpoWebappBundlesOrThrow({ rootDir, stackName: name, webappUrl });
|
|
88
|
+
return webappUrl;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const webappUrl = await resolveAndAssert();
|
|
93
|
+
if (printer) printer.stop('✓', label);
|
|
94
|
+
return webappUrl;
|
|
95
|
+
} catch (initialErr) {
|
|
96
|
+
const recovery = await tryStartStackUiInBackgroundForAuth({
|
|
97
|
+
rootDir,
|
|
98
|
+
stackName: name,
|
|
99
|
+
env,
|
|
100
|
+
});
|
|
101
|
+
if (recovery.ok) {
|
|
102
|
+
try {
|
|
103
|
+
const webappUrl = await resolveAndAssert();
|
|
104
|
+
if (printer) printer.stop('✓', label);
|
|
105
|
+
return webappUrl;
|
|
106
|
+
} catch (retryErr) {
|
|
107
|
+
const enriched = await appendRunnerLogTailDiagnostics({
|
|
108
|
+
stackName: name,
|
|
109
|
+
message: appendCauseText(
|
|
110
|
+
'[auth] attempted to start stack UI in background, but guided login web UI is still not ready.',
|
|
111
|
+
retryErr instanceof Error ? retryErr.message : String(retryErr)
|
|
112
|
+
),
|
|
113
|
+
});
|
|
114
|
+
throw new Error(enriched);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const enriched = await appendRunnerLogTailDiagnostics({
|
|
118
|
+
stackName: name,
|
|
119
|
+
message: appendCauseText(
|
|
120
|
+
'[auth] attempted to start stack UI in background, but startup failed.',
|
|
121
|
+
recovery.error || (initialErr instanceof Error ? initialErr.message : String(initialErr))
|
|
122
|
+
),
|
|
123
|
+
});
|
|
124
|
+
throw new Error(enriched);
|
|
125
|
+
}
|
|
126
|
+
} catch (e) {
|
|
127
|
+
if (printer) printer.stop('x', label);
|
|
128
|
+
throw e;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function runGuidedLogin({ rootDir, stackName, env, webappUrl, forwarder } = {}) {
|
|
133
|
+
const name = String(stackName ?? '').trim() || 'main';
|
|
134
|
+
const url = String(webappUrl ?? '').trim();
|
|
135
|
+
if (!url) {
|
|
136
|
+
throw new Error('[auth] guided login requires a webappUrl');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
forwarder?.pause?.();
|
|
141
|
+
} catch {
|
|
142
|
+
// ignore
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
await guidedStackAuthLoginNow({
|
|
146
|
+
rootDir,
|
|
147
|
+
stackName: name,
|
|
148
|
+
env: { ...(env ?? process.env), HAPPIER_STACK_AUTH_SKIP_BUNDLE_CHECK: '1' },
|
|
149
|
+
webappUrl: url,
|
|
150
|
+
});
|
|
151
|
+
} finally {
|
|
152
|
+
try {
|
|
153
|
+
forwarder?.resume?.();
|
|
154
|
+
} catch {
|
|
155
|
+
// ignore
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export async function resolveServerPortForPostAuthDaemonStart({ stackName, env = process.env } = {}) {
|
|
161
|
+
const name = String(stackName ?? '').trim() || 'main';
|
|
162
|
+
const runtimeStatePath = getStackRuntimeStatePath(name);
|
|
163
|
+
const st = await readStackRuntimeStateFile(runtimeStatePath);
|
|
164
|
+
const runtimePort = Number(st?.ports?.server);
|
|
165
|
+
const ownerPid = Number(st?.ownerPid);
|
|
166
|
+
const runtimeOwnerAlive = !Number.isFinite(ownerPid) || ownerPid <= 1 || isPidAlive(ownerPid);
|
|
167
|
+
if (runtimeOwnerAlive && Number.isFinite(runtimePort) && runtimePort > 0) {
|
|
168
|
+
return runtimePort;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const envPort = Number((env?.HAPPIER_STACK_SERVER_PORT ?? '').toString().trim());
|
|
172
|
+
if (Number.isFinite(envPort) && envPort > 0) {
|
|
173
|
+
return envPort;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
throw new Error('[auth] post-auth daemon start failed: could not resolve server port from stack.runtime.json');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export async function startDaemonPostAuth({
|
|
180
|
+
rootDir,
|
|
181
|
+
stackName,
|
|
182
|
+
env = process.env,
|
|
183
|
+
forceRestart = true,
|
|
184
|
+
webappUrl = '',
|
|
185
|
+
} = {}) {
|
|
186
|
+
const name = String(stackName ?? '').trim() || 'main';
|
|
187
|
+
const serverPort = await resolveServerPortForPostAuthDaemonStart({ stackName: name, env });
|
|
188
|
+
|
|
189
|
+
const { envPath, baseDir } = resolveStackEnvPath(name, env);
|
|
190
|
+
const stackEnv = await readEnvObjectFromFile(envPath);
|
|
191
|
+
const mergedEnv = { ...process.env, ...(stackEnv ?? {}), ...(env ?? {}) };
|
|
192
|
+
|
|
193
|
+
const cliHomeDir =
|
|
194
|
+
(mergedEnv.HAPPIER_STACK_CLI_HOME_DIR ?? '').toString().trim() ||
|
|
195
|
+
join(baseDir, 'cli');
|
|
196
|
+
const cliDir = getComponentDir(rootDir, 'happier-cli', mergedEnv);
|
|
197
|
+
const cliBin = join(cliDir, 'bin', 'happier.mjs');
|
|
198
|
+
|
|
199
|
+
const internalServerUrl = `http://127.0.0.1:${serverPort}`;
|
|
200
|
+
const explicitWebappUrl = String(webappUrl ?? '').trim();
|
|
201
|
+
const { publicServerUrl: resolvedPublicServerUrl } = await resolveServerUrls({
|
|
202
|
+
env: mergedEnv,
|
|
203
|
+
serverPort,
|
|
204
|
+
allowEnable: false,
|
|
205
|
+
});
|
|
206
|
+
const { envWebappUrl } = getWebappUrlEnvOverride({ env: mergedEnv, stackName: name });
|
|
207
|
+
const publicServerUrl = explicitWebappUrl || envWebappUrl || resolvedPublicServerUrl;
|
|
208
|
+
|
|
209
|
+
await startLocalDaemonWithAuth({
|
|
210
|
+
cliBin,
|
|
211
|
+
cliHomeDir,
|
|
212
|
+
internalServerUrl,
|
|
213
|
+
publicServerUrl,
|
|
214
|
+
isShuttingDown: () => false,
|
|
215
|
+
forceRestart: Boolean(forceRestart),
|
|
216
|
+
env: mergedEnv,
|
|
217
|
+
stackName: name,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Verify (best-effort): daemon wrote state.
|
|
221
|
+
const deadline = Date.now() + 10_000;
|
|
222
|
+
while (Date.now() < deadline) {
|
|
223
|
+
const s = checkDaemonState(cliHomeDir, { serverUrl: internalServerUrl, env: mergedEnv });
|
|
224
|
+
if (s.status === 'running') {
|
|
225
|
+
return {
|
|
226
|
+
ok: true,
|
|
227
|
+
cliHomeDir,
|
|
228
|
+
internalServerUrl,
|
|
229
|
+
publicServerUrl,
|
|
230
|
+
pid: s.pid,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
// eslint-disable-next-line no-await-in-loop
|
|
234
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
ok: false,
|
|
238
|
+
cliHomeDir,
|
|
239
|
+
internalServerUrl,
|
|
240
|
+
publicServerUrl,
|
|
241
|
+
pid: null,
|
|
242
|
+
error: '[auth] post-auth daemon start verification timed out (daemon did not report running)',
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export async function runOrchestratedGuidedAuthFlow({
|
|
247
|
+
rootDir,
|
|
248
|
+
stackName,
|
|
249
|
+
env = process.env,
|
|
250
|
+
verbosity = 0,
|
|
251
|
+
json = false,
|
|
252
|
+
webappUrl = '',
|
|
253
|
+
} = {}) {
|
|
254
|
+
const name = String(stackName ?? '').trim() || 'main';
|
|
255
|
+
|
|
256
|
+
const steps = createStepPrinter({ enabled: Boolean(process.stdout.isTTY && !json) });
|
|
257
|
+
|
|
258
|
+
let forwarder = null;
|
|
259
|
+
if (!json && verbosity > 0) {
|
|
260
|
+
try {
|
|
261
|
+
const logPath = await resolveRunnerLogPathFromRuntime({ stackName: name, waitMs: 10_000 });
|
|
262
|
+
if (logPath) {
|
|
263
|
+
forwarder = createFileLogForwarder({
|
|
264
|
+
path: logPath,
|
|
265
|
+
enabled: true,
|
|
266
|
+
label: 'stack',
|
|
267
|
+
startFromEnd: false,
|
|
268
|
+
});
|
|
269
|
+
await forwarder.start();
|
|
270
|
+
}
|
|
271
|
+
} catch {
|
|
272
|
+
forwarder = null;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
let resolved = String(webappUrl ?? '').trim();
|
|
277
|
+
try {
|
|
278
|
+
if (!resolved) {
|
|
279
|
+
resolved = await prepareGuidedLoginWebapp({ rootDir, stackName: name, env, steps });
|
|
280
|
+
}
|
|
281
|
+
await runGuidedLogin({ rootDir, stackName: name, env, webappUrl: resolved, forwarder });
|
|
282
|
+
} finally {
|
|
283
|
+
try {
|
|
284
|
+
await forwarder?.stop?.();
|
|
285
|
+
} catch {
|
|
286
|
+
// ignore
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return { ok: true, webappUrl: resolved };
|
|
291
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
|
|
4
|
+
import { resolveStackEnvPath } from '../paths/paths.mjs';
|
|
5
|
+
import { findAnyCredentialPathInCliHome } from './credentials_paths.mjs';
|
|
6
|
+
|
|
7
|
+
export function stackHasAccessKey(stackName) {
|
|
8
|
+
try {
|
|
9
|
+
const { baseDir, envPath } = resolveStackEnvPath(stackName);
|
|
10
|
+
if (!existsSync(envPath)) return false;
|
|
11
|
+
return Boolean(findAnyCredentialPathInCliHome({ cliHomeDir: join(baseDir, 'cli') }));
|
|
12
|
+
} catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Seed sources that are safe to reuse locally.
|
|
19
|
+
*
|
|
20
|
+
* Note: deliberately does NOT include legacy ~/.happy sources; in many contexts we cannot reliably
|
|
21
|
+
* seed DB Account rows, which leads to broken stacks.
|
|
22
|
+
*/
|
|
23
|
+
export function detectSeedableAuthSources() {
|
|
24
|
+
const out = [];
|
|
25
|
+
if (stackHasAccessKey('dev-auth')) out.push('dev-auth');
|
|
26
|
+
if (stackHasAccessKey('main')) out.push('main');
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const SERVER_ID_SAFE_RE = /^[A-Za-z0-9._-]{1,64}$/;
|
|
2
|
+
|
|
3
|
+
function isScopeIdSafe(raw) {
|
|
4
|
+
const value = String(raw ?? '').trim();
|
|
5
|
+
if (!value) return false;
|
|
6
|
+
if (value === '.' || value === '..') return false;
|
|
7
|
+
if (value.includes('/') || value.includes('\\')) return false;
|
|
8
|
+
return SERVER_ID_SAFE_RE.test(value);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function normalizeScopePart(raw, fallback) {
|
|
12
|
+
const base = String(raw ?? '')
|
|
13
|
+
.trim()
|
|
14
|
+
.toLowerCase()
|
|
15
|
+
.replace(/[^a-z0-9._-]+/g, '-')
|
|
16
|
+
.replace(/-+/g, '-')
|
|
17
|
+
.replace(/^[-._]+/, '')
|
|
18
|
+
.replace(/[-._]+$/, '');
|
|
19
|
+
return base || fallback;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function hashFNV1a32(value) {
|
|
23
|
+
const text = String(value ?? '');
|
|
24
|
+
let h = 2166136261;
|
|
25
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
26
|
+
h ^= text.charCodeAt(i);
|
|
27
|
+
h = Math.imul(h, 16777619);
|
|
28
|
+
}
|
|
29
|
+
return (h >>> 0).toString(16);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function clipPart(value, maxLength) {
|
|
33
|
+
const v = String(value ?? '').trim();
|
|
34
|
+
if (!v) return '';
|
|
35
|
+
if (v.length <= maxLength) return v;
|
|
36
|
+
return v.slice(0, maxLength).replace(/[-._]+$/, '');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function buildStackStableScopeId({ stackName = 'main', cliIdentity = 'default' } = {}) {
|
|
40
|
+
const stack = normalizeScopePart(stackName, 'main');
|
|
41
|
+
const identity = normalizeScopePart(cliIdentity, 'default');
|
|
42
|
+
|
|
43
|
+
const base = `stack_${stack}__id_${identity}`;
|
|
44
|
+
if (isScopeIdSafe(base)) return base;
|
|
45
|
+
|
|
46
|
+
const hash = hashFNV1a32(`${stack}::${identity}`);
|
|
47
|
+
const stackShort = clipPart(stack, 20) || 'main';
|
|
48
|
+
const identityShort = clipPart(identity, 20) || 'default';
|
|
49
|
+
const compact = `stack_${stackShort}__id_${identityShort}__${hash}`;
|
|
50
|
+
if (isScopeIdSafe(compact)) return compact;
|
|
51
|
+
|
|
52
|
+
const fallback = `stack_${hash}`;
|
|
53
|
+
return isScopeIdSafe(fallback) ? fallback : 'cloud';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function isStableScopeDisabled(env = process.env) {
|
|
57
|
+
const raw = String(env?.HAPPIER_STACK_DISABLE_STABLE_SCOPE ?? '').trim().toLowerCase();
|
|
58
|
+
return raw === '1' || raw === 'true' || raw === 'yes' || raw === 'y';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function resolveStackActiveServerId({ env = process.env, stackName = null, cliIdentity = null } = {}) {
|
|
62
|
+
if (isStableScopeDisabled(env)) return '';
|
|
63
|
+
const explicit = String(env?.HAPPIER_ACTIVE_SERVER_ID ?? '').trim();
|
|
64
|
+
const inStackContext =
|
|
65
|
+
Boolean((stackName ?? '').toString().trim()) ||
|
|
66
|
+
Boolean((cliIdentity ?? '').toString().trim()) ||
|
|
67
|
+
Boolean((env?.HAPPIER_STACK_STACK ?? '').toString().trim()) ||
|
|
68
|
+
Boolean((env?.HAPPIER_STACK_CLI_IDENTITY ?? '').toString().trim());
|
|
69
|
+
if (!inStackContext && isScopeIdSafe(explicit)) return explicit;
|
|
70
|
+
|
|
71
|
+
const name =
|
|
72
|
+
(stackName ?? '').toString().trim() ||
|
|
73
|
+
(env?.HAPPIER_STACK_STACK ?? '').toString().trim() ||
|
|
74
|
+
'main';
|
|
75
|
+
const identity =
|
|
76
|
+
(cliIdentity ?? '').toString().trim() ||
|
|
77
|
+
(env?.HAPPIER_STACK_CLI_IDENTITY ?? '').toString().trim() ||
|
|
78
|
+
'default';
|
|
79
|
+
return buildStackStableScopeId({ stackName: name, cliIdentity: identity });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function applyStackActiveServerScopeEnv({ env = process.env, stackName = null, cliIdentity = null } = {}) {
|
|
83
|
+
const base = { ...(env ?? {}) };
|
|
84
|
+
const activeServerId = resolveStackActiveServerId({ env: base, stackName, cliIdentity });
|
|
85
|
+
if (!activeServerId) {
|
|
86
|
+
delete base.HAPPIER_ACTIVE_SERVER_ID;
|
|
87
|
+
return base;
|
|
88
|
+
}
|
|
89
|
+
base.HAPPIER_ACTIVE_SERVER_ID = activeServerId;
|
|
90
|
+
return base;
|
|
91
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
applyStackActiveServerScopeEnv,
|
|
6
|
+
buildStackStableScopeId,
|
|
7
|
+
resolveStackActiveServerId,
|
|
8
|
+
} from './stable_scope_id.mjs';
|
|
9
|
+
|
|
10
|
+
test('buildStackStableScopeId is deterministic for stack + identity', () => {
|
|
11
|
+
const a = buildStackStableScopeId({ stackName: 'main', cliIdentity: 'default' });
|
|
12
|
+
const b = buildStackStableScopeId({ stackName: 'main', cliIdentity: 'default' });
|
|
13
|
+
assert.equal(a, b);
|
|
14
|
+
assert.equal(a, 'stack_main__id_default');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('buildStackStableScopeId isolates identities within the same stack', () => {
|
|
18
|
+
const a = buildStackStableScopeId({ stackName: 'dev-auth', cliIdentity: 'default' });
|
|
19
|
+
const b = buildStackStableScopeId({ stackName: 'dev-auth', cliIdentity: 'account-b' });
|
|
20
|
+
assert.notEqual(a, b);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('resolveStackActiveServerId honors explicit env override when enabled', () => {
|
|
24
|
+
const env = { HAPPIER_ACTIVE_SERVER_ID: 'custom_scope_1' };
|
|
25
|
+
const id = resolveStackActiveServerId({ env, stackName: null, cliIdentity: null });
|
|
26
|
+
assert.equal(id, 'custom_scope_1');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('applyStackActiveServerScopeEnv overwrites leaked active server id for stack scope', () => {
|
|
30
|
+
const env = {
|
|
31
|
+
HAPPIER_ACTIVE_SERVER_ID: 'stack_other__id_default',
|
|
32
|
+
};
|
|
33
|
+
const next = applyStackActiveServerScopeEnv({ env, stackName: 'dev-auth', cliIdentity: 'default' });
|
|
34
|
+
assert.equal(next.HAPPIER_ACTIVE_SERVER_ID, 'stack_dev-auth__id_default');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('applyStackActiveServerScopeEnv unsets active scope when stable scope is disabled', () => {
|
|
38
|
+
const env = {
|
|
39
|
+
HAPPIER_STACK_STACK: 'main',
|
|
40
|
+
HAPPIER_STACK_DISABLE_STABLE_SCOPE: '1',
|
|
41
|
+
HAPPIER_ACTIVE_SERVER_ID: 'stack_main__id_default',
|
|
42
|
+
};
|
|
43
|
+
const next = applyStackActiveServerScopeEnv({ env, stackName: 'main', cliIdentity: 'default' });
|
|
44
|
+
assert.equal(next.HAPPIER_ACTIVE_SERVER_ID, undefined);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('applyStackActiveServerScopeEnv sets generated stable scope id by default', () => {
|
|
48
|
+
const env = { HAPPIER_STACK_STACK: 'feature-123' };
|
|
49
|
+
const next = applyStackActiveServerScopeEnv({ env, stackName: 'feature-123', cliIdentity: 'account-b' });
|
|
50
|
+
assert.equal(next.HAPPIER_ACTIVE_SERVER_ID, 'stack_feature-123__id_account-b');
|
|
51
|
+
});
|