@happier-dev/stack 0.1.0-preview.74.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +501 -0
- package/bin/hstack.mjs +348 -0
- package/docs/codex-mcp-resume.md +129 -0
- package/docs/edison.md +74 -0
- package/docs/forking-and-branding.md +189 -0
- package/docs/happy-development.md +22 -0
- package/docs/isolated-linux-vm.md +243 -0
- package/docs/menubar.md +244 -0
- package/docs/mobile-ios.md +322 -0
- package/docs/monorepo-migration.md +20 -0
- package/docs/paths-and-env.md +154 -0
- package/docs/remote-access.md +43 -0
- package/docs/server-flavors.md +147 -0
- package/docs/stacks.md +330 -0
- package/docs/tauri.md +60 -0
- package/docs/worktrees-and-forks.md +133 -0
- package/extras/swiftbar/auth-login.sh +29 -0
- package/extras/swiftbar/git-cache-refresh.sh +122 -0
- package/extras/swiftbar/hstack-term.sh +133 -0
- package/extras/swiftbar/hstack.5s.sh +296 -0
- package/extras/swiftbar/hstack.sh +35 -0
- package/extras/swiftbar/icons/happy-green.png +0 -0
- package/extras/swiftbar/icons/happy-orange.png +0 -0
- package/extras/swiftbar/icons/happy-red.png +0 -0
- package/extras/swiftbar/icons/logo-white.png +0 -0
- package/extras/swiftbar/install.sh +265 -0
- package/extras/swiftbar/lib/git.sh +629 -0
- package/extras/swiftbar/lib/icons.sh +92 -0
- package/extras/swiftbar/lib/render.sh +999 -0
- package/extras/swiftbar/lib/system.sh +244 -0
- package/extras/swiftbar/lib/utils.sh +717 -0
- package/extras/swiftbar/set-interval.sh +65 -0
- package/extras/swiftbar/set-server-flavor.sh +61 -0
- package/extras/swiftbar/wt-pr.sh +140 -0
- package/node_modules/@happier-dev/cli-common/README.md +6 -0
- package/node_modules/@happier-dev/cli-common/dist/index.d.ts +4 -0
- package/node_modules/@happier-dev/cli-common/dist/index.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/index.js +4 -0
- package/node_modules/@happier-dev/cli-common/dist/index.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/links/index.d.ts +18 -0
- package/node_modules/@happier-dev/cli-common/dist/links/index.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/links/index.js +25 -0
- package/node_modules/@happier-dev/cli-common/dist/links/index.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/links.d.ts +2 -0
- package/node_modules/@happier-dev/cli-common/dist/links.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/links.js +2 -0
- package/node_modules/@happier-dev/cli-common/dist/links.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/update/index.d.ts +67 -0
- package/node_modules/@happier-dev/cli-common/dist/update/index.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/update/index.js +259 -0
- package/node_modules/@happier-dev/cli-common/dist/update/index.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/workspaces/index.d.ts +17 -0
- package/node_modules/@happier-dev/cli-common/dist/workspaces/index.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/workspaces/index.js +80 -0
- package/node_modules/@happier-dev/cli-common/dist/workspaces/index.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/package.json +26 -0
- package/package.json +77 -0
- package/scripts/auth.mjs +1829 -0
- package/scripts/auth_copy_from_pglite_lock_in_use.integration.test.mjs +90 -0
- package/scripts/auth_copy_from_runCapture.integration.test.mjs +447 -0
- package/scripts/auth_help_cmd.test.mjs +28 -0
- package/scripts/auth_login_flow_in_tty.test.mjs +100 -0
- package/scripts/auth_login_force_default.test.mjs +66 -0
- package/scripts/auth_login_guided_server_no_expo.test.mjs +126 -0
- package/scripts/auth_login_method_override.test.mjs +67 -0
- package/scripts/auth_login_print_includes_configure_links.test.mjs +99 -0
- package/scripts/auth_status_server_validation.integration.test.mjs +140 -0
- package/scripts/build.mjs +266 -0
- package/scripts/bundleWorkspaceDeps.mjs +38 -0
- package/scripts/bundleWorkspaceDeps.test.mjs +77 -0
- package/scripts/ci.mjs +135 -0
- package/scripts/ci.test.mjs +50 -0
- package/scripts/cli-link.mjs +57 -0
- package/scripts/completion.mjs +395 -0
- package/scripts/contrib.mjs +333 -0
- package/scripts/daemon.mjs +1160 -0
- package/scripts/daemon.status_scope.test.mjs +51 -0
- package/scripts/daemon_cmd.mjs +26 -0
- package/scripts/daemon_dist_guard.test.mjs +171 -0
- package/scripts/daemon_invalid_auth_reseed_stack_name.integration.test.mjs +608 -0
- package/scripts/daemon_server_scoped_state.test.mjs +49 -0
- package/scripts/daemon_start_verification.integration.test.mjs +296 -0
- package/scripts/dev.mjs +545 -0
- package/scripts/doctor.mjs +340 -0
- package/scripts/doctor_cmd.test.mjs +22 -0
- package/scripts/doctor_ui_index_missing.test.mjs +37 -0
- package/scripts/eas.mjs +367 -0
- package/scripts/eas_platform_parsing.test.mjs +63 -0
- package/scripts/edison.mjs +1848 -0
- package/scripts/env.mjs +149 -0
- package/scripts/env_cmd.test.mjs +118 -0
- package/scripts/exit_cleanup_kills_detached_children_on_crash.integration.test.mjs +80 -0
- package/scripts/happier.mjs +82 -0
- package/scripts/import.mjs +1327 -0
- package/scripts/init.mjs +464 -0
- package/scripts/install.mjs +550 -0
- package/scripts/lint.mjs +177 -0
- package/scripts/menubar.mjs +202 -0
- package/scripts/migrate.mjs +318 -0
- package/scripts/mobile.mjs +353 -0
- package/scripts/mobile_dev_client.mjs +87 -0
- package/scripts/monorepo.mjs +2234 -0
- package/scripts/monorepo_port.apply.integration.test.mjs +680 -0
- package/scripts/monorepo_port.conflicts.integration.test.mjs +454 -0
- package/scripts/monorepo_port.validation.integration.test.mjs +486 -0
- package/scripts/orchestrated_stack_auth_flow.test.mjs +134 -0
- package/scripts/orchestrated_stack_auth_flow_resolve_port.test.mjs +98 -0
- package/scripts/orchestrated_stack_auth_flow_webapp_url.test.mjs +119 -0
- package/scripts/pack.mjs +257 -0
- package/scripts/pack.test.mjs +68 -0
- package/scripts/pglite_lock.integration.test.mjs +152 -0
- package/scripts/provision/linux-ubuntu-e2e.sh +132 -0
- package/scripts/provision/linux-ubuntu-review-pr.sh +66 -0
- package/scripts/provision/macos-lima-happy-vm.sh +192 -0
- package/scripts/provision/macos-lima-hstack-e2e.sh +100 -0
- package/scripts/release.mjs +53 -0
- package/scripts/release_binary_smoke.integration.test.mjs +138 -0
- package/scripts/review.mjs +1752 -0
- package/scripts/review_pr.mjs +435 -0
- package/scripts/run.mjs +561 -0
- package/scripts/run_script_with_stack_env.restart_port_reuse.test.mjs +30 -0
- package/scripts/self.mjs +465 -0
- package/scripts/self_host.mjs +9 -0
- package/scripts/self_host_binary_smoke.integration.test.mjs +74 -0
- package/scripts/self_host_runtime.mjs +883 -0
- package/scripts/self_host_runtime.test.mjs +82 -0
- package/scripts/self_host_systemd.real.integration.test.mjs +367 -0
- package/scripts/server_flavor.mjs +148 -0
- package/scripts/service.mjs +868 -0
- package/scripts/service_mode_help.test.mjs +27 -0
- package/scripts/setup.mjs +1324 -0
- package/scripts/setup_non_interactive_flag.test.mjs +60 -0
- package/scripts/setup_pr.mjs +605 -0
- package/scripts/setup_pr_orchestrated_auth_flow_util_import.test.mjs +117 -0
- package/scripts/stack/command_arguments.mjs +91 -0
- package/scripts/stack/copy_auth_from_stack.mjs +111 -0
- package/scripts/stack/delegated_script_commands.mjs +92 -0
- package/scripts/stack/help_text.mjs +110 -0
- package/scripts/stack/port_reservation.mjs +74 -0
- package/scripts/stack/repo_checkout_resolution.mjs +31 -0
- package/scripts/stack/run_script_with_stack_env.mjs +634 -0
- package/scripts/stack/stack_daemon_command.mjs +219 -0
- package/scripts/stack/stack_delegated_help.mjs +81 -0
- package/scripts/stack/stack_environment.mjs +151 -0
- package/scripts/stack/stack_environment.sanitization.test.mjs +75 -0
- package/scripts/stack/stack_happier_passthrough_command.mjs +63 -0
- package/scripts/stack/stack_info_snapshot.mjs +167 -0
- package/scripts/stack/stack_mobile_install_command.mjs +61 -0
- package/scripts/stack/stack_resume_command.mjs +76 -0
- package/scripts/stack/stack_stop_command.mjs +34 -0
- package/scripts/stack/stack_workspace_command.mjs +83 -0
- package/scripts/stack/transient_repo_overrides.mjs +29 -0
- package/scripts/stack.mjs +2388 -0
- package/scripts/stack_archive_cmd.integration.test.mjs +31 -0
- package/scripts/stack_audit_fix_light_env.test.mjs +129 -0
- package/scripts/stack_background_pinned_stack_json.test.mjs +81 -0
- package/scripts/stack_copy_auth_server_scoped.test.mjs +243 -0
- package/scripts/stack_daemon_cmd.integration.test.mjs +484 -0
- package/scripts/stack_eas_help.test.mjs +72 -0
- package/scripts/stack_editor_workspace_monorepo_root.test.mjs +102 -0
- package/scripts/stack_env_cmd.test.mjs +107 -0
- package/scripts/stack_guided_login_bundle_error_parse.test.mjs +20 -0
- package/scripts/stack_guided_login_inner_invocation.test.mjs +46 -0
- package/scripts/stack_happy_cmd.integration.test.mjs +263 -0
- package/scripts/stack_info_snapshot_running_status.test.mjs +186 -0
- package/scripts/stack_interactive_monorepo_group.test.mjs +128 -0
- package/scripts/stack_monorepo_defaults.test.mjs +31 -0
- package/scripts/stack_monorepo_repo_dev_token.test.mjs +32 -0
- package/scripts/stack_monorepo_server_light_from_happy_spec.test.mjs +37 -0
- package/scripts/stack_new_name_normalize_cmd.test.mjs +38 -0
- package/scripts/stack_pr_name_normalize_cmd.test.mjs +84 -0
- package/scripts/stack_resume_cmd.integration.test.mjs +134 -0
- package/scripts/stack_server_flavors_defaults.test.mjs +64 -0
- package/scripts/stack_shorthand_cmd.integration.test.mjs +74 -0
- package/scripts/stack_stop_sweeps_legacy_infra_without_kind.integration.test.mjs +44 -0
- package/scripts/stack_stop_sweeps_when_runtime_missing.integration.test.mjs +42 -0
- package/scripts/stack_stop_sweeps_when_runtime_stale.integration.test.mjs +50 -0
- package/scripts/stack_wt_list.test.mjs +117 -0
- package/scripts/start_ui_required_default.test.mjs +63 -0
- package/scripts/stop.mjs +190 -0
- package/scripts/stopStackWithEnv_no_autosweep_when_runtime_missing.integration.test.mjs +95 -0
- package/scripts/swiftbar_git_monorepo_cmd.test.mjs +75 -0
- package/scripts/swiftbar_render_monorepo_wt_actions.integration.test.mjs +116 -0
- package/scripts/swiftbar_utils_cmd.test.mjs +92 -0
- package/scripts/swiftbar_wt_pr_backcompat.test.mjs +162 -0
- package/scripts/systemd_unit_info.test.mjs +24 -0
- package/scripts/tailscale.mjs +490 -0
- package/scripts/test_ci.mjs +36 -0
- package/scripts/test_cmd.mjs +274 -0
- package/scripts/test_cmd.test.mjs +133 -0
- package/scripts/test_integration.mjs +33 -0
- package/scripts/testkit/auth_testkit.mjs +121 -0
- package/scripts/testkit/doctor_testkit.mjs +68 -0
- package/scripts/testkit/monorepo_port_testkit.mjs +157 -0
- package/scripts/testkit/stack_archive_command_testkit.mjs +55 -0
- package/scripts/testkit/stack_new_monorepo_testkit.mjs +83 -0
- package/scripts/testkit/stack_script_command_testkit.mjs +27 -0
- package/scripts/testkit/stack_stop_sweeps_testkit.mjs +172 -0
- package/scripts/testkit/worktrees_monorepo_testkit.mjs +53 -0
- package/scripts/tools.mjs +70 -0
- package/scripts/tui.mjs +914 -0
- package/scripts/tui_stopStackForTuiExit_no_autosweep.integration.test.mjs +95 -0
- package/scripts/typecheck.mjs +178 -0
- package/scripts/ui_gateway.mjs +247 -0
- package/scripts/uninstall.mjs +179 -0
- package/scripts/utils/auth/credentials_paths.mjs +181 -0
- package/scripts/utils/auth/credentials_paths.test.mjs +187 -0
- package/scripts/utils/auth/daemon_gate.mjs +66 -0
- package/scripts/utils/auth/daemon_gate.test.mjs +116 -0
- package/scripts/utils/auth/decode_jwt_payload_unsafe.mjs +16 -0
- package/scripts/utils/auth/dev_key.mjs +163 -0
- package/scripts/utils/auth/files.mjs +56 -0
- package/scripts/utils/auth/guided_pr_auth.mjs +86 -0
- package/scripts/utils/auth/guided_stack_web_login.mjs +56 -0
- package/scripts/utils/auth/handy_master_secret.mjs +42 -0
- package/scripts/utils/auth/interactive_stack_auth.mjs +70 -0
- package/scripts/utils/auth/login_ux.mjs +105 -0
- package/scripts/utils/auth/orchestrated_stack_auth_flow.mjs +291 -0
- package/scripts/utils/auth/sources.mjs +28 -0
- package/scripts/utils/auth/stable_scope_id.mjs +91 -0
- package/scripts/utils/auth/stable_scope_id.test.mjs +51 -0
- package/scripts/utils/auth/stack_guided_login.mjs +438 -0
- package/scripts/utils/cli/arg_values.mjs +23 -0
- package/scripts/utils/cli/arg_values.test.mjs +43 -0
- package/scripts/utils/cli/args.mjs +17 -0
- package/scripts/utils/cli/cli.mjs +24 -0
- package/scripts/utils/cli/cli_registry.mjs +440 -0
- package/scripts/utils/cli/cwd_scope.mjs +158 -0
- package/scripts/utils/cli/cwd_scope.test.mjs +154 -0
- package/scripts/utils/cli/flags.mjs +17 -0
- package/scripts/utils/cli/log_forwarder.mjs +157 -0
- package/scripts/utils/cli/normalize.mjs +16 -0
- package/scripts/utils/cli/prereqs.mjs +103 -0
- package/scripts/utils/cli/prereqs.test.mjs +33 -0
- package/scripts/utils/cli/progress.mjs +141 -0
- package/scripts/utils/cli/smoke_help.mjs +44 -0
- package/scripts/utils/cli/verbosity.mjs +11 -0
- package/scripts/utils/cli/wizard.mjs +139 -0
- package/scripts/utils/cli/wizard_promptSelect.test.mjs +44 -0
- package/scripts/utils/cli/wizard_prompt_worktree_source_lazy.test.mjs +132 -0
- package/scripts/utils/cli/wizard_worktree_slug.test.mjs +33 -0
- package/scripts/utils/crypto/tokens.mjs +14 -0
- package/scripts/utils/dev/daemon.mjs +232 -0
- package/scripts/utils/dev/daemon_watch_resilience.test.mjs +224 -0
- package/scripts/utils/dev/expo_dev.buildEnv.test.mjs +35 -0
- package/scripts/utils/dev/expo_dev.mjs +478 -0
- package/scripts/utils/dev/expo_dev.test.mjs +89 -0
- package/scripts/utils/dev/expo_dev_restart_port_reservation.test.mjs +120 -0
- package/scripts/utils/dev/expo_dev_verbose_logs.test.mjs +60 -0
- package/scripts/utils/dev/server.mjs +180 -0
- package/scripts/utils/dev_auth_key.mjs +7 -0
- package/scripts/utils/edison/git_roots.mjs +30 -0
- package/scripts/utils/edison/git_roots.test.mjs +49 -0
- package/scripts/utils/env/config.mjs +52 -0
- package/scripts/utils/env/dotenv.mjs +32 -0
- package/scripts/utils/env/dotenv.test.mjs +32 -0
- package/scripts/utils/env/env.mjs +130 -0
- package/scripts/utils/env/env_file.mjs +98 -0
- package/scripts/utils/env/env_file.test.mjs +49 -0
- package/scripts/utils/env/env_local.mjs +25 -0
- package/scripts/utils/env/load_env_file.mjs +34 -0
- package/scripts/utils/env/read.mjs +30 -0
- package/scripts/utils/env/sandbox.mjs +13 -0
- package/scripts/utils/env/scrub_env.mjs +69 -0
- package/scripts/utils/env/scrub_env.test.mjs +102 -0
- package/scripts/utils/env/values.mjs +13 -0
- package/scripts/utils/expo/command.mjs +65 -0
- package/scripts/utils/expo/expo.mjs +139 -0
- package/scripts/utils/expo/expo_state_running.test.mjs +48 -0
- package/scripts/utils/expo/metro_ports.mjs +101 -0
- package/scripts/utils/expo/metro_ports.test.mjs +35 -0
- package/scripts/utils/fs/atomic_dir_swap.mjs +55 -0
- package/scripts/utils/fs/atomic_dir_swap.test.mjs +54 -0
- package/scripts/utils/fs/file_has_content.mjs +10 -0
- package/scripts/utils/fs/fs.mjs +11 -0
- package/scripts/utils/fs/json.mjs +25 -0
- package/scripts/utils/fs/ops.mjs +29 -0
- package/scripts/utils/fs/package_json.mjs +8 -0
- package/scripts/utils/fs/tail.mjs +12 -0
- package/scripts/utils/git/dev_checkout.mjs +127 -0
- package/scripts/utils/git/dev_checkout.test.mjs +115 -0
- package/scripts/utils/git/git.mjs +67 -0
- package/scripts/utils/git/parse_name_status_z.mjs +21 -0
- package/scripts/utils/git/refs.mjs +26 -0
- package/scripts/utils/git/worktrees.mjs +323 -0
- package/scripts/utils/git/worktrees_monorepo.test.mjs +60 -0
- package/scripts/utils/git/worktrees_pathstyle.test.mjs +53 -0
- package/scripts/utils/llm/assist.mjs +260 -0
- package/scripts/utils/llm/codex_exec.mjs +61 -0
- package/scripts/utils/llm/codex_exec.test.mjs +46 -0
- package/scripts/utils/llm/hstack_runner.mjs +59 -0
- package/scripts/utils/llm/tools.mjs +56 -0
- package/scripts/utils/llm/tools.test.mjs +67 -0
- package/scripts/utils/menubar/swiftbar.mjs +121 -0
- package/scripts/utils/menubar/swiftbar.test.mjs +85 -0
- package/scripts/utils/mobile/config.mjs +35 -0
- package/scripts/utils/mobile/dev_client_links.mjs +59 -0
- package/scripts/utils/mobile/identifiers.mjs +46 -0
- package/scripts/utils/mobile/identifiers.test.mjs +41 -0
- package/scripts/utils/mobile/ios_xcodeproj_patch.mjs +128 -0
- package/scripts/utils/mobile/ios_xcodeproj_patch.test.mjs +131 -0
- package/scripts/utils/net/bind_mode.mjs +39 -0
- package/scripts/utils/net/dns.mjs +10 -0
- package/scripts/utils/net/lan_ip.mjs +24 -0
- package/scripts/utils/net/ports.mjs +110 -0
- package/scripts/utils/net/tcp_forward.mjs +162 -0
- package/scripts/utils/net/url.mjs +30 -0
- package/scripts/utils/net/url.test.mjs +29 -0
- package/scripts/utils/paths/canonical_home.mjs +15 -0
- package/scripts/utils/paths/canonical_home.test.mjs +28 -0
- package/scripts/utils/paths/localhost_host.mjs +112 -0
- package/scripts/utils/paths/localhost_host.test.mjs +58 -0
- package/scripts/utils/paths/paths.mjs +302 -0
- package/scripts/utils/paths/paths_env_win32.test.mjs +36 -0
- package/scripts/utils/paths/paths_monorepo.test.mjs +58 -0
- package/scripts/utils/paths/paths_server_flavors.test.mjs +50 -0
- package/scripts/utils/paths/runtime.mjs +41 -0
- package/scripts/utils/pglite_lock.mjs +107 -0
- package/scripts/utils/proc/commands.mjs +33 -0
- package/scripts/utils/proc/exit_cleanup.mjs +57 -0
- package/scripts/utils/proc/happy_monorepo_deps.mjs +37 -0
- package/scripts/utils/proc/happy_monorepo_deps.test.mjs +89 -0
- package/scripts/utils/proc/ownership.mjs +217 -0
- package/scripts/utils/proc/ownership_killProcessGroupOwnedByStack.test.mjs +216 -0
- package/scripts/utils/proc/ownership_listPidsWithEnvNeedles.test.mjs +88 -0
- package/scripts/utils/proc/package_scripts.mjs +38 -0
- package/scripts/utils/proc/package_scripts.test.mjs +58 -0
- package/scripts/utils/proc/parallel.mjs +25 -0
- package/scripts/utils/proc/pids.mjs +11 -0
- package/scripts/utils/proc/pm.mjs +478 -0
- package/scripts/utils/proc/pm_spawn.integration.test.mjs +131 -0
- package/scripts/utils/proc/pm_stack_cache_env.test.mjs +313 -0
- package/scripts/utils/proc/proc.mjs +331 -0
- package/scripts/utils/proc/proc.test.mjs +85 -0
- package/scripts/utils/proc/terminate.mjs +69 -0
- package/scripts/utils/proc/terminate.test.mjs +54 -0
- package/scripts/utils/proc/watch.mjs +63 -0
- package/scripts/utils/review/augment_runner_integration.test.mjs +105 -0
- package/scripts/utils/review/base_ref.mjs +82 -0
- package/scripts/utils/review/base_ref.test.mjs +89 -0
- package/scripts/utils/review/chunks.mjs +55 -0
- package/scripts/utils/review/chunks.test.mjs +107 -0
- package/scripts/utils/review/detached_worktree.mjs +61 -0
- package/scripts/utils/review/detached_worktree.test.mjs +61 -0
- package/scripts/utils/review/findings.mjs +278 -0
- package/scripts/utils/review/findings.test.mjs +203 -0
- package/scripts/utils/review/head_slice.mjs +132 -0
- package/scripts/utils/review/head_slice.test.mjs +117 -0
- package/scripts/utils/review/instructions/deep.md +20 -0
- package/scripts/utils/review/prompts.mjs +279 -0
- package/scripts/utils/review/prompts.test.mjs +77 -0
- package/scripts/utils/review/run_reviewers_safe.mjs +12 -0
- package/scripts/utils/review/run_reviewers_safe.test.mjs +45 -0
- package/scripts/utils/review/runners/augment.mjs +91 -0
- package/scripts/utils/review/runners/augment.test.mjs +64 -0
- package/scripts/utils/review/runners/claude.mjs +92 -0
- package/scripts/utils/review/runners/claude.test.mjs +47 -0
- package/scripts/utils/review/runners/coderabbit.mjs +105 -0
- package/scripts/utils/review/runners/coderabbit.test.mjs +32 -0
- package/scripts/utils/review/runners/codex.mjs +129 -0
- package/scripts/utils/review/runners/codex.test.mjs +115 -0
- package/scripts/utils/review/slice_mode.mjs +20 -0
- package/scripts/utils/review/slice_mode.test.mjs +69 -0
- package/scripts/utils/review/sliced_runner.mjs +39 -0
- package/scripts/utils/review/sliced_runner.test.mjs +57 -0
- package/scripts/utils/review/slices.mjs +140 -0
- package/scripts/utils/review/slices.test.mjs +41 -0
- package/scripts/utils/review/targets.mjs +23 -0
- package/scripts/utils/review/targets.test.mjs +31 -0
- package/scripts/utils/review/tool_home_seed.mjs +106 -0
- package/scripts/utils/review/tool_home_seed.test.mjs +124 -0
- package/scripts/utils/review/uncommitted_ops.mjs +77 -0
- package/scripts/utils/review/uncommitted_ops.test.mjs +117 -0
- package/scripts/utils/sandbox/review_pr_sandbox.mjs +105 -0
- package/scripts/utils/server/apply_server_light_env_defaults.mjs +14 -0
- package/scripts/utils/server/flavor_scripts.mjs +138 -0
- package/scripts/utils/server/flavor_scripts.test.mjs +115 -0
- package/scripts/utils/server/infra/happy_server_infra.mjs +444 -0
- package/scripts/utils/server/mobile_api_url.mjs +60 -0
- package/scripts/utils/server/mobile_api_url.test.mjs +58 -0
- package/scripts/utils/server/port.mjs +55 -0
- package/scripts/utils/server/prisma_import.mjs +36 -0
- package/scripts/utils/server/prisma_import.test.mjs +78 -0
- package/scripts/utils/server/server.mjs +109 -0
- package/scripts/utils/server/ui_build_check.mjs +37 -0
- package/scripts/utils/server/ui_build_check.test.mjs +70 -0
- package/scripts/utils/server/ui_env.mjs +13 -0
- package/scripts/utils/server/ui_env.test.mjs +57 -0
- package/scripts/utils/server/urls.mjs +100 -0
- package/scripts/utils/server/validate.mjs +60 -0
- package/scripts/utils/server/validate.test.mjs +76 -0
- package/scripts/utils/service/autostart_darwin.mjs +198 -0
- package/scripts/utils/service/autostart_darwin.test.mjs +49 -0
- package/scripts/utils/service/autostart_darwin_keepalive.test.mjs +19 -0
- package/scripts/utils/stack/cli_identities.mjs +29 -0
- package/scripts/utils/stack/context.mjs +19 -0
- package/scripts/utils/stack/dirs.mjs +26 -0
- package/scripts/utils/stack/editor_workspace.mjs +126 -0
- package/scripts/utils/stack/interactive_stack_config.mjs +266 -0
- package/scripts/utils/stack/interactive_stack_config.port_validation.test.mjs +93 -0
- package/scripts/utils/stack/interactive_stack_config.remote_validation.test.mjs +122 -0
- package/scripts/utils/stack/interactive_stack_config.stack_name_validation.test.mjs +76 -0
- package/scripts/utils/stack/interactive_stack_config_testkit.mjs +18 -0
- package/scripts/utils/stack/names.mjs +27 -0
- package/scripts/utils/stack/names.test.mjs +26 -0
- package/scripts/utils/stack/pr_stack_name.mjs +16 -0
- package/scripts/utils/stack/runtime_state.mjs +88 -0
- package/scripts/utils/stack/stacks.mjs +40 -0
- package/scripts/utils/stack/startup.mjs +370 -0
- package/scripts/utils/stack/startup_server_light_dirs.test.mjs +119 -0
- package/scripts/utils/stack/startup_server_light_generate.test.mjs +20 -0
- package/scripts/utils/stack/startup_server_light_legacy.test.mjs +79 -0
- package/scripts/utils/stack/startup_server_light_testkit.mjs +106 -0
- package/scripts/utils/stack/stop.mjs +284 -0
- package/scripts/utils/stack_context.mjs +1 -0
- package/scripts/utils/stack_runtime_state.mjs +1 -0
- package/scripts/utils/stacks.mjs +1 -0
- package/scripts/utils/tailscale/ip.mjs +116 -0
- package/scripts/utils/tauri/stack_overrides.mjs +22 -0
- package/scripts/utils/test/collect_test_files.mjs +29 -0
- package/scripts/utils/time/get_today_ymd.mjs +7 -0
- package/scripts/utils/tui/cleanup.mjs +38 -0
- package/scripts/utils/ui/ansi.mjs +47 -0
- package/scripts/utils/ui/browser.mjs +31 -0
- package/scripts/utils/ui/browser.test.mjs +56 -0
- package/scripts/utils/ui/clipboard.mjs +38 -0
- package/scripts/utils/ui/layout.mjs +44 -0
- package/scripts/utils/ui/qr.mjs +17 -0
- package/scripts/utils/ui/terminal_launcher.mjs +129 -0
- package/scripts/utils/ui/text.mjs +16 -0
- package/scripts/utils/update/auto_update_notice.mjs +93 -0
- package/scripts/utils/validate.mjs +5 -0
- package/scripts/where.mjs +138 -0
- package/scripts/worktrees.mjs +2174 -0
- package/scripts/worktrees_archive_cmd.integration.test.mjs +228 -0
- package/scripts/worktrees_cursor_monorepo_root.test.mjs +23 -0
- package/scripts/worktrees_list_specs_no_recurse.test.mjs +32 -0
- package/scripts/worktrees_monorepo_testkit.test.mjs +29 -0
- package/scripts/worktrees_monorepo_use_group.test.mjs +41 -0
package/scripts/lint.mjs
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import './utils/env/env.mjs';
|
|
2
|
+
import { parseArgs } from './utils/cli/args.mjs';
|
|
3
|
+
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
4
|
+
import { getComponentDir, getRootDir } from './utils/paths/paths.mjs';
|
|
5
|
+
import { ensureDepsInstalled } from './utils/proc/pm.mjs';
|
|
6
|
+
import { pathExists } from './utils/fs/fs.mjs';
|
|
7
|
+
import { run } from './utils/proc/proc.mjs';
|
|
8
|
+
import { detectPackageManagerCmd, pickFirstScript, readPackageJsonScripts } from './utils/proc/package_scripts.mjs';
|
|
9
|
+
import { getInvokedCwd, inferComponentFromCwd } from './utils/cli/cwd_scope.mjs';
|
|
10
|
+
|
|
11
|
+
const VALID_TARGETS = ['ui', 'cli', 'server'];
|
|
12
|
+
|
|
13
|
+
function targetFromComponent(component) {
|
|
14
|
+
const c = String(component ?? '').trim();
|
|
15
|
+
if (c === 'happier-ui' || c === 'happy') return 'ui';
|
|
16
|
+
if (c === 'happier-cli' || c === 'happy-cli') return 'cli';
|
|
17
|
+
if (c === 'happier-server' || c === 'happier-server-light' || c === 'happy-server' || c === 'happy-server-light') return 'server';
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function componentFromTarget(target) {
|
|
22
|
+
const t = String(target ?? '').trim();
|
|
23
|
+
if (t === 'ui') return 'happier-ui';
|
|
24
|
+
if (t === 'cli') return 'happier-cli';
|
|
25
|
+
if (t === 'server') return 'happier-server';
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeTargetsOrThrow(rawTargets) {
|
|
30
|
+
const requested = Array.isArray(rawTargets) ? rawTargets.map((t) => String(t ?? '').trim()).filter(Boolean) : [];
|
|
31
|
+
if (!requested.length) return ['all'];
|
|
32
|
+
|
|
33
|
+
const mapped = requested
|
|
34
|
+
.map((t) => {
|
|
35
|
+
const lower = t.toLowerCase();
|
|
36
|
+
if (lower === 'all') return 'all';
|
|
37
|
+
if (VALID_TARGETS.includes(lower)) return lower;
|
|
38
|
+
return targetFromComponent(lower) ?? null;
|
|
39
|
+
})
|
|
40
|
+
.filter(Boolean);
|
|
41
|
+
|
|
42
|
+
if (!mapped.length) return ['all'];
|
|
43
|
+
return mapped;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function pickLintScript(scripts) {
|
|
47
|
+
const candidates = [
|
|
48
|
+
'lint',
|
|
49
|
+
'lint:ci',
|
|
50
|
+
'check',
|
|
51
|
+
'check:lint',
|
|
52
|
+
'eslint',
|
|
53
|
+
'eslint:check',
|
|
54
|
+
];
|
|
55
|
+
return pickFirstScript(scripts, candidates);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function main() {
|
|
59
|
+
const argv = process.argv.slice(2);
|
|
60
|
+
const { flags } = parseArgs(argv);
|
|
61
|
+
const json = wantsJson(argv, { flags });
|
|
62
|
+
|
|
63
|
+
if (wantsHelp(argv, { flags })) {
|
|
64
|
+
printResult({
|
|
65
|
+
json,
|
|
66
|
+
data: { targets: [...VALID_TARGETS, 'all'], flags: ['--json'] },
|
|
67
|
+
text: [
|
|
68
|
+
'[lint] usage:',
|
|
69
|
+
' hstack lint [ui|cli|server|all] [--json]',
|
|
70
|
+
'',
|
|
71
|
+
'targets:',
|
|
72
|
+
` ${[...VALID_TARGETS, 'all'].join(' | ')}`,
|
|
73
|
+
'',
|
|
74
|
+
'examples:',
|
|
75
|
+
' hstack lint',
|
|
76
|
+
' hstack lint ui',
|
|
77
|
+
' hstack lint ui cli',
|
|
78
|
+
'',
|
|
79
|
+
'note:',
|
|
80
|
+
' If run from inside a repo checkout/worktree and no targets are provided, defaults to the inferred app (ui/cli/server).',
|
|
81
|
+
].join('\n'),
|
|
82
|
+
});
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const rootDir = getRootDir(import.meta.url);
|
|
87
|
+
|
|
88
|
+
const positionals = argv.filter((a) => !a.startsWith('--'));
|
|
89
|
+
const inferredComponent =
|
|
90
|
+
positionals.length === 0
|
|
91
|
+
? inferComponentFromCwd({
|
|
92
|
+
rootDir,
|
|
93
|
+
invokedCwd: getInvokedCwd(process.env),
|
|
94
|
+
components: ['happier-ui', 'happier-cli', 'happier-server', 'happier-server-light', 'happy', 'happy-cli', 'happy-server', 'happy-server-light'],
|
|
95
|
+
})
|
|
96
|
+
: null;
|
|
97
|
+
if (inferredComponent) {
|
|
98
|
+
if (!(process.env.HAPPIER_STACK_REPO_DIR ?? '').toString().trim()) {
|
|
99
|
+
process.env.HAPPIER_STACK_REPO_DIR = inferredComponent.repoDir;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const inferredTarget = inferredComponent ? targetFromComponent(inferredComponent.component) : null;
|
|
104
|
+
const requested = normalizeTargetsOrThrow(positionals.length ? positionals : inferredTarget ? [inferredTarget] : ['all']);
|
|
105
|
+
const wantAll = requested.includes('all');
|
|
106
|
+
const targets = wantAll ? VALID_TARGETS : requested;
|
|
107
|
+
|
|
108
|
+
const results = [];
|
|
109
|
+
for (const target of targets) {
|
|
110
|
+
if (!VALID_TARGETS.includes(target)) {
|
|
111
|
+
results.push({ target, ok: false, skipped: false, error: `unknown target (expected one of: ${[...VALID_TARGETS, 'all'].join(', ')})` });
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const component = componentFromTarget(target);
|
|
116
|
+
const dir = getComponentDir(rootDir, component);
|
|
117
|
+
if (!(await pathExists(dir))) {
|
|
118
|
+
results.push({ target, ok: false, skipped: false, dir, error: `missing target dir: ${dir}` });
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const scripts = await readPackageJsonScripts(dir);
|
|
123
|
+
if (!scripts) {
|
|
124
|
+
results.push({ target, ok: true, skipped: true, dir, reason: 'no package.json' });
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const script = pickLintScript(scripts);
|
|
129
|
+
if (!script) {
|
|
130
|
+
results.push({ target, ok: true, skipped: true, dir, reason: 'no lint script found in package.json' });
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
await ensureDepsInstalled(dir, target);
|
|
135
|
+
const pm = await detectPackageManagerCmd(dir);
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
// eslint-disable-next-line no-console
|
|
139
|
+
console.log(`[lint] ${target}: running ${pm.name} ${script}`);
|
|
140
|
+
await run(pm.cmd, pm.argsForScript(script), { cwd: dir, env: process.env });
|
|
141
|
+
results.push({ target, ok: true, skipped: false, dir, pm: pm.name, script });
|
|
142
|
+
} catch (e) {
|
|
143
|
+
results.push({ target, ok: false, skipped: false, dir, pm: pm.name, script, error: String(e?.message ?? e) });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const ok = results.every((r) => r.ok);
|
|
148
|
+
if (json) {
|
|
149
|
+
printResult({ json, data: { ok, results } });
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const lines = ['[lint] results:'];
|
|
154
|
+
for (const r of results) {
|
|
155
|
+
if (r.ok && r.skipped) {
|
|
156
|
+
lines.push(`- ↪ ${r.target}: skipped (${r.reason})`);
|
|
157
|
+
} else if (r.ok) {
|
|
158
|
+
lines.push(`- ✅ ${r.target}: ok (${r.pm} ${r.script})`);
|
|
159
|
+
} else {
|
|
160
|
+
lines.push(`- ❌ ${r.target}: failed (${r.pm ?? 'unknown'} ${r.script ?? ''})`);
|
|
161
|
+
if (r.error) lines.push(` - ${r.error}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (!ok) {
|
|
165
|
+
lines.push('');
|
|
166
|
+
lines.push('[lint] failed');
|
|
167
|
+
}
|
|
168
|
+
printResult({ json: false, text: lines.join('\n') });
|
|
169
|
+
if (!ok) {
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
main().catch((err) => {
|
|
175
|
+
console.error('[lint] failed:', err);
|
|
176
|
+
process.exit(1);
|
|
177
|
+
});
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import './utils/env/env.mjs';
|
|
2
|
+
import { cp, mkdir } from 'node:fs/promises';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
7
|
+
import { getHappyStacksHomeDir, getRootDir } from './utils/paths/paths.mjs';
|
|
8
|
+
import { parseArgs } from './utils/cli/args.mjs';
|
|
9
|
+
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
10
|
+
import { ensureEnvLocalUpdated } from './utils/env/env_local.mjs';
|
|
11
|
+
import { isSandboxed, sandboxAllowsGlobalSideEffects } from './utils/env/sandbox.mjs';
|
|
12
|
+
import { normalizeProfile } from './utils/cli/normalize.mjs';
|
|
13
|
+
import { banner, kv, sectionTitle } from './utils/ui/layout.mjs';
|
|
14
|
+
import { cyan, dim, green } from './utils/ui/ansi.mjs';
|
|
15
|
+
import { detectSwiftbarPluginInstalled, removeSwiftbarPlugins } from './utils/menubar/swiftbar.mjs';
|
|
16
|
+
|
|
17
|
+
async function ensureSwiftbarAssets({ cliRootDir }) {
|
|
18
|
+
const homeDir = getHappyStacksHomeDir();
|
|
19
|
+
const destDir = join(homeDir, 'extras', 'swiftbar');
|
|
20
|
+
const srcDir = join(cliRootDir, 'extras', 'swiftbar');
|
|
21
|
+
|
|
22
|
+
if (!existsSync(srcDir)) {
|
|
23
|
+
throw new Error(`[menubar] missing assets at: ${srcDir}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
await mkdir(destDir, { recursive: true });
|
|
27
|
+
await cp(srcDir, destDir, {
|
|
28
|
+
recursive: true,
|
|
29
|
+
force: true,
|
|
30
|
+
filter: (p) => !p.includes('.DS_Store'),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return { homeDir, destDir };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function openSwiftbarPluginsDir() {
|
|
37
|
+
const s = 'DIR="$(defaults read com.ameba.SwiftBar PluginDirectory 2>/dev/null)"; if [[ -z "$DIR" ]]; then DIR="$HOME/Library/Application Support/SwiftBar/Plugins"; fi; open "$DIR"';
|
|
38
|
+
const res = spawnSync('bash', ['-lc', s], { stdio: 'inherit' });
|
|
39
|
+
if (res.status !== 0) {
|
|
40
|
+
process.exit(res.status ?? 1);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function sandboxPluginBasename() {
|
|
45
|
+
const sandboxDir = (process.env.HAPPIER_STACK_SANDBOX_DIR ?? '').trim();
|
|
46
|
+
if (!sandboxDir) return '';
|
|
47
|
+
const hash = createHash('sha256').update(sandboxDir).digest('hex').slice(0, 10);
|
|
48
|
+
return `hstack.sandbox-${hash}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function main() {
|
|
52
|
+
const rawArgv = process.argv.slice(2);
|
|
53
|
+
const argv = rawArgv[0] === 'menubar' ? rawArgv.slice(1) : rawArgv;
|
|
54
|
+
const helpSepIdx = argv.indexOf('--');
|
|
55
|
+
const helpScopeArgv = helpSepIdx === -1 ? argv : argv.slice(0, helpSepIdx);
|
|
56
|
+
const { flags } = parseArgs(helpScopeArgv);
|
|
57
|
+
const json = wantsJson(helpScopeArgv, { flags });
|
|
58
|
+
|
|
59
|
+
const cmd = helpScopeArgv.find((a) => a && a !== '--' && !a.startsWith('-')) || 'help';
|
|
60
|
+
const wantsHelpFlag = wantsHelp(helpScopeArgv, { flags });
|
|
61
|
+
const usageByCmd = new Map([
|
|
62
|
+
['install', 'hstack menubar install [--json]'],
|
|
63
|
+
['uninstall', 'hstack menubar uninstall [--json]'],
|
|
64
|
+
['open', 'hstack menubar open [--json]'],
|
|
65
|
+
['mode', 'hstack menubar mode <selfhost|dev> [--json]'],
|
|
66
|
+
['status', 'hstack menubar status [--json]'],
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
if (wantsHelpFlag && cmd !== 'help') {
|
|
70
|
+
const usage = usageByCmd.get(cmd);
|
|
71
|
+
if (usage) {
|
|
72
|
+
printResult({
|
|
73
|
+
json,
|
|
74
|
+
data: { ok: true, cmd, usage },
|
|
75
|
+
text: [`[menubar ${cmd}] usage:`, ` ${usage}`, '', 'see also:', ' hstack menubar --help'].join('\n'),
|
|
76
|
+
});
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (wantsHelpFlag || cmd === 'help') {
|
|
82
|
+
printResult({
|
|
83
|
+
json,
|
|
84
|
+
data: { commands: ['install', 'uninstall', 'open', 'mode', 'status'] },
|
|
85
|
+
text: [
|
|
86
|
+
banner('menubar', { subtitle: 'SwiftBar menu bar plugin (macOS).' }),
|
|
87
|
+
'',
|
|
88
|
+
sectionTitle('usage:'),
|
|
89
|
+
` ${cyan('hstack menubar')} install [--json]`,
|
|
90
|
+
` ${cyan('hstack menubar')} uninstall [--json]`,
|
|
91
|
+
` ${cyan('hstack menubar')} open [--json]`,
|
|
92
|
+
` ${cyan('hstack menubar')} mode <selfhost|dev> [--json]`,
|
|
93
|
+
` ${cyan('hstack menubar')} status [--json]`,
|
|
94
|
+
'',
|
|
95
|
+
sectionTitle('notes:'),
|
|
96
|
+
`- ${dim('Installs the SwiftBar plugin into the active SwiftBar plugin folder')}`,
|
|
97
|
+
`- ${dim('Keeps plugin source under <homeDir>/extras/swiftbar for stability')}`,
|
|
98
|
+
`- ${dim('Sandbox mode: install/uninstall are disabled by default (set HAPPIER_STACK_SANDBOX_ALLOW_GLOBAL=1 to override)')}`,
|
|
99
|
+
].join('\n'),
|
|
100
|
+
});
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const cliRootDir = getRootDir(import.meta.url);
|
|
105
|
+
|
|
106
|
+
if (cmd === 'menubar:open' || cmd === 'open') {
|
|
107
|
+
if (json) {
|
|
108
|
+
printResult({ json, data: { ok: true } });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
openSwiftbarPluginsDir();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (cmd === 'menubar:uninstall' || cmd === 'uninstall') {
|
|
116
|
+
if (isSandboxed() && !sandboxAllowsGlobalSideEffects()) {
|
|
117
|
+
printResult({ json, data: { ok: true, skipped: 'sandbox' }, text: '[menubar] uninstall skipped (sandbox mode)' });
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const patterns = isSandboxed()
|
|
121
|
+
? [`${sandboxPluginBasename()}.*.sh`]
|
|
122
|
+
: ['hstack.*.sh'];
|
|
123
|
+
const res = await removeSwiftbarPlugins({ patterns });
|
|
124
|
+
const dir = res.pluginsDir;
|
|
125
|
+
printResult({
|
|
126
|
+
json,
|
|
127
|
+
data: { ok: res.ok, pluginsDir: dir, removed: res.removed },
|
|
128
|
+
text: dir
|
|
129
|
+
? (res.ok ? `[menubar] removed plugins from ${dir}` : `[menubar] failed to remove plugins from ${dir}`)
|
|
130
|
+
: '[menubar] no plugins dir found',
|
|
131
|
+
});
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (cmd === 'status') {
|
|
136
|
+
const mode = (process.env.HAPPIER_STACK_MENUBAR_MODE ?? 'dev').trim() || 'dev';
|
|
137
|
+
const swift = await detectSwiftbarPluginInstalled();
|
|
138
|
+
printResult({
|
|
139
|
+
json,
|
|
140
|
+
data: { ok: true, mode, pluginsDir: swift.pluginsDir, installed: swift.installed },
|
|
141
|
+
text: [
|
|
142
|
+
sectionTitle('Menubar'),
|
|
143
|
+
`- ${kv('mode:', cyan(mode))}`,
|
|
144
|
+
`- ${kv('swiftbar plugin:', swift.installed ? green('installed') : dim('not installed'))}`,
|
|
145
|
+
swift.pluginsDir ? `- ${kv('plugins dir:', swift.pluginsDir)}` : null,
|
|
146
|
+
].filter(Boolean).join('\n'),
|
|
147
|
+
});
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (cmd === 'mode') {
|
|
152
|
+
const positionals = argv.filter((a) => !a.startsWith('--'));
|
|
153
|
+
const raw = positionals[1] ?? '';
|
|
154
|
+
const mode = normalizeProfile(raw);
|
|
155
|
+
if (!mode) {
|
|
156
|
+
throw new Error('[menubar] usage: hstack menubar mode <selfhost|dev> [--json]');
|
|
157
|
+
}
|
|
158
|
+
await ensureEnvLocalUpdated({
|
|
159
|
+
rootDir: cliRootDir,
|
|
160
|
+
updates: [
|
|
161
|
+
{ key: 'HAPPIER_STACK_MENUBAR_MODE', value: mode },
|
|
162
|
+
],
|
|
163
|
+
});
|
|
164
|
+
printResult({ json, data: { ok: true, mode }, text: `[menubar] mode set: ${mode}` });
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (cmd === 'menubar:install' || cmd === 'install') {
|
|
169
|
+
if (isSandboxed() && !sandboxAllowsGlobalSideEffects()) {
|
|
170
|
+
throw new Error(
|
|
171
|
+
'[menubar] install is disabled in sandbox mode.\n' +
|
|
172
|
+
'Reason: SwiftBar plugin installation writes to a global user folder.\n' +
|
|
173
|
+
'If you really want this, set: HAPPIER_STACK_SANDBOX_ALLOW_GLOBAL=1'
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
const { destDir } = await ensureSwiftbarAssets({ cliRootDir });
|
|
177
|
+
const installer = join(destDir, 'install.sh');
|
|
178
|
+
const env = {
|
|
179
|
+
...process.env,
|
|
180
|
+
HAPPIER_STACK_HOME_DIR: getHappyStacksHomeDir(),
|
|
181
|
+
...(isSandboxed()
|
|
182
|
+
? {
|
|
183
|
+
HAPPIER_STACK_SWIFTBAR_PLUGIN_BASENAME: sandboxPluginBasename(),
|
|
184
|
+
HAPPIER_STACK_SWIFTBAR_PLUGIN_WRAPPER: '1',
|
|
185
|
+
}
|
|
186
|
+
: {}),
|
|
187
|
+
};
|
|
188
|
+
const res = spawnSync('bash', [installer, '--force'], { stdio: 'inherit', env });
|
|
189
|
+
if (res.status !== 0) {
|
|
190
|
+
process.exit(res.status ?? 1);
|
|
191
|
+
}
|
|
192
|
+
printResult({ json, data: { ok: true }, text: '[menubar] installed' });
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
throw new Error(`[menubar] unknown command: ${cmd}`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
main().catch((err) => {
|
|
200
|
+
console.error('[menubar] failed:', err);
|
|
201
|
+
process.exit(1);
|
|
202
|
+
});
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import './utils/env/env.mjs';
|
|
2
|
+
import { cp, mkdir, readFile } from 'node:fs/promises';
|
|
3
|
+
import { basename, join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { parseArgs } from './utils/cli/args.mjs';
|
|
6
|
+
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
7
|
+
import { ensureEnvFileUpdated } from './utils/env/env_file.mjs';
|
|
8
|
+
import { readEnvObjectFromFile } from './utils/env/read.mjs';
|
|
9
|
+
import { getComponentDir, getRootDir, resolveStackEnvPath } from './utils/paths/paths.mjs';
|
|
10
|
+
import { ensureDepsInstalled } from './utils/proc/pm.mjs';
|
|
11
|
+
import { ensureHappyServerManagedInfra, applyHappyServerMigrations } from './utils/server/infra/happy_server_infra.mjs';
|
|
12
|
+
import { runCapture } from './utils/proc/proc.mjs';
|
|
13
|
+
import { pickNextFreeTcpPort } from './utils/net/ports.mjs';
|
|
14
|
+
import { getEnvValue } from './utils/env/values.mjs';
|
|
15
|
+
import { importPrismaClientFromNodeModules } from './utils/server/prisma_import.mjs';
|
|
16
|
+
|
|
17
|
+
function usage() {
|
|
18
|
+
return [
|
|
19
|
+
'[migrate] usage:',
|
|
20
|
+
' hstack migrate light-to-server --from-stack=<name> --to-stack=<name> [--include-files] [--force] [--json]',
|
|
21
|
+
'',
|
|
22
|
+
'Notes:',
|
|
23
|
+
'- This migrates chat data from happier-server-light (PG_Light via embedded PGlite) to happier-server (Docker Postgres).',
|
|
24
|
+
'- It preserves IDs, so existing session URLs keep working on the new server.',
|
|
25
|
+
'- If --include-files is set, it mirrors server-light local files into Minio (S3) in the target stack.',
|
|
26
|
+
].join('\n');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const readEnvObject = readEnvObjectFromFile;
|
|
30
|
+
|
|
31
|
+
function normalizePgliteServerConnToDatabaseUrl(rawConn) {
|
|
32
|
+
const raw = String(rawConn ?? '').trim();
|
|
33
|
+
if (!raw) throw new Error('[migrate] invalid PGlite server connection string (empty)');
|
|
34
|
+
const url = (() => {
|
|
35
|
+
try {
|
|
36
|
+
return new URL(raw);
|
|
37
|
+
} catch {
|
|
38
|
+
// pglite-socket can return "127.0.0.1:NNNN" (host:port). Convert to a postgres URL.
|
|
39
|
+
return new URL(`postgresql://postgres@${raw}/postgres?sslmode=disable`);
|
|
40
|
+
}
|
|
41
|
+
})();
|
|
42
|
+
url.searchParams.set('connection_limit', '1');
|
|
43
|
+
return url.toString();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function ensureTargetSecretMatchesSource({ sourceSecretPath, targetSecretPath }) {
|
|
47
|
+
try {
|
|
48
|
+
const src = (await readFile(sourceSecretPath, 'utf-8')).trim();
|
|
49
|
+
if (!src) return null;
|
|
50
|
+
await mkdir(join(targetSecretPath, '..'), { recursive: true }).catch(() => {});
|
|
51
|
+
const { rename, writeFile } = await import('node:fs/promises');
|
|
52
|
+
// Write with a trailing newline, via atomic replace.
|
|
53
|
+
const tmp = join(join(targetSecretPath, '..'), `.handy-master-secret.${Date.now()}.tmp`);
|
|
54
|
+
await writeFile(tmp, src + '\n', { encoding: 'utf-8', mode: 0o600 });
|
|
55
|
+
await rename(tmp, targetSecretPath);
|
|
56
|
+
return src;
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function migrateLightToServer({ rootDir, fromStack, toStack, includeFiles, force, json }) {
|
|
63
|
+
const from = resolveStackEnvPath(fromStack);
|
|
64
|
+
const to = resolveStackEnvPath(toStack);
|
|
65
|
+
|
|
66
|
+
const fromEnv = await readEnvObject(from.envPath);
|
|
67
|
+
const toEnv = await readEnvObject(to.envPath);
|
|
68
|
+
|
|
69
|
+
const fromFlavor = getEnvValue(fromEnv, 'HAPPIER_STACK_SERVER_COMPONENT') || 'happier-server-light';
|
|
70
|
+
const toFlavor = getEnvValue(toEnv, 'HAPPIER_STACK_SERVER_COMPONENT') || 'happier-server-light';
|
|
71
|
+
|
|
72
|
+
if (fromFlavor !== 'happier-server-light') {
|
|
73
|
+
throw new Error(`[migrate] from-stack must use happier-server-light (got: ${fromFlavor})`);
|
|
74
|
+
}
|
|
75
|
+
if (toFlavor !== 'happier-server') {
|
|
76
|
+
throw new Error(`[migrate] to-stack must use happier-server (got: ${toFlavor})`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const fromDataDir = getEnvValue(fromEnv, 'HAPPIER_SERVER_LIGHT_DATA_DIR') || join(from.baseDir, 'server-light');
|
|
80
|
+
const fromFilesDir = getEnvValue(fromEnv, 'HAPPIER_SERVER_LIGHT_FILES_DIR') || join(fromDataDir, 'files');
|
|
81
|
+
const fromDbDir = getEnvValue(fromEnv, 'HAPPIER_SERVER_LIGHT_DB_DIR') || join(fromDataDir, 'pglite');
|
|
82
|
+
|
|
83
|
+
const toPortRaw = getEnvValue(toEnv, 'HAPPIER_STACK_SERVER_PORT');
|
|
84
|
+
let toPort = toPortRaw ? Number(toPortRaw) : NaN;
|
|
85
|
+
const toEphemeral = !toPortRaw;
|
|
86
|
+
if (!Number.isFinite(toPort) || toPort <= 0) {
|
|
87
|
+
// Ephemeral-port stacks don't pin ports in env. Pick a free port for this one-off migration run.
|
|
88
|
+
toPort = await pickNextFreeTcpPort(3005);
|
|
89
|
+
if (!json) {
|
|
90
|
+
// eslint-disable-next-line no-console
|
|
91
|
+
console.log(`[migrate] to-stack has no pinned port; using ephemeral port ${toPort} for this migration run`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Ensure target secret is the same as source so auth tokens remain valid after migration.
|
|
96
|
+
const sourceSecretPath = join(fromDataDir, 'handy-master-secret.txt');
|
|
97
|
+
const targetSecretPath = getEnvValue(toEnv, 'HAPPIER_STACK_HANDY_MASTER_SECRET_FILE') || join(to.baseDir, 'happier-server', 'handy-master-secret.txt');
|
|
98
|
+
await ensureTargetSecretMatchesSource({ sourceSecretPath, targetSecretPath });
|
|
99
|
+
await ensureEnvFileUpdated({
|
|
100
|
+
envPath: to.envPath,
|
|
101
|
+
updates: [{ key: 'HAPPIER_STACK_HANDY_MASTER_SECRET_FILE', value: targetSecretPath }],
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Resolve component dirs (prefer stack-pinned dirs).
|
|
105
|
+
const lightDir = getComponentDir(rootDir, 'happier-server-light', fromEnv);
|
|
106
|
+
const fullDir = getComponentDir(rootDir, 'happier-server', toEnv);
|
|
107
|
+
|
|
108
|
+
await ensureDepsInstalled(lightDir, 'happier-server-light');
|
|
109
|
+
await ensureDepsInstalled(fullDir, 'happier-server');
|
|
110
|
+
|
|
111
|
+
// Bring up infra and ensure env vars are present.
|
|
112
|
+
const infra = await ensureHappyServerManagedInfra({
|
|
113
|
+
stackName: toStack,
|
|
114
|
+
baseDir: to.baseDir,
|
|
115
|
+
serverPort: toPort,
|
|
116
|
+
publicServerUrl: `http://127.0.0.1:${toPort}`,
|
|
117
|
+
envPath: to.envPath,
|
|
118
|
+
env: {
|
|
119
|
+
...process.env,
|
|
120
|
+
...(toEphemeral ? { HAPPIER_STACK_EPHEMERAL_PORTS: '1' } : {}),
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
await applyHappyServerMigrations({ serverDir: fullDir, env: { ...process.env, ...infra.env } });
|
|
124
|
+
|
|
125
|
+
// Snapshot the embedded DB dir so migration is consistent even if the source server is running.
|
|
126
|
+
const snapshotDir = join(to.baseDir, 'migrations');
|
|
127
|
+
await mkdir(snapshotDir, { recursive: true });
|
|
128
|
+
const snapshotDbDir = join(snapshotDir, `pglite.${basename(fromDbDir)}.${Date.now()}`);
|
|
129
|
+
await cp(fromDbDir, snapshotDbDir, { recursive: true, force: true });
|
|
130
|
+
|
|
131
|
+
// Ensure schema is applied on the snapshot DB (idempotent).
|
|
132
|
+
await runCapture('yarn', ['-s', 'migrate:light:deploy'], {
|
|
133
|
+
cwd: lightDir,
|
|
134
|
+
env: {
|
|
135
|
+
...process.env,
|
|
136
|
+
HAPPIER_SERVER_LIGHT_DATA_DIR: fromDataDir,
|
|
137
|
+
HAPPIER_SERVER_LIGHT_FILES_DIR: fromFilesDir,
|
|
138
|
+
HAPPIER_SERVER_LIGHT_DB_DIR: snapshotDbDir,
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Read from the snapshot via a temporary pglite socket and the standard Prisma client.
|
|
143
|
+
const { PGlite } = await import('@electric-sql/pglite');
|
|
144
|
+
const { PGLiteSocketServer } = await import('@electric-sql/pglite-socket');
|
|
145
|
+
const pglite = new PGlite(snapshotDbDir);
|
|
146
|
+
await pglite.waitReady;
|
|
147
|
+
const pgliteServer = new PGLiteSocketServer({ db: pglite, host: '127.0.0.1', port: 0 });
|
|
148
|
+
await pgliteServer.start();
|
|
149
|
+
const snapshotDbUrl = normalizePgliteServerConnToDatabaseUrl(pgliteServer.getServerConn());
|
|
150
|
+
|
|
151
|
+
const SourcePrismaClient = await importPrismaClientFromNodeModules({ dir: lightDir });
|
|
152
|
+
const TargetPrismaClient = await importPrismaClientFromNodeModules({ dir: fullDir });
|
|
153
|
+
|
|
154
|
+
const sourceDb = new SourcePrismaClient({ datasources: { db: { url: snapshotDbUrl } } });
|
|
155
|
+
const targetDb = new TargetPrismaClient({ datasources: { db: { url: infra.env.DATABASE_URL } } });
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
// Fail-fast unless target is empty (keeps this safe).
|
|
159
|
+
const existingSessions = await targetDb.session.count();
|
|
160
|
+
const existingMessages = await targetDb.sessionMessage.count();
|
|
161
|
+
if (!force && (existingSessions > 0 || existingMessages > 0)) {
|
|
162
|
+
throw new Error(
|
|
163
|
+
`[migrate] target database is not empty (sessions=${existingSessions}, messages=${existingMessages}).\n` +
|
|
164
|
+
`Pass --force to attempt a merge (skipDuplicates), or migrate into a fresh stack.`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Core entities
|
|
169
|
+
const accounts = await sourceDb.account.findMany();
|
|
170
|
+
if (accounts.length) {
|
|
171
|
+
await targetDb.account.createMany({ data: accounts, skipDuplicates: true });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const machines = await sourceDb.machine.findMany();
|
|
175
|
+
if (machines.length) {
|
|
176
|
+
await targetDb.machine.createMany({ data: machines, skipDuplicates: true });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const accessKeys = await sourceDb.accessKey.findMany();
|
|
180
|
+
if (accessKeys.length) {
|
|
181
|
+
await targetDb.accessKey.createMany({ data: accessKeys, skipDuplicates: true });
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const sessions = await sourceDb.session.findMany();
|
|
185
|
+
if (sessions.length) {
|
|
186
|
+
await targetDb.session.createMany({ data: sessions, skipDuplicates: true });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Messages: stream in batches to avoid high memory.
|
|
190
|
+
let migrated = 0;
|
|
191
|
+
const batchSize = 1000;
|
|
192
|
+
let cursor = null;
|
|
193
|
+
while (true) {
|
|
194
|
+
// eslint-disable-next-line no-await-in-loop
|
|
195
|
+
const page = await sourceDb.sessionMessage.findMany({
|
|
196
|
+
...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
|
|
197
|
+
orderBy: { id: 'asc' },
|
|
198
|
+
take: batchSize,
|
|
199
|
+
});
|
|
200
|
+
if (!page.length) break;
|
|
201
|
+
cursor = page[page.length - 1].id;
|
|
202
|
+
// eslint-disable-next-line no-await-in-loop
|
|
203
|
+
await targetDb.sessionMessage.createMany({ data: page, skipDuplicates: true });
|
|
204
|
+
migrated += page.length;
|
|
205
|
+
// eslint-disable-next-line no-console
|
|
206
|
+
if (!json && migrated % (batchSize * 20) === 0) console.log(`[migrate] migrated ${migrated} messages...`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Pending queue (small)
|
|
210
|
+
const pending = await sourceDb.sessionPendingMessage.findMany();
|
|
211
|
+
if (pending.length) {
|
|
212
|
+
await targetDb.sessionPendingMessage.createMany({ data: pending, skipDuplicates: true });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (includeFiles) {
|
|
216
|
+
// Mirror server-light local files (public/*) into Minio bucket root.
|
|
217
|
+
// This assumes server-light stored public files under HAPPIER_SERVER_LIGHT_FILES_DIR/public/...
|
|
218
|
+
// (Matches happier-server Minio object keys).
|
|
219
|
+
const { composePath, projectName } = infra;
|
|
220
|
+
await runCapture('docker', [
|
|
221
|
+
'compose',
|
|
222
|
+
'-f',
|
|
223
|
+
composePath,
|
|
224
|
+
'-p',
|
|
225
|
+
projectName,
|
|
226
|
+
'run',
|
|
227
|
+
'--rm',
|
|
228
|
+
'-T',
|
|
229
|
+
'-v',
|
|
230
|
+
`${fromFilesDir}:/src:ro`,
|
|
231
|
+
'minio-init',
|
|
232
|
+
'sh',
|
|
233
|
+
'-lc',
|
|
234
|
+
[
|
|
235
|
+
`mc alias set local http://minio:9000 ${infra.env.S3_ACCESS_KEY} ${infra.env.S3_SECRET_KEY}`,
|
|
236
|
+
`mc mirror --overwrite /src local/${infra.env.S3_BUCKET}`,
|
|
237
|
+
].join(' && '),
|
|
238
|
+
]);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
printResult({
|
|
242
|
+
json,
|
|
243
|
+
data: {
|
|
244
|
+
ok: true,
|
|
245
|
+
fromStack,
|
|
246
|
+
toStack,
|
|
247
|
+
snapshotDbDir,
|
|
248
|
+
migrated: { accounts: accounts.length, sessions: sessions.length, messages: migrated, machines: machines.length, accessKeys: accessKeys.length },
|
|
249
|
+
filesMirrored: Boolean(includeFiles),
|
|
250
|
+
},
|
|
251
|
+
text: [
|
|
252
|
+
`[migrate] ok`,
|
|
253
|
+
`[migrate] from: ${fromStack} (${fromFlavor})`,
|
|
254
|
+
`[migrate] to: ${toStack} (${toFlavor})`,
|
|
255
|
+
`[migrate] pglite snapshot dir: ${snapshotDbDir}`,
|
|
256
|
+
`[migrate] messages: ${migrated}`,
|
|
257
|
+
includeFiles ? `[migrate] files: mirrored from ${fromFilesDir} -> minio bucket ${infra.env.S3_BUCKET}` : `[migrate] files: skipped`,
|
|
258
|
+
].join('\n'),
|
|
259
|
+
});
|
|
260
|
+
} finally {
|
|
261
|
+
try {
|
|
262
|
+
await sourceDb.$disconnect();
|
|
263
|
+
} catch {
|
|
264
|
+
// ignore
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
await targetDb.$disconnect();
|
|
268
|
+
} catch {
|
|
269
|
+
// ignore
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
await pgliteServer.stop();
|
|
273
|
+
} catch {
|
|
274
|
+
// ignore
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
await pglite.close();
|
|
278
|
+
} catch {
|
|
279
|
+
// ignore
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async function main() {
|
|
285
|
+
const argv = process.argv.slice(2);
|
|
286
|
+
const { flags, kv } = parseArgs(argv);
|
|
287
|
+
const json = wantsJson(argv, { flags });
|
|
288
|
+
if (wantsHelp(argv, { flags })) {
|
|
289
|
+
printResult({ json, data: { ok: true }, text: usage() });
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const cmd = argv.find((a) => !a.startsWith('--')) ?? '';
|
|
294
|
+
if (!cmd) {
|
|
295
|
+
throw new Error(usage());
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (cmd !== 'light-to-server') {
|
|
299
|
+
throw new Error(`[migrate] unknown subcommand: ${cmd}\n\n${usage()}`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const fromStack = (kv.get('--from-stack') ?? 'main').trim();
|
|
303
|
+
const toStack = (kv.get('--to-stack') ?? '').trim();
|
|
304
|
+
const includeFiles = flags.has('--include-files') || (kv.get('--include-files') ?? '').trim() === '1';
|
|
305
|
+
const force = flags.has('--force');
|
|
306
|
+
if (!toStack) {
|
|
307
|
+
throw new Error('[migrate] --to-stack is required');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const rootDir = getRootDir(import.meta.url);
|
|
311
|
+
await migrateLightToServer({ rootDir, fromStack, toStack, includeFiles, force, json });
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
main().catch((err) => {
|
|
315
|
+
// eslint-disable-next-line no-console
|
|
316
|
+
console.error(err);
|
|
317
|
+
process.exit(1);
|
|
318
|
+
});
|